Files
glucose-dashboard/docs/requirements-reporting.md
T
willemjanandClaude Sonnet 5 3ef9a7666a add reporting foundation: multipage nav, auth gate, date-range fetch
First increment of the clinical reporting feature (see
docs/requirements-reporting.md and docs/design-reporting.md): moves the
existing live view to pages/1_Live.py, adds a password gate since the
dashboard URL will be shared with the diabetes team, adds
fetch_entries_range/fetch_treatments_range for multi-day report queries,
and adds reporting.py with the core clinical calculations (GMI, %CV,
5-band time-in-range, hypo/hyper event detection per the Battelino et al.
2019 persistence rule). No report pages wired up yet — that's next.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-26 20:00:48 +02:00

11 KiB
Raw Blame History

Requirements: Clinical reporting page

Status: draft — for review and editing Owner: willemjan Related: requirements-profile-tuning.md (separate feature, separate app)

1. Purpose & background

The previous FreeStyle Libre 2 setup (via LibreView) produced clinical reports that the hospital/diabetes team wants to keep seeing: a multi-month time-in-range (TIR) overview, day-by-day glucose graphs, hypoglycemia event analysis, and an estimated HbA1c. The current DIY closed-loop setup (Medtrum TouchCare Nano 200U + Lyumjev + Dexcom One+ via xDrip+ + AndroidAPS) stores everything in Nightscout, which has no equivalent built-in report.

This feature adds a reporting page to the existing glucose-dashboard Streamlit app, generating the closest practical equivalent from Nightscout data — read-only, no new infrastructure, deployed through the existing CI/CD pipeline.

2. Scope

In scope:

  • A new set of pages in the existing Streamlit app, reading entries (glucose) and treatments (insulin, carbs) from Nightscout.
  • Multi-day statistical summaries, an AGP-style percentile chart, day-by-day glucose graphs, hypo/hyper event analysis, and an estimated HbA1c (GMI) on separate pages where not closely related.
  • Generating PDF output of user selected pages, so the user can send the report to the hospital.

Out of scope (this feature):

  • Anything that writes to Nightscout (see the separate profile-tuning spec).
  • Non-Nightscout data sources.
  • The oref0-autotune integration.

3. Clinical definitions & standards used

These are the standard definitions this report is built against (international consensus, mainly based on dutch requirements, not invented for this project) the ranges are described in the table below.

Term Definition Source
Hypoglycemia Level 1 < 3.9 mmol/L (<70 mg/dL) Battelino et al. 2019, Diabetes Care 42(8):1593–1603
Hypoglycemia Level 2 < 3.0 mmol/L (<54 mg/dL) same
Hyperglycemia Level 1 > 10.0 mmol/L (>180 mg/dL) same
Hyperglycemia Level 2 > 13.9 mmol/L (>250 mg/dL) same
Event persistence rule A hypo/hyper event starts once glucose has been continuously past the threshold for ≥15 minutes, and ends once it has recovered past the threshold for ≥15 minutes. A brief single reading crossing back doesn't end the event. Battelino et al. 2019
Time-in-range targets (adult, general) TIR (3.9–10.0) >70% · TBR<3.9 <4% · TBR<3.0 <1% · TAR>10.0 <25% · TAR>13.9 <5% Battelino et al. 2019
GMI (estimated HbA1c) GMI% = 3.31 + 0.02392 × mean(mg/dL) = 3.31 + 0.4311 × mean(mmol/L) Bergenstal et al. 2018, Diabetes Care 41(11):2275–2280
Minimum valid report ≥14 days of data with ≥70% sensor-active time consensus standard

Note: this dashboard already uses TARGET_LOW = 3.9 / TARGET_HIGH = 10.0 in data_processing.py — that matches Level 1 thresholds above, so the existing constants carry over directly; Level 2 thresholds are new.

GMI is explicitly not a substitute for a lab HbA1c (can differ by ~0.5 percentage points due to red blood cell/hemoglobin factors) — the report should label it "estimated HbA1c (GMI)", not "HbA1c", to avoid confusing the hospital team.

4. Functional requirements

Each FR below is meant to be independently implementable and testable, so they can be delivered one at a time through the CI/CD pipeline.

FR1 — Report period selector

The user can choose the reporting period: 14 days (clinical default), 30 days, 90 days (matches the old Libre 2 quarterly view), or a custom date range. The period should be settable for each page separately since 90 days are relevant for A1C calculations and time in range but not so much for daily overview where 14 days is more relavant to the clinic. The selection drives every other requirement below.

Acceptance: selecting a period reloads the specific report page for that exact date range; the 90-day option must not time out or hang (see NFR1 on data volume).

FR2 — AGP-style summary statistics

A summary panel showing, for the selected period:

  • % of time CGM was active (data completeness)
  • Average glucose (mmol/L)
  • Estimated HbA1c (GMI)
  • Glucose variability (%CV = stdev/mean × 100)
  • Time-in-range breakdown across the 5 standard bands (<3.0, 3.0–3.9, 3.9–10.0, 10.0–13.9, >13.9), each shown as a percentage and as hours:minutes/day
  • Each stat shown against its consensus target (pass/fail or color-coded)

Acceptance: stats match a hand-calculated reference on a small fixture dataset (see §7); TIR/TBR/TAR percentages sum to 100%.

FR3 — AGP percentile chart

