Tilannekuva 30.8.2026 · tuotantoarkkitehtuuri

Sääksi 10+1 Laps — arkkitehtuuri

Kaksi kaaviota: mitä tapahtuu ajonaikana, ja miten koodi ja data päätyvät ajoon. Tämä dokumentti kuvaa saaksi.bike-palvelun nykyisen tuotantotilan: Cloudflare Pages + Functions, yksi keskitetty Worker (live.saaksi.bike), kolmitasoinen tilanhallinta (KV, R2 ja SQLite Durable Object), GPU-kiihdytetyt MapLibre GL -vektorikartat, monialustainen Sääksi Bike -mobiilisovellus (Flutter/Dart iOS:lle ja Androidille), kisastudio, näyteikkunakioskit ja Python-datanormitus. Cloudflaren ydinkomponentit toimivat ilmaistasolla.

Ajonaikainen rakenne

Järjestelmä jakautuu selkeisiin vastuisiin: Cloudflare Pages (+ Functions _middleware.js) tarjoaa staattiset sisällöt ja kanoniset uudelleenohjaukset, kun taas live.saaksi.bike -Worker käsittelee reaaliaikaisen tilan. Tilanhallinta on 3-tasoinen: nopea jaettu tila KV:ssa, pysyvä arkisto R2:ssa, ja samanaikaisten mobiilikuskien nopea tilasarjallistus omassa SQLite-pohjaisessa Durable Objectissa (MobileLiveHub).

Kaaviota voi vierittää vaakasuunnassa. Sama sisältö on selitetty tekstinä kaavion alla.

