DDD: Operational & Documentation Bounded Contexts
Overview
Section titled “Overview”This document explains the database design for Operational and Documentation modules using Domain-Driven Design (DDD) principles. The architecture separates workflow management from content documentation, resulting in a scalable, maintainable, audit-compliant enterprise system.
1. Architectural Philosophy
Section titled “1. Architectural Philosophy”1.1 Bounded Contexts Overview
Section titled “1.1 Bounded Contexts Overview”| Bounded Context | Domain | Responsibility |
|---|---|---|
| Operational Context | Administrative/Workflow | Manages the logistics of a resource’s engagement: registration, intake, queue management, billing groups |
| Documentation Context | Content Documentation | Focuses on operator findings, entries, classifications, and structured documentation |
graph TB
subgraph "Operational Context (Administrative)"
V[engagements]
VO[orders]
VR[coverage_rights]
GO[group_records]
A[intakes]
VS[measurements]
end
subgraph "Documentation Context (Content)"
CF[findings]
CD[entries]
CDR[drawings]
CA[attachments]
CE[audit_records]
end
GO -->|"Aggregate Root Bridge"| CF
GO -->|"Aggregate Root Bridge"| CD
GO -->|"Aggregate Root Bridge"| CDR
GO -->|"Aggregate Root Bridge"| CA
GO -->|"Aggregate Root Bridge"| CE
V --> GO
VO --> GO
VR --> GO
A --> V
VS --> V
style GO fill:#f9f,stroke:#333,stroke-width:4px
style V fill:#e1f5fe
style VO fill:#e1f5fe
style VR fill:#e1f5fe
style CF fill:#e8f5e9
style CD fill:#e8f5e9
1.2 Why Two Bounded Contexts?
Section titled “1.2 Why Two Bounded Contexts?”Separation of Concerns:
- Administrative staff manage engagement logistics (registration, coverage verification, billing)
- Operational staff focus on content documentation (findings, entries, classifications)
Different Lifecycles:
- Engagement data is typically finalized when the resource leaves the active workflow
- Documentation data may be amended, versioned, or referenced indefinitely
Compliance Requirements:
- Content documentation requires strict audit trails (who, what, when)
- Administrative data requires billing accuracy and coverage compliance
2. The Bridge: GroupRecord as Aggregate Root
Section titled “2. The Bridge: GroupRecord as Aggregate Root”The GroupRecord entity serves as the central hub that bridges the Operational and Documentation contexts. It is the Aggregate Root that maintains consistency across both domains.
2.1 Purpose of GroupRecord
Section titled “2.1 Purpose of GroupRecord”graph LR
subgraph "Operational Context"
V[Engagement RN]
VO[Orders]
VR[Coverage Rights]
end
subgraph "Bridge"
GO[GroupRecord<br/>Aggregate Root]
end
subgraph "Documentation Context"
CF[Findings]
CD[Entries]
end
V --> GO
VO --> GO
VR --> GO
GO --> CF
GO --> CD
style GO fill:#ff9800,stroke:#e65100,stroke-width:3px
Key Responsibilities:
- Order Grouping: Groups multiple
orders(items, services, procedures) under a single documentation context - Coverage Binding: Associates
coverage_rights(insurance/coverage) with the operational justification - Documentation Linkage: Links administrative orders to content findings and entries
- Billing Integrity: Ensures every order has operational justification for accurate billing
2.2 GroupRecord Entity Design
Section titled “2.2 GroupRecord Entity Design”import { BaseEntity } from '@lib/common/abstracts/base-entity.abstract';
@Entity({ name: 'group_records', database: AppDatabases.APP_CORE })export class GroupRecord extends BaseEntity { @Column({ type: 'uuid', comment: 'Reference to the parent engagement' }) engagement_id: string;
@Column({ type: 'uuid', nullable: true, comment: 'Coverage/insurance for this group' }) coverage_right_id: string | null;
@Column({ type: 'enum', enum: GroupRecordStatus, default: GroupRecordStatus.ACTIVE, comment: 'Current status of the record group', }) status: GroupRecordStatus;
@Column({ type: 'text', nullable: true, comment: 'Operational notes for the group' }) operational_notes: string | null;
// Relationships @ManyToOne(() => Engagement, (engagement) => engagement.group_records) engagement: Engagement;
@ManyToOne(() => CoverageRight, (right) => right.group_records) coverage_right: CoverageRight;
@OneToMany(() => Order, (order) => order.group_record) orders: Order[];
@OneToMany(() => Finding, (finding) => finding.group_record) findings: Finding[];
@OneToMany(() => Entry, (entry) => entry.group_record) entries: Entry[];
// Note: id, created_at, updated_at, created_by, updated_by, // is_deleted, deleted_at, deleted_by, deleted_reason // are inherited from BaseEntity}2.3 Cardinality Design Decision
Section titled “2.3 Cardinality Design Decision”1:M (One-to-Many) relationships between GroupRecord and documentation entities instead of 1:1. This architectural decision supports:
| Scenario | 1:1 Limitation | 1:M Benefit |
|---|---|---|
| Multi-Operator Review | Cannot record findings from multiple operators | Each operator adds their own findings |
| Revision History | Must overwrite previous data | New records preserve history |
| Partial Updates | Must save complete document | Add incremental findings |
| Audit Trail | Limited change tracking | Full version history |
3. Operational Module
Section titled “3. Operational Module”The Operational module handles all administrative aspects of a resource’s engagement.
3.1 Entity Overview
Section titled “3.1 Entity Overview”| Entity | Table Name | Description |
|---|---|---|
| Engagement | engagements | Primary engagement record containing RN (Record Number) |
| Order | orders | Individual items ordered (Service, Category, Procedure, etc.) |
| CoverageRight | coverage_rights | Insurance/Coverage assigned to a group |
| GroupRecord | group_records | Aggregate root connecting orders to documentation context |
| Intake | intakes | Staff intake data (initial evaluation, chief concern) |
| Measurement | measurements | Time-series measurement records |
3.2 Engagement Entity
Section titled “3.2 Engagement Entity”import { BaseEntity } from '@lib/common/abstracts/base-entity.abstract';
@Entity({ name: 'engagements', database: AppDatabases.APP_CORE })@Index('idx_engagements_rn', ['rn'], { unique: true })@Index('idx_engagements_resource_id', ['resource_id'])@Index('idx_engagements_engagement_date', ['engagement_date'])export class Engagement extends BaseEntity { @Column({ type: 'varchar', length: 15, unique: true, comment: 'Record Number (RN) - unique identifier for this engagement', }) rn: string;
@Column({ type: 'uuid', comment: 'Reference to the resource' }) resource_id: string;
@Column({ type: 'enum', enum: EngagementType, comment: 'Type of engagement: STANDARD, EXTENDED, URGENT, etc.', }) engagement_type: EngagementType;
@Column({ type: 'enum', enum: EngagementStatus, default: EngagementStatus.WAITING, comment: 'Current engagement status in the workflow', }) status: EngagementStatus;
@Column({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP', comment: 'Date and time of the engagement', }) engagement_date: Date;
@Column({ type: 'uuid', nullable: true, comment: 'Assigned department' }) department_id: string | null;
@Column({ type: 'uuid', nullable: true, comment: 'Assigned operator' }) assigned_operator_id: string | null;
// Relationships @OneToMany(() => GroupRecord, (group) => group.engagement) group_records: GroupRecord[];
@OneToMany(() => Intake, (intake) => intake.engagement) intakes: Intake[];
@OneToMany(() => Measurement, (m) => m.engagement) measurements: Measurement[];
// Note: id, created_at, updated_at, created_by, updated_by, // is_deleted, deleted_at, deleted_by, deleted_reason // are inherited from BaseEntity}3.3 Order Entity
Section titled “3.3 Order Entity”import { BaseEntity } from '@lib/common/abstracts/base-entity.abstract';
@Entity({ name: 'orders', database: AppDatabases.APP_CORE })@Index('idx_orders_group_record_id', ['group_record_id'])@Index('idx_orders_order_type', ['order_type'])export class Order extends BaseEntity { @Column({ type: 'uuid', comment: 'Reference to the group record' }) group_record_id: string;
@Column({ type: 'varchar', length: 20, unique: true, comment: 'Order number for tracking', }) order_number: string;
@Column({ type: 'enum', enum: OrderType, comment: 'Type: SERVICE, CATEGORY, PROCEDURE, RESOURCE, etc.', }) order_type: OrderType;
@Column({ type: 'varchar', length: 50, comment: 'Item code from master data' }) order_code: string;
@Column({ type: 'varchar', length: 255, comment: 'Item name/description' }) order_name: string;
@Column({ type: 'int', default: 1, comment: 'Quantity ordered' }) quantity: number;
@Column({ type: 'enum', enum: OrderPriority, default: OrderPriority.ROUTINE, comment: 'Priority: STAT, URGENT, ROUTINE', }) priority: OrderPriority;
@Column({ type: 'enum', enum: OrderStatus, default: OrderStatus.REGISTERED, comment: 'Current order status in workflow', }) status: OrderStatus;
@Column({ type: 'decimal', precision: 12, scale: 2, comment: 'Unit price' }) unit_price: number;
@Column({ type: 'decimal', precision: 12, scale: 2, comment: 'Total price' }) total_price: number;
@Column({ type: 'uuid', nullable: true, comment: 'User who placed the order' }) ordered_by: string | null;
// Relationships @ManyToOne(() => GroupRecord, (group) => group.orders) @JoinColumn({ name: 'group_record_id' }) group_record: GroupRecord;
// Note: id, created_at, updated_at, created_by, updated_by, // is_deleted, deleted_at, deleted_by, deleted_reason // are inherited from BaseEntity}3.4 CoverageRight Entity
Section titled “3.4 CoverageRight Entity”import { BaseEntity } from '@lib/common/abstracts/base-entity.abstract';
@Entity({ name: 'coverage_rights', database: AppDatabases.APP_CORE })export class CoverageRight extends BaseEntity { @Column({ type: 'uuid', comment: 'Reference to the engagement' }) engagement_id: string;
@Column({ type: 'varchar', length: 50, comment: 'Coverage scheme code' }) scheme_code: string;
@Column({ type: 'varchar', length: 255, comment: 'Coverage scheme name' }) scheme_name: string;
@Column({ type: 'enum', enum: RightType, comment: 'Type: PRIMARY, SECONDARY, TERTIARY', }) right_type: RightType;
@Column({ type: 'varchar', length: 50, nullable: true, comment: 'Policy number' }) policy_number: string | null;
@Column({ type: 'date', nullable: true, comment: 'Coverage start date' }) coverage_start: Date | null;
@Column({ type: 'date', nullable: true, comment: 'Coverage end date' }) coverage_end: Date | null;
@Column({ type: 'decimal', precision: 5, scale: 2, default: 100, comment: 'Coverage percentage', }) coverage_percent: number;
@Column({ type: 'boolean', default: true, comment: 'Is this right verified?' }) is_verified: boolean;
// Relationships @OneToMany(() => GroupRecord, (group) => group.coverage_right) group_records: GroupRecord[];
// Note: id, created_at, updated_at, created_by, updated_by, // is_deleted, deleted_at, deleted_by, deleted_reason // are inherited from BaseEntity}3.5 Engagement Status Flow
Section titled “3.5 Engagement Status Flow”stateDiagram-v2
[*] --> WAITING: Resource Registered
WAITING --> PROCESSING: Called to Queue
PROCESSING --> COMPLETED: Processing Done
PROCESSING --> WAITING: Back to Queue
COMPLETED --> [*]: Engagement Finalized
WAITING --> CANCELLED: Resource Left/No Show
PROCESSING --> CANCELLED: Engagement Cancelled
CANCELLED --> [*]
4. Documentation Module
Section titled “4. Documentation Module”The Documentation module handles all content documentation. All entities use a consistent doc_ prefix to clearly distinguish them from operational entities.
4.1 Entity Overview
Section titled “4.1 Entity Overview”| Entity | Table Name | Description |
|---|---|---|
| Finding | findings | Stores subjective and objective findings from the session |
| Entry | entries | Classification codes and structured documentation entries |
| Drawing | drawings | JSONB data for canvas-based illustrations and diagrams |
| Attachment | attachments | Captured images and supporting evidence |
| AuditRecord | audit_records | Junction table serving as session log and audit trail |
4.2 Finding Entity
Section titled “4.2 Finding Entity”Stores subjective and objective findings from each session.
import { BaseEntity } from '@lib/common/abstracts/base-entity.abstract';
@Entity({ name: 'findings', database: AppDatabases.APP_CORE })@Index('idx_findings_group_record_id', ['group_record_id'])@Index('idx_findings_finding_type', ['finding_type'])export class Finding extends BaseEntity { @Column({ type: 'uuid', comment: 'Reference to the group record' }) group_record_id: string;
@Column({ type: 'enum', enum: FindingType, comment: 'Type: CHIEF_CONCERN, PRESENT_STATUS, EVALUATION, REVIEW_OF_ITEMS', }) finding_type: FindingType;
@Column({ type: 'text', comment: 'Detailed finding content (Subjective or Objective)', }) content: string;
@Column({ type: 'varchar', length: 50, nullable: true, comment: 'Category or domain (for structured evaluation)', }) category: string | null;
@Column({ type: 'int', default: 0, comment: 'Display order for findings' }) display_order: number;
@Column({ type: 'uuid', comment: 'Operator who recorded this finding' }) recorded_by: string;
@Column({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP', comment: 'When the finding was recorded', }) recorded_at: Date;
// Relationships @ManyToOne(() => GroupRecord, (group) => group.findings) @JoinColumn({ name: 'group_record_id' }) group_record: GroupRecord;
// Note: id, created_at, updated_at, created_by, updated_by, // is_deleted, deleted_at, deleted_by, deleted_reason // are inherited from BaseEntity}Finding Types:
| Type | Description | Example |
|---|---|---|
CHIEF_CONCERN | Main reason for engagement | ”Service disruption reported for 2 days” |
PRESENT_STATUS | History of the current issue | ”Issue started 2 days ago, intermittent, worsening” |
EVALUATION | Structured evaluation results | ”System: All functions nominal, no anomalies” |
REVIEW_OF_ITEMS | Systematic item review | ”General: No critical alerts, no escalation needed” |
4.3 Entry Entity
Section titled “4.3 Entry Entity”Unified table for structured classification entries with versioning support.
import { BaseEntity } from '@lib/common/abstracts/base-entity.abstract';
@Entity({ name: 'entries', database: AppDatabases.APP_CORE })@Index('idx_entries_group_record_id', ['group_record_id'])@Index('idx_entries_classification_code', ['classification_code'])@Index('idx_entries_entry_type', ['entry_type'])export class Entry extends BaseEntity { @Column({ type: 'uuid', comment: 'Reference to the group record' }) group_record_id: string;
@Column({ type: 'enum', enum: ClassificationVersion, default: ClassificationVersion.V2, comment: 'Classification version: V1 or V2', }) classification_version: ClassificationVersion;
@Column({ type: 'varchar', length: 20, comment: 'Classification code' }) classification_code: string;
@Column({ type: 'varchar', length: 500, comment: 'Entry description' }) description: string;
@Column({ type: 'enum', enum: EntryType, default: EntryType.PRIMARY, comment: 'Type: PRIMARY, SECONDARY, COMORBIDITY, COMPLICATION', }) entry_type: EntryType;
@Column({ type: 'enum', enum: EntryCertainty, default: EntryCertainty.CONFIRMED, comment: 'Certainty: CONFIRMED, PROVISIONAL, DIFFERENTIAL, RULED_OUT', }) certainty: EntryCertainty;
@Column({ type: 'int', default: 1, comment: 'Entry sequence/priority' }) sequence: number;
@Column({ type: 'text', nullable: true, comment: 'Additional operational notes' }) notes: string | null;
@Column({ type: 'uuid', comment: 'Operator who made this entry' }) entered_by: string;
@Column({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP', comment: 'When the entry was made', }) entered_at: Date;
// Relationships @ManyToOne(() => GroupRecord, (group) => group.entries) @JoinColumn({ name: 'group_record_id' }) group_record: GroupRecord;
// Note: id, created_at, updated_at, created_by, updated_by, // is_deleted, deleted_at, deleted_by, deleted_reason // are inherited from BaseEntity}Entry Types for Billing:
| Type | Description | Billing Impact |
|---|---|---|
PRIMARY | Primary classification for the session | Main justification for charges |
SECONDARY | Additional classifications | Supports billing necessity |
COMORBIDITY | Pre-existing conditions | Affects billing calculation |
COMPLICATION | Complications during service | May affect reimbursement |
4.4 Drawing Entity
Section titled “4.4 Drawing Entity”Stores canvas-based illustrations using JSONB for schema flexibility.
import { BaseEntity } from '@lib/common/abstracts/base-entity.abstract';
@Entity({ name: 'drawings', database: AppDatabases.APP_CORE })@Index('idx_drawings_group_record_id', ['group_record_id'])export class Drawing extends BaseEntity { @Column({ type: 'uuid', comment: 'Reference to the group record' }) group_record_id: string;
@Column({ type: 'varchar', length: 100, comment: 'Title of the drawing' }) title: string;
@Column({ type: 'varchar', length: 50, nullable: true, comment: 'Region or section (e.g., OVERVIEW, DETAIL, SECTION_A)', }) region: string | null;
@Column({ type: 'jsonb', comment: 'Canvas drawing data in JSON format (fabric.js or similar)', }) canvas_data: object;
@Column({ type: 'uuid', comment: 'Operator who created this drawing' }) created_by_operator: string;
// Relationships @ManyToOne(() => GroupRecord, (group) => group.drawings) @JoinColumn({ name: 'group_record_id' }) group_record: GroupRecord;}Why JSONB for Drawings?
| Approach | Limitation | JSONB Benefit |
|---|---|---|
| Fixed columns | Schema migration for each canvas change | No migration needed |
| Separate table | Complex joins for canvas elements | Single read for full canvas |
| File storage | File I/O for each load | Database query with indexing |
| JSONB ✅ | - | Flexible schema, queryable, indexed |
5. Database Relationships
Section titled “5. Database Relationships”5.1 Complete ERD
Section titled “5.1 Complete ERD”erDiagram
engagements ||--o{ group_records : "has many"
engagements ||--o{ intakes : "has many"
engagements ||--o{ measurements : "has many"
group_records ||--o{ orders : "has many"
group_records ||--o{ findings : "has many"
group_records ||--o{ entries : "has many"
group_records ||--o{ drawings : "has many"
group_records ||--o{ attachments : "has many"
group_records ||--o{ audit_records : "has many"
coverage_rights ||--o{ group_records : "covers many"
engagements {
uuid id PK
varchar rn UK
uuid resource_id
enum engagement_type
enum status
timestamptz engagement_date
uuid department_id
}
group_records {
uuid id PK
uuid engagement_id FK
uuid coverage_right_id FK
enum status
text operational_notes
}
orders {
uuid id PK
uuid group_record_id FK
varchar order_number UK
enum order_type
varchar order_code
enum status
decimal unit_price
}
findings {
uuid id PK
uuid group_record_id FK
enum finding_type
text content
uuid recorded_by
timestamptz recorded_at
}
entries {
uuid id PK
uuid group_record_id FK
varchar classification_code
enum entry_type
enum certainty
uuid entered_by
}
6. Service Layer: Cross-Context Operations
Section titled “6. Service Layer: Cross-Context Operations”6.1 Creating an Intake Evaluation
Section titled “6.1 Creating an Intake Evaluation”When a staff member submits an intake evaluation, the system must coordinate across both contexts within a single transaction.
Controller: apps/data-consumer-bc/src/controllers/intakes.proxy-controller.ts
Entry Point: POST /intakes
sequenceDiagram
participant S as Staff (Intake)
participant OPD as Data Consumer BC
participant EMR as Data Owner BC
participant DB as Database
S->>OPD: POST /intakes
Note over S,OPD: CreateIntakeDTO<br/>{resource_id, measurements[], orders[]}
OPD->>EMR: createIntakeEvaluation (Microservice)
activate EMR
EMR->>DB: Begin Transaction
EMR->>DB: Resolve or Create Engagement
Note over EMR,DB: 1 resource = 1 engagement per day rule
EMR->>DB: Upsert Intake Record
Note over EMR,DB: Create or update intake for this engagement
EMR->>DB: Create Measurement Records
Note over EMR,DB: Time-series data — always create new records
EMR->>DB: Create Orders (if provided)
Note over EMR,DB: Orders with chief_concern<br/>Status: REGISTERED initially
EMR->>DB: Update Order Status → REVIEWED
Note over EMR,DB: Link intake_id to orders<br/>Change status from REGISTERED to REVIEWED
EMR->>DB: Commit Transaction
deactivate EMR
EMR-->>OPD: Return Intake with Relations
OPD-->>S: 201 Created / 200 OK
Business Logic Deep Dive
Section titled “Business Logic Deep Dive”Step 1: Engagement Resolution
// Logic: 1 resource = 1 engagement per dayif (data.engagement_id) { engagement = await findExistingEngagement(data.engagement_id);} else { const todayFilter = { resource_id: data.resource_id, engagement_date: Between(startOfDay, endOfDay), is_deleted: false, }; engagement = (await findOne(todayFilter)) || (await createNewEngagement());}Step 2: Intake Upsert
// Check if intake exists for this engagementconst existingIntake = await findOne({ engagement_id: engagement.id });
if (existingIntake) { // UPDATE: Auto-map all DTO fields const updateData = { chief_concern: data.chief_concern, pain_score: data.pain_score, risk_level: data.risk_level, // ... all other intake fields updated_by: currentUser.id, }; await update(existingIntake.id, updateData);} else { // CREATE: New intake await create({ engagement_id: engagement.id, resource_id: data.resource_id, ...intakeFields, created_by: currentUser.id, });}Step 3: Measurement Creation
// Always create new measurement records (time-series data)for (const measurement of data.measurements) { await create({ intake_id: intake.id, engagement_id: engagement.id, measurement_type: measurement.type, value: measurement.value, measured_at: new Date(), created_by: currentUser.id, });}Step 4: Orders & Status Update
// Create orders if providedif (data.orders?.length) { for (const order of data.orders) { await createOrder({ engagement_id: engagement.id, intake_id: null, // Will be updated in next step status: OrderStatus.REGISTERED, ...orderData, }); }}
// Update all orders for this engagementawait updateOrders({ where: { engagement_id: engagement.id }, set: { intake_id: intake.id, status: OrderStatus.REVIEWED, },});Key Characteristics:
- Transactional: All operations in a single database transaction
- Idempotent: Can be called multiple times (upsert pattern)
- Status Progression: REGISTERED → REVIEWED
- Audit Trail: Tracks
created_byandupdated_by
6.2 Flow 2: registerOrder (Registration Desk)
Section titled “6.2 Flow 2: registerOrder (Registration Desk)”Controller: apps/data-owner-bc/src/modules/engagement/controllers/engagements.controller.ts
Entry Point: POST /engagements
This flow is initiated at the registration desk when a resource arrives.
sequenceDiagram
participant R as Registration Staff
participant VC as Engagements Controller
participant VS as Engagements Service
participant VOS as Orders Service
participant DB as Database
participant IAM as System Admin Service
R->>VC: POST /engagements
Note over R,VC: CreateEngagementDTO<br/>{resource_id, department_id, room_id, queue_number}
VC->>VS: registerOrder(data, currentUser)
activate VS
VS->>DB: Validate Resource Exists
VS->>DB: Check Today's Engagement
Note over VS,DB: WHERE resource_id = ? AND<br/>engagement_date BETWEEN startOfDay AND endOfDay
alt Engagement Exists Today
VS->>VOS: Count Orders with group_record_id = NULL
Note over VS,VOS: For sequence numbering
else No Engagement Today
VS->>DB: Create New Engagement
Note over VS,DB: rn = NULL (temporary)<br/>status = WAITING
end
VS->>IAM: Get Department Info (Microservice)
IAM-->>VS: {name_en}
VS->>IAM: Get Room Info (Microservice)
IAM-->>VS: {room_name_en}
VS->>VOS: createOrder(orderData)
Note over VS,VOS: group_record_id = NULL<br/>intake_id = NULL or existing<br/>status = REGISTERED or REVIEWED<br/>sequence = count + 1
VOS->>DB: INSERT orders
VS->>DB: Fetch Engagement with Relations
deactivate VS
VS-->>VC: Return Engagement
VC-->>R: 201 Created
Key Characteristics:
- Record Number Deferred: RN is NULL initially, generated later when processing begins
- Denormalized Data: Department/room names stored for query performance
- Smart Status: Auto-detects if intake exists
- Sequence Tracking: Orders numbered sequentially per engagement
- Microservice Integration: Fetches department/room data from System Admin BC
6.3 Comparison: Two Flows
Section titled “6.3 Comparison: Two Flows”| Aspect | createIntakeEvaluation | registerOrder |
|---|---|---|
| Initiated By | Intake Staff | Registration Staff |
| Entry Point | Data Consumer BC → Data Owner BC (MS) | Direct Data Owner BC |
| Primary Purpose | Intake evaluation & measurements | Administrative registration |
| Record Number | Not generated | Not generated (deferred) |
| Intake | Always created/updated | May or may not exist |
| Measurements | Always created | Not created |
| Orders | Optional, status → REVIEWED | Always created, status → REGISTERED/REVIEWED |
| Transaction | Single transaction (all or nothing) | Single operation |
| Idempotency | Yes (upsert pattern) | Partial (creates new order each time) |
7. Order Status State Machine
Section titled “7. Order Status State Machine”7.1 Status Transition Flow
Section titled “7.1 Status Transition Flow”stateDiagram-v2
[*] --> REGISTERED: Resource Registered
REGISTERED --> REVIEWED: Intake Complete
REVIEWED --> WAITING_PROCESSING: Called to Queue
WAITING_PROCESSING --> PROCESSING: Operator Starts
PROCESSING --> PROCESSED: Processing Complete
PROCESSED --> COMPLETED: Session Finalized
REGISTERED --> CANCELLED: Session Cancelled
REVIEWED --> CANCELLED: Session Cancelled
WAITING_PROCESSING --> CANCELLED: Session Cancelled
PROCESSING --> CANCELLED: Session Cancelled
REGISTERED --> NO_SHOW: Resource Didn't Arrive
REVIEWED --> NO_SHOW: Resource Left
WAITING_PROCESSING --> NO_SHOW: Resource Left
COMPLETED --> [*]
CANCELLED --> [*]
NO_SHOW --> [*]
note right of COMPLETED: Terminal State
note right of CANCELLED: Terminal State
note right of NO_SHOW: Terminal State
7.2 Validation Logic
Section titled “7.2 Validation Logic”// Define allowed transitions: targetStatus → requiredCurrentStatusconst allowedTransitions: Record<OrderStatus, OrderStatus | null> = { [OrderStatus.WAITING_PROCESSING]: OrderStatus.REVIEWED, [OrderStatus.PROCESSING]: OrderStatus.WAITING_PROCESSING, [OrderStatus.PROCESSED]: OrderStatus.PROCESSING,
// These transitions are not allowed through this method [OrderStatus.NO_SHOW]: null, [OrderStatus.REGISTERED]: null, [OrderStatus.REVIEWED]: null, [OrderStatus.COMPLETED]: null, [OrderStatus.CANCELLED]: null,};
function validateStatusTransition( currentStatus: OrderStatus, targetStatus: OrderStatus,): void { const requiredStatus = allowedTransitions[targetStatus];
if (requiredStatus === null) { throw new BadRequestException( `Status transition to '${targetStatus}' is not allowed through this method.`, ); }
if (currentStatus !== requiredStatus) { throw new BadRequestException( `Invalid workflow: Cannot transition from '${currentStatus}' to '${targetStatus}'. ` + `Current status must be '${requiredStatus}'.`, ); }}7.3 Allowed Transitions Table
Section titled “7.3 Allowed Transitions Table”| Current Status | Target Status | Allowed? | Use Case |
|---|---|---|---|
REGISTERED | REVIEWED | ❌ | Use createIntakeEvaluation |
REVIEWED | WAITING_PROCESSING | ✅ | Resource called to queue |
WAITING_PROCESSING | PROCESSING | ✅ | Operator starts session |
PROCESSING | PROCESSED | ✅ | Session completed |
PROCESSED | COMPLETED | ❌ | Use session finalization |
COMPLETED | Any | ❌ | Terminal state |
CANCELLED | Any | ❌ | Terminal state |
8. Multi-Operator Scenario
Section titled “8. Multi-Operator Scenario”graph TB
subgraph "Engagement RN-2025-001"
GO1[GroupRecord 1<br/>Primary Review]
GO2[GroupRecord 2<br/>Specialist Consult]
GO3[GroupRecord 3<br/>Follow-up]
end
GO1 --> CF1[Finding: General Evaluation]
GO1 --> CD1[Entry: Classification A]
GO2 --> CF2[Finding: Specialist Evaluation]
GO2 --> CD2[Entry: Classification B]
GO3 --> VO1[Order: Service Item 1]
GO3 --> VO2[Order: Service Item 2]
style GO1 fill:#e3f2fd
style GO2 fill:#fce4ec
style GO3 fill:#e8f5e9
Each GroupRecord represents an independent context within the same engagement — multiple operators can document concurrently without overwriting each other’s work.
9. Why This Design?
Section titled “9. Why This Design?”9.1 Scalability Benefits
Section titled “9.1 Scalability Benefits”| Design Choice | Scalability Benefit |
|---|---|
| 1:M Relations | Support unlimited findings/entries per engagement |
| JSONB for Drawings | Schema-flexible canvas data storage |
| Separate Contexts | Independent scaling of operational vs. documentation |
| Soft Deletes | Preserve data for audit while “removing” from view |
9.2 Audit Trail Benefits
Section titled “9.2 Audit Trail Benefits”| Feature | Audit Capability |
|---|---|
| AuditRecord | Full change history with before/after snapshots |
| recorded_by / entered_by | Track who documented each item |
| performed_at timestamps | Precise timing of all actions |
9.3 Billing / Coverage Benefits
Section titled “9.3 Billing / Coverage Benefits”| Linkage | Billing Impact |
|---|---|
| GroupRecord → CoverageRight | Each order group tied to specific coverage |
| GroupRecord → Entry | Operational necessity documented |
| Order → GroupRecord | Each charge has operational justification |
| EntryType (PRIMARY/SECONDARY) | Proper billing calculation |
10. Summary
Section titled “10. Summary”10.1 Key Architectural Decisions
Section titled “10.1 Key Architectural Decisions”- Bounded Contexts: Clear separation between Operational (Administrative) and Documentation (Content) contexts
- Aggregate Root:
GroupRecordserves as the bridge and maintains consistency - 1:M Relationships: Supports versioning, multi-operator scenarios, and audit trails
- Documentation Prefix: All documentation entities use consistent naming for clarity
- JSONB for Flexibility: Drawings stored as JSONB for schema evolution
- Comprehensive Audit:
AuditRecordlogs all changes with full context
10.2 Benefits Summary
Section titled “10.2 Benefits Summary”| Area | Benefit |
|---|---|
| Development | Clear domain boundaries, easier maintenance |
| Scalability | Independent scaling, flexible relationships |
| Compliance | Full audit trail, data integrity |
| Billing | Operational justification for every charge |
| Operations | Support for complex multi-operator workflows |
This architecture provides a solid foundation for an enterprise system that is both operationally comprehensive and administratively efficient, while maintaining the highest standards for data integrity, audit compliance, and billing accuracy.