Skip to content

DDD: Operational & Documentation Bounded Contexts

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.


Bounded ContextDomainResponsibility
Operational ContextAdministrative/WorkflowManages the logistics of a resource’s engagement: registration, intake, queue management, billing groups
Documentation ContextContent DocumentationFocuses 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

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.

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:

  1. Order Grouping: Groups multiple orders (items, services, procedures) under a single documentation context
  2. Coverage Binding: Associates coverage_rights (insurance/coverage) with the operational justification
  3. Documentation Linkage: Links administrative orders to content findings and entries
  4. Billing Integrity: Ensures every order has operational justification for accurate billing
group-record.entity.ts
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
}

1:M (One-to-Many) relationships between GroupRecord and documentation entities instead of 1:1. This architectural decision supports:

Scenario1:1 Limitation1:M Benefit
Multi-Operator ReviewCannot record findings from multiple operatorsEach operator adds their own findings
Revision HistoryMust overwrite previous dataNew records preserve history
Partial UpdatesMust save complete documentAdd incremental findings
Audit TrailLimited change trackingFull version history

The Operational module handles all administrative aspects of a resource’s engagement.

EntityTable NameDescription
EngagementengagementsPrimary engagement record containing RN (Record Number)
OrderordersIndividual items ordered (Service, Category, Procedure, etc.)
CoverageRightcoverage_rightsInsurance/Coverage assigned to a group
GroupRecordgroup_recordsAggregate root connecting orders to documentation context
IntakeintakesStaff intake data (initial evaluation, chief concern)
MeasurementmeasurementsTime-series measurement records
engagement.entity.ts
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
}
order.entity.ts
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
}
coverage-right.entity.ts
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
}
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 --> [*]

The Documentation module handles all content documentation. All entities use a consistent doc_ prefix to clearly distinguish them from operational entities.

EntityTable NameDescription
FindingfindingsStores subjective and objective findings from the session
EntryentriesClassification codes and structured documentation entries
DrawingdrawingsJSONB data for canvas-based illustrations and diagrams
AttachmentattachmentsCaptured images and supporting evidence
AuditRecordaudit_recordsJunction table serving as session log and audit trail

Stores subjective and objective findings from each session.

finding.entity.ts
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:

TypeDescriptionExample
CHIEF_CONCERNMain reason for engagement”Service disruption reported for 2 days”
PRESENT_STATUSHistory of the current issue”Issue started 2 days ago, intermittent, worsening”
EVALUATIONStructured evaluation results”System: All functions nominal, no anomalies”
REVIEW_OF_ITEMSSystematic item review”General: No critical alerts, no escalation needed”

Unified table for structured classification entries with versioning support.

entry.entity.ts
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:

TypeDescriptionBilling Impact
PRIMARYPrimary classification for the sessionMain justification for charges
SECONDARYAdditional classificationsSupports billing necessity
COMORBIDITYPre-existing conditionsAffects billing calculation
COMPLICATIONComplications during serviceMay affect reimbursement

Stores canvas-based illustrations using JSONB for schema flexibility.

drawing.entity.ts
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?

ApproachLimitationJSONB Benefit
Fixed columnsSchema migration for each canvas changeNo migration needed
Separate tableComplex joins for canvas elementsSingle read for full canvas
File storageFile I/O for each loadDatabase query with indexing
JSONB-Flexible schema, queryable, indexed

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”

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

Step 1: Engagement Resolution

// Logic: 1 resource = 1 engagement per day
if (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 engagement
const 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 provided
if (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 engagement
await 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_by and updated_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

AspectcreateIntakeEvaluationregisterOrder
Initiated ByIntake StaffRegistration Staff
Entry PointData Consumer BC → Data Owner BC (MS)Direct Data Owner BC
Primary PurposeIntake evaluation & measurementsAdministrative registration
Record NumberNot generatedNot generated (deferred)
IntakeAlways created/updatedMay or may not exist
MeasurementsAlways createdNot created
OrdersOptional, status → REVIEWEDAlways created, status → REGISTERED/REVIEWED
TransactionSingle transaction (all or nothing)Single operation
IdempotencyYes (upsert pattern)Partial (creates new order each time)

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
// Define allowed transitions: targetStatus → requiredCurrentStatus
const 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}'.`,
);
}
}
Current StatusTarget StatusAllowed?Use Case
REGISTEREDREVIEWEDUse createIntakeEvaluation
REVIEWEDWAITING_PROCESSINGResource called to queue
WAITING_PROCESSINGPROCESSINGOperator starts session
PROCESSINGPROCESSEDSession completed
PROCESSEDCOMPLETEDUse session finalization
COMPLETEDAnyTerminal state
CANCELLEDAnyTerminal state

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.


Design ChoiceScalability Benefit
1:M RelationsSupport unlimited findings/entries per engagement
JSONB for DrawingsSchema-flexible canvas data storage
Separate ContextsIndependent scaling of operational vs. documentation
Soft DeletesPreserve data for audit while “removing” from view
FeatureAudit Capability
AuditRecordFull change history with before/after snapshots
recorded_by / entered_byTrack who documented each item
performed_at timestampsPrecise timing of all actions
LinkageBilling Impact
GroupRecord → CoverageRightEach order group tied to specific coverage
GroupRecord → EntryOperational necessity documented
Order → GroupRecordEach charge has operational justification
EntryType (PRIMARY/SECONDARY)Proper billing calculation

  1. Bounded Contexts: Clear separation between Operational (Administrative) and Documentation (Content) contexts
  2. Aggregate Root: GroupRecord serves as the bridge and maintains consistency
  3. 1:M Relationships: Supports versioning, multi-operator scenarios, and audit trails
  4. Documentation Prefix: All documentation entities use consistent naming for clarity
  5. JSONB for Flexibility: Drawings stored as JSONB for schema evolution
  6. Comprehensive Audit: AuditRecord logs all changes with full context
AreaBenefit
DevelopmentClear domain boundaries, easier maintenance
ScalabilityIndependent scaling, flexible relationships
ComplianceFull audit trail, data integrity
BillingOperational justification for every charge
OperationsSupport 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.