feat(enroll): report views with no recorded source
Six enrolled views predate ATTRIBUTION.json and their source URLs were never recorded. They are not wrong - each is independent of the evaluation photo (highest template match 0.166 against add/image6.jpg) and each is above the enrollment floor - but the provenance is gone, and provenance is what lets a later audit tell a re-sourced packshot from a leaked shelf crop. rebuild_catalog now names them instead of leaving the gap to be rediscovered, and ATTRIBUTION.json records both the l06 replacement and the gap itself, with what is verifiable about those six. Reported, not refused: refusing would drop six working references to punish a missing field.
This commit is contained in:
parent
20fb459ee7
commit
8db280d220
2 changed files with 34 additions and 0 deletions
|
|
@ -345,6 +345,25 @@ def audit(
|
|||
return kept, refused
|
||||
|
||||
|
||||
def unattributed(source: str) -> List[str]:
|
||||
"""SKUs whose views have no entry in the source's ATTRIBUTION.json.
|
||||
|
||||
Reported, not refused. A view with no recorded source is still a usable
|
||||
reference - the six that predate the attribution file are independent of
|
||||
the evaluation photo and their OCR matches their catalog names - but the
|
||||
provenance is gone, and provenance is what lets a later audit tell a
|
||||
re-sourced packshot from a leaked shelf crop. Naming them keeps the gap
|
||||
from growing quietly.
|
||||
"""
|
||||
path = os.path.join(source, "ATTRIBUTION.json")
|
||||
try:
|
||||
with open(path) as handle:
|
||||
recorded = json.load(handle)
|
||||
except (OSError, ValueError):
|
||||
recorded = {}
|
||||
return sorted(sku for sku in collect(source) if sku not in recorded)
|
||||
|
||||
|
||||
def text_discriminated_siblings(
|
||||
kept: Dict[str, List[str]], source: str, names: Optional[Dict[str, str]] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
|
|
@ -564,6 +583,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|||
text_only = sorted(set(names) - set(kept))
|
||||
siblings = text_discriminated_siblings(kept, args.source, names)
|
||||
small = below_pyramid_floor(kept, args.source)
|
||||
no_source = unattributed(args.source)
|
||||
verdict = live_ready_verdict(kept, names, shelf_skus)
|
||||
report = {
|
||||
"source": args.source,
|
||||
|
|
@ -577,6 +597,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|||
"text_only_skus": text_only,
|
||||
"text_discriminated_siblings": siblings,
|
||||
"below_pyramid_floor": small,
|
||||
"unattributed_skus": no_source,
|
||||
"completeness": verdict,
|
||||
"skus": {sku: views for sku, views in sorted(kept.items())},
|
||||
"refused": refused,
|
||||
|
|
@ -600,6 +621,9 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|||
for row in small:
|
||||
print(f" {row['sku']:<10} {row['view']:<20} {row['height']}px tall - "
|
||||
"scores are not comparable with the rest; re-source this view")
|
||||
if no_source:
|
||||
print(f"\nno recorded source ({len(no_source)}): {', '.join(no_source)} - "
|
||||
"usable, but provenance is missing; re-sourcing would close it")
|
||||
if verdict["live_ready"] is None:
|
||||
print(f"\ncompleteness: unknown - {verdict['reason']}")
|
||||
elif verdict["live_ready"]:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue