8e2e2b7125
- Add Heatmap tab to Patient Pathways TAB_DEFINITIONS (was only in ALL_TAB_DEFINITIONS) - Add dmc.SegmentedControl (Patients/Cost/Cost p.a.) to PP chart card header, hidden by default - update_chart callback controls toggle visibility via heatmap-metric-wrapper style output - _render_heatmap() now accepts metric param from toggle - Add dmc.SegmentedControl to TC heatmap chart cell inline - tc_heatmap callback reads tc-heatmap-metric-toggle value and passes metric to figure fn
263 lines
11 KiB
Python
263 lines
11 KiB
Python
"""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, ""
|
|
|
|
# --- Trust Comparison dashboard charts (6 charts) ---
|
|
|
|
def _tc_empty(message):
|
|
"""Return a blank figure with a centered message for TC dashboard."""
|
|
fig = go.Figure()
|
|
fig.update_layout(
|
|
xaxis={"visible": False}, yaxis={"visible": False},
|
|
plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)",
|
|
margin={"t": 0, "l": 0, "r": 0, "b": 0}, height=300,
|
|
annotations=[{
|
|
"text": message, "xref": "paper", "yref": "paper",
|
|
"x": 0.5, "y": 0.5, "showarrow": False,
|
|
"font": {"size": 14, "color": "#768692", "family": "Source Sans 3"},
|
|
"xanchor": "center", "yanchor": "middle",
|
|
}],
|
|
)
|
|
return fig
|
|
|
|
def _tc_title(app_state):
|
|
"""Generate a short title suffix from global filter state."""
|
|
chart_type = (app_state or {}).get("chart_type", "directory")
|
|
label = "By Indication" if chart_type == "indication" else "By Directory"
|
|
initiated = (app_state or {}).get("initiated", "all")
|
|
last_seen = (app_state or {}).get("last_seen", "6mo")
|
|
i_labels = {"all": "All years", "1yr": "Last 1 yr", "2yr": "Last 2 yrs"}
|
|
l_labels = {"6mo": "6 mo", "12mo": "12 mo"}
|
|
return f"{label} | {i_labels.get(initiated, 'All')} / {l_labels.get(last_seen, '6 mo')}"
|
|
|
|
# 1. Market Share — drug breakdown per trust
|
|
@app.callback(
|
|
Output("tc-chart-market-share", "figure"),
|
|
Input("app-state", "data"),
|
|
prevent_initial_call=True,
|
|
)
|
|
def tc_market_share(app_state):
|
|
selected = (app_state or {}).get("selected_comparison_directorate")
|
|
if not selected:
|
|
return no_update
|
|
from dash_app.data.queries import get_trust_market_share
|
|
from visualization.plotly_generator import create_trust_market_share_figure
|
|
filter_id = app_state.get("date_filter_id", "all_6mo")
|
|
chart_type = app_state.get("chart_type", "directory")
|
|
try:
|
|
data = get_trust_market_share(filter_id, chart_type, selected)
|
|
except Exception:
|
|
return _tc_empty("Failed to load market share data.")
|
|
if not data:
|
|
return _tc_empty("No market share data for this selection.")
|
|
return create_trust_market_share_figure(data, _tc_title(app_state))
|
|
|
|
# 2. Cost Waterfall — cost per patient by trust
|
|
@app.callback(
|
|
Output("tc-chart-cost-waterfall", "figure"),
|
|
Input("app-state", "data"),
|
|
prevent_initial_call=True,
|
|
)
|
|
def tc_cost_waterfall(app_state):
|
|
selected = (app_state or {}).get("selected_comparison_directorate")
|
|
if not selected:
|
|
return no_update
|
|
from dash_app.data.queries import get_trust_cost_waterfall
|
|
from visualization.plotly_generator import create_cost_waterfall_figure
|
|
filter_id = app_state.get("date_filter_id", "all_6mo")
|
|
chart_type = app_state.get("chart_type", "directory")
|
|
try:
|
|
data = get_trust_cost_waterfall(filter_id, chart_type, selected)
|
|
except Exception:
|
|
return _tc_empty("Failed to load cost data.")
|
|
if not data:
|
|
return _tc_empty("No cost data for this selection.")
|
|
# Reuse existing waterfall figure — map trust_name to directory key
|
|
mapped = [{"directory": d["trust_name"], "patients": d["patients"],
|
|
"total_cost": d["total_cost"], "cost_pp": d["cost_pp"]} for d in data]
|
|
return create_cost_waterfall_figure(mapped, _tc_title(app_state), is_trust_comparison=True)
|
|
|
|
# 3. Dosing — drug dosing intervals by trust
|
|
@app.callback(
|
|
Output("tc-chart-dosing", "figure"),
|
|
Input("app-state", "data"),
|
|
prevent_initial_call=True,
|
|
)
|
|
def tc_dosing(app_state):
|
|
selected = (app_state or {}).get("selected_comparison_directorate")
|
|
if not selected:
|
|
return no_update
|
|
from dash_app.data.queries import get_trust_dosing
|
|
from visualization.plotly_generator import create_dosing_figure
|
|
filter_id = app_state.get("date_filter_id", "all_6mo")
|
|
chart_type = app_state.get("chart_type", "directory")
|
|
try:
|
|
data = get_trust_dosing(filter_id, chart_type, selected)
|
|
except Exception:
|
|
return _tc_empty("Failed to load dosing data.")
|
|
if not data:
|
|
return _tc_empty("No dosing data for this selection.")
|
|
# Add directory field expected by _dosing_by_trust helper
|
|
for d in data:
|
|
d["directory"] = selected
|
|
return create_dosing_figure(data, _tc_title(app_state), group_by="trust")
|
|
|
|
# 4. Heatmap — trust x drug matrix
|
|
@app.callback(
|
|
Output("tc-chart-heatmap", "figure"),
|
|
Input("app-state", "data"),
|
|
Input("tc-heatmap-metric-toggle", "value"),
|
|
prevent_initial_call=True,
|
|
)
|
|
def tc_heatmap(app_state, heatmap_metric):
|
|
selected = (app_state or {}).get("selected_comparison_directorate")
|
|
if not selected:
|
|
return no_update
|
|
from dash_app.data.queries import get_trust_heatmap
|
|
from visualization.plotly_generator import create_trust_heatmap_figure
|
|
filter_id = app_state.get("date_filter_id", "all_6mo")
|
|
chart_type = app_state.get("chart_type", "directory")
|
|
metric = heatmap_metric or "patients"
|
|
try:
|
|
data = get_trust_heatmap(filter_id, chart_type, selected)
|
|
except Exception:
|
|
return _tc_empty("Failed to load heatmap data.")
|
|
if not data.get("trusts") or not data.get("drugs"):
|
|
return _tc_empty("No heatmap data for this selection.")
|
|
return create_trust_heatmap_figure(data, _tc_title(app_state), metric=metric)
|
|
|
|
# 5. Duration — drug durations by trust
|
|
@app.callback(
|
|
Output("tc-chart-duration", "figure"),
|
|
Input("app-state", "data"),
|
|
prevent_initial_call=True,
|
|
)
|
|
def tc_duration(app_state):
|
|
selected = (app_state or {}).get("selected_comparison_directorate")
|
|
if not selected:
|
|
return no_update
|
|
from dash_app.data.queries import get_trust_durations
|
|
from visualization.plotly_generator import create_trust_duration_figure
|
|
filter_id = app_state.get("date_filter_id", "all_6mo")
|
|
chart_type = app_state.get("chart_type", "directory")
|
|
try:
|
|
data = get_trust_durations(filter_id, chart_type, selected)
|
|
except Exception:
|
|
return _tc_empty("Failed to load duration data.")
|
|
if not data:
|
|
return _tc_empty("No duration data for this selection.")
|
|
return create_trust_duration_figure(data, _tc_title(app_state))
|
|
|
|
# 6. Cost Effectiveness — pathway costs within directorate (NOT split by trust)
|
|
@app.callback(
|
|
Output("tc-chart-cost-effectiveness", "figure"),
|
|
Input("app-state", "data"),
|
|
prevent_initial_call=True,
|
|
)
|
|
def tc_cost_effectiveness(app_state):
|
|
selected = (app_state or {}).get("selected_comparison_directorate")
|
|
if not selected:
|
|
return no_update
|
|
from dash_app.data.queries import get_pathway_costs
|
|
from data_processing.parsing import calculate_retention_rate
|
|
from visualization.plotly_generator import create_cost_effectiveness_figure
|
|
filter_id = app_state.get("date_filter_id", "all_6mo")
|
|
chart_type = app_state.get("chart_type", "directory")
|
|
try:
|
|
data = get_pathway_costs(filter_id, chart_type, directory=selected)
|
|
except Exception:
|
|
return _tc_empty("Failed to load pathway cost data.")
|
|
if not data:
|
|
return _tc_empty("No pathway cost data for this selection.")
|
|
retention = calculate_retention_rate(data)
|
|
return create_cost_effectiveness_figure(data, retention, _tc_title(app_state))
|