150 lines
6.1 KiB
Markdown
150 lines
6.1 KiB
Markdown
# 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 3–5 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 3–5
|
||
|
||
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 |