Skip to content

feat(reports): postbox-data problem reporting, admin review & OSM pipeline - #117

Merged
code418 merged 1 commit into
masterfrom
feat/postbox-data-reports
May 11, 2026
Merged

code418 merged 1 commit into
masterfrom
feat/postbox-data-reports

Conversation

@code418

@code418 code418 commented May 11, 2026

Copy link
Copy Markdown
Owner

What & why

Postbox data imported from OpenStreetMap is often wrong or incomplete (a real box missing entirely, or a wrong/missing royal cypher). This adds an end-to-end feedback loop so players can flag it and designated admins can action it.

Features

  • Report a missing postbox — from the Claim/Nearby empty state; captures the user's live GPS as the authoritative location, plus an optional note, suggested cypher, and up to 3 photos.
  • Report a wrong/missing cypher — from the History screen's postbox detail sheet.
  • Geotagged photo evidence — up to 3 optional photos per report, uploaded to Cloud Storage under report_photos/{uid}/; EXIF GPS / capture time extracted client-side for reviewer verification (best-effort — browsers strip it, iOS needs requestFullMetadata + camera location on, so the live geolocator fix is always authoritative).
  • In-app admin review — Pending/Accepted/Rejected tabs streaming the reports collection, visible only to users with the admin Firebase Auth custom claim; cards show location (with a "Map" link), current vs suggested cypher, note, and photo thumbnails (tap → full view with the photo's EXIF GPS vs the reported location); Accept (with a final-cypher picker + ref/note) and Reject actions.
  • Acceptance pipeline — accepting updates the Firestore postbox/{id} entry (or creates postbox/manual_{reportId} with source: 'user_report' for a missing box), retroactively re-scores every affected claim plus the users' aggregates and all leaderboards when the points value changes, and generates an osmChange .osc file + an openstreetmap.org/edit deep-link for a human to push to OSM (deliberately not automated, per OSM's automated-edit policy).

Backend (functions/)

  • src/reports.ts — submitReport / reviewReport callables (wired into index.ts) + the pure, unit-tested buildOsmChange. reviewReport is gated on request.auth.token.admin === true.
  • src/_recomputeScores.ts — repointClaimsForPostbox (batched claim rewrite with an audit trail), recomputeUserAggregates (re-derives lifetimePoints / uniquePostboxesClaimed / maxDailyPoints from claims, re-sums daily/weekly/monthly via the existing updateUserLeaderboards, refreshes leaderboards/lifetime + the box's-county stats/leaderboard), rescoreAfterCypherChange orchestrator. Streaks are intentionally not recomputed (they depend on claim dates, not points).
  • _getPoints.ts — KNOWN_MONARCHS, pointsForMonarch.
  • types.ts — ReportDoc / ReportPhoto; PostboxDoc correction/source fields.
  • firestore.rules — reports/{id} readable by the reporter or an admin, no client writes. storage.rules (new, registered in firebase.json) — report_photos/{uid}/{file} owner-write + owner/admin-read (≤10 MB, images only); osm_changesets/{file} admin-read-only, server-write only.
  • set_admin.js (new CLI) — node set_admin.js <uid> [--remove] grants/revokes the admin custom claim (mirrors import_postboxes.js).

Flutter (lib/)

  • reports/ — report_repository.dart (pick photos, extract EXIF, upload to Storage, call submitReport), report_missing_postbox_screen.dart, report_cypher_screen.dart, my_reports_screen.dart, report_form_widgets.dart (shared CypherPicker + PhotoPickerField).
  • admin/ — admin_access.dart (AdminAccess.isAdmin() reads the cached custom claim), admin_reports_screen.dart.
  • Wiring — "Report a missing postbox" buttons on the Claim and Nearby empty states; "Report wrong cypher" in the History detail sheet; History gained a list/map ViewToggle; "My reports" (always) and "Admin · Reports" (admins only) in the app-bar overflow menu; a JamesMessages.reportSent line.
  • New deps: firebase_storage, image_picker, exif, url_launcher; iOS Info.plist gained NSCameraUsageDescription / NSPhotoLibraryUsageDescription.

Tests / verification

  • functions: build + lint clean; 262 TS tests passing (adds buildOsmChange, parsePhotos, pointsForMonarch, maxDailyFromClaims, and submitReport / reviewReport auth & validation).
  • Flutter: flutter analyze clean; 105 Dart tests passing.

Operational notes / follow-ups

  • The Firebase project needs a Storage bucket enabled, and the first admin set with node functions/set_admin.js <uid> --project the-postbox-game (the user re-authenticates afterwards).
  • Retroactive rescoring runs inline in reviewReport, bounded by a box's distinct claimants; could move to an async rescoreJobs trigger if a box ever has very many claimants.
  • A postbox/manual_{reportId} box will collide with the same OSM node once it appears in a future import — add dedup to import_postboxes.js as a follow-up.
  • Consider a per-uid daily rate limit on submitReport.

🤖 Generated with Claude Code

…eline

Add a facility for players to report problems with postbox data and for
designated admins to action them:

- Report a missing postbox from the Claim/Nearby empty state (captures live
  GPS as the authoritative location).
- Report a wrong/missing cypher from the History screen's postbox detail.
- Up to 3 optional geotagged photos per report, uploaded to Cloud Storage;
  EXIF GPS / capture time extracted client-side for reviewer verification.
- In-app admin review queue (Pending/Accepted/Rejected) gated on the `admin`
  Firebase Auth custom claim; accept/reject with a final-cypher picker.
- Accepting a report updates the Firestore postbox entry, retroactively
  re-scores every affected claim plus the users' aggregates and leaderboards
  when the points value changes, and generates an osmChange (.osc) file +
  an openstreetmap.org/edit deep-link for a human to push to OSM.

Backend: new `submitReport`/`reviewReport` callables (`functions/src/reports.ts`),
`_recomputeScores.ts` for retroactive re-scoring, `KNOWN_MONARCHS`/`pointsForMonarch`
in `_getPoints.ts`, `reports` Firestore rules, `storage.rules`, and a
`set_admin.js` CLI to grant/revoke the admin claim. Flutter: `lib/reports/`
(repository + 3 screens + shared widgets), `lib/admin/` (access helper +
review screen), wired into Claim/Nearby empty states, History (map/list
toggle + report action), and the Home overflow menu. New deps: firebase_storage,
image_picker, exif, url_launcher; iOS Info.plist camera/photo usage strings.

Tests: 262 TS passing (adds buildOsmChange, parsePhotos, pointsForMonarch,
maxDailyFromClaims, submitReport/reviewReport auth & validation); 105 Dart
passing; `flutter analyze` clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@code418
code418 merged commit 4acdbdf into master May 11, 2026
1 of 2 checks passed
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