Skip to main content

Patient Portal Service — User Stories

Service: patient-portal-service Story prefix: PORTAL-US Last updated: 2026-04-18

Stories

PORTAL-US-001 — Register portal account

FieldValue
Issue typeStory
SummaryPatient registers for portal account via email/phone verification
Epic linkPORTAL-EPIC-01
StatusTo Do
PriorityMust
Story points5
Labelsservice:patient-portal-service, type:backend, type:api, slice:S4
Componentsportal-account, keycloak-patient-realm
FR referencesFR-PORTAL-001
Legacy FR refsFR-PORT-001
Dependenciesidentity-service IDENT-US-001

User story: As a patient, when I visit the portal registration page, I want to create an account linked to my health record so that I can securely access my health information online.

Acceptance criteria (Gherkin):

  • Given a valid patient MRN and email, when I submit the registration form, then a PortalAccount is created with status = pending_verification and a verification email is sent.
  • Given a verification link from the email, when I click it within 24 hours, then my account moves to active status.
  • Given an expired or already-used verification link, when I click it, then 400 LINK_EXPIRED is returned and a new link can be requested.

Technical notes:

  • Keycloak patient realm creates the user; portal-account is linked via idp_subject (Keycloak sub).
  • Publishes portal.account.created.v1.

Definition of Done:

  • Unit + integration tests added; coverage ≥ thresholds.
  • OpenAPI contract updated; Pact consumer tests green.
  • Event schema registered; conformance test green.
  • Telemetry spans/metrics added.

PORTAL-US-002 — Enroll and enforce MFA

FieldValue
Issue typeStory
SummaryMFA TOTP enrollment is mandatory before first portal access
Epic linkPORTAL-EPIC-01
StatusTo Do
PriorityMust
Story points3
Labelsservice:patient-portal-service, type:backend, slice:S4
Componentsauthentication, keycloak-patient-realm
FR referencesFR-PORTAL-001
Legacy FR refsFR-PORT-001
DependenciesPORTAL-US-001

User story: As a patient, when I activate my portal account, I want to enroll a TOTP authenticator app so that my account is protected against unauthorized access.

Acceptance criteria (Gherkin):

  • Given an account in active status without MFA enrolled, when I try to access any portal resource, then I am redirected to MFA enrollment.
  • Given successful TOTP enrollment, when I log in, then I must enter a TOTP code to complete authentication.
  • Given an incorrect TOTP code, when I submit, then 401 MFA_FAILED is returned; after 5 failures the account is temporarily locked.

Technical notes:

  • Keycloak TOTP policy configured; mfaEnabled flag set on PortalAccount after first successful TOTP login.

Definition of Done:

  • Unit + integration tests added. E2E MFA enrollment test passing.

PORTAL-US-003 — Suspend and reinstate portal account

FieldValue
Issue typeStory
SummaryAdmin can suspend and reinstate a patient portal account
Epic linkPORTAL-EPIC-01
StatusTo Do
PriorityMust
Story points2
Labelsservice:patient-portal-service, type:backend, type:api, slice:S4
Componentsportal-account, admin-api
FR referencesFR-PORTAL-009
Legacy FR refsFR-PORT-009
DependenciesPORTAL-US-001

User story: As a portal administrator, when a patient account shows signs of unauthorized access, I want to suspend the account so that further access is immediately blocked.

Acceptance criteria (Gherkin):

  • Given an active account, when admin calls suspend, then status = suspended and portal.account.suspended.v1 is published.
  • Given a suspended account, when the patient tries to authenticate, then 403 ACCOUNT_NOT_ACTIVE is returned.
  • Given a suspended account, when admin calls reinstate, then status = active and account is accessible again.

Definition of Done: Standard DoD.


PORTAL-US-004 — Request account deletion

FieldValue
Issue typeStory
SummaryPatient can request deletion of their portal account
Epic linkPORTAL-EPIC-01
StatusTo Do
PriorityShould
Story points3
Labelsservice:patient-portal-service, type:backend, type:api, slice:S4
Componentsportal-account, gdpr
FR referencesFR-PORTAL-010
Legacy FR refsFR-PORT-010
DependenciesPORTAL-US-001

User story: As a patient, when I no longer wish to use the portal, I want to request deletion of my account so that my personal data is removed per data protection regulations.

Acceptance criteria (Gherkin):

  • Given an active account, when I request deletion, then the account moves to closed and portal.account.closed.v1 is published.
  • Given a closed account, when the patient tries to log in, then 403 ACCOUNT_NOT_ACTIVE is returned.
  • Given a deletion request, when processed, then portal-owned PHI (demographics requests, access events older than retention period) is purged per data-retention policy.

