Skip to content

ADR-015: Separate Patient Service and Prescription HTTP Contracts ​

Status ​

Accepted

Context ​

The reference prescription workspace contains detailed clinical sections, patient search and prior-prescription history. The previous prescription HTTP routes used an undocumented in-memory store and reduced the clinical document to a small DTO. Patient registration and search need their own service boundary. Neighboring services already use validated Huma contracts with individual 501-only use cases.

Decision ​

Service ownership and composition ​

Patient is a separate Go module at services/patient, composed into the existing API server. It owns organization-scoped demographics, contact details, addresses, registration, editing and search. It does not own prescription content or a separate encounter/history repository.

Prescription owns doctor-authored clinical documents and history derived from issued/canceled prescriptions. Organization context controls sharing; the prescription retains its authoring doctor. Patient and organization references are immutable after creation.

Patient exposes patient/public.PatientReader through patient/module.Patients(). Prescription reuses patient-owned public DTOs and will use that narrow capability for authorized synchronous lookups. Patient has no dependency on Prescription. Neither service imports another service's internal packages or repositories.

Both services register their Huma operations in the runtime API and the offline OpenAPI generator. Inputs and intended success outputs generate a single contract for runtime documentation and the checked-in specification.

Minimal input and clinical content ​

Patient registration requires organization context, name, free-text age and sex. Age accepts arbitrary nonblank text, without a numeric value/unit structure. Email, phone, blood group and a single address string are optional. Birth date, allergies and chronic conditions are removed from profile contracts. Patient identifiers, MRN, audit actors and timestamps are server-owned. Duplicate names/phone numbers do not merge records.

Draft creation requires only patient_id. The future implementation derives the organization from the authorized patient and the author from authentication, never from client-supplied doctor IDs or X-User-ID/X-Org-ID headers. Each new consultation receives its own prescription ID; multiple drafts per patient are allowed. Encounter date and visit reason belong to Prescription.

Clinical DTOs preserve ordered complaints and qualifiers/custom clarifications, history, textual observations, investigation orders, diagnoses, medications with structured SIG, advice and follow-up. Catalog references are optional and display labels remain independent. Catalogs, templates, layout settings and external lab result entry are outside this contract release.

Updates and lifecycle ​

Patient PATCH preserves omissions, supports null clearing of optional profile fields, and rejects empty patches. Supplied name, age and sex cannot be null or blank. Normalized age changes refresh the server-managed age_recorded_at.

Prescription PATCH replaces only supplied clinical sections. Omitted sections remain unchanged, [] clears an array, and null clears nullable fields such as follow-up. Presence-aware DTO decoding distinguishes omission from explicit null. Referenced nullable objects use anyOf with null in OpenAPI; a supplementary HTTP check rejects non-nullable fields that the pinned Huma validator otherwise skips.

Prescription edit, issue and cancel requests require expected_version. The intended implementation atomically compares and increments version; stale writes return 409. A version-only PATCH is invalid. IDs, author, organization, status, audit timestamps and issuance snapshots cannot be written by clients.

The intended lifecycle permits draft to issued, draft to canceled, and issued to canceled. Issuance requires a linked patient and at least one diagnosis, medication, investigation or advice entry. Each medication needs frequency or a meaningful instruction. Issuance freezes clinical content, patient/doctor display snapshots and issued_at. Cancellation requires a nonblank reason, retains the record and records its actor/time. Repeated issue/cancel transitions return 409. Corrections require canceling the original and creating a new draft. Printing remains a separate frontend action.

Sharing and API conventions ​

Authorized active organization doctors, admins and staff can register/edit patient profiles. Drafts are author-private; issued/canceled prescription history is shared with authorized active organization members. Only the authoring doctor may mutate a prescription, with active organization affiliation. Out-of-scope identifiers return 404; known forbidden actions return 403 once permissions are implemented.

Collection queries require organization_id. Patient search accepts optional search (with q retained as a deprecated alias); prescriptions accept optional patient/doctor/status filters. Prescription pages use items and optional next_cursor. Patient HTTP lists use the common success envelope with a data array and full pagination metadata: next_cursor, current_page, per_page, total_pages and total_items. Totals include only authorized profiles matching the search. Both default to limit 20/max 100, ordered by created_at and ID descending. Cursors are opaque and bound to filters; Patient cursors also bind page size and current read access. Prescription-owned patient history includes issued/canceled summaries and recent observations from issued records; it does not claim investigation completion or external lab results.

Legacy in-memory prescription routes are replaced at /api/v1/prescriptions. PATCH replaces PUT updates, and POST cancellation replaces DELETE cancellation. Doctor owns patient portal endpoints at /api/v1/doctors/patients, delegating through Patient public capabilities. Generic Patient CRUD aliases are retired. No patient-keyed /drafts endpoints, physical deletion, patient merging, or independent deployment is introduced.

Implementation Status ​

Patient Phase 1 is implemented: PostgreSQL profiles, organization-local MRNs, organization-scoped safe create retries, normalized contacts, creator-based own/any restrictions, access-filtered pagination, versioned patches, subscription write enforcement and atomic audit. Doctor owns concrete named HTTP DTOs and four /api/v1/doctors/patients operations. Patient owns business use cases and exposes creator/lister/reader/patcher capabilities. Runtime and offline schema registration agree.

Doctor portal eligibility requires an active Identity account, professional profile and exact active organization affiliation. The composed authorization membership reader prefers ordinary membership, otherwise uses doctor_affiliation:<uuid>. Explicit audited administrator provisioning assigns the clinical_doctor role; no automatic enrollment or startup grants are introduced. Relationship transitions invalidate old grants. Patient's transaction-bound checks follow Identity account then Organization lock order and read grants on the same transaction.

The profile creator defines own only for organization permission restrictions. Email/version fields are profile-specific; Prescription Snapshot shares the current age/sex/address demographics contract. Idempotency is actor/organization/operation scoped, replays return current authorized data, and no-op updates preserve version. Each organization registers its own independent profile even when contacts match.

Prescription clinical operations remain validated 501 stubs; clinical persistence, issuance, history and immutable snapshots remain future implementation. Patient self-access is also deferred: verified contacts may identify internal candidates, but require profile-bound confirmation before any record is exposed. Matching never merges organization profiles or establishes doctor access.

See the Doctor patient workflow for HTTP envelopes, provisioning and rollout details and the patient plan at plans/patient-profile-implementation.md for the deferred connection design.

Consequences ​

Positive ​

  • Patient contracts evolve independently from prescription content and history.
  • Minimal registration avoids fabricated demographic data.
  • Curated contracts prevent circular dependencies and duplicate encounter storage.
  • Explicit mutation contracts preserve history and define concurrency behavior before persistence implementation.
  • Runtime and generated OpenAPI use the same service registrations.

Negative ​

  • Replacing legacy in-memory PUT/DELETE behavior is a breaking API change.
  • Frontend integration requires snake_case conversion, pagination and prescription-ID-based draft restoration.
  • Valid requests remain non-operational until business behavior is implemented.
  • Functional implementation must add authorization, transactions, snapshotting, version-race tests and canonical events after authoritative commits.

Documentation follows the source. Planned contracts are marked explicitly.