listOutcomesForEmployee(subjectId, 100000) returns every outcome row a subject has ever accrued and each store implementation JSON.parses that row's evidence_json. Five callers do this; four are batch or operator-facing, and one is now on an interactive, point-of-care path.
| caller |
path |
mcp/tools.ts:348 |
check_compliance |
routes/compliance-api.ts:271 |
GET /api/v1/compliance/... (ADR-061 mode=latest) |
routes/identity.ts:102 |
cross-system identity |
run/employee-profile.ts:127 |
employee profile |
routes/cds-hooks.ts:153 |
POST /cds-services/{id} — a CDS client renders this while a clinician waits |
Why the window is large, and why shrinking it is the wrong fix
The 100,000 is deliberate and documented at compliance-api.ts:262-270: outcomes has no uniqueness on (subject, measure, period), so every run inserts a fresh row — a nightly ALL_PROGRAMS run over ~16 measures writes ~16 rows per subject per night. A small window makes a valid older outcome fall outside it and read as "no run has covered this subject", which for the CDS surface is the exact confusion noEvaluationCard exists to prevent. So the lever is not the limit.
The fix
A per-measure query in the store, so the work is bounded by the measure count rather than by history:
OutcomeStore.listLatestOutcomesPerMeasure(subjectId) in src/stores/outcome-store.ts
- Postgres ceiling:
DISTINCT ON (measure_id) ... ORDER BY measure_id, evaluated_at DESC
- SQLite floor: a window function or a
GROUP BY with a correlated max
- A case in
src/stores/store-contract.ts, so both implementations are held to the same behaviour — including the part the current code does in TypeScript: only rows whose run is COMPLETED or PARTIAL_FAILURE count, so the query must join runs rather than return the newest row regardless of run status
Then move cds-hooks.ts onto it, and consider compliance-api.ts and check_compliance, which want the same shape for one measure.
Scope note
Not a correctness bug and not introduced by #469 — the CDS route copied an existing constant deliberately. What #469 introduced is a latency budget for it. Nothing fires the hook today (no WebChart client, no configured CDS client), so this is unobserved: worth measuring against a realistic history before optimising, since the "~11k rows after two years" figure is extrapolation, not measurement.
Stated as a limit in docs/CDS_HOOKS.md → Limits, stated so an integrator reading the contract is not surprised.
Found by code review of #469.
listOutcomesForEmployee(subjectId, 100000)returns every outcome row a subject has ever accrued and each store implementationJSON.parses that row'sevidence_json. Five callers do this; four are batch or operator-facing, and one is now on an interactive, point-of-care path.mcp/tools.ts:348check_complianceroutes/compliance-api.ts:271GET /api/v1/compliance/...(ADR-061mode=latest)routes/identity.ts:102run/employee-profile.ts:127routes/cds-hooks.ts:153POST /cds-services/{id}— a CDS client renders this while a clinician waitsWhy the window is large, and why shrinking it is the wrong fix
The 100,000 is deliberate and documented at
compliance-api.ts:262-270:outcomeshas no uniqueness on(subject, measure, period), so every run inserts a fresh row — a nightlyALL_PROGRAMSrun over ~16 measures writes ~16 rows per subject per night. A small window makes a valid older outcome fall outside it and read as "no run has covered this subject", which for the CDS surface is the exact confusionnoEvaluationCardexists to prevent. So the lever is not the limit.The fix
A per-measure query in the store, so the work is bounded by the measure count rather than by history:
OutcomeStore.listLatestOutcomesPerMeasure(subjectId)insrc/stores/outcome-store.tsDISTINCT ON (measure_id) ... ORDER BY measure_id, evaluated_at DESCGROUP BYwith a correlated maxsrc/stores/store-contract.ts, so both implementations are held to the same behaviour — including the part the current code does in TypeScript: only rows whose run isCOMPLETEDorPARTIAL_FAILUREcount, so the query must joinrunsrather than return the newest row regardless of run statusThen move
cds-hooks.tsonto it, and considercompliance-api.tsandcheck_compliance, which want the same shape for one measure.Scope note
Not a correctness bug and not introduced by #469 — the CDS route copied an existing constant deliberately. What #469 introduced is a latency budget for it. Nothing fires the hook today (no WebChart client, no configured CDS client), so this is unobserved: worth measuring against a realistic history before optimising, since the "~11k rows after two years" figure is extrapolation, not measurement.
Stated as a limit in
docs/CDS_HOOKS.md→ Limits, stated so an integrator reading the contract is not surprised.Found by code review of #469.