Skip to main content

Facility Service — User Stories

Service: facility-service Story prefix: FAC-US Last updated: 2026-04-17

Stories

FAC-US-001 — Create hierarchy node validated against profile

FieldValue
Issue typeStory
SummaryAdmin creates a ward node validated against active profile
Epic linkFAC-EPIC-01
StatusTo Do
PriorityMust
Story points5
Labelsservice:facility-service, type:backend, slice:S0
Componentshierarchy
FR referencesFR-FAC-HIER-001
Legacy FR refsFR-HIER-NODE-001, FR-HIER-NODE-002
Dependencies

User story: As a tenant facility admin, when I need to model a new ward, I want to create a HierarchyNode that is validated against my tenant's active profile, so that I cannot accidentally create an invalid structure.

Acceptance criteria (Gherkin):

  • Given my tenant has AFG_MOPH profile active, when I POST a node with type=ward and a valid department parent, then 201 is returned and facility.hierarchy_node.created.v1 is published.
  • Given my profile does not allow type=virtual-clinic, when I POST such a node, then 422 HIERARCHY_NODE_TYPE_NOT_ALLOWED is returned.
  • Given a root node already exists in my tenant, when I POST a second moph root, then 409 HIERARCHY_MULTIPLE_ROOTS is returned.

Technical notes:

  • Endpoint POST /api/v1/hierarchy/nodes, handler CreateHierarchyNodeHandler.
  • Validation runs against profile.allowedNodeTypes.
  • Authorisation via access-policy.evaluate(action: hierarchy.node:create).
  • Emits facility.hierarchy_node.created.v1.

Definition of Done: Unit + integration tests, OpenAPI, event schema, Pact consumer (tenant admin UI), telemetry span, 80%+ coverage.


FAC-US-002 — Prevent cycles on contains edges

FieldValue
Issue typeStory
SummaryReject edge creation that would form a cycle
Epic linkFAC-EPIC-01
StatusTo Do
PriorityMust
Story points5
Labelsservice:facility-service, type:backend, slice:S0
FR referencesFR-FAC-HIER-005
Legacy FR refsFR-HIER-EDGE-003, BR-HIER-003

User story: As a tenant admin, when I link nodes, I want cycles on contains edges blocked so the hierarchy remains a DAG.

Acceptance criteria:

  • Given A contains B contains C, when I POST edge {parent:C, child:A, relationshipType:contains}, then 409 HIERARCHY_CYCLE.
  • Given A manages B, when I POST edge {parent:B, child:A, relationshipType:manages}, then 201 (cycles allowed on non-contains).
  • Given I POST a self-loop, then 400.

Technical notes:

  • Recursive CTE in Postgres; p95 ≤ 50ms up to 1k descendants.
  • Alert fires when cycle rejections > 50/min/tenant.

Definition of Done: as above + perf test.


FAC-US-003 — Profile-based seeding on tenant creation

FieldValue
Epic linkFAC-EPIC-01
PriorityMust
Story points3
FR referencesFR-FAC-HIER-007
Legacy FR refsFR-HIER-PROF-004

User story: As the platform, when a tenant is created, I want to seed a default profile and root node so the tenant is immediately usable.

Acceptance criteria:

  • Given a tenant.tenant.created.v1 event with country=AF, when consumed, then AFG_MOPH is set as default and a root MoPH node is created.
  • Given the country is unknown, then CLINIC_SIMPLE is chosen.

Technical notes: OnTenantCreatedPolicy; inbox-dedup keyed on eventId.


FAC-US-004 — Subtree query with depth limit

FieldValue
Epic linkFAC-EPIC-01
PriorityMust
Story points3
FR referencesFR-FAC-TRV-001
Legacy FR refsFR-HIER-TRV-001, NFR-HIER-002

User story: As a downstream service, I want to fetch the subtree of any node up to a depth, so I can resolve scope without over-fetching.

Acceptance criteria:

  • Given a subtree of 1,000 nodes, GET /hierarchy/nodes/:id/subtree?maxDepth=3 returns in ≤ 100 ms p95.
  • When maxDepth > profile.constraints.maxDepth, return 422.

FAC-US-005 — Ancestor chain lookup (Licensing hot path)

FieldValue
Epic linkFAC-EPIC-01
PriorityMust
Story points3
FR referencesFR-FAC-TRV-002
Legacy FR refsFR-HIER-TRV-004

User story: As the Licensing Service, I want the ordered root→node ancestor chain so I can resolve effective licenses.

Acceptance criteria:

  • Given cached context, response p99 ≤ 20 ms.
  • When cache miss, DB fallback p95 ≤ 80 ms.