Definition of Done: Standard DoD plus GDPR erasure verification.


PORTAL-US-005 — View patient summary and chart sections

FieldValue
Issue typeStory
SummaryPatient views health summary including allergies, meds, vitals, conditions
Epic linkPORTAL-EPIC-02
StatusTo Do
PriorityMust
Story points8
Labelsservice:patient-portal-service, type:backend, type:api, slice:S4
Componentschart-sections, fhir-bff, policy-filter
FR referencesFR-PORTAL-002
Legacy FR refsFR-PORT-002
Dependenciesregistration-service, patient-chart-service, immunizations-service

User story: As a patient, when I open my portal, I want to see a summary of my health information including active conditions, allergies, and medications so that I have an up-to-date picture of my health.

Acceptance criteria (Gherkin):

  • Given an authenticated patient with SMART scope patient/Patient.read, when I call GET /v1/portal/chart/summary, then a FHIR Bundle with Patient + conditions + allergies is returned.
  • Given a chart section request for allergies, when called with valid scope, then AllergyIntolerance resources are returned in a searchset Bundle.
  • Given an invalid section name, when called, then 400 INVALID_SECTION is returned.

Technical notes:

  • Results cached in Redis (30s). Cache TTL re-evaluated on upstream concept.updated events.
  • Access events appended for each section view.

Definition of Done: Standard DoD.


PORTAL-US-006 — View released lab results

FieldValue
Issue typeStory
SummaryPatient views only clinician-released lab results
Epic linkPORTAL-EPIC-02
StatusTo Do
PriorityMust
Story points5
Labelsservice:patient-portal-service, type:backend, type:api, slice:S4
Componentslab-results-bff, release-policy
FR referencesFR-PORTAL-007
Legacy FR refsFR-PORT-007
Dependencieslaboratory-service

User story: As a patient, when I navigate to the Results section, I want to see only lab results that my care team has authorized for release so that I'm not confused by unreleased or preliminary findings.

Acceptance criteria (Gherkin):

  • Given released Observation resources in laboratory-service, when I call GET /v1/portal/results/lab, then only patient-visible observations are returned.
  • Given unreleased observations exist, when called, then unreleased resources are excluded from the response.
  • Given a portal.result.viewed.v1 event, when generated, then it includes resourceType=Observation and correct resourceId.

Definition of Done: Standard DoD plus release-policy regression test suite green.


PORTAL-US-007 — View released radiology reports

FieldValue
Issue typeStory
SummaryPatient views only released imaging/radiology reports
Epic linkPORTAL-EPIC-02
StatusTo Do
PriorityMust
Story points3
Labelsservice:patient-portal-service, type:backend, type:api, slice:S4
Componentsradiology-results-bff, release-policy
FR referencesFR-PORTAL-007
Legacy FR refsFR-PORT-007
Dependenciesradiology-service

User story: As a patient, when I navigate to the Imaging section, I want to see radiology reports that have been released by my radiologist so that I can review my imaging results.

Acceptance criteria (Gherkin):

  • Given released DiagnosticReport resources, when I call GET /v1/portal/results/radiology, then only patient-visible reports are returned.
  • Given an unreleased report, when called, then it is absent from the response.

Definition of Done: Standard DoD.


PORTAL-US-008 — View appointments

FieldValue
Issue typeStory
SummaryPatient views upcoming and past appointments
Epic linkPORTAL-EPIC-03
StatusTo Do
PriorityMust
Story points3
Labelsservice:patient-portal-service, type:backend, type:api, slice:S4
Componentsappointment-bff
FR referencesFR-PORTAL-003
Legacy FR refsFR-PORT-003
Dependenciesscheduling-service

User story: As a patient, when I open my portal, I want to see my upcoming and past appointments so that I can track my care schedule.

Acceptance criteria (Gherkin):

  • Given Appointment resources in scheduling-service, when I call GET /v1/portal/appointments?status=booked, then upcoming appointments are returned as a FHIR Bundle.
  • Given no appointments, when called, then an empty searchset Bundle is returned.

Definition of Done: Standard DoD.


PORTAL-US-009 — Request new appointment

FieldValue
Issue typeStory
SummaryPatient submits an appointment request to scheduling
Epic linkPORTAL-EPIC-03
StatusTo Do
PriorityMust
Story points5
Labelsservice:patient-portal-service, type:backend, type:api, slice:S4
Componentsappointment-bff, scheduling-client
FR referencesFR-PORTAL-003
Legacy FR refsFR-PORT-003
Dependenciesscheduling-service SCHED-EPIC-01

User story: As a patient, when I want to see a provider, I want to request an appointment from the portal so that I don't have to call the clinic.

