Skip to content

Compliance: calculateCompliance ignores daysOfWeek + intervalWeeks → weekly meds report ~13% instead of ~100% #214

@MBombeck

Description

@MBombeck

Symptom

Per-medication compliance percentage reported on the medication card, on the Health Score on the dashboard, in the AI Coach prompt features, and on every insight that reads compliance7.rate / compliance30.rate is wrong for any medication whose schedule is not "every day". The bug compounds with multi-dose-per-day schedules that are also restricted by daysOfWeek (e.g. metformin weekdays-only).

Marc reported it as "I feel the compliance level is wrongly calculated, especially for medications taken not once a day but once a week".

Root cause

src/lib/analytics/compliance.ts:131-206 (calculateCompliance) computes:

totalExpected = schedules.length * effectiveDays

The ScheduleWindow interface at src/lib/analytics/compliance.ts:11-14 only exposes windowStart / windowEnd. The MedicationSchedule.daysOfWeek column (prisma/schema.prisma:832) and intervalWeeks recurrence are never read by this function.

Numeric examples

Once-per-week Ozempic, Mondays only, 30-day window:

  • Truth: 4–5 Mondays → expected = 4 or 5
  • Code: 1 schedule × 30 days = 30 expected
  • User takes every Monday (4 doses): min(100, round(4/30 × 100)) = 13 % instead of 100 %
  • User misses one Monday (3 of 4): 10 % instead of 75 %

Every weekly medication looks ~85 % non-compliant.

3×/day metformin, weekdays only, 30-day window:

  • Truth: 3 doses × 22 weekdays = 66 expected
  • Code: 3 × 30 = 90 expected
  • User takes all 66 scheduled: 73 % instead of 100 %

A 27 % undercount that hits anyone with a weekday-only schedule.

3×/day metformin, every day (no weekday restriction):

  • Truth: 21 expected per week, user takes 18 → 86 %
  • Code: 3 × 7 = 21 expected → 86 % ✓ correct

The pure multi-dose-per-day path is fine — the bug only fires when daysOfWeek or intervalWeeks is involved.

Blast radius (8 production call sites)

  • /api/medications/[id]/compliance — per-medication card
  • src/lib/analytics/health-score-fast-path.ts:326,339Health Score on the dashboard
  • src/lib/insights/features.ts:925-927 — AI Coach prompt context
  • src/lib/medications/medication-compliance-status.ts:187,193 — status card
  • src/lib/medications/blood-pressure-status.ts:309,315 — BP-status compliance gate
  • src/app/api/insights/targets/route.ts:893,899 — insight targets

Fix

The cadence-aware correct path already exists at src/lib/medications/scheduling/compliance.ts:129-162 (complianceChips). It uses expandScheduleSlots (src/lib/medications/scheduling/cadence.ts:167-220) which honours daysOfWeek + intervalWeeks + DST + timezone correctly. It is already used on /api/medications/[id]/cadence but the rest of the surface still calls the legacy aggregator.

The fix is to replace every calculateCompliance(events, med.schedules, days, createdAt) call with complianceChips(med.schedules, mappedEvents, new Date(), days, med.createdAt, userTz), adapt the result-shape (the chips path returns slightly different fields — compliance7.streak, .missed), then delete the legacy calculateCompliance function (keep classifyIntakeTiming which is independent).

Pair with a parameterised test matrix covering weekly / bi-weekly / weekday-only / 3×/day combinations to prevent regression.

Release target

v1.5.1. Rationale: shifting Health-Score numerics the same week as the iOS launch (every user with weekly meds will see their Score move 10–20 points upward) creates UAT noise that masks iOS-handoff issues. Land the fix the week after iOS goes live; pair with a one-time "Health Score updated for weekly medications" toast so the change is transparent.

Acceptance criteria

  • A weekly Ozempic schedule with 4 taken intakes on 4 scheduled Mondays in a 30-day window reports compliance30.rate === 100
  • A weekday-only 3×/day metformin schedule with all 66 weekday doses taken in 30 days reports compliance30.rate === 100
  • A daily 3×/day metformin schedule with 18 of 21 doses taken in 7 days still reports compliance7.rate === 86 (no regression on the path that works today)
  • Parameterised test matrix in src/lib/analytics/__tests__/ pins the contract for: weekly Mondays, bi-weekly, weekday-only, weekend-only, 1×/day, 2×/day, 3×/day, and 4×/day combinations

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions