docs: design specification for Phase 10 two-view architecture (Task 10.1)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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": '<rect x="3" y="3" width="7" height="7"/>...', # existing
|
||||
"compare": '<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>', # 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; }
|
||||
}
|
||||
```
|
||||
+113
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user