interns_manager / router.py
banao-tech's picture
Update router.py
d62901b verified
"""
router.py β€” Event router
Only processes app_mention events β€” when someone tags @intern-eod.
All other channel messages are ignored by Slack before they reach us.
Cron-based miss warnings run independently and are unaffected.
"""
import os
import time
import sheets
import handlers
HR_SLACK_IDS = set(os.environ.get("HR_SLACK_IDS", "").split(","))
# ─────────────────────────────────────────────
# CHANNEL CACHE
# ─────────────────────────────────────────────
_managed_channels_cache: set = set()
_cache_last_updated: float = 0
_CACHE_TTL: int = 300
def _get_managed_channels() -> set:
global _managed_channels_cache, _cache_last_updated
if time.time() - _cache_last_updated > _CACHE_TTL:
try:
candidates = sheets.get_active_candidates()
managed = {
c.get("channel_id", "").strip()
for c in candidates
if c.get("channel_id", "").strip()
}
except Exception as e:
print(f"[router] cache refresh failed: {e}")
managed = set()
env_channel = os.environ.get("SLACK_CHANNEL_ID", "").strip()
if env_channel:
managed.add(env_channel)
_managed_channels_cache = managed
_cache_last_updated = time.time()
print(f"[router] managed channels: {_managed_channels_cache}")
return _managed_channels_cache
def invalidate_channel_cache() -> None:
global _cache_last_updated
_cache_last_updated = 0
# ─────────────────────────────────────────────
# ROLE DETECTION
# ─────────────────────────────────────────────
def _get_ft_slack_ids() -> set:
try:
return {c["ft_slack_id"] for c in sheets.get_active_candidates() if c.get("ft_slack_id")}
except Exception:
return set()
def _get_sender_role(user_id: str) -> str:
"""Candidate check first β€” person can be both HR and candidate."""
try:
if sheets.get_candidate_by_slack_id(user_id):
return "candidate"
except Exception:
pass
if user_id in HR_SLACK_IDS:
return "hr"
if user_id in _get_ft_slack_ids():
return "ft"
return "unknown"
def _get_candidate_with_pending_recommendation() -> dict | None:
try:
for candidate in sheets.get_active_candidates():
if sheets.get_pending_recommendation(candidate["candidate_id"]):
return candidate
except Exception:
pass
return None
# ─────────────────────────────────────────────
# MAIN ROUTER
# ─────────────────────────────────────────────
def route_event(event: dict) -> None:
"""
Processes two event types:
1. app_mention β€” someone tagged @intern-eod (primary EOD submission path)
2. message β€” for HR approve/reject confirmations only
"""
event_type = event.get("type", "")
user_id = event.get("user", "")
channel_id = event.get("channel", "")
text = event.get("text", "").strip()
# Ignore bot messages and empty messages
if event.get("bot_id") or event.get("subtype") or not text or not user_id:
return
# Ignore thread replies
if event.get("thread_ts") and event.get("thread_ts") != event.get("ts"):
return
# Only handle managed channels
if channel_id not in _get_managed_channels():
return
role = _get_sender_role(user_id)
if role == "unknown":
return
print(f"[router] type={event_type} role={role} user={user_id} channel={channel_id}")
print(f"[router] text_preview={text[:80]!r}")
# ── Candidate: Claude will verify if it is a real EOD report ──
if role == "candidate":
candidate = sheets.get_candidate_by_slack_id(user_id)
if candidate:
handlers.handle_candidate_message(event, candidate)
return
# ── FT: short signal answers ──
if role == "ft":
lower = text.strip().lower()
ft_answers = {"yes","no","partial","specific","vague","on_time",
"late_flagged","late_no_flag","not_delivered","engaged",
"defensive","collapsed","pushing","avoiding","unclear"}
if len(lower) < 80 and any(a in lower for a in ft_answers):
candidate = _get_ft_candidate(user_id, channel_id)
if candidate:
handlers.handle_ft_signal_answer(event, candidate)
return
# ── HR approve/reject ──
if role == "hr":
lower = text.strip().lower()
if lower in ("approve", "reject"):
candidate = _get_candidate_with_pending_recommendation()
if candidate:
handlers.handle_hr_confirmation(event, candidate)
def _get_ft_candidate(ft_slack_id: str, channel_id: str) -> dict | None:
try:
candidates = sheets.get_active_candidates()
matches = [
c for c in candidates
if c.get("ft_slack_id") == ft_slack_id
and c.get("channel_id", "").strip() == channel_id
]
return matches[0] if matches else None
except Exception:
return None