ortho_triage / app.py
Rathapoom's picture
Update app.py
0706381 verified
import streamlit as st
import pandas as pd
from sklearn.preprocessing import LabelEncoder
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
import math
import urllib.parse
import json
import re
import json
from datetime import datetime
import gspread
from oauth2client.service_account import ServiceAccountCredentials
# Load data and model (ปรับ path ตามที่คุณเก็บไฟล์ไว้ใน Colab)
@st.cache_resource
def load_data_and_model():
try:
data = pd.read_excel('ortho_traige_gen_symptom_expanded.xlsx')
except FileNotFoundError:
st.error("ไม่พบไฟล์ Excel กรุณาตรวจสอบว่าได้อัปโหลดไฟล์แล้ว")
return None, None, None, None
label_encoder = LabelEncoder()
data['clinic'] = label_encoder.fit_transform(data['clinic'])
model_save_path = 'finetune_saved_model'
try:
loaded_model = AutoModelForSequenceClassification.from_pretrained(model_save_path)
loaded_tokenizer = AutoTokenizer.from_pretrained(model_save_path)
except Exception as e:
st.error(f"เกิดข้อผิดพลาดในการโหลดโมเดล: {str(e)}")
return None, None, None, None
return data, label_encoder, loaded_model, loaded_tokenizer
data, label_encoder, loaded_model, loaded_tokenizer = load_data_and_model()
# Clinic to doctor mapping
clinic_doctor_mapping = {
'spine': ['จักรพล', 'วรายศ'],
'hand': ['อรอรุณ', 'เติมพงศ์'],
'sport': ['สิทธานต์', 'วสพล'],
'foot': ['ทินนาถ'],
'oncology': ['ธนพล', 'พิชยา'],
'osteoporosis': ['รัฐภูมิ'],
'arthroplasty': ['อรรถพร', 'ณัฐวุฒิ']
}
# Time slots mapping
doctor_time_slots = {
'รัฐภูมิ': ["วันพุธ: 9.00 - 16.00", "วันศุกร์: 9.00 - 12.00", "วันพฤหัสบดี: 16.00"],
'ทินนาถ': ["วันจันทร์: 9.00 - 12.00", "วันอังคาร: 9.00 - 16.00", "วันพุธ: 16.00", "วันอาทิตย์: 9.00 - 12.00"],
'สิทธานต์': ["วันจันทร์: 9.00 - 16.00", "วันพุธ: 9.00 - 12.00", "วันอังคาร: 16.00", "วันเสาร์: 9.00 - 12.00"],
'วสพล': ["วันพุธ: 13.00 - 16.00", "วันศุกร์: 9.00 - 16.00", "วันศุกร์: 16.00", "วันอาทิตย์: 9.00 - 12.00"],
'อรอรุณ': ["วันจันทร์: 13.00 - 16.00", "วันพุธ: 9.00 - 16.00", "วันอังคาร: 16.00", "วันเสาร์: 9.00 - 12.00"],
'เติมพงศ์': ["วันจันทร์: 9.00 - 16.00", "วันอังคาร: 9.00 - 12.00", "วันพฤหัสบดี: 16.00", "วันศุกร์: 16.00"],
'อรรถพร': ["วันจันทร์: 9.00 - 16.00", "วันอังคาร: 13.00 - 16.00", "วันอาทิตย์: 9.00 - 12.00"],
'ณัฐวุฒิ': ["วันอังคาร: 9.00 - 16.00", "วันศุกร์: 9.00 - 12.00", "วันพุธ: 16.00", "วันเสาร์: 9.00 - 12.00"],
'ธนพล': ["วันพุธ: 9.00 - 16.00", "วันจันทร์: 16.00"],
'พิชยา': ["วันอังคาร: 9.00 - 16.00", "วันพฤหัสบดี: 13.00 - 16.00", "วันอังคาร: 16.00", "วันพฤหัสบดี: 16.00", "วันเสาร์: 13.00 - 16.00"],
'จักรพล': ["วันพุธ: 13.00 - 16.00", "วันศุกร์: 9.00 - 16.00", "วันจันทร์: 16.00", "วันอาทิตย์: 9.00 - 12.00"],
'วรายศ': ["วันพฤหัสบดี: 13.00 - 16.00", "วันศุกร์: 9.00 - 16.00", "วันพฤหัสบดี: 16.00", "วันศุกร์: 16.00"]
}
clinic_name_to_key_mapping = {
'คลินิกกระดูกสันหลัง': 'spine',
'คลินิกมือและจุลศัลยกรรม': 'hand',
'คลินิกเวชศาสตร์การกีฬา และการส่องกล้องหัวไหล่': 'sport',
'คลินิกเท้าและข้อเท้า': 'foot',
'คลินิกมะเร็งกระดูกและเนื้อเยื่อ': 'oncology',
'คลินิกโรคกระดูกพรุนและกระดูกหัก': 'osteoporosis',
'คลินิกข้อเข่าและข้อสะโพกเทียม': 'arthroplasty'
}
@st.cache_data
def predict_clinic(age, symptom):
try:
loaded_model.eval()
inputs = loaded_tokenizer(symptom, return_tensors="pt", truncation=True, padding=True, max_length=128)
with torch.no_grad():
outputs = loaded_model(**inputs)
probabilities = torch.nn.functional.softmax(outputs.logits, dim=1)
predicted_class = torch.argmax(probabilities, dim=1).item()
predicted_label = label_encoder.inverse_transform([predicted_class])[0]
probability = probabilities[0][predicted_class].item()
clinic_mapping = {
'spine': 'คลินิกกระดูกสันหลัง',
'hand': 'คลินิกมือและจุลศัลยกรรม',
'sport': 'คลินิกเวชศาสตร์การกีฬา และการส่องกล้องหัวไหล่',
'foot': 'คลินิกเท้าและข้อเท้า',
'oncology': 'คลินิกมะเร็งกระดูกและเนื้อเยื่อ',
'osteoporosis': 'คลินิกโรคกระดูกพรุนและกระดูกหัก',
'arthroplasty': 'คลินิกข้อเข่าและข้อสะโพกเทียม'
}
if predicted_label == 'knee':
if age < 50:
predicted_label = 'sport'
else:
predicted_label = 'arthroplasty'
clinic_name = clinic_mapping.get(predicted_label, None)
clinic_key = clinic_name_to_key_mapping.get(clinic_name, None)
if probability < 0.90 or clinic_name is None or clinic_key is None:
return "ไม่พบคลินิกเฉพาะทางที่ท่านค้นหา กรุณาระบุอาการใหม่อีกครั้ง", None, None
else:
return f"ท่านควรเข้ารับการตรวจที่ {clinic_name}", clinic_name, clinic_key
except Exception as e:
st.error(f"เกิดข้อผิดพลาดในการทำนายคลินิก: {str(e)}")
return "เกิดข้อผิดพลาดในการทำนายคลินิก กรุณาลองใหม่อีกครั้ง", None, None
def get_doctor_images(clinic_key):
try:
base_path = "รูปหมอออโถ/"
doctor_images = {}
for doctor in clinic_doctor_mapping[clinic_key]:
image_path = f"{base_path}{doctor}.png"
doctor_images[doctor] = image_path
return doctor_images
except Exception as e:
st.error(f"เกิดข้อผิดพลาดในการโหลดรูปภาพแพทย์: {str(e)}")
return {}
def create_line_deep_link(message, line_id):
try:
encoded_message = urllib.parse.quote(message)
return f"https://line.me/R/oaMessage/{line_id}/?{encoded_message}"
except Exception as e:
st.error(f"เกิดข้อผิดพลาดในการสร้าง LINE Deep Link: {str(e)}")
return None
def save_appointment(appointment_data):
try:
with open('appointments.json', 'a') as f:
json.dump(appointment_data, f)
f.write('\n')
st.success("บันทึกข้อมูลการนัดหมายเรียบร้อยแล้ว")
except Exception as e:
st.error(f"เกิดข้อผิดพลาดในการบันทึกข้อมูลการนัดหมาย: {str(e)}")
def validate_hn(hn):
if not hn.isdigit():
return False
if len(hn) != 9:
return False
if hn[0] not in ['5', '6']:
return False
return True
def reset_session_state():
# รายการ keys ที่ต้องการเก็บไว้
keys_to_keep = ['step', 'age']
# สร้าง dict ใหม่ที่มีเฉพาะ keys ที่ต้องการเก็บไว้
new_state = {key: st.session_state[key] for key in keys_to_keep if key in st.session_state}
# ล้าง session state ทั้งหมด
st.session_state.clear()
# เพิ่ม keys ที่ต้องการเก็บไว้กลับเข้าไป
st.session_state.update(new_state)
# กำหนดค่าเริ่มต้นใหม่
st.session_state.step = 1
st.session_state.age = 30
st.session_state.symptom = ""
st.session_state.clinic_result = None
st.session_state.clinic_name = None
st.session_state.clinic_key = None
st.session_state.doctor_images = None
st.session_state.name = ""
st.session_state.has_hn = True
st.session_state.hn = ""
st.session_state.selected_doctor = None
st.session_state.selected_slot = None
st.session_state.appointment_confirmed = False
st.session_state.appointment_saved = False
def on_hn_change():
# ตรวจสอบว่า 'hn_input' มีอยู่ใน session state ก่อนที่จะใช้
if 'hn_input' in st.session_state:
st.session_state.hn = st.session_state.hn_input
# เพิ่มฟังก์ชันนี้ที่ด้านบนของไฟล์ของคุณ
def load_feedback():
try:
with open('user_feedback.json', 'r') as f:
return [json.loads(line) for line in f]
except FileNotFoundError:
return []
# เพิ่มฟังก์ชันนี้สำหรับบันทึกข้อเสนอแนะพร้อมเวลา
def save_feedback_with_timestamp(feedback):
try:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open('user_feedback.json', 'a') as f:
json.dump({"feedback": feedback, "timestamp": timestamp}, f)
f.write('\n')
st.success("ขอบคุณสำหรับข้อเสนอแนะของท่าน")
except Exception as e:
st.error(f"เกิดข้อผิดพลาดในการบันทึกข้อเสนอแนะ: {str(e)}")
# ฟังก์ชันสำหรับเชื่อมต่อกับ Google Sheets
def connect_to_sheets():
scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']
creds = ServiceAccountCredentials.from_json_keyfile_name('carbide-calling-435015-v0-039bd8f32a3c.json', scope)
client = gspread.authorize(creds)
return client
# อัพเดทฟังก์ชันสำหรับบันทึกข้อเสนอแนะ
def save_feedback_to_sheet(feedback):
client = connect_to_sheets()
sheet = client.open_by_key('1ge4z5UWXR9QJGHHIdJCwaidbbN62sxtaM8WbiliwNhY').sheet1
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sheet.append_row([timestamp, feedback])
st.success("ขอบคุณสำหรับข้อเสนอแนะของท่าน")
# อัพเดทฟังก์ชันสำหรับบันทึกข้อมูลการนัดหมาย
def save_appointment_to_sheet(appointment_data):
if not st.session_state.appointment_saved:
client = connect_to_sheets()
sheet = client.open_by_key('1AcYyG43ZjPXa-xn_ztXFW9TNwFUjiopdFiaIO-h3um0').sheet1
row = [
appointment_data['name'],
appointment_data['hn'],
appointment_data['age'],
appointment_data['symptom'],
appointment_data['clinic'],
appointment_data['doctor'],
appointment_data['preferred_time_slot'],
"ใดก็ได้" if appointment_data['doctor'] == "แพทย์ท่านใดก็ได้" else "เฉพาะเจาะจง",
datetime.now().strftime("%Y-%m-%d %H:%M:%S")
]
sheet.append_row(row)
st.session_state.appointment_saved = True
st.success("บันทึกข้อมูลการนัดหมายเรียบร้อยแล้ว")
else:
st.info("ข้อมูลการนัดหมายได้ถูกบันทึกแล้ว")
def main():
st.sidebar.title("เมนู")
page = st.sidebar.radio("เลือกหน้า", ["หน้าหลัก", "Admin"])
if page == "หน้าหลัก":
main_page()
elif page == "Admin":
admin_page()
def main_page():
st.title("AI ออร์โธปิดิกส์อัจฉริยะ")
st.subheader("จากอาการสู่คลินิก แพทย์ และการนัดหมาย")
st.markdown("""
ยินดีต้อนรับสู่ระบบ AI ออร์โธปิดิกส์อัจฉริยะ!
เพียงแค่คุณบอกอาการ ระบบของเราจะ:
1. 🏥 แนะนำคลินิกที่เหมาะสมที่สุดสำหรับคุณ
2. 👨‍⚕️ เสนอรายชื่อแพทย์ผู้เชี่ยวชาญ
3. 📅 ช่วยคุณนัดหมายได้อย่างสะดวกและรวดเร็ว
เริ่มต้นใช้งานได้ทันทีด้านล่างนี้!
""")
# Initialize session state
if 'step' not in st.session_state:
st.session_state.step = 1
if 'age' not in st.session_state:
st.session_state.age = 30
if 'symptom' not in st.session_state:
st.session_state.symptom = ""
if 'clinic_result' not in st.session_state:
st.session_state.clinic_result = None
if 'clinic_name' not in st.session_state:
st.session_state.clinic_name = None
if 'clinic_key' not in st.session_state:
st.session_state.clinic_key = None
if 'doctor_images' not in st.session_state:
st.session_state.doctor_images = None
if 'name' not in st.session_state:
st.session_state.name = ""
if 'hn' not in st.session_state:
st.session_state.hn = ""
if 'has_hn' not in st.session_state:
st.session_state.has_hn = True
if 'selected_doctor' not in st.session_state:
st.session_state.selected_doctor = None
if 'selected_slot' not in st.session_state:
st.session_state.selected_slot = None
if 'appointment_confirmed' not in st.session_state:
st.session_state.appointment_confirmed = False
if 'appointment_saved' not in st.session_state:
st.session_state.appointment_saved = False
# Initialize session state
if 'hn_valid' not in st.session_state:
st.session_state.hn_valid = False
if st.session_state.step == 1:
st.session_state.name = st.text_input("ชื่อผู้ป่วย", value=st.session_state.get('name', ''))
st.session_state.has_hn = st.checkbox("มีเลข HN (เลขประจำตัวผู้ป่วยของโรงพยาบาลจุฬาภรณ์)", value=st.session_state.get('has_hn', True))
if st.session_state.has_hn:
hn_input = st.text_input(
"เลข HN (9 หลัก)",
value=st.session_state.get('hn', ''),
key='hn_input',
on_change=on_hn_change
)
if hn_input and not validate_hn(hn_input):
st.warning("HN ของท่านน่าจะผิด กรุณากรอกใหม่ หรือถ้าไม่มีให้เลือกว่าไม่มี HN")
st.session_state.hn_valid = False
else:
st.session_state.hn = hn_input
st.session_state.hn_valid = validate_hn(hn_input)
else:
st.warning("ท่านสามารถตรวจสอบอาการและดูข้อมูลแพทย์ได้ แต่จะไม่สามารถทำการนัดหมายผ่านระบบนี้ได้")
st.session_state.hn = "" # เคลียร์ค่า HN เมื่อไม่มี HN
st.session_state.hn_valid = False
st.session_state.age = st.number_input("กรุณาระบุอายุของผู้ป่วย", min_value=0, max_value=120, value=st.session_state.get('age', 30))
st.session_state.symptom = st.text_area("กรุณาระบุอาการของผู้ป่วย (ภาษาไทยเท่านั้น)", value=st.session_state.get('symptom', ''))
if st.button("ทำนายคลินิก"):
if not st.session_state.name.strip():
st.warning("กรุณากรอกชื่อผู้ป่วย")
elif not st.session_state.symptom.strip():
st.warning("กรุณาระบุอาการของผู้ป่วย")
else:
result, clinic_name, clinic_key = predict_clinic(st.session_state.age, st.session_state.symptom)
st.session_state.clinic_result = result
st.session_state.clinic_name = clinic_name
st.session_state.clinic_key = clinic_key
if clinic_key:
st.session_state.doctor_images = get_doctor_images(clinic_key)
st.session_state.step = 2
if st.session_state.step >= 2:
st.write(f"ผลการทำนาย: {st.session_state.clinic_result}")
if st.session_state.clinic_key:
st.subheader(f"แพทย์ในคลินิก {st.session_state.clinic_name}:")
# แสดงรูปภาพแพทย์
if st.session_state.doctor_images:
num_doctors = len(st.session_state.doctor_images)
num_rows = math.ceil(num_doctors / 2)
for i in range(0, num_doctors, 2):
col1, col2 = st.columns(2)
doctors = list(st.session_state.doctor_images.items())
doctor, image_path = doctors[i]
col1.image(image_path, caption=doctor, use_column_width=True)
if i + 1 < num_doctors:
doctor, image_path = doctors[i + 1]
col2.image(image_path, caption=doctor, use_column_width=True)
##
if st.session_state.hn_valid:
doctor_options = ["-- เลือกชื่อแพทย์ --"] + clinic_doctor_mapping[st.session_state.clinic_key] + ["แพทย์ท่านใดก็ได้"]
st.session_state.selected_doctor = st.selectbox("เลือกแพทย์", options=doctor_options, key='doctor_select')
if st.session_state.selected_doctor and st.session_state.selected_doctor != "-- เลือกชื่อแพทย์ --":
if st.session_state.selected_doctor == "แพทย์ท่านใดก็ได้":
st.subheader("เลือกช่วงเวลาที่สะดวก:")
time_options = ["ในเวลาราชการ", "นอกเวลาราชการ (คิวจะเร็วกว่า เป็นช่วงหลัง 16.00 น. และวันเสาร์-อาทิตย์)"]
st.session_state.selected_slot = st.radio("", options=time_options)
else:
st.subheader(f"เวลาตรวจของ {st.session_state.selected_doctor}:")
time_slots = doctor_time_slots[st.session_state.selected_doctor]
st.session_state.selected_slot = st.selectbox("เลือกเวลาตรวจ", options=["-- เลือกเวลาตรวจ --"] + time_slots, key='time_slot_select')
if st.session_state.selected_doctor == "แพทย์ท่านใดก็ได้":
st.info("หมายเหตุ: การตรวจนอกเวลาราชการ (หลัง 16.00 น.) รวมทั้งวันหยุดเสาร์-อาทิตย์ จะมีค่าใช้จ่ายเพิ่มเติมในการตรวจ")
else:
st.info("หมายเหตุ: การตรวจนอกเวลาราชการ (หลัง 16.00 น.) รวมทั้งวันหยุดเสาร์-อาทิตย์ จะมีค่าใช้จ่ายเพิ่มเติมในการตรวจ")
if ((st.session_state.selected_doctor == "แพทย์ท่านใดก็ได้" and st.session_state.selected_slot) or
(st.session_state.selected_doctor != "แพทย์ท่านใดก็ได้" and st.session_state.selected_slot != "-- เลือกเวลาตรวจ --")):
if st.button("สรุปข้อมูลที่ท่านจะทำนัด"):
st.session_state.appointment_confirmed = True
##
else:
st.warning("แนะนำให้เปิดเลข HN กับโรงพยาบาลจุฬาภรณ์ก่อน และกลับมาทำนัดทางระบบอีกครั้ง")
st.info("หากท่านต้องการข้อมูลเพิ่มเติมเกี่ยวกับการเปิด HN กรุณาติดต่อ 02-576-6000")
# ส่วนที่เพิ่มใหม่สำหรับการสร้าง LINE Deep Link
if st.session_state.get('appointment_confirmed') and st.session_state.hn_valid:
summary = f"นัดหมาย: คนไข้ {st.session_state.name} อายุ {st.session_state.age} ปี\n"
summary += f"HN: {st.session_state.hn}\n"
summary += f"อาการ: {st.session_state.symptom}\n"
summary += f"คลินิก: {st.session_state.clinic_name}\n"
summary += f"แพทย์: {st.session_state.selected_doctor}\n"
summary += f"เวลาที่ต้องการ: {st.session_state.selected_slot}"
line_id = "@987vwxrs" # LINE ID ของแผนก
deep_link = create_line_deep_link(summary, line_id)
st.success("ข้อมูลการขอนัดหมายของท่านได้ถูกบันทึกแล้ว")
st.warning("""
โปรดทราบ: การนัดหมายจะสมบูรณ์ต่อเมื่อท่านดำเนินการดังนี้
1. ส่งข้อความหาเจ้าหน้าที่ผ่านทาง LINE Official Account
2. รอการตอบกลับจากเจ้าหน้าที่เพื่อยืนยันวันและเวลานัดที่แน่นอน
เจ้าหน้าที่จะตรวจสอบคิวนัดของแพทย์และเลือกวันนัดที่เร็วที่สุดให้ท่าน
โดยอาจใช้เวลาระหว่าง 1-3 เดือน ขึ้นอยู่กับความพร้อมของแพทย์แต่ละท่าน
""")
if deep_link:
st.markdown(f"<a href='{deep_link}' target='_blank'><button style='padding: 15px 32px; font-size: 20px; margin: 4px 2px; cursor: pointer;'>คลิกที่นี่เพื่อส่งข้อมูลการขอนัดไปยัง LINE Official Account</button></a>", unsafe_allow_html=True)
st.info("คำแนะนำ: กดปุ่มด้านบนเพื่อเปิด LINE และส่งข้อมูลการขอนัดโดยอัตโนมัติ หากปุ่มไม่ทำงาน ให้คัดลอกข้อความด้านล่างและส่งไปยัง LINE @987vwxrs ด้วยตนเอง")
st.text_area("ข้อความสรุปการขอนัด (สามารถคัดลอกได้):", value=summary, height=200)
# บันทึกข้อมูลการนัดหมาย
appointment_data = {
"name": st.session_state.name,
"hn": st.session_state.hn,
"age": st.session_state.age,
"symptom": st.session_state.symptom,
"clinic": st.session_state.clinic_name,
"doctor": st.session_state.selected_doctor,
"preferred_time_slot": st.session_state.selected_slot
}
save_appointment_to_sheet(appointment_data)
if st.button("เริ่มใหม่"):
reset_session_state()
st.rerun()
# แก้ไขส่วนการรับข้อเสนอแนะเป็น:
st.subheader("ข้อเสนอแนะ")
user_feedback = st.text_area("กรุณาแสดงความคิดเห็นหรือข้อเสนอแนะของท่าน:")
if st.button("ส่งข้อเสนอแนะ"):
if user_feedback.strip():
save_feedback_to_sheet(user_feedback)
else:
st.warning("กรุณากรอกข้อเสนอแนะก่อนกดส่ง")
def on_hn_change():
if 'hn_input' in st.session_state:
st.session_state.hn = st.session_state.hn_input
st.session_state.hn_valid = validate_hn(st.session_state.hn)
def admin_page():
st.title("หน้า Admin - AI ออร์โธปิดิกส์อัจฉริยะ")
st.subheader("จัดการระบบแนะนำและนัดหมายอัตโนมัติ")
# รหัสผ่านอย่างง่าย (ในการใช้งานจริงควรใช้วิธีที่ปลอดภัยกว่านี้)
password = st.text_input("กรุณาใส่รหัสผ่าน", type="password")
if password != "admin123": # เปลี่ยนรหัสผ่านนี้เป็นรหัสที่ปลอดภัยกว่า
st.warning("รหัสผ่านไม่ถูกต้อง")
return
st.subheader("ข้อเสนอแนะทั้งหมด")
feedback_data = load_feedback()
for item in feedback_data:
st.text(f"เวลา: {item.get('timestamp', 'ไม่ระบุ')}")
st.text(f"ข้อเสนอแนะ: {item['feedback']}")
st.markdown("---")
if __name__ == "__main__":
main()