Skip to content

Match real labels: E-numbers, aliases and Open Food Facts import; crop scans to the frame - #3

Merged
HedgehogsGX merged 2 commits into
capy/fix-dev-loop-breakagesfrom
capy/real-label-matching
Sep 9, 2026
Merged

HedgehogsGX merged 2 commits into
capy/fix-dev-loop-breakagesfrom
capy/real-label-matching

Conversation

@HedgehogsGX

Copy link
Copy Markdown
Owner

Stacked on #2. Makes the matcher work on what labels actually print, and stops uploading the whole shelf.

Backend: E-numbers, aliases, token-based matching

  • Additive gains a unique nullable e_number (E250, E150a, E101(i)) and a JSON list of lowercase aliases; the four risk factors become nullable. Both fields are in to_dict().
  • import_additives.py upserts the bundled Open Food Facts additives taxonomy (backend/data/additives.txt, raw and unmodified, SHA-256 + ODbL attribution in PROVENANCE.md): 643 concrete E-number entries with English names and synonyms. It's idempotent, offline, preserves the five seeded ratings, and assigns no risk factors to imported rows.
  • Matching is now a pure match_additives(strings, index) over an in-memory index rebuilt only when the additive table changes (or on ScanService.refresh_alias_index(), which the importer calls):
    • OCR text is normalised (lowercase, punctuation → spaces, 0o inside alphabetic tokens).
    • E-numbers are extracted with OCR-tolerant spacing (E 250, E-250, e250); bare codes only count after an additive-class word (preservative (250), flavour enhancer (621)), mirroring EU label conventions.
    • Names/aliases match on exact phrase boundaries first; fuzzy matching applies only to aliases of 6+ characters, and lone words need a stricter score so everyday label words don't collide with additive names (carbonated no longer matches E502 Carbonates). Short aliases like MSG match exactly only.
    • Each additive is returned once with matched_text showing what was read.
  • Additives without curated factors return risk_score: null and traffic_light: "Unrated". The overall rating ignores them; if every hit is unrated the overall is null/Unrated; no hits stays 0/Green.
  • Keeps Fix dev-loop breakages and harden the scan API #2's lock-guarded lazy EasyOCR reader.

Frontend

  • Crop to the scan frame: the camera and frame rects are measured with onLayout, mapped through the preview's centred cover fit to photo pixels (utils/cropRect.ts, pure and unit-tested), cropped with expo-image-manipulator, downscaled to ≤1600 px on the long side at JPEG q0.8, then uploaded. The dark overlay now surrounds the frame on all four sides (the top overlay style existed but was never rendered).
  • Results via context: ScanResultProvider in _layout.tsx replaces the JSON-in-route-params hand-off; /result redirects home when there's no scan.
  • Unrated rendering: grey info badge, explanatory copy, per-factor "Unrated", for a null overall score; matched_text shown on each card.
  • npm test runs the crop geometry tests with Node's built-in runner (no Jest); wired into CI.

Verification

  • pytest: 45 passed (realistic labels: cured meat by name, soda by E-numbers only, mixed, none, OCR noise, short-alias guard, nitrite/nitrate and ascorbic/isoascorbic non-confusion, full-snapshot import idempotency and rating preservation, snapshot checksum).
  • Full-snapshot smoke: soda label → E338, E211, E150d, E950, E951; cured-meat label with s0dium nitrate → E301, E250, E251; plain oats label → nothing.
  • npm test: 6 passed. npx tsc --noEmit: clean. expo install --check accepts expo-image-manipulator ~14.0.8.
  • Not verified on a device: the preview→photo crop mapping is tested as pure geometry only and needs a real iOS/Android check.

Known limitations

  • Coverage ≠ ratings: 638 of 643 imported additives are Unrated until curated factors are sourced.
  • Some taxonomy aliases are shared (e.g. pectin → E440 and E440a), so one word can yield two results.
  • One upstream data quirk imports verbatim (synonyms l-xylo-ascorbic acid on E300).

Open in Capy

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

View 3 other findings in Capy.

Open in Capy

@HedgehogsGX
HedgehogsGX merged commit 5016da4 into capy/fix-dev-loop-breakages Sep 9, 2026
2 checks passed
HedgehogsGX pushed a commit that referenced this pull request Sep 11, 2026
The repository still held the February 2026 Flask + Expo prototype, so every
change made against it (PRs #1-#3) targeted a codebase that had already been
superseded locally. This commit makes the repository match the actual project:
a native SwiftUI iOS app that runs Apple Vision OCR on device and calls
OpenRouter only for per-ingredient commentary on already-parsed names.

Also fixes the online analysis path, which was dead on arrival:

- OpenRouter retired `stealth/ox-alpha` when its stealth testing period ended,
  so every online request returned HTTP 404. The model is now published as
  `z-ai/glm-5.3-flash`; verified end to end against the live API with the app's
  exact payload (reasoning excluded, JSON mode, require_parameters,
  data_collection=deny), which returns a well-formed result at ~$0.00015 per
  four-ingredient scan.
- The 404 was already handled gracefully as `modelUnavailable`, so the app fell
  back to local analysis rather than failing; no error handling change needed.
- User-facing copy named "Ox Alpha" and described an anonymous stealth upstream.
  Both are now inaccurate: the model is Z.ai's GLM-5.3 Flash, routed by
  OpenRouter to named third-party inference providers. The privacy policy,
  consent sheet and profile copy now say so, and the display name lives in one
  constant instead of eight literals.

Replaces the Python CI workflow with the iOS one (xcodegen, simulator unit
tests, unsigned release build, privacy-manifest assertions). The developer
OpenRouter key stays in the gitignored Config/Secrets.xcconfig; only the
placeholder .example file is tracked. 79 unit tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant