87 lines
3.4 KiB
Python
87 lines
3.4 KiB
Python
"""Weak-area evidence contract tests."""
|
|
from __future__ import annotations
|
|
|
|
from app.services.trainee import analyze_weak_areas
|
|
|
|
|
|
def _session(sid, *, outcome="lost", status="finished", mode="trainee", user="u1", org="org-1", score=20, text=True):
|
|
return {
|
|
"id": sid,
|
|
"user_id": user,
|
|
"org_id": org,
|
|
"mode": mode,
|
|
"status": status,
|
|
"outcome": outcome,
|
|
"persona_id": f"p-{sid}",
|
|
"persona_name": f"Persona {sid}",
|
|
"persona_meta": {"tier": "B", "initiation_mode": "customer", "channel": "line"},
|
|
"debrief": {
|
|
"score": score,
|
|
"why": "seller did not ask about pain or handle the price objection before the close" if text else "",
|
|
"failurePoints": ["missed pain discovery", "weak objection handling"] if text else [],
|
|
"coaching": ["build trust and ask for the next step"] if text else [],
|
|
"painProgress": {"core pain": 20} if text else {},
|
|
},
|
|
}
|
|
|
|
|
|
def test_weak_areas_use_judge_evidence_and_exclude_active_preview():
|
|
sessions = [
|
|
_session("lost-1"),
|
|
_session("won-1", outcome="won", score=80, text=False),
|
|
_session("active-1", status="active", outcome=None),
|
|
_session("preview-1", mode="preview"),
|
|
]
|
|
|
|
result = analyze_weak_areas(sessions, user_id="u1", org_id="org-1")
|
|
|
|
assert result["total_sessions"] == 2
|
|
assert result["wins"] == 1
|
|
assert result["losses"] == 1
|
|
dimensions = result["dimensions"]
|
|
assert dimensions["pain_discovery"]["evidence_count"] == 1
|
|
assert dimensions["objection_handling"]["evidence_count"] == 1
|
|
assert dimensions["trust_building"]["evidence_count"] == 1
|
|
assert dimensions["close_attempt"]["evidence_count"] == 1
|
|
assert dimensions["pain_discovery"]["session_ids"] == ["lost-1"]
|
|
assert all("preview-1" not in item["session_ids"] for item in dimensions.values())
|
|
assert "why" not in result["top_loss_personas"][0]
|
|
|
|
|
|
def test_weak_areas_return_stable_empty_dimensions_without_sessions():
|
|
result = analyze_weak_areas([], user_id="u1", org_id="org-1")
|
|
|
|
assert result["total_sessions"] == 0
|
|
assert result["wins"] == 0
|
|
assert result["losses"] == 0
|
|
assert set(result["dimensions"]) == {
|
|
"pain_discovery", "objection_handling", "negotiation", "trust_building", "close_attempt"
|
|
}
|
|
assert all(item["evidence_count"] == 0 for item in result["dimensions"].values())
|
|
|
|
|
|
def test_weak_area_function_does_not_mix_users_when_caller_scopes_input():
|
|
result = analyze_weak_areas([_session("mine", user="u1")], user_id="u1", org_id="org-1")
|
|
|
|
assert result["top_loss_personas"][0]["persona_id"] == "p-mine"
|
|
assert all("u2" not in item["session_ids"] for item in result["dimensions"].values())
|
|
|
|
|
|
def test_weak_area_result_does_not_return_internal_debrief_reasoning():
|
|
session = _session("private-reasoning")
|
|
session["debrief"]["internal_reasoning"] = "hidden chain of thought"
|
|
|
|
result = analyze_weak_areas([session], user_id="u1", org_id="org-1")
|
|
|
|
assert "why" not in result["top_loss_personas"][0]
|
|
assert "internal_reasoning" not in str(result)
|
|
assert "hidden chain of thought" not in str(result)
|
|
|
|
|
|
def test_weak_area_analysis_requires_actor_scope():
|
|
try:
|
|
analyze_weak_areas([_session("unscoped")])
|
|
except TypeError:
|
|
return
|
|
raise AssertionError("weak-area analysis must require an actor user_id scope")
|