Skip to content
version: 1.1.2-7

ERO Reference

Schemas, payloads, and endpoint contracts. This document is the single owner of the Event Route Optimiser data model. Where another document needs a field list, it links here.

1. Storage schema

The schema maintains structural parity across Cloudflare D1 at the edge and Dexie over IndexedDB on the device, so synchronisation is a merge rather than a translation.

1.1 Festivals

FieldTypeNotes
idTEXTPrimary key, unique identifier or slug
nameTEXTFull festival name
urlTEXTOrigin Clashfinder URL
created_atINTEGEREpoch milliseconds

1.2 Stages

FieldTypeNotes
idTEXTPrimary key, generated GUID
festival_idTEXTForeign key to festivals.id
nameTEXTStage name

1.3 Bands

FieldTypeNotes
idTEXTPrimary key, generated GUID
stage_idTEXTForeign key to stages.id
nameTEXTAct or band name
start_timeINTEGEREpoch milliseconds
end_timeINTEGEREpoch milliseconds

1.4 User routes

Local-first. Owned by the client and authoritative on conflict.

FieldTypeNotes
idTEXTPrimary key, GUID
band_idTEXTForeign key to bands.id
priorityINTEGERRating or precedence score
sync_statusTEXTsynced, pending, or failed
updated_atINTEGEREpoch milliseconds

1.5 Local override fields

The client Act schema in apps/event-route-optimiser/web/src/lib/db.ts carries four additional fields that exist only on device.

FieldTypeDefault
is_local_overridebooleanfalse
local_start_timestringoptional
local_end_timestringoptional
local_stage_idstringoptional

A differential merge must never overwrite a row where is_local_override is true.

2. API contract

2.1 Fetch a festival

GET /api/festivals/:id

Returns nested festival, stage, and band entities in one payload, so the client can hydrate Dexie in a single pass.

2.2 Ingest a Clashfinder event

POST /api/ingest/clashfinder

Request body:

{ "festivalId": "string", "username": "string" }

Returns a success payload reporting the number of stages and acts imported or updated.

2.3 Generate a route

GET /api/route/:username

Returns a strict itinerary alternating act and transit entries.

{
"username": "example-user",
"itinerary": [
{ "type": "act", "band": "Concrete Age", "stage": "New Blood Stage", "startTime": 1786017600000, "endTime": 1786019400000 },
{ "type": "transit", "instruction": "Walk to Main Stage", "durationMinutes": 8 },
{ "type": "act", "band": "Lamb of God", "stage": "Main Stage", "startTime": 1786020000000, "endTime": 1786023600000 }
]
}

3. Clashfinder source payload

The upstream Clashfinder JSON is fetched from https://clashfinder.com/data/event/{festivalId}.json?user={username}.

3.1 Event

FieldType
eventstring, festival name
urlstring, original Clashfinder URL

3.2 Day

FieldType
idstring, for example fri
datestring, ISO date YYYY-MM-DD

3.3 Stage

FieldType
idstring
namestring, full display name

3.4 Act

FieldType
namestring
stagestring, matches a stage id
daystring, matches a day id
startstring, HH:MM
endstring, HH:MM
highlightnumber, greater than zero means the user selected it

4. Mapping rules

Converting the source payload into the storage schema requires two transformations.

  1. Time. Combine the HH:MM time string with the day YYYY-MM-DD date and convert to epoch milliseconds for start_time and end_time. Storing milliseconds on both sides is what keeps D1 and Dexie in parity.
  2. Keys. Generate a GUID for any entity that arrives without a stable identifier.