05. Data Models & Status Flow - โครงสร้างข้อมูลและสถานะ
เอกสารสรุปโครงสร้างข้อมูล (Data Models), การเปลี่ยนแปลงสถานะ (Status Transitions), และความสัมพันธ์ของข้อมูล (Data Relationships) ในระบบชันสูตร (LAB Module)
เวอร์ชัน: 1.0
อัปเดตล่าสุด: 26 ธันวาคม 2567
ผู้เขียน: HIS Development Team
📑 สารบัญ
- Data Models (โครงสร้างข้อมูล)
- 1.1 Patient Model
- 1.2 LabOrder Model
- 1.3 LabItem Model
- 1.4 LabResult Model
- 1.5 Specimen Model
- 2.1 Order Status Flow
- 2.2 Result Status Flow
- 3.1 Entity Relationship Diagram
- 4.1 Storage Keys
-
4.2 Data Persistence
- 5.1 JSON Files Overview
- 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:
- Cannot skip states: ต้องเดินตาม Flow ตามลำดับ (ไม่สามารถข้ามขั้นตอนได้)
- Specimen Quality Check: ต้องมี Specimen คุณภาพ Good/Acceptable อย่างน้อย 1 รายการก่อนส่ง LIS
- All Results Required: ต้องลงผลครบทุกรายการก่อนส่ง Approval
- Partial Approval Allowed: สามารถอนุมัติผลบางรายการได้ (ไม่จำเป็นต้องทั้งหมด)
- Critical Alert: ถ้ามี Critical Result ต้องแสดง Alert และ Confirm ก่อน Approve
- Recollection Limit: แนะนำไม่เกิน 3 ครั้ง (R3) - เกินนี้ควรยกเลิก Order
- Cancellation Window: สามารถยกเลิกได้เฉพาะสถานะ
pending(ก่อน Lab ยืนยัน)
Performance Metrics:
- TAT (Turnaround Time): นับจาก
orderDate→completedAt - STAT: ≤ 60 minutes
- Urgent: ≤ 120 minutes
-
Routine: ≤ 240 minutes (4 hours)
-
Specimen Collection Time: นับจาก
confirmedAt→specimen_collected -
Target: ≤ 30 minutes
-
Result Entry Time: นับจาก
sent_to_lis→pending_approval -
Depends on test type (lab-items.json: normalTime/urgentTime/statTime)
-
Approval Time: นับจาก
pending_approval→approved - 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:
- Alert Display: Show Critical Alert Modal
- List all critical results
- Show countdown timer (30 minutes)
-
Require double-check confirmation checkbox
-
Countdown Timer: 30-minute countdown
- Display in MM:SS format
- Change color to red when < 5 minutes
-
Pulse animation when time is critical
-
Double-Check Required:
- Cannot save without checking "ได้ตรวจสอบผลซ้ำแล้ว" checkbox
-
Ensure Lab Technician verifies critical value
-
Notification (Mock):
- Success message: "บันทึกผลวิกฤตสำเร็จ X รายการ ระบบได้แจ้งแพทย์แล้ว"
-
In real system: Send SMS/Email/In-app notification to doctor
-
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:
- Cascade Delete: ไม่มี (Mock data, no real database)
- Orphan Prevention:
- Cannot delete Patient if has active Orders
- Cannot delete Order if has Results
- Cannot delete Specimen if status = sent_to_lis
- Foreign Key Validation:
- LabOrder.hn must exist in PatientService
- LabResult.orderNumber must exist in LabOrderService
- Specimen.orderNumber must exist in LabOrderService
Business Constraints:
- Unique Constraints:
- Patient.hn (Unique)
- LabOrder.orderNumber (Unique)
- LabItem.id (Unique)
- LabItem.code (Unique)
-
Specimen.specimenNumber (Unique)
-
Required Fields:
- LabOrder must have: hn, orderDate, priority, items (length ≥ 1)
- LabResult must have: orderNumber, hn, labItemId
-
Specimen must have: orderNumber, hn, specimenType
-
Validation Rules:
- LabOrder.priority ∈ {'routine', 'urgent', 'stat'}
- LabResult.flag ∈ {'normal', 'high', 'low', 'critical_high', 'critical_low'}
- 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
ให้ฉันตรวจสอบและยืนยันก่อนจะดำเนินการต่อนะครับ 😊