F3.3 — Dashboard Metrics
Deliver the data layer for the WPP fieldwork dashboard (F1.6): an hourly per-market metrics snapshot produced inside the fieldwork-processing Step Functions workflow (EventBridge hourly start → discover → fail-closed QC gate → per-market metrics compute → S3). Each successful cycle writes a validated, aggregate-only MetricsSnapshot JSON to a dedicated snapshot bucket: There is no HTTP API in v1 (design change 2026-08-05): the previously planned /v1/dashboard/* endpoints, ?fresh=true recompute, OpenAPI client codegen, and per-market SQS fan-out are all gone. The dashboard reads the snapshot JSON directly from S3; the embedded computedAt is the UI's "last sync" indicator. There is no per-request recompute path. v1 is scoped to data we already capture: completes vs target, starts/in-progress/IR%, screen-out breakdown, per-QC-check counts, research-defender counts, quota-cell fill, LOI (average/median/per-section), daily completes, grade/region tallies, and per-cell drill-down data. Derived values (percentages, deltas, totals) are deliberately not stored — consumers compute them. Forecast/RAG/Fount go-live metrics are v2, blocked on WPP supplying field dates.
19 of 28 stories shipped across 31 merged fieldwork-processing PRs: the hourly pipeline (discover to fail-closed QC gate to per-market metrics compute to S3 snapshot) is wired end to end, the 17 QC methods and workbook builders are ported, and a per-client API-key metrics endpoint shipped. Two export-scope stories are blocked on open product decisions and five more remain todo, including streaming export artifacts to S3 instead of buffering them in memory.
- Since last activity
- 5d
- Oldest staged work
- 29d
- Acceptance criteria covered
- 3/5
- PRs open
- 0
Release gate: main → no release PR found— never assumed shipped.
Stories
- No PR on the story itself
11 sub-tasks· 10/11 shipped
- fieldwork-processing#23 (merged to the default branch)Shipped
- fieldwork-processing#24 (merged to the default branch)Shipped
- fieldwork-processing#26 (merged to the default branch)Shipped
- fieldwork-processing#27 (merged to the default branch)Shipped
- fieldwork-processing#29 (merged to the default branch)Shipped
- fieldwork-processing#30 (merged to the default branch)Shipped
- Done, unverified
- fieldwork-processing#28 (merged to the default branch)Shipped
- fieldwork-processing#31 (merged to the default branch)Shipped
- fieldwork-processing#32 (merged to the default branch)Shipped
- fieldwork-processing#34 (merged to the default branch)Shipped
- Done, unverified
- No branch or PR yet
- No branch or PR yet
- No branch or PR yet
- No branch or PR yet
- No branch or PR yet
- To doNo branch or PR yet
- No branch or PR yet
28 tracked · 19 shipped, 2 done, unverified, 2 blocked, 5 to do
Acceptance criteria
- The dashboard polls the snapshot and gets computedAt with no per-viewer Firestore fan-outBOUN-11442covered
- A failed compute preserves the last-known-good snapshot, badges it stale with lastError/failedAttempts/lastSuccessfulAt, and a snapshot-age alarm fires after 3 missed cyclespartialBOUN-11442partially covered
- A delayed or duplicate execution cannot overwrite a newer snapshot (ETag-conditional writes, monotonic computedAt guard)BOUN-11414covered
- The snapshot validates against the strict, versioned MetricsSnapshotSchema and carries aggregates only, no PIIBOUN-11411covered
- A QC failure anywhere fails the workflow closed, leaving the existing snapshot untouched and skipping metrics computepartialBOUN-11459partially covered
Gaps
Criteria are read from the ticket description on every run. A gap closes on its own once a story or PR matching it appears — nothing here is closed by hand.