""" NextBestActionEngine (Rules 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91): - Conversational State Machine Evaluation - Priority Scoring & Action Selection - Cooldown & Intent Deduplication - Status-aware field tracking (reported, confirmed_none, unknown, declined, skipped, not_asked) """ from typing import Dict, Any, List, Optional, Tuple class NextBestActionEngine: """ Evaluates intake state and determines the next-best action and target question. """ def calculate_priority_score( self, field: str, current_state: Dict[str, Any], is_psych: bool = False, asked_intents: Optional[List[str]] = None ) -> float: """ Calculates question priority score based on Rule 83: priority = safety_weight + clinical_importance + symptom_completeness + specialty_relevance + information_value - repetition_penalty - already_answered_penalty - unnecessary_question_penalty """ score = 0.0 asked_intents = asked_intents or [] weights = { "emergency": 100.0, "crisis": 100.0, "reason_for_visit": 50.0, "symptoms": 50.0, "duration": 45.0, "severity": 30.0, "medical_history": 35.0, "medications": 30.0, "allergies": 25.0, "previous_episodes": 15.0, "family_history": 10.0, "additional_information": 5.0, } score += weights.get(field, 10.0) # Repetition penalty (Rule 84 & 85) if field in asked_intents: score -= 60.0 # Specialty relevance bonus if is_psych and field in ["duration", "medical_history"]: score += 10.0 return score def determine_next_action( self, current_state: Dict[str, Any], is_emergency: bool = False, is_crisis: bool = False, is_psych: bool = False, missing_fields: Optional[List[str]] = None ) -> Tuple[str, Optional[str]]: """ Returns (action_type, target_field) Action types (Rule 82): - SAFETY_ACTION - CLARIFY - ASK_QUESTION - COMPLETE - HANDLE_REFUSAL - HANDLE_UNKNOWN """ if is_crisis or is_emergency: return "SAFETY_ACTION", None # Check for state conflicts requiring clarification (Rule 67) if current_state and current_state.get("conflicts"): return "CLARIFY", current_state["conflicts"][-1].get("field") missing_fields = missing_fields or [] if not missing_fields: return "COMPLETE", None # Score missing fields using priority score (Rule 83) scored_fields = [] for field in missing_fields: score = self.calculate_priority_score(field, current_state, is_psych=is_psych) scored_fields.append((score, field)) scored_fields.sort(key=lambda x: x[0], reverse=True) best_field = scored_fields[0][1] return "ASK_QUESTION", best_field