Back to home

Architecture

DynamoDB single-table design — one table, all access patterns covered by PK/SK + one GSI.

Single Table

Every entity (sightings, users, watches, confirmations) lives in one DynamoDB table, accessed via PK/SK patterns.

TTL as UI Signal

expiresAt is both a DynamoDB TTL for deletion AND the source of confidence %. No separate cron job needed.

One GSI for Trending

TrendingIndex on GSI1PK (CATEGORY#X) + GSI1SK (ISO timestamp) enables the 6-hour trending query across all areas.

Table: blip (single-table)

EntityPK (hash)SK (range)GSI1PKGSI1SKTTL
Sighting

Core item — TTL drives confidence decay in UI

AREA#<areaSlug>SIGHTING#<reportedAtISO>#<sightingId>CATEGORY#<category><reportedAtISO>#<sightingId>expiresAt (epoch)
User Profile

Trust tier, streak, report totals

USER#<userId>PROFILE
Watch Alert

User-defined area+category monitors

USER#<userId>WATCH#<areaSlug>#<category>#<watchId>
Confirmation

Audit trail + confirms that sighting is still live

SIGHTING#<sightingId>CONFIRMATION#<ISO>#<nanoId>expiresAt (matches parent sighting)

Access Patterns

Get all active sightings for an area

PK: AREA#DowntownSK: begins_with(SIGHTING#)Main Table
GET /api/sightings?area=Downtown

Get user profile

PK: USER#u_abc123SK: = PROFILEMain Table
GET /api/profile?userId=u_abc123

Get user watch alerts

PK: USER#u_abc123SK: begins_with(WATCH#)Main Table
GET /api/watch?userId=u_abc123

Trending by category (6h window)

PK: CATEGORY#SneakersSK: GSI1SK >= cutoff_ISOTrendingIndex (GSI)
GET /api/trending

Confirm a sighting

PK: AREA#DowntownSK: begins_with(SIGHTING#) + filter sightingIdMain Table
POST /api/sightings/:id/confirm

Confidence Score Formula

confidence =
(expiresAt - now) / 1800 // time ratio [0-1]
+ min(confirmationCount * 0.1, 0.3) // confirm boost, max 30%
+ trustBoost // bronze 0 / silver 5% / gold 10%

> 66%

Fresh

Bright orange ring, high opacity

33–66%

Fading

Yellow ring, slightly dimmed

< 33%

Stale

Gray ring, reduced opacity

Load demo data

Seed 8 realistic sightings across all areas to see the decay engine live.

Seed Demo

TrendingIndex GSI — How It Works

Write path (on sighting creation)

GSI1PK =

"CATEGORY#Sneakers"

GSI1SK =

"2026-06-25T14:30:00Z#abc123"

Read path (trending query)

// For each category:

KeyCondition:

GSI1PK = "CATEGORY#X"

AND GSI1SK >= "6h ago ISO"

Filter: expiresAt > now

// Count = trending score