Skip to main content

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

TermMeaning
HierarchyNodeSingle entity in the org graph (moph, region, district, hospital, clinic, department, ward, room, virtual-clinic)
HierarchyEdgeDirected relationship: contains (ownership), manages, refers-to
HierarchyProfileNamed schema per country/deployment that constrains allowed node types, root types, relationship types, depth
ProviderNodeMembershipProvider↔Node assignment (primary/secondary, effective-from/to)
LocationOperational metadata anchored to exactly one HierarchyNode (service hours, timezone, locale, capacity)
BedManaged bed resource with class (GENERAL/ICU/etc.) and occupancy state
ResourceCatalogItemRoom or equipment resource consumable by scheduling
Service lineOutpatient vs Inpatient vs Emergency vs ICU vs Diagnostic — routing & reporting dimension
Contains cycleForbidden graph state: a node reachable from itself via contains edges

3. Value Objects

VOShapeValidationNotes
HierarchyNodeIdBranded<string,'HierarchyNodeId'>ULID, prefix hnd_F03
HierarchyEdgeIdBrandedULID, prefix hed_F03
ProfileIdBrandedULID, prefix hpf_F03
LocationIdBrandedULID, prefix loc_F03
BedIdBrandedULID, prefix bed_F03
ResourceIdBrandedULID, prefix res_F03
MembershipIdBrandedULID, prefix pnm_F03
TenantIdBranded (shared kernel)ULID, prefix ten_F03
NodeTypestring closed setmoph|region|district|hospital|clinic|department|ward|room|virtual-clinic|virtual-departmentExtensible per profile
EdgeRelenumcontains / manages / refers-toOnly contains is ownership
ServiceTypeenumOUTPATIENT|INPATIENT|EMERGENCY|ICU|DIAGNOSTIC
BedStatusenumAVAILABLE|RESERVED|OCCUPIED|CLEANING|MAINTENANCE|OUT_OF_SERVICE
BedClassenumGENERAL|SEMI_PRIVATE|PRIVATE|ICU|NICU|ISOLATION
OperationalHoursVO7-day window with open/close or closed=true
TimeZoneIANA stringe.g., Asia/Kabul (+04:30), Asia/Dubai (+04:00)
LocaleCodeBCP-47en, ps, fa-AF, ar
ContactInfoJSONphone[], 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 = true per tenantId.
  • 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:

  • tenantId immutable.
  • typeprofile.allowedNodeTypes.
  • Root (no contains-parent) must have typeprofile.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 contains edges is a DAG (no cycles).
  • relationshipTypeprofile.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

EventAggregateTrigger
facility.hierarchy_node.created.v1HierarchyNodeCreate
facility.hierarchy_node.updated.v1HierarchyNodeUpdate name/code/metadata
facility.hierarchy_node.deactivated.v1HierarchyNodeSoft-deactivate
facility.hierarchy_edge.created.v1HierarchyEdgeCreate
facility.hierarchy_edge.removed.v1HierarchyEdgeRemove
facility.hierarchy_profile.updated.v1HierarchyProfileUpdate
facility.location.created.v1LocationCreate
facility.location.updated.v1LocationUpdate
facility.location.deactivated.v1LocationDeactivate
facility.bed.created.v1BedCreate
facility.bed.status_changed.v1BedStatus transition
facility.resource.created.v1ResourceCreate
facility.resource.updated.v1ResourceUpdate
facility.provider_membership.assigned.v1MembershipAssign
facility.provider_membership.revoked.v1MembershipRevoke

6. Ubiquitous Flows

6.1 Create ward + attach bed config