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.
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.
| Osoite / Polku | Käyttötarkoitus & Ominaisuudet | Datalä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.