A single chart showing the 5th/25th/50th(median)/75th/95th percentile glucose bands, aggregated by time-of-day across the selected period onto one 24-hour composite axis (the standard AGP visualization).

Acceptance: median line at a given hour matches the median of all readings at that hour-of-day across the period, computed independently in a test.

FR4 — Day-by-day glucose graphs

One glucose trace per calendar day in the selected period (reusing the existing target-range-band chart style from app.py's build_chart, adapted for a single day per panel), so the hospital team can review specific days the way the Libre 2 report's daily log did.

Acceptance: each day's panel shows exactly that day's readings; days with no data are shown as empty/skipped rather than causing an error.

FR5 — Hypoglycemia event analysis

Detect discrete hypo events per the persistence rule in §3, and report:

  • Total event count (Level 1 and Level 2 separately)
  • Duration of each event, and summary stats (average, longest, total time low)
  • Events per week
  • Time-of-day distribution (e.g., overnight vs. daytime split)

Acceptance: event count/duration matches a hand-traced fixture (a synthetic glucose series with known dips of known duration, including edge cases: a dip just under 15 minutes that should NOT count, and two dips separated by a brief recovery that should count as one continuous event per the persistence rule).

FR6 — Hyperglycemia event analysis

Same as FR5, symmetric, for Level 1/Level 2 highs. (Not explicitly requested by the user but a natural complement and confirmed to implement by the user.)

FR7 — Estimated HbA1c trend

GMI computed per rolling period (e.g., per month within a 90-day report), plotted as a small trend, so the hospital team can see whether control is improving/worsening over the quarter, not just the endpoint value.

FR8 — Candidate additional statistics

The user mentioned having seen "other interesting stats" in other reports without specifying which. Candidates to consider:

  • Day-of-week pattern (e.g., weekends vs. weekdays)
  • Overnight (00:00–06:00) vs. daytime TIR split
  • Basal:bolus insulin ratio
  • Insulin pump site-change and sensor-change adherence (interval between changes, reusing existing classify_treatments output)
  • Glucose trends after site-change
  • Pump site age vs insuline usage vs glucose levels. Corrected for carb intake to see if age of the pump-site and/or insuline age influence insulin usage
  • Total daily insulin dose trend

FR9 — Data completeness / quality indicator

Flag periods with low sensor-active time (<70%) or visible data gaps, since a report built on incomplete data is clinically unreliable — the report should surface this rather than silently computing misleading stats.

FR10 — Export / print-friendly view (open question)

The Libre 2 reports were shown to or printed for the hospital team. The pages needs a print-optimized layout, a PDF export, and a "share the dashboard URL".

5. Non-functional requirements

  • NFR1 — Data volume: the existing fetch_entries/fetch_treatments in nightscout_client.py scale their count parameter linearly with hours, which doesn't scale cleanly to 90 days (~26,000 5-minute readings). This feature needs either a proper date-range fetch (find[date] query params) or paginated fetching — a new capability, not a reuse of the existing rolling-window functions as-is.
  • NFR2 — Caching: historical data for a fixed past period doesn't change; cache TTL should be much longer than the existing 60-second rolling-view cache (app.py's load_nightscout_data), e.g. cached per exact date-range until explicitly refreshed.
  • NFR3 — Consistency: reuse the existing PALETTE dict and st.context.theme detection pattern from app.py rather than introducing new colors; reuse TARGET_LOW/TARGET_HIGH from data_processing.py.
  • NFR4 — Language: Dutch UI strings and docstrings, consistent with the rest of the app.
  • NFR5 — Multipage navigation: this is the first feature needing more than one page — introduces st.Page/st.navigation (anticipated in the project's own homelab-cicd-werkinstructie.md).
  • NFR6 - Password protection on the dashboard pages: as dashboard urls will be sharable and reachable by the diabetes team, access should be restricted to authorized users.

6. Data requirements

  • Glucose entries over an arbitrary date range (new: fetch_entries needs a date-range mode, not just "last N hours").
  • Treatments over the same range (same scaling concern).
  • No new Nightscout permissions needed — this feature only reads, so the existing read-only token is sufficient.

7. Testing & acceptance criteria

  • Each new pure function (event detection, GMI calculation, percentile aggregation, %CV) gets unit tests in data_processing.py or a new reporting.py module, following the existing tests/test_data_processing.py fixture-based pattern — including edge cases called out per FR above (empty data, exactly-15-minute boundary events, 100% in-range, etc.).
  • Any new Nightscout fetch logic gets tests following the existing tests/test_nightscout_client.py monkeypatch-on-requests pattern.
  • A UAT checklist (produced during the design phase) will map each FR's "Acceptance" line above to a concrete scenario the user can manually verify against their own real Nightscout data.

8. Open questions for the user's edit pass

  1. Confirm the clinical thresholds/targets in §3 match what your hospital actually uses, or note any local adaptations. - Use the suggested clinical thresholds and targets from §3
  2. FR6 (hyperglycemia analysis) — keep
  3. FR8 — which candidate stats actually matter to you / your team? All that are in FR8 now
  4. FR10 — is a print/export view needed, or is viewing the dashboard directly enough? - pdf export needed
  5. Default report period: 14-day (clinical standard) or 90-day (matches your old Libre 2 habit) as the page's default view? Default 90 days for statistics, but 14 days for recent glucose charts.