|
21 | 21 | _pending_intents: dict = {} |
22 | 22 |
|
23 | 23 |
|
| 24 | +async def _medhack_daily_case_loop(): |
| 25 | + """Background task that posts a new diagnosis case each day.""" |
| 26 | + import asyncio |
| 27 | + from .slack_client import get_channel_id, post_message |
| 28 | + from .utils import get_current_date |
| 29 | + |
| 30 | + # Wait a bit for the app to fully start |
| 31 | + await asyncio.sleep(10) |
| 32 | + |
| 33 | + while True: |
| 34 | + try: |
| 35 | + today = get_current_date() |
| 36 | + # Try medhack-testing first (for dev), then medhack-frontiers (for prod) |
| 37 | + channel_id = get_channel_id("medhack-testing") or get_channel_id("medhack-frontiers") |
| 38 | + if not channel_id: |
| 39 | + print("⚠️ MedHack: neither #medhack-testing nor #medhack-frontiers channel found, skipping daily case") |
| 40 | + await asyncio.sleep(3600) # Retry in an hour |
| 41 | + continue |
| 42 | + |
| 43 | + # Load the medhack client |
| 44 | + from pathlib import Path |
| 45 | + import sys |
| 46 | + settings = get_settings() |
| 47 | + skills_dir = Path(settings.SKILLS_DIR) / "medhack" |
| 48 | + client_path = skills_dir / "client.py" |
| 49 | + |
| 50 | + if not client_path.exists(): |
| 51 | + print("⚠️ MedHack client.py not found, skipping daily case") |
| 52 | + await asyncio.sleep(3600) |
| 53 | + continue |
| 54 | + |
| 55 | + # Import the client |
| 56 | + import importlib.util |
| 57 | + spec = importlib.util.spec_from_file_location("medhack_daily_client", client_path) |
| 58 | + mod = importlib.util.module_from_spec(spec) |
| 59 | + spec.loader.exec_module(mod) |
| 60 | + client = mod.MedHackClient() |
| 61 | + |
| 62 | + # Check if there's already a case for today |
| 63 | + current = client.get_current_case(today) |
| 64 | + if current is None: |
| 65 | + # Start a new case |
| 66 | + new_case = client.start_new_case(today) |
| 67 | + if new_case: |
| 68 | + difficulty = new_case.get("difficulty", "medium").upper() |
| 69 | + title = new_case.get("title", "") |
| 70 | + title_str = f' - _{title}_' if title else "" |
| 71 | + header = f"*GUESS THE DIAGNOSIS* - Daily Challenge [{difficulty}]{title_str}" |
| 72 | + |
| 73 | + # New-style cases have an ed_first_look narrative + triage note |
| 74 | + if new_case.get("ed_first_look"): |
| 75 | + scene = new_case["ed_first_look"].strip() |
| 76 | + triage = new_case["presenting_complaint"].strip() |
| 77 | + message = ( |
| 78 | + f"{header}\n\n" |
| 79 | + f"{scene}\n\n" |
| 80 | + f"*Triage note:* {triage}\n\n" |
| 81 | + f"Ask me questions about this patient to work towards the diagnosis. " |
| 82 | + f"When you're ready, tell me your diagnosis!\n\n" |
| 83 | + f"_You have 3 guesses. First correct answer wins 12 MLAI points " |
| 84 | + f"+ DM Dr Sam for a free ticket code to MedHack: Frontiers!_" |
| 85 | + ) |
| 86 | + else: |
| 87 | + complaint = new_case["presenting_complaint"].strip() |
| 88 | + message = ( |
| 89 | + f"{header}\n\n" |
| 90 | + f"{complaint}\n\n" |
| 91 | + f"Ask me questions about this patient to work towards the diagnosis. " |
| 92 | + f"When you're ready, tell me your diagnosis!\n\n" |
| 93 | + f"_You have 3 guesses. First correct answer wins 12 MLAI points " |
| 94 | + f"+ DM Dr Sam for a free ticket code to MedHack: Frontiers!_" |
| 95 | + ) |
| 96 | + post_message(channel=channel_id, text=message) |
| 97 | + print(f"Posted new MedHack case #{new_case['id']} for {today}") |
| 98 | + else: |
| 99 | + print("⚠️ No available MedHack cases to post") |
| 100 | + |
| 101 | + except Exception as e: |
| 102 | + print(f"❌ MedHack daily case error: {e}") |
| 103 | + import traceback |
| 104 | + traceback.print_exc() |
| 105 | + |
| 106 | + # Sleep until tomorrow (calculate seconds until next 10 AM AEST) |
| 107 | + from datetime import datetime, timedelta |
| 108 | + from zoneinfo import ZoneInfo |
| 109 | + tz = ZoneInfo(get_settings().TIMEZONE) |
| 110 | + now = datetime.now(tz) |
| 111 | + next_post = now.replace(hour=10, minute=0, second=0, microsecond=0) |
| 112 | + if now >= next_post: |
| 113 | + next_post += timedelta(days=1) |
| 114 | + sleep_seconds = (next_post - now).total_seconds() |
| 115 | + print(f"MedHack: Next case in {sleep_seconds/3600:.1f} hours") |
| 116 | + await asyncio.sleep(sleep_seconds) |
| 117 | + |
| 118 | + |
24 | 119 | @asynccontextmanager |
25 | 120 | async def lifespan(app: FastAPI): |
26 | 121 | """Application lifespan handler for startup/shutdown.""" |
27 | 122 | settings = get_settings() |
28 | 123 | print(f"🦘 Roo Standalone starting...") |
29 | 124 | print(f" LLM Provider: {settings.default_llm_provider}") |
30 | 125 | print(f" Skills Dir: {settings.SKILLS_DIR}") |
31 | | - |
| 126 | + |
32 | 127 | # Initialize agent on startup |
33 | 128 | agent = get_agent() |
34 | 129 | print(f" Loaded {len(agent.skills)} skills") |
35 | | - |
| 130 | + |
| 131 | + # Start MedHack daily case scheduler |
| 132 | + import asyncio |
| 133 | + medhack_task = asyncio.create_task(_medhack_daily_case_loop()) |
| 134 | + print(" Started MedHack daily case scheduler") |
| 135 | + |
36 | 136 | yield |
37 | | - |
| 137 | + |
| 138 | + # Cancel the background task on shutdown |
| 139 | + medhack_task.cancel() |
38 | 140 | print("🦘 Roo Standalone shutting down...") |
39 | 141 |
|
40 | 142 |
|
|
0 commit comments