ข้ามไปที่เนื้อหา

05. Data Models & Status Flow - โครงสร้างข้อมูลและสถานะ

เอกสารสรุปโครงสร้างข้อมูล (Data Models), การเปลี่ยนแปลงสถานะ (Status Transitions), และความสัมพันธ์ของข้อมูล (Data Relationships) ในระบบชันสูตร (LAB Module)

เวอร์ชัน: 1.0
อัปเดตล่าสุด: 26 ธันวาคม 2567
ผู้เขียน: HIS Development Team


📑 สารบัญ

  1. Data Models (โครงสร้างข้อมูล)
  2. 1.1 Patient Model
  3. 1.2 LabOrder Model
  4. 1.3 LabItem Model
  5. 1.4 LabResult Model
  6. 1.5 Specimen Model
  7. 1.6 FileAttachment Model

  8. Status Transitions (การเปลี่ยนแปลงสถานะ)

  9. 2.1 Order Status Flow
  10. 2.2 Result Status Flow
  11. 2.3 Specimen Status Flow

  12. Data Relationships (ความสัมพันธ์ของข้อมูล)

  13. 3.1 Entity Relationship Diagram
  14. 3.2 Data Flow Diagram

  15. LocalStorage Structure (โครงสร้างการจัดเก็บ)

  16. 4.1 Storage Keys
  17. 4.2 Data Persistence

  18. Mock Data Schema (โครงสร้างข้อมูลทดสอบ)

  19. 5.1 JSON Files Overview
  20. 5.2 Sample Data

1. Data Models (โครงสร้างข้อมูล)

1.1 Patient Model

Purpose: เก็บข้อมูลผู้ป่วย (ใช้ร่วมกับระบบทะเบียน)

File Location: assets/js/models/Patient.js

Class Definition:

class Patient {
  constructor(data) {
    this.hn = data.hn;                    // Hospital Number (HN000001)
    this.title = data.title;              // 'mr', 'mrs', 'ms', 'master', 'miss'
    this.firstName = data.firstName;      // Thai first name
    this.lastName = data.lastName;        // Thai last name
    this.dateOfBirth = data.dateOfBirth;  // YYYY-MM-DD
    this.age = data.age;                  // Calculated or provided
    this.gender = data.gender;            // 'male', 'female'
    this.bloodGroup = data.bloodGroup;    // 'A+', 'B+', 'O+', 'AB+', etc.
    this.allergies = data.allergies || [];           // Array of allergy strings
    this.chronicDiseases = data.chronicDiseases || []; // Array of disease strings
    this.insuranceType = data.insuranceType;         // 'uc', 'social', 'government', etc.
    this.status = data.status || 'active';           // 'active', 'inactive'
  }

  // Methods
  getFullName() { return `${this.firstName} ${this.lastName}`; }
  getTitleThai() { /* Returns Thai title */ }
  calculateAge() { /* Calculate age from dateOfBirth */ }
  toJSON() { /* Serialize to JSON */ }
}

Properties:

Property Type Required Description Example
hn String Hospital Number (Unique) "HN000123"
title String Title code "mr", "mrs"
firstName String Thai first name "สมชาย"
lastName String Thai last name "ใจดี"
dateOfBirth String Date (ISO format) "1995-05-15"
age Number Age in years 30
gender String Gender "male", "female"
bloodGroup String Blood type "O+", "A+"
allergies Array List of allergies ["Penicillin"]
chronicDiseases Array List of chronic diseases ["Diabetes Type 2"]
insuranceType String Insurance/payment type "uc", "social"
status String Patient status "active"

Usage in LAB Module: - Search patient by HN before creating order - Display patient info in Order/Result pages - Check allergies/chronic diseases for safety - Used by PatientService (shared service)

Sample JSON:

{
  "hn": "HN000123",
  "title": "mr",
  "firstName": "สมชาย",
  "lastName": "ใจดี",
  "dateOfBirth": "1995-05-15",
  "age": 30,
  "gender": "male",
  "bloodGroup": "O+",
  "allergies": ["Penicillin", "Aspirin"],
  "chronicDiseases": ["Diabetes Type 2"],
  "insuranceType": "uc",
  "status": "active"
}


1.2 LabOrder Model

Purpose: เก็บข้อมูลคำสั่งตรวจแล็บ (Lab Request)

File Location: assets/js/models/LabOrder.js

Class Definition:

class LabOrder {
  constructor(data) {
    this.id = data.id;                      // Auto-increment ID
    this.orderNumber = data.orderNumber;    // LAB-YYYYMMDD-XXXX
    this.hn = data.hn;                      // Patient HN
    this.patientName = data.patientName;    // Patient full name (cached)
    this.vn = data.vn;                      // Visit Number (optional)
    this.orderDate = data.orderDate;        // ISO timestamp
    this.orderType = data.orderType;        // 'OPD', 'IPD', 'ER'
    this.priority = data.priority;          // 'routine', 'urgent', 'stat'
    this.doctorId = data.doctorId;          // Doctor ID
    this.doctorName = data.doctorName;      // Doctor name (cached)
    this.department = data.department;      // Department code
    this.clinicCode = data.clinicCode;      // Clinic code (OPD)
    this.wardCode = data.wardCode;          // Ward code (IPD)
    this.items = data.items || [];          // Array of LabOrderItem
    this.status = data.status || 'pending'; // Order status
    this.totalPrice = data.totalPrice || 0; // Total price (THB)
    this.notes = data.notes || '';          // Clinical notes
    this.createdBy = data.createdBy;        // User ID
    this.createdByName = data.createdByName; // User name
    this.createdAt = data.createdAt;        // ISO timestamp
    this.confirmedBy = data.confirmedBy;    // User ID (Order Queue)
    this.confirmedByName = data.confirmedByName; // User name
    this.confirmedAt = data.confirmedAt;    // ISO timestamp
  }

  // Methods
  addItem(item) { /* Add lab item to order */ }
  removeItem(itemId) { /* Remove lab item */ }
  calculateTotalPrice() { /* Sum all item prices */ }
  isSTAT() { return this.priority === 'stat'; }
  toJSON() { /* Serialize to JSON */ }
}

// LabOrderItem (Nested object in items array)
{
  itemId: "CBC001",              // Lab item ID
  itemCode: "CBC",               // Lab item code
  itemName: "Complete Blood Count", // Lab item name
  itemNameTh: "ตรวจนับเม็ดเลือด", // Thai name
  price: 350,                    // Price (THB)
  isOutsourced: false,           // Send to external lab?
  specimenType: "Blood",         // Specimen type required
  containerType: "EDTA Tube"     // Container required
}

Properties:

Property Type Required Description Example
id Number Auto-increment ID 1
orderNumber String Order number (Unique) "LAB-20241226-0001"
hn String Patient HN "HN000123"
patientName String Patient full name (cached) "นายสมชาย ใจดี"
vn String Visit Number "VN20241226001"
orderDate String Order date (ISO) "2024-12-26T10:30:00"
orderType String Order type "OPD", "IPD", "ER"
priority String Priority level "routine", "stat"
doctorId String Doctor ID "DOC001"
doctorName String Doctor name (cached) "นพ.สมชาย"
department String Department code "MED", "SURG"
clinicCode String Clinic code (OPD) "CLI001"
wardCode String Ward code (IPD) "WARD01"
items Array Array of LabOrderItem [{itemId: "CBC001"}]
status String Order status "pending", "confirmed"
totalPrice Number Total price (THB) 1450
notes String Clinical notes "ผู้ป่วยเบาหวาน"
createdBy String Creator user ID "DOC001"
createdByName String Creator name "นพ.สมชาย"
createdAt String Created timestamp "2024-12-26T10:30:00"
confirmedBy String Confirmer user ID (Lab Staff) "LAB001"
confirmedByName String Confirmer name "เจ้าหน้าที่แล็บ"
confirmedAt String Confirmed timestamp "2024-12-26T11:00:00"

Order Status Values (See Section 2.1 for flow): - pending: รอ Lab ยืนยัน - confirmed: Lab ยืนยันรับรายการแล้ว - specimen_pending: รอเก็บ Specimen - specimen_collected: เก็บ Specimen แล้ว - sent_to_lis: ส่ง LIS แล้ว (พร้อมลงผล) - in_progress: กำลังลงผล - partial_result: ลงผลบางส่วน - pending_approval: รอหัวหน้าแล็บอนุมัติ - approved: อนุมัติผลแล้ว - completed: เสร็จสมบูรณ์ - cancelled: ยกเลิก - rejected_specimen: ปฏิเสธ Specimen (ต้องเก็บใหม่)

Sample JSON:

