Seth0330 commited on
Commit
cf6097f
·
verified ·
1 Parent(s): 8e1a014

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +128 -136
app.py CHANGED
@@ -18,28 +18,25 @@ st.set_page_config(
18
  )
19
 
20
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
21
- if not OPENAI_API_KEY:
22
- st.error("Missing OPENAI_API_KEY. Please set it in your Space secrets.")
23
  client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None
24
 
25
- # Models
26
- VISION_MODEL = "gpt-4.1-mini" # vision-capable
27
- REASONING_MODEL = "gpt-4.1-mini" # reasoning / agent
28
- ASR_MODEL = "whisper-1" # transcription
29
 
30
- # Health Canada Drug Product Database API (public)
31
  DPD_BASE_URL = "https://health-products.canada.ca/api/drug/drugproduct"
32
 
33
- # Recalls & Safety Alerts JSON feed (configurable; keep overridable)
34
  RECALLS_FEED_URL = os.getenv(
35
  "RECALLS_FEED_URL",
36
  "https://recalls-rappels.canada.ca/static-data/items-en.json"
37
  )
38
 
39
- # CIHI wait-times info (general reference, not live triage)
40
  WAIT_TIMES_INFO_URL = "https://www.cihi.ca/en/topics/access-and-wait-times"
41
 
42
- # Postal code to province mapping (by first letter)
43
  POSTAL_PREFIX_TO_PROVINCE = {
44
  "A": "Newfoundland and Labrador",
45
  "B": "Nova Scotia",
@@ -58,12 +55,12 @@ POSTAL_PREFIX_TO_PROVINCE = {
58
  "T": "Alberta",
59
  "V": "British Columbia",
60
  "X": "Northwest Territories / Nunavut",
61
- "Y": "Yukon"
62
  }
63
 
64
 
65
  # =========================
66
- # HELPERS
67
  # =========================
68
 
69
  def safe_get_json(url, params=None, timeout=6):
@@ -86,85 +83,63 @@ def shorten(text, max_chars=400):
86
 
87
 
88
  # =========================
89
- # REGION CONTEXT (POSTAL CODE)
90
  # =========================
91
 
92
  def infer_province_from_postal(postal_code: str):
93
- """
94
- Very simple FSA-based province inference using the first character.
95
- """
96
  if not postal_code:
97
  return None
98
  pc = postal_code.strip().upper().replace(" ", "")
99
  if not pc:
100
  return None
101
- first = pc[0]
102
- return POSTAL_PREFIX_TO_PROVINCE.get(first)
103
 
104
 
105
  def tool_region_context(postal_code: str) -> str:
106
- """
107
- Build region-aware context for the agent:
108
- - Province guess (if possible)
109
- - Telehealth-style guidance
110
- - Where to check local wait times / services
111
- """
112
  province = infer_province_from_postal(postal_code)
113
  if not postal_code:
114
- base = "No postal code provided. Provide general Canada-wide guidance."
115
- return base
116
 
117
  if not province:
118
  return (
119
- f"Postal code '{postal_code}' could not be confidently mapped. "
120
- "Provide generic Canada-wide guidance and suggest checking local provincial health websites."
121
  )
122
 
123
- # Generic, safe phrasing without hard-coding all numbers:
124
  telehealth_note = (
125
- f"In {province}, guide the user to their official provincial Telehealth or 811-style nurse advice line "
126
- "and ministry of health website for region-specific instructions."
127
  )
128
-
129
  wait_note = (
130
- "Advise the user that approximate wait-time and access information may be available via their "
131
- "provincial or regional health authority website, and through CIHI's access and wait-times resources. "
132
- f"Reference (non-exclusively): {WAIT_TIMES_INFO_URL}"
133
  )
134
 
135
  return (
136
- f"User-provided postal code: '{postal_code}' mapped to: {province}.\n"
137
  f"{telehealth_note}\n"
138
  f"{wait_note}"
139
  )
140
 
141
 
142
  # =========================
143
- # TOOL: DRUG PRODUCT LOOKUP (DPD)
144
  # =========================
145
 
146
  def tool_lookup_drug_products(candidates):
147
  if not candidates:
148
- return "No clear medication names detected that require a drug database lookup."
149
 
150
  found_lines = []
151
  for name in candidates[:5]:
152
- params = {
153
- "lang": "en",
154
- "type": "json",
155
- "brandname": name
156
- }
157
  data = safe_get_json(DPD_BASE_URL, params=params)
158
  if not data:
159
  continue
160
 
161
  unique_brands = set()
162
  for item in data[:3]:
163
- b = (
164
- item.get("brand_name")
165
- or item.get("brandname")
166
- or name
167
- )
168
  if b:
169
  unique_brands.add(b)
170
 
@@ -181,30 +156,37 @@ def tool_lookup_drug_products(candidates):
181
  )
