แผนการเรียนรู้
/
AMMS 302 | Hour 1 (Workshop Blueprint)

การสร้างข้อมูลสุขภาพสังเคราะห์และการทำความสะอาดข้อมูลคลินิก

รายวิชา ปฏิบัติการสารสนเทศด้านสุขภาพ (อจวพ ๓๐๒)
หลักสูตรวิทยาศาสตรบัณฑิต สาขาวิชาวิทยาศาสตร์การแพทย์ โครงการจัดตั้งวิทยาเขตอำนาจเจริญ มหาวิทยาลัยมหิดล

🎯 สารบัญและลำดับการเรียนรู้ชั่วโมงที่ 1

💡 คลิกปุ่มเพื่อข้ามไปยังสไลด์ที่ต้องการ หรือใช้ปุ่มลูกศรควบคุมการนำเสนอ

Slide 1: Final Product Preview AMMS 302 | Hour 1

เป้าหมายปลายทาง: ระบบเฝ้าระวังโรคเรื้อรังและสนับสนุนการตัดสินใจทางคลินิก

ตลอดเวิร์กช็อป 6 ชั่วโมง นิสิตจะได้ลงมือสร้างระบบ **Chronic Disease Surveillance & CDSS** ฉบับสมบูรณ์ที่ปรับใช้ได้กับทุกโครงงานกลุ่ม ตั้งแต่การประมวลผลข้อมูลดิบจนถึงการขึ้นระบบเว็บแอปพลิเคชันบน Cloud (PythonAnywhere)

📱

1. ส่วนคัดกรองประเมินความเสี่ยงรายบุคคล (Patient View)

ฟอร์มแบบ Interactive (Alpine.js) สำหรับประเมินความเสี่ยงเบาหวานและความดันโลหิตสูงล่วงหน้าแบบเรียลไทม์ ก่อนการบันทึกลงฐานข้อมูล

✔ Live Risk Score Calculation
✔ Validation Range: SBP (50-300), FPG (40-500)
✔ CSRF Protected & Responsive Web UI
🩺

2. แดชบอร์ดเฝ้าระวังสำหรับทีมแพทย์ (Clinician View)

ระบบจัดอันดับกลุ่มเสี่ยงวิกฤต (Triage Queue) ค้นหาผู้ป่วยค่าแล็บสูงวิกฤตแต่ **"ยังไม่ได้รับยา"** ด้วยคำสั่ง Anti-Join ใน SQL

🚨 High-Risk Care Gap Alert (NOT EXISTS)
📊 Population Risk Tier Cards (Red/Yellow/Green)
📈 7-Day Moving Average Vitals Trends
⚙️ Software Stack ที่ใช้: PySynthea ➔ Pandas ➔ SQLite (3NF) ➔ Django ➔ Tailwind CSS + Alpine.js ➔ PythonAnywhere
Slide 2: PySynthea Generator AMMS 302 | Hour 1

ขั้นตอนที่ 1: การติดตั้งและสร้างข้อมูลผู้ป่วยสังเคราะห์ด้วย PySynthea

ใช้งาน PySynthea Framework ร่วมกับไฟล์โครงสร้างประชากรไทย thai_demographics.csv เพื่อจำลองเวชระเบียนผู้ป่วยตามจังหวัดต่างๆ ในประเทศไทย

TAB 1 CONTENT: PySynthea with thai_demographics.csv
💻 Python Script: pysynthea_generate_raw_data.py Ready to Copy-and-Paste
# 1. ติดตั้งไลบรารี PySynthea และ Pandas
# pip install pysynthea pandas

import pandas as pd
from synthea import Generator, GeneratorOptions

# 2. ตั้งค่า GeneratorOptions โหลดไฟล์ประชากรไทย thai_demographics.csv
options = GeneratorOptions()
options.population_size = 1000
options.seed = 42
options.demographics_file = "thai_demographics.csv"  # โหลดไฟล์โครงสร้าง 77 จังหวัด
options.state = "Thailand"
options.city = "อำนาจเจริญ"                 # ระบุจังหวัดเป้าหมาย (หรือสุ่มจาก csv)

# 3. รันสร้างข้อมูลประชากรผู้ป่วยสังเคราะห์ด้วย PySynthea Engine
generator = Generator(options)
cohort = generator.run()

# 4. แปลงผลลัพธ์ลง Pandas DataFrame และส่งออก CSV
df_raw = cohort.to_dataframe(table="patients")
df_raw.to_csv('project_raw_patients.csv', index=False)

print("✅ บันทึก project_raw_patients.csv สำเร็จโดยใช้ PySynthea + thai_demographics.csv!")
💻 Python Script: generate_raw_data.py Mock Generator for Testing Edge Cases
import pandas as pd
import random
from datetime import datetime, timedelta

# อ่านรายชื่อจังหวัดจากไฟล์ thai_demographics.csv
df_demo = pd.read_csv('thai_demographics.csv')
provinces = df_demo['city'].tolist()

random.seed(42)
data = []

for i in range(1, 1001):
    admit = datetime(2026, 1, 1) + timedelta(days=random.randint(1, 180))
    disch = admit + timedelta(days=random.randint(-2, 14)) # จำลองตรรกะเวลาผิดพลาด
    
    data.append({
        'patient_id': f'HN-{i:05d}',
        'patient_name': f'Patient_{i}',
        'province': random.choice(provinces),
        'age': random.choice([25, 45, 62, 78, 92, 105]), # Age > 89 Outliers
        'gender': random.choice(['M', 'F']),
        'sbp': random.choice([115, 128, 142, 185, 350, None]),
        'hba1c': random.choice([5.4, 6.2, 7.8, 11.2, None]),
        'admittime': admit.strftime('%Y-%m-%d %H:%M:%S'),
        'dischtime': disch.strftime('%Y-%m-%d %H:%M:%S')
    })

df_raw = pd.DataFrame(data)
df_raw.to_csv('project_raw_patients.csv', index=False)
print("✅ บันทึก project_raw_patients.csv สำเร็จ (พร้อมคอลัมน์ province ไทย)!")
Slide 3: Exploratory Data Analysis AMMS 302 | Hour 1

ขั้นตอนที่ 2: การนำเข้าและการสำรวจข้อมูลเบื้องต้น (EDA)

ใช้คำสั่งพื้นฐานใน Pandas เพื่อตรวจสอบโครงสร้างชุดข้อมูล มิติข้อมูล (Shape) และนับจำนวนค่าว่าง (Null Values)

💻 Python Script: eda_inspection.py Ready to Copy-and-Paste
import pandas as pd

