dek924 commited on
Commit
ba9cf35
·
1 Parent(s): 5f9fc5a

feat: improve conversation readability

Browse files
Files changed (1) hide show
  1. app.py +118 -59
app.py CHANGED
@@ -163,83 +163,142 @@ def build_recap_html(hadm_id: str, model: str, cefr: str, personality: str, reca
163
  )
164
 
165
 
166
-
167
- def _row(label: str, val) -> str:
 
 
 
168
  safe_val = html.escape(str(val)) if val not in (None, "") else "N/A"
169
  safe_label = html.escape(str(label))
170
  return (
171
- f"<tr>"
172
- f"<td style='padding:3px 8px;white-space:nowrap;vertical-align:top'><b>{safe_label}:</b></td>"
173
- f"<td style='padding:3px 8px'>{safe_val}</td>"
174
- f"</tr>"
 
 
175
  )
176
 
177
 
178
- def _section(title: str, rows_html: str) -> str:
 
179
  return (
180
- f"<div style='font-weight:bold;color:#2c3e50;border-bottom:1px solid #ccc;"
181
- f"margin:10px 0 4px;padding-bottom:2px'>{title}</div>"
182
- f"<table style='border-collapse:collapse;font-size:13px;margin-bottom:4px'>"
183
- f"{rows_html}</table>"
 
 
 
 
 
 
 
184
  )
185
 
186
 
187
  def build_profile_html(p: dict) -> str:
188
- basic = (
189
- "<h3 style='margin:0 0 6px'>Patient Profile</h3>"
190
- f"<table style='border-collapse:collapse;font-size:14px;margin-bottom:4px'>"
191
- f"{_row('Age', p.get('age'))}"
192
- f"{_row('Gender', p.get('gender'))}"
193
- f"{_row('Race', p.get('race'))}"
194
- f"{_row('Arrival Transport', p.get('arrival_transport'))}"
195
- f"</table>"
 
 
 
 
 
 
 
 
 
 
 
196
  )
197
- social = _section(
198
- "Social History",
199
- _row("Tobacco", p.get("tobacco"))
200
- + _row("Alcohol", p.get("alcohol"))
201
- + _row("Illicit Drug", p.get("illicit_drug"))
202
- + _row("Exercise", p.get("exercise"))
203
- + _row("Marital Status", p.get("marital_status"))
204
- + _row("Children", p.get("children"))
205
- + _row("Living Situation", p.get("living_situation"))
206
- + _row("Occupation", p.get("occupation"))
207
- + _row("Insurance", p.get("insurance")),
208
  )
209
- history = _section(
210
- "Previous Medical History",
211
- _row("Allergies", p.get("allergies"))
212
- + _row("Family History", p.get("family_medical_history"))
213
- + _row("Medical Devices", p.get("medical_device"))
214
- + _row("Prior Medical History", p.get("medical_history")),
215
  )
216
- visit = _section(
217
- "Current Visit",
218
- _row("Present Illness (+)", p.get("present_illness_positive"))
219
- + _row("Present Illness (−)", p.get("present_illness_negative"))
220
- + _row("Chief Complaint", p.get("chiefcomplaint"))
221
- + _row("Pain (0–10)", p.get("pain"))
222
- + _row("Medications", p.get("medication"))
223
- + _row("Disposition", p.get("disposition"))
224
- + _row("Diagnosis", p.get("diagnosis")),
 
 
 
 
 
 
 
 
 
225
  )
226
- return f"<div style='font-family:Noto Sans KR,Noto Sans,Malgun Gothic,Apple SD Gothic Neo,sans-serif;font-size:14px;line-height:1.5;background:#ffffff;padding:16px;border-radius:10px'>{basic}{social}{history}{visit}</div>"
227
 
228
 
229
  def build_blind_profile_html(p: dict) -> str:
230
  """Show only basic demographic info for practice mode without full case details."""
