Skip to content

POS-Dependent Metrics

Metrics that require POS (Point of Sale) data correlated with the video session. All POS metrics require pos_correlation.status == "confirmed".

Changed in version 2.6.0

M-2/M-7/M-9/M-10/M-11 use positive operational order evidence and emit service_timing_contract_version: 1. POS v2 never falls back to final financial items, even when its operational array is empty or missing. Missing/contradictory evidence produces null, not a manufactured zero. Legacy POS v1 retains its actual item timestamps; pre-video timestamps are invalid.

Positive order evidence

The Python producer owns event eligibility, category selection, duration calculation, and navigation anchors. Consumers must not infer these independently from raw POS arrays.

  • Known positive roles: command, production, order_event, order, timeline_enrichment. Known positive event kinds: production_comanda, positive_order, order_created, item_added.
  • Explicit final/negative/cancelled/deleted flags, non-authoritative association, and final, payment, receipt, reconciliation, cancellation, refund, transfer, or deletion semantics override a positive role. An unknown role is not positive evidence simply because a financial flag is missing.
  • Explicit zero, negative, boolean, or non-finite quantities do not represent positive order lines. Missing quantities remain unknown; no quantity of one is fabricated.
  • An unpriced positive command is still order evidence. Its price and order total remain null; a genuinely recorded zero price remains 0. Financial amounts are never borrowed to price an operational command.
  • Event-local categories take priority. Missing categories can be inherited from final items using normalized item names, category aliases, and the existing clear-majority rule; ties remain unknown. This lookup reads source categories independently of legacy financial presentation. Raw POS rows and final financial items are not modified.
  • POS timestamps retain timezone offsets and fractional precision. Timezone-free timestamps are UTC; source nanoseconds are accepted at Python datetime's microsecond resolution. The earliest parseable positive-order timestamp is selected before session-boundary validation and duration rounding. A known pre-video first order also invalidates that first-order interval; a later order cannot replace it. Later valid orders may still be displayed as orders, but are not promoted to first-order timing evidence.

When categories or source evidence are corrected, reenrich the approved session inventory to persist new fingerprints. See contract and rollout.


POS Item Offset Calculation

POS items have UTC timestamps. To relate them to the video timeline, we calculate:

offset = item_timestamp_utc - video_start_utc
flowchart TD
    A["POS Item Timestamp<br/>UTC"] --> B{Parse UTC}
    B --> C[item_dt]
    D["Video Start UTC<br/>from shift_meta"] --> E["offset = item_dt - video_start"]
    C --> E
    E --> F{"offset >= 0?"}
    F -->|Yes| G["Valid offset<br/>in seconds"]
    F -->|No| H["Invalid anchor<br/>never published"]

Negative offsets, including sub-second negatives, are never published as timing anchors or markers. The canonical calculator retains their signed value internally to reject an invalid first order rather than silently promoting a later one. Other legacy offset callers keep their existing exclusion policy. Offsets remain fractional. Duration values are floor(end_offset - start_offset) only when both anchors are valid and the end is not before the start.


M-2: Time to First POS Order

Field: time_to_first_pos_order_seconds · Type: int | null

Seconds from start-session annotation to the first eligible positive POS order timestamp.

Gate: start-session + confirmed POS + video_start_utc

Formula: floor(first_pos_item_offset - start_session.time) for valid, ordered anchors; otherwise null.

Changed in version 2.6.0

Final bills and non-positive operational events are not orders. Contradictory chronology is unavailable, not zero.


M-7: Food Delivery from First POS Item

Field: food_delivery_from_first_pos_item_seconds · Type: int | null

Seconds from the first POS item (any category) to food-arrived.

Gate: confirmed POS + video_start_utc + start-session + food-arrived

Changed in version 2.6.0

Uses the positive-order policy above and floors only non-negative elapsed time. Missing or contradictory evidence returns null.


M-9: Drink Delivery from First POS Item

Field: drink_delivery_from_first_pos_item_seconds · Type: int | null

Seconds from the first eligible positive POS item (any category) to drinks-arrived.

Changed in version 2.6.0

Uses the positive-order policy above and floors only non-negative elapsed time. This generic duration is not a fallback for M-10.


M-10: Drink Delivery from First Drink Order

Field: drink_delivery_from_first_drink_order_seconds · Type: int | null

Seconds from the first POS item with category == "drink" to drinks-arrived.

For POS v2, this exclusively uses eligible metadata.order_events[] with a drink category, assigned by the backend policy above.

Formula: floor(drinks_arrived.time - first_drink_offset) for valid, ordered anchors; otherwise null.

Changed in version 2.6.0

No generic POS, manual order-taken, or arrival-from-session-start substitution. Contradictory delivery is not an early-success zero.

Tip

This metric is used by SLA Detection to flag drink delays (threshold: 480s / 8 min).


M-11: Food Delivery from First Main Dish Order

Field: food_delivery_from_first_main_dish_order_seconds · Type: int | null

Seconds from the first POS item with category == "main_plate" to food-arrived.

For POS v2, this exclusively uses eligible metadata.order_events[] with a main-dish category, assigned by the backend policy above.

Formula: floor(food_arrived.time - first_main_dish_offset) for valid, ordered anchors; otherwise null.

Changed in version 2.6.0

No generic POS or manual-order fallback. Category timing is independent: a missing drink order does not invalidate a valid food metric.

Tip

This metric is used by SLA Detection to flag food delays (threshold: 1200s / 20 min).


M-15: Drink Item Count

Field: drink_item_count · Type: int · Default: 0

Count of POS items where category == "drink". Only requires confirmed POS (no video_start_utc or start-session needed).


M-16: Main Dish Item Count

Field: main_dish_item_count · Type: int · Default: 0

Count of POS items where category == "main_plate".


M-17: Dessert Item Count

Field: dessert_item_count · Type: int · Default: 0

Count of POS items where category == "dessert".


M-18: Ticket Subtotal

Field: pos_ticket_subtotal · Type: float | null

Total ticket value from pos_data.totals.subtotal. Rounded to 2 decimals.


M-19: Ticket Average Per Person

Field: pos_ticket_avg · Type: float | null

pos_ticket_subtotal / people_count. Returns null if people_count is missing or zero.


M-20: Drink Ticket Total

Field: pos_drink_ticket_total · Type: float | null

Sum of price for all POS items with category == "drink".


M-21: Dessert Ticket Total

Field: pos_dessert_ticket_total · Type: float | null

Sum of price for all POS items with category == "dessert".


M-22: Server Name (Open)

Field: pos_server_name_open · Type: str | null

Employee who opened the POS session. Excludes "root" (system user).


M-23: Server Name (Close)

Field: pos_server_name_close · Type: str | null

Employee who closed the POS session. Excludes "root".


M-24: Top Seller

Field: pos_top_seller · Type: str | null

Employee with the most items sold in the session. Groups by employee_id when available (resolves to employee_name or str(employee_id)). Falls back to grouping by employee_name when no item has employee_id (e.g., Queops POS). No hardcoded employee maps.

Changed in version 2.3.1

Added fallback to group by employee_name when employee_id is absent, enabling M-24 for Queops-style POS systems.