Facility Service — Domain Model
Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · 02 DDD · SERVICE_OVERVIEW
1. Aggregate Map
2. Ubiquitous Language
| Term | Meaning |
|---|---|
| HierarchyNode | Single entity in the org graph (moph, region, district, hospital, clinic, department, ward, room, virtual-clinic) |
| HierarchyEdge | Directed relationship: contains (ownership), manages, refers-to |
| HierarchyProfile | Named schema per country/deployment that constrains allowed node types, root types, relationship types, depth |
| ProviderNodeMembership | Provider↔Node assignment (primary/secondary, effective-from/to) |
| Location | Operational metadata anchored to exactly one HierarchyNode (service hours, timezone, locale, capacity) |
| Bed | Managed bed resource with class (GENERAL/ICU/etc.) and occupancy state |
| ResourceCatalogItem | Room or equipment resource consumable by scheduling |
| Service line | Outpatient vs Inpatient vs Emergency vs ICU vs Diagnostic — routing & reporting dimension |
| Contains cycle | Forbidden graph state: a node reachable from itself via contains edges |
3. Value Objects
| VO | Shape | Validation | Notes |
|---|---|---|---|
HierarchyNodeId | Branded<string,'HierarchyNodeId'> | ULID, prefix hnd_ | F03 |
HierarchyEdgeId | Branded | ULID, prefix hed_ | F03 |
ProfileId | Branded | ULID, prefix hpf_ | F03 |
LocationId | Branded | ULID, prefix loc_ | F03 |
BedId | Branded | ULID, prefix bed_ | F03 |
ResourceId | Branded | ULID, prefix res_ | F03 |
MembershipId | Branded | ULID, prefix pnm_ | F03 |
TenantId | Branded (shared kernel) | ULID, prefix ten_ | F03 |
NodeType | string closed set | moph|region|district|hospital|clinic|department|ward|room|virtual-clinic|virtual-department | Extensible per profile |
EdgeRel | enum | contains / manages / refers-to | Only contains is ownership |
ServiceType | enum | OUTPATIENT|INPATIENT|EMERGENCY|ICU|DIAGNOSTIC | |
BedStatus | enum | AVAILABLE|RESERVED|OCCUPIED|CLEANING|MAINTENANCE|OUT_OF_SERVICE | |
BedClass | enum | GENERAL|SEMI_PRIVATE|PRIVATE|ICU|NICU|ISOLATION | |
OperationalHours | VO | 7-day window with open/close or closed=true | |
TimeZone | IANA string | e.g., Asia/Kabul (+04:30), Asia/Dubai (+04:00) | |
LocaleCode | BCP-47 | en, ps, fa-AF, ar | |
ContactInfo | JSON | phone[], email[], address |
4. Aggregates
4.1 HierarchyProfile (root)
interface HierarchyProfile {
id: ProfileId;
tenantId: TenantId | null; // null = platform default
name: string; // AFG_MOPH, UAE_DOH, PRIVATE_HOSPITAL, CLINIC_SIMPLE, TELEMEDICINE, <custom>
allowedNodeTypes: NodeType[];
allowedRootTypes: NodeType[];
allowedRelationships: EdgeRel[];
constraints: {
maxDepth: number; // default 8, platform max 15
requiredChildTypes?: Record<NodeType, NodeType[]>;
};
isDefault: boolean;
version: number; // optimistic lock
}
Invariants:
- At most one
isDefault = truepertenantId. - Platform-level profiles (
tenantId = null) are read-only to Tenant Admins. - Updating a profile must not retroactively invalidate existing nodes — only warn.
4.2 HierarchyNode (root)
interface HierarchyNode {
id: HierarchyNodeId;
tenantId: TenantId; // immutable after creation
profileId: ProfileId; // governs validation
type: NodeType;
name: string; // display name (multilingual via metadata)
code?: string; // external/regulatory code (MoPH UID, DoH code)
metadata: Record<string, unknown>; // address, timezone, regulatoryCodes, capacity
status: 'active' | 'inactive';
version: number;
}
Invariants:
tenantIdimmutable.type∈profile.allowedNodeTypes.- Root (no
contains-parent) must havetype∈profile.allowedRootTypes; at most one active root per tenant. - Soft-delete only (
status = 'inactive'); no hard delete.
State machine:
4.3 HierarchyEdge (root)
interface HierarchyEdge {
id: HierarchyEdgeId;
tenantId: TenantId;
parentNodeId: HierarchyNodeId;
childNodeId: HierarchyNodeId;
relationshipType: EdgeRel;
}
Invariants:
parentNodeId ≠ childNodeId(no self-loop).- The graph of
containsedges is a DAG (no cycles). relationshipType∈profile.allowedRelationships.
4.4 Location (root)
interface Location {
id: LocationId;
tenantId: TenantId;
hierarchyNodeId: HierarchyNodeId; // REQUIRED; no orphan locations
name: string;
serviceType: ServiceType;
capacity?: number;
timezone: TimeZone;
locale: LocaleCode;
currency?: string;
contactInfo?: ContactInfo;
status: 'active' | 'inactive';
version: number;
updatedAt: ISODate;
}
interface LocationHours {
locationId: LocationId;
dayOfWeek: 0|1|2|3|4|5|6;
openTime?: string; // HH:mm 24h
closeTime?: string;
isAvailable: boolean;
}
Invariants (BR-FAC-002): A location can only be deactivated when no occupied beds remain OR an admin-force override is supplied.
4.5 Bed (root)
interface Bed {
id: BedId;
tenantId: TenantId;
locationId: LocationId; // must reference an active Location
bedNumber: string; // unique within locationId
bedClass: BedClass;
status: BedStatus;
isolationCapable: boolean;
housekeepingStatus?: 'clean' | 'in_progress' | 'dirty';
currentPatientId?: PatientId; // set only when status = OCCUPIED
version: number;
}
Invariants (BR-FAC-001): status = OCCUPIED ⇒ exactly one currentPatientId; cannot transition to OCCUPIED with a different patientId while already OCCUPIED.
State machine:
4.6 ResourceCatalogItem (root)
interface ResourceCatalogItem {
id: ResourceId;
tenantId: TenantId;
locationId?: LocationId; // null ⇒ tenant-wide
resourceType: 'ROOM' | 'EQUIPMENT' | 'VEHICLE' | 'OTHER';
name: string;
status: 'active' | 'inactive' | 'maintenance';
attributes?: Record<string, unknown>;
version: number;
}
4.7 ProviderNodeMembership (root)
interface ProviderNodeMembership {
id: MembershipId;
tenantId: TenantId;
providerId: PractitionerId; // shared kernel with provider-directory-service
nodeId: HierarchyNodeId;
primary: boolean; // at most one primary per provider
effectiveFrom: ISODate;
effectiveTo?: ISODate;
version: number;
}
Invariant: At most one primary = true membership per providerId at any point in time.
5. Domain Events
| Event | Aggregate | Trigger |
|---|---|---|
facility.hierarchy_node.created.v1 | HierarchyNode | Create |
facility.hierarchy_node.updated.v1 | HierarchyNode | Update name/code/metadata |
facility.hierarchy_node.deactivated.v1 | HierarchyNode | Soft-deactivate |
facility.hierarchy_edge.created.v1 | HierarchyEdge | Create |
facility.hierarchy_edge.removed.v1 | HierarchyEdge | Remove |
facility.hierarchy_profile.updated.v1 | HierarchyProfile | Update |
facility.location.created.v1 | Location | Create |
facility.location.updated.v1 | Location | Update |
facility.location.deactivated.v1 | Location | Deactivate |
facility.bed.created.v1 | Bed | Create |
facility.bed.status_changed.v1 | Bed | Status transition |
facility.resource.created.v1 | Resource | Create |
facility.resource.updated.v1 | Resource | Update |
facility.provider_membership.assigned.v1 | Membership | Assign |
facility.provider_membership.revoked.v1 | Membership | Revoke |