# 1. อ่านไฟล์ข้อมูลดิบที่สร้างขึ้น
df = pd.read_csv('project_raw_patients.csv')

# 2. ตรวจสอบมิติข้อมูล (แถว, คอลัมน์) *ข้อควรระวัง: df.shape เป็น Property ไม่มีตัวเล็บ*
print(f"มิติชุดข้อมูล (Rows, Cols): {df.shape}")

# 3. ตรวจสอบชนิดข้อมูลและจำนวณค่าที่ไม่เป็น Null
print("\n--- สรุปชนิดข้อมูล (df.info()) ---")
df.info()

# 4. ตรวจสอบจำนวนค่าว่างในแต่ละคอลัมน์ (Missing Value Audit)
print("\n--- สรุปจำนวนค่าว่าง (isnull().sum()) ---")
missing_summary = df.isnull().sum()
print(missing_summary)

# 5. แสดงสถิติเชิงพรรณนาเบื้องต้นสำหรับคอลัมน์ตัวเลข
print("\n--- สถิติสรุปภาพรวม (df.describe()) ---")
print(df[['age', 'sbp', 'hba1c']].describe())
Slide 4: Temporal & Logical Validation AMMS 302 | Hour 1

ขั้นตอนที่ 3: การตรวจตรรกะเวลาและค่าที่เป็นไปไม่ได้ทางคลินิก

แปลงข้อความวันที่เป็น `datetime` เพื่อตรวจสอบเงื่อนไขความขัดแย้งเชิงเวลา (`dischtime >= admittime`) และกรองค่าที่เป็นไปไม่ได้ออก

💻 Python Script: temporal_validation.py Ready to Copy-and-Paste
import pandas as pd

df = pd.read_csv('project_raw_patients.csv')

# 1. แปลงคอลัมน์ข้อความวันที่ให้อยู่ในรูป datetime object
df['admittime'] = pd.to_datetime(df['admittime'])
df['dischtime'] = pd.to_datetime(df['dischtime'])

# 2. คำนวณระยะเวลาการนอนโรงพยาบาล (Length of Stay - LOS) ในหน่วยวัน
df['los_days'] = (df['dischtime'] - df['admittime']).dt.total_seconds() / 86400

# 3. กรองเฉพาะแถวข้อมูลที่มีตรรกะเวลาถูกต้อง (los_days >= 0)
valid_time_mask = df['los_days'] >= 0
df_time_clean = df[valid_time_mask].copy()

# 4. กรองค่าสัญญานชีพที่เป็นไปไม่ได้ทางคลินิก (Physiologically Implausible Bounds)
# ค่า SBP ปกติของมนุษย์ควรอยู่ระหว่าง 50 ถึง 300 mmHg
valid_vitals_mask = (df_time_clean['sbp'].isnull()) | (df_time_clean['sbp'].between(50, 300))
df_valid = df_time_clean[valid_vitals_mask].copy()

print(f"จำนานแถวเดิม: {len(df)} ➔ หลังตรวจตรรกะเวลาและค่าเป็นไปไม่ได้: {len(df_valid)}")
Slide 5: IQR Outlier Filtering & Export AMMS 302 | Hour 1

ขั้นตอนที่ 4: การตัด Outliers ด้วยวิธี IQR และการส่งออกข้อมูล

ใช้ค่าพิสัยระหว่างควอร์ไทล์ (IQR) ในการตัด Outliers ทางสถิติ และส่งออกไฟล์ CSV สะอาดพร้อมสำหรับขั้นตอนการคุ้มครองความเป็นส่วนตัว (PDPA) ในชั่วโมงถัดไป

💻 Python Script: iqr_clean_export.py Ready to Copy-and-Paste
import pandas as pd

# โหลดชุดข้อมูลที่ผ่านการตรวจตรรกะเวลาแล้ว
df = df_valid.copy()

