Match real labels: E-numbers, aliases and Open Food Facts import; crop scans to the frame - #3
Merged
HedgehogsGX merged 2 commits intoSep 9, 2026
Conversation
…o-frame, results context
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Additivegains a unique nullablee_number(E250,E150a,E101(i)) and a JSON list of lowercasealiases; the four risk factors become nullable. Both fields are into_dict().import_additives.pyupserts the bundled Open Food Facts additives taxonomy (backend/data/additives.txt, raw and unmodified, SHA-256 + ODbL attribution inPROVENANCE.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.match_additives(strings, index)over an in-memory index rebuilt only when the additive table changes (or onScanService.refresh_alias_index(), which the importer calls):0→oinside alphabetic tokens).E 250,E-250,e250); bare codes only count after an additive-class word (preservative (250),flavour enhancer (621)), mirroring EU label conventions.carbonatedno longer matches E502Carbonates). Short aliases likeMSGmatch exactly only.matched_textshowing what was read.risk_score: nullandtraffic_light: "Unrated". The overall rating ignores them; if every hit is unrated the overall isnull/Unrated; no hits stays0/Green.Frontend
onLayout, mapped through the preview's centred cover fit to photo pixels (utils/cropRect.ts, pure and unit-tested), cropped withexpo-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).ScanResultProviderin_layout.tsxreplaces the JSON-in-route-params hand-off;/resultredirects home when there's no scan.Unratedrendering: grey info badge, explanatory copy, per-factor "Unrated",—for a null overall score;matched_textshown on each card.npm testruns 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).s0dium nitrate→ E301, E250, E251; plain oats label → nothing.npm test: 6 passed.npx tsc --noEmit: clean.expo install --checkacceptsexpo-image-manipulator ~14.0.8.Known limitations
pectin→ E440 and E440a), so one word can yield two results.synonyms l-xylo-ascorbic acidon E300).