Technical manual
Read-only visualisation dashboard over observations delivered to gateway.pdhc
from upstream providers. Serves an eligible-patient list, cohort time-activity
curves, and a per-patient dashboard. Part of the PDHC microservice suite.
[provider.pdhc] ── push ──► [gateway.pdhc] ◄── GET Observation ── [dashboard.pdhc]
│
Postgres (9026)
│
Flask (9027)
dashboard_pdhc_db, port 9026).app/__init__.py — Flask app factory, config, logging to results/<ts>/app.log.app/models/__init__.py — SQLAlchemy models: User, OrgMembership,ObservationCache, RefreshLog.app/auth.py — AUTH_MODE=off bypass (dev SU), SSO stub, scope_to_user_orgsflask create-su CLI (Rule 23).app/services/gateway_client.py — GatewayClient.fetch_observations() callsGET {GATEWAY_BASE_URL}/fhir/Observation?organization=<org>; normalise()ObservationCache shape; refresh_org() is theRefreshLog.app/routes/views.py — HTML routes: /, /patient/<guid>, POST /refresh.app/routes/api.py — JSON API: /api/v1/series, plus /metadataapp/templates/ — base.html (PDHC styling, 12px), landing.html,patient.html (Chart.js time scatter).app/migrations/ — Alembic migrations (flask db upgrade)./refresh.OrgMembership rows for current user, callsrefresh_org(user, org) for each.refresh_org fetches bundle → normalises → replaces ObservationCacheRefreshLog with count + status.scope_to_user_orgs.DATABASE_URL — postgresql connection stringAPP_PORT — Flask port (default 9027)AUTH_MODE — off (dev) or sso (prod)GATEWAY_BASE_URL — e.g. https://gateway.pdhc.seGATEWAY_TOKEN — bearer token (keep in .env only; see API keys)SECRET_KEY — Flask session secretOBSERVATION_CACHE_TTL_HOURS — retention TTL for cached observationsObservationCache rows are a derived, time-bounded copy of patient
observations the dashboard pulled from gateway. They must not survive
indefinitely — the source of truth (gateway, CDRs) may block, scrub,
or update a row, and a stale dashboard copy would defeat both
right-to-block (PDL Ch 4 § 4) and need-to-know (PDL Ch 4 § 1).
The retention policy has two parts:
Time-based sweep. Rows whose fetched_at is older than
OBSERVATION_CACHE_TTL_HOURS (default 48h) are removed by the
flask cache-sweep CLI. Run hourly from cron on the macmini:
0 * * * * cd /usr/local/www/dashboard.pdhc/current \
&& docker exec dashboard_pdhc_app flask cache-sweep \
>> shared/logs/cache_sweep.log 2>&1
flask cache-sweep --dry-run reports the count that would be
removed without touching the table — useful when tuning TTL.
Targeted admin scrub. SU admins can immediately drop rows
matching patient_guid / org_guid / both via
POST /admin/cache/scrub:
json
{
"patient_guid": "...", // optional
"org_guid": "...", // optional; at least one required
"reason": "patient deletion request"
}
The endpoint writes a dashboard_audit row with
event_type='cache_scrub', admin_justification=<reason>,
n_rows_returned=<deleted_count>, and a payload_snapshot JSONB
carrying the verbatim filter + count. Non-SU callers get 403; an
empty filter returns 400 (a no-filter scrub would wipe everything).
Audit consumers (/admin/audit, #215) can join cache_scrub
rows with the same patient's read / admin_override rows to
reconstruct who saw what before it was scrubbed.
.env only, never committed. .gitignore excludes .env.GATEWAY_TOKEN in .env, restart with ./start.sh.| Path | Method | Purpose |
|---|---|---|
/healthz |
GET | liveness + current AUTH_MODE |
/metadata |
GET | FHIR R5 CapabilityStatement |
/ |
GET | landing page (eligible patients + optional cohort curves) |
/patient/<guid> |
GET | patient dashboard |
/refresh |
POST | pull observations from gateway for user's orgs |
/api/v1/series?patient&concept |
GET | FHIR Bundle of Observations |
/admin/cache/scrub |
POST | SU-only: drop ObservationCache rows by patient/org (#213) |
/admin/audit |
GET | SU-only: browse dashboard_audit rows with filters + pagination (#215) |
/admin/audit/export.csv |
GET | SU-only: CSV export of the filtered set (cap 50000 rows) (#215) |
./start.sh
docker compose down + venv deactivateapp/.venv/bin/python -m pytest app/tests
./scripts/test_api.sh # exercises live endpoints against a running instance