feat: add trust selection to drawer with filter wiring (Task 5.1)

This commit is contained in:
Andrew Charlwood
2026-02-06 14:09:36 +00:00
parent e8145867c1
commit f0505ee43e
10 changed files with 104 additions and 17 deletions
+1
View File
@@ -25,6 +25,7 @@ app.layout = dmc.MantineProvider(
"date_filter_id": "all_6mo",
"selected_drugs": [],
"selected_directorates": [],
"selected_trusts": [],
}),
dcc.Store(id="chart-data", storage_type="memory"),
dcc.Store(id="reference-data", storage_type="session"),
+9
View File
@@ -32,6 +32,13 @@ def _generate_chart_title(app_state):
else:
parts.append(f"{len(selected_directorates)} directorates")
selected_trusts = app_state.get("selected_trusts") or []
if selected_trusts:
if len(selected_trusts) <= 2:
parts.append(", ".join(selected_trusts))
else:
parts.append(f"{len(selected_trusts)} trusts")
return " | ".join(parts) if parts else "All Patients"
@@ -53,12 +60,14 @@ def register_chart_callbacks(app):
chart_type = app_state.get("chart_type", "directory")
selected_drugs = app_state.get("selected_drugs") or None
selected_directorates = app_state.get("selected_directorates") or None
selected_trusts = app_state.get("selected_trusts") or None
return query_pathway_data(
filter_id=filter_id,
chart_type=chart_type,
selected_drugs=selected_drugs,
selected_directorates=selected_directorates,
selected_trusts=selected_trusts,
)
@app.callback(
+12 -10
View File
@@ -9,14 +9,16 @@ def register_drawer_callbacks(app):
Output("drug-drawer", "opened"),
Input("sidebar-drug-selection", "n_clicks"),
Input("sidebar-indications", "n_clicks"),
Input("sidebar-trust-selection", "n_clicks"),
prevent_initial_call=True,
)
def open_drawer(_drug_clicks, _indication_clicks):
"""Open the drawer when sidebar Drug Selection or Indications is clicked."""
def open_drawer(_drug_clicks, _indication_clicks, _trust_clicks):
"""Open the drawer when sidebar Drug Selection, Indications, or Trust Selection is clicked."""
return True
@app.callback(
Output("all-drugs-chips", "value"),
Output("trust-chips", "value"),
Input({"type": "drug-fragment", "index": ALL}, "n_clicks"),
Input("clear-drug-filters", "n_clicks"),
State("all-drugs-chips", "value"),
@@ -27,20 +29,20 @@ def register_drawer_callbacks(app):
"""Handle drug fragment badge click (substring match) or clear all filters.
Fragment click: find all full drug names containing the fragment substring,
toggle them in the chip selection.
Clear click: reset chip selection to empty.
toggle them in the chip selection. Trust chips unchanged.
Clear click: reset both drug and trust chip selections.
"""
triggered = ctx.triggered_id
# Clear button
# Clear button — reset both drug and trust chips
if triggered == "clear-drug-filters":
return []
return [], []
# Fragment badge click — triggered_id is a dict like {"type": "drug-fragment", "index": "DIR|FRAG"}
if isinstance(triggered, dict) and triggered.get("type") == "drug-fragment":
# Check if any fragment was actually clicked (not just initial render)
if not any(n for n in (fragment_clicks or []) if n):
return no_update
return no_update, no_update
fragment_key = triggered["index"] # e.g. "CARDIOLOGY|ABCIXIMAB"
fragment = fragment_key.split("|", 1)[-1] if "|" in fragment_key else fragment_key
@@ -55,7 +57,7 @@ def register_drawer_callbacks(app):
]
if not matching_drugs:
return no_update
return no_update, no_update
# Toggle: if all matching drugs are already selected, deselect them;
# otherwise, add them to selection
@@ -67,6 +69,6 @@ def register_drawer_callbacks(app):
else:
updated = current | set(matching_drugs)
return sorted(updated)
return sorted(updated), no_update
return no_update
return no_update, no_update
+6 -2
View File
@@ -32,12 +32,14 @@ def register_filter_callbacks(app):
Input("filter-initiated", "value"),
Input("filter-last-seen", "value"),
Input("all-drugs-chips", "value"),
Input("trust-chips", "value"),
State("app-state", "data"),
)
def update_app_state(
_dir_clicks, _ind_clicks, initiated, last_seen, selected_drugs, current_state
_dir_clicks, _ind_clicks, initiated, last_seen, selected_drugs,
selected_trusts, current_state
):
"""Update app-state when chart type toggle, date filters, or drug chips change."""
"""Update app-state when chart type toggle, date filters, drug chips, or trust chips change."""
if not current_state:
current_state = {
"chart_type": "directory",
@@ -46,6 +48,7 @@ def register_filter_callbacks(app):
"date_filter_id": "all_6mo",
"selected_drugs": [],
"selected_directorates": [],
"selected_trusts": [],
}
triggered_id = ctx.triggered_id
@@ -68,6 +71,7 @@ def register_filter_callbacks(app):
"last_seen": last_seen,
"date_filter_id": date_filter_id,
"selected_drugs": selected_drugs or [],
"selected_trusts": selected_trusts or [],
}
# Toggle pill CSS classes
+36 -2
View File
@@ -1,8 +1,9 @@
"""
Drug browser drawer component using Dash Mantine Components.
Filter drawer component using Dash Mantine Components.
Provides a right-side drawer with:
- "All Drugs" section: flat alphabetical list of all drugs from pathway_nodes
- "Trusts" section: all NHS trusts from pathway_nodes for trust filtering
- Directorate cards: grouped by PrimaryDirectorate from DimSearchTerm.csv,
with Accordion items per Search_Term containing drug fragment chips
- "Clear Filters" button at the bottom
@@ -11,7 +12,7 @@ Provides a right-side drawer with:
from dash import html
import dash_mantine_components as dmc
from dash_app.data.card_browser import build_directorate_tree, get_all_drugs
from dash_app.data.card_browser import build_directorate_tree, get_all_drugs, get_all_trusts
def _make_drug_chips(drugs: list[str]) -> dmc.ChipGroup:
@@ -27,6 +28,19 @@ def _make_drug_chips(drugs: list[str]) -> dmc.ChipGroup:
)
def _make_trust_chips(trusts: list[str]) -> dmc.ChipGroup:
"""Create a ChipGroup with multiple selection for the 'Trusts' section."""
return dmc.ChipGroup(
id="trust-chips",
multiple=True,
value=[],
children=[
dmc.Chip(trust, value=trust, size="xs")
for trust in trusts
],
)
def _make_directorate_card(directorate: str, indications: dict[str, list[str]]) -> dmc.AccordionItem:
"""
Create an AccordionItem for a single directorate.
@@ -97,6 +111,7 @@ def make_drawer():
Returns a dmc.Drawer that will be opened/closed via callbacks in Phase 4.2.
"""
drugs = get_all_drugs()
trusts = get_all_trusts()
directorate_tree = build_directorate_tree()
# All Drugs section
@@ -116,6 +131,23 @@ def make_drawer():
],
)
# Trusts section
trusts_section = html.Div(
className="drawer-section",
children=[
dmc.Text("Trusts", fw=700, size="sm", className="drawer-section-title"),
dmc.Text(
f"{len(trusts)} NHS trusts",
size="xs",
c="dimmed",
),
html.Div(
className="drawer-chips-wrap",
children=_make_trust_chips(trusts),
),
],
)
# Directorate cards section
directorate_items = [
_make_directorate_card(directorate, indications)
@@ -163,6 +195,8 @@ def make_drawer():
children=[
all_drugs_section,
dmc.Divider(),
trusts_section,
dmc.Divider(),
directorate_section,
clear_button,
],
+3 -1
View File
@@ -47,7 +47,9 @@ def make_sidebar():
_sidebar_item(
"Drug Selection", "drug", item_id="sidebar-drug-selection"
),
_sidebar_item("Trust Selection", "trust"),
_sidebar_item(
"Trust Selection", "trust", item_id="sidebar-trust-selection"
),
_sidebar_item("Directory Selection", "directory"),
_sidebar_item(
"Indications", "indication", item_id="sidebar-indications"
+12
View File
@@ -81,3 +81,15 @@ def get_all_drugs() -> list[str]:
data = load_initial_data()
return data.get("available_drugs", [])
def get_all_trusts() -> list[str]:
"""
Return a sorted flat list of all unique trust names from pathway_nodes level 1.
Delegates to load_initial_data() which already queries the database.
"""
from dash_app.data.queries import load_initial_data
data = load_initial_data()
return data.get("available_trusts", [])
+2
View File
@@ -26,6 +26,7 @@ def load_pathway_data(
chart_type: str = "directory",
selected_drugs: Optional[list[str]] = None,
selected_directorates: Optional[list[str]] = None,
selected_trusts: Optional[list[str]] = None,
) -> dict:
"""Load pre-computed pathway nodes with optional filters."""
return _load_pathway_nodes(
@@ -34,4 +35,5 @@ def load_pathway_data(
chart_type=chart_type,
selected_drugs=selected_drugs,
selected_directorates=selected_directorates,
selected_trusts=selected_trusts,
)