Enrolling a packshot from an external set is normal - it is how F-12 was closed - and it silently turns one of those rows into training data. The external sets were scored by an ad-hoc script that never checked, so sku_l06_1.jpg was being reported as a held-out correct accept while being byte-identical to the enrolled view. code/eval_external.py now scores those sets with the same leaked() check the shelf evaluator runs, and refuses a row that has since been enrolled. That check needed fixing first. Its 64x64 signature fires on two different photographs of the same product: the l09 rear label from a retailer page sits 19.5 from the enrolled front packshot, under the 20.0 line, while being a different photograph (resized MAD 26.3). A signature hit is now confirmed by correlation - a genuine re-cut, re-encode or rescale correlates at 0.99+, that pair at 0.28 - so the two signals together are decisive where either alone is not. Verified against both directions: identical, q55 re-encode and 700px rescale are caught; the different-photo pair is not. Also adds the van backend (always-argmax, no gate) so the stream comparison in DECISION.md is reproducible rather than asserted, with two tests pinning that it answers where the shipped gate abstains and still refuses an empty score set. External result on the corrected basis: 19 scored, 12 correct, 7 abstain, 0 false accepts, 1 leak excluded. Suite 307 -> 309.
69 lines
2.8 KiB
Python
69 lines
2.8 KiB
Python
"""Ranking contract for decide_identity: what a score set is allowed to name.
|
|
|
|
Moved out of test_observe.py when the legacy session model was deleted; the
|
|
ranking rules are the live path's, not the legacy one's.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import unittest
|
|
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
if current_dir not in sys.path:
|
|
sys.path.insert(0, current_dir)
|
|
|
|
from identity import ( # noqa: E402
|
|
IDENTITY_ACCEPTED,
|
|
IDENTITY_AMBIGUOUS,
|
|
IDENTITY_REJECTED,
|
|
IDENTITY_UNKNOWN,
|
|
decide_identity,
|
|
)
|
|
|
|
|
|
class IdentityRankingTests(unittest.TestCase):
|
|
def test_zero_scores_are_rejected_not_a_sku(self):
|
|
ranked = decide_identity({"sku_a": 0.0, "sku_b": 0.0})
|
|
self.assertEqual(ranked["identity_level"], IDENTITY_REJECTED)
|
|
self.assertIsNone(ranked["best_sku"])
|
|
self.assertEqual(ranked["status"], IDENTITY_REJECTED)
|
|
|
|
def test_empty_scores_are_rejected(self):
|
|
ranked = decide_identity({})
|
|
self.assertEqual(ranked["identity_level"], IDENTITY_REJECTED)
|
|
self.assertIsNone(ranked["best_sku"])
|
|
|
|
def test_weak_unique_score_is_unknown(self):
|
|
ranked = decide_identity({"sku_a": 0.4, "sku_b": 0.0})
|
|
self.assertEqual(ranked["identity_level"], IDENTITY_UNKNOWN)
|
|
self.assertIsNone(ranked["best_sku"])
|
|
|
|
def test_close_scores_are_ambiguous(self):
|
|
ranked = decide_identity({"sku_a": 20.0, "sku_b": 19.5})
|
|
self.assertEqual(ranked["identity_level"], IDENTITY_AMBIGUOUS)
|
|
self.assertIsNone(ranked["best_sku"])
|
|
|
|
def test_unique_strong_score_is_accepted(self):
|
|
ranked = decide_identity({"sku_a": 40.0, "sku_b": 5.0})
|
|
self.assertEqual(ranked["identity_level"], IDENTITY_ACCEPTED)
|
|
self.assertEqual(ranked["best_sku"], "sku_a")
|
|
self.assertEqual(ranked["status"], "success")
|
|
|
|
def test_van_answers_what_the_gate_would_refuse(self):
|
|
# Van's contract is the baseline the decision is measured against, so it
|
|
# must answer exactly where the shipped gate abstains. A weak score and
|
|
# a close pair are both refused above; here both are named.
|
|
weak = decide_identity({"sku_a": 0.4, "sku_b": 0.0}, scale="van")
|
|
self.assertEqual(weak["identity_level"], IDENTITY_ACCEPTED)
|
|
self.assertEqual(weak["best_sku"], "sku_a")
|
|
close = decide_identity({"sku_a": 20.0, "sku_b": 19.5}, scale="van")
|
|
self.assertEqual(close["best_sku"], "sku_a")
|
|
|
|
def test_van_still_rejects_when_there_is_no_evidence(self):
|
|
# Always-argmax is not always-answer: with nothing to rank there is no
|
|
# argmax, and naming a SKU from an empty score set would be a fabrication.
|
|
ranked = decide_identity({}, scale="van")
|
|
self.assertEqual(ranked["identity_level"], IDENTITY_REJECTED)
|
|
self.assertIsNone(ranked["best_sku"])
|