182
 
183
  found_lines.append(
184
- "For definitive product information, ingredients, status, and monographs, "
185
- "users must consult the official Health Canada Drug Product Database."
186
  )
187
  return "\n".join(found_lines)
188
 
189
 
190
  # =========================
191
- # TOOL: RECALLS & SAFETY ALERTS
192
  # =========================
193
 
194
  def tool_get_recent_recalls_snippet():
195
  data = safe_get_json(RECALLS_FEED_URL)
196
- if not data or not isinstance(data, list):
 
 
 
 
 
 
 
 
197
  return (
198
- "Unable to fetch recent recalls. Direct users to the official Government of Canada "
199
  "Recalls and Safety Alerts website for up-to-date information."
200
  )
201
 
202
- items = data[:5]
203
- lines = ["Recent recalls and safety alerts (high-level snapshot):"]
204
  for item in items:
205
  title = (
206
  item.get("title")
207
  or item.get("english_title")
 
208
  or "Recall / Alert"
209
  )
210
  date = item.get("date_published") or item.get("date") or ""
@@ -217,23 +199,23 @@ def tool_get_recent_recalls_snippet():
217
 
218
 
219
  # =========================
220
- # TOOL: WAIT TIMES AWARENESS (GENERIC)
221
  # =========================
222
 
223
  def tool_get_wait_times_awareness():
224
  return textwrap.dedent(f"""
225
- Use open Canadian data conceptually:
226
- - CIHI and several provinces publish indicators and dashboards for emergency room, surgery, and diagnostic wait times.
227
- - These are averages and not real-time triage.
228
  Guidance:
229
- - For non-urgent issues: consider checking local clinic or urgent care centre information online.
230
- - For urgent symptoms: do not spend time checking wait-time tools; go to the nearest ER or call 911.
231
- Reference (general): {WAIT_TIMES_INFO_URL}
232
  """).strip()
233
 
234
 
235
  # =========================
236
- # TOOL: CANDIDATE TERM EXTRACTION
237
  # =========================
238
 
239
  def extract_candidate_terms(text: str):
@@ -248,7 +230,7 @@ def extract_candidate_terms(text: str):
248
 
249
 
250
  # =========================
251
- # OPENAI: VISION, ASR, AGENT
252
  # =========================
253
 
254
  def call_vision_summarizer(image_bytes: bytes) -> str:
@@ -256,14 +238,15 @@ def call_vision_summarizer(image_bytes: bytes) -> str:
256
  return ""
257
 
258
  b64 = base64.b64encode(image_bytes).decode("utf-8")
 
259
  prompt = (
260
  "You are a cautious Canadian health-information assistant.\n"
261
- "Look at the image and ONLY describe observable details in neutral terms.\n"
262
- "- If it's a skin issue: describe general appearance (location/size/colour/pattern). Do NOT name diseases.\n"
263
- "- If it's a pill bottle or box: note visible drug name/strength/marks if legible.\n"
264
- "- If it's a health letter/report: state the type (e.g., appointment letter, lab report) without exposing identifiers.\n"
265
- "- If it's irrelevant or unclear: say it is not medically interpretable.\n"
266
- "Never diagnose. Never prescribe. Keep within ~80-100 words."
267
  )
268
 
269
  try:
