Skip to content

Stamps — Domain Spec

A stamp is curated artwork for a place, earned by genuine visits. Every stamp carries a guide — a curated list of entries (landmarks, cities, regions, or countries depending on scope). Visit minimum_visits of them and the stamp lands in the user's Passport.

Mental model

Stamps are exclusively about places — never about activity volume, social actions, or recurrence (those belong to challenges + curios).

One earn shape across all stamps: "visit X of Y guide entries." What "an entry" is depends on the stamp's scope.

Scope → entry shape

Scope Guide entries are… A visit satisfies an entry when…
landmark A single location_id (the iconic place itself) travel_log.location_id matches
district location_id list (landmarks within the district) location_id matches
city location_id list (landmarks within the city) location_id matches
region Mix of city and/or region entries travel_log.city or travel_log.region matches
country Mix of region and/or city entries travel_log.region or travel_log.city matches
multi_country country entries (ISO-2 codes) travel_log.country matches

A region stamp like "Tuscany Highlights" can list specific cities (Florence, Siena, Lucca) AND broader regions (Tuscany) — visiting any place in Florence satisfies the Florence entry; visiting any place in Tuscany satisfies the Tuscany entry. Country stamps for, say, France can mix region entries (Île-de-France, Provence) with city entries (Lyon, Marseille).

Guide content

Each entry is also user-facing content. On the stamp's detail screen, the guide renders as a card list — a small travel guide:

StampGuideEntry
├── title           — "Pike Place Market" / "Florence" / "Tuscany" / "Japan"
├── description     — short blurb / why it matters
├── photo_key       — REQUIRED for every entry; surfaces the visual hook
├── recommendations — optional tips (what to do, when to go)
└── sort_order      — admin-controlled ordering in the guide

Pre-earn: the guide is a discovery surface — places worth visiting. Post-earn: shows which entries you satisfied and which you skipped.

Minimum visits

minimum_visits defaults to the total entry count at admin save time — "visit all" is the default. Admin can lower it to make an "X of Y" stamp (e.g., a 12-temple guide where visiting any 5 earns the stamp).

Lifecycle

Phase Entered when Exit
draft Admin saves Admin publishes
published Admin publishes Admin archives
archived Admin archives (terminal)

Stamps don't have time-bounded windows like challenges — they're always-on once published.

Earn flow

The engine's stampResolver runs on every TravelLogRecorded signal. For the row's geography, it queries candidate stamps (published + not already earned by the user + at least one guide entry that this row's geography matches). For each candidate, it determines which entries the row satisfies, appends new entry IDs to user_stamp_progress.matched_ids, bumps current_count. When current_count >= minimum_visits, sets earned_at.

matched_ids tracks satisfied entries, not raw location IDs. Revisiting a place that satisfies an already-matched entry is a no-op.

Retroactive evaluation on stamp publish (so users with qualifying prior visits earn the stamp on publish day) is admin-triggered; see the Retroactive replay section below.

Retroactive replay

Admin can trigger retroactive evaluation on a published stamp. All stamps are eligible (no opt-in or cadence). The engine walks every user's historical travel-log rows that match any of the stamp's guide entries and credits matches accordingly. Idempotent — re-running is safe because matched_ids dedup prevents double-counts. Auto-enqueue on publish is off; admin clicks Replay explicitly.

What stamps don't do

  • No rule trees — fixed guide-entry semantics
  • No opt-in — a published stamp evaluates passively for every user
  • No reward chain — a stamp doesn't grant a curio; the stamp IS the reward
  • No time-bounded expiry — stamps live forever once published

See also