Compliance Metrics¶
Compliance measures whether restaurant staff responded to Emilia's real-time AI suggestions (drink opportunities, dessert opportunities).
The Problem Clustering Solves¶
Emilia lacks context about what's happening at the table. When no one responds to a suggestion, she re-sends the same alert every ~5 minutes. Without clustering:
5 events for the same situation + waiter goes once = ⅕ = 20% compliance
But the waiter DID attend the need → should be ~100%
The hybrid clustering algorithm groups rapid-fire alerts into opportunity moments, so one waiter visit covers the whole burst. But if events happen at genuinely different moments (separated by a waiter visit), they correctly form separate clusters.
Algorithm¶
flowchart TD
A["EchoBase Events<br/>from metadata.tables[].echobase_events"] --> B["Filter by alertType<br/>second-drink-opportunity<br/>dessert-opportunity"]
B --> C["Sort by time per type"]
C --> D{"Gap > 600s?"}
D -->|Yes| E["New Cluster"]
D -->|No| F{"Annotation between<br/>consecutive events?"}
F -->|Yes| E
F -->|No| G["Same Cluster"]
E --> H["Evaluate Cluster"]
G --> H
H --> I{"First annotation<br/>in cluster window?"}
I -->|waiter-touch| J["COMPLIANT"]
I -->|no-waiter-touch| K["NOT COMPLIANT"]
I -->|none found| L["EXCLUDED<br/>events not counted"] Cluster Splitting Rules¶
A cluster boundary is created when either condition is met:
- Time gap > 600 seconds between consecutive events of the same type
- Annotation between events — a
waiter-touchorno-waiter-touchannotation falls between two consecutive events, meaning the situation was acknowledged
Cluster Evaluation: First Annotation Wins¶
For each cluster, the algorithm searches its time window [window_start, window_end] for annotations:
waiter-touchfound first → cluster is COMPLIANTno-waiter-touchfound first → cluster is NOT COMPLIANT- Neither found → cluster is EXCLUDED (all events omitted from counts)
The result is propagated to every event in the cluster.
Real-World Example¶
sequenceDiagram
participant E as Emilia AI
participant W as Waiter
participant A as Annotator
Note over E: Cluster 1 starts
E->>E: second-drink-opportunity at 11980s
E->>E: second-drink-opportunity at 12526s
W->>A: waiter-touch at 12913s
Note over E: Cluster 1 = COMPLIANT
Note over E: Cluster 2 starts
E->>E: second-drink-opportunity at 13795s
E->>E: second-drink-opportunity at 14116s
W->>A: waiter-touch at 14128s
Note over E: Cluster 2 = COMPLIANT Both clusters are compliant. Result: drink_suggestion_count = 4, drink_suggestion_compliance_pct = 100.0.
Output Fields¶
| Field | Type | Description |
|---|---|---|
drink_suggestion_count | int | Count of events (not clusters) for second-drink-opportunity |
drink_suggestion_compliance_pct | float | null | Percentage of compliant drink events. null if count is 0 |
dessert_suggestion_count | int | Count of events for dessert-opportunity |
dessert_suggestion_compliance_pct | float | null | Percentage of compliant dessert events |
Percentages are rounded to 1 decimal place.
real_time_suggestions Detail¶
When compliance events are found, a real_time_suggestions array is added to the session (alongside metrics):
{
"alert_type": "second-drink-opportunity",
"time_seconds": 11980.0,
"alert_timestamp_utc": "2026-03-08T18:05:04Z",
"operator_id": "valeria",
"compliant": true,
"cluster_id": 0
}
This array is included in the fingerprint calculation — changes to suggestion matching trigger a new version even if metrics values are identical.