110 lines
3.5 KiB
Python
110 lines
3.5 KiB
Python
|
|
"""
|
||
|
|
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"
|