Skip to main content

Ghasi e-Prescribing Gateway Service — Application Logic

Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 03 platform-services · 02 DDD

Use Cases — Commands

Use caseTriggerActor constraintEmits event
CreateMedicationRequestUseCasePOST /fhir/MedicationRequestehr-backend onlyeprescribing.medication_request.created.v1
UpdateMedicationRequestUseCasePUT /fhir/MedicationRequest/:idehr-backend onlyeprescribing.medication_request.updated.v1
CancelMedicationRequestUseCasePATCH MR status=cancelled or via Taskehr-backend or policy workfloweprescribing.medication_request.cancelled.v1
CreateMedicationDispenseUseCasePOST /fhir/MedicationDispensepharmacy-backend onlyeprescribing.medication_dispense.created.v1
UpdateMedicationDispenseUseCasePUT /fhir/MedicationDispense/:idpharmacy-backendeprescribing.medication_dispense.updated.v1
RegisterSubscriptionUseCasePOST /fhir/Subscriptionehr-backend, pharmacy-backend
CreateWorkflowTaskUseCasePOST /fhir/Taskpharmacy or EHR per policyeprescribing.task.status_changed.v1
ReplaySubscriptionUseCaseAdmin replay triggerplatform-admineprescribing.subscription.replayed.v1

Use Cases — Queries

Use caseEndpointDescription
GetMedicationRequestUseCaseGET /fhir/MedicationRequest/:idSingle MR with ETag header
SearchMedicationRequestsUseCaseGET /fhir/MedicationRequest?patient=...Paginated search by patient, status, date
GetMedicationDispenseUseCaseGET /fhir/MedicationDispense/:idSingle MD
SearchMedicationDispensesUseCaseGET /fhir/MedicationDispense?request=...Fulfillments for a prescription
DirectorySearchUseCaseGET /fhir/Organization?name=...Pharmacy directory search (P1)
GetSubscriptionStatusUseCaseGET /fhir/Subscription/:idChannel status and cursor

Ports (Interfaces)

PortDirectionAdapter
MedicationRequestRepositoryOutboundPostgres / Drizzle (gepgw_medication_requests)
MedicationDispenseRepositoryOutboundPostgres / Drizzle (gepgw_medication_dispenses)
SubscriptionRepositoryOutboundPostgres / Drizzle (gepgw_subscriptions)
IdempotencyStoreOutboundPostgres (Phase 1) / Redis (Phase 2)
IgValidatorOutboundZod (Phase 1) / HAPI FHIR (Phase 2)
PharmacyRouterOutboundOrganization/Endpoint resolution via provider-directory-service
SubscriptionNotifierOutboundHTTPS delivery with HMAC signing
EventPublisherOutboundNATS JetStream outbox relay
AuditClientOutboundaudit-service (fire-and-forget)
TerminologyClientOutboundterminology-service (ATC/RxNorm validation)

Core Orchestration Flow — Prescribe → Route → Dispense

Idempotency Flow

  1. Client sends POST /fhir/MedicationRequest with Idempotency-Key: <uuid>.
  2. Gateway computes payload_fingerprint = sha256(canonicalized_body).
  3. Checks gepgw_idempotency_records for (tenant_id, idempotency_key, payload_fingerprint).
  4. If found and outcome success: return stored response (same 201, same id).
  5. If found but in processing state: return 409 Conflict (in-flight duplicate).
  6. If not found: proceed with create; insert idempotency record atomically.

ETag / If-Match Concurrency

  • Every GET returns ETag: W/"<sha256>" header.
  • Update operations require If-Match: W/"<stored-etag>".
  • Mismatch returns 412 Precondition Failed with current resource in body for reconciliation.
  • Client must refresh and re-submit with updated ETag.

Outbox / Event Delivery Pattern

All domain events inserted into gepgw_outbox in the same DB transaction as the resource mutation. The OutboxRelayWorker polls and publishes to NATS JetStream. The SubscriptionNotifier reads from NATS and delivers signed HTTPS payloads to registered channels. Failed deliveries enter DLQ with metadata for manual replay.

Error Handling

ConditionHTTPError code
Wrong persona (EHR tries to write MD)403FORBIDDEN_WRITE_PERSONA
FHIR profile validation failed422PROFILE_VALIDATION_FAILURE + OperationOutcome
MR not found for MD link422PRESCRIPTION_NOT_FOUND
Duplicate Idempotency-Key + different payload409IDEMPOTENCY_KEY_CONFLICT
Stale ETag412PRECONDITION_FAILED
Module not licensed403MODULE_NOT_LICENSED
Tenant isolation violation403TENANT_ISOLATION_VIOLATION
IG validator unavailable (degraded)503VALIDATOR_UNAVAILABLE
Rate limit exceeded429RATE_LIMIT_EXCEEDED