Spaces:
Sleeping
Sleeping
Download src/shelf/validation.py from fgeeha/shelf-control: direct link, hf CLI and curl.
- Browser
- Download file 4.04 kB
-
https://huggingface.co/spaces/fgeeha/shelf-control/resolve/main/src/shelf/validation.py
- Command line
-
hf download hf://spaces/fgeeha/shelf-control/src/shelf/validation.py
-
curl -L -o validation.py https://huggingface.co/spaces/fgeeha/shelf-control/resolve/main/src/shelf/validation.py
4.04 kB
| """Validation helpers for barcodes, SKU values and normalized numeric fields. | |
| The challenge scoring is very sensitive to false positives: a wrong barcode or | |
| SKU is worse than an empty value. This module centralizes the strict validators | |
| used by OCR, QR parsing, multi-frame voting and catalog lookup. | |
| """ | |
| from __future__ import annotations | |
| import re | |
| from typing import Any | |
| SKU_RE = re.compile(r"^2\d{11}$") | |
| def normalize_digits(raw: Any) -> str: | |
| """Return only digits from ``raw`` while handling pandas float strings.""" | |
| if raw is None: | |
| return "" | |
| try: | |
| if raw != raw: # NaN | |
| return "" | |
| except Exception: | |
| pass | |
| text = str(raw).strip().replace("\u00a0", " ") | |
| if not text or text.lower() in {"nan", "none", "нет"}: | |
| return "" | |
| if re.fullmatch(r"\d+\.0", text): | |
| text = text[:-2] | |
| return re.sub(r"\D", "", text) | |
| def ean13_checksum_valid(code: str) -> bool: | |
| """Return True only for a valid 13-digit EAN-13 code.""" | |
| if len(code) != 13 or not code.isdigit(): | |
| return False | |
| digits = [int(c) for c in code] | |
| total = sum(d * (3 if idx % 2 else 1) for idx, d in enumerate(digits[:12])) | |
| return (10 - total % 10) % 10 == digits[12] | |
| def ean13_check_digit(prefix12: str) -> str: | |
| """Compute the EAN-13 check digit for a 12-digit prefix.""" | |
| if len(prefix12) != 12 or not prefix12.isdigit(): | |
| raise ValueError("EAN-13 prefix must contain exactly 12 digits") | |
| digits = [int(c) for c in prefix12] | |
| total = sum(d * (3 if idx % 2 else 1) for idx, d in enumerate(digits)) | |
| return str((10 - total % 10) % 10) | |
| def repair_ean13( | |
| raw: Any, | |
| *, | |
| allow_append_12: bool = True, | |
| allow_drop_14: bool = True, | |
| allow_one_digit_repair: bool = False, | |
| ) -> str: | |
| """Conservatively repair a trusted barcode candidate into valid EAN-13. | |
| OCR text is not a trusted source for 12-digit values because Lenta SKU values | |
| have exactly the same length. Callers should set ``allow_append_12`` only for | |
| QR/pyzbar/barcode-ROI sources, never for arbitrary OCR text. | |
| """ | |
| digits = normalize_digits(raw) | |
| if ean13_checksum_valid(digits): | |
| return digits | |
| if len(digits) == 12 and allow_append_12: | |
| candidate = digits + ean13_check_digit(digits) | |
| return candidate if ean13_checksum_valid(candidate) else "" | |
| if len(digits) == 14 and allow_drop_14: | |
| for candidate in (digits[:-1], digits[1:]): | |
| if ean13_checksum_valid(candidate): | |
| return candidate | |
| if len(digits) == 13 and allow_one_digit_repair: | |
| for idx, current in enumerate(digits): | |
| for repl in "0123456789": | |
| if repl == current: | |
| continue | |
| candidate = digits[:idx] + repl + digits[idx + 1 :] | |
| if ean13_checksum_valid(candidate): | |
| return candidate | |
| return "" | |
| def normalize_ean13( | |
| raw: Any, | |
| *, | |
| allow_repair: bool = False, | |
| allow_append_12: bool = False, | |
| allow_drop_14: bool = True, | |
| allow_one_digit_repair: bool = False, | |
| ) -> str: | |
| """Normalize and validate an EAN-13 candidate. | |
| By default this function is strict: invalid 13-digit values and 12-digit SKU | |
| lookalikes are rejected. Repair is opt-in and should be enabled only for | |
| trusted scanner/ROI sources. | |
| """ | |
| digits = normalize_digits(raw) | |
| if ean13_checksum_valid(digits): | |
| return digits | |
| if not allow_repair: | |
| return "" | |
| return repair_ean13( | |
| digits, | |
| allow_append_12=allow_append_12, | |
| allow_drop_14=allow_drop_14, | |
| allow_one_digit_repair=allow_one_digit_repair, | |
| ) | |
| def is_valid_sku(raw: Any) -> bool: | |
| """Return True for Lenta SKU values: exactly 12 digits starting with 2.""" | |
| return bool(SKU_RE.fullmatch(normalize_digits(raw))) | |
| def normalize_sku(raw: Any) -> str: | |
| """Return a strict SKU string or an empty value.""" | |
| digits = normalize_digits(raw) | |
| return digits if SKU_RE.fullmatch(digits) else "" | |