Acceptance criteria (Gherkin):

  • Given valid preferred provider, facility, and time, when I submit POST /v1/portal/appointments/request, then 201 with requestId and status: pending is returned.
  • Given scheduling-service is unavailable, when I submit a request, then 503 UPSTREAM_UNAVAILABLE is returned and no partial state is created.
  • Given a successful request, when processed, then portal.appointment.requested.v1 is published.

Definition of Done: Standard DoD.


PORTAL-US-010 — View medication list

FieldValue
Issue typeStory
SummaryPatient views active and historical medication requests
Epic linkPORTAL-EPIC-04
StatusTo Do
PriorityMust
Story points3
Labelsservice:patient-portal-service, type:backend, type:api, slice:S4
Componentsmedication-bff
FR referencesFR-PORTAL-004
Legacy FR refsFR-PORT-004
Dependenciesmedication-service

User story: As a patient, when I open my portal, I want to see my current and past medications so that I can track what I've been prescribed.

Acceptance criteria (Gherkin):

  • Given MedicationRequest resources in medication-service, when I call GET /v1/portal/medications?status=active, then active medications are returned.

Definition of Done: Standard DoD.


PORTAL-US-011 — Request medication refill

FieldValue
Issue typeStory
SummaryPatient requests a prescription refill from the portal
Epic linkPORTAL-EPIC-04
StatusTo Do
PriorityShould
Story points5
Labelsservice:patient-portal-service, type:backend, type:api, slice:S4
Componentsmedication-bff, refill-request
FR referencesFR-PORTAL-004
Legacy FR refsFR-PORT-004
Dependenciesmedication-service MED-EPIC-01

User story: As a patient with an existing prescription, when my medication is running low, I want to submit a refill request from the portal so that I can get a new supply without an office visit.

Acceptance criteria (Gherkin):

  • Given an active MedicationRequest, when I call POST /v1/portal/medications/{id}/refill, then 201 with refillRequestId is returned.
  • Given a non-active medication, when I request a refill, then 400 INVALID_REQUEST is returned.

Definition of Done: Standard DoD.


PORTAL-US-012 — Grant proxy delegation

FieldValue
Issue typeStory
SummaryPatient grants a proxy or caregiver scoped access to their portal
Epic linkPORTAL-EPIC-05
StatusTo Do
PriorityMust
Story points8
Labelsservice:patient-portal-service, type:backend, type:api, slice:S4
Componentsproxy-delegation
FR referencesFR-PORTAL-006
Legacy FR refsFR-PORT-006
DependenciesPORTAL-US-001

User story: As a parent, when I need to manage my child's health records, I want to be granted proxy access to their portal account so that I can view their results and appointments on their behalf.

Acceptance criteria (Gherkin):

  • Given valid proxy account ID and scope, when I call POST /v1/portal/proxy/delegations, then ProxyDelegation created with status=active and portal.proxy.delegation.granted.v1 published.
  • Given a duplicate delegation (same grantor + proxy), when submitted, then 409 DELEGATION_ALREADY_EXISTS is returned.
  • Given a proxy session using an expired delegation, when they try to access data, then 403 PROXY_SCOPE_EXCEEDED is returned.

Definition of Done: Standard DoD plus proxy isolation E2E test green.


PORTAL-US-013 — Revoke proxy delegation

FieldValue
Issue typeStory
SummaryPatient revokes a proxy delegation immediately
Epic linkPORTAL-EPIC-05
StatusTo Do
PriorityMust
Story points2
Labelsservice:patient-portal-service, type:backend, type:api, slice:S4
Componentsproxy-delegation
FR referencesFR-PORTAL-006
Legacy FR refsFR-PORT-006
DependenciesPORTAL-US-012

User story: As a patient, when I want to remove a caregiver's access, I want to revoke their delegation immediately so that they can no longer access my records.

Acceptance criteria (Gherkin):

  • Given an active delegation, when I call DELETE /v1/portal/proxy/delegations/{id}, then status=revoked and portal.proxy.delegation.revoked.v1 published.
  • Given a revoked delegation, when the proxy tries to access data, then 403 PROXY_SCOPE_EXCEEDED is returned immediately (not cached).

Definition of Done: Standard DoD.


PORTAL-US-014 — Request PHR export

FieldValue
Issue typeStory
SummaryPatient requests a FHIR Bundle export of their health record
Epic linkPORTAL-EPIC-06
StatusTo Do
PriorityShould
Story points8
Labelsservice:patient-portal-service, type:backend, type:api, slice:S4
Componentsexport-job, fhir-bundle-assembly
FR referencesFR-PORTAL-011
Legacy FR refsFR-PORT-011
DependenciesPORTAL-EPIC-02

