Spaces:
Running
Running
| """ | |
| 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 |