@@ -339,29 +322,26 @@ def call_reasoning_agent(
339
  You are CareCall AI, an agentic Canadian health information assistant.
340
 
341
  Rules:
342
- - You DO NOT diagnose.
343
- - You DO NOT prescribe or give specific dosing.
344
- - You DO NOT claim exact real-time wait times.
345
- - You MAY:
346
- - Summarize concerns neutrally.
347
- - Map them to broad, possible categories (e.g., irritation, infection, allergy, medication question, admin/letter).
348
- - Use the provided Drug Database context, Recalls context, wait-time awareness, and region context
349
- to make the guidance safer and more relevant.
350
- - Suggest next steps:
351
- * Self-care for clearly mild issues,
352
- * Pharmacist / family doctor / walk-in clinic,
353
- * Provincial Telehealth / 811-style nurse line,
354
- * ER / 911 if red-flag symptoms are present.
355
- - Red flags (e.g., chest pain, difficulty breathing, signs of stroke, heavy bleeding, suicidal thoughts,
356
- severe abdominal pain, high fever with stiff neck, major trauma, rapidly worsening symptoms)
357
- -> clearly recommend ER/911.
358
- - Always mention that official sources include:
359
- Health Canada, Public Health Agency of Canada, CIHI, and the user's provincial health services.
360
- - Max 350 words. Clear, calm, practical.
361
  """
362
 
363
  user_prompt = f"""
364
- INPUT TO THE AGENT
365
 
366
  1) User narrative:
367
  {narrative}
@@ -383,17 +363,17 @@ INPUT TO THE AGENT
383
 
384
  TASK
385
 
386
- Using ONLY the above information:
387
-
388
- 1. Briefly summarize what the user seems worried about (neutral; no diagnosis).
389
- 2. Describe, in broad terms, what categories of issues it could relate to, without asserting a specific disease.
390
- 3. Provide clear next-step options in bullet form, aligned with Canadian context and (if available) their province:
391
- - When self-care might be reasonable.
392
- - When to talk to a pharmacist, family doctor, or walk-in clinic.
393
- - When to call their provincial Telehealth / nurse line.
394
- - When to bypass everything and go to ER / call 911.
395
- 4. If any medication/recall hints appear, remind them to confirm via Health Canada resources and follow professional advice.
396
- 5. End with a concise disclaimer that this is informational only and not medical care.
397
  """
398
 
399
  try:
@@ -409,7 +389,7 @@ Using ONLY the above information:
409
  except Exception as e:
410
  return (
411
  f"(Reasoning agent unavailable: {e})\n\n"
412
- "Please contact a licensed provider, Telehealth, or emergency services as appropriate."
413
  )
414
 
415
 
@@ -420,31 +400,38 @@ Using ONLY the above information:
420
  st.title("CareCall AI (Canada)")
421
  st.caption("Agentic, vision + voice + postal-aware health info companion. Not a doctor. Not a diagnosis.")
422
 
 
 
 
423
  st.markdown(
424
  """
