runtime data

This commit is contained in:
lachtan
2026-07-22 12:32:23 +02:00
parent 8e66d6b92a
commit 94dccb3758
11 changed files with 1475 additions and 194 deletions

View File

@@ -0,0 +1,150 @@
# flight-search skill
## Kontext
User pravidelně hledá letenky (např. PRG→PTY na Vánoce 2026). Chce automatizovaný skill, který vyhledává letenky přes KAYAK, ukládá výsledky do SQLite a umožňuje dotazování nad historii. Později se napojí na cron pro pravidelné hledání a alertování.
## Rozhodnutí z plánovací fáze
- **Zdroj dat**: Jen KAYAK (spolehlivě funguje přes Jina extractor)
- **Výstup**: Top 35 nabídek v tabulce (aerolinka, trasa, cena, doba letu, přestup)
- **Layover**: Max 1 přestup, v tabulce uvést čas přestupu
- **DB**: Uložit vše — definice hledání i nalezené výsledky, původní měnu + CZK
- **Cron**: Neimplementovat teď, ale DB schema připravit pro rozšíření
- **Měna**: Vždy hledat s `curr=CZK`, uložit `price_czk`; pokud je viditelná původní měna, uložit i tu
- **Flex data**: ±3 dny, ale kombinace odlet+návrat musí splňovat `min_stay_days`
## Postup
### 1. Vytvořit adresářovou strukturu skillu
```
skills/flight-search/
├── SKILL.md
└── scripts/
└── flight_search.py
```
DB se auto-vytvoří v `db/flight_search.sqlite`.
### 2. DB schema
```sql
CREATE TABLE searches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
origin TEXT NOT NULL, -- IATA kód (PRG)
destination TEXT NOT NULL, -- IATA kód (PTY)
dep_date TEXT NOT NULL, -- ISO date (2026-12-21)
ret_date TEXT NOT NULL, -- ISO date (2027-01-04)
adults INTEGER NOT NULL DEFAULT 1,
max_layovers INTEGER NOT NULL DEFAULT 1,
min_stay_days INTEGER NOT NULL DEFAULT 1,
flex_days INTEGER NOT NULL DEFAULT 0,
currency TEXT NOT NULL DEFAULT 'CZK',
status TEXT NOT NULL DEFAULT 'active', -- active, completed, failed
created_at TEXT NOT NULL,
completed_at TEXT
);
CREATE TABLE results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
search_id INTEGER NOT NULL REFERENCES searches(id),
airline TEXT NOT NULL,
route TEXT NOT NULL, -- "PRG→AMS→PTY / PTY→AMS→PRG"
dep_date TEXT NOT NULL, -- skutečný datum odletu (může se lišit od search.dep_date při flex)
ret_date TEXT NOT NULL,
dep_time TEXT, -- čas odletu
arr_time TEXT, -- čas příletu
layovers INTEGER NOT NULL DEFAULT 0,
layover_info TEXT, -- "AMS 2h 15m"
duration TEXT, -- celková doba letu "12h 30m"
price REAL, -- cena v původní měně
price_currency TEXT NOT NULL DEFAULT 'CZK',
price_czk REAL NOT NULL, -- cena v CZK
booking_url TEXT,
found_at TEXT NOT NULL
);
CREATE INDEX idx_results_search ON results(search_id);
CREATE INDEX idx_results_route ON results(route);
```
### 3. CLI skript `flight_search.py`
Podpříkazy:
| Příkaz | Popis |
|--------|-------|
| `create-search` | Vytvoří záznam hledání, vrátí `search_id` + seznam KAYAK URL k fetchovat |
| `add-result` | Přidá jeden výsledek k hledání |
| `add-results` | Přidá víc výsledků najednou (JSON ze stdin) |
| `results <search_id>` | Vypíše top N výsledků seřazených podle ceny |
| `list-searches` | Seznam všech hledání |
| `delete-search <id>` | Smaže hledání + výsledky |
**`create-search`** generuje KAYAK URL:
- Základní URL: `https://www.kayak.com/flights/{origin}-{destination}/{dep_date}/{ret_date}/{adults}adults?sort=price_a&curr={currency}`
- S flex dny: generuje všechny platné kombinace (dep ± flex, ret ± flex, ret - dep >= min_stay_days)
- Max 15 URL (cap na rozumný počet requestů)
- Strategie: středový datum pár první, pak expanduje
- Výstup: JSON s `search_id` a `urls` pole
**`add-result`** přijímá parametry jako CLI flagy:
```
flight_search.py add-result --search-id 1 --airline "KLM" --route "PRG→AMS→PTY" ...
```
**`results`** formátuje tabulku:
```
#1 KLM PRG→AMS→PTY / PTY→AMS→PRG 12h30m 1 stop (AMS 2h15m) 15 420 CZK
#2 TAP PRG→LIS→PTY / PTY→LIS→PRG 14h10m 1 stop (LIS 1h45m) 16 200 CZK
```
### 4. SKILL.md
Skill je **agent-driven** (jako deep-research). Agent:
1. Získá parametry od uživatele (origin, destination, dates, adults, flex, min_stay, max_layovers)
2. Zavolá `create-search` → získá search_id a seznam URL
3. Pro každou URL zavolá `web_fetch`
4. Z fetched markdown parsuje letové výsledky (aerolinka, trasa, časy, cena, přestupy)
5. Uloží výsledky přes `add-result`
6. Zavolá `results <search_id>` a prezentuje uživateli top 35
SKILL.md obsahuje:
- Kdy se skill aktivuje ("hledej letenky", "flight search", "letenky do X")
- Postup orchestration (krok za krokem)
- KAYAK URL konstrukce (včetně flex data strategie)
- Parsing guidance (co hledat v markdown výstupu KAYAKu)
- Formátování výstupu pro uživatele
- Command reference pro `flight_search.py`
### 5. KAYAK URL parametry k ověření
Při implementaci ověřit:
- Filter na max 1 přestup: pravděpodobně `fs=stops=0` (0 additional stops), ale nutno testovat
- Flex dates: zda KAYAK má nativní flex parameter (např. `flexible_dates=3`), což by snížilo počet requestů
- Pokud nativní flex nefunguje, použít strategii generování kombinací z bodu 3
### 6. CZK přepočet
- KAYAK URL vždy s `curr=CZK` → ceny rovnou v CZK
- `price_czk` = cena z KAYAKu
- `price_currency` = CZK
- Pokud by se později přidaly zdroje v jiné měně, přidat exchange rate fetch (např. z ČNB API)
### 7. Cron příprava (neimplementovat)
DB schema je připraveno pro rozšíření:
- Přidat tabulku `watchers` s threshold cenou a notifikačním kanálem
- Přidat flag `is_watched` do `searches`
- Cron job by volal `create-search` + fetch + `add-result` + kontrola threshold
## Ověření
1. Vytvořit skill adresář a skript
2. Spustit `create-search --origin PRG --destination PTY --dep 2026-12-21 --ret 2027-01-04 --adults 3 --flex 3 --min-stay 14 --max-layovers 1`
3. Ověřit, že generované URL jsou platné KAYAK odkazy
4. Fetchnout jednu URL přes `web_fetch` a ověřit, že vrací letové výsledky
5. Uložit výsledek přes `add-result` a ověřit v DB
6. Zavolat `results <search_id>` a ověřit formátování
7. Ověřit, že flex date kombinace respektují min_stay constraint