All documentation Download (Markdown) Technical

1177 · 1177.pdhc.se

Technical manual


1177.pdhc — Technical specification

1177.pdhc is the platform's patient-facing form host: it ingests a FHIR
questionnaire targeted at a patient (pushed by request.pdhc), renders it in
a 1177-style web UI, collects the answers, produces a FHIR R5
QuestionnaireResponse, and dispatches it to gateway.pdhc. Internal DB
name forms_1177; branding "1177 Forms".

1. Data flow

plan.pdhc (Questionnaire) → request.pdhc (ServiceRequest Bundle)
        │  POST /api/webhook/inbound  (X-API-Key)
        ▼
   Assignment (one per Questionnaire, status=pending)
        │  operator/patient fills + submits (UI or API)
        ▼
   QuestionnaireResponse (FHIR R5, stored)  ──►  gateway.pdhc
        POST {GATEWAY_URL}/api/v1/provider/report/<request_guid>
        headers: X-Provider-Token; body {patient_guid, grant_token,
                 status:"completed", report_payload:<QuestionnaireResponse>}

2. HTTP API

Health (routes/health.py) — GET /api/health, no auth; runs SELECT 1,
returns {status, database}; CORS Access-Control-Allow-Origin: https://www.pdhc.se (so services.html can read it).

Webhook (routes/webhook.py, @require_api_key) —
POST /api/webhook/inbound: parse the FHIR Bundle, read delivery meta.tag
(system https://pdhc.se/delivery: grant_token, contract_guid,
organisation_guid, expires_at), unwrap the ServiceRequest + contained
Patient + each Questionnaire, strip the questionnaire- id prefix, and
create one Assignment per Questionnaire (pending). 201 with the created
assignments; 400 if no Questionnaire is inlined (request.pdhc must resolve it
from plan.pdhc first).

Assignments REST (routes/assignments.py, @require_api_key):

Method Path Notes
GET /api/assignments?patient_guid=&status=&limit=&offset= paginated summaries (patient_guid required)
GET /api/assignments/<guid> full assignment
POST /api/assignments/<guid>/submit body {items:[{linkId,text,answer:[…]}]} → builds QR, dispatches; 409 if already completed/cancelled/expired
POST /api/assignments/<guid>/cancel pending/opened → cancelled

Admin web UI (routes/admin.py, @login_required): /dashboard,
/assignments (paginated, archive-aware), /assignments/<guid> (renders the
form; auto pending→opened; read-only once terminal), /assignments/<guid>/submit,
/assignments/<guid>/archive, /settings, /settings/api-keys*,
/settings/cleanup* (bulk delete guarded by confirm=DELETE). Login/keys via
pdhc_keyauth (/login, /logout, /keyauth/*).

Not live: routes/inbound.py (/inbound/push) exists but its blueprint is
not registered — dead code, ignore.

3. Data model (app/models/)

Table Purpose Key columns
assignments one questionnaire assigned to a patient assignment_guid, patient_guid, form_guid, form_version, questionnaire_fhir (JSON, verbatim), request_guid, grant_token, contract_guid, organisation_guid, grant_expires_at, status (pending|opened|completed|expired|cancelled), response_guid, archived
questionnaire_responses submitted answers response_guid, form_guid, form_version, patient_guid, assignment_guid, fhir_json (JSON QR), submitted_at
api_keys service/webhook keys (sha256, scoped, revocable) key_hash, name, scope, is_active, expires_at

Plus the shared pdhc_keyauth tables: keyauth_users, keyauth_keys,
keyauth_access_log (patient-data access audit). Schema via
app/scripts/init_db.py (db.create_all()); ad-hoc migrations:
flask migrate-assignments, flask migrate-archived.

4. FHIR

5. Auth

6. Config & deployment

.env (see .env.example — but note the app reads GATEWAY_URL
(default https://gateway.pdhc.se) and GATEWAY_PROVIDER_TOKEN, whereas
the example file still lists stale GATEKEEPER_* names):

DATABASE_URL=postgresql+psycopg://forms_user:…@localhost:9037/forms_1177
API_KEY=<service webhook key>
SECRET_KEY / ALLOWED_ORIGINS
GATEWAY_URL=https://gateway.pdhc.se   GATEWAY_PROVIDER_TOKEN=<PAT>
BOOTSTRAP_ADMIN_USER / BOOTSTRAP_ADMIN_PASSWORD

Deploy: docker-compose.yml runs Postgres 16 only (forms_1177_db, volume
forms_1177_data, 127.0.0.1:9037). App runs under gunicorn via start.sh /
safe_restart.sh (bind 127.0.0.1:9036, 2 workers, timeout 30, daemon, PID
file, OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES for macOS). safe_restart.sh
pg_dumpalls to db_backups/ (keeps last 10) before restarting and runs a
5-attempt /api/health check. nginx (server_configs/…_ssl) terminates TLS
on 1177.pdhc.se and proxies to 127.0.0.1:9036.

7. Tests

tests/test_{webhook,routes,models,assignment_form}.py (~52 passing) —
webhook bundle parsing, REST + admin routes, models, and the type-aware form
rendering/submission.