{
  "id": 1,
  "orderNumber": "LAB-20241226-0001",
  "hn": "HN000123",
  "patientName": "นายสมชาย ใจดี",
  "vn": "VN20241226001",
  "orderDate": "2024-12-26T10:30:00.000Z",
  "orderType": "OPD",
  "priority": "stat",
  "doctorId": "DOC001",
  "doctorName": "นพ.สมชาย วรรณคดี",
  "department": "MED",
  "clinicCode": "CLI001",
  "items": [
    {
      "itemId": "CBC001",
      "itemCode": "CBC",
      "itemName": "Complete Blood Count",
      "itemNameTh": "ตรวจนับเม็ดเลือด",
      "price": 350,
      "isOutsourced": false,
      "specimenType": "Blood",
      "containerType": "EDTA Tube"
    }
  ],
  "status": "confirmed",
  "totalPrice": 350,
  "notes": "ผู้ป่วยเบาหวาน ตรวจติดตามค่าน้ำตาล",
  "createdBy": "DOC001",
  "createdByName": "นพ.สมชาย วรรณคดี",
  "createdAt": "2024-12-26T10:30:00.000Z",
  "confirmedBy": "LAB001",
  "confirmedByName": "เจ้าหน้าที่แล็บสมหญิง",
  "confirmedAt": "2024-12-26T11:00:00.000Z"
}


1.3 LabItem Model

Purpose: เก็บข้อมูลรายการตรวจแล็บ (Lab Test Catalog)

File Location: assets/js/models/LabItem.js

Data Source: data/lab-items.json (Master Data, Read-only)

Class Definition:

class LabItem {
  constructor(data) {
    this.id = data.id;                      // Lab item ID
    this.code = data.code;                  // Lab item code (short)
    this.name = data.name;                  // English name
    this.nameTh = data.nameTh;              // Thai name
    this.category = data.category;          // Category code
    this.categoryName = data.categoryName;  // Category name (Thai)
    this.specimenType = data.specimenType;  // Required specimen type
    this.containerType = data.containerType; // Required container
    this.price = data.price;                // Price (THB)
    this.normalTime = data.normalTime;      // TAT (minutes)
    this.urgentTime = data.urgentTime;      // TAT for urgent (minutes)
    this.statTime = data.statTime;          // TAT for STAT (minutes)
    this.isOutsourced = data.isOutsourced || false; // Send to external lab?
    this.resultType = data.resultType;      // 'numeric', 'text', 'file'
    this.resultUnit = data.resultUnit;      // Unit (e.g., "g/dL")
    this.referenceLow = data.referenceLow;  // Reference range low
    this.referenceHigh = data.referenceHigh; // Reference range high
    this.criticalLow = data.criticalLow;    // Critical value low
    this.criticalHigh = data.criticalHigh;  // Critical value high
    this.deltaCriticalPercent = data.deltaCriticalPercent; // Delta check threshold
    this.status = data.status || 'active';  // 'active', 'inactive'
  }

  // Methods
  isActive() { return this.status === 'active'; }
  isNumeric() { return this.resultType === 'numeric'; }
  isText() { return this.resultType === 'text'; }
  hasReferenceRange() { return this.referenceLow != null && this.referenceHigh != null; }
  toJSON() { /* Serialize to JSON */ }
}

Properties:

Property Type Required Description Example
id String Lab item ID (Unique) "CBC001"
code String Short code "CBC"
name String English name "Complete Blood Count"
nameTh String Thai name "ตรวจนับเม็ดเลือด"
category String Category code "HEM"
categoryName String Category name (Thai) "โลหิตวิทยา"
specimenType String Required specimen "Blood"
containerType String Required container "EDTA Tube"
price Number Price (THB) 350
normalTime Number TAT normal (minutes) 120
urgentTime Number TAT urgent (minutes) 60
statTime Number TAT STAT (minutes) 30
isOutsourced Boolean Send to external lab? false
resultType String Result data type "numeric", "text"
resultUnit String Unit (numeric only) "g/dL", "x10³/µL"
referenceLow Number Reference range low 4.5
referenceHigh Number Reference range high 11.0
criticalLow Number Critical value low 2.0
criticalHigh Number Critical value high 30.0
deltaCriticalPercent Number Delta check threshold (%) 50
status String Item status "active"

Categories: - HEM: โลหิตวิทยา (Hematology) - CHEM: เคมีคลินิก (Clinical Chemistry) - MICRO: จุลชีววิทยา (Microbiology) - IMMUNO: ภูมิคุ้มกันวิทยา (Immunology) - SERO: เซรุ่มวิทยา (Serology) - COAG: การแข็งตัวของเลือด (Coagulation)

Sample JSON:

{
  "id": "CBC001",
  "code": "CBC",
  "name": "Complete Blood Count",
  "nameTh": "ตรวจนับเม็ดเลือด",
  "category": "HEM",
  "categoryName": "โลหิตวิทยา",
  "specimenType": "Blood",
  "containerType": "EDTA Tube",
  "price": 350,
  "normalTime": 120,
  "urgentTime": 60,
  "statTime": 30,
  "isOutsourced": false,
  "resultType": "numeric",
  "resultUnit": "x10³/µL",
  "referenceLow": 4.5,
  "referenceHigh": 11.0,
  "criticalLow": 2.0,
  "criticalHigh": 30.0,
  "deltaCriticalPercent": 50,
  "status": "active"
}


1.4 LabResult Model

Purpose: เก็บข้อมูลผลการตรวจแล็บ

File Location: assets/js/models/LabResult.js

Class Definition:

class LabResult {
  constructor(data) {
    this.id = data.id;                      // Auto-increment ID
    this.orderNumber = data.orderNumber;    // Related order number
    this.hn = data.hn;                      // Patient HN
    this.labItemId = data.labItemId;        // Lab item ID
    this.labItemCode = data.labItemCode;    // Lab item code (cached)
    this.labItemName = data.labItemName;    // Lab item name (cached)
    this.resultValue = data.resultValue;    // Result value (numeric/text)
    this.resultUnit = data.resultUnit;      // Unit (cached from LabItem)
    this.resultType = data.resultType;      // 'numeric', 'text', 'file'
    this.flag = data.flag;                  // 'normal', 'high', 'low', 'critical_high', 'critical_low'
    this.referenceLow = data.referenceLow;  // Reference range low (cached)
    this.referenceHigh = data.referenceHigh; // Reference range high (cached)
    this.criticalLow = data.criticalLow;    // Critical value low (cached)
    this.criticalHigh = data.criticalHigh;  // Critical value high (cached)
    this.note = data.note || '';            // Lab note
    this.attachments = data.attachments || []; // File attachments (outlab)
    this.status = data.status || 'pending'; // Result status
    this.enteredBy = data.enteredBy;        // User ID (Lab Technician)
    this.enteredByName = data.enteredByName; // User name
    this.enteredAt = data.enteredAt;        // Entered timestamp
    this.approvedBy = data.approvedBy;      // User ID (Lab Supervisor)
    this.approvedByName = data.approvedByName; // User name
    this.approvedAt = data.approvedAt;      // Approved timestamp
    this.rejectedBy = data.rejectedBy;      // User ID (if rejected)
    this.rejectedByName = data.rejectedByName; // User name
    this.rejectedAt = data.rejectedAt;      // Rejected timestamp
    this.rejectionReason = data.rejectionReason; // Reason text
    this.instrument = data.instrument;      // Instrument used
    this.reportedBy = data.reportedBy;      // Lab technician name
  }

  // Methods
  calculateFlag() { /* Calculate flag based on value and reference range */ }
  isCritical() { 
    return this.flag === 'critical_high' || this.flag === 'critical_low'; 
  }
  isAbnormal() {
    return this.flag === 'high' || this.flag === 'low' || this.isCritical();
  }
  getResultWithUnit() { 
    return `${this.resultValue} ${this.resultUnit || ''}`.trim(); 
  }
  getFlagDisplay() { /* Return Thai flag text */ }
  getReferenceRangeText() {
    return `${this.referenceLow} - ${this.referenceHigh}`;
  }
  toJSON() { /* Serialize to JSON */ }
}

Properties:

Property Type Required Description Example
id Number Auto-increment ID 101
orderNumber String Related order number "LAB-20241226-0001"
hn String Patient HN "HN000123"
labItemId String Lab item ID "CBC001"
labItemCode String Lab item code (cached) "CBC"
labItemName String Lab item name (cached) "Complete Blood Count"
resultValue String Result value "8.5", "Negative"
resultUnit String Unit (cached) "x10³/µL"
resultType String Result type "numeric", "text"
flag String Interpretation flag "normal", "critical_low"
referenceLow Number Reference low (cached) 4.5
referenceHigh Number Reference high (cached) 11.0
criticalLow Number Critical low (cached) 2.0
criticalHigh Number Critical high (cached) 30.0
note String Lab note/comment "ตรวจซ้ำ"
attachments Array File attachments (outlab) [{fileName: "..."}]
status String Result status "pending", "final"
enteredBy String Entered by user ID "LAB_TECH001"
enteredByName String Entered by name "เทคนิคแล็บสมหญิง"
enteredAt String Entered timestamp "2024-12-26T14:30:00"
approvedBy String Approved by user ID "LAB_CHIEF001"
approvedByName String Approved by name "หัวหน้าห้องแล็บ"
approvedAt String Approved timestamp "2024-12-26T15:00:00"
rejectedBy String Rejected by user ID "LAB_CHIEF001"
rejectedByName String Rejected by name "หัวหน้าห้องแล็บ"
rejectedAt String Rejected timestamp "2024-12-26T15:00:00"
rejectionReason String Rejection reason "ผลผิดปกติ..."
instrument String Instrument used "Sysmex XN-1000"
reportedBy String Lab technician name "เทคนิคแล็บสมหญิง"