User story: As a patient, when I want to share my health records with another provider, I want to download my full health record as a FHIR file so that I can take my data with me.

Acceptance criteria (Gherkin):

  • Given an authenticated patient, when I call POST /v1/portal/export, then 202 with exportJobId is returned.
  • Given the export job completes, when I poll the status endpoint, then status=complete and a time-limited downloadUrl is returned.
  • Given an export job in progress, when I submit a new export request, then the existing in-progress job ID is returned (idempotent).

Definition of Done: Standard DoD plus GDPR portability validation.


PORTAL-US-015 — Download export bundle

FieldValue
Issue typeStory
SummaryPatient downloads a completed PHR export via time-limited URL
Epic linkPORTAL-EPIC-06
StatusTo Do
PriorityShould
Story points3
Labelsservice:patient-portal-service, type:backend, slice:S4
Componentsexport-job, object-storage
FR referencesFR-PORTAL-011
DependenciesPORTAL-US-014

User story: As a patient, when my export is complete, I want to download my health record file so that I can store or share it securely.

Acceptance criteria (Gherkin):

  • Given a completed export job, when I access the downloadUrl, then the FHIR ndjson file is returned.
  • Given an expired download URL (24h TTL), when accessed, then 404 NOT_FOUND is returned and a new export must be requested.
  • Given a download URL, when accessed from a different tenant session, then access is denied.

Definition of Done: Standard DoD.


PORTAL-US-016 — Enable AI navigation assistant

FieldValue
Issue typeStory
SummaryTenant admin enables AI navigation assistant for patient portal
Epic linkPORTAL-EPIC-07
StatusTo Do
PriorityCould
Story points3
Labelsservice:patient-portal-service, type:backend, slice:S4
Componentsai-navigation, feature-flag
FR referencesFR-PORTAL-013
Legacy FR refsFR-AI-007
Dependenciesai-gateway-service AIGW-EPIC-01

User story: As a tenant administrator, when I want to improve patient engagement, I want to enable the AI navigation assistant so that patients can ask natural language questions about the portal.

Acceptance criteria (Gherkin):

  • Given ai.patient-assistant feature flag enabled for tenant, when patient calls POST /v1/portal/ai/navigate, then a navigation reply is returned.
  • Given feature flag disabled, when the endpoint is called, then 403 FEATURE_NOT_ENABLED is returned.
  • Given ai-gateway-service unavailable, when called, then 503 returned; rest of portal unaffected.

Definition of Done: Standard DoD plus PHI-in-prompt unit test green.


PORTAL-US-017 — Portal navigation via AI assistant

FieldValue
Issue typeStory
SummaryPatient uses natural language to navigate to the right portal section
Epic linkPORTAL-EPIC-07
StatusTo Do
PriorityCould
Story points5
Labelsservice:patient-portal-service, type:api, slice:S4
Componentsai-navigation
FR referencesFR-PORTAL-013
DependenciesPORTAL-US-016

User story: As a patient, when I'm unsure where to find my lab results, I want to ask the portal assistant in my own words so that I'm guided to the right section quickly.

Acceptance criteria (Gherkin):

  • Given a navigation question ("Where are my blood tests?"), when I call POST /v1/portal/ai/navigate, then a reply with navigationHint pointing to /v1/portal/results/lab is returned.
  • Given a clinical question ("Do I have diabetes?"), when submitted, then the moderation filter intercepts and returns the fallback message.
  • Given more than 10 requests in a session, when submitted, then 429 RATE_LIMITED is returned.

Definition of Done: Standard DoD plus moderation boundary test cases green.


PORTAL-US-018 — View insurance coverage and EOB

FieldValue
Issue typeStory
SummaryPatient views active insurance coverage and explanation of benefits
Epic linkPORTAL-EPIC-08
StatusTo Do
PriorityShould
Story points3
Labelsservice:patient-portal-service, type:backend, type:api, slice:S2
Componentsbilling-bff, claims-client
FR referencesFR-PORTAL-008
Legacy FR refsFR-PORT-008
Dependenciesclaims-service CLAIMS-EPIC-01

User story: As a patient, when I want to understand my insurance coverage, I want to view my active coverage details and past explanation of benefits so that I can verify what costs my insurer covers.

Acceptance criteria (Gherkin):

  • Given Coverage resources in claims-service, when I call GET /v1/portal/billing/coverage, then active coverage resources are returned.
  • Given EOB resources, when I call GET /v1/portal/billing/eob, then EOBs are returned paginated.
  • Given claims-service unavailable, when called, then cached billing data is returned with a degraded banner.

Definition of Done: Standard DoD.