Spaces:
Sleeping
Sleeping
import os | |
import threading | |
import logging | |
import uvicorn | |
from fastapi import FastAPI, Request, HTTPException, Header | |
from linebot import LineBotApi, WebhookHandler | |
from linebot.exceptions import InvalidSignatureError | |
from linebot.models import MessageEvent, TextMessage, TextSendMessage | |
from pythainlp.tokenize import word_tokenize | |
from rapidfuzz import fuzz | |
# Logging | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
# LINE Credentials | |
LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN") | |
LINE_CHANNEL_SECRET = os.getenv("LINE_CHANNEL_SECRET") | |
# Initialize | |
app = FastAPI() | |
line_bot_api = LineBotApi(LINE_ACCESS_TOKEN) | |
handler = WebhookHandler(LINE_CHANNEL_SECRET) | |
async def root(): | |
return {"message": "Triage Bot is running (Thai NLP + fuzzy + triage level)"} | |
async def webhook(request: Request, x_line_signature: str = Header(None)): | |
if not x_line_signature: | |
raise HTTPException(status_code=400, detail="Missing X-Line-Signature") | |
body = await request.body() | |
body_text = body.decode("utf-8") | |
logger.info(f"Received webhook: {body_text}") | |
try: | |
threading.Thread(target=handle_webhook, args=(body_text, x_line_signature)).start() | |
return {"status": "OK"} | |
except Exception as e: | |
logger.error(f"Webhook error: {e}") | |
return {"status": "Error"}, 500 | |
def handle_webhook(body_text, x_line_signature): | |
try: | |
handler.handle(body_text, x_line_signature) | |
except InvalidSignatureError: | |
logger.error("Invalid signature") | |
except Exception as e: | |
logger.error(f"Handle webhook error: {e}") | |
def handle_message(event): | |
user_message = event.message.text | |
logger.info(f"User message: {user_message}") | |
response_text = check_emergency(user_message) | |
logger.info(f"Bot response: {response_text}") | |
threading.Thread(target=reply_message, args=(event.reply_token, response_text)).start() | |
def reply_message(reply_token, message): | |
try: | |
line_bot_api.reply_message(reply_token, TextSendMessage(text=message)) | |
logger.info("Reply sent") | |
except Exception as e: | |
logger.error(f"Send reply error: {e}") | |
# ----------------- Emergency Symptom and Levels ------------------ | |
EMERGENCY_SYMPTOMS = { | |
"หมดสติ / ไม่รู้สึกตัว": [ | |
"หมดสติ", "ไม่รู้สึกตัว", "เป็นลม", "ล้มแล้วไม่ตื่น", "ไม่ตอบสนอง", | |
"หลับไม่รู้เรื่อง", "ไม่ขยับ", "ปลุกไม่ตื่น", "เหม่อลอย", "สลบ" | |
], | |
"หายใจผิดปกติ": [ | |
"หายใจเร็ว", "หายใจเหนื่อย", "หายใจลำบาก", "หายใจติดขัด", "หอบ", | |
"หายใจเสียงดัง", "แน่นหน้าอก", "หายใจไม่ทัน", "อึดอัด", "หายใจถี่" | |
], | |
"เจ็บหน้าอก": [ | |
"เจ็บหน้าอก", "แน่นหน้าอก", "จุกหน้าอก", "เสียวหน้าอก", "ปวดอก", | |
"บีบหน้าอก", "เจ็บลึกๆในอก", "อกแน่น", "อกอึดอัด", "หน้าอกปวดจี๊ด" | |
], | |
"ชัก / หยุดหายใจ": [ | |
"ชัก", "เกร็ง", "หยุดหายใจ", "หมดลม", "ตัวสั่น", | |
"ชักกระตุก", "หมดแรง", "ดิ้น", "ลืมตาไม่ขึ้น", "ตัวแข็ง" | |
], | |
"อัมพฤกษ์ / พูดไม่ชัด": [ | |
"อัมพฤกษ์", "อัมพาต", "พูดไม่ชัด", "หน้าเบี้ยว", "แขนขาอ่อนแรง", | |
"ยิ้มไม่เท่ากัน", "ยกแขนไม่ขึ้น", "เดินเซ", "ลิ้นแข็ง", "แขนขาไม่มีแรง" | |
], | |
"เลือดออก / บาดแผล": [ | |
"เลือดออก", "เลือดไม่หยุด", "เลือดไหล", "แผลฉกรรจ์", "แผลลึก", | |
"เลือดพุ่ง", "เลือดเปื้อนตัว", "แผลเปิด", "เลือดซึมตลอด", "แผลทะลุ" | |
], | |
"กระดูกหัก": [ | |
"กระดูกหัก", "แขนหัก", "ขาหัก", "นิ้วหัก", "บวมผิดรูป", | |
"ขางอผิดรูป", "ขาใช้การไม่ได้", "ขยับแล้วเจ็บมาก", "ปูด", "โค้งผิดปกติ" | |
], | |
"อุบัติเหตุรุนแรง": [ | |
"รถชน", "รถล้ม", "ตกจากที่สูง", "หัวฟาด", "อุบัติเหตุแรง", | |
"รถคว่ำ", "ล้มแรง", "โดนกระแทกแรง", "ลื่นหัวฟาด", "โดนรถชน" | |
], | |
"เด็กมีไข้ / ชัก": [ | |
"ไข้สูง", "เด็กชัก", "ลูกชัก", "ชักตอนมีไข้", "ตัวร้อนจัด", | |
"ไข้ขึ้นสูง", "ตาค้าง", "เด็กไม่ตอบสนอง", "มือแข็ง", "สั่นทั้งตัว" | |
], | |
"ภาวะช็อก / หัวใจเต้นผิดจังหวะ": [ | |
"ช็อก", "ความดันต่ำ", "ใจสั่น", "หัวใจเต้นผิดจังหวะ", "ชีพจรเร็ว", | |
"หมดแรงกะทันหัน", "ตัวเย็น", "เหงื่อแตก", "ใจเต้นแรง", "แน่นท้องหายใจไม่ออก" | |
], | |
"ได้รับสารพิษ / ยาเกินขนาด / สัตว์มีพิษกัด": [ | |
"สารพิษ", "กินยาเกิน", "ยาเกินขนาด", "พิษ", "ถูกงูกัด", | |
"แมงป่องต่อย", "แมลงกัด", "แพ้ยา", "แพ้อาหารรุนแรง", "คลื่นไส้อาเจียนมาก" | |
], | |
"ผู้ป่วยตั้งครรภ์ใกล้คลอด": [ | |
"ตั้งครรภ์", "เจ็บท้องคลอด", "น้ำเดิน", "มูกเลือด", "ปวดท้องใกล้คลอด", | |
"ท้องแข็ง", "มดลูกบีบ", "ปวดหน่วง", "เจ็บท้องถี่", "ลูกดิ้นแรงผิดปกติ" | |
] | |
} | |
EMERGENCY_LEVELS = { | |
"หมดสติ / ไม่รู้สึกตัว": 1, | |
"หายใจผิดปกติ": 1, | |
"เจ็บหน้าอก": 2, | |
"ชัก / หยุดหายใจ": 1, | |
"อัมพฤกษ์ / พูดไม่ชัด": 2, | |
"เลือดออก / บาดแผล": 2, | |
"กระดูกหัก": 2, | |
"อุบัติเหตุรุนแรง": 1, | |
"เด็กมีไข้ / ชัก": 2, | |
"ภาวะช็อก / หัวใจเต้นผิดจังหวะ": 1, | |
"ได้รับสารพิษ / ยาเกินขนาด / สัตว์มีพิษกัด": 1, | |
"ผู้ป่วยตั้งครรภ์ใกล้คลอด": 2 | |
} | |
FUZZY_THRESHOLD = 85 | |
def check_emergency(message): | |
tokens = word_tokenize(message.lower(), engine="newmm") | |
logger.info(f"Tokenized: {tokens}") | |
for label, keywords in EMERGENCY_SYMPTOMS.items(): | |
for token in tokens: | |
for keyword in keywords: | |
score = fuzz.partial_ratio(token, keyword) | |
if score >= FUZZY_THRESHOLD: | |
level = EMERGENCY_LEVELS.get(label, 2) | |
if level == 1: | |
return f"🚨 อาการของคุณเข้าข่ายฉุกเฉินระดับ **1 (ด่วนมาก)**: {label}\nกรุณารีบไปห้องฉุกเฉินทันที!" | |
elif level == 2: | |
return f"⚠️ อาการของคุณเข้าข่ายฉุกเฉินระดับ **2 (เร่งด่วน)**: {label}\nควรไปโรงพยาบาลโดยเร็วที่สุด" | |
return "🟢 อาการของคุณไม่เข้าข่ายฉุกเฉิน สามารถเข้ารับบริการที่ห้องตรวจทั่วไปในวันถัดไปได้" | |
# ---------------------------------------------------------------- | |
if __name__ == "__main__": | |
uvicorn.run(app, host="0.0.0.0", port=7860) | |