Skip to main content

Patient Portal Service — Domain Model

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

Aggregates

The patient-portal-service owns a small set of portal-specific aggregates. Clinical data is sourced from upstream services; the portal never stores clinical records authoritatively.

1. PortalAccount (Root Aggregate)

Represents a patient's portal account — the binding between a patient identity and a Keycloak subject.

ID prefix: pact_

Fields:

FieldTypeDescription
idPortalAccountId (pact_)ULID with prefix
tenantIdTenantIdRLS partition key
patientIdPatientIdCanonical patient identity from registration-service
identityProviderSubjectstringKeycloak sub claim — links account to IdP
statusAccountStatusactive | suspended | pending_verification | closed
mfaEnabledbooleanWhether MFA is active for this account
preferredLanguagestring?BCP 47 language tag
lastLoginAtTimestamp?
createdAtTimestamp
updatedAtTimestamp

State Machine:


2. ProxyDelegation (Aggregate)

Represents a legally-permitted proxy or caregiver relationship granting a proxy access to a patient's portal data.

ID prefix: pdel_

Fields:

FieldTypeDescription
idProxyDelegationId (pdel_)ULID with prefix
tenantIdTenantIdRLS partition key
grantorPatientIdPatientIdPatient granting access
proxyPortalAccountIdPortalAccountIdPortal account of the proxy
relationshipTypeProxyRelationshipparent | guardian | caregiver | spouse | authorized_representative
scopeDelegationScope[]Permitted access scopes
validFromDateDelegation start date
validToDate?Expiry (null = indefinite until revoked)
statusDelegationStatusactive | revoked | expired
revokedAtTimestamp?If manually revoked
createdAtTimestamp

3. DemographicsUpdateRequest (Entity)

Represents a patient-initiated request to update their demographic information. Requires staff verification before becoming authoritative in registration-service.

ID prefix: demreq_

Fields:

FieldTypeDescription
idDemographicsRequestId (demreq_)ULID with prefix
tenantIdTenantId
patientIdPatientId
portalAccountIdPortalAccountIdSubmitting account
requestedChangesJsonBProposed field changes
statusRequestStatuspending | approved | rejected | cancelled
requestedAtTimestamp
reviewedByStaffId?Staff who reviewed
reviewedAtTimestamp?
rejectionReasonstring?

4. PortalAccessEvent (Append-Only Log)

Immutable record of what the patient (or proxy) accessed in the portal.

ID prefix: paev_

Fields:

FieldTypeDescription
idAccessEventId (paev_)ULID with prefix
tenantIdTenantId
portalAccountIdPortalAccountId
patientIdPatientIdPatient whose data was accessed
actingAsProxybooleanTrue if accessed via proxy delegation
proxyDelegationIdProxyDelegationId?If actingAsProxy
eventTypePortalEventTypelogin | record.viewed | result.viewed | appointment.requested | demographics.update.requested | export.requested | message.read
resourceTypestring?FHIR resource type if applicable
resourceIdstring?FHIR resource ID if applicable
ipHashstring?Hashed client IP (PII-safe)
occurredAtTimestamp

Domain Events (Produced by portal-service)

Event TypeTriggerPayload Summary
portal.account.created.v1New portal account createdaccountId, patientId
portal.account.suspended.v1Account suspendedaccountId, actorId
portal.account.closed.v1Account deleted/closedaccountId, patientId
portal.login.v1Successful authenticationaccountId, patientId, mfaUsed
portal.record.viewed.v1Patient views recordaccountId, resourceType, resourceId
portal.result.viewed.v1Patient views lab/imaging resultaccountId, observationId / reportId
portal.appointment.requested.v1Appointment request submittedaccountId, patientId, requestedSlot
portal.demographics.update.requested.v1Demographic update submittedaccountId, requestId
portal.proxy.delegation.granted.v1Proxy access granteddelegationId, grantorPatientId
portal.proxy.delegation.revoked.v1Proxy access revokeddelegationId, actorId
portal.export.requested.v1Patient-initiated FHIR Bundle exportaccountId, patientId

Value Objects

Value ObjectDescription
PortalAccountIdBranded ULID pact_*
ProxyDelegationIdBranded ULID pdel_*
DemographicsRequestIdBranded ULID demreq_*
AccessEventIdBranded ULID paev_*
AccountStatusactive | suspended | pending_verification | closed
DelegationScoperead:record | read:results | read:appointments | read:messages | read:billing
ProxyRelationshipparent | guardian | caregiver | spouse | authorized_representative
PortalEventTypeEnum of portal access event types
ResultReleasePolicyimmediate | clinician_release | timed (N hours) | never

Ubiquitous Language

TermDefinition
Portal AccountA patient's registered identity binding between the EHR patient record and the patient-facing portal
ProxyAn authorized person (parent, guardian, caregiver) permitted to access another patient's portal within defined scope limits
DelegationA scoped, time-limited grant of proxy access
Result Release PolicyThe rule governing when a clinical result becomes visible to the patient
BFFBackend-for-Frontend — a server-side aggregation layer that tailors upstream service data for a specific client
SMART on FHIROAuth2/OIDC extension for FHIR-scoped patient access authorization
Constrained FHIR surfaceThe subset of FHIR R4 resources and search parameters exposed to patient clients, with policy filters applied
Demographics Update RequestA patient-initiated change to their demographic record that requires staff verification
Access LogThe immutable portal access event trail used for compliance and patient transparency