425
- This demo:
426
- - Accepts a photo (rash, pill label, letter, etc.).
427
- - Accepts a voice note and/or typed description.
428
- - Optionally uses your **postal code** to tailor guidance to your province.
429
- - Uses:
430
- - OpenAI vision (for safe visual summary),
431
- - Whisper (for transcription),
432
- - Canadian open-data style tools (Drug Product Database, recalls, wait-time context),
433
- - A reasoning agent with strict safety rules.
 
 
 
 
434
  """
435
  )
436
 
437
- # Postal code
438
  st.markdown("### 1. Postal code (optional, Canada only)")
439
  postal_code = st.text_input(
440
- "Enter your Canadian postal code (e.g., M5V 2T6). Leave blank if you prefer not to share.",
441
- max_chars=7
442
  )
443
 
444
- # Image upload
445
  st.markdown("### 2. Upload an image (optional)")
446
  image_file = st.file_uploader(
447
- "Upload a photo (JPG/PNG). Avoid sharing highly identifying or explicit content.",
448
  type=["jpg", "jpeg", "png"],
449
  accept_multiple_files=False,
450
  )
@@ -452,8 +439,9 @@ image_file = st.file_uploader(
452
  image_bytes = None
453
  if image_file is not None:
454
  try:
455
- img = Image.open(image_file)
456
- st.image(img, caption="Image received", use_column_width=True)
 
457
  buf = io.BytesIO()
458
  img.save(buf, format="JPEG")
459
  image_bytes = buf.getvalue()
@@ -461,7 +449,7 @@ if image_file is not None:
461
  st.warning(f"Could not read that image: {e}")
462
  image_bytes = None
463
 
464
- # Audio upload
465
  st.markdown("### 3. Add a voice note (optional)")
466
  audio_file = st.file_uploader(
467
  "Upload a short voice note (wav/mp3/m4a).",
@@ -469,9 +457,10 @@ audio_file = st.file_uploader(
469
  accept_multiple_files=False,
470
  )
471
 
472
- # Text description
 
473
  user_text = st.text_area(
474
- "Or type a short description:",
475
  placeholder='Example: "Red itchy patch on forearm for 3 days, mild burning, no fever, using a new soap."',
476
  height=120,
477
  )
@@ -493,7 +482,7 @@ if st.button("Run CareCall Agent"):
493
  if audio_file is not None:
494
  voice_text = call_asr(audio_file)
495
 
496
- # Candidates for DPD lookup
497
  combined_for_terms = " ".join(
498
  t for t in [user_text or "", voice_text or "", vision_summary or ""] if t
499
  )
@@ -503,12 +492,14 @@ if st.button("Run CareCall Agent"):
503
  # Recalls
504
  recalls_context = tool_get_recent_recalls_snippet()
505
 
506
- # Wait-time awareness
507
  wait_times_context = tool_get_wait_times_awareness()
508
 
509
- # Region context from postal
510
- region_context = tool_region_context(postal_code) if postal_code else (
511
- "No postal code provided. Use Canada-wide guidance and suggest provincial resources."
 
 
512
  )
513
 
514
  # Final reasoning
@@ -528,15 +519,16 @@ if st.button("Run CareCall Agent"):
528
  st.markdown(
529
  """
530
  ---
531
- Critical Safety Notice
532
-
533
- - This tool does not provide medical diagnoses, prescriptions, or emergency triage.
534
- - It may be incomplete or incorrect and must not replace a doctor, nurse, pharmacist,
535
- Telehealth service, or emergency care.
536
- - If you have severe or worsening symptoms (such as chest pain, difficulty breathing,
537
- signs of stroke, heavy bleeding, suicidal thoughts, major injury, high fever with stiff neck),
538
- call 911 or go to the nearest emergency department immediately.
539
- - For dependable information, consult:
540
- Health Canada, the Public Health Agency of Canada, CIHI, and your provincial/territorial health services.
 
