"""Analytics — in-your-control metrics from implementation_plan/06 §5.""" from tests._helpers import ( auth_headers, create_creator, create_persona, enable_widget, seed_approved_clip, signup, ) def _start_and_send(client, persona, text: str) -> str: r = client.post("/widget/sessions", json={"public_key": persona["public_key"]}) sid = r.json()["id"] client.post(f"/widget/sessions/{sid}/message", json={"text": text}) return sid def test_analytics_basic_counts(client, fake_storage, no_redis): token = signup(client, "an1@example.com") creator = create_creator(client, token) persona = create_persona(client, token, creator["id"]) enable_widget(client, token, persona["id"]) seed_approved_clip(client, token, persona["id"], intent_tag="pricing", transcript="המחיר X") # 2 sessions: one served (pricing), one fallback (oos) _start_and_send(client, persona, "כמה זה עולה?") _start_and_send(client, persona, "מי גילה את אמריקה") r = client.get( f"/personas/{persona['id']}/analytics", headers=auth_headers(token) ) assert r.status_code == 200 body = r.json() assert body["persona_id"] == persona["id"] assert body["sessions_started"] == 2 assert body["sessions_completed"] == 2 assert body["avg_questions_per_session"] == 1.0 # 1 served + 1 fallback → fallback_rate = 0.5 assert body["fallback_rate"] == 0.5 # Reasons present (out_of_scope from the second message) assert "out_of_scope" in body["fallback_rate_by_reason"] # missing_topics — only pricing is covered, the rest of core_intents are missing assert "who_you_are" in body["missing_topics"] assert "pricing" not in body["missing_topics"] def test_analytics_lead_conversion(client, fake_storage, no_redis): token = signup(client, "an2@example.com") creator = create_creator(client, token) persona = create_persona(client, token, creator["id"]) enable_widget(client, token, persona["id"]) # One session, one lead captured at the end sid = _start_and_send(client, persona, "מי גילה את אמריקה") client.post( f"/widget/sessions/{sid}/lead", json={"name": "X", "contact": "x@x.io", "question_context": "ctx"}, ) r = client.get( f"/personas/{persona['id']}/analytics", headers=auth_headers(token) ) body = r.json() assert body["leads_count"] == 1 assert body["sessions_completed"] == 1 assert body["lead_conversion_rate"] == 1.0 def test_analytics_cross_tenant_403(client, fake_storage, no_redis): token_a = signup(client, "alice-a@example.com", org="AnA") token_b = signup(client, "bob-a@example.com", org="AnB") creator = create_creator(client, token_a) persona = create_persona(client, token_a, creator["id"]) r = client.get( f"/personas/{persona['id']}/analytics", headers=auth_headers(token_b) ) assert r.status_code == 403 def test_analytics_top_intents_orders_by_count(client, fake_storage, no_redis): token = signup(client, "an3@example.com") creator = create_creator(client, token) persona = create_persona(client, token, creator["id"]) enable_widget(client, token, persona["id"]) seed_approved_clip(client, token, persona["id"], intent_tag="pricing", transcript="A") seed_approved_clip(client, token, persona["id"], intent_tag="how_it_works", transcript="B", filename="b.mp4") # 3 pricing questions, 1 how_it_works for _ in range(3): _start_and_send(client, persona, "כמה זה עולה?") _start_and_send(client, persona, "איך התהליך עובד?") r = client.get( f"/personas/{persona['id']}/analytics", headers=auth_headers(token) ) top = r.json()["top_intents"] assert top[0]["intent"] == "pricing" assert top[0]["count"] == 3 intents = {row["intent"]: row["count"] for row in top} assert intents.get("how_it_works") == 1