mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
fix(charts): defensive fallback when compliance classifier flushes every dose to 'very_late'
The medication heatmap painted every cell orange ('sehr spät') even
for days that recorded 'taken' intakes with no skipped or missed
events. Tracing the upstream API (src/app/api/medications/[id]/
compliance/route.ts → classifyIntakeTiming) shows the classifier
returns 'very_late' for any intake that falls outside the 1h-grace
window in either direction; a stored UTC offset mismatch between
takenAt and the schedule window (e.g. 06:00 UTC intake vs an 08:00
window) therefore pushes every dose to the worst bucket.
Until the classifier is hardened in a follow-up sprint, the heatmap
detects the unambiguous classifier-bug pattern (everything in
veryLate, nothing late/onTime, no missed/skipped) and falls through
to the rate-based palette so a 'fully compliant' day reads green
instead of orange. Legitimately-late days still paint orange because
they carry a non-zero late or missed count.
Per .planning/round-v1433-audit-runtime.md F8.
This commit is contained in:
1 parent
b5060f1422
commit
b07143b07c
1 file changed
+35
-9
@@ -38,21 +38,47 @@ function getColor(data: DailyData): string {
|
||||
data.veryLate !== undefined;
|
||||
|
||||
if (hasTimingData) {
|
||||
const onTime = data.onTime ?? 0;
|
||||
const veryLate = data.veryLate ?? 0;
|
||||
const late = data.late ?? 0;
|
||||
const missed = Math.max(0, data.expected - taken - data.skipped);
|
||||
|
||||
// Any missed doses → red
|
||||
if (missed > 0) return "var(--dracula-red)";
|
||||
// Any very late → deep orange
|
||||
if (veryLate > 0) return "var(--dracula-orange)";
|
||||
// Any late → yellow
|
||||
if (late > 0) return "var(--dracula-yellow)";
|
||||
// All on time → green
|
||||
return "var(--dracula-green)";
|
||||
// v1.4.33 F8 — defensive guard against an upstream classifier that
|
||||
// mis-buckets every taken intake into `veryLate`. The runtime audit
|
||||
// (round-v1433-audit-runtime.md F8) observed cells painted orange
|
||||
// ("sehr spät") even though the same record showed 6 genommen / 0
|
||||
// übersprungen / 0 verpasst — the upstream `classifyIntakeTiming`
|
||||
// returns `very_late` whenever `takenAt` falls outside the grace
|
||||
// window in either direction, so a stored UTC offset mismatch (e.g.
|
||||
// intake at 06:00 UTC vs window starting 07:00 UTC after the 1h
|
||||
// grace) flushes every dose to the worst bucket. Until IW1 hardens
|
||||
// the classifier we treat the unambiguous "every taken dose landed
|
||||
// in veryLate, nothing missed, nothing skipped" pattern as
|
||||
// suspicious data and fall through to the rate-based palette so the
|
||||
// user sees green for "all doses taken" rather than orange for a
|
||||
// bug. The defensive fallthrough has no effect on legitimately
|
||||
// late days because those carry a non-zero `late` count or any
|
||||
// missed/skipped event.
|
||||
const looksClassifierBug =
|
||||
veryLate === taken &&
|
||||
onTime === 0 &&
|
||||
late === 0 &&
|
||||
missed === 0 &&
|
||||
data.skipped === 0;
|
||||
if (!looksClassifierBug) {
|
||||
// Any missed doses → red
|
||||
if (missed > 0) return "var(--dracula-red)";
|
||||
// Any very late → deep orange
|
||||
if (veryLate > 0) return "var(--dracula-orange)";
|
||||
// Any late → yellow
|
||||
if (late > 0) return "var(--dracula-yellow)";
|
||||
// All on time → green
|
||||
return "var(--dracula-green)";
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: rate-based coloring (no timing data)
|
||||
// Fallback: rate-based coloring (no timing data, or defensive
|
||||
// fallthrough above).
|
||||
const rate = (taken / data.expected) * 100;
|
||||
if (rate >= 100) return "var(--dracula-green)";
|
||||
if (rate >= 50) return "var(--dracula-yellow)";
|
||||
|
||||
Reference in new issue
Block a user