|
|
@@ -1244,3 +1244,53 @@ def test_signal_lines_never_raises_on_malformed(tmp_path):
|
|
|
assert analyzer._signal_lines({}) == [] # type: ignore[dict-item]
|
|
|
assert analyzer._signal_lines({"order": "nope", "items": "nope"}) == [] # type: ignore[dict-item]
|
|
|
assert analyzer._signal_lines({"order": ["ghost"], "items": {}}) == []
|
|
|
+
|
|
|
+
|
|
|
+def test_signal_lines_opportunity_kept_below_threshold(tmp_path):
|
|
|
+ questions = _rich_questions()
|
|
|
+ questions["opportunity"] = {"type": "noul", "instructions": "A new lead."}
|
|
|
+ analyzer = _analyzer(tmp_path, questions)
|
|
|
+ items = _rich_signals()["items"]
|
|
|
+ items["opportunity"] = {
|
|
|
+ "kind": "noul",
|
|
|
+ "p": 0.10,
|
|
|
+ "confidence": 0.9,
|
|
|
+ "answer_confidence": 0.9,
|
|
|
+ "gate": 0.9,
|
|
|
+ "flagged": False,
|
|
|
+ }
|
|
|
+ signals = {"order": ["blocker", "opportunity", "churn_risk"],
|
|
|
+ "items": items, "escalated": False}
|
|
|
+ lines = analyzer._signal_lines(signals)
|
|
|
+ assert any("opportunity" in line for line in lines)
|
|
|
+ assert not any("churn_risk" in line for line in lines)
|
|
|
+
|
|
|
+
|
|
|
+def test_signal_lines_opportunity_ordered_first(tmp_path):
|
|
|
+ questions = _rich_questions()
|
|
|
+ questions["opportunity"] = {"type": "noul", "instructions": "A new lead."}
|
|
|
+ analyzer = _analyzer(tmp_path, questions)
|
|
|
+ items = _rich_signals()["items"]
|
|
|
+ items["opportunity"] = {
|
|
|
+ "kind": "noul",
|
|
|
+ "p": 0.80,
|
|
|
+ "confidence": 0.9,
|
|
|
+ "answer_confidence": 0.9,
|
|
|
+ "gate": 0.9,
|
|
|
+ "flagged": False,
|
|
|
+ }
|
|
|
+ signals = {"order": ["blocker", "severity", "opportunity"],
|
|
|
+ "items": items, "escalated": False}
|
|
|
+ lines = analyzer._signal_lines(signals)
|
|
|
+ assert lines[0].startswith("- opportunity")
|
|
|
+ # relative order of the rest preserved
|
|
|
+ rest_idx = [i for i, line in enumerate(lines) if "blocker" in line][0]
|
|
|
+ sev_idx = [i for i, line in enumerate(lines) if "severity" in line][0]
|
|
|
+ assert rest_idx < sev_idx
|
|
|
+
|
|
|
+
|
|
|
+def test_build_ledger_prompt_contains_opportunity_lead_rule(tmp_path):
|
|
|
+ analyzer = _analyzer(tmp_path, _rich_questions())
|
|
|
+ prompt = analyzer.build_ledger_prompt("Summary: x", [], ["hi"], None)
|
|
|
+ assert "currently-open opportunities first" in prompt
|
|
|
+ assert "Opportunities section the most complete" in prompt
|