541
  """
542
  )
 
18
  )
19
 
20
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
 
 
21
  client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None
22
 
23
+ VISION_MODEL = "gpt-4.1-mini" # vision-capable OpenAI model
24
+ REASONING_MODEL = "gpt-4.1-mini" # reasoning / agent model
25
+ ASR_MODEL = "whisper-1" # transcription model
 
26
 
27
+ # Health Canada Drug Product Database API
28
  DPD_BASE_URL = "https://health-products.canada.ca/api/drug/drugproduct"
29
 
30
+ # Recalls & Safety Alerts JSON feed (example; can override via env if GC changes path)
31
  RECALLS_FEED_URL = os.getenv(
32
  "RECALLS_FEED_URL",
33
  "https://recalls-rappels.canada.ca/static-data/items-en.json"
34
  )
35
 
36
+ # CIHI wait-times info (general reference)
37
  WAIT_TIMES_INFO_URL = "https://www.cihi.ca/en/topics/access-and-wait-times"
38
 
39
+ # Postal code to province mapping (first letter)
40
  POSTAL_PREFIX_TO_PROVINCE = {
41
  "A": "Newfoundland and Labrador",
42
  "B": "Nova Scotia",
 
55
  "T": "Alberta",
56
  "V": "British Columbia",
57
  "X": "Northwest Territories / Nunavut",
58
+ "Y": "Yukon",
59
  }
60
 
61
 
62
  # =========================
63
+ # GENERIC HELPERS
64
  # =========================
65
 
66
  def safe_get_json(url, params=None, timeout=6):
 
83
 
84
 
85
  # =========================
86
+ # REGION / POSTAL HELPERS
87
  # =========================
88
 
89
  def infer_province_from_postal(postal_code: str):
 
 
 
90
  if not postal_code:
91
  return None
92
  pc = postal_code.strip().upper().replace(" ", "")
93
  if not pc:
94
  return None
95
+ return POSTAL_PREFIX_TO_PROVINCE.get(pc[0])
 
96
 
97
 
98
  def tool_region_context(postal_code: str) -> str:
 
 
 
 
 
 
99
  province = infer_province_from_postal(postal_code)
100
  if not postal_code:
101
+ return "No postal code provided. Use general Canada-wide guidance."
 
102
 
103
  if not province:
104
  return (
105
+ f"Postal code '{postal_code}' could not be mapped reliably. "
106
+ "Provide Canada-wide guidance and advise checking local provincial health websites and Telehealth services."
107
  )
108
 
 
109
  telehealth_note = (
110
+ f"In {province}, suggest the official provincial Telehealth / 811-style nurse advice line "
111
+ "and Ministry of Health website for region-specific guidance."
112
  )
 
113
  wait_note = (
114
+ "For non-urgent issues, users can check provincial or regional health authority pages and CIHI indicators "
115
+ f"for access and wait-time information. Reference (general): {WAIT_TIMES_INFO_URL}"
 
116
  )
117
 
118
  return (
119
+ f"User postal code: '{postal_code}' mapped to {province}.\n"
120
  f"{telehealth_note}\n"
121
  f"{wait_note}"
122
  )
123
 
124
 
125
  # =========================
126
+ # DRUG PRODUCT LOOKUP (DPD)
127
  # =========================
128
 
129
  def tool_lookup_drug_products(candidates):
130
  if not candidates:
131
+ return "No clear medication/product names detected that require a Drug Product Database lookup."
132
 
133
  found_lines = []
134
  for name in candidates[:5]:
135
+ params = {"lang": "en", "type": "json", "brandname": name}
 
 
 
 
136
  data = safe_get_json(DPD_BASE_URL, params=params)
137
  if not data:
138
  continue
139
 
140
  unique_brands = set()
141
  for item in data[:3]:
142
+ b = item.get("brand_name") or item.get("brandname") or name
 
 
 
 
143
  if b:
144
  unique_brands.add(b)
145
 
 
156
  )
157
 
158
  found_lines.append(
159
+ "For definitive product information, users must consult the official Health Canada Drug Product Database."
 
160
  )
161
  return "\n".join(found_lines)
162
 
163
 
164
  # =========================
165
+ # RECALLS & SAFETY ALERTS
166
  # =========================
167
 
168
  def tool_get_recent_recalls_snippet():
169
  data = safe_get_json(RECALLS_FEED_URL)
170
+ items = []
171
+
172
+ if isinstance(data, list):
173
+ items = data[:5]
174
+ elif isinstance(data, dict):
175
+ # some feeds use { items: [...] }
176
+ items = (data.get("items") or data.get("results") or [])[:5]
177
+
178
+ if not items:
179
  return (
180
+ "Unable to load recent recalls. Direct users to the official Government of Canada "
181
  "Recalls and Safety Alerts website for up-to-date information."
182
  )
183
 
184
+ lines = ["Recent recalls & safety alerts (high-level snapshot):"]
 
185
  for item in items:
186
  title = (
187
  item.get("title")
188
  or item.get("english_title")
189
+ or item.get("name")
190
  or "Recall / Alert"
191
  )
192
  date = item.get("date_published") or item.get("date") or ""
 
199
 
200
 
201
  # =========================
202
+ # WAIT TIMES AWARENESS (GENERIC)
203
  # =========================
204
 
205
  def tool_get_wait_times_awareness():
206
  return textwrap.dedent(f"""
207
+ Use Canadian open-data wait-time indicators conceptually:
208
+ - CIHI and several provinces publish average wait-time dashboards.
209
+ - These are not real-time triage.
210
  Guidance:
