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:
| Field | Type | Description |
|---|---|---|
id | PortalAccountId (pact_) | ULID with prefix |
tenantId | TenantId | RLS partition key |
patientId | PatientId | Canonical patient identity from registration-service |
identityProviderSubject | string | Keycloak sub claim — links account to IdP |
status | AccountStatus | active | suspended | pending_verification | closed |
mfaEnabled | boolean | Whether MFA is active for this account |
preferredLanguage | string? | BCP 47 language tag |
lastLoginAt | Timestamp? | |
createdAt | Timestamp | |
updatedAt | Timestamp |
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:
| Field | Type | Description |
|---|---|---|
id | ProxyDelegationId (pdel_) | ULID with prefix |
tenantId | TenantId | RLS partition key |
grantorPatientId | PatientId | Patient granting access |
proxyPortalAccountId | PortalAccountId | Portal account of the proxy |
relationshipType | ProxyRelationship | parent | guardian | caregiver | spouse | authorized_representative |
scope | DelegationScope[] | Permitted access scopes |
validFrom | Date | Delegation start date |
validTo | Date? | Expiry (null = indefinite until revoked) |
status | DelegationStatus | active | revoked | expired |
revokedAt | Timestamp? | If manually revoked |
createdAt | Timestamp |
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:
| Field | Type | Description |
|---|---|---|
id | DemographicsRequestId (demreq_) | ULID with prefix |
tenantId | TenantId | |
patientId | PatientId | |
portalAccountId | PortalAccountId | Submitting account |
requestedChanges | JsonB | Proposed field changes |
status | RequestStatus | pending | approved | rejected | cancelled |
requestedAt | Timestamp | |
reviewedBy | StaffId? | Staff who reviewed |
reviewedAt | Timestamp? | |
rejectionReason | string? |
4. PortalAccessEvent (Append-Only Log)
Immutable record of what the patient (or proxy) accessed in the portal.
ID prefix: paev_
Fields:
| Field | Type | Description |
|---|---|---|
id | AccessEventId (paev_) | ULID with prefix |
tenantId | TenantId | |
portalAccountId | PortalAccountId | |
patientId | PatientId | Patient whose data was accessed |
actingAsProxy | boolean | True if accessed via proxy delegation |
proxyDelegationId | ProxyDelegationId? | If actingAsProxy |
eventType | PortalEventType | login | record.viewed | result.viewed | appointment.requested | demographics.update.requested | export.requested | message.read |
resourceType | string? | FHIR resource type if applicable |
resourceId | string? | FHIR resource ID if applicable |
ipHash | string? | Hashed client IP (PII-safe) |
occurredAt | Timestamp |
Domain Events (Produced by portal-service)
| Event Type | Trigger | Payload Summary |
|---|---|---|
portal.account.created.v1 | New portal account created | accountId, patientId |
portal.account.suspended.v1 | Account suspended | accountId, actorId |
portal.account.closed.v1 | Account deleted/closed | accountId, patientId |
portal.login.v1 | Successful authentication | accountId, patientId, mfaUsed |
portal.record.viewed.v1 | Patient views record | accountId, resourceType, resourceId |
portal.result.viewed.v1 | Patient views lab/imaging result | accountId, observationId / reportId |
portal.appointment.requested.v1 | Appointment request submitted | accountId, patientId, requestedSlot |
portal.demographics.update.requested.v1 | Demographic update submitted | accountId, requestId |
portal.proxy.delegation.granted.v1 | Proxy access granted | delegationId, grantorPatientId |
portal.proxy.delegation.revoked.v1 | Proxy access revoked | delegationId, actorId |
portal.export.requested.v1 | Patient-initiated FHIR Bundle export | accountId, patientId |
Value Objects
| Value Object | Description |
|---|---|
PortalAccountId | Branded ULID pact_* |
ProxyDelegationId | Branded ULID pdel_* |
DemographicsRequestId | Branded ULID demreq_* |
AccessEventId | Branded ULID paev_* |
AccountStatus | active | suspended | pending_verification | closed |
DelegationScope | read:record | read:results | read:appointments | read:messages | read:billing |
ProxyRelationship | parent | guardian | caregiver | spouse | authorized_representative |
PortalEventType | Enum of portal access event types |
ResultReleasePolicy | immediate | clinician_release | timed (N hours) | never |
Ubiquitous Language
| Term | Definition |
|---|---|
| Portal Account | A patient's registered identity binding between the EHR patient record and the patient-facing portal |
| Proxy | An authorized person (parent, guardian, caregiver) permitted to access another patient's portal within defined scope limits |
| Delegation | A scoped, time-limited grant of proxy access |
| Result Release Policy | The rule governing when a clinical result becomes visible to the patient |
| BFF | Backend-for-Frontend — a server-side aggregation layer that tailors upstream service data for a specific client |
| SMART on FHIR | OAuth2/OIDC extension for FHIR-scoped patient access authorization |
| Constrained FHIR surface | The subset of FHIR R4 resources and search parameters exposed to patient clients, with policy filters applied |
| Demographics Update Request | A patient-initiated change to their demographic record that requires staff verification |
| Access Log | The immutable portal access event trail used for compliance and patient transparency |