รายวิชา ปฏิบัติการสารสนเทศด้านสุขภาพ (อจวพ ๓๐๒)
หลักสูตรวิทยาศาสตรบัณฑิต สาขาวิชาวิทยาศาสตร์การแพทย์ โครงการจัดตั้งวิทยาเขตอำนาจเจริญ มหาวิทยาลัยมหิดล
💡 คลิกปุ่มเพื่อข้ามไปยังสไลด์ที่ต้องการ หรือใช้ปุ่มลูกศรควบคุมการนำเสนอ
ตลอดเวิร์กช็อป 6 ชั่วโมง นิสิตจะได้ลงมือสร้างระบบ **Chronic Disease Surveillance & CDSS** ฉบับสมบูรณ์ที่ปรับใช้ได้กับทุกโครงงานกลุ่ม ตั้งแต่การประมวลผลข้อมูลดิบจนถึงการขึ้นระบบเว็บแอปพลิเคชันบน Cloud (PythonAnywhere)
ฟอร์มแบบ Interactive (Alpine.js) สำหรับประเมินความเสี่ยงเบาหวานและความดันโลหิตสูงล่วงหน้าแบบเรียลไทม์ ก่อนการบันทึกลงฐานข้อมูล
ระบบจัดอันดับกลุ่มเสี่ยงวิกฤต (Triage Queue) ค้นหาผู้ป่วยค่าแล็บสูงวิกฤตแต่ **"ยังไม่ได้รับยา"** ด้วยคำสั่ง Anti-Join ใน SQL
ใช้งาน PySynthea Framework ร่วมกับไฟล์โครงสร้างประชากรไทย thai_demographics.csv เพื่อจำลองเวชระเบียนผู้ป่วยตามจังหวัดต่างๆ ในประเทศไทย
# 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!")
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 ไทย)!")
ใช้คำสั่งพื้นฐานใน Pandas เพื่อตรวจสอบโครงสร้างชุดข้อมูล มิติข้อมูล (Shape) และนับจำนวนค่าว่าง (Null Values)
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())
แปลงข้อความวันที่เป็น `datetime` เพื่อตรวจสอบเงื่อนไขความขัดแย้งเชิงเวลา (`dischtime >= admittime`) และกรองค่าที่เป็นไปไม่ได้ออก
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)}")
ใช้ค่าพิสัยระหว่างควอร์ไทล์ (IQR) ในการตัด Outliers ทางสถิติ และส่งออกไฟล์ CSV สะอาดพร้อมสำหรับขั้นตอนการคุ้มครองความเป็นส่วนตัว (PDPA) ในชั่วโมงถัดไป
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)} แถว)")
การประมวลผลข้อมูลเวชระเบียนตามมาตรฐาน PDPA พ.ศ. 2562, HIPAA Safe Harbor,
และการสร้างท่อปกปิดตัวตนด้วยเทคนิค Cryptographic Salt-Hashing
💡 คลิกปุ่มเพื่อข้ามไปยังสไลด์ที่ต้องการ หรือใช้ปุ่มลูกศรควบคุมการนำเสนอ
ข้อมูลสุขภาพจัดเป็น **ข้อมูลส่วนบุคคลอ่อนไหว (Sensitive Personal Data)** ซึ่งต้องมีมาตรการคุ้มครองและกำกับดูแลขั้นสูงสุดตามกฎหมายทั้งในระดับประเทศและสากล
ตามมาตรฐาน HIPAA การนำชุดข้อมูลสุขภาพไปใช้ในการวิจัยหรือพัฒนาระบบ AI จะต้องทำการลบหรือแปลงข้อมูลระบุตัวตนทั้ง 18 รายการดังต่อไปนี้:
'90+'
Linkage Attack (Sweeney, 1997): แม้จะลบชื่อออก แต่เพียงใช้ รหัสไปรษณีย์ + วันเกิด + เพศ สามารถระบุตัวตนประชากรในสหรัฐฯ ได้ถูกต้องถึง **87%** เมื่อนำไปเทียบตารางทะเบียนราษฎร์
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)
นำชุดข้อมูลที่ผ่านการทำความสะอาดจากชั่วโมงที่ 1 (cleaned_patients_hour1.csv) มาผ่านกระบวนการ De-identification ทั้งหมดแล้วส่งออกไฟล์พร้อมใช้
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))
การแปลงชุดข้อมูลแบน (Flat File) สู่ตารางฐานข้อมูลเชิงสัมพันธ์ในระดับ 3rd Normal Form (3NF)
การเขียนสคีมา SQL DDL พร้อมกฎควบคุมความสมบูรณ์ของข้อมูล (Integrity Constraints) บน SQLite
💡 คลิกปุ่มเพื่อข้ามไปยังสไลด์ที่ต้องการ หรือใช้ปุ่มลูกศรควบคุมการนำเสนอ
การนำตารางแบน (Flat Spreadsheet) มาจัดเก็บโดยตรงทำให้เกิด **Data Redundancy** และข้อผิดพลาดขณะปรับปรุงข้อมูล (Insertion, Update, Deletion Anomalies)
เราจะทำการแตกตารางแบนออกเป็น **3 ตารางหลัก** เชื่อมโยงด้วยความสัมพันธ์แบบ One-to-Many (1:N) ผ่าน Foreign Key
เก็บข้อมูลสถิติประชากรที่ทำ De-identification แล้ว
เก็บประวัติสัญญาณชีพและผลแล็บตามลำดับเวลา
เก็บข้อมูลประวัติการสั่งจ่ายยารักษาโรคเรื้อรัง
กำหนดกฎการควบคุมความถูกต้องของข้อมูลผ่านทาง CHECK, NOT NULL และการบังคับใช้ Foreign Keys
-- 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
);
สร้างฐานข้อมูล SQLite บันทึกสคีมา DDL และย้ายข้อมูลจาก deidentified_patients_hour2.csv ลงตารางตามบรรทัดฐาน 3NF
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 สำเร็จเรียบร้อยแล้ว!")
การแปลงเกณฑ์เวชปฏิบัติสู่ตรรกะ SQL (CASE WHEN Risk Triage)
การตรวจหาช่องว่างการรักษาด้วย Anti-Join (NOT EXISTS) และการวิเคราะห์อนุกรมเวลาด้วย Window Functions
💡 คลิกปุ่มเพื่อข้ามไปยังสไลด์ที่ต้องการ หรือใช้ปุ่มลูกศรควบคุมการนำเสนอ
แปลงเกณฑ์ทางการแพทย์ (Thai Diabetes & Hypertension Guidelines) ให้เป็นตรรกะ SQL เพื่อจัดกลุ่มความเสี่ยงผู้ป่วยโดยอัตโนมัติ
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;
ค้นหาผู้ป่วยที่มีค่าสัญญาณชีพวิกฤต (SBP >= 180) แต่ **"ไม่มีประวัติการสั่งจ่ายยา"** ในตาราง prescriptions (หลีกเลี่ยงกับดัก NULL ของ `NOT IN`)
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
);
WHERE id NOT IN (SELECT id FROM ...) เพราะหากคอลัมน์ใน Subquery มีค่า NULL แม้แต่แถวเดียว ผลลัพธ์ทั้งหมดจะกลายเป็นเซตว่างทันที!
ใช้ ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) เพื่อดึงเฉพาะผลแล็บบันทึกล่าสุดของผู้ป่วยแต่ละคน
-- ใช้ 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;
ประมวลผลการจัดกลุ่มความเสี่ยง ค้นหากลุ่มเสี่ยงวิกฤตที่ขาดการรับยา และส่งออกรายงานเฝ้าระวัง critical_triage_report.csv
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)} ราย)")
การเชื่อมต่อฐานข้อมูล health_cdss.db เข้าสู่สถาปัตยกรรม Django MVT Pattern
การสร้างหน้าคัดกรองตอบสนองเรียลไทม์ และแดชบอร์ดเฝ้าระวังกลุ่มเสี่ยงสำหรับทีมแพทย์
💡 คลิกปุ่มเพื่อข้ามไปยังสไลด์ที่ต้องการ หรือใช้ปุ่มลูกศรควบคุมการนำเสนอ
Django ใช้สถาปัตยกรรม **Model-View-Template (MVT)** ในการแยกส่วนจัดการข้อมูล (Model), ตรรกะประมวลผล (View) และส่วนแสดงผล (Template)
# 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
เขียนฟังก์ชัน View ใน surveillance/views.py เพื่อรันคำสั่ง SQL CDSS และส่งข้อมูลไปยัง HTML Template
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)
ใช้ **Alpine.js** ในการประเมินระดับความเสี่ยงล่วงหน้าแบบ Client-Side Reactive ก่อนการส่งบันทึกลงฐานข้อมูล
<!-- โหลด 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>
แม่แบบ surveillance/templates/surveillance/dashboard.html ตกแต่งด้วย Tailwind CSS แสดงตาราง Triage Queue
<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>
การล็อกสภาพแวดล้อมด้วย uv.lock, การติดตั้งเว็บแอปพลิเคชัน Django บน PythonAnywhere ผ่าน WSGI
และการประเมินความปลอดภัยของผู้ป่วยตามกรอบ 3P Safety Audit
💡 คลิกปุ่มเพื่อข้ามไปยังสไลด์ที่ต้องการ หรือใช้ปุ่มลูกศรควบคุมการนำเสนอ
ระบบสุขภาพที่ได้มาตรฐานต้องรับประกันว่า โค้ดประมวลผลและเว็บแอปพลิเคชันจะให้ผลลัพธ์เดียวกันเสมอเมื่อนำไปรันบนเครื่องอื่น โดยใช้ uv ล็อกเวอร์ชันไลบรารี
# 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
นำโปรเจกต์จาก GitHub ลงบนคลาวด์ [PythonAnywhere]([suspicious link removed]) สร้าง Virtual Environment และเชื่อมต่อไฟล์ WSGI
# 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()
ตั้งค่า STATIC_ROOT เพื่อรวบรวมไฟล์ CSS/JS ทั้งหมด รัน Migration และปิดการทำงานของ `DEBUG` เพื่อความปลอดภัย
# 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 เพื่อเปิดใช้งานเว็บแอปพลิเคชันจริง!
ตรวจสอบความสมบูรณ์ของระบบสุขภาพดิจิทัลตามกรอบความปลอดภัย 3 ด้าน ก่อนนำเสนอผลงานฉบับสมบูรณ์
ระบบ CDSS มีเกณฑ์ Cut-off แม่นยำตามแนวทางเวชปฏิบัติ แจ้งเตือน Care Gap ผู้ป่วยวิกฤตที่ขาดการรับยาได้ถูกต้อง ลดความผิดพลาดทางการแพทย์
ออกแบบแดชบอร์ด Triage Queue ให้อ่านง่าย ลดภาระงานแบบภาระทางสมอง (Cognitive Overload) ของบุคลากรทางการแพทย์
ปกปิดตัวตนด้วย Salt-Hashing และ HIPAA Safe Harbor ป้องกันข้อมูลรั่วไหล สอดคล้องกับ พ.ร.บ. คุ้มครองข้อมูลส่วนบุคคล พ.ศ. 2562