Care fulfilment: prescriptions, referrals & slots
Schema deep-dive · living document · #16 in the reading sequence
| Tables | public.prescriptions, public.diagnostics_referrals, public.provider_slots[1], public.appointment_reminders_sent[2] (db care, schema public) |
| Owner service | care (sole writer) |
| Locators | RX- / REF- / SLT- + 8 hex chars, minted app-side[3]; appointment_reminders_sent has no locator - natural key is appointment_locator |
| Last updated | 2026-08-21 |
| Companion | Care Pathways (narrative) · previous: Care (#11 - episodes + appointments; this page is its second ring) |
1. Scope and usage
This is the fulfilment layer beyond the appointment. The Care page (#11) covered the state-bearing spine - the episode (one health concern) and the appointment (a concrete booking inside it). What hangs off those two is what a clinician does during care: writes an e-prescription (prescriptions), makes an onward diagnostic referral (diagnostics_referrals), draws from a provider's bookable-slot inventory (provider_slots), and - operationally - records that a member's reminder was already sent (appointment_reminders_sent). All four live on the same public schema of the care db and are written only by the care service.
The fulfilment surface is modelled ahead of the operations that fill it, and that is the point of the design. The full CRUD-plus-lifecycle Go around these tables is real and reads cleanly, sitting ready for the partner integrations (Kry/Livi GP booking) and clinical operations that will drive e-prescribing, onward diagnostic referral and provider-slot inventory. The headroom is concrete: when e-prescribing goes live, medication is jsonb, so whatever shape the prescribing system emits lands without a migration, and the episode FK already ties the script to the right health concern; when onward referrals run, diagnostics_referrals already carries episode → referral → resulting appointment; when a provider-availability sync lands, provider_slots already holds bookable inventory with a status and an appointment back-pointer. §9 maps each of those to the file you would touch.
Live population as of 2026-08-21: 2 prescriptions, 2 diagnostics referrals, 3 provider slots and 1 reminder marker. The prescription and referral rows are seed data attached to a single legacy episode (EP-3991134f, party party-003), inserted directly into the DB rather than through the service, so no care.prescription.* or care.referral.* event exists in the outbox (§6). The one table already carrying live writes is appointment_reminders_sent: exactly one row, claimed by the T-24h reminder sweep for a real member's confirmed appointment (§6).
So the page reads as: here is the fulfilment surface already modelled and where you extend it (§9), and here is precisely what runs today (§6). Where the Care page documents the live booking path, this page documents the fulfilment layer that switches on when the partner integrations and clinical operations that use it come online.
2. Boundaries and relationships
| A fulfilment record is not… | That concern lives in | Join |
|---|---|---|
| the episode / appointment | care.episodes / care.appointments (the #11 spine); every prescription and referral belongs to an episode by real FK | episode_locator → episodes(locator)[4][5] |
| the provider directory | provider.providers; a slot names a provider it never joins, and AddSlot re-validates ACTIVE over HTTP[6] | provider_locator, no FK |
| the live slot search | the member-facing calendar (GET /slots) is served by a synthetic in-memory generator, not this table (see the Care page's slot-source seam) | provider_slots is the owned-inventory alternative, read only by the provider-scoped list[7] |
| the pharmacy / lab that fulfils it | reserved for the fulfilling system - status already walks ISSUED→FILLED/VOIDED and REFERRED→BOOKED/COMPLETED/CANCELLED in code, and a pharmacy or lab integration drives those transitions when one lands | status text only |
| the notification | notifications consumes care.events, but its router only handles care.appointment.*; the prescription/referral event types this layer emits fall through to no category[8] | Kafka, unconsumed |
| the coverage decision | eligibility; nothing here checks whether a prescription or referral is covered | - |
provider_locator and clinic_locator are soft references - text, no FK, the standard cross-service posture. The one structural exception this schema inherits from #11 is the locator-valued FK: prescriptions and diagnostics_referrals both REFERENCES episodes(locator), joining on the external identifier rather than a uuid - unique to care in the estate.
3. Structure
DDL[1][2] · Go models[9] · locator minting[3]
prescriptions
| Field | Type | Req | Notes |
|---|---|---|---|
id / locator | uuid / text | ✓ | PK / UNIQUE RX- + hex8 |
episode_locator | text | ✓ | Real FK to episodes(locator)[4] |
provider_locator | text | ✓ | Prescribing clinician; soft ref, no FK |
medication | jsonb | ✓ | Free-shape clinical payload - absorbs any medication shape without a migration (see below; the typed Medication struct beside it is unreferenced and its keys disagree, §9) |
status | text | ✓ | Default 'ISSUED'; ISSUED | FILLED | VOIDED by convention |
issued_at | timestamptz | ✓ | Caller-supplied, or now() if omitted[10] |
created_at | timestamptz | ✓ | Insert stamp |
diagnostics_referrals
| Field | Type | Req | Notes |
|---|---|---|---|
id / locator | uuid / text | ✓ | PK / UNIQUE REF- + hex8 |
episode_locator | text | ✓ | Real FK to episodes(locator)[5] |
referral_type | text | ✓ | Free text; live: physiotherapy, cardiology |
clinic_locator | text | Nullable; set at create or overwritten on book | |
status | text | ✓ | Default 'REFERRED'; REFERRED | BOOKED | COMPLETED | CANCELLED |
appointment_locator | text | Nullable back-ref set only by BookReferral; NULL on every live row | |
created_at | timestamptz | ✓ | Insert stamp |
provider_slots
| Field | Type | Req | Notes |
|---|---|---|---|
id / locator | uuid / text | ✓ | PK / UNIQUE SLT- + hex8 |
provider_locator | text | ✓ | Whose diary; indexed. No FK to the directory |
start_time, end_time | timestamptz | ✓ | The slot window |
status | text | ✓ | Default 'AVAILABLE'; AVAILABLE | BOOKED | BLOCKED; indexed |
appointment_locator | text | Back-pointer set on claim, cleared on release (see #11 §3) | |
created_at | timestamptz | ✓ | Insert stamp |
appointment_reminders_sent
| Field | Type | Req | Notes |
|---|---|---|---|
appointment_locator | text | ✓ | PRIMARY KEY - the whole point; one row = "reminded, don't again"[2] |
sent_at | timestamptz | ✓ | Default now(); when the marker was claimed |
Field-by-field: what and why
Locators - RX-8b953576, REF-995523a2, SLT-5e4dd8d8 - all three fulfilment tables mint the same way the episode/appointment do: prefix + first 8 hex of a uuid, app-side, no sequence and no year segment[3]. appointment_reminders_sent breaks the pattern deliberately: it has no locator at all because it is a dedupe ledger, not a domain entity - its identity is the appointment it guards.
prescriptions.medication - the free-shape column, and the struct beside it - the column is jsonb NOT NULL, and the Go model stores it as an opaque json.RawMessage: the handler passes the request body's medication straight through, the service never parses it, and the repository never inspects it[11]. There is a typed Medication struct { Name, Dosage, Frequency, Instructions } in the same models file[12] - but nothing references it, and its field names do not match the live data (which uses name / dose / days). That mismatch is a defect to fix, not a constraint on the column: the jsonb itself takes any medication shape a prescribing system emits (§9). The one validation is in the handler: medication must be non-empty, else 400[10].
Issuing a prescription - IssuePrescription first resolves the episode (the FK target must exist, else the insert would fail anyway), then inserts the row and enqueues care.prescription.issued on care.events in one transaction[11] - the same transactional-outbox posture as the rest of care. UpdatePrescriptionStatus walks fill→FILLED and void→VOIDED, each in its own tx with its own event (.filled / .voided)[13]. There is no current-state guard: fill on an already-VOIDED prescription would succeed, exactly as appointment transitions are unguarded (#11 §4).
Creating and booking a referral - CreateReferral mirrors the prescription path: episode-exists check, insert + care.referral.created in one tx[14]. UpdateReferralStatus handles complete/cancel transactionally with events[15]. BookReferral is the method that carries the episode → referral → appointment linkage: it sets status = BOOKED, overwrites clinic_locator, and stamps appointment_locator. It is also the one mutation here that does a plain Update, outside any transaction, and emits no event[16], breaking care's outbox invariant - a defect with a named fix in §9.
provider_slots - owned inventory the live path does not read. The claim / release / block cycle is documented in full on the Care page: booking an OWN_SLOT appointment flips the slot to BOOKED atomically, cancelling releases it. This page adds the two write paths that are about the table itself: AddSlot validates the provider is ACTIVE over HTTP then inserts an AVAILABLE slot[6], and BlockSlot flips AVAILABLE→BLOCKED (refusing any non-AVAILABLE slot)[17]. The member-facing calendar (GET /slots) never touches this table - it runs the synthetic generator - so the table is read only by the provider-scoped GET /providers/{id}/slots, whose query filters to status = 'AVAILABLE'[7]. All 3 live rows are AVAILABLE and belong to a retired stub provider, so they do not surface to a booking member today; a provider-availability sync writing real rows is what turns this inventory on (§6, §9).
appointment_reminders_sent - the one live-written table. The T-24h reminder sweep (#1630) has no scheduler behind it: it is an in-process ticker (default 15 minutes, CARE_REMINDER_SWEEP_MINUTES=0 disables) started in main[18]. Each pass lists CONFIRMED appointments starting within 24 hours[19], and for each one runs, in a single transaction, TryMarkReminderSent (INSERT … ON CONFLICT DO NOTHING) followed by the outbox enqueue - so only the run that wins the insert emits care.appointment.reminder[20][21]. The RowsAffected == 1 return is the whole invariant: the ledger PK makes "exactly once per appointment" true across restarts and replicas. An appointment whose episode has no resolvable party is skipped without claiming, so a later fix can still send inside the window[20].
4. Invariants
| Invariant | Enforced by |
|---|---|
| All three locators unique | DB UNIQUE constraints[1] |
| Prescription / referral belongs to a real episode | DB FK on the locator value[4][5] |
| One reminder per appointment, ever | DB PK + same-tx INSERT ON CONFLICT DO NOTHING claim[2][21] |
| Provider must be ACTIVE to add a slot | Application, HTTP to the provider service[6] |
| A slot can only be BLOCKED from AVAILABLE | Application check in BlockSlot[17]; nothing in the DB |
provider_type for slot search is GP/Physio/MentalHealth | Application map, handler 400 on anything else[22] |
| Write + its event share one commit | Application transaction for issue/fill/void, create/complete/cancel, reminder[11][20] - except BookReferral, which does neither[16] |
status / referral_type / medication-shape vocabularies | Nothing - no CHECKs; convention only, and the live medication keys already disagree with the typed struct |
| Prescription / referral status transitions form a valid machine | Nothing - UpdatePrescriptionStatus / UpdateReferralStatus apply any action to any current status[13][15] |
| Row changes captured to CDC | Debezium dbz_care covers prescriptions, diagnostics_referrals, provider_slots - but not appointment_reminders_sent (operational ledger, deliberately out) (live \d) |
5. Lifecycle
Prescriptions and referrals are small status machines; the intended shape is below, but per §4 no code guards the current state, so any arrow can be taken from any node.
Every transition except BookReferral writes its row and its care.events outbox message in one transaction[13][15]. provider_slots does not appear here because its lifecycle (AVAILABLE ⇄ BOOKED on claim/release, AVAILABLE → BLOCKED) is driven from the appointment side and documented on the Care page. appointment_reminders_sent has no lifecycle: a row is inserted once and never updated or deleted.
The reminder sweep is the only scheduled motion in the whole schema:
6. Populated example: the seed prescription vs the real reminder
Two rows tell the whole story of this layer - one seeded directly, one written by running code. medication clinical text is redacted; locators are real.
The prescription - RX-8b953576 (seeded, not yet through the write path)
{
"locator": "RX-8b953576",
"episode_locator": "EP-3991134f",
"provider_locator": "PRV-GP-001",
"medication": { "name": "[redacted]", "dose": "[redacted]", "days": 7 },
"status": "ISSUED",
"issued_at": "2026-06-14T10:00:00Z",
"created_at": "2026-06-14T22:11:05Z"
}| Key | Read by | What actually happens |
|---|---|---|
episode_locator: EP-3991134f | the real FK | resolves to a legacy seed episode - party-003, care_type GP, created 2026-05-28, not a PTY- member. Both prescriptions and both referrals hang off this one episode |
provider_locator: PRV-GP-001 | nothing | the retired in-code stub locator (#11 §6), not a PRV-YYYY-NNNNNN directory provider |
medication keys name/dose/days | nothing parses it | free-shape jsonb, which is why an arbitrary payload stores cleanly; note the keys do not match the unreferenced Medication struct's name/dosage/frequency/instructions[12] (§9) |
status: ISSUED | nothing | not yet advanced - no fill/void has run; all live prescriptions are ISSUED |
The tell that this is seed data, not code output: IssuePrescription enqueues care.prescription.issued in the same transaction as the insert[11]. The care outbox census as of 2026-08-21 holds zero care.prescription.* and zero care.referral.* rows (only care.episode.opened ×145, .closed ×1, care.appointment.confirmed ×3, .attended ×1, .reminder ×1). If the two prescriptions and two referrals had gone through the service, four events would be sitting in that outbox. They are not - the rows were inserted directly.
The referrals - both REFERRED, both un-booked
[
{ "locator": "REF-995523a2", "episode_locator": "EP-3991134f",
"referral_type": "physiotherapy", "clinic_locator": "CLN-PHYSIO-01",
"status": "REFERRED", "appointment_locator": null },
{ "locator": "REF-f073e3e9", "episode_locator": "EP-3991134f",
"referral_type": "cardiology", "clinic_locator": "CLN-CARDIO-01",
"status": "REFERRED", "appointment_locator": null }
]appointment_locator is NULL on both because BookReferral has not yet run: the referral → appointment linkage is modelled and not yet exercised (§9). clinic_locator values are stub CLN-* ids, not directory locators.
The reminder marker - APT-2b9efdea (the one real write)
{ "appointment_locator": "APT-2b9efdea", "sent_at": "2026-08-12T13:58:04Z" }| Fact | Evidence |
|---|---|
It guards a genuine, CONFIRMED, video appointment scheduled 2026-08-13T09:47Z | live join appointments ⋈ appointment_reminders_sent |
…for a real member PTY-2026-000741 on episode EP-2a343913 | live join through episodes |
The sweep emitted exactly one care.appointment.reminder | the outbox census above - count 1, matching this one marker |
sent_at is ~20 h before scheduled_at | inside the T-24h window - the sweep worked as designed |
This is the whole live footprint of the reminder subsystem: one appointment, one marker, one event. It is small but it is real, and it is the only table on this page written by running code rather than a seed script.
Live population, 2026-08-21: 2 prescriptions (both ISSUED), 2 diagnostics referrals (both REFERRED), 3 provider slots (all AVAILABLE, all PRV-GP-001), 1 reminder marker - against 141 episodes and 67 appointments in the same db.
7. Who references these tables
| Where | Column / mechanism | Meaning there |
|---|---|---|
care.episodes | prescriptions.episode_locator / diagnostics_referrals.episode_locator FK[4][5] | the DB refuses a prescription/referral against a non-existent episode |
care.appointments | provider_slots.appointment_locator, diagnostics_referrals.appointment_locator | soft back-pointers (no FK); the slot's is used by the claim/release cycle, the referral's is reserved for BookReferral and not yet set live |
| the reminder sweep | appointment_reminders_sent.appointment_locator (its own ON CONFLICT) | the only reader of the ledger - it is write-and-dedupe, nothing queries it for display |
notifications | consumes care.events | handles care.appointment.confirmed/.reminder only; care.prescription.* and care.referral.* reach the topic but no consumer routes them[8] |
| member timeline | - | the timeline sources episodes and appointments; a fulfilment lane over these two tables is one added source adapter (§9) |
| REST callers | POST /episodes/{loc}/prescriptions, /referrals; PATCH …/fill,…/void,…/book,…/complete,…/cancel; GET/POST /providers/{loc}/slots[23] | the JWT-protected write/read surface - member routes are owner-guarded (#1544) |
None of the cross-service references are FKs; all are locator values or Kafka topics (the cross-service rule). Within the db, the episode FKs are real.
8. Design determinations
- Fulfilment records hang off episodes by locator-valued FK - a prescription or referral cannot exist without a real episode, enforced in the DB, joining on the external
EP-id rather than a uuid. Inherited from the care schema's unusual choice (D-12, #1015; see Care page §1). - One reminder per appointment via a dedupe ledger - a one-column table claimed with
INSERT ON CONFLICT DO NOTHINGin the same transaction as the outbox enqueue makes "exactly once" survive restarts and replicas without a scheduler. #1630[2][20]. - The reminder ledger is out of CDC on purpose -
appointment_reminders_sentis the only care table absent fromdbz_care: it is operational bookkeeping, not domain data anyone downstream should project. - Slot inventory is owned, and the member calendar reads a generator until the sync lands - the table, claim/release and
BlockSlotare the provider-sync seam (D-12); the synthetic generator serves the member calendar in the meantime, which is why these 3 rows do not reach a member today (§6; Care page §3). - Status transitions are convention, not machine - like appointments, prescription and referral status changes are unguarded and the vocabularies have no CHECK; the code trusts the caller. A deliberate carry-over of care's "no status CHECKs" posture (§4), with the guard gap tracked in §9.
- The fulfilment layer is modelled ahead of the products that use it - the tables, the locators, the episode FKs, the status vocabularies and the CRUD-plus-lifecycle Go all exist before e-prescribing, onward referral and provider-availability sync do. Turning each on is wiring an existing write path, not a migration (§9).
- Everything is outbox-wrapped, and
BookReferralis the exception - it mutates without a transaction or an event, unlike every sibling method; the fix is named in §9.
No dedicated decision record was found for the prescriptions/referrals tables themselves; they were introduced with the initial care schema migration and the HTTP handlers in the same feature series as the rest of care.
9. Caveats and extensibility
Group and individual. Nothing here knows about schemes or plan tiers - a prescription and a referral reach a member only through their episode's party_locator, and a slot is provider-scoped, not member-scoped at all. Whether the prescribed drug or the onward referral is covered is eligibility's question, asked (or not) by callers; this schema records the clinical fact, not the coverage. So group and individual cover share these tables unchanged, the same way they share the episode schema (#11 §9).
Extension points - when, what, where.
| When we need … | What to add | Where |
|---|---|---|
| e-prescribing to go live | The record is already modelled: prescriptions holds the issued script, medication is jsonb so any medication shape the prescribing system emits stores without a migration, and the episode FK ties the script to the health concern. The work is wiring the clinician-facing issue path onto the existing IssuePrescription, which already inserts the row and enqueues care.prescription.issued in one transaction | services/care/internal/handler/prescriptions.go[10] and services/care/internal/service/service.go (IssuePrescription)[11] |
| onward referrals to run | diagnostics_referrals already links episode → referral → resulting appointment: BookReferral sets BOOKED, overwrites clinic_locator and stamps appointment_locator. The work is booking a real clinic appointment behind that call, and making the mutation transactional + event-emitting (defect below) | services/care/internal/service/service.go (BookReferral)[16] |
| provider availability to sync (Kry/Livi partner integration) | provider_slots already holds bookable inventory keyed by provider, with a status and an appointment back-pointer; AddSlot (ACTIVE-provider check), BlockSlot, the provider-scoped list and the atomic claim/release cycle all work. The sync writes rows, and the member calendar flips from the synthetic generator to preferring them | services/care/internal/service/service.go (AddSlot)[6], services/care/internal/repository/gorm_slots.go[7]; the D-12 seam (§8; Care page §3) |
| prescription / referral notifications to reach members | The events are already emitted on care.events in the same transaction as the write; the category router needs cases for care.prescription.* and care.referral.* | services/notifications/internal/projection/consumer.go (careCategoryFor)[8] |
| medication to be queryable (drug interaction checks, formulary reporting) | Adopt a typed shape over the jsonb plus validation or a CHECK, reconciling it with the keys the data uses. Until then the column takes anything, which is what makes the first e-prescribing integration a no-migration change | services/care/internal/repository/models.go[12] |
| fulfilment to show on the member timeline | The timeline sources episodes and appointments; a fulfilment lane over prescriptions / diagnostics_referrals is one added source adapter, no schema change | timeline service source adapters (§7) |
What the current population means. All 4 prescription/referral rows were inserted directly onto one legacy episode (party-003), which is why no care.prescription.* / care.referral.* event exists in the outbox (§6). The 3 provider_slots rows are AVAILABLE on the retired stub PRV-GP-001, read only by the provider-scoped list. Both are sequencing: the write paths are built and the tables are ready for the integrations above. appointment_reminders_sent is the deliberate exception in both directions - it is the one table written by running code, and it is deliberately out of dbz_care because it is a dedupe ledger rather than domain data anyone downstream should project (§3, §8).
Known defects, with the fix:
- The
Medicationstruct does not match the data it documents. It is referenced nowhere, and its field names (name/dosage/frequency/instructions) disagree with the live jsonb keys (name/dose/days), so it misleads anyone reading the model for the payload shape. Fix: delete it, or reconcile its fields with the emitted keys and use it inIssuePrescription. Where:services/care/internal/repository/models.go[12] (§3). BookReferralbreaks care's outbox invariant. It does a plainUpdateoutside any transaction and emits no event, so a booked referral is not announced to any consumer - the only mutation in this schema that does so. Fix: wrap the update and acare.referral.bookedoutbox enqueue in one transaction, asCreateReferralandUpdateReferralStatusalready do. Where:services/care/internal/service/service.go[16] (§4, §5).- Status transitions have no current-state guard.
fillon a VOIDED prescription, orcompleteon a CANCELLED referral, would succeed. Fix: check the current status before applying the action (BlockSlot's AVAILABLE-only check is the in-service pattern to copy[17]), optionally backed by a CHECK. Where:UpdatePrescriptionStatus[13] andUpdateReferralStatus[15] (§4). - Prescription and referral events have no consumer routing them. They reach
care.eventscorrectly, but the notifications router handles onlycare.appointment.*, so acare.prescription.issuedarrives and is dropped on the floor rather than categorised. Fix: add the two event families tocareCategoryFor. Where:services/notifications/internal/projection/consumer.go[8] (§2, §7).
References
Code links are pinned to commit b61c5802 on main (2026-08-21); the file is the anchor if lines drift. Pins are checked mechanically by docs/site/scripts/check-code-refs.py.
care/migrations/00001_care_schema.sql- provider_slots L18-27 · prescriptions L48-57 · diagnostics_referrals L61-70; UNIQUE locators L20/L50/L63care/migrations/00006_appointment_reminders_sent.sql- reminder dedupe ledger, PK = appointment_locator (#1630); rationale comment L2-6care/internal/service/service.go#L73-newLocator: prefix + hex8 of a uuid (RX/REF/SLT)00001_care_schema.sql#L51-prescriptions.episode_locator REFERENCES episodes(locator)00001_care_schema.sql#L64-diagnostics_referrals.episode_locator REFERENCES episodes(locator)care/internal/service/service.go#L693-AddSlot: ValidateActive (L697-700) then insert AVAILABLE slotcare/internal/repository/gorm_slots.go#L29-ListAvailableByProvider:status = 'AVAILABLE', the only read of provider_slotsnotifications/internal/projection/consumer.go#L35-careCategoryFor: only appointment.confirmed/.reminder/notes.returned; prescription/referral fall throughcare/internal/repository/models.go#L42- ProviderSlot / Prescription / DiagnosticsReferral gorm modelscare/internal/handler/prescriptions.go#L28- medication-required 400 (L28-31); issuedAt defaults to now (L32-35)care/internal/service/service.go#L757-IssuePrescription: episode-exists check, insert +care.prescription.issuedin one txcare/internal/repository/models.go#L55- the unreferencedMedicationstruct (name/dosage/frequency/instructions), keys disagree with the live jsonbcare/internal/service/service.go#L804-UpdatePrescriptionStatus: fill/void, per-tx event, no state guardcare/internal/service/service.go#L858-CreateReferral: episode-exists check, insert +care.referral.createdin one txcare/internal/service/service.go#L923-UpdateReferralStatus: complete/cancel, per-tx event, no state guardcare/internal/service/service.go#L904-BookReferral: plain Update, no transaction, no event (the outbox-invariant exception)care/internal/service/service.go#L728-BlockSlot: AVAILABLE-only guard (L737-739) then flip to BLOCKEDcare/cmd/server/main.go#L118- reminder sweep ticker start;CARE_REMINDER_SWEEP_MINUTES(default 15, 0 disables)care/internal/repository/gorm_appointments.go#L52-ListConfirmedInWindow: CONFIRMED + scheduled within [from,to]care/internal/service/reminders.go#L43-SweepReminders: window scan, no-party skip (L54-62), claim-in-tx + enqueue (L73-82)care/internal/repository/gorm_appointments.go#L61-TryMarkReminderSent:INSERT ON CONFLICT DO NOTHING,RowsAffected == 1care/internal/handler/slots.go#L14-validProviderTypesenum + slot-search 400s (28-day cap etc.)care/internal/handler/handler.go#L96- slot / prescription / referral routes; owner-guarded member routes (#1544)
Live-schema facts (row counts and status distributions for all four tables, the dbz_care publication membership, the outbox event-type census proving no prescription/referral events exist, the seed-episode lineage, and the reminder marker's join to a real CONFIRMED appointment and PTY- member) come from PGPASSWORD=… psql -h 10.0.1.2 -U olly -d care · \d prescriptions, \d diagnostics_referrals, \d provider_slots, \d appointment_reminders_sent, select … from prescriptions/diagnostics_referrals/provider_slots/appointment_reminders_sent, select payload->>'eventType', count(*) from outbox group by 1, and select tablename from pg_publication_tables where pubname='dbz_care', 2026-08-21.
