docs planning PHOTO_PRICE_CONTRIBUTION_PLAN - AlexandreZanata/Abastevo GitHub Wiki
Android photo price contribution plan
Current initial-policy amendment: ADR-021 / PRM15–PRM21 supersedes mandatory 150m/GPS contribution admission and proximity-based price confidence. Identity, media, privacy and source-integrity checks remain. Historical location evidence and optional nearby geometry retain their original scope; future mandatory proximity is PRM22 DEFERRED.
State: PREPARATION_LOCAL_DONE / IMPLEMENTATION_PLANNED, 2026-10-07. Owner: P37-T01–T03, following P34–P36 source integration. This record prepares the user's requested flow and private real-photo evaluation; it does not certify OCR, proximity, uploads, training storage or production. Develop one bounded task at a time on maintained dev under ADR-020. Backend contracts and critical tests precede affected Android consumers. iOS remains deferred.
Accepted product decisions
- Open the camera only after the selected station's proximity requirements pass. Initial maximum distance: 150 metres, confirmed by the user. This is a radius around a reviewed station point, not proof of being inside its property boundary.
- Capture, recognize locally, review a large photo, optionally zoom/pan/crop locally, recognize the resulting crop again, edit/remove fuel rows, then explicitly submit. No photo upload during capture, zoom, crop or OCR.
- Initially show only recognized fuel rows with usable prices. Each row has the existing fuel icon, name/unit, editable price and an accessible X/remove action, arranged vertically. One or several fuels are valid; no complete price list is required. Blank values and removed rows are excluded.
- No payment-method or loyalty-program inputs in this flow. Preserve price-condition truth internally; restricted/ambiguous prices cannot silently become STANDARD.
- Optional AI-training consent, off by default. Operational photo copies expire within 24 hours. An explicitly authorized, minimized training copy may have permanent storage with no automatic TTL, subject to the continuing purpose and applicable revocation/erasure obligations. The consent amendment plan owns this exception; current runtime policy has not changed.
Inspected baseline and preparation evidence
Source revision: 286c418, maintained dev, opening tree clean; locally recorded main/origin/main a424688 is an ancestor. Existing source work is preserved. Remote PR/check state was not refreshed; historical PR #124 records in PROGRESS remain historical. No push, PR, merge, deployment, server mutation or training upload belongs to this preparation.
- CaptureScreen: system-camera capture and zoom/crop UI exist. Permissions currently request camera and location together. Review enumerates the entire fuel enum rather than recognized rows. Crop is a centered fixed-ratio operation; decode/crop/encoding and original reads need bounded worker execution, orientation handling and reliable cleanup.
- CaptureOcrViewModel:
preparePhotopasses null OCR text;replacePhotodoes not rerun recognition. The 150m gate uses nearby/last-known distance, rather than a complete fresh location-risk decision. Submit creates capture timestamps at submission time, generates new IDs and does not correctly expose every enqueue outcome. Bind station independently from any initially selected fuel. - LocalRegexPriceOcr parses supplied text; it is not an image-to-text recognizer or a spatial fuel/price associator. Existing mocked OCR tests are not real-image accuracy evidence.
- Server location intake accepts optional location for the existing observation contract. A UI gate alone is not authoritative photo-flow enforcement. Reuse location integrity and signed intake; preserve existing metadata/manual contribution semantics.
- Wire fuel vocabulary: Android's legacy
GASOLINE_PREMIUMmaps to wireGASOLINE_ADDITIVED. True premium gasoline such as Podium has no distinct v1 product. Do not map it to regular/additivated gasoline. Generic “Diesel” is insufficient evidence for S500. Any required vocabulary extension needs an explicit contract/migration task before its consumer. - Private corpus: 1,632 source files, 1,564 distinct SHA-256 images, 68 exact duplicate copies. Manual pixel review selected 1,263 files / 1,219 unique images containing visible unit-price displays, including distant/partial signs and pump displays. 369 files were excluded as no visible unit price. Filenames were not used as price/fuel labels. Original files remain untouched; selected copies were hash-verified.
- Private output:
combustiveis/teste_ocr/precos/, completeclassificacao.csv/json,resumo.json, twelveamostra_celular/images, manually readgabarito_smoke.jsonand an empty results CSV template. All remain ignored by Git. Classification means prices are visible, not that all prices/products are unambiguous or machine-readable. ./gradlew :app:assembleDebug --offline --no-daemon --console=plainsucceeded. The unchanged-source debug APK installed and launched on the user-connected POCO model 2311DRK48G, Android API 36. Explore → station list → station detail worked in landscape and portrait; the selected station displayed community-price unavailability and a disabledAtualizar preçoaction. A prior Samsung tablet API 30 also received/launched the same APK before it was disconnected. These are limited preparation checks, not camera/OCR/staging acceptance.- Twelve smoke images (2,757,325 bytes) were staged through ADB in the POCO's
/data/local/tmp/abastevo-ocr-20261007/, outside Gallery. This is an external evaluation corpus, not an app capture, current contribution or training-consented collection. Device identifiers and screenshots stay private. Recognition, inside/outside station tests, crop and upload remain NOT_EXECUTED. - Baseline command
./gradlew :app:testDebugUnitTest --tests com.anpfuel.app.capture.CaptureOcrViewModelTest :data:testDebugUnitTest --tests com.anpfuel.data.local.ocr.LocalRegexPriceOcrTest --offline --no-daemon --console=plainpassed: 16 capture ViewModel tests and 3 regex-parser tests, zero failures/errors/skips. They validate existing mocked/text behavior only. Original source hashes, selected-copy hashes, twelve unique smoke annotations and nine local document links were checked. The disabled action was tapped on POCO and the station screen remained, with no camera launch; this does not establish a geofence denial result.
Behavior contract — document before implementation
- B-BR-PC01 — Camera eligibility: station has a canonical UUID and usable reviewed coordinates; obtain location only for this action and classify freshness, accuracy, clock and OS mock signal with the existing P16 contract. Missing/denied/stale/inaccurate/mock/unknown evidence does not open the camera. No background tracking. Existing anonymous reads/manual lookup remain usable. A finite, nonnegative measured distance at most 150m is necessary; a distance from a stale list alone is insufficient. Preserve existing frozen freshness/accuracy bounds and document the uncertainty decision before code.
- B-BR-PC02 — Local-first image processing: preserve actual capture time and immutable source generation. Sample-decode off the UI thread, apply EXIF orientation before coordinate mapping, display a large image, permit pinch zoom/pan and adjustable crop, and retain fuel labels with prices and visible restrictions. Cropping away a condition does not erase an ambiguity detected in the original. Encode the final crop with the existing P15 JPEG/dimension/attempt budgets; do not increase budgets by default. No original/GPS metadata goes to the server. Delete superseded temporary files and do not reset expiry on crop/retry.
- B-BR-PC03 — OCR as a proposal: recognize pixels into text blocks with positions; associate label, unit and neighboring price by spatial row/column. Accept comma/dot and supported two/three decimal digits into exact integer milli-BRL. Distinguish R$/L from pump total, litres, dates, address numbers, serials and watermarks. A zero, uncertain variant, unsupported premium, multiple conflicting candidates or restricted-price ambiguity requires correction/retake rather than an invented row. Never choose the smallest number automatically.
- B-BR-PC04 — Reviewed subset: render recognized, supported fuel rows only. Show one row per unambiguous product/price; preserve edits/removals through recomposition, rotation and crop reanalysis. A new crop may suggest changes but must not overwrite user edits or restore removed rows silently. Allow an explicit add-fuel action when OCR misses a visible product; this does not populate every enum automatically. Empty recognition presents crop/retake and manual addition with the retained photo.
- B-BR-PC05 — Confirmation and truth: the explicit confirmation attests the selected rows and advertised default price without exposing payment inputs. If the image only offers a cash/app/loyalty price or conflicts across conditions, request a photo/crop of the default price or a corrected supported default-price selection. Exclude unsupported conditional rows from this simplified journey. Existing condition-aware routes retain their contract; do not rewrite CASH/APP/LOYALTY to STANDARD. Surface an explanation rather than guessing.
- B-BR-PC06 — Durable submission: accept at least one valid, nonblank row, validate every remaining row, then persist a single review snapshot with stable per-row submission IDs and one media reference. Preserve original capture time; never relabel old images as new. Handle disabled/denied/expired/failed enqueue results truthfully. Double taps, process death and retries cannot create duplicates. One row's error cannot report all rows accepted. Server validation/publication remains distinct from local queueing.
- B-BR-PC07 — Optional training use: no preselected consent, bundled mandatory acceptance or trust/payment advantage. Record the version and permission applicable to each image; check current server consent again before promotion, export and training. Consent refusal never blocks an otherwise valid contribution. No training reuse of the historical evaluation corpus merely because its owner requested app tests.
Use cases: BUC-PC01 station/context → eligibility → capture or recovery instructions; BUC-PC02 recognize → zoom/crop → re-recognize → edit/remove/add → confirm subset; BUC-PC03 durable enqueue → upload → owner-visible pending/accepted/rejected/retry/expired status; BUC-PC04 optional consent → private training promotion → revoke/delete.
Portuguese UI copy to validate in the existing design system:
- Primary action:
Contribuir com foto. - Outside:
Para contribuir com foto, você precisa estar a até 150 metros deste posto. Aproxime-se e tente novamente. - No reliable fix:
Não foi possível confirmar sua localização. Ative a localização e tente novamente.Give the specific permission/accuracy recovery action when known; do not describe every failure as outside. - Review:
Confira os preços da foto/Aproxime e recorte para destacar os combustíveis e preços/Você pode enviar apenas os combustíveis que quiser. Valores em branco não serão enviados. - No detected rows:
Não reconhecemos preços com segurança. Recorte a placa, tire outra foto ou adicione um combustível. - Ambiguity:
A foto mostra um preço com condição ou um combustível que não conseguimos identificar. Confira o preço padrão ou tire outra foto. - Submit:
Confirmar e enviarwith selected-row count; queued:Aguardando envio, neverPublicado.
Sequential execution slices
P37-PC01 — Backend proximity contract and replay protection
Freeze B-BR-PC01/PC06 against existing signed observation/media/location ports and OpenAPI first. Decide a scoped photo-capture eligibility proof, bound to account/key, station, server policy, time window and capture session, using existing nonce/atomic-consume mechanisms. The exact wire addition is proposed, not an already deployed endpoint. Nearby reads are not permission proofs. Reject missing/stale/replayed/cross-account/cross-station evidence and revoked sessions. A client inside=true, isMock=false or timestamp alone grants no authority. Discard precise fixes after classification; store only permitted bands/outcomes. Do not enforce this new photo-specific rule on all old manual metadata submissions.
Test domain boundaries including 149.9m/150m/150.1m, NaN/infinity/negative/null, missing coordinates, stale/poor/coarse/mock fixes and clock errors. Immediately test signed auth/IDOR/replay, parallel consumption, real PostGIS distance and boundary behavior, policy rollout/old-client compatibility, expiry and failures. Offline behavior must not bypass eligibility: fail closed before camera when required proof cannot be acquired; an eligible existing draft may queue only within its established proof/media deadlines. Freeze any proposed evidence-renewal behavior explicitly before code.
Exit: documented transport plus tested backend behavior, generated contracts and rollback/compatibility. No station-coordinate fabrication or global GPS simulation.
P37-PC02 — Bounded image recognition adapter
Reuse the existing OCR port and pure parser, add the native pixel recognizer behind it, with cancellation and generation-based late-result rejection. Prefer an on-device Latin-script engine; no new server AI service is required for this MVP. Evaluate ML Kit's Play-Services model against the bundled model. The official Android guide distinguishes com.google.android.gms:play-services-mlkit-text-recognition (downloaded model) from com.google.mlkit:text-recognition (bundled model). Installation guidance explains availability/offline implications. Correct the existing DI comment; do not claim Apache-2.0 licensing based on sample-code licensing. Record dependency need, exact supported version, SDK terms/license, transitive/security review and measured release-size impact before addition.
Separate native decoding/OCR blocks from pure spatial association and exact money parsing. Test synthetic labelled blocks for swapped rows, separator loss, multiple columns, conditions, generic diesel, premium/additivated distinction, zero, pump total and watermark rejection. Test model not installed/download failure, no Play Services, cold/offline availability, recognition failure/cancellation and stale results. Real images remain private external fixtures, never Gradle APK assets or repository fixtures.
Exit: twelve-case device benchmark with actual recognized rows/timing, explicit manual fallbacks, and no fabricated baseline. Existing integer-money and fuel-wire contract tests pass.
P37-PC03 — Location-first camera and review interface
Request location first; request camera permission/launch only after eligible station decision. Route station UUID independently of initially selected fuel so the review can contain several products. Reuse existing icons/components; implement the large image/crop controls and vertical dynamic rows. Persist draft state through supported saved-state/ephemeral storage rather than bitmap or URI-only remember. Preserve orientation and crop coordinates; reanalysis is explicit and cancellation-safe. No heavy reads/encode on Compose's UI thread. Keep app-owned originals/crops/results private and expiring.
Test camera launcher called zero times on every denial path; denial recovery, camera permission refusal/cancellation/no-handler, invalid/huge image, portrait/landscape/process recreation, repeated crops, late OCR, edits and X/blank omission. Verify 1/2/3 fuels and zero remaining rows; unavailable model still offers honest manual correction. Test large font, TalkBack removal labels, keyboard/insets, image zoom gestures and reachable submit on POCO. No payment controls are present.
Exit: local camera/review behavior passes with synthetic controlled ports; genuine POCO capture/OCR result recorded separately.
P37-PC04 — Photo and subset outbox integration
Reuse P37-T02 proof, upload negotiation, private media and observation/outbox lifecycle. Freeze partial-batch behavior without inventing a bulk API: prefer a durable local review envelope with stable row commands through the existing one-observation-per-command contract. If current media ownership prevents safe sharing, implement/test the minimal backend reference-lifecycle change before the consumer. Do not add a new bulk endpoint solely for UI convenience.
Test transactional draft persistence, two simultaneous taps/retries, per-row idempotency, partial denial/acceptance, capture-age preservation, environment/account switches, logout/revocation, process death between negotiate/upload/complete/observe and cancellation while another row still references the same photo. Media expiry does not reset on retry or training promotion. Run immediate backend negative/concurrency/recovery/PostGIS tests for affected critical surfaces, plus affected Android Room/outbox tests.
Exit: only selected confirmed fuels arrive, truthful owner status survives restart, no duplicate facts/votes/objects and no image leaves before confirmation. Live gate needs verified staging HTTPS, canonical target, signed test account and attached private media; missing prerequisites remain named blockers.
P37-PC05 — Consent, permanent training copy and deletion
Implement the consent amendment before any collection. Add versioned server-side consent and revocation, promotion authorization and restricted storage lifecycle. Apply append-only migrations with recovery tests. Update privacy notice/terms/account deletion/export and local-media policy together; never change a generic TTL job to retain every consenting user's operational originals. Keep rollout OFF until consent/promotion/deletion/restore tests pass; disabling collection does not waive existing deletion duties.
Exit: opt-out/default/offline/revoked users have zero training copies; authorized minimized copies have no automatic TTL; ordinary evidence retains 24h; withdrawals deny reuse immediately and complete measured deletion. No training job/model is created by this app-flow task.
P37-PC06 — Real POCO and station acceptance
Run the private protocol below after the required source slices are ready. User explicitly authorized this connected-device preparation, overriding the older defer-device instruction only for this requested slice. It does not waive remaining P38/G24/G09 acceptance. Record source/backend revisions, environment, engine/model readiness, device/API and statuses without precise GPS/identity/photo data in Git.
Exit: documented genuine outcomes for pre-camera proximity, local OCR/crop/review, selected-row submission, expiry and consent. Missing acceptance stays NOT_EXECUTED/BLOCKED; APK compilation or a successful screen launch is insufficient.
Private evaluation protocol
- Use
combustiveis/teste_ocr/README.mdand the twelve-image ground truth. Run identical inputs through the actual Android adapter in a debug/instrumentation-only harness with no network upload, no fake location claims and no release import/gallery bypass. The current APK has no such harness; staged fixtures do not mean OCR ran. - Evaluate full image and user crop retaining fuel labels. Record exact product+amount matches, missed rows, false rows, unsupported/conditional/zero handling, edits, latency and peak additional memory. Ground truth includes ambiguous fuel/null and unsupported premium on purpose. Do not score an unsafe guess as a successful detection.
- Extend to at least 100 manually verified, stratified images from the selected corpus, plus consent-free synthetic no-price/invalid cases. Include LED, physical digits, worn/missing separators, reflection, shadow, angle, distant/partial signs, one fuel, multiple fuels, three decimals, zeros and watermarks. Deduplicate by hash and group near-duplicate station/sign/scene photos before tuning/evaluation splitting; 1,219 hashes are not 1,219 independent situations. Keep annotations/images private.
- Proposed benchmark targets to freeze after the first baseline: clear supported rows ≥99% precision and ≥95% recall; zero fabricated rows from non-price numbers in the annotated challenge set; no zero/unsupported/conditional price silently submitted as STANDARD. Report denominator, families and fallback rate. The twelve-image smoke is not statistical acceptance. Measure cold/warm p50/p95 OCR and memory on POCO; existing P15 memory limits remain hypotheses until measured on required devices.
- Then use the genuine camera to photograph a displayed selected image: this tests optics/reflection/perspective locally, not station presence or current prices. Historical test images must never update a real station's live price. Keep this isolated from network-capable contribution paths; do not trick the geofence to obtain the photo.
- Finally, with the user physically near a reviewed real station and an authorized staging test target, test outside >150m, boundary, inside ≤150m, permission denied, stale/inaccurate fix, airplane mode and camera cancellation. Field measurements and controlled synthetic boundary tests are separate evidence. Re-photograph the current sign; compare the real review with the sign, submit only the owned staging cases and verify 1/2/3-fuel omission/retry/status. No location spoofing or production publication.
- Test consent unchecked/checked, restart/offline queue, revoked before promotion, promotion racing revocation, account deletion, expired operational evidence, storage failure and restore. Use synthetic pictures to prove backend/backup deletion; retain only aggregate evidence in Git. Real device media test records stay private and expire according to the tested app policy; external source corpus stays under user control.
Rollout and completion criteria
Roll out to restricted staging first with separate capture/OCR and training-collection controls. On failure, disable the affected photo journey and present a truthful recovery state; do not bypass proximity or relabel condition. Preserve existing read/manual routes and identity/trust rules. Monitor only minimized counts, latency, failures and overdue-deletion metrics; no OCR text, real photos or precise contributor GPS in logs.
Each slice closes with its scoped tests and affected compile/format/static checks, diff and secret-surface review; critical changes require their negative/concurrency/real-PostGIS evidence immediately. Local source completion is LOCAL_DONE / INTEGRATION_PENDING. Protected dev → main delivery and wiki mirror occur only at the authorized construction-batch end. This preparation does not publish and does not claim G09 certification.
Next implementation task: P37-PC01, scoped backend photo eligibility/compatibility tests and contract, after confirming the current P34–P36 prerequisites. OCR and review source slices follow that checkpoint; private media readiness and consent deletion are explicit live gates.