# 1. คำนวณค่า Quantile Q1 (25%), Q3 (75%) และ IQR สำหรับคอลัมน์ sbp
Q1 = df['sbp'].quantile(0.25)
Q3 = df['sbp'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# 2. กรองข้อมูลเฉพาะแถวที่อยู่ในช่วง [Lower Bound, Upper Bound] หรือเป็นค่าว่าง (Null)
iqr_mask = (df['sbp'].isnull()) | ((df['sbp'] >= lower_bound) & (df['sbp'] <= upper_bound))
df_cleaned = df[iqr_mask].copy()

# 3. ส่งออกชุดข้อมูลทำความสะอาดแล้วลงไฟล์ CSV (ระบุ index=False เพื่อคงโครงสร้างคอลัมน์)
df_cleaned.to_csv('cleaned_patients_hour1.csv', index=False)

print(f"ขอบเขต IQR ของ SBP: [{lower_bound:.1f}, {upper_bound:.1f}] mmHg")
print(f"✅ บันทึกไฟล์ cleaned_patients_hour1.csv เรียบร้อยแล้ว (คงเหลือ {len(df_cleaned)} แถว)")
AMMS 302 | Hour 2 (Privacy & De-identification Pipeline)

ธรรมาภิบาลข้อมูลสุขภาพ และการคุ้มครองความเป็นส่วนตัว

การประมวลผลข้อมูลเวชระเบียนตามมาตรฐาน PDPA พ.ศ. 2562, HIPAA Safe Harbor,
และการสร้างท่อปกปิดตัวตนด้วยเทคนิค Cryptographic Salt-Hashing

🎯 สารบัญและลำดับการเรียนรู้ชั่วโมงที่ 2

💡 คลิกปุ่มเพื่อข้ามไปยังสไลด์ที่ต้องการ หรือใช้ปุ่มลูกศรควบคุมการนำเสนอ

Slide 7: Legal Frameworks AMMS 302 | Hour 2

เปรียบเทียบกรอบกฎหมาย: พ.ร.บ. คุ้มครองข้อมูลส่วนบุคคล (PDPA) vs. HIPAA

ข้อมูลสุขภาพจัดเป็น **ข้อมูลส่วนบุคคลอ่อนไหว (Sensitive Personal Data)** ซึ่งต้องมีมาตรการคุ้มครองและกำกับดูแลขั้นสูงสุดตามกฎหมายทั้งในระดับประเทศและสากล

🇹🇭

Thailand PDPA พ.ศ. 2562 (มาตรา 26)

  • Explicit Consent: การเก็บรวบรวมข้อมูลสุขภาพต้องได้รับความยินยอมโดยชัดแจ้ง
  • ข้อยกเว้นทางการแพทย์: ยกเว้นขอ consent เมื่อเป็นประโยชน์ต่อชีวิต (Vital Interest), การรักษาพยาบาลโดยผู้ประกอบวิชาชีพ, หรือสาธารณสุข
  • ผู้มีส่วนได้ส่วนเสีย: Data Subject (คนไข้), Data Controller (รพ.), Data Processor (คลาวด์/ผู้ประมวลผล)
  • บทลงโทษ: โทษปรับทางปกครองสูงสุด 5 ล้านบาท และโทษทางอาญา
🇺🇸

US HIPAA (Privacy & Security Rule)

  • PHI Protection: คุ้มครอง Protected Health Information ในทุกรูปแบบ
  • Minimum Necessary Standard: เข้าถึงข้อมูลเฉพาะเท่าที่จำเป็นต่อการปฏิบัติงาน
  • 2 วิธีการ De-identification:
    1. Safe Harbor Method: ตัดข้อมูลระบุตัวตน 18 รายการ
    2. Expert Determination: ใช้หลักสถิติมั่นใจว่าความเสี่ยงชี้ตัวต่ำมาก
  • มาตรฐานอ้างอิง: ใช้กับชุดข้อมูลวิจัยเปิดระดับโลก เช่น MIMIC-IV
📌 หลักการสำคัญ: ข้อมูลส่วนบุคคลที่ผ่านกระบวนการ De-identification จนไม่สามารถระบุตัวตนได้อีกต่อไป จะหลุดพ้นจากขอบเขตบังคับของ PDPA
Slide 8: Safe Harbor 18 Identifiers AMMS 302 | Hour 2

หลักเกณฑ์ Safe Harbor: 18 รายการข้อมูลระบุตัวตนที่ต้องกำจัดออก

ตามมาตรฐาน HIPAA การนำชุดข้อมูลสุขภาพไปใช้ในการวิจัยหรือพัฒนาระบบ AI จะต้องทำการลบหรือแปลงข้อมูลระบุตัวตนทั้ง 18 รายการดังต่อไปนี้:

1. ชื่อ-นามสกุล (Names)
2. ที่อยู่อาศัยย่อยกว่าจังหวัด (Geographic data)
3. วันที่เกี่ยวกับบุคคล (ยกเว้นปี ค.ศ.)
4. หมายเลขโทรศัพท์ (Phone)
5. หมายเลขแฟกซ์ (Fax)
6. ที่อยู่อีเมล (Email)
7. เลขบัตรประชาชน / SSN
8. เลขประจำตัวผู้ป่วย (HN / MRN)
9. เลขที่สิทธิประกันสุขภาพ
10. เลขที่บัญชีธนาคาร
11. เลขใบอนุญาต / ทะเบียนรถ
12. Serial Number เครื่องมือแพทย์
13. Web URL ที่ระบุบุคคล
14. IP Address
15. อัตลักษณ์ชีวภาพ (Biometrics)
16. ภาพถ่ายใบหน้าเต็ม (Full-face photo)
17. รหัสพันธุกรรม / ลายนิ้วมือ
18. หมายเลขระบุตัวตนเฉพาะอื่นใด
⚠️
กฎการรวมกลุ่มอายุเกิน 89 ปี (Age > 89 Rule):
เนื่องจากผู้สูงอายุที่มีอายุเกิน 89 ปีจัดเป็นกลุ่มประชากรขนาดเล็กที่มีความเสี่ยงสูงต่อการถูกชี้ตัว (Extreme Outliers) ตามหลัก HIPAA จึงต้องแปลงอายุที่มากกว่า 89 ปีทั้งหมดให้เป็นหมวดหมู่รวม เช่น '90+'
Slide 9: Linkage Attack & Salt-Hashing AMMS 302 | Hour 2

ความเสี่ยง Linkage Attack และการเข้ารหัสด้วย SHA-256 + Salt

Linkage Attack (Sweeney, 1997): แม้จะลบชื่อออก แต่เพียงใช้ รหัสไปรษณีย์ + วันเกิด + เพศ สามารถระบุตัวตนประชากรในสหรัฐฯ ได้ถูกต้องถึง **87%** เมื่อนำไปเทียบตารางทะเบียนราษฎร์

💻 Python Script: salt_hashing_demo.py Ready to Copy-and-Paste
import hashlib

# 1. กำหนดค่า Secret Salt (ต้องเก็บรักษาสมุดรหัสนี้ไว้ในที่ปลอดภัย ไม่ commit ขึ้น GitHub)
SALT = "AMMS302_HEALTH_INFORMATICS_SALT_KEY_2026"

def pseudonymize_id(original_id):
    """
    แปลงรหัสเวชระเบียน (HN) เป็น Pseudonym Hash สั้น 16 หลักด้วย SHA-256 + Salt
    เพื่อป้องกัน Rainbow Table Attacks และการสืบค้นย้อนกลับโดยไม่มี Secret Key
    """
    salted_input = SALT + str(original_id)
    hash_object = hashlib.sha256(salted_input.encode('utf-8'))
    return hash_object.hexdigest()[:16]

# ทดสอบการแปลงรหัสผู้ป่วย HN-00001
raw_hn = "HN-00001"
hashed_hn = pseudonymize_id(raw_hn)

print(f"รหัสเดิม (Direct Identifier): {raw_hn}")
print(f"รหัสใหม่ (Pseudonymized Hash): {hashed_hn}")
# ผลลัพธ์: e9c08d5f3a21b84e (แปลงกลับไม่ได้ถ้าไม่มี Secret Salt)
Slide 10: Workshop De-Identification Pipeline AMMS 302 | Hour 2

เวิร์กช็อป: การสร้างท่อปกปิดตัวตนข้อมูลสุขภาพฉบับสมบูรณ์

นำชุดข้อมูลที่ผ่านการทำความสะอาดจากชั่วโมงที่ 1 (cleaned_patients_hour1.csv) มาผ่านกระบวนการ De-identification ทั้งหมดแล้วส่งออกไฟล์พร้อมใช้

💻 Python Script: full_deidentification_pipeline.py Ready to Copy-and-Paste
import pandas as pd
import hashlib

# 1. โหลดชุดข้อมูลที่ผ่านการทำความสะอาดแล้วจากชั่วโมงที่ 1
df = pd.read_csv('cleaned_patients_hour1.csv')

# 2. ฟังก์ชัน Salt-Hashing
SALT = "AMMS302_SECRET_SALT_KEY_2026"
def hash_patient_id(patient_id):
    return hashlib.sha256((SALT + str(patient_id)).encode('utf-8')).hexdigest()[:16]

# 3. แปลงรหัสประจำตัวด้วย Pseudonymization
df['patient_id_hash'] = df['patient_id'].apply(hash_patient_id)

# 4. ทำ Age Binning และใช้กฎ Age > 89 Rule
def bin_age(age):
    if age >= 90:
        return '90+' # HIPAA Outlier Rule
    group_start = (age // 10) * 10
    return f"{group_start}-{group_start+9}"

df['age_group'] = df['age'].apply(bin_age)

# 5. ลบข้อมูลระบุตัวตนโดยตรง (Direct Identifiers) ทั้งหมดตาม HIPAA Safe Harbor
df_deidentified = df.drop(columns=['patient_id', 'patient_name', 'national_id', 'age'])

# 6. ส่งออกชุดข้อมูล De-identified พร้อมสำหรับนำไปออกแบบฐานข้อมูลในชั่วโมงที่ 3
df_deidentified.to_csv('deidentified_patients_hour2.csv', index=False)

print("✅ สร้างไฟล์ deidentified_patients_hour2.csv เรียบร้อยแล้ว!")
print(df_deidentified[['patient_id_hash', 'age_group', 'gender', 'sbp', 'hba1c']].head(3))
AMMS 302 | Hour 3 (Database Architecture & 3NF)

การออกแบบสถาปัตยกรรมฐานข้อมูลเชิงสัมพันธ์ และมาตรฐาน 3NF

การแปลงชุดข้อมูลแบน (Flat File) สู่ตารางฐานข้อมูลเชิงสัมพันธ์ในระดับ 3rd Normal Form (3NF)
การเขียนสคีมา SQL DDL พร้อมกฎควบคุมความสมบูรณ์ของข้อมูล (Integrity Constraints) บน SQLite

🎯 สารบัญและลำดับการเรียนรู้ชั่วโมงที่ 3

💡 คลิกปุ่มเพื่อข้ามไปยังสไลด์ที่ต้องการ หรือใช้ปุ่มลูกศรควบคุมการนำเสนอ

Slide 12: OLTP vs OLAP & Normalization AMMS 302 | Hour 3

สถาปัตยกรรมฐานข้อมูลและขั้นตอนการแตกตาราง (Normalization 1NF ➔ 3NF)

การนำตารางแบน (Flat Spreadsheet) มาจัดเก็บโดยตรงทำให้เกิด **Data Redundancy** และข้อผิดพลาดขณะปรับปรุงข้อมูล (Insertion, Update, Deletion Anomalies)

🔄 OLTP vs. OLAP ในระบบสุขภาพ

  • OLTP (Online Transaction Processing): ระบบ Hospital Information System (HIS) สำหรับการลงบันทึกเวชระเบียนประจำวัน เน้นความรวดเร็วในการ C.R.U.D. และต้องผ่าน Normalization (3NF) เพื่อป้องกันข้อมูลขัดแย้ง
  • OLAP (Online Analytical Processing): ระบบคลังข้อมูล (Data Warehouse / DuckDB) สำหรับวิเคราะห์เชิงลึก เน้นการอ่านข้อมูลปริมาณมาก (Read-Optimized)

📐 ลำดับการทำ Normalization (1NF ➔ 3NF)

  • 1NF (First Normal Form): ทุกเซลล์ต้องเป็นค่าเดี่ยว (Atomic Values) ไม่มี Repeating Groups
  • 2NF (Second Normal Form): อยู่ใน 1NF และทุกคอลัมน์ที่ไม่ใช่ PK ต้องขึ้นตรงกับ Primary Key แบบเต็มสูบ (Full Functional Dependency)
  • 3NF (Third Normal Form): อยู่ใน 2NF และไม่มีการพึ่งพาแบบส่งผ่าน (No Transitive Dependency: ข้อมูลที่ไม่ใช่ Key ห้ามขึ้นกับข้อมูลที่ไม่ใช่ Key ด้วยกัน)
⚠️ ตัวอย่าง Anomaly: หากเก็บชื่อยาและขนาดไว้ในตารางผู้ป่วยโดยตรง เมื่อมีการแก้ไขชื่อยา แพทย์ต้องตามแก้ทุกแถวที่มีผู้ป่วยรับยานั้น (Update Anomaly)
Slide 13: ER Diagram & Relational Schema AMMS 302 | Hour 3

โครงสร้างสคีมาฐานข้อมูลเชิงสัมพันธ์ 3NF ของระบบ CDSS

เราจะทำการแตกตารางแบนออกเป็น **3 ตารางหลัก** เชื่อมโยงด้วยความสัมพันธ์แบบ One-to-Many (1:N) ผ่าน Foreign Key

📋 Table 1: patients Demographics
🔑 patient_id_hash (PK)
• age_group (TEXT)
• gender (TEXT)

เก็บข้อมูลสถิติประชากรที่ทำ De-identification แล้ว

🩺 Table 2: vitals_labs Clinical Vitals
🔑 lab_id (PK, AUTO)
🔗 patient_id_hash (FK)
• sbp (REAL)
• hba1c (REAL)
• lab_date (TEXT, ISO)

เก็บประวัติสัญญาณชีพและผลแล็บตามลำดับเวลา

💊 Table 3: prescriptions Medications
🔑 rx_id (PK, AUTO)
🔗 patient_id_hash (FK)
• drug_name (TEXT)
• rx_date (TEXT, ISO)

เก็บข้อมูลประวัติการสั่งจ่ายยารักษาโรคเรื้อรัง

1 patients ➔ N vitals_labs | 1 patients ➔ N prescriptions
Slide 14: SQL DDL Execution AMMS 302 | Hour 3

ขั้นตอนที่ 1: การเขียนสคีมา SQL DDL และกำหนด Integrity Constraints

กำหนดกฎการควบคุมความถูกต้องของข้อมูลผ่านทาง CHECK, NOT NULL และการบังคับใช้ Foreign Keys

💻 SQL DDL Script: schema.sql Ready to Copy-and-Paste
-- 1. บังคับใช้ Foreign Key Constraint ใน SQLite (ต้องเปิดทุกครั้งที่เชื่อมต่อ)
PRAGMA foreign_keys = ON;

-- 2. สร้างตารางประชากรผู้ป่วย (patients)
CREATE TABLE IF NOT EXISTS patients (
    patient_id_hash TEXT PRIMARY KEY,
    age_group TEXT NOT NULL,
    gender TEXT CHECK(gender IN ('M', 'F'))
);

-- 3. สร้างตารางสัญญาณชีพและผลแล็บ (vitals_labs) พร้อม Domain Constraints
CREATE TABLE IF NOT EXISTS vitals_labs (
    lab_id INTEGER PRIMARY KEY AUTOINCREMENT,
    patient_id_hash TEXT NOT NULL,
    sbp REAL CHECK(sbp BETWEEN 50 AND 300),
    hba1c REAL CHECK(hba1c BETWEEN 3.0 AND 20.0),
    lab_date TEXT NOT NULL,
    FOREIGN KEY (patient_id_hash) REFERENCES patients(patient_id_hash) ON DELETE CASCADE
);

-- 4. สร้างตารางประวัติการสั่งจ่ายยา (prescriptions)
CREATE TABLE IF NOT EXISTS prescriptions (
    rx_id INTEGER PRIMARY KEY AUTOINCREMENT,
    patient_id_hash TEXT NOT NULL,
    drug_name TEXT NOT NULL,
    rx_date TEXT NOT NULL,
    FOREIGN KEY (patient_id_hash) REFERENCES patients(patient_id_hash) ON DELETE CASCADE
);
Slide 15: Workshop DB Creation AMMS 302 | Hour 3

เวิร์กช็อป: สคริปต์ Python สร้างฐานข้อมูล `health_cdss.db` และนำเข้าข้อมูล

สร้างฐานข้อมูล SQLite บันทึกสคีมา DDL และย้ายข้อมูลจาก deidentified_patients_hour2.csv ลงตารางตามบรรทัดฐาน 3NF

💻 Python Script: build_sqlite_db.py Ready to Copy-and-Paste
import sqlite3
import pandas as pd

# 1. เชื่อมต่อฐานข้อมูล SQLite (จะสร้างไฟล์ health_cdss.db โดยอัตโนมัติ)
conn = sqlite3.connect('health_cdss.db')
cursor = conn.cursor()
cursor.execute("PRAGMA foreign_keys = ON;")

# 2. อ่านไฟล์ CSV จากชั่วโมงที่ 2
df = pd.read_csv('deidentified_patients_hour2.csv')

# 3. นำเข้าข้อมูลตาราง patients (เฉพาะคอลัมน์ประชากรศาสตร์)
df_patients = df[['patient_id_hash', 'age_group', 'gender']].drop_duplicates()
df_patients.to_sql('patients', conn, if_exists='append', index=False)

# 4. นำเข้าข้อมูลตาราง vitals_labs (แยกเฉพาะข้อมูลผลการตรวจ)
df_vitals = df[['patient_id_hash', 'sbp', 'hba1c', 'admittime']].copy()
df_vitals.rename(columns={'admittime': 'lab_date'}, inplace=True)
df_vitals.to_sql('vitals_labs', conn, if_exists='append', index=False)

# 5. จำลองประวัติการสั่งจ่ายยาสำหรับผู้ป่วยที่มีค่า SBP >= 140 (ตาราง prescriptions)
df_rx = df[df['sbp'] >= 140][[%'patient_id_hash', 'admittime']].copy()
df_rx['drug_name'] = 'Amlodipine 5mg'
df_rx.rename(columns={'admittime': 'rx_date'}, inplace=True)
df_rx.to_sql('prescriptions', conn, if_exists='append', index=False)

conn.commit()
conn.close()
print("✅ สร้างและย้ายข้อมูลลงฐานข้อมูล health_cdss.db สำเร็จเรียบร้อยแล้ว!")
AMMS 302 | Hour 4 (SQL Engine & CDSS Logic)

การประมวลผลคำสั่ง SQL ขั้นสูง และระบบสนับสนุนการตัดสินใจทางคลินิก

การแปลงเกณฑ์เวชปฏิบัติสู่ตรรกะ SQL (CASE WHEN Risk Triage)
การตรวจหาช่องว่างการรักษาด้วย Anti-Join (NOT EXISTS) และการวิเคราะห์อนุกรมเวลาด้วย Window Functions

🎯 สารบัญและลำดับการเรียนรู้ชั่วโมงที่ 4

💡 คลิกปุ่มเพื่อข้ามไปยังสไลด์ที่ต้องการ หรือใช้ปุ่มลูกศรควบคุมการนำเสนอ

Slide 17: CDSS & CASE WHEN Logic AMMS 302 | Hour 4

ขั้นตอนที่ 1: การจำแนกชั้นความเสี่ยงด้วย `CASE WHEN` ตามแนวทางเวชปฏิบัติ

แปลงเกณฑ์ทางการแพทย์ (Thai Diabetes & Hypertension Guidelines) ให้เป็นตรรกะ SQL เพื่อจัดกลุ่มความเสี่ยงผู้ป่วยโดยอัตโนมัติ

💻 SQL Query: cdss_risk_triage.sql Ready to Copy-and-Paste
SELECT 
    patient_id_hash,
    sbp,
    hba1c,
    lab_date,
    CASE
        WHEN sbp >= 180 OR hba1c >= 9.0 THEN 'CRITICAL'   -- วิกฤต: เสี่ยงภาวะแทรกซ้อนเฉียบพลัน
        WHEN sbp >= 140 OR hba1c >= 7.0 THEN 'HIGH'       -- สูง: ควบคุมไม่ได้ตามเป้าหมาย
        WHEN sbp >= 120 OR hba1c >= 5.7 THEN 'MODERATE'   -- ปานกลาง: ระยะเสี่ยง (Pre-HT / Pre-DM)
        ELSE 'NORMAL'                                    -- ปกติ
    END AS risk_tier
FROM vitals_labs
ORDER BY sbp DESC;
Slide 18: Anti-Join Pattern AMMS 302 | Hour 4

ขั้นตอนที่ 2: การค้นหากลุ่มเสี่ยงวิกฤตที่ไม่ได้รับยาด้วย Anti-Join (`NOT EXISTS`)

ค้นหาผู้ป่วยที่มีค่าสัญญาณชีพวิกฤต (SBP >= 180) แต่ **"ไม่มีประวัติการสั่งจ่ายยา"** ในตาราง prescriptions (หลีกเลี่ยงกับดัก NULL ของ `NOT IN`)

💻 SQL Query: care_gap_anti_join.sql Ready to Copy-and-Paste
SELECT 
    v.patient_id_hash,
    v.sbp,
    v.hba1c,
    v.lab_date
FROM vitals_labs v
WHERE v.sbp >= 180
  -- รูปแบบ Anti-Join Pattern: ค้นหาผู้ป่วยที่ "ไม่พบข้อมูล" ในตารางใบสั่งยา
  AND NOT EXISTS (
      SELECT 1 
      FROM prescriptions p
      WHERE p.patient_id_hash = v.patient_id_hash
  );
⚠️
คำเตือนเรื่อง SQL Null Trap: ห้ามใช้ WHERE id NOT IN (SELECT id FROM ...) เพราะหากคอลัมน์ใน Subquery มีค่า NULL แม้แต่แถวเดียว ผลลัพธ์ทั้งหมดจะกลายเป็นเซตว่างทันที!
Slide 19: Window Functions AMMS 302 | Hour 4

ขั้นตอนที่ 3: การดึงสัญญาณชีพล่าสุดด้วย Window Functions (`ROW_NUMBER`)

ใช้ ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) เพื่อดึงเฉพาะผลแล็บบันทึกล่าสุดของผู้ป่วยแต่ละคน

💻 SQL Query: latest_vitals_window.sql Ready to Copy-and-Paste
-- ใช้ Common Table Expression (CTE) ร่วมกับ Window Function
WITH RankedVitals AS (
    SELECT 
        patient_id_hash,
        sbp,
        hba1c,
        lab_date,
        -- จัดลำดับประวัติตามวันที่ล่าสุดของผู้ป่วยแต่ละราย (rn = 1 คือพารามิเตอร์ล่าสุด)
        ROW_NUMBER() OVER (
            PARTITION BY patient_id_hash 
            ORDER BY lab_date DESC
        ) AS rn
    FROM vitals_labs
)
SELECT 
    patient_id_hash,
    sbp,
    hba1c,
    lab_date AS latest_visit_date
FROM RankedVitals
WHERE rn = 1;
Slide 20: Workshop CDSS Engine Script AMMS 302 | Hour 4

เวิร์กช็อป: สคริปต์ Python รันประมวลผล CDSS Engine จากฐานข้อมูล `health_cdss.db`

ประมวลผลการจัดกลุ่มความเสี่ยง ค้นหากลุ่มเสี่ยงวิกฤตที่ขาดการรับยา และส่งออกรายงานเฝ้าระวัง critical_triage_report.csv

💻 Python Script: run_cdss_engine.py Ready to Copy-and-Paste
import sqlite3
import pandas as pd

# 1. เชื่อมต่อฐานข้อมูล SQLite ที่สร้างไว้จากชั่วโมงที่ 3
conn = sqlite3.connect('health_cdss.db')

# 2. คำสั่ง SQL รวมพลัง: CTE + Window Function + Anti-Join + CASE WHEN Risk Triage
cdss_query = """
WITH LatestVitals AS (
    SELECT 
        patient_id_hash, sbp, hba1c, lab_date,
        ROW_NUMBER() OVER (PARTITION BY patient_id_hash ORDER BY lab_date DESC) as rn
    FROM vitals_labs
)
SELECT 
    v.patient_id_hash,
    p.age_group,
    p.gender,
    v.sbp,
    v.hba1c,
    v.lab_date,
    CASE 
        WHEN v.sbp >= 180 OR v.hba1c >= 9.0 THEN 'CRITICAL'
        WHEN v.sbp >= 140 OR v.hba1c >= 7.0 THEN 'HIGH'
        ELSE 'MODERATE'
    END AS risk_tier
FROM LatestVitals v
JOIN patients p ON v.patient_id_hash = p.patient_id_hash
WHERE v.rn = 1
  AND NOT EXISTS (
      SELECT 1 FROM prescriptions rx 
      WHERE rx.patient_id_hash = v.patient_id_hash
  )
ORDER BY v.sbp DESC;
"""

# 3. ดึงผลลัพธ์ใส่ Pandas DataFrame
df_triage = pd.read_sql_query(cdss_query, conn)
conn.close()

# 4. ส่งออกรายงานผู้ป่วยเสี่ยงวิกฤตขาดการรับยา (Care Gap Report)
df_triage.to_csv('critical_triage_report.csv', index=False)

print("🚨 รายงานผู้ป่วยกลุ่มวิกฤตขาดการรับยา (Care Gap Triage Queue):")
print(df_triage.head(5))
print(f"\n✅ ส่งออกไฟล์ critical_triage_report.csv เรียบร้อยแล้ว (จำนวน {len(df_triage)} ราย)")
AMMS 302 | Hour 5 (Django Web App & Frontend)

การพัฒนาเว็บพอร์ทัลสุขภาพด้วย Django, Tailwind CSS และ Alpine.js

การเชื่อมต่อฐานข้อมูล health_cdss.db เข้าสู่สถาปัตยกรรม Django MVT Pattern
การสร้างหน้าคัดกรองตอบสนองเรียลไทม์ และแดชบอร์ดเฝ้าระวังกลุ่มเสี่ยงสำหรับทีมแพทย์

🎯 สารบัญและลำดับการเรียนรู้ชั่วโมงที่ 5

💡 คลิกปุ่มเพื่อข้ามไปยังสไลด์ที่ต้องการ หรือใช้ปุ่มลูกศรควบคุมการนำเสนอ

Slide 22: Django MVT & Setup AMMS 302 | Hour 5

ขั้นตอนที่ 1: สถาปัตยกรรม Django MVT และการตั้งค่าโปรเจกต์เว็บ

Django ใช้สถาปัตยกรรม **Model-View-Template (MVT)** ในการแยกส่วนจัดการข้อมูล (Model), ตรรกะประมวลผล (View) และส่วนแสดงผล (Template)

💻 Terminal Commands: django_setup.sh Ready to Copy-and-Paste
# 1. ติดตั้ง Django ในสภาพแวดล้อมทำงาน
pip install django pandas

# 2. สร้างโปรเจกต์ Django ใหม่ชื่อ cdss_portal
django-admin startproject cdss_portal .

# 3. สร้างแอปพลิเคชันย่อยชื่อ surveillance
python manage.py startapp surveillance

# 4. กำหนดค่าใน cdss_portal/settings.py (เชื่อมต่อฐานข้อมูล health_cdss.db ที่สร้างไว้)
# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': BASE_DIR / 'health_cdss.db',
#     }
# }

# 5. ทดสอบรันเซิร์ฟเวอร์การพัฒนา
python manage.py runserver
Slide 23: Django Views & Logic AMMS 302 | Hour 5

ขั้นตอนที่ 2: การเขียน Django Views ดึงข้อมูลด้วย SQL/Pandas Engine

เขียนฟังก์ชัน View ใน surveillance/views.py เพื่อรันคำสั่ง SQL CDSS และส่งข้อมูลไปยัง HTML Template

💻 Python Script: surveillance/views.py Ready to Copy-and-Paste
import sqlite3
import pandas as pd
from django.shortcuts import render

def clinician_dashboard(request):
    """View สำหรับประมวลผลและแสดงรายการผู้ป่วยกลุ่มเสี่ยงวิกฤตที่ขาดการรับยา"""
    conn = sqlite3.connect('health_cdss.db')
    
    cdss_query = """
    WITH LatestVitals AS (
        SELECT patient_id_hash, sbp, hba1c, lab_date,
               ROW_NUMBER() OVER (PARTITION BY patient_id_hash ORDER BY lab_date DESC) as rn
        FROM vitals_labs
    )
    SELECT v.patient_id_hash, p.age_group, p.gender, v.sbp, v.hba1c, v.lab_date,
           CASE 
               WHEN v.sbp >= 180 OR v.hba1c >= 9.0 THEN 'CRITICAL'
               WHEN v.sbp >= 140 OR v.hba1c >= 7.0 THEN 'HIGH'
               ELSE 'MODERATE'
           END AS risk_tier
    FROM LatestVitals v
    JOIN patients p ON v.patient_id_hash = p.patient_id_hash
    WHERE v.rn = 1 AND NOT EXISTS (
        SELECT 1 FROM prescriptions rx WHERE rx.patient_id_hash = v.patient_id_hash
    )
    ORDER BY v.sbp DESC;
    """
    
    df_triage = pd.read_sql_query(cdss_query, conn)
    conn.close()
    
    # แปลง DataFrame เป็น List of Dictionaries เพื่อส่งให้ Template
    patients_list = df_triage.to_dict('records')
    
    context = {
        'patients': patients_list,
        'critical_count': len(df_triage[df_triage['risk_tier'] == 'CRITICAL']),
        'total_care_gaps': len(df_triage)
    }
    return render(request, 'surveillance/dashboard.html', context)
Slide 24: Alpine.js Interactive Form AMMS 302 | Hour 5

ขั้นตอนที่ 3: ฟอร์มคัดกรองตอบสนองเรียลไทม์ฝั่งผู้ป่วยด้วย Alpine.js

ใช้ **Alpine.js** ในการประเมินระดับความเสี่ยงล่วงหน้าแบบ Client-Side Reactive ก่อนการส่งบันทึกลงฐานข้อมูล

💻 HTML Template: patient_screening.html Ready to Copy-and-Paste
<!-- โหลด Alpine.js และ Tailwind CSS ผ่าน CDN ใน Template -->
<script src="[suspicious link removed]"></script>
<script defer src="[suspicious link removed]"></script>

<!-- Alpine.js Reactive Component -->
<div x-data="{ sbp: 120, hba1c: 5.5, get riskTier() {
    if (this.sbp >= 180 || this.hba1c >= 9.0) return 'CRITICAL';
    if (this.sbp >= 140 || this.hba1c >= 7.0) return 'HIGH';
    if (this.sbp >= 120 || this.hba1c >= 5.7) return 'MODERATE';
    return 'NORMAL';
}}" class="p-6 bg-slate-900 text-white rounded-2xl max-w-md mx-auto">

  <h3 class="text-lg font-bold text-yellow-300 mb-4">🩺 แบบคัดกรองความเสี่ยงสุขภาพส่วนบุคคล</h3>
  
  <label class="block text-xs mb-1">ความดันโลหิตตัวบน (SBP mmHg):</label>
  <input type="number" x-model="sbp" class="w-full p-2 bg-slate-800 rounded mb-3 text-white border border-slate-700">

  <label class="block text-xs mb-1">น้ำตาลสะสมในเลือด (HbA1c %):</label>
  <input type="number" step="0.1" x-model="hba1c" class="w-full p-2 bg-slate-800 rounded mb-4 text-white border border-slate-700">

  <!-- แสดงผลการประเมินความเสี่ยงแบบเรียลไทม์เปลี่ยนสีตามเงื่อนไข -->
  <div class="p-3 rounded-xl text-center font-bold text-xs"
       :class="{
         'bg-rose-600 text-white': riskTier === 'CRITICAL',
         'bg-amber-500 text-black': riskTier === 'HIGH',
         'bg-blue-500 text-white': riskTier === 'MODERATE',
         'bg-emerald-600 text-white': riskTier === 'NORMAL'
       }">
    ผลการประเมินความเสี่ยง: <span x-text="riskTier"></span>
  </div>
