Skip to content

Commit 5b5b523

Browse files
authored
Merge pull request #72 from MLAI-AUS-Inc/medhack-frontiers-skill
adding medhack sim patients skill
2 parents aecbb11 + 63f8e50 commit 5b5b523

File tree

9 files changed

+1395
-18
lines changed

9 files changed

+1395
-18
lines changed

roo-standalone/roo/agent.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ async def handle_mention(
8686
return fast_result
8787

8888
# 2. Select appropriate skill (LLM Routing)
89-
skill = await self._select_skill(clean_text, thread_history)
89+
skill = await self._select_skill(clean_text, thread_history, channel_id)
9090

9191
if skill:
9292
print(f"🎯 Selected skill: {skill.name}")
@@ -273,24 +273,40 @@ def _clean_mention(self, text: str) -> str:
273273
cleaned = ' '.join(cleaned.split())
274274
return cleaned.strip()
275275

276-
async def _select_skill(self, text: str, history: List[dict] = None) -> Optional[Skill]:
276+
async def _select_skill(self, text: str, history: List[dict] = None, channel_id: Optional[str] = None) -> Optional[Skill]:
277277
"""Use LLM to decide which skill to use."""
278278
if not self.skills:
279279
return None
280-
280+
281281
# First check trigger keywords for quick matching
282282
text_lower = text.lower()
283283
for skill in self.skills:
284284
for keyword in skill.trigger_keywords:
285285
if keyword.lower() in text_lower:
286286
return skill
287-
287+
288+
# Resolve channel name for priority matching
289+
channel_priority_hint = ""
290+
if channel_id:
291+
from .slack_client import get_channel_name
292+
channel_name = get_channel_name(channel_id)
293+
if channel_name:
294+
for skill in self.skills:
295+
if channel_name in skill.priority_channels:
296+
channel_priority_hint = (
297+
f"\nIMPORTANT: The user is in #{channel_name}. "
298+
f"Strongly prefer the '{skill.name}' skill unless "
299+
f"the request is clearly about a different skill "
300+
f"(e.g. checking points balance, writing content).\n"
301+
)
302+
break
303+
288304
# Fall back to LLM classification
289305
skill_descriptions = "\n".join(
290-
f"- {s.name}: {s.description}"
306+
f"- {s.name}: {s.description}"
291307
for s in self.skills
292308
)
293-
309+
294310
# Format history for context
295311
history_context = ""
296312
if history:
@@ -302,7 +318,7 @@ async def _select_skill(self, text: str, history: List[dict] = None) -> Optional
302318
Available skills:
303319
{skill_descriptions}
304320
- none: Use this if no skill is appropriate (general conversation)
305-
321+
{channel_priority_hint}
306322
{history_context}
307323
User message: "{text}"
308324
@@ -313,18 +329,18 @@ async def _select_skill(self, text: str, history: List[dict] = None) -> Optional
313329
{"role": "system", "content": "You are a skill router. Respond with only the skill name."},
314330
{"role": "user", "content": prompt}
315331
])
316-
332+
317333
skill_name = response.content.strip().lower()
318334
# Normalize: both underscores and hyphens should match
319335
skill_name_normalized = skill_name.replace("_", "-")
320-
336+
321337
for skill in self.skills:
322338
skill_normalized = skill.name.lower().replace("_", "-")
323339
if skill_normalized == skill_name_normalized:
324340
return skill
325-
341+
326342
return None
327-
343+
328344
except Exception as e:
329345
print(f"❌ Skill selection failed: {e}")
330346
return None

roo-standalone/roo/main.py

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,122 @@
2121
_pending_intents: dict = {}
2222

2323

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+
24119
@asynccontextmanager
25120
async def lifespan(app: FastAPI):
26121
"""Application lifespan handler for startup/shutdown."""
27122
settings = get_settings()
28123
print(f"🦘 Roo Standalone starting...")
29124
print(f" LLM Provider: {settings.default_llm_provider}")
30125
print(f" Skills Dir: {settings.SKILLS_DIR}")
31-
126+
32127
# Initialize agent on startup
33128
agent = get_agent()
34129
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+
36136
yield
37-
137+
138+
# Cancel the background task on shutdown
139+
medhack_task.cancel()
38140
print("🦘 Roo Standalone shutting down...")
39141

40142

0 commit comments

Comments
 (0)