# Redis cache

## Cache pro načítání GTFS jízdních řádů

### files:gtfsStatic:*

- Klíč `files:gtfsStatic:*`, kde `*` je název souboru, popř. cesta k souboru v případě ZIP archivu
- Hodnota CSV/JSON jako string
- TTL žádné
- Retence dat až do úspěšného načtení GTFS jíždních řádů
- Cache obsahuje všechny načtené soubory z ROPID FTP (viz [implementační dokumentace](../../implementation_documentation.md#files-gtfs-static#ropid-ftp-obecně))
- Menší soubory jsou ukládány pomocí `SET`, větší jsou streamovány pomocí knihovny [redis-wstream](https://github.com/jeffbski/redis-wstream) (`SET` + `APPEND`)
- Menší soubory jsou následně načteny pomocí `GET`, větší jsou streamovány z Redisu do PSQL pomocí knihovny [redis-rstream](https://github.com/jeffbski/redis-rstream) (`GETRANGE`)

### Flowchart

```mermaid
flowchart TD;
    redis_1[("Redis files:gtfsStatic:*")];
    redis_2[("Redis files:gtfsStatic:*")];
    redis_3[("Redis files:gtfsStatic:*")];

    psql[("PSQL ropidgtfs_*_tmp")];
    amqp_transformAndSaveData[AMQP transformAndSaveData];

    initial["Zpráva z cronu, stažení z ROPID FTP"];
    initial--SET malý soubor-->redis_1;
    initial--Wstream velký soubor-->redis_1;
    initial-->amqp_transformAndSaveData;

    amqp_transformAndSaveData--GET malý soubor<-->redis_2;
    amqp_transformAndSaveData--Rstream velký soubor<-->redis_2;
    amqp_transformAndSaveData--INSERT malý soubor-->psql;
    amqp_transformAndSaveData--COPY stream velký soubor-->psql;

    continue[Pokračování v načítání JŘ];
    amqp_transformAndSaveData-->continue;

    cache_manager["DataCacheManager"];
    continue-->cache_manager;
    cache_manager--Truncate-->redis_3;

```

## Cache pro GTFS-RT feedy

### files:gtfsRt

- Klíč `files:gtfsRt`, pomocný `files:gtfsRt_*_timestamp`, kde `*` je název feedu bez přípony
- Hodnota je hash mapa, kde klíč je název feedu s příponou `.pb`
  - `trip_updates.pb`
  - `vehicle_positions.pb`
  - `pid_feed.pb`
  - `alerts.pb`
- Hodnota v hash mapě je binární blob reprezentující specifický GTFS-RT feed
- Hodnota v pomocném klíči je unix timestamp poslední aktualizace daného feed v milisekundách
- TTL žádné
- Retence dat aktivní, vždy uchováváme nejnovější feedy
- Všechny feedy jsou ukládány pomocí `HSET`, pomocné timestampy pomocí `SET`
- Feedy jsou nabízený přes API `/v2/vehiclepositions/gtfsrt/{feed}`, kde `{feed}` je název feedu s příponou `.pb`
  -  Žádaný feed se načte pomocí `HGET` a vrátí se jako binární blob. Pokud feed neexistuje, vrátí se 404
  -  Pomocný timestamp se načte pomocí `GET` a vrátí se jako hodnota hlavičky `Last-Modified` převedená na ISO string. Pokud timestamp neexistuje, hodnota hlavičky bude unix timestamp 0 jako ISO string. Využívá se především pro monitoring a alerting
- Feedy se generují nejméně 3x za minutu (rabín 2x) pomocí queue-message
  -  Používají redis-mutex na zamykání, aby neběželo více shodných tasků v jeden okamžik
  -  Pokud generování stíhá běžet rychleji, task se snaží do ±7 vteřin svého běhu spustit nové generování souborů (opět přes queue-message).

### Flowchart

```mermaid
flowchart TD;
    redis_f_1[("Redis files:gtfsRt")];
    redis_t_1[("Redis files:gtfsRt_*_timestamp")];
    psql[("PSQL vehiclepositions_*, ropidvymi_*")];

    connector[("OG Redis connector")];
    repository["IE GtfsRtRedisRepository"];

    get_gtfsrt["GET /v2/vehiclepositions/gtfsrt/$feed"];

    initial["Zpráva z cronu, vygenerování feedů"];
    initial--Select<-->psql;
    initial-->repository;
    repository--HSET *.pb-->redis_f_1;
    repository--SET *_timestamp-->redis_t_1;

    get_gtfsrt-->connector;
    connector--HGET *.pb<-->redis_f_1;
    connector--GET *_timestamp<-->redis_t_1;
```

## Cache pro zpracování RT dat

### gtfsRunSchedule:*

- key = `*${route_id}_${run_number}`
- retence dat aktivne v tride `DataCacheManager` 
- TTL podle casu vygenerovane ho z methody `getNextExpireTimestamp`
- hodnota je `string`
- cache obsaguje data z tabulky ropidgtfs_precomputed_trip_schedule

```mermaid
flowchart TD;
    psql[("PSQL ropidgtfs_precomputed_trip_schedule")];
    redis_trip_schedule[("Redis gtfsRunSchedule:*")];
    connector[("OG Redis connector")];
    repository["IE RunTripsRedisRepository"];

    propagateDelay["propagateDelay"];


    repository<--GET*.runTuple--->propagateDelay;
    runManager["AbstractGTFSTripRunManager"]
    get_gtfsrt["GET /v2/departureboards"];

    initial["Zpráva z vozidla"];
    initial--process-->runManager;
    repository--GET*.runTuple<-->redis_trip_schedule;
    runManager--GET*.runTuple<-->repository;
    runManager--SET *.runTuple-->repository;
    repository--SET *.runTuple-->redis_trip_schedule;
    runManager--SELECT if not found in redis<-->psql;

    get_gtfsrt-->connector;
    connector--**MGET** *runTuple<-->redis_trip_schedule;
```

### gtfsBlockSchedule:*

- key = `*gtfs_block_id`
- retence dat aktivne v tride `DataCacheManager` 
- TTL podle casu vygenerovane ho z methody `getNextExpireTimestamp`
- hodnota je `string`
- cache obsahuje data trip_stops podle gtfs block_id

```mermaid
flowchart TD;
    psql[("PSQL ropidgtfs_stop_times")];
    cache[("Redis gtfsBlockSchedule:*")];
    repository["IE BlockStopsRedisRepository"];
    updateDelay["updateDelay"];
    updateGTFSTripId["updateGTFSTripId"];
    tripStopsManager["tripStopsManager"];
    initial["Zpráva z vozidla"];

    initial--process-->updateGTFSTripId;
    updateGTFSTripId<--create cache for trip stops-->tripStopsManager;
    updateGTFSTripId-->updateDelay;
    updateDelay<--get/update cache for trip stops-->tripStopsManager;
    updateDelay-->propagateDelay;
    propagateDelay-->propagateTrainDelay;
    propagateTrainDelay<--get/update cache for trip stops-->tripStopsManager;



    repository--GET*.blockId<-->redis_trip_schedule;
    tripStopsManager--GET*.blockId<-->repository;
    tripStopsManager--SET *.blockId-->repository;
    repository--SET *.blockId-->cache;
    tripStopsManager--SELECT if not found in redis<-->psql;

```
### gtfsDelayComputation:*

- key = `*${gtfs_trip_id}`
- TTL nastavovano na valid_to posledni pozice kdyz je znama kdyz ne tak na 30 min 
- hodnota je `json`
- cache obsahuje informace o tripu
  - stop_times
  - shapes_anchor_points
  - shapes
  
### Flowchart

```mermaid
flowchart TD;

updateDelay["UpdateDelay Task"];

saveRunToDb["save*RunToDb Task"];
updateRunsGTFSTripId["updateRunsGTFSTripId Task"]
processRegionalBusPositions["processRegionalBusPositions Task"]
processRegionalBusRunMessages["processRegionalBusRunMessages Task"]
repository["DelayComputationRedisRepository"];
cache["Redis gtfsDelayComputation:*"];
initial["Zprava z vozidla"]

initial-->saveRunToDb
saveRunToDb--common message-->updateRunsGTFSTripId
updateRunsGTFSTripId-->updateDelay;
updateDelay<--getTripPropertiesBatch-->repository;
updateDelay<--cacheTripDataBatch-->repository;

saveRunToDb--ArrivaCity run message-->processRegionalBusRunMessages
processRegionalBusRunMessages-->processRegionalBusPositions
processRegionalBusPositions<--getTripPropertiesBatch-->repository;
processRegionalBusPositions<--cacheTripDataBatch-->repository;

repository--JSON.GET-->cache
repository--JSON.SET-->cache

```

## Cache pro předzpracování a párování dat z Telmaxu (Arriva City)

### vpRegionalBusCisLookup:*

- key = `*${external_trip_id}`
- TTL podle casu vygenerovane ho z methody `getNextExpireTimestamp`
- hodnota je string (JSON.stringify())
- cache obsahuje cis informace o tripu
  - cis_line_id
  - cis_trip_number
  - registration_number
  
```mermaid
flowchart TD
    cache["vpRegionalBusCisLookup:*"]

    repo["RegionalBusCisCacheRepository"]
    telmax["Telmax (Arriva City)"]
    task["saveArrivaCityRunsToDB task"]

    telmax-->|bus position message|task
    task-->|lookup CIS ID|repo
    repo-->|get/set|cache
    task-->|update cache|repo
    cache-->|return data|repo
```

### vpRegionalBusGtfsLookup:*

- key = `*${cis_line_id}_${cis_trip_number}`
- TTL zadne nema
- retence dat aktivne v tride `DataCacheManager` 
- hodnota je string (JSON.stringify())
- cache obsahuje gtfs informace o tripu
    - gtfs_trip_id
    - route_name
    - run_number
    - agency_name
    - is_wheelchair_accessible

  
```mermaid
flowchart TD
    cache["vpRegionalBusGtfsLookup:*"]

    repo["RegionalBusGtfsCacheRepository"]
    telmax["Telmax (Arriva City)"]
    task["processRegionalBusRunMessages Task"]
    psql["gtfs data in DB"]



    telmax-->|bus position message|task
    task<--No gtfs data for trip in cache-->psql
    task-->|lookup GTFS trip|repo
    repo-->|get/set|cache
    task-->|update cache|repo
    cache-->|return data|repo
```

## Cache pro public API

### vpPublicCache:*

- Hlavní komponentou je sorted set `vpPublicCache:$setId`, kde `$setId` je ID vygenerované datové sady (4 náhodné bajty reprentované hexadecimálně)
- Score je GPS souřadnice (`GEOADD`), member je ID vozidla. Sorted set se používá jako základ pro public VP (`GEOSEARCH`)
- ID nejnovější datové sady se propaguje přes PubSub channel `vpPublicCache`
- Další komponenty:
  - `vpPublicCache:$setId:trip-$gtfsTripId` - list ID vozidel asociovaných s daným GTFS trip id (možnost více vozidel na jednom spoji)
  - `vpPublicCache:$setId:vehicle-$vehicleId` - JSON jako string s informacemi o vozidle a spoji, které vykonává
  - `vpPublicCache:$setId:future-trip-$gtfsTripId` - list ID vozidel asociovaných s daným GTFS trip id (možnost více vozidel na jednom spoji). Vozidlo je před trasou a vykonává předchozí spoj na oběhu (nemá se zobrazit na mapě). Využití v public odjezdech
  - `vpPublicCache:$setId:future-vehicle-$vehicleId-$gtfsTripId` - JSON jako string s informacemi o vozidle. Vozidlo je před trasou a vykonává předchozí spoj na oběhu (nemá se zobrazit na mapě). Využití v public odjezdech a public VP detailu s GTFS lookupem
  - `vpPublicCache:$setId:canceled-trips-$gtfsTripId` - JSON jako string s informacemi o zrušeném spoji. Využití v public odjezdech
- TTL 1 minuta
- Nová datová sada se generuje přibližně jednou za 3 vteřiny a obsahuje všechny RT spoje (aktivní, budoucí a zrušené)
- Populace `future-trip-*`, `future-vehicle-*` a `canceled-trips-*` sub-klíčů je řízena stavem spoje (před trasou / zrušen) a nesouvisí s logikou deduplikace odjezdů v `/v2/public/departureboards` – ta pracuje nad `gtfsPublicDepartureCache` a tuto cache neovlivňuje

### vpPublicStopTimeCache:*

- Klíč `vpPublicStopTimeCache:*`, kde `*` je kombinace ID vozidla a GTFS id spoje (možnost více vozidel na jednom spoji)
- Hodnota je JSON jako string s informacemi o zastávkách a jejich časech
  - `sequence` - pořadí zastávky podle GTFS stop times
  - `arr_delay` - čas příjezdu na zastávku - reálný čas u projetých zastávek, predikce u budoucích
  - `dep_delay` - čas odjezdu ze zastávky - reálný čas u projetých zastávek, predikce u budoucích
  - `cis_stop_platform_code` - nástupiště/kolej podle CIS u vlakových zastávek, jinde null
  - `platform_code` - označení zastávky podle GTFS
  - `stop_id` - GTFS id zastávky
  - `stop_name` - název zastávky
- TTL 1 hodina a 5 minut
- Automatické promazání dat po úspěšném načtení GTFS jíždních řádů
- Cca jednou za 5 vteřin se přenačtou stop times pro všechny aktivní spoje. Generování může běžet pouze jednou, po dokončení tasku se spustí znovu (max čekání 4.8 vteřin)

### gtfsPublicDepartureCache:*

- Klíč `gtfsPublicDepartureCache:*`, kde `*` je GTFS id zastávky
- Hodnota je sorted set, kde score je unix timestamp odjezdu v sekundách a member je JSON string s informacemi o odjezdu:
  - `stop_id` – GTFS id zastávky
  - `departure_datetime` – plánovaný čas odjezdu (ISO 8601)
  - `arrival_datetime` – plánovaný čas příjezdu (ISO 8601), nebo `null`
  - `route_short_name` – název linky
  - `route_type` – typ dopravy (GTFS route_type)
  - `trip_id` – GTFS id spoje
  - `stop_sequence` – pořadí zastávky v spoji
  - `platform_code` – označení nástupiště, nebo `null`
  - `trip_headsign` – cílová zastávka spoje (nebo `stop_headsign`, pokud je definován)
  - `trip_headsign_icons` – ikony přestupů u cílové zastávky jako řetězec dvouznaková kódů (např. `"MbMcSb"`), nebo `null`
  - `connections` – seznam garantovaných přestupů čekajících na tento spoj, nebo `null`; každý záznam obsahuje:
    - `from_trip_id` – GTFS id spoje, na který se čeká
    - `max_wait_sec` – maximální čekací doba v sekundách
    - Přednačítá se z tabulky `ropidgtfs_precomputed_trip_connections` (původně z `transfers.txt` kde `transfer_type = 1`), JOIN na odjezdy přes `to_trip_id` a `to_stop_id`
  - `wheelchair_accessible` – přístupnost vozidla pro vozíčkáře z GTFS `trips.txt` (`0` = bez informace, `1` = přístupné, `2` = nepřístupné), nebo `null`
  - `direction_id` – směr jízdy z GTFS `trips.txt` (`0` nebo `1`), nebo `null`; využívá se při seskupování odjezdů podle směru a filtrování opačného směru v přestupních tabulích
  - `flags` – nepovinný objekt s příznaky spoje, vyplněn pouze pokud má alespoň jeden z příznaků hodnotu `1` (zdroj `ropidgtfs_departures.is_night/is_regional/is_substitute_transport`); v objektu se objeví pouze pravdivé klíče:
    - `night` (`1`) – noční spoj
    - `reg` (`1`) – regionální spoj
    - `subst` (`1`) – náhradní doprava
- Existuje záznam pro každou zastávku v GTFS. Pokud v ní ale není žádný odjezd, je vyplněný pouze jeden prázdný member - detekce validní zastávky bez odjezdu
- TTL 7 hodin
- Retence dat aktivní, jednou za hodinu se smažou všechny odjezdy starší než 3 hodiny
- Jednou za hodinu se vygenerují nové odjezdy pro všechny zastávky v intervalu +5 hodin až +7 hodin, po přenačtení JŘ se přegenerují všechny odjezdy v interval -3 hodiny až +7 hodin
- Ukládá se pomocí `ZADD`, čte se pomocí `ZRANGEBYSCORE` (-inf, now + `minutesAfter`)
- Využití v public odjezdech
- Využití v endpointu přestupních tabulí (`/v4/pid/transferboards`)

### Flowchart (refresh)

```mermaid
flowchart LR;
    finish_up_gtfs["Dokončení zpracování GTFS JŘ"];

    psql_rt[("PSQL vehiclepositions_*")];
    psql_stop_times[("PSQL v_public_vehiclepositions_combined_stop_times")];
    psql_departures[("PSQL ropidgtfs_departures")];

    redis_pubsub_channel["Redis channel vpPublicCache"];
    redis_public_cache[("Redis vpPublicCache:*")];
    redis_stop_times[("Redis vpPublicStopTimeCache:*")];
    redis_departures[("Redis gtfsPublicDepartureCache:*")];

    amqp_refreshPublicTripCache[AMQP refreshPublicTripCache];
    %% amqp_refreshPublicTripCache--"Čekání max 2.4 vteřin od spuštění tasku"-->amqp_refreshPublicTripCache;
    amqp_refreshPublicStopTimeCache[AMQP refreshPublicStopTimeCache];
    %% amqp_refreshPublicStopTimeCache--"Čekání max 4.8 vteřin od spuštění tasku"-->amqp_refreshPublicStopTimeCache;
    amqp_refreshPublicGtfsDepartureCache[AMQP refreshPublicGtfsDepartureCache];

    cron_public_cache["Cron public VP 1x za minutu (backup)"];
    cron_public_cache-->amqp_refreshPublicTripCache;

    cron_stop_times["Cron zastávky spojů 1x za minutu (backup)"];
    cron_stop_times-->amqp_refreshPublicStopTimeCache;

    cron_departures["Cron odjezdy 1x za hodinu"];
    cron_departures--"{fromHours: 5, toHours: 7}"-->amqp_refreshPublicGtfsDepartureCache;

    finish_up_gtfs--"Truncate"-->redis_stop_times;
    finish_up_gtfs--"{fromHours: -3, toHours: 7}"-->amqp_refreshPublicGtfsDepartureCache;

    amqp_refreshPublicTripCache--"Aktivní spoje a jejich polohy"<-->psql_rt;
    amqp_refreshPublicTripCache--"GEOADD + RPUSH + SET"-->redis_public_cache;
    amqp_refreshPublicTripCache--"ID/název nové datové sady"-->redis_pubsub_channel;

    amqp_refreshPublicStopTimeCache--"Zastávky u všech aktivních spojů"<-->psql_stop_times;
    amqp_refreshPublicStopTimeCache--"MSET"-->redis_stop_times;

    amqp_refreshPublicGtfsDepartureCache--"Odjezdy v intervalu"<-->psql_departures;
    amqp_refreshPublicGtfsDepartureCache--"ZREMRANGEBYSCORE + ZADD"-->redis_departures;
```

### Flowchart (public API VP)
```mermaid
flowchart TD;
    redis_pubsub_channel["Redis channel vpPublicCache"];
    redis_subscriber["VPSubscriber"];
    redis_public_cache[("Redis vpPublicCache:* (RT info)")];
    redis_public_cache_misc[("Redis vpPublicCache:* (aktivní, budoucí a zrušené spoje)")];
    redis_repository["OG PublicVehiclePositionsRepository"];

    redis_repository<-->redis_public_cache_misc;
    redis_repository<-->redis_public_cache;

    redis_subscriber--"Přihlášení k odběru"-->redis_pubsub_channel;
    redis_pubsub_channel--"ID/název nové datové sady"-->redis_subscriber--"setCurrentSetName"-->redis_repository;

    get_vp["GET /v2/public/vehiclepositions"];
    get_vp--"GEOSEARCH + vehicle_id lookup pouze aktivních spojů"-->redis_repository;
```

### Flowchart (public API VP detail)
```mermaid
flowchart TD;
    redis_pubsub_channel["Redis channel vpPublicCache"];
    redis_subscriber["VPSubscriber"];
    redis_public_cache[("Redis vpPublicCache:* (RT info)")];
    redis_public_cache_misc[("Redis vpPublicCache:* (aktivní, budoucí a zrušené spoje)")];
    redis_stop_times[("Redis vpPublicStopTimeCache:*")];
    redis_delay_computation[("Redis gtfsDelayComputation:* (pouze shapes)")];
    redis_repository["OG PublicVehiclePositionsRepository"];
    redis_repository<-->redis_public_cache_misc;
    redis_repository<-->redis_public_cache;

    redis_subscriber--"Přihlášení k odběru"-->redis_pubsub_channel;
    redis_pubsub_channel--"ID/název nové datové sady"-->redis_subscriber--"setCurrentSetName"-->redis_repository;

    get_vp_detail["GET /v2/public/vehiclepositions/$vehicle_id"];

    get_vp_detail--"Lookup aktivního GTFS spoje"-->redis_repository;
    get_vp_detail--"scope[]=stop_times"<-->redis_stop_times;
    get_vp_detail--"scope[]=shapes"<-->redis_delay_computation;
```

### Flowchart (public API VP detail with GTFS)
```mermaid
flowchart TD;
    redis_pubsub_channel["Redis channel vpPublicCache"];
    redis_subscriber["VPSubscriber"];
    redis_public_cache[("Redis vpPublicCache:* (RT info)")];
    redis_public_cache_misc[("Redis vpPublicCache:* (aktivní, budoucí a zrušené spoje)")];
    redis_stop_times[("Redis vpPublicStopTimeCache:*")];
    redis_delay_computation[("Redis gtfsDelayComputation:* (pouze shapes)")];
    redis_repository["OG PublicVehiclePositionsRepository"];

    redis_repository<-->redis_public_cache_misc;
    redis_repository<-->redis_public_cache;

    redis_subscriber--"Přihlášení k odběru"-->redis_pubsub_channel;
    redis_pubsub_channel--"ID/název nové datové sady"-->redis_subscriber--"setCurrentSetName"-->redis_repository;

    get_vp_detail_with_gtfs["GET /v2/public/vehiclepositions/$vehicle_id;gtfsTripId=$gtfsTripId"];

    get_vp_detail_with_gtfs--"Lookup aktivního nebo budoucího GTFS spoje (exact match)"-->redis_repository;
    get_vp_detail_with_gtfs--"scope[]=stop_times"<-->redis_stop_times;
    get_vp_detail_with_gtfs--"scope[]=shapes"<-->redis_delay_computation;
```

### Flowchart (public API GTFS static lookup)

:warning: GTFS static lookup neřeší RT informace a jako jediný EP je napojený přímo na PSQL. Kvůli předpokládáně nižšímu využití (detail odjezdů v budoucnosti) to tolik nevadí

```mermaid
flowchart TD;
    psql_trip[("PSQL ropidgtfs_trips, ropidgtfs_routes, ropidgtfs_precomputed_trip_schedule")];
    psql_stop_times[("PSQL ropidgtfs_stop_times, ropidgtfs_stops")];
    psql_shapes[("PSQL ropidgtfs_shapes")];

    get_gtfs_detail["GET /v2/public/gtfs/trips/$trip_id"];
    get_gtfs_detail--"Lookup GTFS trip id"<-->psql_trip;
    get_gtfs_detail--"scope[]=stop_times"<-->psql_stop_times;
    get_gtfs_detail--"scope[]=shapes"<-->psql_shapes;
```

### Flowchart (public API departures)
```mermaid
flowchart TD;
    redis_pubsub_channel["Redis channel vpPublicCache"];
    redis_subscriber["VPSubscriber"];
    redis_public_cache[("Redis vpPublicCache:* (RT info)")];
    redis_public_cache_misc[("Redis vpPublicCache:* (aktivní, budoucí a zrušené spoje)")];
    redis_departures[("Redis gtfsPublicDepartureCache:*")];
    redis_repository["OG PublicVehiclePositionsRepository"];

    redis_subscriber--"Přihlášení k odběru"-->redis_pubsub_channel;
    redis_pubsub_channel--"ID/název nové datové sady"-->redis_subscriber--"setCurrentSetName"-->redis_repository;

    get_departures["GET /v2/public/departureboards"];

    get_departures--"Odjezdy pro zastávky"-->redis_departures;
    get_departures--"Aktivní vozidlo podle GTFS trip id"-->redis_repository<-->redis_public_cache_misc;
    rt_info["Obohacení o RT informace"];
    get_departures--"Alespoň jedno vozidlo připadá nějakému spoji (aktivní, budoucí i zrušené) na odjezdu"-->rt_info;
    rt_info--"RT informace o spoji"-->redis_repository<-->redis_public_cache;
```

## Cache pro API přestupních tabulí (transfer boards)

*(přestupní tabule využívají také cache pro Public API, která je již popsána výše)*

### gtfsStopsCache:*

- Jednotlivé komponenty cache jsou členěny do sad s `$setId`
    - `$setId` je id vygenerované datové sady (4 náhodné bajty reprezentované hexadecimálně)
    - Nová sada je vytvořena při každém přenačtení cache
    - Id `$setId` nejnovější datové sady se propaguje přes PubSub channel `gtfsStopsCache` a je také uloženo v cache jako hodnota položky `gtfsStopsCache:activeSetName`
- Komponenty:
  - `gtfsStopsCache:$setId:aswNodeToGtfsStops` - Hash mapa, mapující daný ASW node na seznam pod něj spadajících zastávek (GTFS ids zastávek, odděleny čárkami)
  - `gtfsStopsCache:$setId:cisToAswNode` - Hash mapa, mapující dané CIS id zastávky na příslušný ASW node
- TTL žádné (mimo přenačtení cache, kdy je namísto okamžitého promazání původnímu setu nastaveno TTL 1 minuta)
- Cache je celá přenačtena vždy po přenačtení jízdních řádů (tedy 1-2x denně při stavu k červenci 2025)

### jisCache:*

- Jednotlivé komponenty cache jsou členěny do sad s `$setId`
    - `$setId` je id vygenerované datové sady (4 náhodné bajty reprezentované hexadecimálně)
    - Nová sada je vytvořena při každém přenačtení cache
    - Id `$setId` nejnovější datové sady se propaguje přes PubSub channel `jisCache` a je také uloženo v cache jako hodnota položky `jisCache:activeSetName`
- Komponenty:
  - `jisCache:$setId:infotexts` - Hash mapa, mapující dané id infotextu na informace o onom infotextu (JSON jako string)
  - `jisCache:$setId:stops-infotexts` - Hash mapa, mapující dané GTFS id zástavky na seznam infotextů s ní asociovaných (ids infotextů, odděleny čárkami)
- TTL žádné (mimo přenačtení cache, kdy je namísto okamžitého promazání původnímu setu nastaveno TTL 1 minuta)
- Cache je celá přenačtena při každé aktualizaci infotextů (tento proces je více popsán v [Dokumentaci propisu VYMI (JIS) dat do datové platformy](../../jis/index.md))

### Flowchart (refresh gtfsStopsCache:*)

```mermaid
flowchart LR
    finish_up_gtfs("`Dokončení zpracování GTFS JŘ`");

    psql_ropidgtfs_cis_stops[("`PSQL ropidgtfs_cis_stops`")];
    psql_ropidgtfs_stops[("`PSQL ropidgtfs_stops`")];

    redis_gtfsStopsCache[("`Redis gtfsStopsCache:\*`")];

    amqp_refreshGtfsStopsCache["`AMQP refreshGtfsStopsCache`"];

    task_RefreshGtfsStopsCacheTask["`RefreshGtfsStopsCacheTask`"];

    finish_up_gtfs --> amqp_refreshGtfsStopsCache;
    amqp_refreshGtfsStopsCache --> task_RefreshGtfsStopsCacheTask;
    task_RefreshGtfsStopsCacheTask <-- "`ASW ids a CIS ids zastávek`" --> psql_ropidgtfs_cis_stops;
    task_RefreshGtfsStopsCacheTask <-- "`ASW nodes a GTFS ids zastávek`" --> psql_ropidgtfs_stops;
    task_RefreshGtfsStopsCacheTask -- "`HSET gtfsStopsCache:$setId:aswNodeToGtfsStops a gtfsStopsCache:$setId:cisToAswNode, EXPIRE staré sady`" --> redis_gtfsStopsCache;
```

### Flowchart (refresh jisCache:*)

```mermaid
flowchart LR
    refresh_infotexts("`Aktualizace infotextů`");

    psql_jis_infotexts[("`PSQL jis_infotexts`")];
    psql_jis_infotexts_ropidgtfs_stops[("`PSQL jis_infotexts_ropidgtfs_stops`")];

    redis_jisCache[("`Redis jisCache:\*`")];

    amqp_refreshJISInfotextsCache["`AMQP refreshJISInfotextsCache`"];

    task_RefreshJISInfotextsCacheTask["`RefreshJISInfotextsCacheTask`"];

    refresh_infotexts --> amqp_refreshJISInfotextsCache;
    amqp_refreshJISInfotextsCache --> task_RefreshJISInfotextsCacheTask;
    task_RefreshJISInfotextsCacheTask <-- "`Data infotextů`" --> psql_jis_infotexts;
    task_RefreshJISInfotextsCacheTask <-- "`GTFS ids zastávek a ids infotextů`" --> psql_jis_infotexts_ropidgtfs_stops;
    task_RefreshJISInfotextsCacheTask -- "`HSET jisCache:$setId:infotexts a jisCache:$setId:stops-infotexts, EXPIRE staré sady`" --> redis_jisCache;
```

### gtfsTripStopsCache:*

- Klíč `gtfsTripStopsCache:*`, kde `*` je GTFS `trip_id`
- Hodnota je JSON string s polem zastávek seřazených podle `stop_sequence`:
  - `stops` – pole dvojic `[stop_id, stop_name]`
- TTL 6 hodin (musí přesáhnout maximální dobu jízdy spoje ~3,5h + interval tasku 1h)
- Cache se přegeneruje jednou za hodinu (cron přes `EnsureCacheTask`) a po přenačtení GTFS jízdních řádů (přes `CheckSavedRowsAndReplaceTablesTask`)
- SQL dotaz vybírá všechny spoje, jejichž první zastávka (`stop_sequence = 1`) má `departure_datetime` v okně `now() − 1h` až `now() + 2h`, a pro každý takový spoj agreguje všechny zastávky
- Zapisuje `GtfsTripStopsCacheRepository` (IE) v dávkách po 200 klíčích přes Redis pipeline; před zápisem se validují data přes AJV schéma, nevalidní záznamy se přeskočí a zalogují
- Čte `GtfsTripStopsRepository` (OG) přes `MGET` — využívá `TransferFacade.findRelevantTripIdsFromLines()` pro filtrování přestupních linek

### Flowchart (v4 transferboards API)

![flowchart.svg](../../assets/V4Transferboards.svg)

link na .drawio [soubor](../../assets/V4Transferboards.drawio)

## Cache pro konfiguraci

### config:notPublicVehicles

- Klíč `config:notPublicVehicles` (jednoduchý klíč, žádný wildcard)
- Hodnota je JSON string struktury `INotPublicVehicles`:
  - `tram.registrationNumbers` – seznam evidenčních čísel tramvají bez platného GTFS spoje
  - `road.registrationNumbers` – seznam evidenčních čísel autobusů a trolejbusů bez platného GTFS spoje (`road` pokrývá obě kategorie, protože sdílejí číselnou řadu)
  - `routeIds` – whitelist čísel linek (např. `"861"` pro náhradní autobus X-C při výluce metra C) zobrazovaných i bez platného JŘ spoje
- TTL žádné – hodnota se přepíše při každém spuštění `SaveStaticDataTask` (po načtení nových JŘ)
- Zapisuje `NotPublicVehiclesRedisRepository` (IE), čte `TripsRepository` / `PositionsMapper` (IE) a `UpdateRunsGtfsTripIdTask` (IE) pro povolení průchodu vozidel bez GTFS spoje

## Manuální refresh cache (EnsureCacheTask)

`EnsureCacheTask` slouží jako manuální spouštěč obnovy všech 5 caches, jejichž data nejsou real-time obnovována. Po přijetí prázdné zprávy na frontě `vehicle-positions.ropidgtfs.ensureCache` odešle paralelně prázdné zprávy do všech 5 cílových front. Dvě z nich míří do různých workerů (cross-worker routing přes RabbitMQ exchange), tři míří do stejného workeru (TimetableWorker).

### Flowchart

```mermaid
flowchart LR
    trigger["`Zpráva do fronty ensureCache`"]
    task["`EnsureCacheTask (TimetableWorker)`"]

    exchange_vp["`Exchange ${RABBIT_EXCHANGE_NAME}.vehiclepositions (VPWorker)`"]
    exchange_self["`Exchange this.queuePrefix (TimetableWorker)`"]
    exchange_jis["`Exchange ${RABBIT_EXCHANGE_NAME}.jis (JISWorker)`"]

    q1["`AMQP refreshGTFSTripData`"]
    q2["`AMQP refreshPublicGtfsDepartureCache`"]
    q3["`AMQP refreshGtfsStopsCache`"]
    q4["`AMQP refreshGtfsTripStopsCache`"]
    q5["`AMQP refreshJISInfotextsCache`"]

    trigger --> task
    task -- cross-worker --> exchange_vp --> q1
    task -- same-worker --> exchange_self --> q2
    exchange_self --> q3
    exchange_self --> q4
    task -- cross-worker --> exchange_jis --> q5
```