</div>
Slide 25: Workshop Clinician Dashboard UI AMMS 302 | Hour 5

เวิร์กช็อป: แม่แบบ HTML แดชบอร์ดเฝ้าระวังกลุ่มเสี่ยง (Clinician Dashboard)

แม่แบบ surveillance/templates/surveillance/dashboard.html ตกแต่งด้วย Tailwind CSS แสดงตาราง Triage Queue

💻 HTML Template: dashboard.html Ready to Copy-and-Paste
<div class="p-6 bg-slate-950 min-h-screen text-slate-100 font-sans">
  <h2 class="text-xl font-bold text-cyan-300 mb-4">🩺 แดชบอร์ดเฝ้าระวังผู้ป่วยกลุ่มเสี่ยงวิกฤตที่ขาดการรับยา (Care Gap Surveillance)</h2>
  
  <!-- Metric Summary Cards -->
  <div class="grid grid-cols-2 gap-4 mb-6">
    <div class="bg-rose-950/80 p-4 rounded-xl border border-rose-800">
      <div class="text-xs text-rose-300">เคสวิกฤตที่ต้องได้รับการติดต่อด่วน (CRITICAL)</div>
      <div class="text-2xl font-bold text-rose-200">{{ critical_count }} ราย</div>
    </div>
    <div class="bg-amber-950/80 p-4 rounded-xl border border-amber-800">
      <div class="text-xs text-amber-300">รวมผู้ป่วยกลุ่มเสี่ยงที่ขาดการรับยา (Total Care Gaps)</div>
      <div class="text-2xl font-bold text-amber-200">{{ total_care_gaps }} ราย</div>
    </div>
  </div>

  <!-- Triage Table -->
  <table class="w-full text-left text-xs bg-slate-900 border border-slate-800 rounded-xl overflow-hidden">
    <thead class="bg-slate-800 text-slate-300">
      <tr><th class="p-3">Patient Hash ID</th><th>Age Group</th><th>SBP (mmHg)</th><th>HbA1c (%)</th><th>Risk Tier</th></tr>
    </thead>
    <tbody>
      {% for p in patients %}
      <tr class="border-b border-slate-800 hover:bg-slate-800/50">
        <td class="p-3 font-mono text-cyan-300">{{ p.patient_id_hash }}</td>
        <td>{{ p.age_group }}</td><td>{{ p.sbp }}</td><td>{{ p.hba1c }}</td>
        <td>
          <span class="px-2 py-1 rounded text-[10px] font-bold {% if p.risk_tier == 'CRITICAL' %}bg-rose-600 text-white{% else %}bg-amber-500 text-black{% endif %}">{{ p.risk_tier }}</span>
        </td>
      </tr>
      {% endfor %}
    </tbody>
  </table>
