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
| Command | Aggregate | Notes |
|---|---|---|
CreateHierarchyNode | HierarchyNode | Validates against active profile; enforces single active root |
UpdateHierarchyNode | HierarchyNode | Name/code/metadata only; type immutable |
DeactivateHierarchyNode | HierarchyNode | Soft delete; cascade-aware |
CreateHierarchyEdge | HierarchyEdge | Rejects if would introduce contains cycle |
RemoveHierarchyEdge | HierarchyEdge | |
CreateHierarchyProfile | HierarchyProfile | Platform admin only |
CloneHierarchyProfile | HierarchyProfile | Tenant admin clones a platform profile |
SetDefaultProfile | HierarchyProfile | Tenant admin |
CreateLocation | Location | Requires hierarchyNodeId that exists + active |
UpdateLocation | Location | Optimistic lock |
SetLocationHours | Location | Full-week replace |
DeactivateLocation | Location | Blocked if occupied beds; force=true admin override (BR-FAC-002) |
CreateBed | Bed | Unique bedNumber within location |
UpdateBedStatus | Bed | Drives state machine (BR-FAC-001) |
CreateResourceCatalogItem | Resource | |
UpdateResourceCatalogItem | Resource | |
DeactivateResourceCatalogItem | Resource | |
AssignProviderToNode | Membership | Called internally by provider-directory-service |
RevokeProviderMembership | Membership | |
ImportFacilityDefinition | Many | Bulk import; dry-run flag |
ExportFacilityDefinition | (query) | Snapshot artefact |
1.2 Queries
| Query | Returns |
|---|---|
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 |
SearchLocations | By hierarchyNodeId, type, status, serviceType, name |
GetAvailableBeds | By location/class/isolation — consumed by inpatient |
GetResourceCatalog | By location, resourceType, status |
GetFacilityReportingDimensions | Aggregates by service line / bed class |
2. Ports
| Port | Purpose |
|---|---|
HierarchyRepository | CRUD + graph traversal over HierarchyNode/Edge |
HierarchyProfileRepository | Profile CRUD |
LocationRepository | CRUD + search |
BedRepository | CRUD + status transitions |
ResourceRepository | CRUD + search |
MembershipRepository | CRUD |
NodeContextCache | Redis adapter with event-driven invalidation |
AccessPolicyClient | POST /internal/access/evaluate |
LicensingClient | POST /internal/licensing/check (called indirectly via guard) |
EventPublisher | Outbox-backed publisher |
FhirProjector | Project 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-relaypublishes to NATS JetStreamFACILITYstream within 2s p95.- Inbox dedupe on incoming tenant/provider-directory events via
inboxtable keyed byeventId.
5. Cache Invalidation
| Event | Invalidate |
|---|---|
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
| Error | HTTP | Code |
|---|---|---|
| Node type not in profile | 422 | HIERARCHY_NODE_TYPE_NOT_ALLOWED |
| Cycle detected | 409 | HIERARCHY_CYCLE |
| Node not found | 404 | HIERARCHY_NODE_NOT_FOUND |
| Orphan location | 400 | LOCATION_NODE_REQUIRED |
| Double root | 409 | HIERARCHY_MULTIPLE_ROOTS |
| Bed double-book | 409 | BED_ALREADY_OCCUPIED |
| Occupied beds on deactivate | 409 | LOCATION_HAS_OCCUPIED_BEDS |
| Access denied | 403 | ACCESS_DENIED |
| License not active | 403 | MODULE_NOT_ACTIVE |
| Optimistic lock | 409 | VERSION_MISMATCH |
All mapped per ERROR_CODES.
7. Policies
| Policy | Trigger | Reaction |
|---|---|---|
OnTenantCreatedPolicy | tenant.tenant.created.v1 | Create default profile + root node |
OnTenantArchivedPolicy | tenant.tenant.archived.v1 | Cascade-deactivate nodes; keep rows |
OnPractitionerCreatedPolicy | provider_directory.practitioner.created.v1 | No-op; allow downstream membership writes |