Update app.py
Browse files
app.py
CHANGED
|
@@ -20,14 +20,14 @@ st.set_page_config(
|
|
| 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
|
| 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 (
|
| 31 |
RECALLS_FEED_URL = os.getenv(
|
| 32 |
"RECALLS_FEED_URL",
|
| 33 |
"https://recalls-rappels.canada.ca/static-data/items-en.json"
|
|
@@ -36,7 +36,7 @@ RECALLS_FEED_URL = os.getenv(
|
|
| 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",
|
|
@@ -73,7 +73,7 @@ def safe_get_json(url, params=None, timeout=6):
|
|
| 73 |
return None
|
| 74 |
|
| 75 |
|
| 76 |
-
def shorten(text, max_chars=
|
| 77 |
if not text:
|
| 78 |
return ""
|
| 79 |
text = text.strip()
|
|
@@ -107,12 +107,12 @@ def tool_region_context(postal_code: str) -> str:
|
|
| 107 |
)
|
| 108 |
|
| 109 |
telehealth_note = (
|
| 110 |
-
f"In {province},
|
| 111 |
-
"
|
| 112 |
)
|
| 113 |
wait_note = (
|
| 114 |
-
"For non-urgent issues,
|
| 115 |
-
f"for access
|
| 116 |
)
|
| 117 |
|
| 118 |
return (
|
|
@@ -122,6 +122,74 @@ def tool_region_context(postal_code: str) -> str:
|
|
| 122 |
)
|
| 123 |
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
# =========================
|
| 126 |
# DRUG PRODUCT LOOKUP (DPD)
|
| 127 |
# =========================
|
|
@@ -156,7 +224,7 @@ def tool_lookup_drug_products(candidates):
|
|
| 156 |
)
|
| 157 |
|
| 158 |
found_lines.append(
|
| 159 |
-
"For definitive product information,
|
| 160 |
)
|
| 161 |
return "\n".join(found_lines)
|
| 162 |
|
|
@@ -172,7 +240,6 @@ def tool_get_recent_recalls_snippet():
|
|
| 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:
|
|
@@ -181,7 +248,7 @@ def tool_get_recent_recalls_snippet():
|
|
| 181 |
"Recalls and Safety Alerts website for up-to-date information."
|
| 182 |
)
|
| 183 |
|
| 184 |
-
lines = ["Recent recalls & safety alerts (
|
| 185 |
for item in items:
|
| 186 |
title = (
|
| 187 |
item.get("title")
|
|
@@ -191,9 +258,10 @@ def tool_get_recent_recalls_snippet():
|
|
| 191 |
)
|
| 192 |
date = item.get("date_published") or item.get("date") or ""
|
| 193 |
category = item.get("category") or item.get("type") or ""
|
| 194 |
-
lines.append(f"- {shorten(title
|
|
|
|
| 195 |
lines.append(
|
| 196 |
-
"For
|
| 197 |
)
|
| 198 |
return "\n".join(lines)
|
| 199 |
|
|
@@ -205,11 +273,11 @@ def tool_get_recent_recalls_snippet():
|
|
| 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
|
| 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 |
|
|
@@ -241,12 +309,12 @@ def call_vision_summarizer(image_bytes: bytes) -> str:
|
|
| 241 |
|
| 242 |
prompt = (
|
| 243 |
"You are a cautious Canadian health-information assistant.\n"
|
| 244 |
-
"Describe only
|
| 245 |
-
"- If skin
|
| 246 |
-
"- If
|
| 247 |
-
"- If
|
| 248 |
-
"- If
|
| 249 |
-
"Never diagnose. Never prescribe.
|
| 250 |
)
|
| 251 |
|
| 252 |
try:
|
|
@@ -302,11 +370,12 @@ def call_reasoning_agent(
|
|
| 302 |
recalls_context: str,
|
| 303 |
wait_times_context: str,
|
| 304 |
region_context: str,
|
|
|
|
| 305 |
) -> str:
|
| 306 |
if not client:
|
| 307 |
return (
|
| 308 |
"AI backend not configured. With a valid API key, this agent would combine your inputs and "
|
| 309 |
-
"open-data context into cautious,
|
| 310 |
)
|
| 311 |
|
| 312 |
narrative_parts = []
|
|
@@ -321,23 +390,43 @@ def call_reasoning_agent(
|
|
| 321 |
system_prompt = """
|
| 322 |
You are CareCall AI, an agentic Canadian health information assistant.
|
| 323 |
|
| 324 |
-
|
| 325 |
-
-
|
| 326 |
-
-
|
| 327 |
-
-
|
| 328 |
-
- Use
|
| 329 |
-
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
-
|
| 339 |
-
|
| 340 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
"""
|
| 342 |
|
| 343 |
user_prompt = f"""
|
|
@@ -358,22 +447,25 @@ INPUT CONTEXT
|
|
| 358 |
5) Wait-time awareness context:
|
| 359 |
{wait_times_context}
|
| 360 |
|
| 361 |
-
6) Region (postal
|
| 362 |
{region_context}
|
| 363 |
|
|
|
|
|
|
|
|
|
|
| 364 |
TASK
|
| 365 |
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
1.
|
| 369 |
-
2.
|
| 370 |
-
3.
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
"""
|
| 378 |
|
| 379 |
try:
|
|
@@ -389,7 +481,7 @@ Based only on this information:
|
|
| 389 |
except Exception as e:
|
| 390 |
return (
|
| 391 |
f"(Reasoning agent unavailable: {e})\n\n"
|
| 392 |
-
"Please contact a licensed provider, Telehealth
|
| 393 |
)
|
| 394 |
|
| 395 |
|
|
@@ -401,37 +493,35 @@ 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
|
| 405 |
|
| 406 |
st.markdown(
|
| 407 |
"""
|
| 408 |
-
**How
|
| 409 |
-
|
| 410 |
-
1.
|
| 411 |
-
2.
|
| 412 |
-
3.
|
| 413 |
-
4.
|
| 414 |
-
-
|
| 415 |
-
-
|
| 416 |
-
-
|
| 417 |
-
|
| 418 |
-
|
| 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
|
| 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
|
| 435 |
type=["jpg", "jpeg", "png"],
|
| 436 |
accept_multiple_files=False,
|
| 437 |
)
|
|
@@ -439,7 +529,7 @@ image_file = st.file_uploader(
|
|
| 439 |
image_bytes = None
|
| 440 |
if image_file is not None:
|
| 441 |
try:
|
| 442 |
-
# Convert to RGB
|
| 443 |
img = Image.open(image_file).convert("RGB")
|
| 444 |
st.image(img, caption="Image received", use_container_width=True)
|
| 445 |
buf = io.BytesIO()
|
|
@@ -461,7 +551,7 @@ audio_file = st.file_uploader(
|
|
| 461 |
st.markdown("### 4. Or type a short description")
|
| 462 |
user_text = st.text_area(
|
| 463 |
"Describe what’s going on:",
|
| 464 |
-
placeholder='Example: "
|
| 465 |
height=120,
|
| 466 |
)
|
| 467 |
|
|
@@ -482,7 +572,7 @@ if st.button("Run CareCall Agent"):
|
|
| 482 |
if audio_file is not None:
|
| 483 |
voice_text = call_asr(audio_file)
|
| 484 |
|
| 485 |
-
# Candidate terms
|
| 486 |
combined_for_terms = " ".join(
|
| 487 |
t for t in [user_text or "", voice_text or "", vision_summary or ""] if t
|
| 488 |
)
|
|
@@ -492,14 +582,20 @@ if st.button("Run CareCall Agent"):
|
|
| 492 |
# Recalls
|
| 493 |
recalls_context = tool_get_recent_recalls_snippet()
|
| 494 |
|
| 495 |
-
# Wait-
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 503 |
)
|
| 504 |
|
| 505 |
# Final reasoning
|
|
@@ -511,24 +607,8 @@ if st.button("Run CareCall Agent"):
|
|
| 511 |
recalls_context=recalls_context,
|
| 512 |
wait_times_context=wait_times_context,
|
| 513 |
region_context=region_context,
|
|
|
|
| 514 |
)
|
| 515 |
|
| 516 |
st.markdown("### CareCall AI Summary (Informational Only)")
|
| 517 |
st.write(final_answer)
|
| 518 |
-
|
| 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 |
-
)
|
|
|
|
| 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 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 (can override with env variable if path changes)
|
| 31 |
RECALLS_FEED_URL = os.getenv(
|
| 32 |
"RECALLS_FEED_URL",
|
| 33 |
"https://recalls-rappels.canada.ca/static-data/items-en.json"
|
|
|
|
| 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 (by first letter)
|
| 40 |
POSTAL_PREFIX_TO_PROVINCE = {
|
| 41 |
"A": "Newfoundland and Labrador",
|
| 42 |
"B": "Nova Scotia",
|
|
|
|
| 73 |
return None
|
| 74 |
|
| 75 |
|
| 76 |
+
def shorten(text, max_chars=280):
|
| 77 |
if not text:
|
| 78 |
return ""
|
| 79 |
text = text.strip()
|
|
|
|
| 107 |
)
|
| 108 |
|
| 109 |
telehealth_note = (
|
| 110 |
+
f"In {province}, advise the user to use the official provincial Telehealth or 811-style nurse advice line "
|
| 111 |
+
"for real-time clinical advice."
|
| 112 |
)
|
| 113 |
wait_note = (
|
| 114 |
+
"For non-urgent issues, they can check provincial or regional health authority websites and CIHI indicators "
|
| 115 |
+
f"for access/wait-time information. Reference (general): {WAIT_TIMES_INFO_URL}"
|
| 116 |
)
|
| 117 |
|
| 118 |
return (
|
|
|
|
| 122 |
)
|
| 123 |
|
| 124 |
|
| 125 |
+
def tool_find_nearby_clinics_and_waits(postal_code: str) -> str:
|
| 126 |
+
"""
|
| 127 |
+
Province-aware pointers to official 'find a clinic' / wait-time resources.
|
| 128 |
+
Not real-time; we send the user to authoritative tools.
|
| 129 |
+
"""
|
| 130 |
+
province = infer_province_from_postal(postal_code)
|
| 131 |
+
if not postal_code or not province:
|
| 132 |
+
return (
|
| 133 |
+
"No precise local clinic data. Suggest the user search 'walk-in clinic near me' plus their city, and use "
|
| 134 |
+
"their provincial health website or health authority finder for verified locations and hours."
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
base = f"For users in {province}, suggest these options to find nearby care and typical waits:\n"
|
| 138 |
+
province_links = []
|
| 139 |
+
|
| 140 |
+
# Very high-level, non-exhaustive hints (you can refine for your production app)
|
| 141 |
+
if province == "Ontario":
|
| 142 |
+
province_links.append(
|
| 143 |
+
"- Use Ontario's official 'Health811' and 'Find a clinic/hospital' tools from Ontario Health."
|
| 144 |
+
)
|
| 145 |
+
elif province == "British Columbia":
|
| 146 |
+
province_links.append(
|
| 147 |
+
"- Use HealthLink BC (811) and the 'Find Health Services' directory for clinics and urgent care."
|
| 148 |
+
)
|
| 149 |
+
elif province == "Alberta":
|
| 150 |
+
province_links.append(
|
| 151 |
+
"- Use Alberta Health Services 'Find Healthcare' directory for walk-ins and urgent care clinics."
|
| 152 |
+
)
|
| 153 |
+
elif province == "Quebec":
|
| 154 |
+
province_links.append(
|
| 155 |
+
"- Use your local CISSS/CIUSSS websites and provincial resources (e.g., Bonjour-santé) for clinic bookings."
|
| 156 |
+
)
|
| 157 |
+
elif province == "Manitoba":
|
| 158 |
+
province_links.append(
|
| 159 |
+
"- Use Manitoba Health / regional health authority sites to locate walk-in and primary care clinics."
|
| 160 |
+
)
|
| 161 |
+
elif province == "Saskatchewan":
|
| 162 |
+
province_links.append(
|
| 163 |
+
"- Use Saskatchewan Health Authority tools to find urgent care and clinics."
|
| 164 |
+
)
|
| 165 |
+
elif province == "Nova Scotia":
|
| 166 |
+
province_links.append(
|
| 167 |
+
"- Use Nova Scotia Health 'Find a Location/Service' for primary care and walk-in options."
|
| 168 |
+
)
|
| 169 |
+
elif province == "New Brunswick":
|
| 170 |
+
province_links.append(
|
| 171 |
+
"- Use Horizon / Vitalité Health Network resources for clinics and after-hours care."
|
| 172 |
+
)
|
| 173 |
+
elif province == "Newfoundland and Labrador":
|
| 174 |
+
province_links.append(
|
| 175 |
+
"- Use provincial health authority websites to locate clinics and ERs."
|
| 176 |
+
)
|
| 177 |
+
elif province == "Prince Edward Island":
|
| 178 |
+
province_links.append(
|
| 179 |
+
"- Use Health PEI resources to find walk-in clinics and primary care."
|
| 180 |
+
)
|
| 181 |
+
else:
|
| 182 |
+
province_links.append(
|
| 183 |
+
"- Use your territorial/provincial health authority website and Telehealth for locations and typical waits."
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
province_links.append(
|
| 187 |
+
"- For emergencies or red-flag symptoms, emphasize going to the nearest emergency department or calling 911 immediately."
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
return base + "\n".join(province_links)
|
| 191 |
+
|
| 192 |
+
|
| 193 |
# =========================
|
| 194 |
# DRUG PRODUCT LOOKUP (DPD)
|
| 195 |
# =========================
|
|
|
|
| 224 |
)
|
| 225 |
|
| 226 |
found_lines.append(
|
| 227 |
+
"For definitive product information, they must consult the official Health Canada Drug Product Database."
|
| 228 |
)
|
| 229 |
return "\n".join(found_lines)
|
| 230 |
|
|
|
|
| 240 |
if isinstance(data, list):
|
| 241 |
items = data[:5]
|
| 242 |
elif isinstance(data, dict):
|
|
|
|
| 243 |
items = (data.get("items") or data.get("results") or [])[:5]
|
| 244 |
|
| 245 |
if not items:
|
|
|
|
| 248 |
"Recalls and Safety Alerts website for up-to-date information."
|
| 249 |
)
|
| 250 |
|
| 251 |
+
lines = ["Recent recalls & safety alerts (snapshot):"]
|
| 252 |
for item in items:
|
| 253 |
title = (
|
| 254 |
item.get("title")
|
|
|
|
| 258 |
)
|
| 259 |
date = item.get("date_published") or item.get("date") or ""
|
| 260 |
category = item.get("category") or item.get("type") or ""
|
| 261 |
+
lines.append(f"- {shorten(title)} ({category}, {date})")
|
| 262 |
+
|
| 263 |
lines.append(
|
| 264 |
+
"For details or to check a specific product, direct users to the official Recalls and Safety Alerts portal."
|
| 265 |
)
|
| 266 |
return "\n".join(lines)
|
| 267 |
|
|
|
|
| 273 |
def tool_get_wait_times_awareness():
|
| 274 |
return textwrap.dedent(f"""
|
| 275 |
Use Canadian open-data wait-time indicators conceptually:
|
| 276 |
+
- CIHI and several provinces publish average wait-time dashboards for ER, surgeries, diagnostics.
|
| 277 |
+
- These are not real-time triage tools.
|
| 278 |
Guidance:
|
| 279 |
+
- For mild issues: local clinics/urgent care may be suitable; check provincial wait-time/clinic-finder tools.
|
| 280 |
+
- For red-flag or severe symptoms: skip online tools and go directly to ER or call 911.
|
| 281 |
General reference: {WAIT_TIMES_INFO_URL}
|
| 282 |
""").strip()
|
| 283 |
|
|
|
|
| 309 |
|
| 310 |
prompt = (
|
| 311 |
"You are a cautious Canadian health-information assistant.\n"
|
| 312 |
+
"Describe only visible features in neutral terms.\n"
|
| 313 |
+
"- If skin/toenail etc.: describe colour, texture, dryness, cracks, swelling, but DO NOT name diseases.\n"
|
| 314 |
+
"- If medication label: read visible drug name/strength if legible.\n"
|
| 315 |
+
"- If health letter/report: say what type it appears to be, without copying identifiers.\n"
|
| 316 |
+
"- If unclear: say it is not medically interpretable.\n"
|
| 317 |
+
"Never diagnose. Never prescribe. 80-100 words max."
|
| 318 |
)
|
| 319 |
|
| 320 |
try:
|
|
|
|
| 370 |
recalls_context: str,
|
| 371 |
wait_times_context: str,
|
| 372 |
region_context: str,
|
| 373 |
+
nearby_services_context: str,
|
| 374 |
) -> str:
|
| 375 |
if not client:
|
| 376 |
return (
|
| 377 |
"AI backend not configured. With a valid API key, this agent would combine your inputs and "
|
| 378 |
+
"open-data context into cautious, structured guidance."
|
| 379 |
)
|
| 380 |
|
| 381 |
narrative_parts = []
|
|
|
|
| 390 |
system_prompt = """
|
| 391 |
You are CareCall AI, an agentic Canadian health information assistant.
|
| 392 |
|
| 393 |
+
HARD RULES:
|
| 394 |
+
- DO NOT give a medical diagnosis.
|
| 395 |
+
- DO NOT prescribe or specify individual dosing.
|
| 396 |
+
- DO NOT guarantee exact real-time wait times or locations.
|
| 397 |
+
- Use ONLY the provided content and tool outputs (vision summary, DPD, recalls, wait-times context, region info, nearby services guide).
|
| 398 |
+
- You MUST classify guidance into a clear, practical structure without pretending to be a doctor.
|
| 399 |
+
|
| 400 |
+
WHAT YOU SHOULD DO:
|
| 401 |
+
|
| 402 |
+
1. Identify red-flag indicators (e.g., chest pain, trouble breathing, stroke signs, rapidly spreading infection,
|
| 403 |
+
severe pain, heavy bleeding, high fever with stiff neck, suicidal thoughts, major trauma).
|
| 404 |
+
- If any such red flags are present or strongly implied:
|
| 405 |
+
PRIMARY PATHWAY = ER/911.
|
| 406 |
+
2. If no red flags and issue seems mild/local (e.g., minor rash, mild toe pain, dry skin, chipped nail, etc.):
|
| 407 |
+
- You may choose PRIMARY PATHWAY = Home care + possibly pharmacist.
|
| 408 |
+
3. If there is uncertainty, ongoing pain, risk factors (diabetes, immune issues, poor circulation),
|
| 409 |
+
or symptoms that are not clearly mild:
|
| 410 |
+
- You may choose PRIMARY PATHWAY = Clinic / family doctor / Telehealth.
|
| 411 |
+
|
| 412 |
+
ALWAYS:
|
| 413 |
+
|
| 414 |
+
- Output a section called "Primary Recommended Pathway" with ONE of:
|
| 415 |
+
* "Home care likely reasonable (monitor closely)"
|
| 416 |
+
* "Pharmacist / Walk-in clinic / Family doctor recommended"
|
| 417 |
+
* "Call provincial Telehealth / nurse line for guidance"
|
| 418 |
+
* "Go to Emergency / Call 911 now"
|
| 419 |
+
- Then:
|
| 420 |
+
* Give a short explanation for WHY that pathway was chosen.
|
| 421 |
+
* Provide a "Home Care (if appropriate)" section with 3–6 simple, generic measures
|
| 422 |
+
(e.g., keep area clean/dry, rest, elevation, cold compress, OTC pain relief as per label, avoid friction),
|
| 423 |
+
ONLY when not clearly contradicted.
|
| 424 |
+
* Provide "When to seek clinic/Telehealth" with concrete triggers.
|
| 425 |
+
* Provide "When this is an emergency" with explicit red-flag bullets.
|
| 426 |
+
* Mention provincial/nearby resources using the region and nearby-services context.
|
| 427 |
+
* If medications/recalls context is relevant, remind the user to verify via Health Canada DPD and Recalls portal.
|
| 428 |
+
- Keep total answer under ~350 words.
|
| 429 |
+
- Reiterate that this is informational only, not medical care, and that professionals/Telehealth/ER override all advice.
|
| 430 |
"""
|
| 431 |
|
| 432 |
user_prompt = f"""
|
|
|
|
| 447 |
5) Wait-time awareness context:
|
| 448 |
{wait_times_context}
|
| 449 |
|
| 450 |
+
6) Region (postal code) context:
|
| 451 |
{region_context}
|
| 452 |
|
| 453 |
+
7) Nearby services & finder resources:
|
| 454 |
+
{nearby_services_context}
|
| 455 |
+
|
| 456 |
TASK
|
| 457 |
|
| 458 |
+
Using ONLY the above, produce a structured response with these sections:
|
| 459 |
+
|
| 460 |
+
1. "Primary Recommended Pathway": one of the four options (see system rules) + 1–2 sentence reasoning.
|
| 461 |
+
2. "What this might relate to (in general terms)": 3–5 bullets of broad categories, no diagnoses.
|
| 462 |
+
3. "Home Care (if appropriate)": 3–6 safe, generic self-care steps OR a note that home care alone is not advised.
|
| 463 |
+
4. "When to see a Pharmacist / Clinic / Telehealth": 3–6 concrete triggers.
|
| 464 |
+
5. "When this is an Emergency (ER/911)": bullet list of red-flag situations.
|
| 465 |
+
6. "Local & Official Resources": 2–5 bullets using the region + nearby-services context and open-data style info.
|
| 466 |
+
7. "Important Disclaimer": 2–4 lines stating this is informational only and not medical care.
|
| 467 |
+
|
| 468 |
+
Be firm and clear but never overconfident.
|
| 469 |
"""
|
| 470 |
|
| 471 |
try:
|
|
|
|
| 481 |
except Exception as e:
|
| 482 |
return (
|
| 483 |
f"(Reasoning agent unavailable: {e})\n\n"
|
| 484 |
+
"Please contact a licensed provider, Telehealth, or emergency services as appropriate."
|
| 485 |
)
|
| 486 |
|
| 487 |
|
|
|
|
| 493 |
st.caption("Agentic, vision + voice + postal-aware health info companion. Not a doctor. Not a diagnosis.")
|
| 494 |
|
| 495 |
if not OPENAI_API_KEY:
|
| 496 |
+
st.warning("Set OPENAI_API_KEY in your Space secrets to enable the AI backend.")
|
| 497 |
|
| 498 |
st.markdown(
|
| 499 |
"""
|
| 500 |
+
**How to use**
|
| 501 |
+
|
| 502 |
+
1. Enter your **Canadian postal code** (optional but helps tailor guidance).
|
| 503 |
+
2. Upload a **photo** related to your concern (e.g., rash, nail, pill bottle, letter) — optional.
|
| 504 |
+
3. Upload a **voice note** and/or type a **short description**.
|
| 505 |
+
4. Click **Run CareCall Agent** to get:
|
| 506 |
+
- A clear **primary recommended pathway**,
|
| 507 |
+
- Safe **home-care tips** when appropriate,
|
| 508 |
+
- Triggers for **clinic/Telehealth**,
|
| 509 |
+
- **Emergency red-flag guidance**,
|
| 510 |
+
- Links to **official & provincial resources**.
|
|
|
|
|
|
|
| 511 |
"""
|
| 512 |
)
|
| 513 |
|
| 514 |
# 1) Postal code
|
| 515 |
st.markdown("### 1. Postal code (optional, Canada only)")
|
| 516 |
postal_code = st.text_input(
|
| 517 |
+
"Canadian postal code (e.g., M5V 2T6). Leave blank for general guidance.",
|
| 518 |
max_chars=7,
|
| 519 |
)
|
| 520 |
|
| 521 |
# 2) Image upload
|
| 522 |
st.markdown("### 2. Upload an image (optional)")
|
| 523 |
image_file = st.file_uploader(
|
| 524 |
+
"Upload a photo (JPG/PNG). Avoid highly identifying or explicit content.",
|
| 525 |
type=["jpg", "jpeg", "png"],
|
| 526 |
accept_multiple_files=False,
|
| 527 |
)
|
|
|
|
| 529 |
image_bytes = None
|
| 530 |
if image_file is not None:
|
| 531 |
try:
|
| 532 |
+
# Convert to RGB so we can safely save as JPEG (avoids RGBA error)
|
| 533 |
img = Image.open(image_file).convert("RGB")
|
| 534 |
st.image(img, caption="Image received", use_container_width=True)
|
| 535 |
buf = io.BytesIO()
|
|
|
|
| 551 |
st.markdown("### 4. Or type a short description")
|
| 552 |
user_text = st.text_area(
|
| 553 |
"Describe what’s going on:",
|
| 554 |
+
placeholder='Example: "Pain and discoloration around big toenail when wearing shoes for 1 week, no fever, no major swelling."',
|
| 555 |
height=120,
|
| 556 |
)
|
| 557 |
|
|
|
|
| 572 |
if audio_file is not None:
|
| 573 |
voice_text = call_asr(audio_file)
|
| 574 |
|
| 575 |
+
# Candidate terms for Drug DB
|
| 576 |
combined_for_terms = " ".join(
|
| 577 |
t for t in [user_text or "", voice_text or "", vision_summary or ""] if t
|
| 578 |
)
|
|
|
|
| 582 |
# Recalls
|
| 583 |
recalls_context = tool_get_recent_recalls_snippet()
|
| 584 |
|
| 585 |
+
# Wait-time awareness
|
| 586 |
wait_times_context = tool_get_wait_times_awareness()
|
| 587 |
|
| 588 |
# Region context
|
| 589 |
region_context = (
|
| 590 |
tool_region_context(postal_code)
|
| 591 |
if postal_code
|
| 592 |
+
else "No postal code provided. Use Canada-wide guidance and mention provincial resources generally."
|
| 593 |
+
)
|
| 594 |
+
|
| 595 |
+
# Nearby clinics / wait finder resources
|
| 596 |
+
nearby_services_context = tool_find_nearby_clinics_and_waits(postal_code) if postal_code else (
|
| 597 |
+
"No postal code. Suggest searchable options like 'walk-in clinic near me' plus city, "
|
| 598 |
+
"and provincial clinic/ER finder tools."
|
| 599 |
)
|
| 600 |
|
| 601 |
# Final reasoning
|
|
|
|
| 607 |
recalls_context=recalls_context,
|
| 608 |
wait_times_context=wait_times_context,
|
| 609 |
region_context=region_context,
|
| 610 |
+
nearby_services_context=nearby_services_context,
|
| 611 |
)
|
| 612 |
|
| 613 |
st.markdown("### CareCall AI Summary (Informational Only)")
|
| 614 |
st.write(final_answer)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|