</div>
AMMS 302 | Hour 6 (Cloud Deployment & Governance)

การติดตั้งแอปพลิเคชันบน Cloud และการตรวจสอบธรรมาภิบาลข้อมูล

การล็อกสภาพแวดล้อมด้วย uv.lock, การติดตั้งเว็บแอปพลิเคชัน Django บน PythonAnywhere ผ่าน WSGI
และการประเมินความปลอดภัยของผู้ป่วยตามกรอบ 3P Safety Audit

🎯 สารบัญและลำดับการเรียนรู้ชั่วโมงที่ 6

💡 คลิกปุ่มเพื่อข้ามไปยังสไลด์ที่ต้องการ หรือใช้ปุ่มลูกศรควบคุมการนำเสนอ

Slide 27: Reproducibility & uv.lock AMMS 302 | Hour 6

ขั้นตอนที่ 1: ความสามารถในการผลิตซ้ำ (Reproducibility) และการล็อกสภาพแวดล้อมด้วย `uv`

ระบบสุขภาพที่ได้มาตรฐานต้องรับประกันว่า โค้ดประมวลผลและเว็บแอปพลิเคชันจะให้ผลลัพธ์เดียวกันเสมอเมื่อนำไปรันบนเครื่องอื่น โดยใช้ uv ล็อกเวอร์ชันไลบรารี

