fix(identity): veto a local answer the read contradicts on package size
The documented near-unknown case could not be rejected. Crops 82-97 of
add/image6.jpg are Kewpie 450g bags the catalog does not hold, and they read
'マヨネーズ 450g'. Every mayonnaise row explains 'マヨネーズ' equally, so the
text ranking ties at 1.0 and the strict-greater veto in DecisionPolicy never
fires - the local lane's answer stood and the bag was labelled as a catalog
row. No amount of IDF tuning fixes this: the veto compares two catalog rows,
and the crop's product is not one of them.
The size is the part that differs and the part printed plainly, so the policy
now checks it. Only a mass in PACKAGE_MASS_RANGE counts: below it are
fragments of a longer number ('20' out of '200g') and above it are two numbers
run together ('5210' out of '210g'), and vetoing on either would cost correct
answers. Percentages are excluded for the same reason - 'カロリー 50% カット'
is a claim about the contents, not a 50 g package.
The veto also blocks the text lane's fill-in, or it would only move the
answer: the local row is blocked and the text lane then names that same row.
Measured on the corrected image6 labels, local lane at full resolution:
precision 86% -> 91%, false accepts 8 -> 5, coverage unchanged at 79%. At live
resolution 95% and 2 false accepts, unchanged. 307 tests pass.
This commit is contained in:
parent
aa838f7b2b
commit
ddead84546
2 changed files with 101 additions and 1 deletions
|
|
@ -632,6 +632,56 @@ class DecisionPolicyTests(unittest.TestCase):
|
|||
)
|
||||
self.assertIsNone(decision["best_sku"])
|
||||
|
||||
def test_a_read_printing_another_size_vetoes_the_local_answer(self):
|
||||
"""The documented near-unknown: a Kewpie 450 g bag the catalog lacks.
|
||||
|
||||
The crop reads "マヨネーズ 450g", which every mayonnaise row explains
|
||||
equally, so the text ranking ties at 1.0 and the strict-greater veto
|
||||
cannot fire. The size is what differs, and the catalog's Kewpie rows
|
||||
are 200 g, so the read contradicts the row the local lane chose.
|
||||
"""
|
||||
from identity_lanes import DecisionPolicy
|
||||
|
||||
policy = DecisionPolicy(signatures={"sku_a": "キューピー ハーフマヨネーズ 400g"})
|
||||
decision = policy.decide(
|
||||
[("sku_a", 5.0)], [("sku_a", 1.0)], text_read="ピーマヨネーズ 450g"
|
||||
)
|
||||
self.assertIsNone(decision["best_sku"])
|
||||
|
||||
def test_a_read_agreeing_on_size_does_not_veto(self):
|
||||
"""The same read with the row's own size must still answer."""
|
||||
from identity_lanes import DecisionPolicy
|
||||
|
||||
policy = DecisionPolicy(signatures={"sku_a": "キューピー ハーフマヨネーズ 400g"})
|
||||
decision = policy.decide(
|
||||
[("sku_a", 5.0)], [("sku_a", 1.0)], text_read="ピーマヨネーズ 400g"
|
||||
)
|
||||
self.assertEqual(decision["best_sku"], "sku_a")
|
||||
|
||||
def test_ocr_noise_around_a_size_does_not_veto(self):
|
||||
"""A fragment or a run-together number is noise, not a package size.
|
||||
|
||||
Live reads of a correct 200 g row came back as "20" and "5210"; both
|
||||
are outside the range a package of this class falls in, and vetoing on
|
||||
either would cost a correct answer.
|
||||
"""
|
||||
from identity_lanes import DecisionPolicy
|
||||
|
||||
policy = DecisionPolicy(signatures={"sku_a": "キューピー ハーフマヨネーズ 200g"})
|
||||
for read in ("アマニ油 20gs", "内臓脂訪を 滅与ず ッデュービーフィ5 210g", "n0gベ% 50"):
|
||||
decision = policy.decide([("sku_a", 5.0)], [("sku_a", 1.0)], text_read=read)
|
||||
self.assertEqual(decision["best_sku"], "sku_a", read)
|
||||
|
||||
def test_a_percentage_is_not_a_package_size(self):
|
||||
"""A low-calorie claim must not read as a 50 g package."""
|
||||
from identity_lanes import DecisionPolicy
|
||||
|
||||
policy = DecisionPolicy(signatures={"sku_a": "キューピー ハーフマヨネーズ 400g"})
|
||||
decision = policy.decide(
|
||||
[("sku_a", 5.0)], [("sku_a", 1.0)], text_read="カロリー 50% カット 400g"
|
||||
)
|
||||
self.assertEqual(decision["best_sku"], "sku_a")
|
||||
|
||||
def test_a_named_read_with_a_size_still_fills_in(self):
|
||||
decision = self.policy().decide([], [("sku_b", 0.9)], text_read="コクうま 65%")
|
||||
self.assertEqual(decision["best_sku"], "sku_b")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue