feat: Trust Comparison landing page + directorate selector (Task 10.7)
- Add get_directorate_summary() query for per-directorate patient/drug counts - Create trust_comparison.py with landing grid and 6-chart dashboard layout - Wire directorate card clicks and back button through app-state callbacks - Add TC landing and dashboard CSS per Phase 10 design spec - Placeholder charts for 6 dashboard graphs (filled in Task 10.8) - Chart type toggle clears selected directorate when switching modes
This commit is contained in:
@@ -8,9 +8,11 @@ def register_callbacks(app):
|
||||
from dash_app.callbacks.kpi import register_kpi_callbacks
|
||||
from dash_app.callbacks.modals import register_modal_callbacks
|
||||
from dash_app.callbacks.navigation import register_navigation_callbacks
|
||||
from dash_app.callbacks.trust_comparison import register_trust_comparison_callbacks
|
||||
|
||||
register_filter_callbacks(app)
|
||||
register_chart_callbacks(app)
|
||||
register_kpi_callbacks(app)
|
||||
register_modal_callbacks(app)
|
||||
register_navigation_callbacks(app)
|
||||
register_trust_comparison_callbacks(app)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Callbacks for reference data loading and filter state management."""
|
||||
from datetime import datetime
|
||||
from dash import Input, Output, State, callback, ctx, no_update
|
||||
from dash import Input, Output, State, callback, ctx, no_update, ALL
|
||||
|
||||
|
||||
def _format_relative_time(iso_timestamp: str) -> str:
|
||||
@@ -75,13 +75,16 @@ def register_filter_callbacks(app):
|
||||
Input("trust-chips", "value"),
|
||||
Input("nav-patient-pathways", "n_clicks"),
|
||||
Input("nav-trust-comparison", "n_clicks"),
|
||||
Input({"type": "tc-selector", "index": ALL}, "n_clicks"),
|
||||
Input("tc-back-btn", "n_clicks"),
|
||||
State("app-state", "data"),
|
||||
)
|
||||
def update_app_state(
|
||||
_dir_clicks, _ind_clicks, initiated, last_seen, selected_drugs,
|
||||
selected_trusts, _nav_pp_clicks, _nav_tc_clicks, current_state
|
||||
selected_trusts, _nav_pp_clicks, _nav_tc_clicks,
|
||||
_tc_selector_clicks, _tc_back_clicks, current_state
|
||||
):
|
||||
"""Update app-state when chart type toggle, date filters, drug/trust chips, or sidebar nav change."""
|
||||
"""Update app-state when any filter, nav, or TC selector changes."""
|
||||
if not current_state:
|
||||
current_state = {
|
||||
"chart_type": "directory",
|
||||
@@ -99,6 +102,7 @@ def register_filter_callbacks(app):
|
||||
|
||||
# Determine chart type from toggle pills
|
||||
chart_type = current_state.get("chart_type", "directory")
|
||||
prev_chart_type = chart_type
|
||||
if triggered_id == "chart-type-directory":
|
||||
chart_type = "directory"
|
||||
elif triggered_id == "chart-type-indication":
|
||||
@@ -111,6 +115,21 @@ def register_filter_callbacks(app):
|
||||
elif triggered_id == "nav-trust-comparison":
|
||||
active_view = "trust-comparison"
|
||||
|
||||
# Trust Comparison directorate selection
|
||||
selected_comparison_directorate = current_state.get("selected_comparison_directorate")
|
||||
|
||||
# Handle TC card click (pattern-matching ID)
|
||||
if isinstance(triggered_id, dict) and triggered_id.get("type") == "tc-selector":
|
||||
selected_comparison_directorate = triggered_id["index"]
|
||||
|
||||
# Handle TC back button
|
||||
if triggered_id == "tc-back-btn":
|
||||
selected_comparison_directorate = None
|
||||
|
||||
# If chart type changed while a directorate is selected, return to landing
|
||||
if chart_type != prev_chart_type and selected_comparison_directorate is not None:
|
||||
selected_comparison_directorate = None
|
||||
|
||||
# Compute date_filter_id from dropdown values
|
||||
date_filter_id = f"{initiated}_{last_seen}"
|
||||
|
||||
@@ -124,6 +143,7 @@ def register_filter_callbacks(app):
|
||||
"selected_drugs": selected_drugs or [],
|
||||
"selected_trusts": selected_trusts or [],
|
||||
"active_view": active_view,
|
||||
"selected_comparison_directorate": selected_comparison_directorate,
|
||||
}
|
||||
|
||||
# Toggle pill CSS classes
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
"""Callbacks for Trust Comparison landing page and dashboard navigation."""
|
||||
from dash import html, Input, Output, State, ctx, no_update
|
||||
import plotly.graph_objects as go
|
||||
|
||||
|
||||
def register_trust_comparison_callbacks(app):
|
||||
"""Register Trust Comparison view callbacks."""
|
||||
|
||||
@app.callback(
|
||||
Output("tc-landing-grid", "children"),
|
||||
Output("tc-landing-grid", "className"),
|
||||
Output("tc-landing-desc", "children"),
|
||||
Input("app-state", "data"),
|
||||
)
|
||||
def populate_landing_grid(app_state):
|
||||
"""Populate the landing page grid with directorate/indication cards."""
|
||||
if not app_state:
|
||||
return [], "tc-landing__grid", "Select a directorate to compare drug usage across trusts."
|
||||
|
||||
from dash_app.data.queries import get_directorate_summary
|
||||
|
||||
chart_type = app_state.get("chart_type", "directory")
|
||||
date_filter_id = app_state.get("date_filter_id", "all_6mo")
|
||||
|
||||
summaries = get_directorate_summary(date_filter_id, chart_type)
|
||||
|
||||
# Build card buttons
|
||||
cards = []
|
||||
for item in summaries:
|
||||
name = item["name"]
|
||||
patients = item["patients"]
|
||||
drugs = item["drugs"]
|
||||
cards.append(
|
||||
html.Button(
|
||||
className="tc-card",
|
||||
id={"type": "tc-selector", "index": name},
|
||||
n_clicks=0,
|
||||
children=[
|
||||
html.Div(name, className="tc-card__name"),
|
||||
html.Div(
|
||||
className="tc-card__stats",
|
||||
children=[
|
||||
html.Span(
|
||||
f"{patients:,} patients",
|
||||
className="tc-card__stat",
|
||||
),
|
||||
html.Span("\u00b7", className="tc-card__dot"),
|
||||
html.Span(
|
||||
f"{drugs} drugs",
|
||||
className="tc-card__stat",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
# Grid class: wider for indication mode (more items)
|
||||
grid_cls = "tc-landing__grid"
|
||||
if chart_type == "indication":
|
||||
grid_cls += " tc-landing__grid--wide"
|
||||
|
||||
# Description text adapts to chart type
|
||||
if chart_type == "indication":
|
||||
desc = "Select an indication to compare drug usage across trusts."
|
||||
else:
|
||||
desc = "Select a directorate to compare drug usage across trusts."
|
||||
|
||||
return cards, grid_cls, desc
|
||||
|
||||
@app.callback(
|
||||
Output("trust-comparison-landing", "style"),
|
||||
Output("trust-comparison-dashboard", "style"),
|
||||
Output("tc-dashboard-title", "children"),
|
||||
Input("app-state", "data"),
|
||||
)
|
||||
def toggle_tc_subviews(app_state):
|
||||
"""Toggle between landing page and 6-chart dashboard."""
|
||||
if not app_state:
|
||||
return {}, {"display": "none"}, ""
|
||||
|
||||
selected = app_state.get("selected_comparison_directorate")
|
||||
show = {}
|
||||
hide = {"display": "none"}
|
||||
|
||||
if selected:
|
||||
chart_type = app_state.get("chart_type", "directory")
|
||||
label = "Indication" if chart_type == "indication" else "Directorate"
|
||||
title = f"{selected} \u2014 Trust Comparison"
|
||||
return hide, show, title
|
||||
else:
|
||||
return show, hide, ""
|
||||
|
||||
# Dashboard chart rendering will be added in Task 10.8.
|
||||
# For now, register empty figure placeholders for the 6 chart IDs
|
||||
# so the dcc.Graph components don't error on load.
|
||||
_tc_chart_ids = [
|
||||
"tc-chart-market-share",
|
||||
"tc-chart-cost-waterfall",
|
||||
"tc-chart-dosing",
|
||||
"tc-chart-heatmap",
|
||||
"tc-chart-duration",
|
||||
"tc-chart-cost-effectiveness",
|
||||
]
|
||||
|
||||
for chart_id in _tc_chart_ids:
|
||||
@app.callback(
|
||||
Output(chart_id, "figure"),
|
||||
Input("app-state", "data"),
|
||||
prevent_initial_call=True,
|
||||
)
|
||||
def _placeholder_chart(app_state, _cid=chart_id):
|
||||
"""Placeholder — returns empty figure until Task 10.8 implements real charts."""
|
||||
selected = (app_state or {}).get("selected_comparison_directorate")
|
||||
if not selected:
|
||||
return no_update
|
||||
fig = go.Figure()
|
||||
fig.update_layout(
|
||||
template="plotly_white",
|
||||
margin=dict(l=20, r=20, t=30, b=20),
|
||||
height=300,
|
||||
annotations=[
|
||||
dict(
|
||||
text="Chart will be implemented in Task 10.8",
|
||||
xref="paper", yref="paper",
|
||||
x=0.5, y=0.5, showarrow=False,
|
||||
font=dict(size=14, color="#999"),
|
||||
)
|
||||
],
|
||||
)
|
||||
return fig
|
||||
Reference in New Issue
Block a user