💻 Terminal Commands: uv_environment_locking.sh Ready to Copy-and-Paste
# 1. ล็อกเวอร์ชันของแพ็กเกจทั้งหมดในโปรเจกต์ลงไฟล์ requirements.txt หรือ uv.lock
uv pip compile pyproject.toml -o requirements.txt

# 2. ตรวจสอบเนื้อหาใน requirements.txt (จะระบุ Exact Pinning เช่น django==5.0.2)
cat requirements.txt

# 3. ส่งโค้ดและไฟล์ตั้งค่าขึ้น Git Repository
git add .
git commit -m "Production ready: Locked dependencies with uv and SQLite schema"
git push origin main
Slide 28: PythonAnywhere WSGI Setup AMMS 302 | Hour 6

ขั้นตอนที่ 2: การติดตั้งและตั้งค่าโปรเจกต์บน PythonAnywhere ผ่าน WSGI

นำโปรเจกต์จาก GitHub ลงบนคลาวด์ [PythonAnywhere]([suspicious link removed]) สร้าง Virtual Environment และเชื่อมต่อไฟล์ WSGI

💻 PythonAnywhere Bash Console Ready to Copy-and-Paste
# 1. Clone โปรเจกต์ลงบน PythonAnywhere Bash Console
git clone [suspicious link removed]
cd health_cdss_portal