231
- content = (
232
- "<h3 style='margin:0 0 6px'>Patient Info</h3>"
233
- "<p style='font-size:12px;color:#888;margin:0 0 8px;font-style:italic'>"
234
- "Basic demographics only — gather the rest through consultation.</p>"
235
- f"<table style='border-collapse:collapse;font-size:14px;margin-bottom:4px'>"
236
- f"{_row('Age', p.get('age'))}"
237
- f"{_row('Gender', p.get('gender'))}"
238
- f"{_row('Race', p.get('race'))}"
239
- f"{_row('Arrival Transport', p.get('arrival_transport'))}"
240
- f"</table>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  )
242
- return f"<div style='font-family:Noto Sans KR,Noto Sans,Malgun Gothic,Apple SD Gothic Neo,sans-serif;font-size:14px;line-height:1.5;background:#ffffff;padding:16px;border-radius:10px'>{content}</div>"
243
 
244
 
245
  # ---------------------------------------------------------------------------
@@ -1088,7 +1147,7 @@ with gr.Blocks(title="PatientSim", theme=gr.themes.Soft(), css=CUSTOM_CSS) as de
1088
  with gr.Column(scale=2):
1089
  auto_chatbot = gr.Chatbot(
1090
  label="Doctor–Patient Dialogue",
1091
- height=560,
1092
  show_label=True,
1093
  avatar_images=(
1094
  _DOCTOR_AVATAR,
@@ -1119,7 +1178,7 @@ with gr.Blocks(title="PatientSim", theme=gr.themes.Soft(), css=CUSTOM_CSS) as de
1119
  with gr.Column(scale=2):
1120
  chatbot = gr.Chatbot(
1121
  label="Consultation",
1122
- height=560,
1123
  show_label=True,
1124
  avatar_images=(
1125
  _DOCTOR_AVATAR,
 
163
  )
164
 
165
 
166
+ # ---------------------------------------------------------------------------
167
+ # Profile HTML helpers (card-based layout matching recap style)
168
+ # ---------------------------------------------------------------------------
169
+ def _profile_item(label: str, val) -> str:
170
+ """Build a single key-value item inside a profile card."""
171
  safe_val = html.escape(str(val)) if val not in (None, "") else "N/A"
172
  safe_label = html.escape(str(label))
173
  return (
174
+ f"<div style='display:flex;gap:6px;padding:5px 0;"
175
+ f"border-bottom:1px solid #f3f4f6;font-size:13px;line-height:1.5'>"
176
+ f"<span style='color:#6b7280;white-space:nowrap;min-width:110px;"
177
+ f"font-weight:500'>{safe_label}</span>"
178
+ f"<span style='color:#1f2937;flex:1'>{safe_val}</span>"
179
+ f"</div>"
180
  )
181
 
182
 
183
+ def _profile_card(icon: str, title: str, items_html: str, accent: str = "#3b82f6") -> str:
184
+ """Build a styled card section for the profile panel."""
185
  return (
186
+ f"<div style='background:var(--background-fill-primary,#fff);"
187
+ f"border:1px solid var(--border-color-primary,#e5e7eb);border-radius:10px;"
188
+ f"padding:14px 16px;margin-bottom:10px'>"
189
+ f"<div style='display:flex;align-items:center;gap:8px;margin-bottom:10px;"
190
+ f"padding-bottom:8px;border-bottom:2px solid {accent}'>"
191
+ f"<span style='font-size:16px'>{icon}</span>"
192
+ f"<span style='font-size:13px;font-weight:600;letter-spacing:0.03em;"
193
+ f"text-transform:uppercase;color:{accent}'>{html.escape(title)}</span>"
194
+ f"</div>"
195
+ f"{items_html}"
196
+ f"</div>"
197
  )
198
 
199
 
200
  def build_profile_html(p: dict) -> str:
201
+ hadm_id = p.get("hadm_id", "")
202
+ avatar_url = _PATIENT_AVATAR_URLS.get(hadm_id, list(_PATIENT_AVATAR_URLS.values())[0])
203
+ safe_avatar = html.escape(avatar_url)
204
+
205
+ # Header with avatar
206
+ header = (
207
+ f"<div style='text-align:center;margin-bottom:14px'>"
208
+ f"<img src='{safe_avatar}' style='width:52px;height:52px;border-radius:50%;"
209
+ f"background:#f3f4f6;padding:3px;margin-bottom:6px' alt='Patient'>"
210
+ f"<div style='font-size:15px;font-weight:600;color:#1f2937'>Patient Profile</div>"
211
+ f"</div>"
212
+ )
213
+
214
+ basic = _profile_card("👤", "Demographics",
215
+ _profile_item("Age", p.get("age"))
216
+ + _profile_item("Gender", p.get("gender"))
217
+ + _profile_item("Race", p.get("race"))
218
+ + _profile_item("Transport", p.get("arrival_transport")),
219
+ accent="#3b82f6",
220
  )
221
+ social = _profile_card("🏠", "Social History",
222
+ _profile_item("Tobacco", p.get("tobacco"))
223
+ + _profile_item("Alcohol", p.get("alcohol"))
224
+ + _profile_item("Illicit Drug", p.get("illicit_drug"))
225
+ + _profile_item("Exercise", p.get("exercise"))
226
+ + _profile_item("Marital Status", p.get("marital_status"))
227
+ + _profile_item("Children", p.get("children"))
228
+ + _profile_item("Living Situation", p.get("living_situation"))
229
+ + _profile_item("Occupation", p.get("occupation"))
230
+ + _profile_item("Insurance", p.get("insurance")),
231
+ accent="#10b981",
232
  )
233
+ history = _profile_card("📋", "Previous Medical History",
234
+ _profile_item("Allergies", p.get("allergies"))
235
+ + _profile_item("Family History", p.get("family_medical_history"))
236
+ + _profile_item("Medical Devices", p.get("medical_device"))
237
+ + _profile_item("Prior History", p.get("medical_history")),
238
+ accent="#f59e0b",
239
  )
240
+ visit = _profile_card("🩺", "Current Visit",
241
+ _profile_item("Present Illness (+)", p.get("present_illness_positive"))
242
+ + _profile_item("Present Illness ()", p.get("present_illness_negative"))
243
+ + _profile_item("Chief Complaint", p.get("chiefcomplaint"))
244
+ + _profile_item("Pain (0–10)", p.get("pain"))
245
+ + _profile_item("Medications", p.get("medication"))
246
+ + _profile_item("Disposition", p.get("disposition"))
247
+ + _profile_item("Diagnosis", p.get("diagnosis")),
248
+ accent="#ef4444",
249
+ )
250
+
251
+ return (
252
+ f"<div style='font-family:Noto Sans KR,Noto Sans,Malgun Gothic,Apple SD Gothic Neo,sans-serif;"
253
+ f"font-size:14px;line-height:1.5;background:var(--color-accent-soft,#f0f7ff);"
254
+ f"border:1px solid var(--border-color-primary,#e5e7eb);border-radius:12px;"
255
+ f"padding:18px 16px;max-height:400px;overflow-y:auto'>"
256
+ f"{header}{basic}{social}{history}{visit}"
257
+ f"</div>"
258
  )
 
259
 
260
 
261
  def build_blind_profile_html(p: dict) -> str:
262
  """Show only basic demographic info for practice mode without full case details."""
263
+ hadm_id = p.get("hadm_id", "")
264
+ avatar_url = _PATIENT_AVATAR_URLS.get(hadm_id, list(_PATIENT_AVATAR_URLS.values())[0])
265
+ safe_avatar = html.escape(avatar_url)
266
+
267
+ header = (
268
+ f"<div style='text-align:center;margin-bottom:14px'>"
269
+ f"<img src='{safe_avatar}' style='width:52px;height:52px;border-radius:50%;"
270
+ f"background:#f3f4f6;padding:3px;margin-bottom:6px' alt='Patient'>"
271
+ f"<div style='font-size:15px;font-weight:600;color:#1f2937'>Patient Info</div>"
272
+ f"<div style='font-size:12px;color:#9ca3af;font-style:italic;margin-top:4px'>"
273
+ f"Basic demographics only — gather the rest through consultation.</div>"
274
+ f"</div>"
275
+ )
276
+
277
+ basic = _profile_card("👤", "Demographics",
278
+ _profile_item("Age", p.get("age"))
279
+ + _profile_item("Gender", p.get("gender"))
280
+ + _profile_item("Race", p.get("race"))
281
+ + _profile_item("Transport", p.get("arrival_transport")),
282
+ accent="#3b82f6",
283
+ )
284
+
285
+ hint = (
286
+ "<div style='background:var(--background-fill-primary,#fff);"
287
+ "border:1px dashed #d1d5db;border-radius:10px;padding:16px;text-align:center;"
288
+ "color:#9ca3af;font-size:13px'>"
289
+ "🔍 Additional information is hidden.<br>"
290
+ "Interview the patient to uncover their history."
291
+ "</div>"
292
+ )
293
+
294
+ return (
295
+ f"<div style='font-family:Noto Sans KR,Noto Sans,Malgun Gothic,Apple SD Gothic Neo,sans-serif;"
296
+ f"font-size:14px;line-height:1.5;background:var(--color-accent-soft,#f0f7ff);"
297
+ f"border:1px solid var(--border-color-primary,#e5e7eb);border-radius:12px;"
298
+ f"padding:18px 16px'>"
299
+ f"{header}{basic}{hint}"
300
+ f"</div>"
301
  )
 
302
 
303
 
304
  # ---------------------------------------------------------------------------
 
1147
  with gr.Column(scale=2):
1148
  auto_chatbot = gr.Chatbot(
1149
  label="Doctor–Patient Dialogue",
1150
+ height=700,
1151
  show_label=True,
1152
  avatar_images=(
1153
  _DOCTOR_AVATAR,
 
1178
  with gr.Column(scale=2):
1179
  chatbot = gr.Chatbot(
1180
  label="Consultation",
1181
+ height=700,
1182
  show_label=True,
1183
  avatar_images=(
1184
  _DOCTOR_AVATAR,