feat(bot): add DeepSeek crisis fallback engine for Jev failure and remove difflib heuristics

- Add app/fallback_matcher.py for intelligent crisis entity matching with token and latency observability
- Route Jev failure/timeouts in app/matching.py to DeepSeek fallback engine
- Handle MatchingFailureError defensively in handlers/__init__.py with honest error message
- Enforce strict 'new' entity status in handlers/quote.py and implement defensive get_or_create on commit
- Clean up app/resolve.py to eliminate difflib and character ratio heuristics
- Pass all smoke tests (59/59), fallback matching tests (15/15), and matching unit tests (13/13)
This commit is contained in:
2026-10-16 02:05:01 +03:30
parent 76b297f823
commit 9e848f535f
9 changed files with 870 additions and 124 deletions
+25 -6
View File
@@ -74,13 +74,24 @@ async def mock_tests() -> None:
"mention_3": {"choice": "دفتر", "confidence": 0.5,
"probabilities": {"دفتر": 0.52, "_none": 0.48}},
})
orig_fallback = matching.match_with_deepseek
async def mock_fallback_below_floor(*args, **kwargs):
ms = kwargs.get("mentions") or (args[1] if len(args) > 1 else [])
return {normalize_text(v): {"status": "new", "field": m.get("field"), "variants": m.get("variants"), "id": None}
for m in ms for v in m.get("variants", [])}
matching.match_with_deepseek = mock_fallback_below_floor
mentions = [
{"field": "project", "variants": ["منشیانه"]},
{"field": "project", "variants": ["دفتر", "دفر"]},
{"field": "project", "variants": ["چیز عجیب"]},
{"field": "project", "variants": ["دفر دو"]},
]
res = await matching.resolve_mentions(ctx, {"items": []}, mentions, None)
try:
res = await matching.resolve_mentions(ctx, {"items": []}, mentions, None)
finally:
matching.match_with_deepseek = orig_fallback
r0 = matching.lookup(res, "منشیانه")
r1 = matching.lookup(res, "دفر") # variant key resolves too
r2 = matching.lookup(res, "چیز عجیب")
@@ -90,15 +101,23 @@ async def mock_tests() -> None:
check("variant of same mention shares one result", r1 is r0 if False else
(r1 and matching.lookup(res, "دفتر") is r1))
check("_none / low conf -> new", r2 and r2["status"] == "new")
check("below-floor pick -> NO result (difflib fallback keeps its chance)",
matching.lookup(res, "دفر دو") is None)
check("below-floor pick -> routed to crisis fallback",
matching.lookup(res, "دفر دو") and matching.lookup(res, "دفر دو")["status"] == "new")
check("below-floor pick did not pollute other mentions", r1 and r1["status"] == "ambiguity")
# 2) Jev failure -> empty map (callers fall back to difflib)
# 2) Jev failure -> DeepSeek crisis fallback
matching.jev_ask = make_fake_ask({})
matching.jev_ask.fail = True
res_fail = await matching.resolve_mentions(ctx, {"items": []}, mentions, None)
check("jev failure -> empty results (difflib fallback path)", res_fail == {})
async def mock_deepseek(*args, **kwargs):
return {normalize_text("منشیانه"): {"status": "exact", "id": "p2", "name": "منشانه",
"field": "project", "variants": ["منشیانه"]}}
matching.match_with_deepseek = mock_deepseek
try:
res_fail = await matching.resolve_mentions(ctx, {"items": []}, mentions, None)
check("jev failure -> triggers crisis fallback",
normalize_text("منشیانه") in res_fail and res_fail[normalize_text("منشیانه")]["status"] == "exact")
finally:
matching.match_with_deepseek = orig_fallback
# 3) apply_exact writes supplier/project ids into a payload
payload = {"items": []}