wensjheng commited on
Commit
4ff9b2a
·
verified ·
1 Parent(s): ea7a9aa

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +156 -0
app.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import json
4
+ import os
5
+ import re
6
+ from datetime import datetime
7
+
8
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
9
+ API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}"
10
+
11
+ INTRO_TEXT = """
12
+ <div>
13
+ <h1>實習醫學生「One-Minute Summary」考核評分</h1>
14
+ <p>本系統根據 <b>等第制 (A~F)</b> 與七大題項 (總分100分) 對你的總結給出評分與評語。</p>
15
+ </div>
16
+ """
17
+
18
+ # === 分數解析工具 ===
19
+ def parse_scores(text: str):
20
+ """嘗試從 Gemini 回覆中抓總分"""
21
+ total = None
22
+ m_total = re.search(r"總分[::]\s*(\d+)\s*/\s*100", text)
23
+ if m_total:
24
+ try:
25
+ total = int(m_total.group(1))
26
+ except:
27
+ total = None
28
+ return total
29
+
30
+ def render_score_card(total, raw):
31
+ parts = []
32
+ if total is not None:
33
+ parts.append(f"### 成績重點\n- **總分**:**{total}/100**\n")
34
+ parts.append("---")
35
+ parts.append(raw.strip())
36
+ return "\n".join(parts)
37
+
38
+ # === 呼叫 Gemini API ===
39
+ def call_gemini(summary):
40
+ if not GEMINI_API_KEY:
41
+ return "【設定錯誤】找不到 GEMINI_API_KEY(到 Space → Settings → Secrets 新增後重啟)"
42
+
43
+ headers = {"Content-Type": "application/json"}
44
+ prompt = f"""你是臨床教師,根據以下七大題項和等第制,請對實習醫學生的一分鐘總結進行評分與評論。
45
+ 評分規準:
46
+ • 優異(A):90-100分
47
+ o 評語: 內容精確、邏輯嚴謹,能對病程變化做出全面且深入的分析。能將各項資訊融會貫通,並在鑑別診斷中展現清晰的臨床思維。
48
+ • 良好(B):80-89分
49
+ o 評語: 內容完整,能掌握核心問題,但部分細節有待加強。能提出合理的鑑別診斷,但推論過程可更為細膩。
50
+ • 普通(C):70-79分
51
+ o 評語: 內容大致正確,但邏輯性不強,部分關鍵資訊遺漏。對鑑別診斷的掌握度有待提升。
52
+ • 尚可(D):60-69分
53
+ o 評語: 內容有所缺失,對病程的理解有限。未能在鑑別診斷中展現足夠的臨床思考。
54
+ • 不及格(F):60分以下
55
+ o 評語: 無法正確理解病程,遺漏過多關鍵資訊,且鑑別診斷不合理。
56
+ ________________________________________
57
+ 各主題詳細評分
58
+ 1. 年齡、性別、Chief Complaint (佔10分)
59
+ • 優異(A): 簡潔扼要地說出患者姓名、年齡、性別,並精確總結主訴,能明確指出時間點、特徵及嚴重程度。
60
+ • 良好(B): 能夠說出患者基本資料與主訴,但主訴的描述可能較為籠統。
61
+ • 尚可(C): 資訊不夠完整,如遺漏年齡或性別,或主訴描述不清。
62
+ • 需加強(F): 提供的基本資訊有誤或與主訴無關。
63
+ 2. 個人病史 (佔10分)
64
+ • 優異(A): 完整且有條理地羅列與本次主訴相關的所有過去病史,並能說明其與當前病情的潛在關聯。
65
+ • 良好(B): 能夠提及主要相關病史,但可能遺漏部分重要細節。
66
+ • 尚可(C): 僅能提及部分或不相關的病史,未建立與當前病情的連結。
67
+ • 需加強(F): 無法回答或對患者個人病史的認知錯誤。
68
+ 3. 家族史 (佔10分)
69
+ • 優異(A): 能主動詢問並說明相關家族史對本次診斷的重要性,即使資訊缺乏,也能展現全面性的思考。
70
+ • 良好(B): 能夠提及是否有家族史,但沒有深入說明其意義。
71
+ • 尚可(C): 僅提及無家族史,但沒有展現主動詢問的意識。
72
+ • 需加強(F): 遺漏此部分,或將其他病史混淆為家族史。
73
+ ________________________________________
74
+ 4. 身體檢查陽性發現 (佔10分)
75
+ • 優異(A): 準確報告關鍵的陽性體徵,並能解釋這些發現所代表的臨床意義。
76
+ • 良好(B): 能夠報告主要的陽性體徵,但未能完整連結其臨床意義。
77
+ • 尚可(C): 報告體徵不完整或包含太多不重要的陰性發現,導致重點模糊。
78
+ • 需加強(F): 無法正確報告任何陽性體徵,或與患者實際情況不符。
79
+ 5. 實驗室與影像結果 (佔10分)
80
+ • 優異(A): 精確總結關鍵的實驗室與影像異常數據,並能解釋這些結果如何支持或排除您的鑑別診斷。
81
+ • 良好(B): 能夠提及主要的異常檢驗結果,但可能遺漏部分數據或缺乏對其臨床意義的深入解釋。
82
+ • 尚可(C): 僅提及部分或不相關的檢驗結果,未能與病情建立有效連結。
83
+ • 需加強(F): 提供的檢驗或影像結果有誤,或完全無法回答。
84
+ 6. 鑑別診斷 (佔10分)
85
+ • 優異(A): 有條理地列出至少三個鑑別診斷,並從最致命的疾病開始,依據病史、體徵及檢驗結果,說明您的推理過程。
86
+ • 良好(B): 能夠列出幾個鑑別診斷,但排序或邏輯性不夠強,解釋不夠充分。
87
+ • 尚可(C): 僅能提出一個或少數幾個鑑別診斷��且缺乏支持論點。
88
+ • 需加強(F): 無法提出合理的鑑別診斷,或提出的診斷與病情明顯不符。
89
+ ________________________________________
90
+ 7. 目前處置與治療 (佔40分)
91
+ • 優異(A): 能清楚說明目前已給予的處置或治療,並解釋其背後的目的或原因。能展現治療決策的邏輯,例如「先穩定生命徵象,再進行進一步檢查」。
92
+ • 良好(B): 能夠提及已採取的處置,但未能完整解釋其目的或背後的治療邏輯。
93
+ • 尚可(C): 僅能提及部分處置,且資訊不夠精確。
94
+ • 需加強(F): 提供的處置資訊有誤,或與病情不符,或完全無法回答此問題。
95
+
96
+
97
+ 請輸出格式:
98
+ 1. 各項逐條給分與簡評
99
+ 2. 總分 (x/100)
100
+ 3. 等第 (A~F)
101
+ 4. 總結性評論
102
+
103
+ 學生的一分鐘總結如下:
104
+ {summary}
105
+ """
106
+ data = {"contents": [{"parts": [{"text": prompt}]}]}
107
+ try:
108
+ resp = requests.post(API_URL, headers=headers, data=json.dumps(data), timeout=30)
109
+ except Exception as e:
110
+ return f"【連線失敗】{e}"
111
+
112
+ if resp.status_code == 200:
113
+ try:
114
+ return resp.json()["candidates"][0]["content"]["parts"][0]["text"]
115
+ except Exception as e:
116
+ return f"【解析回應失敗】{e}|原始:{resp.text[:300]}"
117
+ else:
118
+ return f"【API 錯誤 {resp.status_code}】{resp.text[:300]}"
119
+
120
+ # === 回呼 ===
121
+ def collect_and_score(summary):
122
+ try:
123
+ raw = call_gemini(summary)
124
+ total = parse_scores(raw)
125
+ md = render_score_card(total, raw)
126
+ return summary, md
127
+ except Exception as e:
128
+ return summary, f"【系統錯誤】{e}"
129
+
130
+ def do_clear():
131
+ return "", "(已清空)"
132
+
133
+ # === Gradio 介面 ===
134
+ with gr.Blocks(title="One-Minute Summary 評分") as demo:
135
+ gr.HTML(INTRO_TEXT)
136
+
137
+ summary_box = gr.Textbox(label="請貼上你的 One-Minute Summary", lines=10)
138
+
139
+ with gr.Row():
140
+ submit_btn = gr.Button("送出評分", variant="primary")
141
+ clear_btn = gr.Button("清空")
142
+
143
+ result = gr.Markdown(label="評分結果")
144
+
145
+ submit_btn.click(
146
+ collect_and_score,
147
+ [summary_box],
148
+ [summary_box, result]
149
+ )
150
+
151
+ clear_btn.click(
152
+ do_clear,
153
+ outputs=[summary_box, result]
154
+ )
155
+
156
+ demo.launch()