Result Status Values (See Section 2.2 for flow): - pending: รอลงผล - preliminary: ลงผลแล้ว (รอ Approve) - final: อนุมัติผลแล้ว - rejected: ปฏิเสธผล (ต้องลงผลใหม่)

Flag Values: - normal: ปกติ (Green) - high: สูงกว่าปกติ (Yellow) - low: ต่ำกว่าปกติ (Yellow) - critical_high: สูงกว่าค่าวิกฤต (Red) - critical_low: ต่ำกว่าค่าวิกฤต (Red)

Sample JSON:

{
  "id": 101,
  "orderNumber": "LAB-20241226-0001",
  "hn": "HN000123",
  "labItemId": "CBC001",
  "labItemCode": "WBC",
  "labItemName": "White Blood Cell",
  "resultValue": "8.5",
  "resultUnit": "x10³/µL",
  "resultType": "numeric",
  "flag": "normal",
  "referenceLow": 4.5,
  "referenceHigh": 11.0,
  "criticalLow": 2.0,
  "criticalHigh": 30.0,
  "note": "",
  "attachments": [],
  "status": "final",
  "enteredBy": "LAB_TECH001",
  "enteredByName": "เทคนิคแล็บสมหญิง",
  "enteredAt": "2024-12-26T14:30:00.000Z",
  "approvedBy": "LAB_CHIEF001",
  "approvedByName": "หัวหน้าห้องแล็บ",
  "approvedAt": "2024-12-26T15:00:00.000Z",
  "instrument": "Sysmex XN-1000",
  "reportedBy": "เทคนิคแล็บสมหญิง"
}


1.5 Specimen Model

Purpose: เก็บข้อมูลสิ่งส่งตรวจ (Specimen)

File Location: assets/js/models/Specimen.js

Class Definition:

class Specimen {
  constructor(data) {
    this.id = data.id;                      // Auto-increment ID
    this.specimenNumber = data.specimenNumber; // SPEC-YYYYMMDD-XXXX
    this.orderNumber = data.orderNumber;    // Related order number
    this.hn = data.hn;                      // Patient HN
    this.labItemIds = data.labItemIds || []; // Array of lab item IDs (1 specimen → N items)
    this.specimenType = data.specimenType;  // "Blood", "Urine", "Stool", etc.
    this.containerType = data.containerType; // "EDTA Tube", "Plain Tube", etc.
    this.collectedBy = data.collectedBy;    // User ID (Lab Staff)
    this.collectedByName = data.collectedByName; // User name
    this.collectedAt = data.collectedAt;    // Collection timestamp
    this.quality = data.quality;            // 'good', 'acceptable', 'poor', 'rejected'
    this.qualityIssues = data.qualityIssues || []; // Array of issue codes
    this.qualityNote = data.qualityNote || ''; // Additional quality note
    this.status = data.status || 'pending'; // Specimen status
    this.recollectionCount = data.recollectionCount || 0; // Recollection counter
    this.parentSpecimenNumber = data.parentSpecimenNumber; // Original specimen (if recollected)
  }

  // Methods
  isRejected() { return this.quality === 'rejected'; }
  isAcceptable() { return this.quality === 'good' || this.quality === 'acceptable'; }
  needsRecollection() { return this.quality === 'rejected'; }
  getSpecimenLabel() { /* Generate label text for printing */ }
  toJSON() { /* Serialize to JSON */ }
}

Properties:

Property Type Required Description Example
id Number Auto-increment ID 1
specimenNumber String Specimen number (Unique) "SPEC-20241226-0001"
orderNumber String Related order number "LAB-20241226-0001"
hn String Patient HN "HN000123"
labItemIds Array Lab items in this specimen ["CBC001", "CBC002"]
specimenType String Specimen type "Blood", "Urine"
containerType String Container type "EDTA Tube"
collectedBy String Collector user ID "LAB001"
collectedByName String Collector name "เจ้าหน้าที่แล็บ"
collectedAt String Collection timestamp "2024-12-26T11:30:00"
quality String Quality assessment "good", "rejected"
qualityIssues Array Quality issue codes ["hemolysis"]
qualityNote String Additional note "Mild hemolysis"
status String Specimen status "collected", "sent"
recollectionCount Number Number of recollections 1 (R1 suffix)
parentSpecimenNumber String Original specimen number "SPEC-20241226-0001"

Specimen Status Values (See Section 2.3 for flow): - pending: รอเก็บ - collected: เก็บแล้ว - sent_to_lis: ส่ง LIS แล้ว - rejected: ปฏิเสธ (ต้องเก็บใหม่) - disposed: ทิ้งแล้ว

Quality Values: - good: คุณภาพดี (เขียว) - acceptable: ยอมรับได้ (เหลือง) - poor: คุณภาพไม่ดี (ส้ม) - ยังสามารถใช้ได้ - rejected: ปฏิเสธ (แดง) - ต้องเก็บใหม่

Quality Issue Codes: - hemolysis: Hemolysis (เม็ดเลือดแตก) - clotted: Clotted (เลือดแข็งตัว) - insufficient: Insufficient Volume (ปริมาณไม่พอ) - unlabeled: Unlabeled/Mislabeled (ไม่มี/ติด Label ผิด) - contaminated: Contamination (ปนเปื้อน) - wrong_container: Wrong Container (ใส่ภาชนะผิด) - lipemia: Lipemia (ไขมันในเลือดสูง)

Sample JSON:

{
  "id": 1,
  "specimenNumber": "SPEC-20241226-0001",
  "orderNumber": "LAB-20241226-0001",
  "hn": "HN000123",
  "labItemIds": ["CBC001", "CBC002", "CBC003"],
  "specimenType": "Blood",
  "containerType": "EDTA Tube",
  "collectedBy": "LAB001",
  "collectedByName": "เจ้าหน้าที่แล็บสมหญิง",
  "collectedAt": "2024-12-26T11:30:00.000Z",
  "quality": "good",
  "qualityIssues": [],
  "qualityNote": "",
  "status": "sent_to_lis",
  "recollectionCount": 0,
  "parentSpecimenNumber": null
}

Recollection Example:

{
  "id": 2,
  "specimenNumber": "SPEC-20241226-0001-R1",
  "orderNumber": "LAB-20241226-0001",
  "hn": "HN000123",
  "labItemIds": ["CBC001"],
  "specimenType": "Blood",
  "containerType": "EDTA Tube",
  "collectedBy": "LAB001",
  "collectedByName": "เจ้าหน้าที่แล็บสมหญิง",
  "collectedAt": "2024-12-26T13:00:00.000Z",
  "quality": "good",
  "qualityIssues": [],
  "qualityNote": "เก็บใหม่ เนื่องจาก Specimen แรก Hemolysis",
  "status": "sent_to_lis",
  "recollectionCount": 1,
  "parentSpecimenNumber": "SPEC-20241226-0001"
}


1.6 FileAttachment Model

Purpose: เก็บไฟล์แนบผลแล็บนอก (Outlab Results)

File Location: Managed by FileAttachmentService.js

Data Structure:

{
  id: "ATT001",                          // Attachment ID
  orderNumber: "LAB-20241226-0001",      // Related order number
  labItemId: "HIV001",                   // Related lab item ID
  fileName: "HIV_Result_26122567.pdf",   // Original file name
  fileType: "application/pdf",           // MIME type
  fileSize: 1228800,                     // File size (bytes)
  fileData: "data:application/pdf;base64,...", // Base64-encoded file data
  labName: "ห้องแล็บศิริราช",            // Lab name (external)
  note: "HIV Viral Load: < 50 copies/mL", // Result note
  uploadedBy: "LAB_TECH001",             // Uploader user ID
  uploadedByName: "เทคนิคแล็บสมหญิง",    // Uploader name
  uploadedAt: "2024-12-26T14:30:00.000Z" // Upload timestamp
}

Allowed File Types: - application/pdf - PDF documents - image/jpeg, image/jpg - JPEG images - image/png - PNG images

Size Limit: 5 MB per file

Storage: LocalStorage (Base64-encoded)


Summary Section 1

เสร็จแล้ว - Section 1: Data Models (6 Models)

เอกสารนี้ครอบคลุม: 1. ✅ Patient Model - ข้อมูลผู้ป่วย 2. ✅ LabOrder Model - คำสั่งตรวจแล็บ 3. ✅ LabItem Model - รายการตรวจแล็บ (Master Data) 4. ✅ LabResult Model - ผลการตรวจแล็บ 5. ✅ Specimen Model - สิ่งส่งตรวจ 6. ✅ FileAttachment Model - ไฟล์แนบ (Outlab)

ถัดไป - Section 2: Status Transitions (3 Flows)


2. Status Transitions (การเปลี่ยนแปลงสถานะ)

2.1 Order Status Flow

Purpose: แสดงวงจรชีวิตของคำสั่งตรวจแล็บ (LabOrder) ตั้งแต่สร้างจนเสร็จสมบูรณ์