211
+ - For mild/non-urgent issues: consider local clinics or urgent care; check provincial resources.
212
+ - For red-flag or severe symptoms: skip tools and go directly to ER or call 911.
213
+ General reference: {WAIT_TIMES_INFO_URL}
214
  """).strip()
215
 
216
 
217
  # =========================
218
+ # TERM EXTRACTION
219
  # =========================
220
 
221
  def extract_candidate_terms(text: str):
 
230
 
231
 
232
  # =========================
233
+ # OPENAI CALLS
234
  # =========================
235
 
236
  def call_vision_summarizer(image_bytes: bytes) -> str:
 
238
  return ""
239
 
240
  b64 = base64.b64encode(image_bytes).decode("utf-8")
241
+
242
  prompt = (
243
  "You are a cautious Canadian health-information assistant.\n"
244
+ "Describe only what is visibly observable in neutral terms.\n"
245
+ "- If skin: describe general appearance (location, size, colour, pattern). Do NOT name diseases.\n"
246
+ "- If pill/box: mention visible drug name/strength/markings if legible.\n"
247
+ "- If document: say what type (e.g., lab report, appointment letter) without copying identifiers.\n"
248
+ "- If irrelevant/unclear: say that it is not medically interpretable.\n"
249
+ "Never diagnose. Never prescribe. Limit to about 80-100 words."
250
  )
251
 
252
  try:
 
322
  You are CareCall AI, an agentic Canadian health information assistant.
323
 
324
  Rules:
325
+ - Do NOT diagnose.
326
+ - Do NOT prescribe or give dosing.
327
+ - Do NOT claim exact real-time wait times.
328
+ - Use only the information and tool outputs provided.
329
+ - Use open-data-style context (Drug Product DB, recalls, wait-time awareness, region) to improve safety.
330
+ - Map concerns to broad categories only (e.g., irritation vs infection vs allergy vs medication vs admin).
331
+ - Give practical next steps in Canada:
332
+ * self-care when clearly mild,
333
+ * pharmacist / family doctor / walk-in clinic,
334
+ * provincial Telehealth / nurse line,
335
+ * ER / 911 for red-flag symptoms.
336
+ - Red flags (chest pain, breathing trouble, stroke signs, severe bleeding, suicidal thoughts,
337
+ major trauma, high fever with stiff neck, rapidly worsening symptoms, etc.) -> clearly say ER/911.
338
+ - Mention official sources: Health Canada, Public Health Agency of Canada, CIHI,
339
+ and the user's provincial health services.
340
+ - Maximum length: 350 words. Clear and calm.
 
 
 
341
  """
342
 
343
  user_prompt = f"""
344
+ INPUT CONTEXT
345
 
346
  1) User narrative:
347
  {narrative}
 
363
 
364
  TASK
365
 
366
+ Based only on this information:
367
+
368
+ 1. Briefly summarize what the user seems concerned about (neutral; no diagnosis).
369
+ 2. Briefly outline broad possible categories (without asserting any specific disease).
370
+ 3. Provide clear next steps in bullet form, aligned with Canadian context and region info where available:
371
+ - when self-care might be reasonable,
372
+ - when to speak with a pharmacist, family doctor, or walk-in clinic,
373
+ - when to use provincial Telehealth / nurse lines,
374
+ - when to go to ER or call 911.
375
+ 4. If medication/recall context looks relevant, remind them to confirm via official Health Canada resources.
376
+ 5. End with a short disclaimer that this is informational only and not medical care.
377
  """
378
 
379
  try:
 
389
  except Exception as e:
390
  return (
391
  f"(Reasoning agent unavailable: {e})\n\n"
392
+ "Please contact a licensed provider, Telehealth service, or emergency care as appropriate."
393
  )
394
 
395
 
 
400
  st.title("CareCall AI (Canada)")
401
  st.caption("Agentic, vision + voice + postal-aware health info companion. Not a doctor. Not a diagnosis.")
402
 
