diff --git a/backend/app/api/chat_routes.py b/backend/app/api/chat_routes.py index 7871655..a75d6c3 100644 --- a/backend/app/api/chat_routes.py +++ b/backend/app/api/chat_routes.py @@ -108,30 +108,19 @@ def _safe_judge_debrief(verdict: object, outcome: str, persona: dict) -> dict: score = max(0, min(100, int(raw.get("score", 0)))) except (TypeError, ValueError): score = 0 - progress = {} - raw_progress = raw.get("painProgress") - if isinstance(raw_progress, dict): - for key, value in list(raw_progress.items())[:20]: - if not isinstance(key, str): - continue - try: - progress[key[:100]] = max(0, min(100, int(value))) - except (TypeError, ValueError): - continue return { "outcome": outcome, "score": score, - "pain": _bounded_text(raw.get("pain")), + # IP protection: do NOT surface the customer's pain (prose or raw list) to any + # user-facing role. Pain drives the judge/training internally but is the core + # "formula" of the coaching product, so it must not leak through the debrief. "why": _bounded_text(raw.get("why")), "failurePoints": _string_list(raw.get("failurePoints")), "coaching": _string_list(raw.get("coaching")), - "painProgress": progress, "revealed_persona": { - "pains": persona.get("pains", []), "income": persona.get("income", ""), "personality": persona.get("personality", ""), "budget": persona.get("budget", ""), - "negotiation_levers": persona.get("negotiation_levers", []), "opener": persona.get("opener", ""), "background": persona.get("background", ""), }, diff --git a/backend/app/services/report.py b/backend/app/services/report.py index 5ad2f01..d017815 100644 --- a/backend/app/services/report.py +++ b/backend/app/services/report.py @@ -51,10 +51,7 @@ def _render_sales_kit(kit: dict[str, Any], thai: bool) -> str: ta = kit.get("targetAudience") or {} if ta.get("segment"): lines.append(f"**{'กลุ่มเป้าหมาย' if thai else 'Target segment'}:** {ta['segment']}") - if kit.get("initialPainFit"): - lines.append(f"**{'Pain ที่สินค้าแก้ได้เบื้องต้น' if thai else 'Initial pain-fit'}:**") - for p in kit["initialPainFit"]: - lines.append(f"- ({p.get('fit', '?')}) {p.get('pain', '')}") + # IP protection: hide pain-fit details from the admin report (the "formula"). return "\n".join(lines) @@ -79,13 +76,6 @@ def _render_persona(p: dict[str, Any], thai: bool) -> str: lines.append(f"- {'รายได้' if thai else 'Income'}: {p.get('income', '-')} | " f"{'ไลฟ์สไตล์' if thai else 'Lifestyle'}: {p.get('lifestyle', '-')}") lines.append(f"- {'นิสัย' if thai else 'Personality'}: {p.get('personality', '-')}") - if p.get("pains"): - lines.append(f"- {'Pain points (latent)' if thai else 'Pains (latent)'}:") - for pain in p.get("pains", []): - conds = "; ".join(pain.get("resolutionConditions", [])) if isinstance(pain, dict) else "" - lines.append(f" - [{pain.get('fit', '?') if isinstance(pain, dict) else '?'}] " - f"{pain.get('name', pain) if isinstance(pain, dict) else pain}" - f"{' — resolve: ' + conds if conds else ''}") if p.get("negotiation_levers"): levers = p.get("negotiation_levers") or [] lines.append(f"- {'ต่อรอง' if thai else 'Negotiation levers'}: " + ", ".join(str(x) for x in levers)) diff --git a/backend/tests/test_final_judge.py b/backend/tests/test_final_judge.py index cacb872..ea57913 100644 --- a/backend/tests/test_final_judge.py +++ b/backend/tests/test_final_judge.py @@ -81,9 +81,11 @@ def test_final_judge_supplies_score_and_debrief_for_automatic_buy(monkeypatch): assert updated["status"] == "finished" assert updated["outcome"] == "won" assert debrief["score"] == 88 - assert debrief["pain"] == "qualified pain" assert debrief["coaching"] == ["Keep the close concise"] - assert debrief["revealed_persona"]["pains"] + # IP protection: pain is hidden from the debrief (prose + raw list). + assert "pain" not in debrief + assert "painProgress" not in debrief + assert "pains" not in debrief["revealed_persona"] assert sessions.updates[0]["messages"] diff --git a/backend/tests/test_sprint1_review_findings.py b/backend/tests/test_sprint1_review_findings.py index fdf825f..f239829 100644 --- a/backend/tests/test_sprint1_review_findings.py +++ b/backend/tests/test_sprint1_review_findings.py @@ -148,12 +148,14 @@ def test_judge_debrief_uses_closed_allowlist(): ) assert set(debrief) == { - "outcome", "score", "pain", "why", "failurePoints", "coaching", - "painProgress", "revealed_persona", + "outcome", "score", "why", "failurePoints", "coaching", "revealed_persona", } assert debrief["score"] == 100 assert debrief["failurePoints"] == ["missed discovery"] - assert debrief["painProgress"] == {"main": 100} + # IP protection: pain (prose + list) and painProgress are stripped from the debrief. + assert "pain" not in debrief + assert "painProgress" not in debrief + assert "pains" not in debrief["revealed_persona"] assert "password_hash" not in str(debrief) assert "provider_path" not in str(debrief) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index ced4cc5..96707db 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -15,10 +15,19 @@
@@ -29,24 +38,32 @@