State Diagram

stateDiagram-v2
    [*] --> pending: แพทย์สั่งตรวจ

    pending --> confirmed: Lab ยืนยันรับรายการ
    pending --> cancelled: แพทย์ยกเลิก

    confirmed --> specimen_pending: เตรียมเก็บ Specimen

    specimen_pending --> specimen_collected: เก็บ Specimen (Good/Acceptable)
    specimen_pending --> rejected_specimen: ปฏิเสธ Specimen (Quality Poor)

    rejected_specimen --> specimen_pending: เก็บ Specimen ใหม่

    specimen_collected --> sent_to_lis: ส่ง Specimen ไป LIS

    sent_to_lis --> in_progress: เริ่มลงผล

    in_progress --> partial_result: บันทึกผลบางส่วน
    in_progress --> pending_approval: ลงผลครบทั้งหมด

    partial_result --> in_progress: ลงผลต่อ
    partial_result --> pending_approval: ลงผลครบทั้งหมด

    pending_approval --> approved: อนุมัติผล (All)
    pending_approval --> rejected_result: ปฏิเสธผล (Some/All)

    rejected_result --> in_progress: แก้ไขผล
    rejected_result --> rejected_specimen: ต้องเก็บ Specimen ใหม่

    approved --> completed: ส่งรายงาน

    cancelled --> [*]
    completed --> [*]

    note right of pending
        แพทย์สั่งตรวจผ่าน
        doctor-order.html
    end note

    note right of confirmed
        Lab Staff ยืนยันผ่าน
        order-queue.html
    end note

    note right of specimen_collected
        เก็บ Specimen ผ่าน
        specimen-collection.html
        + ตรวจสอบ Quality
    end note

    note right of in_progress
        ลงผลผ่าน
        result-entry.html
    end note

    note right of approved
        อนุมัติผลผ่าน
        result-approval.html
    end note

Status Table

Status Thai Name Color Icon Description
pending รอ Lab ยืนยัน 🟡 Order ใหม่ รอ Lab Staff ยืนยันรับรายการ
confirmed Lab ยืนยันแล้ว 🔵 Lab ยืนยันรับรายการแล้ว เตรียมเก็บ Specimen
specimen_pending รอเก็บ Specimen 🟠 🔬 รอเจ้าหน้าที่เก็บ Specimen
specimen_collected เก็บ Specimen แล้ว 🔵 เก็บ Specimen แล้ว คุณภาพดี/ยอมรับได้
sent_to_lis ส่ง LIS แล้ว 🟣 📤 ส่ง Specimen ไป LIS แล้ว พร้อมลงผล
in_progress กำลังลงผล 🔵 ⚙️ Lab Technician กำลังลงผลการตรวจ
partial_result ลงผลบางส่วน 🟠 📝 ลงผลบางรายการแล้ว ยังมีรายการค้างอยู่
pending_approval รออนุมัติผล 🟡 ลงผลครบแล้ว รอหัวหน้าแล็บอนุมัติ
approved อนุมัติผลแล้ว 🟢 หัวหน้าแล็บอนุมัติผลแล้ว
completed เสร็จสมบูรณ์ 🟢 ✓✓ ส่งรายงานให้แพทย์แล้ว เสร็จสิ้น
cancelled ยกเลิก แพทย์ยกเลิกคำสั่งตรวจ
rejected_specimen ปฏิเสธ Specimen 🔴 ⚠️ Specimen คุณภาพไม่ดี ต้องเก็บใหม่
rejected_result ปฏิเสธผล 🔴 ⚠️ ผลการตรวจไม่ถูกต้อง ต้องลงผลใหม่

Transition Rules

1. pending → confirmed - Trigger: Lab Staff click [ยืนยันรับรายการ] ใน order-queue.html - Action: Update confirmedBy, confirmedByName, confirmedAt - Next: Auto-transition to specimen_pending

2. confirmed → specimen_pending - Trigger: Auto after confirmation - Action: None (waiting for specimen collection)

3. specimen_pending → specimen_collected - Trigger: Lab Staff เก็บ Specimen + Quality = Good/Acceptable - Action: Create Specimen record with quality assessment - Validation: Must have at least 1 specimen with acceptable quality

4. specimen_pending → rejected_specimen - Trigger: Lab Staff ตรวจสอบ Specimen + Quality = Rejected - Action: Create Specimen record with quality = 'rejected', qualityIssues - Next: Return to specimen_pending for recollection

5. rejected_specimen → specimen_pending - Trigger: Lab Staff เตรียมเก็บ Specimen ใหม่ - Action: Update order status, create new Specimen record with R1/R2 suffix

6. specimen_collected → sent_to_lis - Trigger: Lab Staff click [ส่ง LIS] ใน specimen-collection.html - Action: Update Specimen.status = 'sent_to_lis', Order.status = 'sent_to_lis'

7. sent_to_lis → in_progress - Trigger: Lab Technician เริ่มลงผลรายการแรก ใน result-entry.html - Action: Create LabResult records with status = 'preliminary'

8. in_progress → partial_result - Trigger: Lab Technician click [บันทึกผลบางส่วน] - Action: Save filled results, leave empty results as pending - Condition: At least 1 result entered, at least 1 result still empty

9. partial_result → in_progress - Trigger: Lab Technician กลับมาลงผลต่อ - Action: None (status updated when saving more results)

10. in_progress/partial_result → pending_approval - Trigger: Lab Technician click [บันทึกทั้งหมด] + All results entered - Action: Update all LabResult.status = 'preliminary', Order.status = 'pending_approval' - Condition: All lab items must have results (no empty values)

11. pending_approval → approved - Trigger: Lab Supervisor click [อนุมัติทั้งหมด] or [อนุมัติที่เลือก] ใน result-approval.html - Action: Update LabResult.status = 'final', approvedBy, approvedByName, approvedAt - Next: When all results approved → Order.status = 'approved'

12. pending_approval → rejected_result - Trigger: Lab Supervisor click [ปฏิเสธที่เลือก] + reason - Action: Update LabResult.status = 'rejected', rejectionReason, Order.status = 'rejected_result' - Options: - If "Require New Specimen" checked → Order.status = 'rejected_specimen' - Else → Order.status = 'rejected_result' (back to result entry)

13. rejected_result → in_progress - Trigger: Lab Technician เปิดหน้า result-entry.html + แก้ไขผล - Action: Update LabResult.status = 'preliminary' (reset)

14. rejected_result → rejected_specimen - Trigger: Lab Supervisor เลือก "ต้องการเก็บ Specimen ใหม่" ใน reject modal - Action: Order.status = 'rejected_specimen', return to specimen collection workflow

15. approved → completed - Trigger: System auto-transition when all results approved - Action: Generate final report, send notification (mock) - Next: Terminal state (end of lifecycle)

16. pending → cancelled - Trigger: Doctor cancels order (before Lab confirmation) - Action: Update Order.status = 'cancelled', cancelledBy, cancelledAt, cancelReason - Next: Terminal state

Business Logic

Validation Rules:

  1. Cannot skip states: ต้องเดินตาม Flow ตามลำดับ (ไม่สามารถข้ามขั้นตอนได้)
  2. Specimen Quality Check: ต้องมี Specimen คุณภาพ Good/Acceptable อย่างน้อย 1 รายการก่อนส่ง LIS
  3. All Results Required: ต้องลงผลครบทุกรายการก่อนส่ง Approval
  4. Partial Approval Allowed: สามารถอนุมัติผลบางรายการได้ (ไม่จำเป็นต้องทั้งหมด)
  5. Critical Alert: ถ้ามี Critical Result ต้องแสดง Alert และ Confirm ก่อน Approve
  6. Recollection Limit: แนะนำไม่เกิน 3 ครั้ง (R3) - เกินนี้ควรยกเลิก Order
  7. Cancellation Window: สามารถยกเลิกได้เฉพาะสถานะ pending (ก่อน Lab ยืนยัน)

Performance Metrics:

  • TAT (Turnaround Time): นับจาก orderDatecompletedAt
  • STAT: ≤ 60 minutes
  • Urgent: ≤ 120 minutes
  • Routine: ≤ 240 minutes (4 hours)

  • Specimen Collection Time: นับจาก confirmedAtspecimen_collected

  • Target: ≤ 30 minutes

  • Result Entry Time: นับจาก sent_to_lispending_approval

  • Depends on test type (lab-items.json: normalTime/urgentTime/statTime)

  • Approval Time: นับจาก pending_approvalapproved

  • Target: ≤ 15 minutes

2.2 Result Status Flow

Purpose: แสดงวงจรชีวิตของผลการตรวจแล็บ (LabResult) ตั้งแต่สร้างจนอนุมัติ

State Diagram