SELAIMET JA MOBIILILAITTEET CLOUDFLARE · SAAKSI.BIKE KARTAT & ULKOISET PALVELUT Kävijän selain Kuskin selain & GPX Sääksi Bike (Flutter) Kisastudio & Kioski Järjestäjän /admin Pages + Functions Worker (live.saaksi.bike) KV · TILA R2 · ARKISTO SQLite DO Thunderforest Outdoors MapLibre GL Vector Strava Beacon Etusivu, /live, /replay, /aita MapLibre GL GPU-vektorikartta Web Push -ilmoitustilaukset Kuvat ja oma tallennus GPX parsitaan selaimessa Lap-winding kierroslaskenta iOS (App Store) + Android (Play-testi) Yhteinen Flutter-koodikanta Traccar-taustapaikannus /kisastudio jaetun ruudun näkymä /kiosk 16:9 näyteikkunanäyttö Puskurit & suorat live-syötteet Avainlinkki / OIDC → 7 vrk keksi Hyväksynnät, hätäkatkaisin saaksi.bike staattinen CDN _middleware.js kanoninen ohjaus Replay data.json & sivusisällöt /live.json · /photos · /recordings /v1/mobile/* (config, sessions, pos) arkistohaut suoraan R2:sta Turnstile-suojatut avoimet lomakkeet Cron: arkistointi 15 min välein, tapahtumapäivänä 2 min Konfiguraatio, kuskiluettelo, Beacon-tila GPX, kuvat, äänestykset, customMetadata -indeksit (ei alipyyntörajaa) ja arkistopaketit MobileLiveHub: 30 rinnakkaista GPS-sessiota, out-of-order aikaleimakorjaus Maastolaatat (tiles.js) Hobby-avain + OSM fallback Sääksi MTB vector styles Korkeuskäyrät & polut Varareitti live-paikannukselle 10s reunacache Workerissa
Kolmitasoinen tilanhallinta ja moderni reunajärjestelmä. Pages (+ Functions) ja keskitetty Worker (live.saaksi.bike) jakavat kuorman. Nopea yhteinen tila elää KV:ssa, pysyvät tiedostot ja kuvat R2:ssa, ja useiden samanaikaisten mobiilikuskien tiheä taustapaikannus sarjallistetaan omassa SQLite-pohjaisessa Durable Objectissa (MobileLiveHub). Raskas geometria parsitaan selaimissa, ja Turnstile estää botit avoimissa lomakkeissa.

Sääksi Bike -mobiilisovellus (Flutter / Dart)

Sääksi Bike (bike.saaksi.app) on Flutter-pohjainen mobiilisovellus iOS:lle ja Androidille. iOS-versio on julkaistu App Storessa (app-id 6806614306) ja Android on avoimessa Google Play -testissä. Taustapaikannus käyttää Traccar SDK -haarukkaa, ja /v1/mobile -rajapinta suojaa sessiot kaksoistokeneilla (owner vs upload secret, tallentaen vain SHA-256 tiivisteet).

MapLibre GL & Sääksi MTB Vector Styles

Reitit ja live-kartat piirretään suoraan GPU:lla MapLibre GL -kirjastolla. Vektorikarttatyylit (map-style/saaksi-mtb.json) korostavat Sääksen maastopyöräpolkuja ja korkeuskäyriä, ja tiles.js tarjoaa automaattisen Thunderforest/OSM -laattareitityksen.

SQLite Durable Object & Lap-Winding

Durable Object (MobileLiveHub) serialisoi jopa 30 samanaikaista sessiota ilman KV-kirjoitusrajojen ylittymistä. Kierroslaskenta perustuu kiertosuuntaiseen kulmaintegraatioon (winding number) ja silmukan sulkeumaan.

Julkaisu, jälkikäsittely ja moniagenttikehitys

Wrangler-julkaisut, Python-jälkikäsittelyputki ja moniagenttisen kehitysparven (Together Swarm) yhteistyömalli Git Worktree -kurilla.

Kaaviota voi vierittää vaakasuunnassa. Sama sisältö on selitetty tekstinä kaavion alla.

KEHITYSKONE & PARVI (SWARM) CLOUDFLARE & GITHUB sportstracker/ (Päärepo & Pages) pipeline/ (Python Data Pipeline) Together Swarm & Git Worktrees GitHub Remote Pages + Functions (saaksi.bike) Worker (live.saaksi.bike) KV R2 SQLite DO site/ — staattiset sivut, MapLibre GL, i18n live-worker/ — Workerin TypeScript/JS kiosk-device/ — Debian 13 Cage/Chromium provisiointi _headers, _redirects, functions/_middleware.js Arkiston GPX + kuvat → replay data.json Porttitimelapse + syntetisoitu ääniraita Automaattiset Open Graph -kuvat jokaiselle polulle Tahko & Sääksi kestävyysfysiikka-analyysit Claude, Codex, Antigravity, Inkling, Pi .worktrees/* eriytetyt aihealuehaarat Append-only koordinaatioloki & katselmoinnit Stop-koukut, Keepalive-valvonta & lukitukset Rinnakkainen kehitysmalli ja vertaiskatselmointi ljack/sportstracker Lähdekoodi & versiohistoria saaksi.bike tuotantoympäristö wrangler pages deploy site --branch main Reunavälimuisti & Web Analytics wrangler deploy live-worker /v1/mobile reitit & 256-bit bearer tokenit Web Push -ilmoituspalvelin & arkistoreitit Cron-arkistointi ja sessioiden sulkeminen git push wrangler pages deploy wrangler deploy tulokset site/-kansioon
Putki ja parvi sulkevat ympyrän. Kehitysmalli perustuu moniagenttiseen parveen ja aihealuekohtaisiin Git Worktree -haaroihin, joissa muutokset koordinoidaan jaetun tapahtumalokin kautta. Python-putki prosessoi raakadatan, generoi timelapset ja leipoo jaettavat kortit. Julkaisu tuotantoon tapahtuu suoraviivaisilla Wrangler-komennoilla suoraan Cloudflaren reunalle.
Osoite / PolkuKäyttötarkoitus & OminaisuudetDatalähde & Moottori
saaksi.bike Tapahtumasivu: live-tilanne, kuvavirta, GPS-latauslomake, mobiilisovelluksen lataus Worker /photos, /recordings, Turnstile-suojaus
/live Reaaliaikainen seuranta: GPU-vektorikartta, kuskien sijainnit, väliajat ja vauhti MapLibre GL (saaksi-mtb.json), live.json (10s reunacache)
/replay Koko kilpailupäivän interaktiivinen toisto: 5 kuskia, 39 kuvaa, aikaleimatut hetket MapLibre GL (saaksi-mtb-dim.json), staattinen data.json
/kisastudio Kisastudio: monikuskiseuranta jaetun ruudun näkymällä katselijoille MapLibre GL, live.json, monikanavainen tila
/kiosk Kiosk-näkymä: 16:9 näyteikkunatila kumppaniliikkeiden Debian 13 -päätteille kiosk-core.js, SVG-projektiot, julkinen JSON
/start Sääksi Bike -sovelluksen asennussivu & käynnistyslinkki Google Play -testiohjelman liittymislinkki
/aita Porttitimelapse: jokaisen kierroksen ohitus, kello ja km ruudussa Staattinen MP4, syntetisoitu ääniraita
/kiekko Kierrosaikojen ja vauhdinjaon visualisointi Staattinen SVG/JS-kierroskiekko
/2023, /2024, /2026, /2027 Kausien arkistot, historiadata ja Tahko 2027 sub-4h -kisasuunnitelma Staattinen historia- ja reittidata
/pakkauslista Katselmoitu 203 km ultra-MTB varustelista Staattinen, interaktiiviset tilat LocalStoragessa
…live.saaksi.bike/v1/mobile/* Sääksi Bike -sovelluksen taustapaikannus- ja synkronointirajapinta Worker Mobile API (/config, /sessions, /positions), SQLite DO, 24h raakadata / 400d yhteenveto
…live.saaksi.bike/admin Ylläpidon hallintapaneeli: kuvien ja jälkien hyväksynnät, hätäkatkaisin Kryptografinen avainlinkki / Google OIDC → 7 vrk suojattu keksi

Jokaisella jaettavalla osoitteella oma kortti

WhatsApp ja sosiaalisen median esikatselut eivät tue ankkurilinkkejä (#kuvat). Siksi jokainen tärkeä hetki ja sivu on oikea polku, jolle Python-putki generoi automaattisesti reaaliaikaisesta datasta piirretyn Open Graph -esikatselukuvan.

Monikielisyys ja Raaka-kieli (i18n)

Sivusto tukee suomea, englantia ja Sääksen omaa 'raaka'-kielivarianttia ilman raskaita riippuvuuksia. Käännökset vaihdetaan lennossa DOM-attribuuttien (data-i18n) avulla.

Moniagenttinen Kehityskuri (Together Swarm)

Koodia kehitetään monen tekoälyagentin (Claude, Codex, Antigravity, Inkling, Pi) yhteistyönä. Kehitysmalli perustuu aihealuekohtaisiin Git Worktree -haaroihin ja jaetun tapahtumalokin katselmointikäytäntöön.

Kohdatut ansat ja arkkitehtuurioppitunnit

Jokainen ratkaisu on syntynyt todellisen tuotantohaasteen pohjalta.

MapLibre GL WebGL-virheenkäsittely

Vanhemmilla mobiililaitteilla tai virransäästötilassa WebGL-konteksti voi kadota. map-gl.js tunnistaa kontekstin puuttumisen heti ja antaa selkeän ilmoituksen yleisen latausvirheen sijasta.

Androidin APK-jakelu suoraan R2:sta (eläköitynyt)

Ennen Play-jakelua Android APK jaettiin R2-ämpäristä Workerin reitittämänä, koska Cloudflare Pagesin 25 MiB tiedostoraja esti jakelun Pagesista. Kanava poistettiin 2026-09-01, kun Google Playn testiohjelma korvasi suoran asennuksen.

~50 alipyyntöä per pyyntö (Subrequest limit)

Satojen kuskien, kuvien tai äänien hakeminen erillisinä R2-kutsuina kaataisi ilmaistason heti. Kaikki tiivistetty metadata tallennetaan R2-objektin customMetadata-kenttiin, jolloin koko tilannekuva saadaan yhdellä ainoalla list()-kutsulla.

R2-metadata HTTP-otsakkeissa

R2-metadata välittyy HTTP-otsakkeissa, mikä rikkoo ääkköset ja erikoismerkit ilman prosenttikoodausta. Aikaleiman korjaus vaatii siirron uuteen avaimeen, koska aikaleima toimii avaimen etuliitteenä.

Cache-ohjaus Pages Functions -reiteillä

Staattinen _headers ei vaikuta Functions-läpimenopyyntöihin. functions/_middleware.js asettaa Cache-Control: public, max-age=0, must-revalidate -otsakkeet suoraan JavaScript-, CSS- ja manifest-vastauksiin.

Pysähtynyt GPS ja 'lähin piste'

Kun referenssikuski pysähtyy, useat pisteet jakavat saman sijainnin. Aikaleimojen rekonstruoinnissa aikaisin samanarvoinen piste voittaa aina: saapuminen on hetki, jolloin paikalle ensin tultiin.