Spaces:
Sleeping
Sleeping
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) | |
async def root(): | |
return {"message": "Triage Bot with improved matching and response."} | |
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 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) | |