"""Neural Sync - Role Dispatcher。""" from __future__ import annotations import logging import re from typing import Any, Optional from pydantic import BaseModel, Field from .dispatcher_rules import ROLE_RULES, TECH_KEYWORDS, Role, RoutingRule from .utils.timezone import now_cn_aware logger = logging.getLogger(__name__) class RoleRouter: """角色路由引擎。""" ROLE_RULES: list[RoutingRule] = ROLE_RULES TECH_KEYWORDS = TECH_KEYWORDS def __init__(self): self._compiled_patterns: dict[Role, list[re.Pattern]] = {} for rule in self.ROLE_RULES: patterns = [re.compile(pattern, re.IGNORECASE) for pattern in rule.patterns] self._compiled_patterns[rule.role] = patterns def route( self, signal_content: str, analysis: Optional[dict[str, Any]] = None, source: Optional[str] = None, ) -> tuple[Role, float, dict[str, Any]]: analysis = analysis or {} details = { "matched_rules": [], "scores": {}, "reasons": [], } if self._should_escalate_to_boss(analysis): details["reasons"].append("触发老板决策阈值") details["matched_rules"].append("threshold_check") return Role.BOSS, 1.0, details scores: dict[Role, float] = {rule.role: 0.0 for rule in self.ROLE_RULES} content_lower = signal_content.lower() for rule in self.ROLE_RULES: keyword_score = sum(1 for kw in rule.keywords if kw.lower() in content_lower) if keyword_score > 0: scores[rule.role] += keyword_score * 0.3 details["scores"][f"{rule.role}_keywords"] = keyword_score for rule in self.ROLE_RULES: if rule.role in self._compiled_patterns: pattern_score = sum( 1 for pattern in self._compiled_patterns[rule.role] if pattern.search(signal_content) ) if pattern_score > 0: scores[rule.role] += pattern_score * 0.5 details["scores"][f"{rule.role}_patterns"] = pattern_score for rule in self.ROLE_RULES: trigger_score = sum(1 for trigger in rule.triggers if trigger.lower() in content_lower) if trigger_score > 0: scores[rule.role] += trigger_score * 0.7 details["scores"][f"{rule.role}_triggers"] = trigger_score for rule in self.ROLE_RULES: if scores[rule.role] > 0: scores[rule.role] *= 1 + rule.priority * 0.01 max_role = Role.AI_ENGINEER max_score = 0.0 for role, score in scores.items(): if score > max_score: max_score = score max_role = role elif score == max_score and score > 0: role_priority = 0 max_role_priority = 0 for rule in self.ROLE_RULES: if rule.role == role: role_priority = rule.priority if rule.role == max_role: max_role_priority = rule.priority if role_priority > max_role_priority: max_role = role confidence = min(max_score / 3.0, 1.0) if max_score > 0 else 0.5 details["matched_rules"] = [ rule.name for rule in self.ROLE_RULES if scores[rule.role] > 0 ] details["final_scores"] = {role.value: score for role, score in scores.items()} details["reasons"].append("基于关键词和模式匹配") return max_role, confidence, details def _should_escalate_to_boss(self, analysis: dict[str, Any]) -> bool: dev_days = analysis.get("resource_cost", {}).get("dev_days", 0) if dev_days >= 5: return True risk_level = analysis.get("risk_level", "") if risk_level in ["CRITICAL", "HIGH"]: return True is_architecture = analysis.get("is_architecture_change", False) if is_architecture: return True return False def get_role_info(self, role: Role) -> dict[str, Any]: for rule in self.ROLE_RULES: if rule.role == role: return { "role": role.value, "name": rule.name, "description": rule.description, "keywords": rule.keywords, "priority": rule.priority, } return {} def get_all_roles(self) -> list[dict[str, Any]]: return [ { "role": rule.role.value, "name": rule.name, "description": rule.description, "priority": rule.priority, } for rule in sorted(self.ROLE_RULES, key=lambda item: -item.priority) ] class RoutingResult(BaseModel): """路由结果模型。""" role: str confidence: float details: dict[str, Any] signal_id: Optional[str] = None timestamp: float = Field(default_factory=lambda: now_cn_aware().timestamp()) _global_router: Optional[RoleRouter] = None def get_router() -> RoleRouter: """获取全局路由器实例。""" global _global_router if _global_router is None: _global_router = RoleRouter() return _global_router def route_signal( signal_content: str, analysis: Optional[dict[str, Any]] = None, signal_id: Optional[str] = None, ) -> RoutingResult: """路由信号到对应角色。""" router = get_router() role, confidence, details = router.route(signal_content, analysis) return RoutingResult( role=role.value, confidence=confidence, details=details, signal_id=signal_id, )