Omni Session Metrics¶
EmiliaVision's Omni Session Metrics module calculates 37 service quality metrics per dining session, combining video annotations, POS data, and real-time AI suggestions into a unified quality profile.
Architecture¶
flowchart TD
A["Shift Map Storage<br/>shift_map_storage"] --> B["Metrics Engine<br/>metrics.py"]
C["POS Sessions v1<br/>pos_sessions_storage"] --> B
D["POS Sessions v2<br/>pos_sessions_v2_storage"] --> B
E["EchoBase Events<br/>metadata.tables[].echobase_events"] --> B
B --> F["Enriched Shift<br/>version N+1"]
F --> G["ClickHouse<br/>shift_map_storage"] The enrichment CLI reads the latest shift version, calculates metrics for every session, and inserts a new version with session.metrics populated. A SHA-256 fingerprint prevents unnecessary writes when metrics haven't changed.
POS v1 and POS v2 Inputs¶
The metrics engine supports both POS generations:
- Legacy POS v1 uses
session.pos_correlation.pos_session_idto readechobasematrix_{env}_pos_sessions_storage. - POS Sessions v2 uses
session.pos_correlation.pos_uuidto read the latest non-deleted row forpos_session_uuidfromechobasematrix_{env}_pos_sessions_v2_storage.
POS v2 separates metadata.items (final financial state for totals, revenue, and item counts) from metadata.order_events (raw operational evidence, including non-order events). The backend selects positive orders using the canonical eligibility policy.
Changed in version 2.6.0
POS v2 no longer falls back to financial items when operational evidence is missing. Category inheritance remains backend-owned and never copies financial timestamps or prices. Legacy POS v1 retains its item timeline, with invalid/pre-video chronology rejected before rounding.
Category-dependent metrics read POS v2 final item categories and may apply them to operational order-event items at read time. If POS v2 item categories are backfilled or corrected, run enrichment again so affected sessions receive new metric fingerprints and updated shift versions.
Service Timing Contract¶
Changed in version 2.6.0
The producer emits numeric service_timing_contract_version: 1 and canonical navigation evidence. An application version string is not proof of compatibility. Missing, boolean, string, or unknown contract revisions must not enable local POS recalculation in clients.
The existing duration fields remain nullable integer seconds. The new service_timing_evidence object accompanies them:
| Field | Shape | Meaning |
|---|---|---|
order | interval or null | Start-session → first positive POS order (M-2) |
drink | interval or null | First drink order → drinks-arrived (M-10) |
food | interval or null | First main-dish order → food-arrived (M-11) |
cleaning | interval or null | End-session → bounded table-cleaned (M-6), independent of POS |
orders | array | Backend-selected positive orders for timeline display; empty when unavailable |
An interval contains finite non-negative start_offset_seconds and end_offset_seconds, relative to the original shift video, preserving fractions. It is null when the metric is unavailable or contradictory. An order contains offset_seconds, event_ids (source references, possibly empty), items (name, nullable category, nullable quantity, nullable line price), and nullable total. A total is available only when all included line prices are known. This operational total is not the final bill total.
Player validates the version/types, renders the supplied values and markers, and seeks to the supplied offsets. It must not select operational events, resolve category conflicts, reconstruct anchors from rounded durations, or substitute manual/generic timing formulas. Raw POS panels can still display financial context without treating it as timing evidence. SLA delay labels use the same canonical category anchor and order items.
Release and bounded reenrichment¶
This contract needs no DDL: it is additive JSON in the existing Shift Map metadata. Deploying code does not update an archived session or report.
- Review the backend change, run producer and consumer contract tests, and publish the metrics image from the reviewed revision. Verify the actual published image, not just local source or a successful build job.
- Inventory the permitted shifts, dates, and locations privately. Dry-run by
--video-task-id, then write and reread a pilot only with operator approval. Continue in resumable batches over that closed inventory; preserve source annotations/POS rows and append new Shift Map versions normally. - Review all three
infra/k8s/metrics/image pins and deploy explicitly using the documented Kubernetes context. The existing jobs have no date filter: changing the metrics version changes every processed fingerprint and can reenrich all selected history even without--force. Do not apply the new pins before approving that scope or an appropriately bounded job policy. - Validate Player, Reports, Workspace, and SLA on the same reenriched sessions before declaring operational completion. Reader-specific compatibility work and immutable report publication are separate steps, not automatic effects of a metrics image release.
- Rollback may restore a previous reviewed image pin. It does not erase appended data versions or authorize overwriting report snapshots.
Metric Catalog¶
All metrics live at metadata.tables[].sessions[].metrics in the shift_map_storage JSON metadata.
| ID | Field | Type | Source | Details |
|---|---|---|---|---|
| M-1 | manager_touch_count | int | Annotations | Annotation Metrics |
| M-2 | time_to_first_pos_order_seconds | int | null | Annotations + POS | POS Metrics |
| M-3 | time_from_session_start_to_drink_arrival_seconds | int | null | Annotations | Annotation Metrics |
| M-4 | waiter_touch_count | int | Annotations | Annotation Metrics |
| M-5 | hand_raise_count | int | Annotations | Annotation Metrics |
| M-6 | table_cleaned_time_seconds | int | null | Annotations | Annotation Metrics |
| M-7 | food_delivery_from_first_pos_item_seconds | int | null | Annotations + POS | POS Metrics |
| M-8 | first_waiter_contact_time_seconds | int | null | Annotations | Annotation Metrics |
| M-9 | drink_delivery_from_first_pos_item_seconds | int | null | Annotations + POS | POS Metrics |
| M-10 | drink_delivery_from_first_drink_order_seconds | int | null | Annotations + POS | POS Metrics |
| M-11 | food_delivery_from_first_main_dish_order_seconds | int | null | Annotations + POS | POS Metrics |
| M-12 | drink_delivery_from_order_taken_seconds | int | null | Annotations | Annotation Metrics |
| M-13 | food_delivery_from_order_taken_seconds | int | null | Annotations | Annotation Metrics |
| M-14 | bill_payment_to_end_session_seconds | int | null | Annotations | Annotation Metrics |
| M-25 | menu_delivery_time_seconds | int | null | Annotations | Annotation Metrics |
| M-26 | bill_closing_time_seconds | int | null | Annotations | Annotation Metrics |
| M-27 | courtesy_delivery_from_session_start_seconds | int | null | Annotations | Annotation Metrics |
| M-15 | drink_item_count | int | POS | POS Metrics |
| M-16 | main_dish_item_count | int | POS | POS Metrics |
| M-17 | dessert_item_count | int | POS | POS Metrics |
| M-18 | pos_ticket_subtotal | float | null | POS | POS Metrics |
| M-19 | pos_ticket_avg | float | null | POS | POS Metrics |
| M-20 | pos_drink_ticket_total | float | null | POS | POS Metrics |
| M-21 | pos_dessert_ticket_total | float | null | POS | POS Metrics |
| M-22 | pos_server_name_open | str | null | POS | POS Metrics |
| M-23 | pos_server_name_close | str | null | POS | POS Metrics |
| M-24 | pos_top_seller | str | null | POS | POS Metrics |
| — | drink_suggestion_count | int | EchoBase Events | Compliance |
| — | drink_suggestion_compliance_pct | float | null | EchoBase Events | Compliance |
| — | dessert_suggestion_count | int | EchoBase Events | Compliance |
| — | dessert_suggestion_compliance_pct | float | null | EchoBase Events | Compliance |
| — | dessert_compliance_conversion | bool | null | EchoBase + POS | Compliance |
| — | dessert_compliance_conversion_items | int | EchoBase + POS | Compliance |
| — | dessert_compliance_conversion_revenue | float | null | EchoBase + POS | Compliance |
| — | dessert_compliance_conversion_delta_seconds | int | null | EchoBase + POS | Compliance |
| — | drink_compliance_conversion | bool | null | EchoBase + POS | Compliance |
| — | drink_compliance_conversion_count | int | EchoBase + POS | Compliance |
| — | drink_compliance_compliant_clusters | int | EchoBase + POS | Compliance |
| — | drink_compliance_conversion_items | list[int] | EchoBase + POS | Compliance |
| — | drink_compliance_conversion_revenue | list[float | null] | EchoBase + POS | Compliance |
| — | drink_compliance_conversion_deltas | list[int] | EchoBase + POS | Compliance |
| — | delayed_food_items | list[str] | POS + Metrics | SLA Detection |
| — | delayed_drink_items | list[str] | POS + Metrics | SLA Detection |
Metadata Fields¶
Every enriched session also includes:
| Field | Type | Description |
|---|---|---|
service_timing_contract_version | number | Explicit service timing contract revision; currently 1 |
service_timing_evidence | object | Nullable canonical intervals and normalized positive order markers |
omni_metrics_version | str | Version of the Omni Metrics module that calculated these metrics |
fingerprint | str | 8-char SHA-256 hash of all metrics + version — used to skip re-writes |
calculated_at | str | ISO 8601 timestamp of when metrics were calculated |
Null Convention¶
Duration metrics return null when their prerequisite annotations are missing:
- Metrics gated on
start-session: M-2, M-3, M-8, M-25, M-27 - Metrics gated on
end-session: M-6, M-14 - Metrics gated on
bill-dropped+bill-payment: M-26 - Metrics gated on
order-taken: M-12, M-13 - Metrics gated on confirmed POS +
video_start_utc: M-2, M-7, M-9, M-10, M-11
A null metric is not an error — it means the required data was not available for that session.
Count metrics (M-1, M-4, M-5, M-15, M-16, M-17) default to 0, never null.