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".

POS v2 category dependency

Category-specific financial metrics use metadata.items[*].category from the correlated POS session. Category-specific timing metrics use metadata.order_events[] timestamps when available; if those operational event items are uncategorized, metrics inherits categories from categorized final items by normalized item name. This is read-time mapping only — POS v2 rows are not mutated. When POS Sessions v2 item categories are backfilled or corrected, rerun metrics enrichment so category-dependent fields are recalculated and new shift versions are written where fingerprints change.


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["null<br/>item predates video"]

Items with negative offsets (before video start) are excluded — they cannot belong to the current video.


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 POS item timestamp.

Gate: start-session + confirmed POS + video_start_utc

Formula: max(0, first_pos_item_offset - start_session.time)


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


M-9: Drink Delivery from First POS Item

Field: drink_delivery_from_first_pos_item_seconds · Type: int | null

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


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 Sessions v2, this uses metadata.order_events[] as the preferred timestamp source. If an order-event item lacks category, the metrics mapper inherits the category from metadata.items[] using the shared normalized item-name contract.

Formula: max(0, drinks_arrived.time - first_drink_offset)

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 Sessions v2, this uses metadata.order_events[] as the preferred timestamp source. If an order-event item lacks category, the metrics mapper inherits the category from metadata.items[] using the shared normalized item-name contract.

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.