from __future__ import annotations import math from typing import Dict, List, Optional from .models import ClarifyingQuestion, IntentMeta def _entropy(probs: List[float]) -> float: return -sum(p * math.log(p + 1e-12) for p in probs if p > 0) def _find_question(questions: List[ClarifyingQuestion], topic_groups: List[str]) -> Optional[ClarifyingQuestion]: topic_set = set(topic_groups) for q in questions: if not q.applies_to_topic_groups: return q if topic_set.intersection(set(q.applies_to_topic_groups)): return q return questions[0] if questions else None def select_clarifying( patient_state: Dict[str, object], intent_scores: Dict[str, float], intents: Dict[str, IntentMeta], highlevel_questions: List[ClarifyingQuestion], detail_questions: List[ClarifyingQuestion], ) -> Optional[ClarifyingQuestion]: state = patient_state or {} # Check if topic is already determined from patient state current_topic = state.get("topic_group") topic_already_set = current_topic and current_topic not in ("other", "unknown", "") if not intent_scores: # No intent candidates - only ask highlevel if topic not yet set if not topic_already_set: return highlevel_questions[0] if highlevel_questions else None # Topic is set but no intents matched - proceed to answer return None sorted_pairs = sorted(intent_scores.items(), key=lambda x: x[1], reverse=True) top_id, top_score = sorted_pairs[0] top2 = sorted_pairs[1][1] if len(sorted_pairs) > 1 else 0.0 probs = [s for _, s in sorted_pairs] ent = _entropy(probs) inferred_topics = list({intents[intent_id].topic_group for intent_id, _ in sorted_pairs if intent_id in intents}) # Only ask highlevel question for TRULY AMBIGUOUS cases: # - Topic not already set by user # - Top intent score is very low (< 0.30) # - High entropy (uncertainty across multiple topics) # - Multiple topics with no clear winner truly_ambiguous = ( top_score < 0.30 and ent > 1.2 and len(inferred_topics) > 1 ) if not topic_already_set and truly_ambiguous: return _find_question(highlevel_questions, inferred_topics) # Determine the topic to use: prefer user-set topic, fallback to inferred best_topic = current_topic if topic_already_set else (intents[top_id].topic_group if top_id in intents else None) def _detail_for(topic: str) -> Optional[ClarifyingQuestion]: return _find_question(detail_questions, [topic]) # Ask detail questions only if the specific state info is missing # These questions help provide targeted, situation-specific advice if best_topic == "sick_day" and state.get("food_intake_level") in ("unknown", "", None): return _detail_for("sick_day") if best_topic == "travel" and state.get("travel_overseas") is None: return _detail_for("travel") if best_topic == "diet" and state.get("insulin_experience") in ("unknown", "", None): return _detail_for("diet") if best_topic in ("complication", "hypoglycemia") and state.get("hypoglycemia_concern") in ("unknown", "", None): return _detail_for("complication") if best_topic == "neuropathy" and state.get("acute_neurologic_red_flags") in (None, ""): return _detail_for("neuropathy") if best_topic == "footcare" and state.get("foot_infection_risk") in (None, ""): return _detail_for("footcare") # No clarifying needed - proceed to answer generation return None