Skip to main content

Facility Service — Application Logic

Status: populated Owner: TBD Last updated: 2026-04-17 Companion: DOMAIN_MODEL · API_CONTRACTS · EVENT_SCHEMAS

1. Use Cases

1.1 Commands

CommandAggregateNotes
CreateHierarchyNodeHierarchyNodeValidates against active profile; enforces single active root
UpdateHierarchyNodeHierarchyNodeName/code/metadata only; type immutable
DeactivateHierarchyNodeHierarchyNodeSoft delete; cascade-aware
CreateHierarchyEdgeHierarchyEdgeRejects if would introduce contains cycle
RemoveHierarchyEdgeHierarchyEdge
CreateHierarchyProfileHierarchyProfilePlatform admin only
CloneHierarchyProfileHierarchyProfileTenant admin clones a platform profile
SetDefaultProfileHierarchyProfileTenant admin
CreateLocationLocationRequires hierarchyNodeId that exists + active
UpdateLocationLocationOptimistic lock
SetLocationHoursLocationFull-week replace
DeactivateLocationLocationBlocked if occupied beds; force=true admin override (BR-FAC-002)
CreateBedBedUnique bedNumber within location
UpdateBedStatusBedDrives state machine (BR-FAC-001)
CreateResourceCatalogItemResource
UpdateResourceCatalogItemResource
DeactivateResourceCatalogItemResource
AssignProviderToNodeMembershipCalled internally by provider-directory-service
RevokeProviderMembershipMembership
ImportFacilityDefinitionManyBulk import; dry-run flag
ExportFacilityDefinition(query)Snapshot artefact

1.2 Queries

QueryReturns
GetNodeSubtree(nodeId, maxDepth?)Tree of descendants
GetNodeAncestors(nodeId)Ordered root → node chain — hot path for Licensing
GetNodeContext(nodeId)Node + ancestors + effective profile; cached in Redis 5min
GetProviderContext(providerId)Active memberships + resolved node subtree
SearchLocationsBy hierarchyNodeId, type, status, serviceType, name
GetAvailableBedsBy location/class/isolation — consumed by inpatient
GetResourceCatalogBy location, resourceType, status
GetFacilityReportingDimensionsAggregates by service line / bed class

2. Ports

PortPurpose
HierarchyRepositoryCRUD + graph traversal over HierarchyNode/Edge
HierarchyProfileRepositoryProfile CRUD
LocationRepositoryCRUD + search
BedRepositoryCRUD + status transitions
ResourceRepositoryCRUD + search
MembershipRepositoryCRUD
NodeContextCacheRedis adapter with event-driven invalidation
AccessPolicyClientPOST /internal/access/evaluate
LicensingClientPOST /internal/licensing/check (called indirectly via guard)
EventPublisherOutbox-backed publisher
FhirProjectorProject Location / Organization / HealthcareService

3. Orchestration

3.1 Hierarchy create with cycle guard

3.2 Bed status transition

4. Saga / Outbox

  • Every state-changing command writes domain rows + outbox row in the same transaction.
  • outbox-relay publishes to NATS JetStream FACILITY stream within 2s p95.
  • Inbox dedupe on incoming tenant/provider-directory events via inbox table keyed by eventId.

5. Cache Invalidation

EventInvalidate
facility.hierarchy_node.*node_context:{nodeId} + descendants
facility.hierarchy_edge.*node_context:{parent} + node_context:{child} + their ancestors
facility.provider_membership.*provider_context:{providerId}
facility.location.*location:{id}, node_context:{hierarchyNodeId}

TTL 5m; explicit invalidate within 30s of mutation (BR-HIER-007).

6. Error Handling

ErrorHTTPCode
Node type not in profile422HIERARCHY_NODE_TYPE_NOT_ALLOWED
Cycle detected409HIERARCHY_CYCLE
Node not found404HIERARCHY_NODE_NOT_FOUND
Orphan location400LOCATION_NODE_REQUIRED
Double root409HIERARCHY_MULTIPLE_ROOTS
Bed double-book409BED_ALREADY_OCCUPIED
Occupied beds on deactivate409LOCATION_HAS_OCCUPIED_BEDS
Access denied403ACCESS_DENIED
License not active403MODULE_NOT_ACTIVE
Optimistic lock409VERSION_MISMATCH

All mapped per ERROR_CODES.

7. Policies

PolicyTriggerReaction
OnTenantCreatedPolicytenant.tenant.created.v1Create default profile + root node
OnTenantArchivedPolicytenant.tenant.archived.v1Cascade-deactivate nodes; keep rows
OnPractitionerCreatedPolicyprovider_directory.practitioner.created.v1No-op; allow downstream membership writes