feat(matching): implement two-stage matching with DeepSeek arbiter, fail-stop guard, and zero-probability filter

This commit is contained in:
2026-10-16 03:13:21 +03:30
parent 9e848f535f
commit 787824a19e
6 changed files with 475 additions and 48 deletions
+20
View File
@@ -81,6 +81,25 @@ async def mock_tests() -> None:
for m in ms for v in m.get("variants", [])}
matching.match_with_deepseek = mock_fallback_below_floor
orig_arbiter = matching.adjudicate_with_deepseek
async def mock_arbiter(*args, **kwargs):
items = kwargs.get("items") or (args[2] if len(args) > 2 else [])
res = {}
for it in items:
choice = it.get("jev_choice")
conf = it.get("jev_confidence", 0.0)
if choice == "منشانه" and conf >= 0.8:
entry = {"status": "exact", "id": "p2", "name": "منشانه", "field": it["field"], "variants": it["variants"]}
elif choice == "دفتر" and conf >= 0.6:
entry = {"status": "ambiguity", "id": "p1", "name": "دفتر", "field": it["field"], "variants": it["variants"],
"candidates": [{"id": "p1", "name": "دفتر", "prob": conf}]}
else:
entry = {"status": "new", "id": None, "name": None, "field": it["field"], "variants": it["variants"], "candidates": []}
for v in it["variants"]:
res[normalize_text(v)] = entry
return res
matching.adjudicate_with_deepseek = mock_arbiter
mentions = [
{"field": "project", "variants": ["منشیانه"]},
{"field": "project", "variants": ["دفتر", "دفر"]},
@@ -91,6 +110,7 @@ async def mock_tests() -> None:
res = await matching.resolve_mentions(ctx, {"items": []}, mentions, None)
finally:
matching.match_with_deepseek = orig_fallback
matching.adjudicate_with_deepseek = orig_arbiter
r0 = matching.lookup(res, "منشیانه")
r1 = matching.lookup(res, "دفر") # variant key resolves too