Dash — NHS Quickstart
Dash turns Python dataframes into interactive, NHS‑branded dashboards without learning React. Ideal for analyst/scientist‑led apps, prototyping operational KPIs, or sharing model outputs.
Great for: BI Analyst · Data Scientist · Clinician‑Researcher.
⚙️ 10‑minute install
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
pip install dash plotly pandas python-dotenv
# Optional SQL Server access
pip install sqlalchemy pyodbc
🚀 “Hello NHS” dashboard
Folder layout
dash-nhs/
.env
app.py
assets/nhs.css # optional branding (see Styling below)
.env
# Optional if using SQL Server
SQLSERVER_SERVER=YOURSERVER
SQLSERVER_DATABASE=NHS_Analytics
app.py (pick a data source below)
- Option A — Parquet (simplest)
- Option B — SQL Server
import dash
from dash import html, dcc, Input, Output
import plotly.express as px
import pandas as pd
# Example small dataset created on first run
try:
df = pd.read_parquet("out/kpi.parquet")
except Exception:
df = pd.DataFrame({
"practice_id": ["A","B","C","A","B","C"],
"total_appointments": [120, 95, 60, 130, 100, 70],
"month": ["2025-06","2025-06","2025-06","2025-07","2025-07","2025-07"]
})
app = dash.Dash(__name__)
app.title = "NHS KPI (Demo)"
app.layout = html.Div([
html.H2("NHS KPI Dashboard"),
dcc.Dropdown(sorted(df["month"].unique()), df["month"].iloc[0], id="month"),
dcc.Graph(id="chart")
], style={"maxWidth":"900px","margin":"40px auto"})
@app.callback(Output("chart","figure"), Input("month","value"))
def update(month):
d = df[df["month"]==month]
return px.bar(d, x="practice_id", y="total_appointments",
title=f"Appointments by Practice — {month}",
labels={"total_appointments":"Appointments","practice_id":"Practice"})
server = app.server # for production WSGI servers
if __name__ == "__main__":
app.run_server(debug=True)
import os, urllib.parse, pandas as pd
import dash
from dash import html, dcc, Input, Output
import plotly.express as px
from sqlalchemy import create_engine
# .env: SQLSERVER_SERVER, SQLSERVER_DATABASE
params = urllib.parse.quote_plus(
"DRIVER={ODBC Driver 18 for SQL Server};"
f"SERVER={os.getenv('SQLSERVER_SERVER')};"
f"DATABASE={os.getenv('SQLSERVER_DATABASE')};"
"Trusted_Connection=Yes;Encrypt=Yes;"
)
engine = create_engine(f"mssql+pyodbc:///?odbc_connect={params}")
df = pd.read_sql("""
SELECT practice_id, total_appointments, CONVERT(char(7), as_of, 126) AS month
FROM dbo.vw_PracticeKPI_Monthly
""", engine)
app = dash.Dash(__name__); app.title = "NHS KPI (Demo)"
app.layout = html.Div([
html.H2("NHS KPI Dashboard"),
dcc.Dropdown(sorted(df["month"].unique()), df["month"].iloc[0], id="month"),
dcc.Graph(id="chart")
], style={"maxWidth":"900px","margin":"40px auto"})
@app.callback(Output("chart","figure"), Input("month","value"))
def update(month):
d = df[df["month"]==month]
return px.bar(d, x="practice_id", y="total_appointments",
title=f"Appointments by Practice — {month}",
labels={"total_appointments":"Appointments","practice_id":"Practice"})
server = app.server
if __name__ == "__main__":
app.run_server(debug=True)
Run
python app.py
# open http://127.0.0.1:8050
🎨 Styling (NHS brand)
Place a CSS file in assets/nhs.css (Dash auto-loads from assets/). For example:
body { font-family: "Segoe UI", Roboto, Arial, sans-serif; }
h2 { color: #005EB8; } /* NHS blue */
.card { border: 1px solid #D8DDE0; border-radius: 8px; padding: 12px; }
For full NHS.UK styling, you can vend your own minimal CSS tokens or embed the NHS Frontend CSS compiled by your web team and include it as assets/nhs-frontend.css.
🧪 Health check (production)
Expose a simple health endpoint using the underlying Flask server:
# add after app = dash.Dash(...)
server = app.server
@server.get("/health")
def health():
return {"status":"ok"}
🐳 Containerise (production)
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir dash plotly pandas python-dotenv gunicorn
ENV PORT=8050
EXPOSE 8050
CMD ["gunicorn","-w","2","-b","0.0.0.0:8050","app:server"]
Deploy to AWS App Runner or Azure App Service / Container Apps (see platform pages).
🔒 NHS‑ready patterns
- Secrets: keep DB creds out of code; use env vars and a secret store (Key Vault / Secrets Manager).
- Caching: cache heavy queries to Parquet on a schedule; refresh via a CI job or ETL.
- RBAC: put Dash behind a reverse proxy with Entra ID/NHS Login; restrict by group.
- Small-number suppression: apply in SQL views or in export steps before display.
- Logging: avoid PHI; add request IDs and basic usage metrics.
🗓️ Week‑one build (repeatable, safe)
- Day 1 — Data contract: one authoritative view per KPI; document definitions.
- Day 2 — Automation: scheduled extract → Parquet; write to read-only share.
- Day 3 — Dashboard: dropdown + trend + “data last updated”; definitions popovers.
- Day 4 — Versioning: repo with app + SQL; PR reviews; simple CI.
- Day 5 — IG checks: secrets via store; small-number suppression; synthetic dev data.
📏 Measuring impact
- Latency: source load → dashboard refresh time.
- Reliability: % successful refreshes; uptime.
- Adoption: weekly active users; stakeholders using the dashboard.
- Quality: validation pass rate; number of rework cycles.
🔗 See also
What’s next?
You’ve completed the Learn — Dash stage. Keep momentum: