From 30c8555ceed7382a5eaef2cdaeff26dfadf91ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 16 May 2026 20:25:09 +0200 Subject: [PATCH] refactor(charts): drop the v1.4.33 heatmap classifier-bug fallback v1.4.33 shipped a defensive fallthrough in `getColor` that detected the unambiguous "every taken dose landed in veryLate" pattern and recoloured those cells via the rate-based palette. With the classifier hardening landed in `compliance.ts` the pattern can no longer arise from a UTC offset between `takenAt` and the schedule window, so the workaround is dead code. Replace the long `looksClassifierBug` block with a single comment pointing at the root fix and let the timing-based palette run unconditionally when timing data is available. --- src/components/charts/compliance-heatmap.tsx | 48 ++++++-------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/src/components/charts/compliance-heatmap.tsx b/src/components/charts/compliance-heatmap.tsx index 7a55409c6..2cf347f37 100644 --- a/src/components/charts/compliance-heatmap.tsx +++ b/src/components/charts/compliance-heatmap.tsx @@ -38,47 +38,25 @@ 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); - // 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)"; - } + // v1.4.34 IW-C — the v1.4.33 `looksClassifierBug` fallthrough is + // gone because the root cause is fixed: `classifyIntakeTiming` now + // routes proactive logs into the `early` bucket (counted as + // compliant by the API) instead of flushing them to `very_late`. + // 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 (incl. early) → green + return "var(--dracula-green)"; } - // Fallback: rate-based coloring (no timing data, or defensive - // fallthrough above). + // Fallback: rate-based coloring when no timing data is supplied. const rate = (taken / data.expected) * 100; if (rate >= 100) return "var(--dracula-green)"; if (rate >= 50) return "var(--dracula-yellow)";