Files
nanobot-runtime/plans/flight-search-skill.md
2026-07-22 12:32:23 +02:00

150 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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