Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -6,89 +6,151 @@ from fastapi import FastAPI, Request, HTTPException, Header
|
|
6 |
from linebot import LineBotApi, WebhookHandler
|
7 |
from linebot.exceptions import InvalidSignatureError
|
8 |
from linebot.models import MessageEvent, TextMessage, TextSendMessage
|
9 |
-
from
|
|
|
10 |
|
11 |
-
#
|
12 |
logging.basicConfig(level=logging.INFO)
|
13 |
logger = logging.getLogger(__name__)
|
14 |
|
15 |
-
#
|
16 |
LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN")
|
17 |
LINE_CHANNEL_SECRET = os.getenv("LINE_CHANNEL_SECRET")
|
18 |
|
19 |
-
# Initialize
|
20 |
app = FastAPI()
|
21 |
-
|
22 |
-
# Initialize LINE SDK
|
23 |
line_bot_api = LineBotApi(LINE_ACCESS_TOKEN)
|
24 |
handler = WebhookHandler(LINE_CHANNEL_SECRET)
|
25 |
|
26 |
@app.get("/")
|
27 |
async def root():
|
28 |
-
return {"message": "Triage Bot is running
|
29 |
|
30 |
@app.post("/webhook")
|
31 |
async def webhook(request: Request, x_line_signature: str = Header(None)):
|
32 |
if not x_line_signature:
|
33 |
raise HTTPException(status_code=400, detail="Missing X-Line-Signature")
|
34 |
-
|
35 |
body = await request.body()
|
36 |
body_text = body.decode("utf-8")
|
37 |
-
|
38 |
-
# Log ข้อความที่ได้รับจาก LINE
|
39 |
-
logger.info(f"Received webhook event: {body_text}")
|
40 |
-
|
41 |
try:
|
42 |
-
# ✅ ตอบกลับ LINE ทันที เพื่อป้องกัน Timeout
|
43 |
threading.Thread(target=handle_webhook, args=(body_text, x_line_signature)).start()
|
44 |
return {"status": "OK"}
|
45 |
except Exception as e:
|
46 |
-
logger.error(f"
|
47 |
return {"status": "Error"}, 500
|
48 |
|
49 |
def handle_webhook(body_text, x_line_signature):
|
50 |
-
""" ฟังก์ชันประมวลผล Webhook แยกต่างหาก """
|
51 |
try:
|
52 |
handler.handle(body_text, x_line_signature)
|
53 |
except InvalidSignatureError:
|
54 |
-
logger.error("Invalid signature
|
55 |
except Exception as e:
|
56 |
-
logger.error(f"
|
57 |
|
58 |
-
# Event handler สำหรับข้อความที่ได้รับ
|
59 |
@handler.add(MessageEvent, message=TextMessage)
|
60 |
def handle_message(event):
|
61 |
user_message = event.message.text
|
62 |
-
logger.info(f"User message: {user_message}")
|
63 |
-
|
64 |
-
|
65 |
-
response_text = get_triage_response(esi_level)
|
66 |
-
|
67 |
-
logger.info(f"Bot response: {response_text}") # Log ข้อความที่บอทจะส่ง
|
68 |
-
|
69 |
-
# ใช้ threading เพื่อให้ bot ตอบกลับเร็วขึ้น
|
70 |
threading.Thread(target=reply_message, args=(event.reply_token, response_text)).start()
|
71 |
|
72 |
def reply_message(reply_token, message):
|
73 |
-
""" ส่งข้อความตอบกลับผู้ใช้ """
|
74 |
try:
|
75 |
line_bot_api.reply_message(reply_token, TextSendMessage(text=message))
|
76 |
-
logger.info("Reply sent
|
77 |
except Exception as e:
|
78 |
-
logger.error(f"
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
""
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
|
93 |
if __name__ == "__main__":
|
94 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
6 |
from linebot import LineBotApi, WebhookHandler
|
7 |
from linebot.exceptions import InvalidSignatureError
|
8 |
from linebot.models import MessageEvent, TextMessage, TextSendMessage
|
9 |
+
from pythainlp.tokenize import word_tokenize
|
10 |
+
from rapidfuzz import fuzz
|
11 |
|
12 |
+
# Logging
|
13 |
logging.basicConfig(level=logging.INFO)
|
14 |
logger = logging.getLogger(__name__)
|
15 |
|
16 |
+
# LINE Credentials
|
17 |
LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN")
|
18 |
LINE_CHANNEL_SECRET = os.getenv("LINE_CHANNEL_SECRET")
|
19 |
|
20 |
+
# Initialize
|
21 |
app = FastAPI()
|
|
|
|
|
22 |
line_bot_api = LineBotApi(LINE_ACCESS_TOKEN)
|
23 |
handler = WebhookHandler(LINE_CHANNEL_SECRET)
|
24 |
|
25 |
@app.get("/")
|
26 |
async def root():
|
27 |
+
return {"message": "Triage Bot is running (Thai NLP + fuzzy + triage level)"}
|
28 |
|
29 |
@app.post("/webhook")
|
30 |
async def webhook(request: Request, x_line_signature: str = Header(None)):
|
31 |
if not x_line_signature:
|
32 |
raise HTTPException(status_code=400, detail="Missing X-Line-Signature")
|
|
|
33 |
body = await request.body()
|
34 |
body_text = body.decode("utf-8")
|
35 |
+
logger.info(f"Received webhook: {body_text}")
|
|
|
|
|
|
|
36 |
try:
|
|
|
37 |
threading.Thread(target=handle_webhook, args=(body_text, x_line_signature)).start()
|
38 |
return {"status": "OK"}
|
39 |
except Exception as e:
|
40 |
+
logger.error(f"Webhook error: {e}")
|
41 |
return {"status": "Error"}, 500
|
42 |
|
43 |
def handle_webhook(body_text, x_line_signature):
|
|
|
44 |
try:
|
45 |
handler.handle(body_text, x_line_signature)
|
46 |
except InvalidSignatureError:
|
47 |
+
logger.error("Invalid signature")
|
48 |
except Exception as e:
|
49 |
+
logger.error(f"Handle webhook error: {e}")
|
50 |
|
|
|
51 |
@handler.add(MessageEvent, message=TextMessage)
|
52 |
def handle_message(event):
|
53 |
user_message = event.message.text
|
54 |
+
logger.info(f"User message: {user_message}")
|
55 |
+
response_text = check_emergency(user_message)
|
56 |
+
logger.info(f"Bot response: {response_text}")
|
|
|
|
|
|
|
|
|
|
|
57 |
threading.Thread(target=reply_message, args=(event.reply_token, response_text)).start()
|
58 |
|
59 |
def reply_message(reply_token, message):
|
|
|
60 |
try:
|
61 |
line_bot_api.reply_message(reply_token, TextSendMessage(text=message))
|
62 |
+
logger.info("Reply sent")
|
63 |
except Exception as e:
|
64 |
+
logger.error(f"Send reply error: {e}")
|
65 |
+
|
66 |
+
# ----------------- Emergency Symptom and Levels ------------------
|
67 |
+
|
68 |
+
EMERGENCY_SYMPTOMS = {
|
69 |
+
"หมดสติ / ไม่รู้สึกตัว": [
|
70 |
+
"หมดสติ", "ไม่รู้สึกตัว", "เป็นลม", "ล้มแล้วไม่ตื��น", "ไม่ตอบสนอง",
|
71 |
+
"หลับไม่รู้เรื่อง", "ไม่ขยับ", "ปลุกไม่ตื่น", "เหม่อลอย", "สลบ"
|
72 |
+
],
|
73 |
+
"หายใจผิดปกติ": [
|
74 |
+
"หายใจเร็ว", "หายใจเหนื่อย", "หายใจลำบาก", "หายใจติดขัด", "หอบ",
|
75 |
+
"หายใจเสียงดัง", "แน่นหน้าอก", "หายใจไม่ทัน", "อึดอัด", "หายใจถี่"
|
76 |
+
],
|
77 |
+
"เจ็บหน้าอก": [
|
78 |
+
"เจ็บหน้าอก", "แน่นหน้าอก", "จุกหน้าอก", "เสียวหน้าอก", "ปวดอก",
|
79 |
+
"บีบหน้าอก", "เจ็บลึกๆในอก", "อกแน่น", "อกอึดอัด", "หน้าอกปวดจี๊ด"
|
80 |
+
],
|
81 |
+
"ชัก / หยุดหายใจ": [
|
82 |
+
"ชัก", "เกร็ง", "หยุดหายใจ", "หมดลม", "ตัวสั่น",
|
83 |
+
"ชักกระตุก", "หมดแรง", "ดิ้น", "ลืมตาไม่ขึ้น", "ตัวแข็ง"
|
84 |
+
],
|
85 |
+
"อัมพฤกษ์ / พูดไม่ชัด": [
|
86 |
+
"อัมพฤกษ์", "อัมพาต", "พูดไม่ชัด", "หน้าเบี้ยว", "แขนขาอ่อนแรง",
|
87 |
+
"ยิ้มไม่เท่ากัน", "ยกแขนไม่ขึ้น", "เดินเซ", "ลิ้นแข็ง", "แขนขาไม่มีแรง"
|
88 |
+
],
|
89 |
+
"เลือดออก / บาดแผล": [
|
90 |
+
"เลือดออก", "เลือดไม่หยุด", "เลือดไหล", "แผลฉกรรจ์", "แผลลึก",
|
91 |
+
"เลือดพุ่ง", "เลือดเปื้อนตัว", "แผลเปิด", "เลือดซึมตลอด", "แผลทะลุ"
|
92 |
+
],
|
93 |
+
"กระดูกหัก": [
|
94 |
+
"กระดูกหัก", "แขนหัก", "ขาหัก", "นิ้วหัก", "บวมผิดรูป",
|
95 |
+
"ขางอผิดรูป", "ขาใช้การไม่ได้", "ขยับแล้วเจ็บมาก", "ปูด", "โค้งผิดปกติ"
|
96 |
+
],
|
97 |
+
"อุบัติเหตุรุนแรง": [
|
98 |
+
"รถชน", "รถล้ม", "ตกจากที่สูง", "หัวฟาด", "อุบัติเหตุแรง",
|
99 |
+
"รถคว่ำ", "ล้มแรง", "โดนกระแทกแรง", "ลื่นหัวฟาด", "โดนรถชน"
|
100 |
+
],
|
101 |
+
"เด็กมีไข้ / ชัก": [
|
102 |
+
"ไข้สูง", "เด็กชัก", "ลูกชัก", "ชักตอนมีไข้", "ตัวร้อนจัด",
|
103 |
+
"ไข้ขึ้นสูง", "ตาค้าง", "เด็กไม่ตอบสนอง", "มือแข็ง", "สั่นทั้งตัว"
|
104 |
+
],
|
105 |
+
"ภาวะช็อก / หัวใจเต้นผิดจังหวะ": [
|
106 |
+
"ช็อก", "ความดันต่ำ", "ใจสั่น", "หัวใจเต้นผิดจังหวะ", "ชีพจรเร็ว",
|
107 |
+
"หมดแรงกะทันหัน", "ตัวเย็น", "เหงื่อแตก", "ใจเต้นแรง", "แน่นท้องหายใจไม่ออก"
|
108 |
+
],
|
109 |
+
"ได้รับสารพิษ / ยาเกินขนาด / สัตว์มีพิษกัด": [
|
110 |
+
"สารพิษ", "กินยาเกิน", "ยาเกินขนาด", "พิษ", "ถูกงูกัด",
|
111 |
+
"แมงป่องต่อย", "แมลงกัด", "แพ้ยา", "แพ้อาหารรุนแรง", "คลื่นไส้อาเจียนมาก"
|
112 |
+
],
|
113 |
+
"ผู้ป่วยตั้งครรภ์ใกล้คลอด": [
|
114 |
+
"ตั้งครรภ์", "เจ็บท้องคลอด", "น้ำเดิน", "มูกเลือด", "ปวดท้องใกล้คลอด",
|
115 |
+
"ท้องแข็ง", "มดลูกบีบ", "ปวดหน่วง", "เจ็บท้องถี่", "ลูกดิ้นแรงผิดปกติ"
|
116 |
+
]
|
117 |
+
}
|
118 |
+
|
119 |
+
EMERGENCY_LEVELS = {
|
120 |
+
"หมดสติ / ไม่รู้สึกตัว": 1,
|
121 |
+
"หายใจผิดปกติ": 1,
|
122 |
+
"เจ็บหน้าอก": 2,
|
123 |
+
"ชัก / หยุดหายใจ": 1,
|
124 |
+
"อัมพฤกษ์ / พูดไม่ชัด": 2,
|
125 |
+
"เลือดออก / บาดแผล": 2,
|
126 |
+
"กระดูกหัก": 2,
|
127 |
+
"อุบัติเหตุรุนแรง": 1,
|
128 |
+
"เด็กมีไข้ / ชัก": 2,
|
129 |
+
"ภาวะช็อก / หัวใจเต้นผิดจังหวะ": 1,
|
130 |
+
"ได้รับสารพิษ / ยาเกินขนาด / สัตว์มีพิษกัด": 1,
|
131 |
+
"ผู้ป่วยตั้งครรภ์ใกล้คลอด": 2
|
132 |
+
}
|
133 |
+
|
134 |
+
FUZZY_THRESHOLD = 85
|
135 |
+
|
136 |
+
def check_emergency(message):
|
137 |
+
tokens = word_tokenize(message.lower(), engine="newmm")
|
138 |
+
logger.info(f"Tokenized: {tokens}")
|
139 |
+
|
140 |
+
for label, keywords in EMERGENCY_SYMPTOMS.items():
|
141 |
+
for token in tokens:
|
142 |
+
for keyword in keywords:
|
143 |
+
score = fuzz.partial_ratio(token, keyword)
|
144 |
+
if score >= FUZZY_THRESHOLD:
|
145 |
+
level = EMERGENCY_LEVELS.get(label, 2)
|
146 |
+
if level == 1:
|
147 |
+
return f"🚨 อาการของคุณเข้าข่ายฉุกเฉินระดับ **1 (ด่วนมาก)**: {label}\nกรุณารีบไปห้องฉุกเฉินทันที!"
|
148 |
+
elif level == 2:
|
149 |
+
return f"⚠️ อาการของคุณเข้าข่ายฉุกเฉินระดับ **2 (เร่งด่วน)**: {label}\nควรไปโรงพยาบาลโดยเร็วที่สุด"
|
150 |
+
|
151 |
+
return "🟢 อาการของคุณไม่เข้าข่ายฉุกเฉิน สามารถเข้ารับบริการที่ห้องตรวจทั่วไปในวันถัดไปได้"
|
152 |
+
|
153 |
+
# ----------------------------------------------------------------
|
154 |
|
155 |
if __name__ == "__main__":
|
156 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|