diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index f71e46d..507b1aa 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -449,6 +449,135 @@ Drawer selection → update_drug_selection → app-state store → load_pathway_ --- +## Phase 10: Two-View Architecture + Header Redesign + +### Context +Phase 9 delivered 8 chart tabs in a single view. User feedback: comparing drugs across directorates is "apples and oranges" — e.g., Remicade (ophthalmology) vs Adalimumab (multi-directorate) isn't useful. The new architecture splits charts into two views with distinct perspectives: +- **Patient Pathways**: Pathway-focused analysis (Icicle + Sankey) with drug/trust/directorate filters +- **Trust Comparison**: Per-directorate analysis comparing drugs across trusts (6 charts for a selected directorate) + +Additionally: KPI row removed, fraction KPIs moved to header, global filter sub-header added. + +### 10.1 Design consultation via frontend-design skill +- [x] Use the `/frontend-design` skill to design the following layouts: + 1. **Header redesign**: Fraction KPIs (X/X patients, X/X drugs, £X/£X cost) integrated into the header bar. Data freshness info stays right. Title stays left. + 2. **Global filter sub-header**: Date filter dropdowns (Initiated, Last Seen) + chart type toggle pills (By Directory / By Indication). Styled as a prominent, permanent fixture directly below the main blue header — visually distinct (semi-light blue or similar). Constant across both views. + 3. **Trust Comparison landing page**: ~14 directorate buttons (or ~32 indication buttons when "By Indication" active). Clickable cards/buttons that lead to the 6-chart dashboard for that directorate. + 4. **Trust Comparison 6-chart dashboard**: Market Share, Cost Waterfall, Dosing, Heatmap, Duration, Cost Effectiveness — all for one selected directorate, comparing drugs across trusts. Layout optimized for 6 charts on one screen. + 5. **Patient Pathways filter placement**: Drug/trust/directorate filter buttons (only visible on Patient Pathways, not Trust Comparison). Design appropriate placement — could be inline with content, or in a secondary bar. +- [x] Capture design decisions (component structure, CSS classes, layout approach) for subsequent tasks +- **Checkpoint**: Design mockups/specifications ready for all 5 areas above + +### 10.2 State management + sidebar restructure +- [ ] Add `active_view` to `app-state`: `"patient-pathways"` (default) or `"trust-comparison"` +- [ ] Add `selected_comparison_directorate` to `app-state`: `null` (landing page) or directorate name +- [ ] Update `dash_app/components/sidebar.py`: + - Rename "Pathway Overview" → "Patient Pathways" + - Add "Trust Comparison" nav item below it + - Active state tracks `active_view` +- [ ] Add callback: sidebar clicks → update `active_view` in app-state +- [ ] Main content area switches between Patient Pathways view and Trust Comparison view based on `active_view` +- [ ] Date filter + chart type toggle remain in global sub-header (visible in both views) +- **Checkpoint**: Sidebar switches between two views, active state highlights correctly, app starts without errors + +### 10.3 Header redesign — remove KPI row, add fraction KPIs +- [ ] Remove `dash_app/components/kpi_row.py` (or gut it) +- [ ] Remove KPI row from `app.py` layout +- [ ] Update `dash_app/components/header.py`: + - Add fraction KPI display: "X / X patients", "X / X drugs", "£X / £X cost" + - Numerator = filtered values (from chart-data store), denominator = global totals (from reference-data store) + - Position: right side of header, alongside existing data freshness indicator + - Remove indication match rate KPI entirely +- [ ] Update header callbacks to receive both filtered and total values +- [ ] Update CSS in `dash_app/assets/nhs.css` for new header layout +- [ ] Apply design from 10.1 +- **Checkpoint**: Header shows fraction KPIs, KPI row is gone, header looks clean with design from 10.1 + +### 10.4 Global filter sub-header bar +- [ ] Extract date filter dropdowns + chart type toggle from `filter_bar.py` into a new sub-header component (or restyle existing filter_bar) +- [ ] Style as a prominent bar directly below the main header — visually distinct per design from 10.1 +- [ ] Remove drug/trust/directorate filter buttons from this bar (they move to Patient Pathways view only — see 10.7) +- [ ] Ensure sub-header is constant across both views (Patient Pathways and Trust Comparison) +- [ ] Date filter and chart type toggle changes update `app-state` globally (triggering updates in whichever view is active) +- [ ] Update CSS per design from 10.1 +- **Checkpoint**: Global sub-header renders below main header, date/chart-type controls work, visible in both views + +### 10.5 Patient Pathways view — reduce to Icicle + Sankey +- [ ] Create a Patient Pathways view component (or update chart_card.py) with only 2 tabs: Icicle, Sankey +- [ ] Remove Market Share, Cost Waterfall, Dosing, Heatmap, Duration, Cost Effectiveness from this view's tab bar +- [ ] Existing filter → chart-data → chart callback pipeline stays for these 2 tabs +- [ ] This view is shown when `active_view == "patient-pathways"` +- **Checkpoint**: Patient Pathways shows only Icicle + Sankey tabs, both still work with all existing filters + +### 10.6 Trust Comparison query functions +- [ ] Add new/modified query functions to `src/data_processing/pathway_queries.py` for per-trust-within-directorate perspective: + - `get_trust_market_share(db_path, filter_id, chart_type, directory)` — drugs by trust within a single directorate (stacked bars per trust instead of per directorate) + - `get_trust_cost_waterfall(db_path, filter_id, chart_type, directory)` — one bar per trust showing cost_pp within that directorate + - `get_trust_dosing(db_path, filter_id, chart_type, directory)` — drug dosing intervals broken down by trust within a directorate + - `get_trust_heatmap(db_path, filter_id, chart_type, directory)` — trust × drug matrix for one directorate (rows=trusts, cols=drugs) + - `get_trust_durations(db_path, filter_id, chart_type, directory)` — drug durations by trust within a directorate + - `get_directorate_pathway_costs(db_path, filter_id, chart_type, directory)` — pathway costs filtered to one directorate (same as existing `get_pathway_costs` with directory param, but verify it works correctly) +- [ ] Add thin wrappers in `dash_app/data/queries.py` +- [ ] Verify all queries return correct data for both "directory" and "indication" chart types +- **Checkpoint**: All 6 query functions return correct per-trust data for sample directorates + +### 10.7 Trust Comparison landing page + directorate selector +- [ ] Create Trust Comparison view component with two states: + - **Landing**: Grid of directorate/indication buttons (source: reference-data store) + - **Dashboard**: 6-chart layout for selected directorate (see 10.8) +- [ ] Directorate buttons: ~14 for "By Directory" mode, ~32 for "By Indication" mode (from chart type toggle) +- [ ] Clicking a button sets `selected_comparison_directorate` in app-state, switching to dashboard view +- [ ] Back button to return to landing page (clears `selected_comparison_directorate`) +- [ ] Apply layout design from 10.1 +- [ ] This view is shown when `active_view == "trust-comparison"` +- **Checkpoint**: Landing page shows directorate buttons, clicking one transitions to dashboard state, back button works + +### 10.8 Trust Comparison 6-chart dashboard +- [ ] Build 6-chart dashboard layout per design from 10.1 +- [ ] All 6 charts scoped to the selected directorate: + 1. **Market Share**: Drug breakdown per trust (using `get_trust_market_share`) + 2. **Cost Waterfall**: Per-trust cost within directorate (using `get_trust_cost_waterfall`) + 3. **Dosing**: Drug dosing intervals by trust (using `get_trust_dosing`) + 4. **Heatmap**: Trust × drug matrix (using `get_trust_heatmap`) + 5. **Duration**: Drug durations by trust (using `get_trust_durations`) + 6. **Cost Effectiveness**: Pathway costs within directorate, NOT split by trust (using `get_directorate_pathway_costs`) +- [ ] Create new visualization functions in `src/visualization/plotly_generator.py` where existing ones don't fit the trust-comparison perspective (may need `create_trust_market_share_figure`, `create_trust_heatmap_figure`, etc., or parameterize existing functions) +- [ ] All 6 charts respond to date filter and chart type toggle (global filters) +- [ ] Dashboard title shows selected directorate name +- [ ] Use `dcc.Loading` wrappers for each chart +- **Checkpoint**: All 6 charts render for a selected directorate, comparing drugs across trusts. Charts update when date filter or chart type changes. + +### 10.9 Patient Pathways filter relocation +- [ ] Drug/trust/directorate filter buttons (with count badges) only visible when on Patient Pathways view +- [ ] Hidden when on Trust Comparison view +- [ ] Placement per design from 10.1 (could be below the global sub-header, or inline with Patient Pathways content) +- [ ] Filter modals still work as before (drug modal, trust modal, directorate modal) +- [ ] "Clear All Filters" still works +- **Checkpoint**: Filters visible on Patient Pathways, hidden on Trust Comparison, all filter functionality preserved + +### 10.10 CSS updates + polish +- [ ] Global filter sub-header styling per design from 10.1 +- [ ] Trust Comparison landing page styling (directorate buttons grid) +- [ ] Trust Comparison dashboard grid styling (6-chart layout) +- [ ] Header fraction KPI styling +- [ ] Remove or repurpose `.kpi-row` / `.kpi-card` CSS +- [ ] Ensure responsive behavior +- [ ] Update `01_nhs_classic.html` if it serves as an ongoing design reference (or note that Phase 10 diverges) +- **Checkpoint**: All new components styled consistently with NHS design system + +### 10.11 Final integration + documentation +- [ ] Verify all views work: Patient Pathways (Icicle + Sankey), Trust Comparison (landing + 6-chart dashboard) +- [ ] Verify global filters (date, chart type) affect both views +- [ ] Verify Patient Pathways filters (drug, trust, directorate) only affect Patient Pathways +- [ ] Verify Trust Comparison directorate selector works for all directorates and indications +- [ ] Verify no regressions in Icicle and Sankey charts +- [ ] Test with both "directory" and "indication" chart types +- [ ] Update CLAUDE.md with new architecture (two views, state management, callback chains) +- [ ] `python run_dash.py` starts cleanly +- **Checkpoint**: Full application works end-to-end, documentation updated ✓ + +--- + ## Completion Criteria All tasks marked `[x]` AND: @@ -481,6 +610,20 @@ All tasks marked `[x]` AND: - [x] Icicle chart has no regressions - [x] `python run_dash.py` starts cleanly with all tabs +### Phase 10 Completion Criteria +- [ ] Sidebar has "Patient Pathways" + "Trust Comparison" navigation items +- [ ] Patient Pathways view shows Icicle + Sankey tabs only +- [ ] Trust Comparison landing page shows directorate/indication buttons +- [ ] Trust Comparison dashboard renders 6 charts for selected directorate (Market Share, Cost Waterfall, Dosing, Heatmap, Duration, Cost Effectiveness) +- [ ] All 6 Trust Comparison charts show per-trust breakdown within selected directorate (except Cost Effectiveness which is directorate-scoped, no trust split) +- [ ] KPI row removed — fraction KPIs (X/X patients, drugs, cost) in header +- [ ] Global filter sub-header (date + chart type) is prominent and constant across both views +- [ ] Drug/trust/directorate filters only visible on Patient Pathways +- [ ] Chart type toggle affects Trust Comparison (indication mode shows indication buttons) +- [ ] Date filter changes update both views +- [ ] Icicle + Sankey have no regressions +- [ ] `python run_dash.py` starts cleanly + --- ## Key Reference Files diff --git a/docs/PHASE10_DESIGN.md b/docs/PHASE10_DESIGN.md new file mode 100644 index 0000000..6380019 --- /dev/null +++ b/docs/PHASE10_DESIGN.md @@ -0,0 +1,740 @@ +# Phase 10 Design Specification + +## Aesthetic Direction + +**Utilitarian clinical** — authoritative, data-dense, no decoration. Every element earns its screen real estate. The NHS brand palette is law. The hierarchy is: + +1. Header (identity + live metrics) +2. Sub-header (global controls — always visible, always the same) +3. Sidebar (view switching) +4. Content (view-specific) + +Vertical rhythm: header 56px → sub-header 44px → content starts at 100px from top. + +--- + +## 1. Header Redesign + +### Layout + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ [NHS] HCD Analysis │ 3,847 / 11,118 39 / 42 £48.2M / £130.6M │ ● 11,118 patients Updated 2h ago │ +│ BRAND │ patients drugs cost │ FRESHNESS │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +The header stays 56px tall. The breadcrumb is REMOVED (it was redundant — the sidebar shows where you are). The middle section becomes **3 inline fraction KPIs**. The right section stays as data freshness. + +### HTML Structure (Dash) + +```python +html.Header(className="top-header", children=[ + # Left: brand (unchanged) + html.Div(className="top-header__brand", children=[ + html.Div("NHS", className="top-header__logo"), + html.Div(html.Div("HCD Analysis", className="top-header__title")), + ]), + + # Center: fraction KPIs + html.Div(className="top-header__kpis", children=[ + html.Div(className="header-kpi", children=[ + html.Span("—", id="kpi-filtered-patients", className="header-kpi__num"), + html.Span(" / ", className="header-kpi__sep"), + html.Span("—", id="kpi-total-patients", className="header-kpi__den"), + html.Span("patients", className="header-kpi__label"), + ]), + html.Div(className="header-kpi", children=[ + html.Span("—", id="kpi-filtered-drugs", className="header-kpi__num"), + html.Span(" / ", className="header-kpi__sep"), + html.Span("—", id="kpi-total-drugs", className="header-kpi__den"), + html.Span("drugs", className="header-kpi__label"), + ]), + html.Div(className="header-kpi", children=[ + html.Span("—", id="kpi-filtered-cost", className="header-kpi__num"), + html.Span(" / ", className="header-kpi__sep"), + html.Span("—", id="kpi-total-cost", className="header-kpi__den"), + html.Span("cost", className="header-kpi__label"), + ]), + ]), + + # Right: data freshness (unchanged structure, same IDs) + html.Div(className="top-header__right", children=[ + html.Span(children=[ + html.Span(className="status-dot"), + html.Span("...", id="header-record-count"), + ]), + html.Span(children=[ + "Updated: ", + html.Span("...", id="header-last-updated"), + ]), + ]), +]) +``` + +### CSS — New Classes + +```css +/* ── Header KPIs ── */ +.top-header__kpis { + display: flex; + align-items: center; + gap: 24px; +} +.header-kpi { + display: flex; + align-items: baseline; + gap: 3px; + color: rgba(255, 255, 255, 0.6); + font-size: 13px; + font-weight: 400; + white-space: nowrap; +} +.header-kpi__num { + color: var(--nhs-white); + font-size: 16px; + font-weight: 700; + font-variant-numeric: tabular-nums; +} +.header-kpi__sep { + color: rgba(255, 255, 255, 0.35); + font-weight: 300; + font-size: 14px; + margin: 0 1px; +} +.header-kpi__den { + color: rgba(255, 255, 255, 0.5); + font-size: 13px; + font-weight: 400; + font-variant-numeric: tabular-nums; +} +.header-kpi__label { + color: rgba(255, 255, 255, 0.4); + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-left: 4px; +} +``` + +### CSS — Modified Classes + +Remove `.top-header__breadcrumb` usage (delete from header.py, CSS can stay for backward compat or be removed). + +### Callback IDs + +- **Outputs (filtered values from chart-data)**: `kpi-filtered-patients`, `kpi-filtered-drugs`, `kpi-filtered-cost` +- **Outputs (total values from reference-data)**: `kpi-total-patients`, `kpi-total-drugs`, `kpi-total-cost` +- **Existing (unchanged)**: `header-record-count`, `header-last-updated` + +--- + +## 2. Global Filter Sub-Header + +### Layout + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ VIEW [By Directory] [By Indication] │ INITIATED [All years ▾] LAST SEEN [Last 6 months ▾] │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Sits directly below the header. Fixed position. Full width minus sidebar. Light blue-grey background (`#E8F0FE` — the same tint used for active sidebar items) with a subtle bottom border. Contains ONLY the chart type toggle and date filters — no drug/trust/directorate buttons. + +### HTML Structure (Dash) + +```python +html.Div(className="sub-header", children=[ + # Chart type toggle + html.Div(className="sub-header__group", children=[ + html.Span("View", className="sub-header__label"), + html.Div(className="toggle-pills", role="radiogroup", + **{"aria-label": "Chart view type"}, children=[ + html.Button("By Directory", id="chart-type-directory", + className="toggle-pill toggle-pill--active", + role="radio", n_clicks=0, **{"aria-checked": "true"}), + html.Button("By Indication", id="chart-type-indication", + className="toggle-pill", role="radio", + n_clicks=0, **{"aria-checked": "false"}), + ]), + ]), + # Divider + html.Div(className="sub-header__divider"), + # Date filters + html.Div(className="sub-header__group", children=[ + html.Span("Initiated", className="sub-header__label"), + dcc.Dropdown(id="filter-initiated", ...same options..., + className="filter-dropdown"), + ]), + html.Div(className="sub-header__group", children=[ + html.Span("Last seen", className="sub-header__label"), + dcc.Dropdown(id="filter-last-seen", ...same options..., + className="filter-dropdown"), + ]), +]) +``` + +### CSS — New Classes + +```css +/* ── Global Filter Sub-Header ── */ +.sub-header { + position: fixed; + top: 56px; /* below main header */ + left: var(--sidebar-w); /* right of sidebar */ + right: 0; + z-index: 150; + height: 44px; + background: #E8F0FE; + border-bottom: 1px solid #C5D4E8; + display: flex; + align-items: center; + padding: 0 24px; + gap: 16px; +} +.sub-header__group { + display: flex; + align-items: center; + gap: 8px; +} +.sub-header__label { + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--nhs-dark-blue); + white-space: nowrap; + opacity: 0.6; +} +.sub-header__divider { + width: 1px; + height: 24px; + background: rgba(0, 48, 135, 0.15); +} +``` + +### CSS — Modified Classes + +`.main` top margin increases from 56px to 100px (56px header + 44px sub-header): + +```css +.main { + margin-left: var(--sidebar-w); + margin-top: 100px; /* was 56px */ + padding: 24px; + min-height: calc(100vh - 100px); /* was 56px */ + display: flex; flex-direction: column; gap: 20px; +} +``` + +`.sidebar` top position increases to 56px (stays below main header, sub-header floats over content area): + +Actually, the sidebar should start below the header (56px), and the sub-header should start at the right of the sidebar. The sidebar extends from 56px to bottom. The sub-header is only in the content area. + +``` +┌──────────────────────────────────────────────────┐ +│ HEADER (56px) │ +├────────┬─────────────────────────────────────────┤ +│ │ SUB-HEADER (44px) │ +│ SIDE ├─────────────────────────────────────────┤ +│ BAR │ │ +│ (240) │ CONTENT AREA │ +│ │ │ +└────────┴─────────────────────────────────────────┘ +``` + +--- + +## 3. Trust Comparison Landing Page + +### Layout + +A clean selector grid. Each button is a card-like element showing the directorate/indication name. Arranged in a responsive grid — 3 columns for ~14 directorates, 4 columns for ~32 indications. + +``` +┌─────────────────────────────────────────────────────┐ +│ Trust Comparison │ +│ Select a directorate to compare drug usage across │ +│ trusts. │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │CARDIOLOGY│ │DERMATOL- │ │DIABETIC │ │ +│ │ │ │OGY │ │MEDICINE │ │ +│ │ 847 pts │ │ 423 pts │ │ 312 pts │ │ +│ │ 12 drugs│ │ 8 drugs│ │ 6 drugs│ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │GASTRO- │ │CLINICAL │ │MEDICAL │ │ +│ │ENTEROLOGY│ │HAEMATOL..│ │ONCOLOGY │ │ +│ │ 298 pts │ │ 567 pts │ │ 234 pts │ │ +│ │ 11 drugs│ │ 15 drugs│ │ 9 drugs│ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +│ ... │ +└─────────────────────────────────────────────────────┘ +``` + +Each card shows: directorate name (bold), patient count, drug count. Sorted by patient count descending. The blue left border on hover provides the NHS accent. + +### HTML Structure (Dash) + +```python +html.Div(className="tc-landing", id="trust-comparison-landing", children=[ + # Header + html.Div(className="tc-landing__header", children=[ + html.H2("Trust Comparison", className="tc-landing__title"), + html.P( + "Select a directorate to compare drug usage across trusts.", + className="tc-landing__desc", + id="tc-landing-desc", + ), + ]), + # Grid of directorate cards + html.Div(className="tc-landing__grid", id="tc-landing-grid", children=[ + # Populated by callback — one per directorate/indication + # Each card: + html.Button( + className="tc-card", + id={"type": "tc-selector", "index": "CARDIOLOGY"}, + n_clicks=0, + children=[ + html.Div("CARDIOLOGY", className="tc-card__name"), + html.Div(className="tc-card__stats", children=[ + html.Span("847 patients", className="tc-card__stat"), + html.Span("·", className="tc-card__dot"), + html.Span("12 drugs", className="tc-card__stat"), + ]), + ], + ), + # ... more cards + ]), +]) +``` + +### CSS — New Classes + +```css +/* ── Trust Comparison Landing ── */ +.tc-landing { + display: flex; + flex-direction: column; + gap: 24px; +} +.tc-landing__header { + padding: 0 0 8px; +} +.tc-landing__title { + font-size: 22px; + font-weight: 700; + color: var(--nhs-dark-blue); + margin-bottom: 4px; +} +.tc-landing__desc { + font-size: 14px; + color: var(--nhs-mid-grey); + font-weight: 400; +} +.tc-landing__grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; +} + +/* Directorate selector cards */ +.tc-card { + display: flex; + flex-direction: column; + gap: 8px; + padding: 16px 20px; + background: var(--nhs-white); + border: 1px solid var(--nhs-pale-grey); + border-left: 4px solid transparent; + cursor: pointer; + text-align: left; + font-family: inherit; + transition: border-color 0.15s, background 0.15s, box-shadow 0.15s; +} +.tc-card:hover { + border-left-color: var(--nhs-blue); + background: #FAFCFF; + box-shadow: 0 1px 4px rgba(0, 48, 135, 0.08); +} +.tc-card:focus-visible { + box-shadow: 0 0 0 3px var(--nhs-yellow); + z-index: 1; +} +.tc-card__name { + font-size: 14px; + font-weight: 700; + color: var(--nhs-dark-blue); + line-height: 1.3; +} +.tc-card__stats { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--nhs-mid-grey); +} +.tc-card__stat { + font-weight: 400; + font-variant-numeric: tabular-nums; +} +.tc-card__dot { + color: var(--nhs-pale-grey); +} +``` + +For indication mode (~32 buttons), switch to 4 columns: + +```css +/* Use this class when chart_type == "indication" */ +.tc-landing__grid--wide { + grid-template-columns: repeat(4, 1fr); +} +``` + +--- + +## 4. Trust Comparison 6-Chart Dashboard + +### Layout + +2-column × 3-row grid of chart cards. Each card has a small title and a `dcc.Graph`. A sticky top bar shows the selected directorate name + back button. + +``` +┌─────────────────────────────────────────────────────┐ +│ ← Back RHEUMATOLOGY — Trust Comparison │ +├────────────────────────┬────────────────────────────┤ +│ Market Share │ Cost Waterfall │ +│ ┌──────────────────┐ │ ┌──────────────────────┐ │ +│ │ dcc.Graph │ │ │ dcc.Graph │ │ +│ └──────────────────┘ │ └──────────────────────┘ │ +├────────────────────────┼────────────────────────────┤ +│ Dosing Intervals │ Drug × Trust Heatmap │ +│ ┌──────────────────┐ │ ┌──────────────────────┐ │ +│ │ dcc.Graph │ │ │ dcc.Graph │ │ +│ └──────────────────┘ │ └──────────────────────┘ │ +├────────────────────────┼────────────────────────────┤ +│ Treatment Duration │ Cost Effectiveness │ +│ ┌──────────────────┐ │ ┌──────────────────────┐ │ +│ │ dcc.Graph │ │ │ dcc.Graph │ │ +│ └──────────────────┘ │ └──────────────────────┘ │ +└────────────────────────┴────────────────────────────┘ +``` + +### HTML Structure (Dash) + +```python +html.Div(className="tc-dashboard", id="trust-comparison-dashboard", children=[ + # Dashboard header with back button + html.Div(className="tc-dashboard__header", children=[ + html.Button("← Back", id="tc-back-btn", className="tc-dashboard__back", + n_clicks=0), + html.H2(id="tc-dashboard-title", className="tc-dashboard__title", + children="RHEUMATOLOGY — Trust Comparison"), + ]), + # 6-chart grid + html.Div(className="tc-dashboard__grid", children=[ + _tc_chart_cell("Market Share", "tc-chart-market-share"), + _tc_chart_cell("Cost Waterfall", "tc-chart-cost-waterfall"), + _tc_chart_cell("Dosing Intervals", "tc-chart-dosing"), + _tc_chart_cell("Drug × Trust Heatmap", "tc-chart-heatmap"), + _tc_chart_cell("Treatment Duration", "tc-chart-duration"), + _tc_chart_cell("Cost Effectiveness", "tc-chart-cost-effectiveness"), + ]), +]) +``` + +Helper for each chart cell: +```python +def _tc_chart_cell(title, graph_id): + return html.Div(className="tc-chart-cell", children=[ + html.Div(title, className="tc-chart-cell__title"), + dcc.Loading(type="circle", color="#005EB8", children=[ + dcc.Graph( + id=graph_id, + config={"displayModeBar": False, "displaylogo": False}, + style={"height": "320px"}, + ), + ]), + ]) +``` + +### CSS — New Classes + +```css +/* ── Trust Comparison Dashboard ── */ +.tc-dashboard { + display: flex; + flex-direction: column; + gap: 16px; +} +.tc-dashboard__header { + display: flex; + align-items: center; + gap: 16px; +} +.tc-dashboard__back { + padding: 6px 12px; + font-size: 14px; + font-weight: 600; + font-family: inherit; + color: var(--nhs-blue); + background: var(--nhs-white); + border: 1px solid var(--nhs-pale-grey); + cursor: pointer; + transition: background 0.15s; + white-space: nowrap; +} +.tc-dashboard__back:hover { + background: #E8F0FE; +} +.tc-dashboard__back:focus-visible { + box-shadow: 0 0 0 3px var(--nhs-yellow); +} +.tc-dashboard__title { + font-size: 20px; + font-weight: 700; + color: var(--nhs-dark-blue); +} + +/* 2×3 chart grid */ +.tc-dashboard__grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} + +/* Individual chart cell */ +.tc-chart-cell { + background: var(--nhs-white); + border: 1px solid var(--nhs-pale-grey); + display: flex; + flex-direction: column; +} +.tc-chart-cell__title { + padding: 10px 16px; + font-size: 13px; + font-weight: 700; + color: var(--nhs-dark-blue); + text-transform: uppercase; + letter-spacing: 0.04em; + border-bottom: 1px solid var(--nhs-pale-grey); +} +``` + +--- + +## 5. Patient Pathways Filter Placement + +### Approach + +The drug/trust/directorate filter buttons sit in a **secondary filter strip** directly below the global sub-header. This strip is ONLY rendered when `active_view == "patient-pathways"`. It's a slimmer, lighter bar that reads as "view-specific controls" vs the sub-header's "global controls." + +``` +┌──────────────────────────────────────────────────────┐ ← HEADER (always) +├────────────────────────────────────────────────────── │ ← SUB-HEADER (always) +├──────────────────────────────────────────────────────┤ +│ Drugs (3) Trusts (2) Directorates │ Clear All │ ← PATHWAY FILTERS (Patient Pathways only) +├──────────────────────────────────────────────────────┤ +│ │ +│ [chart card with tabs + graph] │ +│ │ +└──────────────────────────────────────────────────────┘ +``` + +This strip uses the existing `.filter-btn` classes. It's rendered as part of the Patient Pathways view content (not fixed position) — it scrolls with the content. + +### HTML Structure (Dash) + +```python +# This goes inside the Patient Pathways view, at the top of its content area +html.Div(className="pathway-filters", id="pathway-filters", children=[ + html.Div(className="pathway-filters__buttons", children=[ + html.Button(children=[ + "Drugs", + html.Span(id="drug-count-badge", + className="filter-btn__badge filter-btn__badge--hidden"), + ], id="open-drug-modal", className="filter-btn", n_clicks=0), + + html.Button(children=[ + "Trusts", + html.Span(id="trust-count-badge", + className="filter-btn__badge filter-btn__badge--hidden"), + ], id="open-trust-modal", className="filter-btn", n_clicks=0), + + html.Button(children=[ + "Directorates", + html.Span(id="directorate-count-badge", + className="filter-btn__badge filter-btn__badge--hidden"), + ], id="open-directorate-modal", className="filter-btn", n_clicks=0), + ]), + html.Button("Clear All", id="clear-all-filters", + className="filter-btn filter-btn--clear", n_clicks=0), +]) +``` + +### CSS — New Classes + +```css +/* ── Patient Pathways Filter Strip ── */ +.pathway-filters { + background: var(--nhs-white); + border: 1px solid var(--nhs-pale-grey); + border-bottom: 2px solid var(--nhs-blue); + padding: 8px 20px; + display: flex; + align-items: center; + justify-content: space-between; +} +.pathway-filters__buttons { + display: flex; + align-items: center; + gap: 8px; +} +``` + +The bottom border `2px solid nhs-blue` gives it a subtle "active" feel that connects it visually to the chart content below. + +--- + +## Page Structure Summary + +### app.py Layout Assembly (Phase 10) + +```python +app.layout = dmc.MantineProvider(children=[ + # State stores + dcc.Store(id="app-state", storage_type="session", data={ + "chart_type": "directory", + "initiated": "all", + "last_seen": "6mo", + "date_filter_id": "all_6mo", + "selected_drugs": [], + "selected_directorates": [], + "selected_trusts": [], + "active_view": "patient-pathways", + "selected_comparison_directorate": None, + }), + dcc.Store(id="chart-data", storage_type="memory"), + dcc.Store(id="reference-data", storage_type="session"), + dcc.Store(id="active-tab", storage_type="memory", data="icicle"), + dcc.Location(id="url", refresh=False), + + # Page structure + make_header(), # Fixed, 56px, dark blue + make_sidebar(), # Fixed, 240px left, below header + make_sub_header(), # Fixed, 44px, light blue, right of sidebar + make_modals(), # Filter modals (drug, trust, directorate) + + html.Main(className="main", children=[ + # Content switched by active_view + html.Div(id="view-container", children=[ + # Patient Pathways view + html.Div(id="patient-pathways-view", children=[ + make_pathway_filters(), # Drug/trust/directorate buttons + make_chart_card(), # Tab bar + chart (Icicle + Sankey only) + ]), + # Trust Comparison view + html.Div(id="trust-comparison-view", style={"display": "none"}, children=[ + make_tc_landing(), # Directorate selector grid + make_tc_dashboard(), # 6-chart dashboard (hidden initially) + ]), + ]), + make_footer(), + ]), +]) +``` + +### Sidebar Changes + +```python +def make_sidebar(): + return html.Nav(className="sidebar", **{"aria-label": "Main navigation"}, children=[ + html.Div(className="sidebar__section", children=[ + html.Div("Analysis", className="sidebar__label"), + _sidebar_item("Patient Pathways", "pathway", + active=True, item_id="nav-patient-pathways"), + _sidebar_item("Trust Comparison", "compare", + active=False, item_id="nav-trust-comparison"), + ]), + html.Div(className="sidebar__footer", children=[ + "NHS Norfolk & Waveney ICB", + html.Br(), + "High Cost Drugs Programme", + ]), + ]) +``` + +New icon needed for "compare": +```python +_ICONS = { + "pathway": '...', # existing + "compare": '', # bar chart icon +} +``` + +### View Switching Callback + +```python +@app.callback( + Output("patient-pathways-view", "style"), + Output("trust-comparison-view", "style"), + Output("nav-patient-pathways", "className"), + Output("nav-trust-comparison", "className"), + Input("app-state", "data"), +) +def switch_view(app_state): + view = app_state.get("active_view", "patient-pathways") + show = {} + hide = {"display": "none"} + active_cls = "sidebar__item sidebar__item--active" + inactive_cls = "sidebar__item" + + if view == "patient-pathways": + return show, hide, active_cls, inactive_cls + else: + return hide, show, inactive_cls, active_cls +``` + +--- + +## CSS Variable Additions + +```css +:root { + /* ... existing ... */ + --sub-header-h: 44px; + --header-total-h: 100px; /* 56px header + 44px sub-header */ +} +``` + +Update `.main`: +```css +.main { + margin-left: var(--sidebar-w); + margin-top: var(--header-total-h); + padding: 24px; + min-height: calc(100vh - var(--header-total-h)); + display: flex; flex-direction: column; gap: 20px; +} +``` + +--- + +## Responsive Adjustments + +```css +@media (max-width: 1200px) { + .tc-landing__grid { grid-template-columns: repeat(2, 1fr); } + .tc-landing__grid--wide { grid-template-columns: repeat(3, 1fr); } +} +@media (max-width: 768px) { + .sidebar { display: none; } + .main { margin-left: 0; } + .sub-header { left: 0; } + .tc-landing__grid { grid-template-columns: 1fr; } + .tc-dashboard__grid { grid-template-columns: 1fr; } +} +``` diff --git a/progress.txt b/progress.txt index 1fc0cdf..cd5a8e7 100644 --- a/progress.txt +++ b/progress.txt @@ -1844,3 +1844,116 @@ Console error: `WARN: Multiple implied roots, cannot build icicle hierarchy of t - The completion signal should be output if all tasks in IMPLEMENTATION_PLAN.md are marked [x]. ### Blocked items: - None + +## Manual Intervention — 2026-02-06 +### Reason: Major UI restructure — split 8-tab single view into two-view architecture (Patient Pathways + Trust Comparison) +### Changes made: +- IMPLEMENTATION_PLAN.md — Added Phase 10 (Tasks 10.1–10.11) for two-view architecture + header redesign +- IMPLEMENTATION_PLAN.md — Added Phase 10 Completion Criteria +- guardrails.md — Added guardrails for two-view architecture, trust comparison queries, and frontend-design skill usage +- progress.txt — This manual intervention entry +### Tasks reset: None (Phase 9 work is still valid as foundation) +### Tasks added: +- 10.1: Design consultation via /frontend-design skill (header, sub-header, landing page, dashboard, filter placement) +- 10.2: State management + sidebar restructure (active_view, selected_comparison_directorate) +- 10.3: Header redesign — remove KPI row, add fraction KPIs +- 10.4: Global filter sub-header bar (date + chart type, prominent, constant across views) +- 10.5: Patient Pathways view — reduce to Icicle + Sankey only +- 10.6: Trust Comparison query functions (6 per-trust-within-directorate queries) +- 10.7: Trust Comparison landing page + directorate selector +- 10.8: Trust Comparison 6-chart dashboard +- 10.9: Patient Pathways filter relocation (drug/trust/directorate only on Patient Pathways) +- 10.10: CSS updates + polish +- 10.11: Final integration + documentation +### Context for next iteration: +- Phase 10 is entirely NEW work. No Phase 9 tasks were reset. The existing 8 chart implementations (query functions + visualization functions) are reusable foundations. +- Task 10.1 MUST come first — use the `/frontend-design` skill (not the frontend-developer agent) to design all layouts before building them. +- The key conceptual shift: Phase 9 charts compared all drugs across all directorates. Phase 10 Trust Comparison charts compare drugs WITHIN a single directorate ACROSS trusts. +- Existing query functions (get_drug_market_share, get_cost_waterfall, etc.) may need NEW variants or parameters for the trust-comparison perspective. The existing functions aggregate across trusts; the new ones must break down BY trust. +- Date filter + chart type toggle are GLOBAL (shared across both views). Drug/trust/directorate filters are PATIENT PATHWAYS ONLY. +- Trust Comparison has its own filter: a directorate selector (landing page buttons). When chart type is "By Indication", the buttons show indications instead of directorates. +- Cost Effectiveness moves to Trust Comparison but is NOT split by trust — it just filters pathways to the selected directorate. +- KPI row is removed. 3 KPIs move to header as fractions: "X / X patients", "X / X drugs", "£X / £X cost" where numerator = filtered, denominator = global total. +- The global filter sub-header (date + chart type) should be styled as a prominent, permanent fixture below the main header. +### New guardrails added: +- Two-view architecture: Patient Pathways vs Trust Comparison state isolation +- Trust Comparison queries: per-trust-within-directorate perspective +- Frontend-design skill: use /frontend-design skill, not frontend-developer agent + +## Iteration 33 — 2026-02-06 +### Task: Phase 10 — Task 10.1 (Design consultation via frontend-design skill) +### Why this task: +- First task in Phase 10, explicitly required to come before all other Phase 10 tasks +- Guardrails mandate using /frontend-design skill for design work +- All subsequent Phase 10 tasks depend on design decisions made here +### Status: COMPLETE +### What was done: +- Read all current components (header.py, filter_bar.py, sidebar.py, kpi_row.py, chart_card.py, app.py, nhs.css) to understand existing layout +- Used /frontend-design skill to design all 5 layout areas +- Created comprehensive design specification document: `docs/PHASE10_DESIGN.md` +- Design decisions for each area: + +**1. Header Redesign:** +- Remove breadcrumb (redundant — sidebar shows location) +- Add 3 inline fraction KPIs in center: `X / X patients`, `X / X drugs`, `£X / £X cost` +- Numerator = filtered (bold white), denominator = muted, label = uppercase tiny +- New CSS classes: `.top-header__kpis`, `.header-kpi`, `.header-kpi__num/sep/den/label` +- 6 new callback output IDs: `kpi-filtered-patients/drugs/cost`, `kpi-total-patients/drugs/cost` + +**2. Global Filter Sub-Header:** +- Fixed 44px bar below header, right of sidebar (`left: var(--sidebar-w)`) +- Background: `#E8F0FE` (light blue tint) with `#C5D4E8` bottom border +- Contains ONLY: chart type toggle pills + date filter dropdowns +- New CSS classes: `.sub-header`, `.sub-header__group/label/divider` +- `.main` margin-top increases from 56px to 100px (56 + 44) + +**3. Trust Comparison Landing Page:** +- Responsive grid of directorate cards (3-col for ~14 directorates, 4-col for ~32 indications) +- Each card: directorate name + patient count + drug count, blue left border on hover +- Pattern-matching IDs: `{"type": "tc-selector", "index": "CARDIOLOGY"}` +- New CSS classes: `.tc-landing`, `.tc-card`, `.tc-card__name/stats/stat/dot` + +**4. Trust Comparison 6-Chart Dashboard:** +- 2×3 grid of chart cells, each with title bar + dcc.Graph (320px height) +- Back button + directorate name title at top +- 6 graph IDs: `tc-chart-market-share`, `tc-chart-cost-waterfall`, `tc-chart-dosing`, `tc-chart-heatmap`, `tc-chart-duration`, `tc-chart-cost-effectiveness` +- New CSS classes: `.tc-dashboard`, `.tc-dashboard__grid`, `.tc-chart-cell` + +**5. Patient Pathways Filter Placement:** +- Secondary filter strip below sub-header, ONLY visible on Patient Pathways view +- Not fixed — scrolls with content (rendered inside view container) +- Blue bottom border (2px) connects it visually to chart content below +- New CSS class: `.pathway-filters`, `.pathway-filters__buttons` +- Reuses existing `.filter-btn` and `.filter-btn__badge` classes + +**Layout summary:** +- Header 56px → Sub-header 44px → Content at 100px from top +- Sidebar unchanged at 240px +- Two views: Patient Pathways (filter strip + chart card with Icicle + Sankey) and Trust Comparison (landing/dashboard) +- View switching via sidebar nav items with IDs `nav-patient-pathways`, `nav-trust-comparison` + +### Validation results: +- Tier 1 (Code): `from dash_app.app import app` — OK, no code changes (design doc only) +- Tier 2 (Design): Comprehensive spec document with exact CSS classes, HTML structure, callback IDs +### Files changed: +- `docs/PHASE10_DESIGN.md` — New: comprehensive design specification for all 5 areas +- `IMPLEMENTATION_PLAN.md` — Task 10.1 marked [x] +### Committed: 89f5b09 "docs: design specification for Phase 10 two-view architecture (Task 10.1)" +### Patterns discovered: +- The sub-header at 44px + header at 56px = 100px total fixed height. Using CSS variables (`--header-total-h: 100px`) makes this maintainable. +- Trust Comparison landing cards should show patient/drug counts to help users choose which directorate to explore — pure directorate names are insufficient context. +- The pathway filters strip (view-specific) should NOT be fixed-position — it scrolls with content, unlike the global sub-header which is fixed. +- For ~32 indications, 4-column grid prevents excessive vertical scrolling. For ~14 directorates, 3 columns provides better card sizing. +### Next iteration should: +- Start Task 10.2 — State management + sidebar restructure +- Key sub-steps: + 1. Add `active_view` and `selected_comparison_directorate` to app-state store defaults + 2. Update sidebar.py: rename "Pathway Overview" → "Patient Pathways", add "Trust Comparison" nav item with IDs from design doc + 3. Add "compare" icon to _ICONS dict in sidebar.py + 4. Add sidebar click callback → update `active_view` in app-state + 5. Add view switching callback: show/hide patient-pathways-view vs trust-comparison-view based on active_view + 6. Restructure app.py layout with view-container pattern from design doc +- Read `docs/PHASE10_DESIGN.md` "Page Structure Summary" section for exact layout assembly +- IMPORTANT: The sub-header component doesn't exist yet (that's Task 10.4). For 10.2, just restructure the state + sidebar + view switching. The existing filter_bar stays temporarily until 10.4 refactors it. +### Blocked items: +- None