403
+ if not OPENAI_API_KEY:
404
+ st.warning("Set OPENAI_API_KEY in your Hugging Face Space secrets to enable the AI backend.")
405
+
406
  st.markdown(
407
  """
408
+ **How it works**
409
+
410
+ 1. Optionally enter your **Canadian postal code** (to tailor guidance to your province).
411
+ 2. Optionally upload a **photo** (rash, pill bottle, letter, etc.).
412
+ 3. Optionally upload a **voice note**, and/or type a **short description**.
413
+ 4. The agent:
414
+ - analyzes the image with a vision model (safely),
415
+ - transcribes your voice with Whisper,
416
+ - looks up related info via:
417
+ - Health Canada Drug Product Database (high-level),
418
+ - Recalls & Safety Alerts feed (snapshot),
419
+ - CIHI/provincial wait-time context (awareness),
420
+ - then generates structured, non-diagnostic next steps.
421
  """
422
  )
423
 
424
+ # 1) Postal code
425
  st.markdown("### 1. Postal code (optional, Canada only)")
426
  postal_code = st.text_input(
427
+ "Canadian postal code (e.g., M5V 2T6). Leave blank to keep guidance general.",
428
+ max_chars=7,
429
  )
430
 
431
+ # 2) Image upload
432
  st.markdown("### 2. Upload an image (optional)")
433
  image_file = st.file_uploader(
434
+ "Upload a photo (JPG/PNG). Avoid very identifying or explicit content.",
435
  type=["jpg", "jpeg", "png"],
436
  accept_multiple_files=False,
437
  )
 
439
  image_bytes = None
440
  if image_file is not None:
441
  try:
442
+ # Convert to RGB to avoid RGBA -> JPEG error
443
+ img = Image.open(image_file).convert("RGB")
444
+ st.image(img, caption="Image received", use_container_width=True)
445
  buf = io.BytesIO()
446
  img.save(buf, format="JPEG")
447
  image_bytes = buf.getvalue()
 
449
  st.warning(f"Could not read that image: {e}")
450
  image_bytes = None
451
 
452
+ # 3) Audio upload
453
  st.markdown("### 3. Add a voice note (optional)")
454
  audio_file = st.file_uploader(
455
  "Upload a short voice note (wav/mp3/m4a).",
 
457
  accept_multiple_files=False,
458
  )
459
 
460
+ # 4) Text description
461
+ st.markdown("### 4. Or type a short description")
462
  user_text = st.text_area(
463
+ "Describe what’s going on:",
464
  placeholder='Example: "Red itchy patch on forearm for 3 days, mild burning, no fever, using a new soap."',
465
  height=120,
466
  )
 
482
  if audio_file is not None:
483
  voice_text = call_asr(audio_file)
484
 
485
+ # Candidate terms (for Drug DB)
486
  combined_for_terms = " ".join(
487
  t for t in [user_text or "", voice_text or "", vision_summary or ""] if t
488
  )
 
492
  # Recalls
493
  recalls_context = tool_get_recent_recalls_snippet()
494
 
495
+ # Wait-times awareness
496
  wait_times_context = tool_get_wait_times_awareness()
497
 
498
+ # Region context
499
+ region_context = (
500
+ tool_region_context(postal_code)
501
+ if postal_code
502
+ else "No postal code provided. Use Canada-wide guidance and suggest provincial resources."
503
  )
504
 
505
  # Final reasoning
 
519
  st.markdown(
520
  """
521
  ---
522
+ **Critical Safety Notice**
523
+
524
+ - This tool does **not** provide medical diagnoses, prescriptions, or emergency triage.
525
+ - It may be incomplete or incorrect and must **not** replace a doctor, nurse, pharmacist,
526
+ Telehealth service, or emergency department.
527
+ - If you have severe or rapidly worsening symptoms (chest pain, trouble breathing, signs of stroke,
528
+ heavy bleeding, suicidal thoughts, major injury, high fever with stiff neck, etc.),
529
+ call **911** or go to the **nearest emergency department** immediately.
530
+ - For trusted information, consult:
531
+ Health Canada, the Public Health Agency of Canada, CIHI,
532
+ and your provincial/territorial health services.
533
  """
534
  )