FAC-US-006 — Clone platform profile for tenant customisation

FieldValue
Epic linkFAC-EPIC-01
PriorityShould
Story points3
FR referencesFR-FAC-PROF-002
Legacy FR refsFR-HIER-PROF-002

User story: As a tenant admin, I want to clone a platform profile and edit it for my needs.

Acceptance criteria:

  • Given platform profile PRIVATE_HOSPITAL, when I POST /hierarchy/profiles/:id/clone, then a new profile exists under my tenant with identical constraints.
  • Given I edit the cloned profile, existing nodes are not invalidated (warn only).

FAC-US-007 — Soft-deactivate hierarchy node

FieldValue
Epic linkFAC-EPIC-01
PriorityMust
Story points2
FR referencesFR-FAC-HIER-004
Legacy FR refsFR-HIER-NODE-004, BR-HIER-004

User story: As an admin, I want to soft-deactivate a node (no cascade) so history is preserved.

Acceptance criteria: child nodes remain active; deactivated node hidden from default list; event emitted.


FAC-US-008 — Create location anchored to node

FieldValue
Epic linkFAC-EPIC-02
PriorityMust
Story points3
FR referencesFR-FAC-LOC-001
Legacy FR refsFR-FAC-001, FR-FAC-002

User story: As an admin, I want to create a Location anchored to exactly one HierarchyNode so no orphan locations exist.

Acceptance criteria:

  • Given a missing hierarchyNodeId, then 400 LOCATION_NODE_REQUIRED.
  • Given an invalid/inactive node, then 404/409.

FAC-US-009 — Set location operational hours

FieldValue
Epic linkFAC-EPIC-02
PriorityMust
Story points3
FR referencesFR-FAC-LOC-002
Legacy FR refsFR-FAC-001 (hours)

User story: As an admin, I want to set 7-day hours per location with time zone so scheduling can build slots.

Acceptance criteria: PUT replaces entire week; closed days allowed; emits facility.location.updated.v1.


FAC-US-010 — Deactivate location with occupied-bed safeguard

FieldValue
Epic linkFAC-EPIC-02
PriorityMust
Story points5
FR referencesFR-FAC-LOC-004
Legacy FR refsBR-FAC-002

User story: As an admin, I should not be able to deactivate a location while beds are occupied unless I explicitly force it.

Acceptance criteria:

  • Given at least one OCCUPIED bed, PUT /locations/:id/status?active=false returns 409 LOCATION_HAS_OCCUPIED_BEDS.
  • Given force=true with admin scope, deactivates and emits facility.audit.location.force_deactivated.

FAC-US-011 — Time zone aware location rendering

FieldValue
Epic linkFAC-EPIC-02
PriorityShould
Story points2
FR referencesFR-FAC-LOC-005
Legacy FR refsFR-FAC-008

User story: As a consumer, I want to receive location timezone and locale so I render times correctly.

Acceptance criteria: timezone IANA valid; defaults Asia/Kabul for AFG, Asia/Dubai for UAE.


FAC-US-012 — Create bed and transition through lifecycle

FieldValue
Epic linkFAC-EPIC-03
PriorityMust
Story points5
FR referencesFR-FAC-BED-001, FR-FAC-BED-002
Legacy FR refsFR-FAC-003, FR-FAC-006

User story: As a ward user, I want to manage beds through AVAILABLE → OCCUPIED → CLEANING → AVAILABLE so occupancy is accurate.

Acceptance criteria: state-machine enforced; invalid transitions 409 BED_INVALID_TRANSITION; event emitted on each change.


FAC-US-013 — Prevent double-assignment (BR-FAC-001)

FieldValue
Epic linkFAC-EPIC-03
PriorityMust
Story points5
FR referencesFR-FAC-BED-003
Legacy FR refsBR-FAC-001

User story: As clinical safety, a bed cannot be assigned to two patients simultaneously.

Acceptance criteria: concurrent PUT with two patient IDs → one 200, one 409 BED_ALREADY_OCCUPIED.


FAC-US-014 — Housekeeping sub-state

FieldValue
Epic linkFAC-EPIC-03
PriorityShould
Story points3
FR referencesFR-FAC-BED-004
Legacy FR refsFR-FAC-003

User story: As housekeeping staff, I want a sub-state (clean / in_progress / dirty) visible to ward dashboards.

Acceptance criteria: sub-state only valid while status ∈ {CLEANING, AVAILABLE}; audit trail.


FAC-US-015 — Create resource catalog item

FieldValue
Epic linkFAC-EPIC-04
PriorityShould
Story points3
FR referencesFR-FAC-RES-001
Legacy FR refsFR-FAC-004

