""" User Signal Detector — heuristic classifier for follow-up messages. Classifies the user's next message (after Garvis responded) into one of: "positive" — explicit praise / satisfaction "negative" — explicit dissatisfaction / error report "correction" — user corrects or rephrases Garvis "refinement" — user extends the prior request "neutral" — new topic or unclassifiable This is intentionally heuristic. Aggregate patterns matter more than any individual classification. The analysis layer must account for noise. No project imports — safe to use anywhere without circular-dep risk. """ from typing import Optional # --------------------------------------------------------------------------- # Keyword tables (extend here without touching classify_signal logic) # --------------------------------------------------------------------------- _POSITIVE_WORDS = frozenset({ "perfect", "great", "excellent", "exactly", "thanks", "thank", "awesome", "good", "nice", "nailed", "correct", "yes", "yep", "sure", "right", "wonderful", "brilliant", "fantastic", "helpful", "appreciate", }) _NEGATIVE_WORDS = frozenset({ "no", "nope", "wrong", "incorrect", "bad", "terrible", "awful", "failed", "broken", "error", "mistake", "off", }) _CORRECTION_WORDS = frozenset({ "actually", "wait", "sorry", "clarify", }) _CORRECTION_PHRASES = frozenset({ "i meant", "i mean", "what i meant", "not that", "let me clarify", "to clarify", "scratch that", "hold on", "my bad", "that's not", "not what i", "try again", "you missed", }) _REFINEMENT_PHRASES = frozenset({ "can you also", "what about", "and also", "additionally", "could you also", "one more", "another thing", "on top of that", "while you're at it", "in addition", "can you add", "please add", "add to that", }) # Time threshold under which a quick reply skews toward correction. _REPHRASE_THRESHOLD_S: float = 30.0 def classify_signal( follow_up_text: str, time_delta_seconds: Optional[float] = None, ) -> str: """Classify a follow-up message as a user feedback signal. Args: follow_up_text: The user's next message after Garvis responded. time_delta_seconds: Seconds elapsed since the previous response. If provided and < 30, rapid replies without positive signals skew toward "correction". Returns: One of: "positive", "negative", "correction", "refinement", "neutral" """ if not follow_up_text or not follow_up_text.strip(): return "neutral" text_lower = follow_up_text.lower().strip() words = set(text_lower.split()) # --- Explicit positive --- if words & _POSITIVE_WORDS: return "positive" # --- Multi-word correction phrases (check before single words) --- for phrase in _CORRECTION_PHRASES: if phrase in text_lower: return "correction" # --- Single-word correction signals --- if words & _CORRECTION_WORDS: return "correction" # --- Explicit negative --- if words & _NEGATIVE_WORDS: return "negative" # --- Refinement patterns --- for phrase in _REFINEMENT_PHRASES: if phrase in text_lower: return "refinement" # --- Rapid rephrase heuristic --- # If the user responds very quickly and no other signal matched, # treat it as a soft correction (likely dissatisfied with the answer). if ( time_delta_seconds is not None and time_delta_seconds < _REPHRASE_THRESHOLD_S ): return "correction" return "neutral"