stateDiagram-v2
    [*] --> pending: สร้าง Result record

    pending --> preliminary: Lab Technician ลงผล

    preliminary --> final: Lab Supervisor อนุมัติ
    preliminary --> rejected: Lab Supervisor ปฏิเสธ

    rejected --> preliminary: Lab Technician แก้ไขผล

    final --> [*]: เสร็จสิ้น

    note right of pending
        สร้างตอน Order
        status = sent_to_lis
    end note

    note right of preliminary
        ลงผลผ่าน
        result-entry.html
        - Enter value
        - Calculate flag
        - Check critical
    end note

    note right of final
        อนุมัติผ่าน
        result-approval.html
        - Final report
    end note

    note right of rejected
        ปฏิเสธเพราะ:
        - ผลผิดปกติ
        - Specimen quality
        - QC ไม่ผ่าน
    end note

Status Table

Status Thai Name Color Icon Description
pending รอลงผล 🟡 Result record สร้างแล้ว รอ Lab Technician ลงผล
preliminary ลงผลแล้ว (รอ Approve) 🟠 📝 ลงผลแล้ว รอหัวหน้าแล็บตรวจสอบและอนุมัติ
final อนุมัติแล้ว 🟢 หัวหน้าแล็บอนุมัติแล้ว สามารถออกรายงานได้
rejected ปฏิเสธ 🔴 หัวหน้าแล็บปฏิเสธ ต้องลงผลใหม่หรือเก็บ Specimen ใหม่

Transition Rules

1. pending → preliminary - Trigger: Lab Technician กรอกค่าผล + click [บันทึก] - Action: - Update resultValue, resultUnit - Calculate flag (normal/high/low/critical_high/critical_low) - Update enteredBy, enteredByName, enteredAt - Save instrument, reportedBy, note (optional) - Validation: - Numeric result: Must be valid number - Text result: Must not be empty - Outlab result: Must have file attachment - Special Cases: - Critical Result: Show Critical Alert Modal with 30-min countdown - Delta Check: Compare with previous result (if available) - Outlab Result: Store file attachment in FileAttachmentService

2. preliminary → final - Trigger: Lab Supervisor click [อนุมัติ] - Action: - Update status = 'final' - Update approvedBy, approvedByName, approvedAt - Check if all results in order are final → Update Order.status = 'approved' - Validation: - Result must have resultValue (not empty) - Critical results must be double-checked - Notifications (Mock): - Critical: Send notification to doctor immediately - Normal: Include in daily report

3. preliminary → rejected - Trigger: Lab Supervisor click [ปฏิเสธ] + reason - Action: - Update status = 'rejected' - Update rejectedBy, rejectedByName, rejectedAt - Store rejectionReason, rejectionReasonCode - Update Order.status = 'rejected_result' (if any result rejected) - Options: - Require New Specimen: Set Order.status = 'rejected_specimen' - Fix Result Only: Keep Order.status = 'rejected_result'

4. rejected → preliminary - Trigger: Lab Technician re-enter result - Action: - Update resultValue (new value) - Recalculate flag - Update status = 'preliminary' (reset) - Clear rejectedBy, rejectionReason (optional: keep for audit) - Update enteredAt (new timestamp) - Note: Keep rejection history for audit trail

Flag Calculation Logic

function calculateFlag(resultValue, referenceLow, referenceHigh, criticalLow, criticalHigh) {
  const value = parseFloat(resultValue);

  if (isNaN(value)) return null; // Text result, no flag

  // Critical checks (highest priority)
  if (criticalLow != null && value <= criticalLow) {
    return 'critical_low';
  }
  if (criticalHigh != null && value >= criticalHigh) {
    return 'critical_high';
  }

  // Abnormal checks
  if (referenceLow != null && value < referenceLow) {
    return 'low';
  }
  if (referenceHigh != null && value > referenceHigh) {
    return 'high';
  }

  // Normal
  return 'normal';
}

Critical Result Handling

When Critical Result Detected:

  1. Alert Display: Show Critical Alert Modal
  2. List all critical results
  3. Show countdown timer (30 minutes)
  4. Require double-check confirmation checkbox

  5. Countdown Timer: 30-minute countdown

  6. Display in MM:SS format
  7. Change color to red when < 5 minutes
  8. Pulse animation when time is critical

  9. Double-Check Required:

  10. Cannot save without checking "ได้ตรวจสอบผลซ้ำแล้ว" checkbox
  11. Ensure Lab Technician verifies critical value

  12. Notification (Mock):

  13. Success message: "บันทึกผลวิกฤตสำเร็จ X รายการ ระบบได้แจ้งแพทย์แล้ว"
  14. In real system: Send SMS/Email/In-app notification to doctor

  15. No Bypass: Cannot skip critical alert (forced workflow)

Partial Result Support

Scenario: ลงผลบางรายการ ยังมีรายการค้างอยู่

Behavior: - Results with values → status = 'preliminary' - Results without values → status = 'pending' - Order status → 'partial_result'

UI Display: - Show progress: "ลงผลแล้ว: 3/5 รายการ (60%)" - Summary box: "✅ ลงผลแล้ว: 3 | ⏳ รอลงผล: 2 | 📊 ทั้งหมด: 5"

Business Rules: - Can save partial results (at least 1 result entered) - Can come back later to complete remaining results - Cannot approve until all results entered


2.3 Specimen Status Flow

Purpose: แสดงวงจรชีวิตของสิ่งส่งตรวจ (Specimen) ตั้งแต่เก็บจนทิ้ง

State Diagram

stateDiagram-v2
    [*] --> pending: Order confirmed

    pending --> collected: เก็บ Specimen (Good/Acceptable)
    pending --> rejected: เก็บ Specimen (Poor/Rejected)

    rejected --> pending: เตรียมเก็บใหม่ (Recollection)

    collected --> sent_to_lis: ส่ง Specimen ไป LIS

    sent_to_lis --> in_analysis: เริ่มตรวจวิเคราะห์

    in_analysis --> completed: ลงผลเสร็จ

    completed --> disposed: ทิ้ง Specimen (หลัง 7 วัน)

    disposed --> [*]

    note right of pending
        รอเก็บ Specimen
        ตรงกับ Order.status
        = specimen_pending
    end note

    note right of collected
        เก็บ Specimen แล้ว
        + ตรวจสอบ Quality:
        - Good (เขียว)
        - Acceptable (เหลือง)
    end note

    note right of rejected
        ปฏิเสธ Specimen
        + Quality Issues:
        - Hemolysis
        - Clotted
        - Insufficient
        - etc.
    end note

    note right of sent_to_lis
        ส่ง LIS แล้ว
        พร้อมลงผล
        (ใน result-entry.html)
    end note

    note right of disposed
        ทิ้งหลังเก็บ 7 วัน
        ตาม Hospital Policy
    end note

Status Table

Status Thai Name Color Icon Description
pending รอเก็บ 🟡 Order ยืนยันแล้ว รอเจ้าหน้าที่เก็บ Specimen
collected เก็บแล้ว 🟢 เก็บ Specimen แล้ว คุณภาพ Good/Acceptable
sent_to_lis ส่ง LIS แล้ว 🔵 📤 ส่ง Specimen ไป LIS แล้ว พร้อมวิเคราะห์
in_analysis กำลังวิเคราะห์ 🔵 🔬 กำลังทำการวิเคราะห์ในห้องแล็บ
completed วิเคราะห์เสร็จ 🟢 ✓✓ วิเคราะห์เสร็จแล้ว เก็บรักษา Specimen
rejected ปฏิเสธ 🔴 Specimen คุณภาพไม่ดี ต้องเก็บใหม่
disposed ทิ้งแล้ว 🗑️ ทิ้ง Specimen แล้ว (หลังเก็บครบกำหนด)

Quality Assessment

Quality Levels:

Quality Thai Name Color Icon Description Action Required
good ดี (Good) 🟢 ✓✓ คุณภาพดี ไม่มีปัญหา ส่ง LIS ได้เลย
acceptable ยอมรับได้ (OK) 🟡 มีปัญหาเล็กน้อย แต่ใช้งานได้ ส่ง LIS ได้ + เพิ่มหมายเหตุ
poor ไม่ดี (Poor) 🟠 ⚠️ คุณภาพไม่ดี แต่ยังใช้ได้ (ระวัง) พิจารณาเก็บใหม่
rejected ปฏิเสธ (Reject) 🔴 คุณภาพไม่ดี ใช้ไม่ได้ ต้องเก็บใหม่

Quality Issue Codes:

Issue Code Thai Name Severity Impact
hemolysis Hemolysis (เม็ดเลือดแตก) Critical ผลผิดพลาด (K+, LDH สูงขึ้น)
clotted Clotted (เลือดแข็งตัว) Critical นับเม็ดเลือดไม่ได้
insufficient Insufficient (ปริมาณไม่พอ) High ตรวจไม่ครบรายการ
unlabeled Unlabeled (ไม่มี Label) High ไม่รู้ว่าเป็นของใคร
contaminated Contamination (ปนเปื้อน) Medium ผลอาจผิดพลาด (เชื้อโรค)
wrong_container Wrong Container (ภาชนะผิด) High ตรวจไม่ได้ (เช่น ใส่ Plain แทน EDTA)
lipemia Lipemia (ไขมันในเลือดสูง) Low ผลอาจสูงกว่าจริง

Transition Rules

