Data Model

Supabase (PostgreSQL). All tables use user_id TEXT (device UUID from AsyncStorage). No auth layer. Migrations live in src/data/migrations/.

Curated POI count 372 hand-curated points of interest in the Barcelona seed dataset. This is the canonical count.

walks

One row per walk session. Created on walk start, updated on end.

ColumnTypeNotes
idUUID (PK)gen_random_uuid()
user_idTEXTDevice UUID
started_atTIMESTAMPTZWalk start
ended_atTIMESTAMPTZWalk end (null if active)
distance_kmNUMERICTotal distance walked
duration_minINTEGERDuration in minutes
route_polylineTEXTEncoded polyline of walk path
quest_idUUIDFK to user_quests (nullable)

photo_snaps

Every snap photo taken during a walk. AI classifies themes for collection badge progress.

ColumnTypeNotes
idUUID (PK)gen_random_uuid()
user_idTEXTDevice UUID
walk_idUUIDFK to walks
photo_urlTEXTSupabase Storage URL
latitudeDOUBLE PRECISIONSnap location
longitudeDOUBLE PRECISIONSnap location
created_atTIMESTAMPTZSnap timestamp
is_publicBOOLEANPublished to public map
display_nameTEXTUser display name (for public map)
captionTEXTUser caption
save_countINTEGERNumber of bookmarks
published_atTIMESTAMPTZWhen published to map
classified_themesTEXT[]AI-classified photo themes for collection badges (e.g. cat, door, tile)

quest_library

Curated quest definitions. Three types: wander, collect, visit.

ColumnTypeNotes
idUUID (PK)gen_random_uuid()
titleTEXTQuest title
quest_typeTEXTwander / collect / visit
descriptionTEXTLong description (3-5 sentences)
short_descriptionTEXTOne-liner for card preview
intentionTEXTQuest intention/prompt (single line shown on card)
neighbourhoodTEXTTarget neighbourhood
tagsTEXT[]Content tags (e.g. architecture, street art)
estimated_duration_minINTEGEREstimated walk time
estimated_distance_kmNUMERICEstimated distance
locationsJSONBArray of {name, lat, lng, neighbourhood} for visit quests
target_countINTEGERSnap target for collect quests (e.g. 5)
verification_methodTEXTsnap / proximity / none
detection_themesTEXT[]Themes for collect quest AI classification (e.g. cat, tile)
day_typesTEXT[]Weekday/weekend eligibility (e.g. {weekday, weekend})
ai_generatedBOOLEANTrue if LLM-generated (not curated)

user_quests

Quest instances accepted by users. Tracks progress and completion.

ColumnTypeNotes
idUUID (PK)gen_random_uuid()
user_idTEXTDevice UUID
quest_idUUIDFK to quest_library (nullable for LLM quests)
quest_dataJSONBFull quest snapshot at accept time
statusTEXTactive / completed / abandoned
progressJSONBProgress state (snaps counted, locations visited)
detection_themesTEXT[]Copied from quest on accept (for ambient SNAP matching)
accepted_atTIMESTAMPTZWhen quest was accepted
completed_atTIMESTAMPTZWhen quest was completed

daily_tailored_quest

One personalised quest per user per day, generated by cron from curated POIs or live Gemini search.

ColumnTypeNotes
idUUID (PK)gen_random_uuid()
user_idTEXTDevice UUID
active_dateDATEQuest date
quest_dataJSONBFull quest JSON
route_chosenTEXTcurated / live
reasoningTEXTLLM reasoning for quest selection
created_atTIMESTAMPTZ

saved_snaps

Bookmarked public map snaps.

ColumnTypeNotes
idUUID (PK)gen_random_uuid()
user_idTEXTDevice UUID
photo_idUUIDFK to photo_snaps (CASCADE)
saved_atTIMESTAMPTZ

collection_map_shares

Shareable collection map links. Each row generates a public token URL for a user's collection quest photos.

ColumnTypeNotes
idUUID (PK)gen_random_uuid()
tokenUUID (UNIQUE)Public share token
user_idTEXTDevice UUID
quest_titleTEXTQuest title for display
detection_themeTEXTTheme filter for photos
created_atTIMESTAMPTZ

user_taste_profile

Taste memory dimensions. Updated by walk behaviour and manual edits.

ColumnTypeNotes
user_idTEXT (PK)Device UUID
dimensionsJSONBTaste dimension scores (architecture, nature, food, etc.)
seeded_fromTEXTOnboarding source
updated_atTIMESTAMPTZ

Notification Tables

user_notification_tokens

Expo push tokens per device.

ColumnTypeNotes
idUUID (PK)gen_random_uuid()
user_idTEXTDevice UUID
push_tokenTEXTExpo push token
platformTEXTios / android
created_atTIMESTAMPTZ

user_notification_preferences

Per-user notification settings with quiet hours.

ColumnTypeNotes
user_idTEXT (PK)Device UUID
enabledBOOLEANMaster toggle (default true)
quiet_hours_startINTEGERHour (0-23), default 22
quiet_hours_endINTEGERHour (0-23), default 8
streak_nudgesBOOLEANDefault true
comeback_nudgesBOOLEANDefault true
weather_nudgesBOOLEANDefault true
event_nudgesBOOLEANDefault true
quest_nudgesBOOLEANDefault true
updated_atTIMESTAMPTZ

notification_log

Record of every push notification sent.

ColumnTypeNotes
idUUID (PK)gen_random_uuid()
user_idTEXTDevice UUID
notification_typeTEXTstreak / comeback / weather / event / quest
titleTEXTNotification title
bodyTEXTNotification body
sent_atTIMESTAMPTZ
opened_atTIMESTAMPTZWhen user tapped notification

user_activity_log

Tracks daily app opens for streak and comeback detection.

ColumnTypeNotes
user_idTEXTComposite PK with active_date
active_dateDATEComposite PK
opened_atTIMESTAMPTZFirst open of the day