# 2. สร้างและเปิดใช้งาน Virtual Environment สำหรับ Python 3.12
mkvirtualenv --python=/usr/bin/python3.12 cdss-venv
pip install -r requirements.txt

# 3. แก้ไขไฟล์ตั้งค่า WSGI บน Web Tab ของ PythonAnywhere (/var/www/[suspicious link removed])
import os
import sys

path = '/home/yourusername/health_cdss_portal'
if path not in sys.path:
    sys.path.append(path)

os.environ['DJANGO_SETTINGS_MODULE'] = 'cdss_portal.settings'

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
Slide 29: Production & Static Files AMMS 302 | Hour 6

ขั้นตอนที่ 3: การรวบรวมไฟล์ Static (`collectstatic`) และการตั้งค่า Production

ตั้งค่า STATIC_ROOT เพื่อรวบรวมไฟล์ CSS/JS ทั้งหมด รัน Migration และปิดการทำงานของ `DEBUG` เพื่อความปลอดภัย

💻 PythonAnywhere Console: Production Setup Ready to Copy-and-Paste
# 1. กำหนดค่าใน cdss_portal/[suspicious link removed]
# DEBUG = False
# ALLOWED_HOSTS = ['[suspicious link removed]']
# STATIC_ROOT = BASE_DIR / 'staticfiles'