User story: As an admin, I want to register resources (rooms, equipment) for scheduling.

Acceptance criteria: create/list/update/deactivate; tenant-wide if no locationId.


FAC-US-016 — Publish resource events to scheduling-service

FieldValue
Epic linkFAC-EPIC-04
PriorityShould
Story points2
FR referencesFR-FAC-RES-002
Legacy FR refs(FR-FAC-004 integration)

User story: As scheduling-service, I want to consume facility.resource.* events to build slots.

Acceptance criteria: Pact contract with scheduling-service passes.


FAC-US-017 — Assign provider to node

FieldValue
Epic linkFAC-EPIC-05
PriorityMust
Story points3
FR referencesFR-FAC-MBR-001
Legacy FR refsFR-HIER-MBR-001

User story: As provider-directory, when I onboard a practitioner, I want to assign them to one or more nodes.

Acceptance criteria: only internal service scope can call; primary rule enforced.


FAC-US-018 — Provider context lookup (hot path)

FieldValue
Epic linkFAC-EPIC-05
PriorityMust
Story points5
FR referencesFR-FAC-CTX-001
Legacy FR refsFR-HIER-MBR-004, NFR-HIER-001

User story: As any clinical service, I want GET /internal/providers/:id/context with p99 ≤ 20ms.

Acceptance criteria: cache hit rate ≥ 95%; fallback ≤ 80ms; event-driven invalidation ≤ 30s.


FAC-US-019 — Cache invalidation within 30s of mutation

FieldValue
Epic linkFAC-EPIC-05
PriorityMust
Story points3
FR referencesFR-FAC-CTX-002
Legacy FR refsBR-HIER-007

User story: As a downstream, the cache must reflect node/edge changes within 30s.

Acceptance criteria: integration test asserts ≤ 30s drift.


FAC-US-020 — FHIR Location projection

FieldValue
Epic linkFAC-EPIC-06
PriorityMust
Story points5
FR referencesFR-FAC-FHIR-001
Legacy FR refsSPEC §6, §10

User story: As an interop consumer, I want /fhir/R4/Location to represent each internal Location + parent HierarchyNode as FHIR Location with partOf.

Acceptance criteria: round-trip search by hierarchy-node query param; contract test with interop-service.


FAC-US-021 — FHIR Organization projection

FieldValue
Epic linkFAC-EPIC-06
PriorityMust
Story points3
FR referencesFR-FAC-FHIR-002
Legacy FR refsSPEC §6

User story: As interop, I want org-level nodes exposed as FHIR Organization.

Acceptance criteria: hospital / clinic / moph nodes map; code + name searchable.


FAC-US-022 — Import facility definition (dry-run + apply)

FieldValue
Epic linkFAC-EPIC-07
PriorityShould
Story points8
FR referencesFR-FAC-IO-001
Legacy FR refsFR-FAC-005

User story: As an onboarding admin, I want to import a full facility snapshot with dry-run before applying.

Acceptance criteria: dry-run returns errors list without mutation; apply is transactional; rollback on failure.


FAC-US-023 — Export facility snapshot

FieldValue
Epic linkFAC-EPIC-07
PriorityShould
Story points3
FR referencesFR-FAC-IO-002
Legacy FR refsFR-FAC-005

User story: As an admin, I want to export the full hierarchy + locations + beds as JSON for DR and migration.

Acceptance criteria: export matches import schema; checksum verified.


FAC-US-024 — SLO dashboards and alerts

FieldValue
Epic linkFAC-EPIC-08
PriorityMust
Story points5
FR referencesNFR-FAC-001..005
Legacy FR refsNFR-HIER-001..005, NFR-FAC-001

User story: As SRE, I want dashboards for the 4 key SLIs and alerts wired to on-call.

Acceptance criteria: all alerts fire in staging drills; runbooks linked.


FAC-US-025 — Tenant-isolation integration spec

FieldValue
Epic linkFAC-EPIC-08
PriorityMust
Story points3
FR referencesNFR-FAC-TEN
Legacy FR refsNFR-HIER-004

User story: As platform security, I need the mandatory tenant-isolation test green.

Acceptance criteria: 100 tenants, property-based; no cross-tenant read/write ever succeeds.


FAC-US-026 — Chaos drill: NATS + Redis + Postgres failover

FieldValue
Epic linkFAC-EPIC-08
PriorityShould
Story points5
FR referencesNFR-FAC-CHAOS
Legacy FR refs— (new)

User story: As SRE, I want to prove the service survives dependency outages without data loss.

Acceptance criteria: chaos scenarios pass; runbooks updated post-drill.