triage-bot / app.py
Tortyfishguy's picture
Update app.py
ae14ed3 verified
import os
os.environ["PYTHAINLP_DATA_DIR"] = "/tmp/pythainlp"
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 API Keys
LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN")
LINE_CHANNEL_SECRET = os.getenv("LINE_CHANNEL_SECRET")
# FastAPI init
app = FastAPI()
line_bot_api = LineBotApi(LINE_ACCESS_TOKEN)
handler = WebhookHandler(LINE_CHANNEL_SECRET)
@app.get("/")
async def root():
return {"message": "Triage Bot with improved matching and response."}
@app.post("/webhook")
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}")
@handler.add(MessageEvent, message=TextMessage)
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 Keywords & Levels ------------------
# คำที่สั้นเกินไป / ไม่ชัดเจน
UNCLEAR_TOKENS = ["เจ็บ", "เวียน", "หน้ามืด", "ปวด", "ใจสั่น", "ตึง", "แน่น", "เมื่อย", "มึน"]
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}")
# ตรวจว่าข้อความอาจไม่ชัดเจนหรือสั้นเกินไป
if len(tokens) <= 2 or all(t in UNCLEAR_TOKENS for t in tokens):
return "❗ อาการของคุณยังไม่ชัดเจน กรุณาพิมพ์รายละเอียดเพิ่มเติม เช่น ตำแหน่ง ความรุนแรง หรืออาการร่วม"
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)