# 2. คำสั่งรวบรวมไฟล์ Static สำหรับการขึ้นระบบจริง
python [suspicious link removed] collectstatic --noinput

# 3. ตรวจสอบความถูกต้องของตารางฐานข้อมูล SQLite บน Cloud
python [suspicious link removed] migrate

# 4. ตั้งค่า Static Files Mapping บนหน้าเว็บ PythonAnywhere
# URL: /static/  ➔  Directory: /home/yourusername/health_cdss_portal/staticfiles/

# 5. กดปุ่ม "Reload" บน Web Tab เพื่อเปิดใช้งานเว็บแอปพลิเคชันจริง!
Slide 30: 3P Safety Audit & Wrap-up AMMS 302 | Hour 6

ขั้นตอนสุดท้าย: การประเมินความปลอดภัย 3P Safety Audit และบทสรุปภาพรวม

ตรวจสอบความสมบูรณ์ของระบบสุขภาพดิจิทัลตามกรอบความปลอดภัย 3 ด้าน ก่อนนำเสนอผลงานฉบับสมบูรณ์

1. Patient Safety (ความปลอดภัยผู้ป่วย)

ระบบ CDSS มีเกณฑ์ Cut-off แม่นยำตามแนวทางเวชปฏิบัติ แจ้งเตือน Care Gap ผู้ป่วยวิกฤตที่ขาดการรับยาได้ถูกต้อง ลดความผิดพลาดทางการแพทย์

2. Personnel Safety (ความปลอดภัยเจ้าหน้าที่)

ออกแบบแดชบอร์ด Triage Queue ให้อ่านง่าย ลดภาระงานแบบภาระทางสมอง (Cognitive Overload) ของบุคลากรทางการแพทย์

3. Public Safety & PDPA (ความปลอดภัยสาธารณะ)

ปกปิดตัวตนด้วย Salt-Hashing และ HIPAA Safe Harbor ป้องกันข้อมูลรั่วไหล สอดคล้องกับ พ.ร.บ. คุ้มครองข้อมูลส่วนบุคคล พ.ศ. 2562

🏆 ความสำเร็จการสร้างท่อส่งข้อมูลสุขภาพตลอด 6 ชั่วโมง (End-to-End Pipeline Completed!)
Hour 1: PySynthea
Hour 2: PDPA/Salt-Hash
Hour 3: SQLite 3NF
Hour 4: SQL CDSS
Hour 5: Django Web UI
Hour 6: Cloud Live