Skip to main content

Provider Directory Service — Domain Model

Status: populated Owner: TBD Last updated: 2026-04-17 Companion: 02 DDD · SERVICE_OVERVIEW

1. Aggregate Map

2. Ubiquitous Language

TermMeaning
PractitionerLicensed clinician (physician, nurse, pharmacist, dentist, technician)
PractitionerRoleScoped role for a Practitioner (e.g., consultant in Ibn Sina ER)
CredentialProof of licensure (license number, issuing authority, expiry)
SpecialtyClinical specialty codified (SNOMED or local system)
HealthcareServiceService offering (e.g., "ENT OPD clinic") bound to location
ServiceEndpointFHIR/HL7 URL per organisation/location used by partners
NameVariantOne of: local script (ps/fa-AF/ar), Latin transliteration
PrivilegeFine-grained capability: can.order.labs, can.prescribe.controlled, can.sign.notes

3. Value Objects

VOShapeValidationNotes
PractitionerIdBrandedULID prc_F03
PractitionerRoleIdBrandedULID prr_F03
CredentialIdBrandedULID crd_F03
HealthcareServiceIdBrandedULID hcs_F03
EndpointIdBrandedULID end_F03
UserIdBranded (shared kernel with identity)ULID usr_optional link
TenantIdBrandedULID ten_F03
PractitionerKindenumphysician|nurse|midwife|pharmacist|dentist|technician|therapist|other
Identifier{ system: uri, value: string, authority: string, use?: 'official'|'temp' }system unique per value per authority
SpecialtyCode{ system: uri, code: string, display?: string }validated via terminology
NameVariant{ use: 'official'|'nickname'|'maiden', family, given[], middle?, scriptCode }scriptCode = BCP-47
Privilege{ key: string, source: 'role'|'credential', grantedAt, expiresAt? }key in closed vocabulary
PhoneNumberE.164country-code validated

4. Aggregates

4.1 Practitioner (root)

interface Practitioner {
id: PractitionerId;
tenantId: TenantId;
userId?: UserId; // link to identity if this provider logs in
kind: PractitionerKind;
names: NameVariant[]; // at least one 'official'
identifiers: Identifier[]; // national IDs, council numbers
telecom: Array<{ system: 'phone'|'email'|'fax'|'url'; value: string; use?: string }>;
specialties: SpecialtyCode[];
credentials: Credential[];
preferredLanguage?: string; // BCP-47
status: 'active' | 'suspended' | 'deactivated';
employmentStatus: 'employed' | 'contract' | 'visiting' | 'inactive';
active: boolean;
version: number;
createdAt: ISODate;
updatedAt: ISODate;
}

interface Credential {
id: CredentialId;
type: 'license' | 'certification' | 'degree';
number: string;
issuingAuthority: string;
issuedAt: ISODate;
expiresAt?: ISODate;
status: 'active' | 'expired' | 'suspended' | 'revoked';
notes?: string;
}

Invariants:

  • At least one names with use='official'.
  • identifier.value unique per (authority, system) (BR-PROV-003 style).
  • Deactivated practitioners cannot be selected in new clinical actions (BR-PROV-002 equivalent).
  • Active PractitionerRole cannot exist without at least one active credential in required set (BR-PROV-001).

State machine:

4.2 PractitionerRole (root)

interface PractitionerRole {
id: PractitionerRoleId;
tenantId: TenantId;
practitionerId: PractitionerId;
hierarchyNodeId?: HierarchyNodeId; // scope: department/location/ward
healthcareServiceId?: HealthcareServiceId;
role: string; // e.g., 'consultant', 'resident', 'head-of-dept'
privileges: Privilege[];
period: { start: ISODate; end?: ISODate };
active: boolean;
version: number;
}

Invariants:

  • Privileges requiring ordering require a valid Credential on the Practitioner (BR-PROV-001).
  • Cannot be created if Practitioner is deactivated.

4.3 HealthcareService (root)

interface HealthcareService {
id: HealthcareServiceId;
tenantId: TenantId;
hierarchyNodeId?: HierarchyNodeId;
locationId?: LocationId;
name: string;
category: string; // e.g., 'outpatient', 'imaging', 'rehabilitation'
specialties: SpecialtyCode[];
active: boolean;
version: number;
}

4.4 ServiceEndpoint (root)

interface ServiceEndpoint {
id: EndpointId;
tenantId: TenantId;
organizationNodeId?: HierarchyNodeId;
locationId?: LocationId;
type: 'fhir-r4' | 'hl7v2' | 'dicom' | 'webhook' | 'other';
address: string; // URL
status: 'active' | 'inactive' | 'error' | 'deprecated';
authMethod: 'oauth2' | 'mtls' | 'basic' | 'none';
lastHealthCheck?: { status: 'ok'|'degraded'|'down'; at: ISODate; latencyMs?: number };
version: number;
}

5. Domain Events

EventAggregateTrigger
provider_directory.practitioner.created.v1PractitionerCreate
provider_directory.practitioner.updated.v1PractitionerName/telecom/specialty change
provider_directory.practitioner.deactivated.v1PractitionerDeactivate
provider_directory.credential.added.v1CredentialAdd credential
provider_directory.credential.updated.v1CredentialUpdate (e.g., extend expiry)
provider_directory.credential.expiring.v1CredentialScheduled: 60/30/7 days pre-expiry
provider_directory.credential.expired.v1CredentialExpiry date passed
provider_directory.role.assigned.v1RoleCreate role
provider_directory.role.revoked.v1RoleEnd role
provider_directory.healthcare_service.created.v1HealthcareServiceCreate
provider_directory.endpoint.updated.v1EndpointUpdate
provider_directory.endpoint.health_changed.v1EndpointHealth status transition

6. Flows

6.1 Onboard a new consultant

6.2 Credential expiry