feat: add Dosing Interval Comparison chart (Task 9.7)

This commit is contained in:
Andrew Charlwood
2026-02-06 19:58:28 +00:00
parent b1136ad7bf
commit 02fe4b4e28
4 changed files with 322 additions and 4 deletions
+57
View File
@@ -1623,3 +1623,60 @@ Console error: `WARN: Multiple implied roots, cannot build icicle hierarchy of t
- Note: Sankey has ordinal suffixes (e.g., "ADALIMUMAB (1st)") to prevent self-loops
### Blocked items:
- None
## Iteration 29 — 2026-02-06
### Task: Phase 9 — Task 9.7 (Dosing Interval Comparison chart — Tab 6)
### Why this task:
- Tasks 9.19.6 complete. Task 9.7 is next in sequence.
- Progress.txt from iteration 28 explicitly recommended this task.
- Uses existing `get_dosing_intervals()` query and `parse_average_spacing()` parsing.
### Status: COMPLETE
### What was done:
- **Created `create_dosing_figure(data, title, group_by)` in `src/visualization/plotly_generator.py`** (~180 lines):
- Two modes via `group_by` parameter:
- **"drug" (overview)**: Weighted average weekly interval per drug, one horizontal bar per drug sorted by patient count. Bars coloured by interval (NHS blue gradient — darker = more frequent dosing). Patient count annotations on right side.
- **"trust" (per-drug comparison)**: Grouped horizontal bars per trust, one trace per directory. Enables cross-trust dosing comparison for a specific drug.
- Hover shows: drug/trust, interval, avg doses, avg treatment weeks, patient count
- Dynamic height, NHS design aesthetic (Source Sans 3, transparent, clean grid)
- Helper functions: `_dosing_by_drug()` and `_dosing_by_trust()` for clean separation
- **Added `_render_dosing(app_state, title)` helper in `dash_app/callbacks/chart.py`**:
- Extracts filter params (date_filter_id, chart_type, single drug/trust)
- Auto-selects `group_by`: "trust" when single drug selected, "drug" for overview
- Calls `get_dosing_intervals()` wrapper then `create_dosing_figure()`
- Handles empty data and exceptions gracefully
- **Wired into `update_chart` dispatch**: `active_tab == "dosing"` → `_render_dosing()`
### Validation results:
- Tier 1 (Code): `from dash_app.app import app` — OK, 11 callbacks registered
- Tier 1 (App starts): `python run_dash.py` → "Dash is running on http://127.0.0.1:8050/" — no errors
- Tier 3 (Functional):
- Directory chart overview: 124 rows → 36 drugs, intervals 4.352.4 weeks — renders correctly
- Indication chart: 219 rows → 37 drugs — renders correctly
- ADALIMUMAB single drug: 14 rows → 4 directory traces, per-trust comparison — renders correctly
- NNUH trust filter: single trust reduces data — filters correctly
- Date filter (2yr_12mo): 89 rows → 29 drugs — filters correctly
- Empty data: returns empty figure — handled correctly
- Icicle still works: 293 nodes, 11,118 patients — no regression
- Market share, cost effectiveness, cost waterfall, Sankey — no regressions
### Files changed:
- `src/visualization/plotly_generator.py` — Added: `create_dosing_figure()`, `_dosing_by_drug()`, `_dosing_by_trust()` (~180 lines)
- `dash_app/callbacks/chart.py` — Added: `_render_dosing()` helper + dispatch branch
- `IMPLEMENTATION_PLAN.md` — Task 9.7 marked [x]
### Committed: [pending]
### Patterns discovered:
- Dosing chart benefits from two distinct modes: overview (all drugs, weighted averages) and comparison (single drug, per-trust). The `group_by` parameter makes this clean without separate figure functions.
- Trust names are very long (e.g., "NORFOLK AND NORWICH UNIVERSITY HOSPITALS NHS FOUNDATION TRUST") — stripping " NHS FOUNDATION TRUST" and " HOSPITALS" suffixes greatly improves y-axis readability.
- Weighted average by patient count is the right aggregation for dosing intervals — it prevents small patient groups from skewing the displayed interval.
- Bar colour gradient by interval value provides immediate visual cue: darker blue = more frequent dosing, lighter = less frequent.
### Next iteration should:
- Start Task 9.8 — Directorate × Drug Heatmap chart (Tab 7)
- Sub-steps:
1. Create figure function in `src/visualization/plotly_generator.py` — `create_heatmap_figure(data, title, metric)`
2. Build Plotly heatmap from `get_drug_directory_matrix()` data
3. Rows = directorates (sorted by total patients), Columns = drugs (sorted by frequency)
4. Cell colour = patient count (default), with possible toggle for cost or cost_pp_pa
5. Wire into `update_chart` via `_render_heatmap()` helper
6. Responds to trust filter, date filter, chart type toggle
- Read `get_drug_directory_matrix()` in pathway_queries.py for exact data shape
- Data returns: `{directories: [...], drugs: [...], matrix: {dir: {drug: {patients, cost, cost_pp_pa}}}}`
### Blocked items:
- None