1. pending → collected - Trigger: Lab Staff เก็บ Specimen + Quality = Good/Acceptable - Action: - Create Specimen record - Update collectedBy, collectedByName, collectedAt - Generate specimenNumber (SPEC-YYYYMMDD-XXXX) - Assess quality (good/acceptable/poor/rejected) - Record qualityIssues array (if any) - Write qualityNote (optional) - Validation: - Must select lab items (at least 1) - Must select quality level - If quality = poor/rejected → Must select quality issues

2. pending → rejected - Trigger: Lab Staff เก็บ Specimen + Quality = Rejected - Action: - Create Specimen record with quality = 'rejected' - Record qualityIssues (required) - Write qualityNote (explain reason) - Update Order.status = 'rejected_specimen' - Next: Prepare for recollection

3. rejected → pending - Trigger: Lab Staff เตรียมเก็บ Specimen ใหม่ - Action: - Create new Specimen record - Increment recollectionCount (R1, R2, R3, ...) - Set parentSpecimenNumber (link to original) - Update specimenNumber with suffix (SPEC-YYYYMMDD-XXXX-R1) - Reset quality = null (ready for new assessment)

4. collected → sent_to_lis - Trigger: Lab Staff click [ส่ง LIS] after reviewing all specimens - Action: - Update Specimen.status = 'sent_to_lis' - Update Order.status = 'sent_to_lis' - Print labels (optional, if not printed earlier) - Record sent timestamp - Validation: - All specimens must have quality = good/acceptable - Must print labels (within 24 hours of collection)

5. sent_to_lis → in_analysis - Trigger: Lab Technician starts entering results - Action: - Update Specimen.status = 'in_analysis' - Update Order.status = 'in_progress' - Auto-triggered: When first result is entered

6. in_analysis → completed - Trigger: All results for this specimen are approved - Action: - Update Specimen.status = 'completed' - Record completion timestamp - Set disposal date (7 days from collection, hospital policy) - Next: Keep specimen for 7 days before disposal

7. completed → disposed - Trigger: Specimen disposal after retention period (7 days) - Action: - Update Specimen.status = 'disposed' - Record disposal timestamp - Record disposed by user - Next: Terminal state (end of lifecycle)

Recollection Logic

Recollection Numbering:

// Original specimen
specimenNumber: "SPEC-20241226-0001"
recollectionCount: 0

// First recollection (R1)
specimenNumber: "SPEC-20241226-0001-R1"
recollectionCount: 1
parentSpecimenNumber: "SPEC-20241226-0001"

// Second recollection (R2)
specimenNumber: "SPEC-20241226-0001-R2"
recollectionCount: 2
parentSpecimenNumber: "SPEC-20241226-0001"

// Third recollection (R3)
specimenNumber: "SPEC-20241226-0001-R3"
recollectionCount: 3
parentSpecimenNumber: "SPEC-20241226-0001"

Recollection Limits: - Recommended: Maximum 3 recollections (R3) - After R3: Consider cancelling order if still rejected - Reasons: Patient safety, cost, time

Quality Issues Tracking:

{
  "specimenNumber": "SPEC-20241226-0001",
  "quality": "rejected",
  "qualityIssues": ["hemolysis", "insufficient"],
  "qualityNote": "เลือดแตก ปริมาณไม่พอ ต้องเก็บใหม่",
  "status": "rejected",
  "recollectionCount": 0
}

Label Printing

Label Format (100mm × 50mm):

┌──────────────────────────────────────────────┐
│ SPEC-20241226-0001                          │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━   │
│ HN: HN000123                                 │
│ ชื่อ: นายสมชาย ใจดี                          │
│ เก็บวันที่: 26/12/67 11:30                   │
│ ประเภท: Blood (EDTA Tube)                   │
│ รายการ: CBC, Platelet Count (2 รายการ)      │
│ ผู้เก็บ: เจ้าหน้าที่แล็บสมหญิง               │
└──────────────────────────────────────────────┘

Printing Rules: - Must print within 24 hours of collection - Cannot print for rejected specimens (quality = rejected) - Can re-print if label damaged (same content) - For recollection: Print with suffix (R1/R2/R3)

Specimen Retention Policy

Standard Retention: 7 days after collection

Extended Retention (Special cases): - Medico-legal cases: 30 days - Research specimens: As per protocol - Quality control: 14 days

Disposal Procedure: 1. Verify retention period expired 2. Check no pending disputes 3. Follow biohazard disposal protocol 4. Update status to 'disposed' 5. Record disposal in audit log


Summary Section 2

เสร็จแล้ว - Section 2: Status Transitions (3 Flows)

เอกสารนี้ครอบคลุม: 1. ✅ Order Status Flow - 13 สถานะ, 16 transitions, State diagram 2. ✅ Result Status Flow - 4 สถานะ, 4 transitions, Flag calculation, Critical handling 3. ✅ Specimen Status Flow - 7 สถานะ, 7 transitions, Quality assessment, Recollection logic

ถัดไป - Section 3: Data Relationships (ER Diagram + Data Flow)


3. Data Relationships (ความสัมพันธ์ของข้อมูล)

3.1 Entity Relationship Diagram

Purpose: แสดงความสัมพันธ์ระหว่าง Data Models ในระบบแล็บ

ER Diagram

