Jak jsme postavili Carivio: dispečink pro 50 vozů od nuly
Třiplatformová taxi dispatching platforma za 4 měsíce. Backend 128 000 řádků, real-time aukce jízd přes SignalR, PostGIS geospatial, produkční Claude AI. Co bylo těžké a co se v demu nevidí.
Carivio je taxi dispatching platforma, kterou jsme postavili pro Transport Prague. Dnes na ní jezdí kolem 50 vozů. Jde o náš vlastní produkt — backend, webová aplikace pro dispečery a mobilní aplikace pro řidiče, postavené od nuly.
Tenhle článek je o architektonických rozhodnutích, která jsme dělali, a o věcech, které jsou v provozu těžké, ale v demu je nikdo nevidí.
Co to je a co musí umět
Taxi dispatching vypadá zvenku jednoduše: zákazník objedná jízdu, řidič ji přijme, odveze zákazníka. V produkci je to stavový automat s desítkami přechodů, šesti rolemi uživatelů a požadavkem, že každá akce proběhne v reálném čase.
Platforma má 6 rolí: zákazník, řidič, dispečer, porter (recepční v hotelu), admin a host. Každá role vidí jiný pohled, má jiná oprávnění a jiné notifikace. Backend je jeden, ale feature modulů je 48. EF Core migrací přibylo 107 — to je dobrý indikátor toho, jak moc se doménový model vyvíjí v průběhu projektu.
Aukční model jízd
Největší architektonické rozhodnutí byl způsob, jakým se jízda přidělí řidiči.
Zvolili jsme aukční model: když přijde objednávka, systém ji rozešle skupině řidičů v dosahu. Ti vidí nabídku na mobilu a mohou ji přijmout. Kdo přijme první (nebo za nejvýhodnějších podmínek), jízdu dostane. Ostatním nabídka vyprší.
Alternativa — přímé přidělení nejbližšímu řidiči — je technicky jednodušší. Jenže vyžaduje buď pevné pravidlo, které nemusí sedět do každé situace, nebo dispečera, který rozhoduje ručně. Aukce dává řidičům autonomii a funguje dobře v situacích, kdy je někdo formálně nejblíž, ale z různých důvodů nemá zájem.
Technicky to znamená: stavový automat na straně backendu, SignalR broadcast nabídky všem relevantním řidičům, push notifikace pro případ, že aplikace není v popředí, a Quartz job, který hlídá timeout a nabídku po uplynutí lhůty zavře. Stav každé jízdy je persistovaný — pokud se server restartuje uprostřed aukce, systém to přežije.
Real-time: čtyři SignalR endpointy
Real-time komunikace běží přes SignalR. Máme čtyři hub endpointy — jeden pro zákazníky, jeden pro řidiče, jeden pro dispečery a jeden pro portery.
Každý hub má jiné skupiny, jiné události a jiné nároky na latenci. Dispečer vidí pozice všech vozů na mapě v reálném čase. Zákazník dostává notifikace o stavu jízdy. Řidič dostává nabídky a aktualizace.
Tohle nestačí postavit a zapomenout. Při každé změně stavového modelu musíte projít všechny huby a ověřit, že každý handler dostane správná data ve správnou chvíli. Reference discovery přes codebase je při vývoji nutnost, ne luxus.
Geospatial: PostGIS
GPS pozice řidičů ukládáme do PostgreSQL s rozšířením PostGIS. PostGIS dává spatial indexy a geografické dotazy v SQL — „najdi všechny řidiče do 5 km od bodu X" je jeden dotaz, ne aplikační logika.
Pro geokódování (adresa → souřadnice) a hledání míst používáme external API s vrstvou HybridCache + Redis. Geokódovací výsledky se cachují — stejné adresy se ptáme jednou, ne stokrát za den.
Backend taky nese vlastní finanční a fakturační engine. Taxi provoz má specifické požadavky na vyúčtování, které krabicová řešení nepokrývají.
Co je v demu neviditelné: background location
Nejkomplikovanější část celé platformy není v backendové logice. Je v mobilní aplikaci na straně Android.
Background location — posílání GPS pozice serveru, i když je aplikace schovaná na pozadí — je na papíře jednoduchá funkce. V produkci je to boj s výrobci telefonů.
Samsung, Xiaomi/MIUI, OnePlus, Huawei — každý má vlastní agresivní battery optimalizaci, která aplikace na pozadí zabíjí. Systém OEM volá „doporučené chování" to, co je de facto zabitý background process. Výsledek: řidič si myslí, že je online, ale GPS pozice přestala chodit před 10 minutami.
Ošetřili jsme to na několika úrovních:
- Detekce OEM prostředí při startu aplikace a zobrazení průvodce nastavením pro konkrétní výrobce
- Foreground service s persistent notifikací — na Androidu jediný spolehlivý způsob, jak přežít battery kill
- Detekce killnutí aplikace a recovery při dalším spuštění (nedokončené stavy jízd, obnovení SignalR spojení)
- Offline queue: pokud je telefon momentálně bez signálu, GPS body se ukládají lokálně a odešlou až při obnovení spojení
Tohle nezní jako velký projekt, ale ladění chování na reálných zařízeních různých výrobců trvalo podstatnou část mobilního vývoje.
Produkční AI: volné zadání → strukturovaná objednávka
Jedna z funkcí, která v systému běží, je zpracování objednávek volným textem přes Claude.
Dispečer nebo admin napíše do pole cokoliv — „zítra ráno v 8 z Václaváku na letiště, 3 osoby, sedan" — a model vrátí strukturovaný objekt: typ vozidla, počet cestujících, místo vyzvednutí a cílová destinace, čas, poznámky.
Implementace má na backendu cost tracking (každý request se loguje s počtem tokenů) a server-side guardraily: model dostane schema, které musí vyplnit, a pokud vrátí neúplný nebo nekonzistentní výstup, systém to odmítne a požádá o opravu. Halucinace v adresách jsou reálný problém — dispečer musí mít možnost výstup zkontrolovat a opravit dřív, než objednávka odejde k řidiči.
Čísla
- Backend: ~128 000 řádků kódu
- Feature modulů: 48
- EF Core migrace: 107
- SignalR hub endpointy: 4
- Quartz background joby: 12 (kontrola stavů jízd, timeouty aukce, připomínky, cleanup offline řidičů)
- Aktivní vozidla v produkci: ~50
- Délka vývoje MVP: 4 měsíce
Co si z toho odnést
MVP za 4 měsíce bylo možné, protože scope byl jasný od začátku a architektonická rozhodnutí padla v prvním týdnu, ne za pochodu. Aukce vs. přímé přidělení, SignalR vs. polling, PostGIS vs. aplikační geospatial — tohle jsou rozhodnutí, která se špatně mění pozdě.
Nejnákladnější část vývoje nebyla komplexní business logika. Bylo to ladění background location na reálných telefonech různých výrobců a ošetření stavů, které v demu nikdy nenastanou, ale v provozu nastanou každý den.
Ne každý projekt jde tímhle tempem. Záleží na tom, kolik věcí se mění za pochodu a jak moc je doménu potřeba prozkoumávat. Carivio měl výhodu v tom, že zadavatel dobře věděl, co chce.
Pokud stavíte vlastní dispatching nebo uvažujete o digitalizaci flotily, napište nám — rádi řekneme, co bychom dělali jinak a co dává smysl pro váš případ.
FAQ
Jak dlouho trvalo postavit Carivio od nuly?
MVP za 4 měsíce. Podmínka byl jasný scope a architektonická rozhodnutí udělaná na začátku, ne za pochodu. Ne každý projekt jde takhle rychle — záleží na složitosti domény a na tom, kolik věcí se během vývoje mění.
Proč jste zvolili aukční model jízd místo přímého přidělení?
Přímé přidělení vyžaduje buď pevné pravidlo (nejbližší řidič), nebo dispečera, který rozhoduje. Aukce dává řidičům autonomii a přirozeně řeší situace, kdy je někdo blíž, ale nemá zájem. Technicky to znamená stavový automat a SignalR broadcast — složitější než jednoduchý assign, ale provozně flexibilnější.
Zvládne platforma větší flotilu než 50 vozů?
Architektura na to je připravená. PostGIS spatial indexy, HybridCache s Redisem a bezestavový backend umožňují horizontální škálování. Skutečná kapacita závisí na provozu a infrastruktuře — to by chtělo zátěžový test s konkrétními čísly.