test: cover the local scale branch and the decision policy

The shipped default scale had no test: make_session only builds matcher-backend
engines, so scale == "local" in _on_classified never ran in the suite - and
that is the branch that was wrong twice (the rank re-sort, and the early return
that made the text lane's fill-in job unreachable).

Adds LocalScaleEngine, which injects the lanes' evidence into the live path, and
two live tests: a text-only read still labels the track, and a text veto blocks a
local accept. Adds DecisionPolicyTests for the veto/fill-in boundary, including
the lane-order-vs-value case that the rank re-sort broke.

Both regressions were re-injected to confirm the tests fail for the intended
reason: restoring the early return fails the fill-in test, and re-sorting the
lane output by value fails the order test. Suite 251 -> 261.
This commit is contained in:
tngo 2026-09-28 22:41:24 +09:00
parent 2b7dd0c193
commit 890e186182
2 changed files with 141 additions and 0 deletions

View file

@ -407,6 +407,72 @@ class LocalLaneTests(unittest.TestCase):
engine.shutdown()
class DecisionPolicyTests(unittest.TestCase):
"""The two jobs the text lane does, and the boundary between them."""
def policy(self):
from identity_lanes import DecisionPolicy
return DecisionPolicy()
def test_local_answer_stands_when_the_text_agrees(self):
decision = self.policy().decide([("sku_a", 5.0)], [("sku_a", 0.9)])
self.assertEqual(decision["best_sku"], "sku_a")
self.assertEqual(decision["source"], "local")
def test_text_vetoes_a_local_answer_naming_another_row(self):
"""The near-unknown case: real geometry, different words.
The veto blocks the local answer, and the text lane then names its own
row - the two jobs compose, so the result is the text's SKU, never the
vetoed local one.
"""
decision = self.policy().decide([("sku_a", 5.0)], [("sku_b", 0.9)])
self.assertEqual(decision["best_sku"], "sku_b")
self.assertEqual(decision["source"], "text")
def test_text_mentioning_the_local_candidate_is_not_a_veto(self):
"""A crop reading "マヨネーズ" scores every mayonnaise row.
The local candidate is usually one of them, so a veto needs the text to
*prefer* another row, not merely to mention it: here the text mentions
sku_b but explains sku_a better, so the local answer stands.
"""
decision = self.policy().decide([("sku_a", 5.0)], [("sku_b", 0.4), ("sku_a", 0.45)])
self.assertEqual(decision["best_sku"], "sku_a")
self.assertEqual(decision["source"], "local")
def test_the_lanes_own_order_wins_over_the_value_order(self):
"""Order and value are different quantities on the local lane.
The order comes from `rank_key` (inliers weighted by hue); the value is
the margin over the runner-up. Re-sorting by value names a different SKU
than the lane chose - here sku_b has the larger value but sku_a is the
lane's own first choice, and sku_a is what must be answered.
"""
decision = self.policy().decide([("sku_a", 3.0), ("sku_b", 5.0)], [])
self.assertEqual(decision["best_sku"], "sku_a")
def test_text_fills_in_when_local_has_nothing(self):
decision = self.policy().decide([], [("sku_b", 0.9)])
self.assertEqual(decision["best_sku"], "sku_b")
self.assertEqual(decision["source"], "text")
def test_text_below_its_own_line_does_not_fill_in(self):
decision = self.policy().decide([], [("sku_b", 0.3)])
self.assertIsNone(decision["best_sku"])
def test_a_close_text_pair_does_not_fill_in(self):
"""The margin gate: two rows the text explains equally must not name one."""
decision = self.policy().decide([], [("sku_b", 0.9), ("sku_c", 0.85)])
self.assertIsNone(decision["best_sku"])
def test_no_evidence_is_rejected(self):
decision = self.policy().decide([], [])
self.assertIsNone(decision["best_sku"])
self.assertEqual(decision["identity_level"], "rejected")
class CatalogImageTests(unittest.TestCase):
def test_catalog_image_path_is_resolved_through_inventory(self):
engine, _, _, _ = make_engine()