erDiagram
    PATIENT ||--o{ LAB_ORDER : "has"
    PATIENT ||--o{ LAB_RESULT : "has"
    PATIENT ||--o{ SPECIMEN : "has"

    LAB_ORDER ||--|{ LAB_ORDER_ITEM : "contains"
    LAB_ORDER ||--o{ LAB_RESULT : "generates"
    LAB_ORDER ||--o{ SPECIMEN : "requires"

    LAB_ITEM ||--o{ LAB_ORDER_ITEM : "referenced in"
    LAB_ITEM ||--o{ LAB_RESULT : "measured by"
    LAB_ITEM ||--o{ SPECIMEN : "requires"

    LAB_RESULT ||--o{ FILE_ATTACHMENT : "has"

    SPECIMEN ||--|{ SPECIMEN_ITEM : "contains"

    PATIENT {
        string hn PK "Hospital Number"
        string firstName
        string lastName
        date dateOfBirth
        int age
        string gender
        string bloodGroup
        array allergies
        string status
    }

    LAB_ORDER {
        int id PK
        string orderNumber UK "LAB-YYYYMMDD-XXXX"
        string hn FK
        datetime orderDate
        string priority "routine/urgent/stat"
        string status "pending/confirmed/..."
        string doctorId
        decimal totalPrice
    }

    LAB_ORDER_ITEM {
        string itemId FK
        string orderNumber FK
        string itemCode
        decimal price
        boolean isOutsourced
    }

    LAB_ITEM {
        string id PK
        string code UK
        string name
        string nameTh
        string category
        string specimenType
        decimal price
        string resultType
        decimal referenceLow
        decimal referenceHigh
    }

    LAB_RESULT {
        int id PK
        string orderNumber FK
        string hn FK
        string labItemId FK
        string resultValue
        string flag "normal/high/low/critical"
        string status "pending/preliminary/final"
        datetime enteredAt
        datetime approvedAt
    }

    SPECIMEN {
        int id PK
        string specimenNumber UK "SPEC-YYYYMMDD-XXXX"
        string orderNumber FK
        string hn FK
        string specimenType
        string quality "good/acceptable/rejected"
        array qualityIssues
        string status "collected/sent/..."
        datetime collectedAt
    }

    SPECIMEN_ITEM {
        string specimenNumber FK
        string labItemId FK
    }

    FILE_ATTACHMENT {
        string id PK
        string orderNumber FK
        string labItemId FK
        string fileName
        string fileData "Base64"
        datetime uploadedAt
    }

Relationship Details

1. Patient → LabOrder (1:N) - Type: One-to-Many - Cardinality: 1 Patient → 0.. Orders - Foreign Key: LabOrder.hn → Patient.hn - Business Rule: - ผู้ป่วยหนึ่งคนสามารถมีหลาย Orders - Order ต้องมีผู้ป่วยเสมอ (Required) - Example*: HN000123 มี 5 orders (LAB-20241226-0001 ถึง LAB-20241226-0005)

2. Patient → LabResult (1:N) - Type: One-to-Many - Cardinality: 1 Patient → 0.. Results - Foreign Key: LabResult.hn → Patient.hn - Business Rule: - ผู้ป่วยหนึ่งคนสามารถมีหลาย Results - Result ต้องมีผู้ป่วยเสมอ (Required) - Purpose*: ใช้ในการ Delta Check (เปรียบเทียบผลเก่า-ใหม่)

3. Patient → Specimen (1:N) - Type: One-to-Many - Cardinality: 1 Patient → 0.. Specimens - Foreign Key: Specimen.hn → Patient.hn - Business Rule*: - ผู้ป่วยหนึ่งคนสามารถมีหลาย Specimens - Specimen ต้องมีผู้ป่วยเสมอ (Required)

4. LabOrder → LabOrderItem (1:N) - Type: One-to-Many (Embedded) - Cardinality: 1 Order → 1.. Items - Storage: Embedded Array in LabOrder.items - Business Rule: - Order ต้องมีอย่างน้อย 1 item - Item เก็บเป็น nested object ใน Order (ไม่แยก table) - Example*:

{
  "orderNumber": "LAB-20241226-0001",
  "items": [
    { "itemId": "CBC001", "price": 350 },
    { "itemId": "CBC002", "price": 200 }
  ]
}

5. LabOrder → LabResult (1:N) - Type: One-to-Many - Cardinality: 1 Order → 0.. Results (depends on order items) - Foreign Key: LabResult.orderNumber → LabOrder.orderNumber - Business Rule: - Order สร้าง Result records เมื่อ status = sent_to_lis - จำนวน Results = จำนวน Lab Items ใน Order - Result ต้องผูกกับ Order เสมอ (Required) - Example*: Order มี 5 items → สร้าง 5 result records

6. LabOrder → Specimen (1:N) - Type: One-to-Many - Cardinality: 1 Order → 1.. Specimens (depends on specimen types) - Foreign Key: Specimen.orderNumber → LabOrder.orderNumber - Business Rule: - Order อาจต้องการหลาย Specimen types (Blood, Urine) - 1 Specimen สามารถใช้สำหรับหลาย Lab Items ได้ - Specimen grouping: Items ที่ใช้ specimen type และ container เดียวกัน → 1 Specimen - Example*: - Order มี CBC, Platelet, Hemoglobin (ทั้งหมดใช้ Blood + EDTA Tube) → 1 Specimen - Order มี CBC (Blood) + Urinalysis (Urine) → 2 Specimens

7. LabItem → LabOrderItem (1:N) - Type: One-to-Many (Reference) - Cardinality: 1 Lab Item → 0.. Order Items - Foreign Key: LabOrderItem.itemId → LabItem.id - Business Rule*: - Lab Item เป็น Master Data (Read-only) - Order Item คัดลอกข้อมูลจาก Lab Item (cache) - ใช้สำหรับดึงข้อมูล price, specimen type, reference range

8. LabItem → LabResult (1:N) - Type: One-to-Many (Reference) - Cardinality: 1 Lab Item → 0.. Results - Foreign Key: LabResult.labItemId → LabItem.id - Business Rule*: - Result คัดลอกข้อมูล reference range จาก Lab Item - ใช้สำหรับคำนวณ Flag (normal/high/low/critical)

9. LabResult → FileAttachment (1:N) - Type: One-to-Many - Cardinality: 1 Result → 0.. Attachments - Foreign Key: FileAttachment.orderNumber + FileAttachment.labItemId → LabResult - Business Rule: - เฉพาะ Outlab Results เท่านั้นที่มี Attachments - Attachment เก็บเป็น Base64 ใน LocalStorage - 1 Result สามารถมีหลาย Attachments (PDF, Images) - Example*: HIV Viral Load Result มี 2 attachments (Report PDF + Result Image)

10. Specimen → SpecimenItem (1:N) - Type: One-to-Many (Embedded) - Cardinality: 1 Specimen → 1.. Lab Items - Storage: Array of labItemIds in Specimen - Business Rule: - 1 Specimen สามารถใช้สำหรับหลาย Lab Items - Lab Items ที่ใช้ specimen type และ container เดียวกัน - Example*:

{
  "specimenNumber": "SPEC-20241226-0001",
  "specimenType": "Blood",
  "containerType": "EDTA Tube",
  "labItemIds": ["CBC001", "CBC002", "CBC003"]
}

Cardinality Summary

Relationship Cardinality Description
Patient → LabOrder 1 : N 1 ผู้ป่วย มีหลาย Orders
Patient → LabResult 1 : N 1 ผู้ป่วย มีหลาย Results
Patient → Specimen 1 : N 1 ผู้ป่วย มีหลาย Specimens
LabOrder → LabOrderItem 1 : N 1 Order มีหลาย Items (embedded)
LabOrder → LabResult 1 : N 1 Order สร้างหลาย Results
LabOrder → Specimen 1 : N 1 Order ต้องการหลาย Specimens
LabItem → LabOrderItem 1 : N 1 Lab Item ถูกสั่งหลาย Orders
LabItem → LabResult 1 : N 1 Lab Item มีหลาย Results (history)
LabResult → FileAttachment 1 : N 1 Result มีหลาย Attachments (outlab)
Specimen → SpecimenItem 1 : N 1 Specimen ใช้สำหรับหลาย Lab Items

Data Integrity Rules

Referential Integrity:

  1. Cascade Delete: ไม่มี (Mock data, no real database)
  2. Orphan Prevention:
  3. Cannot delete Patient if has active Orders
  4. Cannot delete Order if has Results
  5. Cannot delete Specimen if status = sent_to_lis
  6. Foreign Key Validation:
  7. LabOrder.hn must exist in PatientService
  8. LabResult.orderNumber must exist in LabOrderService
  9. Specimen.orderNumber must exist in LabOrderService

Business Constraints:

  1. Unique Constraints:
  2. Patient.hn (Unique)
  3. LabOrder.orderNumber (Unique)
  4. LabItem.id (Unique)
  5. LabItem.code (Unique)
  6. Specimen.specimenNumber (Unique)

  7. Required Fields:

  8. LabOrder must have: hn, orderDate, priority, items (length ≥ 1)
  9. LabResult must have: orderNumber, hn, labItemId
  10. Specimen must have: orderNumber, hn, specimenType

  11. Validation Rules:

  12. LabOrder.priority ∈ {'routine', 'urgent', 'stat'}
  13. LabResult.flag ∈ {'normal', 'high', 'low', 'critical_high', 'critical_low'}
  14. Specimen.quality ∈ {'good', 'acceptable', 'poor', 'rejected'}

3.2 Data Flow Diagram

Purpose: แสดงการไหลของข้อมูลผ่านระบบแล็บแต่ละขั้นตอน

Level 0: Context Diagram

graph TB
    subgraph External Entities
        Doctor[👨‍⚕️ Doctor]
        Patient[👤 Patient]
        LabStaff[🧑‍🔬 Lab Staff]
        LabTech[🔬 Lab Technician]
        LabChief[👔 Lab Supervisor]
    end

    subgraph LAB System
        LabModule[🧪 LAB Module<br/>Laboratory Management System]
    end

    Doctor -->|Lab Order Request| LabModule
    Patient -.->|Patient Info| LabModule
    LabModule -->|Order Confirmation| LabStaff
    LabStaff -->|Specimen Collection| LabModule
    LabModule -->|Specimens| LabTech
    LabTech -->|Lab Results| LabModule
    LabModule -->|Results for Approval| LabChief
    LabChief -->|Approved Results| LabModule
    LabModule -->|Final Report| Doctor

    style LabModule fill:#667eea,stroke:#764ba2,stroke-width:3px,color:#fff

Level 1: System-Level Data Flow

graph TB
    subgraph Inputs
        DoctorOrder[📋 Doctor Order<br/>- Patient HN<br/>- Lab Items<br/>- Priority<br/>- Clinical Notes]
        SpecimenData[🔬 Specimen Data<br/>- Collection Time<br/>- Quality Assessment<br/>- Container Info]
        ResultData[📊 Result Data<br/>- Result Values<br/>- Flags<br/>- Lab Notes<br/>- Attachments]
        ApprovalData[✅ Approval Data<br/>- Approval Status<br/>- Rejection Reason<br/>- Approver Info]
    end

    subgraph Processes
        P1[1.0<br/>Order Management<br/>doctor-order.html<br/>order-queue.html]
        P2[2.0<br/>Specimen Collection<br/>specimen-collection.html]
        P3[3.0<br/>Result Entry<br/>result-entry.html]
        P4[4.0<br/>Result Approval<br/>result-approval.html]
        P5[5.0<br/>Reporting<br/>Generate Final Report]
    end

    subgraph Data Stores
        DS1[(D1: Lab Orders<br/>his_lab_orders)]
        DS2[(D2: Specimens<br/>his_specimens)]
        DS3[(D3: Lab Results<br/>his_lab_results)]
        DS4[(D4: Lab Items<br/>lab-items.json)]
        DS5[(D5: Patients<br/>his_patients)]
        DS6[(D6: Attachments<br/>his_file_attachments)]
    end

    subgraph Outputs
        OrderConfirm[✓ Order Confirmation]
        SpecimenLabels[🏷️ Specimen Labels]
        PrelimResults[📝 Preliminary Results]
        FinalReport[📄 Final Lab Report]
    end

    DoctorOrder --> P1
    P1 --> DS1
    DS1 --> OrderConfirm
    DS1 --> P2
    DS5 -.-> P1
    DS4 -.-> P1

    SpecimenData --> P2
    P2 --> DS2
    DS2 --> SpecimenLabels
    DS2 --> P3
    DS1 -.-> P2

    ResultData --> P3
    P3 --> DS3
    DS3 --> PrelimResults
    DS3 --> P4
    DS1 -.-> P3
    DS4 -.-> P3
    DS6 --> P3

    ApprovalData --> P4
    P4 --> DS3
    DS3 --> P5

    P5 --> FinalReport
    DS1 -.-> P5
    DS3 -.-> P5
    DS5 -.-> P5

    style P1 fill:#4CAF50,stroke:#2E7D32,color:#fff
    style P2 fill:#FF9800,stroke:#E65100,color:#fff
    style P3 fill:#2196F3,stroke:#0D47A1,color:#fff
    style P4 fill:#9C27B0,stroke:#4A148C,color:#fff
    style P5 fill:#F44336,stroke:#B71C1C,color:#fff

Level 2: Process-Level Data Flow (Detail)

Process 1.0: Order Management

graph LR
    subgraph 1.0 Order Management
        P11[1.1<br/>Search Patient]
        P12[1.2<br/>Select Lab Items]
        P13[1.3<br/>Calculate Price]
        P14[1.4<br/>Create Order]
        P15[1.5<br/>Confirm Order]
    end

    DS5[(Patients)]
    DS4[(Lab Items)]
    DS1[(Lab Orders)]

    DS5 --> P11
    P11 -->|Patient Info| P12
    DS4 --> P12
    P12 -->|Selected Items| P13
    DS4 -.->|Pricing| P13
    P13 -->|Order Data| P14
    P14 --> DS1
    DS1 --> P15
    P15 -->|Confirmed| DS1

    style P14 fill:#4CAF50,stroke:#2E7D32,color:#fff
    style P15 fill:#2196F3,stroke:#0D47A1,color:#fff

Process 2.0: Specimen Collection

graph LR
    subgraph 2.0 Specimen Collection
        P21[2.1<br/>Load Orders]
        P22[2.2<br/>Select Items]
        P23[2.3<br/>Assess Quality]
        P24[2.4<br/>Generate Specimen#]
        P25[2.5<br/>Print Label]
        P26[2.6<br/>Send to LIS]
    end

    DS1[(Lab Orders)]
    DS2[(Specimens)]

    DS1 --> P21
    P21 -->|Order Info| P22
    P22 -->|Selected Items| P23
    P23 -->|Quality OK?| P24
    P23 -.->|Quality Bad| P21
    P24 --> DS2
    DS2 --> P25
    P25 -->|Label| P26
    P26 --> DS2
    DS2 -.->|Update Status| DS1

    style P23 fill:#FF9800,stroke:#E65100,color:#fff
    style P24 fill:#4CAF50,stroke:#2E7D32,color:#fff

Process 3.0: Result Entry

graph LR
    subgraph 3.0 Result Entry
        P31[3.1<br/>Load Orders]
        P32[3.2<br/>Enter Results]
        P33[3.3<br/>Calculate Flags]
        P34[3.4<br/>Delta Check]
        P35[3.5<br/>Critical Alert]
        P36[3.6<br/>Upload Outlab]
        P37[3.7<br/>Save Results]
    end

    DS1[(Lab Orders)]
    DS3[(Lab Results)]
    DS4[(Lab Items)]
    DS6[(Attachments)]
    Previous[(Previous Results)]

    DS1 --> P31
    P31 -->|Order Info| P32
    P32 -->|Result Value| P33
    DS4 -.->|Reference Range| P33
    P33 -->|Flag| P34
    Previous -.->|Old Value| P34
    P34 -->|Delta %| P35
    P35 -.->|Critical?| P37
    P32 -.->|Outlab?| P36
    P36 --> DS6
    P37 --> DS3
    DS3 -.->|Update Status| DS1

    style P33 fill:#2196F3,stroke:#0D47A1,color:#fff
    style P35 fill:#F44336,stroke:#B71C1C,color:#fff
    style P36 fill:#9C27B0,stroke:#4A148C,color:#fff

Process 4.0: Result Approval

graph LR
    subgraph 4.0 Result Approval
        P41[4.1<br/>Load Pending]
        P42[4.2<br/>Review Results]
        P43[4.3<br/>Approve/Reject]
        P44[4.4<br/>Rejection Workflow]
        P45[4.5<br/>Update Status]
    end

    DS3[(Lab Results)]
    DS1[(Lab Orders)]

    DS3 --> P41
    P41 -->|Preliminary| P42
    P42 -->|Decision| P43
    P43 -.->|Reject?| P44
    P44 -.->|Reason| DS3
    P44 -.->|New Specimen?| DS1
    P43 -->|Approve| P45
    P45 --> DS3
    DS3 -.->|All Approved?| DS1

    style P43 fill:#4CAF50,stroke:#2E7D32,color:#fff
    style P44 fill:#F44336,stroke:#B71C1C,color:#fff

Data Flow Patterns

1. Create Order Flow

Doctor Input → Validate → Create Order → Confirm → Generate Specimen List

2. Specimen Collection Flow

Load Order → Select Items → Collect → QC Check → Generate Number → Print Label → Send LIS
                                          ↓ (Rejected)
                                     Back to Collect

3. Result Entry Flow

Load Order → Enter Value → Calculate Flag → Delta Check → Critical? → Save
                                                             ↓ (Yes)
                                                        Alert Modal

4. Approval Flow

Load Preliminary → Review → Approve? → Update Status
                              ↓ (No)
                          Reject + Reason → Back to Entry or Recollect

Data Store Access Patterns

Process Read From Write To
1.0 Order Management Patients, Lab Items Lab Orders
2.0 Specimen Collection Lab Orders Specimens, Lab Orders (status)
3.0 Result Entry Lab Orders, Lab Items, Previous Lab Results, Attachments, Orders (status)
4.0 Result Approval Lab Results Lab Results (status), Orders (status)
5.0 Reporting Lab Orders, Lab Results, Patients None (Generate output)

Critical Data Flows

Critical Result Notification Flow:

sequenceDiagram
    participant Tech as Lab Technician
    participant System as LAB System
    participant Storage as LocalStorage
    participant Alert as Critical Alert Modal
    participant Doctor as Doctor (Mock)

    Tech->>System: Enter Critical Result (Platelet = 48)
    System->>System: Calculate Flag → critical_low
    System->>Alert: Show Critical Alert Modal
    Alert->>Alert: Start 30-min Countdown
    Alert->>Tech: Require Double-Check ✓
    Tech->>Alert: Confirm "ตรวจซ้ำแล้ว"
    Alert->>System: Save Critical Result
    System->>Storage: Update Result (status: preliminary)
    System->>Doctor: Send Notification (Mock)
    System->>Tech: Alert "ระบบได้แจ้งแพทย์แล้ว"

Rejection Flow (Require New Specimen):

sequenceDiagram
    participant Chief as Lab Supervisor
    participant System as LAB System
    participant Storage as LocalStorage
    participant Staff as Lab Staff

    Chief->>System: Review Result
    Chief->>System: Click [ปฏิเสธ] + Reason
    Chief->>System: ✓ "ต้องการเก็บ Specimen ใหม่"
    System->>Storage: Update Result (status: rejected)
    System->>Storage: Update Order (status: rejected_specimen)
    System->>Staff: Show in Specimen Collection Page
    Staff->>System: Collect New Specimen (R1)
    System->>Storage: Create New Specimen Record
    System->>Storage: Update Order (status: specimen_collected)

Data Transformation Points

1. Order Creation → Result Initialization

// Transform
Order.items.forEach(item => {
  createResultRecord({
    orderNumber: Order.orderNumber,
    hn: Order.hn,
    labItemId: item.itemId,
    labItemCode: item.itemCode,
    labItemName: item.itemName,
    // Copy reference ranges from LabItem
    referenceLow: labItem.referenceLow,
    referenceHigh: labItem.referenceHigh,
    criticalLow: labItem.criticalLow,
    criticalHigh: labItem.criticalHigh,
    status: 'pending'
  });
});

2. Result Entry → Flag Calculation

// Transform
resultValue (raw)  parseFloat()  compare with ranges  flag (enum)
Example: "8.5"  8.5  compare(4.5, 11.0)  "normal"
Example: "50"  50  compare(150, 400, critical 50)  "critical_low"

3. Specimen Collection → Grouping

// Transform: Group items by specimen type + container
Order.items
  .filter(item => item.specimenType === 'Blood' && item.containerType === 'EDTA Tube')
  .map(item => item.itemId)
   Specimen { specimenType: 'Blood', containerType: 'EDTA Tube', labItemIds: [...] }

4. Approval → Status Update

// Transform: Cascade status updates
LabResult.status = 'preliminary'  'final'
  
Check all results in order
  
If all final  Order.status = 'approved'  'completed'


Summary Section 3

เสร็จแล้ว - Section 3: Data Relationships

เอกสารนี้ครอบคลุม: 1. ✅ Entity Relationship Diagram - 10 relationships พร้อม cardinality และ business rules 2. ✅ Data Flow Diagram - Level 0 (Context), Level 1 (System), Level 2 (Process detail) 3. ✅ Data Flow Patterns - Create, Collection, Entry, Approval flows 4. ✅ Critical Flows - Critical Result Notification, Rejection with Recollection 5. ✅ Data Transformation Points - 4 key transformations

ถัดไป - Section 4: LocalStorage Structure + Section 5: Mock Data Schema

ให้ฉันตรวจสอบและยืนยันก่อนจะดำเนินการต่อนะครับ 😊