---
name: koudy-t212-upload
description: "Use when Patrik invokes /koudy-t212-upload. Imports transactions from one or more bank/broker statements (Trading 212 / Paynetics PDF, Moneta Money Bank PDF, XTB xlsx, Investown CSV) into the Koudy Finance dashboard, then schedules the next monthly Notion reminder with a per-account checklist."
---

Imports transactions from one or more bank/broker statements (Trading 212 / Paynetics PDF, Moneta Money Bank PDF, XTB xlsx export with CZK/EUR/USD sub-accounts, Investown CSV) into the Koudy Finance dashboard, then schedules the next monthly Notion reminder with a per-account checklist.

ARGUMENTS: one or more file or directory paths, space-separated.
- Trading 212 / Moneta → `.pdf` files
- XTB → either an xlsx file (`(CZK|EUR|USD)_<digits>_<date>_<date>.xlsx`) or a directory containing such files in sub-folders (the format that XTB ships its export in)
- Investown → `.csv` file (UTF-8 BOM, semicolon-separated, header `Datum;Časová zóna;Typ;Detail;Částka [CZK];...`)

Format is auto-detected per file. If no path is given, prompt Patrik for the file(s).

---

## STEP 0 — Loop and aggregate

Resolve every input path:
- File ending `.pdf` → process as a single PDF.
- File ending `.xlsx` matching `(CZK|EUR|USD)_<digits>_<date>_<date>\.xlsx` → process as one XTB sub-account.
- File ending `.csv` whose first line starts with `﻿Datum;Časová zóna;Typ;` → process as Investown export.
- Directory → recursively pick up every matching `.pdf`, `.xlsx`, `.csv` inside it.

For each resolved file run STEPs 1–3, accumulating into in-memory lists:
- `expense_rows` — `{date, description, amount_haler, original_amount_minor, currency, category, source, source_account?}`
- `income_rows` — same shape
- `investment_snapshots` — `{platform, currency, original_invested_minor, original_current_minor, fx_rate, entry_price?, price_currency?, notes?, cash_balance_minor?, original_cash_balance_minor?}` (XTB per-ticker positions + XTB per-currency cash + Investown aggregate + Moneta per-account closing balance + T212 Paynetics card balance). `cash_balance_minor` (CZK haléř) is the dashboard's free-cash field that feeds Zůstatek; `original_cash_balance_minor` is the same number in the row's source currency. Set both for cash-only rows (Moneta, Paynetics card, future Investown wallet imports).

`source` ∈ `"t212"` | `"moneta"` | `"xtb"` | `"investown"`. For XTB rows also set `source_account` ∈ `"CZK"` | `"EUR"` | `"USD"`.

After all files are processed run STEPs 4–5 ONCE on the combined lists. Then STEP 6 (combined summary) and STEPs 7–8 (Notion) ONCE.

If a file fails format detection, report it and continue with the rest. If ALL files fail → STOP, do not touch Notion.

---

## STEP 1 — Read and detect format

For each file:

- `.pdf` → Read with `pages: "1-5"`. Inspect page 1:
  - Contains **"Card statement"** AND **"Paynetics"** → `format = t212`
  - Contains **"MONETA"** or **"Moneta Money Bank"** → `format = moneta`
- `.xlsx` matching `(CZK|EUR|USD)_<digits>_<date>_<date>\.xlsx` → `format = xtb`, `currency = <prefix>`
- `.csv` whose first line begins with `﻿Datum;Časová zóna;Typ;` (UTF-8 BOM, semicolon-separated) → `format = investown`, `currency = "CZK"`
- Otherwise → report `Nerozpoznaný formát: <filename>.` and skip.

If a Moneta PDF spans multiple pages, read additional pages until the entire transaction list is captured (Moneta tables can run to 5+ pages).

---

## STEP 2 — Extract transactions

### 2a. Trading 212 (Paynetics)

From the "Card transactions" page(s), every row where TYPE = "Purchase":

- **date**: TIME column → `YYYY-MM-DD`
- **description**: MERCHANT column, raw merchant name
- **original amount (CZK)**: TOTAL column, absolute value, strip "CZK" prefix and minus sign — float CZK
- **currency**: `"CZK"`
- **source**: `"t212"`

Skip rows of type "Refund", "Payout", "ATM Withdrawal", or anything that is not "Purchase". Push to `expense_rows`.

#### Card balance snapshot (1 row per Paynetics PDF)

Z hlavičky / summary stránky vytáhni aktuální zůstatek karty. Hledej v tomto pořadí:
1. `Closing balance` / `Konečný zůstatek` v summary boxu
2. `Available balance` / `Dostupný zůstatek`
3. Poslední řádek z transakční tabulky se sloupcem `Balance` (running balance)

Pokud nic z toho nenajdeš → SKIP cash snapshot pro tenhle PDF a v summary uveď `Paynetics card balance: nenalezeno`. Nikdy NEhádej.

Z PDF vytáhni **last 4 digits** čísla karty (obvykle "Card ending **** XXXX" nebo "Card number ...XXXX") pro stable dedupe key.

Push do `investment_snapshots`:
```
{
  platform: 'Trading 212',
  currency: 'CZK',
  original_invested_minor: 0,
  original_current_minor: 0,
  cash_balance_minor: round(card_balance_czk × 100),
  original_cash_balance_minor: round(card_balance_czk × 100),
  fx_rate: 1.0,
  notes: '[T212 card] Paynetics ****<LAST4>'
}
```

Push i pokud `card_balance_czk == 0` (zůstatek může klesnout na 0 a UPSERT to musí zaktualizovat).

### 2b. Moneta Money Bank

Tabulka transakcí má sloupce typu `Datum zaúčtování` / `Popis transakce` / `Částka` / `Měna` / `Zůstatek`.

Každou **výdajovou** transakci (záporná `Částka`):

- **date**: `Datum zaúčtování` → `YYYY-MM-DD`
- **description**: první smysluplná řádka v `Popis transakce` (merchant / protistrana). Prefix typu `Platba kartou`, `Inkaso`, `Trvalý příkaz`, `SIPO` ponechej.
- **original amount (CZK)**: absolutní hodnota `Částka` v CZK
- **currency**: `"CZK"`
- **source**: `"moneta"`

**Skip**: příchozí platby, interní převody mezi vlastními účty, ATM výběry, provozní poplatky < 50 Kč.
Pokud jsi nejistý jestli je to výdaj → přeskoč a v souhrnu uveď jako `Skipped (uncertain)`.

Push k `expense_rows`.

#### Closing balance snapshot (1 row per Moneta PDF)

Každý Moneta PDF představuje **jeden účet** (běžný / spořicí / kreditní). Z hlavičky / summary vytáhni:

1. **`account_id`** — číslo účtu, formát `<8-10 digits>/<4 digits>` (např. `123456789/0600`). Hledej v hlavičce po klíčovém slovu `Číslo účtu`. Pokud chybí → SKIP snapshot, log warning.
2. **`account_label`** — typ účtu z PDF header / title:
   - `běžný účet` / `Běžný účet` → `běžný`
   - `spořicí účet` / `Spořicí účet` → `spořicí`
   - `kreditní karta` / `Kreditní karta` / `kreditní účet` → `kreditní`
   - jinak → `účet`
3. **`closing_balance_czk`** — preferuj v tomto pořadí:
   - explicitní `Konečný zůstatek` ze summary (obvykle `Konečný zůstatek k <datum>: X,XX CZK`)
   - `Zůstatek k <end_date>` v hlavičce / summary
   - poslední hodnota ze sloupce `Zůstatek` v transakční tabulce (running balance po poslední transakci)

   Měna musí být CZK; cizí měny SKIP s warningem.

Pokud `closing_balance_czk` nelze určit (žádná z variant nesedí) → SKIP snapshot, log do summary jako `Moneta <account_label> closing balance: nenalezeno (<filename>)`. Nikdy NEhádej.

Pro **kreditní karta**: `closing_balance_czk` je obvykle ZÁPORNÉ (dluh) nebo kladné (přeplatek). Ulož s odpovídajícím znaménkem — záporné `cash_balance_minor` = pasivní zůstatek. Aktuální `cashflow.js` filtr `cash_balance_minor > 0` to NEzachytí; to je očekávaná konzervativní volba (kreditka zatím není v Zůstatku, jen běžné a spořicí), neřeš.

Push do `investment_snapshots`:
```
{
  platform: 'Moneta',
  currency: 'CZK',
  original_invested_minor: 0,
  original_current_minor: 0,
  cash_balance_minor: round(closing_balance_czk × 100),
  original_cash_balance_minor: round(closing_balance_czk × 100),
  fx_rate: 1.0,
  notes: '[Moneta cash] <account_label> <account_id>'
}
```

Příklad notes: `[Moneta cash] běžný 123456789/0600`. Stable napříč uploady — dedupe v 5b matchne přes přesný `notes` string.

### 2c. XTB (xlsx)

Otevři xlsx přes `openpyxl` (Python) — sheet `Cash Operations`. Header je řádek index 4 (sloupce `Type | Ticker | Instrument | Time | Amount | ID | Comment | Product`); data od řádku 5. Přeskoč `Total` řádek na konci. Také přečti `Closed Positions` sheet (jen pro snapshot výpočet, ne na řádkové zápisy).

Pro každý cash op routuj podle `Type`:

| XTB `Type`             | Target          | Category               | Knot                        |
|------------------------|-----------------|------------------------|-----------------------------|
| `Dividend`             | `income_rows`   | `Investment - Dividend`| abs(Amount)                 |
| `Dividend equivalent`  | `income_rows`   | `Investment - Dividend`| abs(Amount)                 |
| `Free funds interest`  | `income_rows`   | `Investment - Interest`| abs(Amount)                 |
| `Withholding tax`      | `expense_rows`  | `Investment - Tax`     | abs(Amount)                 |
| `Free funds interest tax` | `expense_rows` | `Investment - Tax`    | abs(Amount)                 |
| `Adjustment fee`       | `expense_rows`  | `Investment - Fee`     | abs(Amount)                 |
| `Stock purchase`       | SKIP (feeds snapshot only) | —          | —                           |
| `Deposit`              | SKIP (transfer)            | —          | —                           |
| anything else          | SKIP, log "Skipped (uncertain)" | —     | —                           |

Pro každý zařazený řádek:
- **date**: prvních 10 znaků z `Time` → `YYYY-MM-DD`
- **description**: `<Ticker> <Instrument>` pokud Ticker není prázdný (např. `MO.US Altria`); jinak `Comment` (např. `Free-funds Interest 2026-04`)
- **original amount minor**: round(abs(Amount) × 100) — v měně `currency` (USD cents / EUR cents / CZK haléř)
- **currency**: file prefix (`CZK` / `EUR` / `USD`)
- **source**: `"xtb"`
- **source_account**: `currency`

#### Position snapshots (per ticker — 1 row per Stock purchase ticker)

Pro každý xlsx, agregruj `Stock purchase` řádky podle `Ticker`:
- `original_invested_minor` = sum (round(abs(Amount) × 100)) per ticker.
- `original_current_minor` = `original_invested_minor` (placeholder; live current value se získá přes Yahoo refresh).
- `entry_price` = vážený průměr ceny per ticker (z `Comment` "OPEN BUY <SHARES> @ <PRICE>").
- `price_currency` = file currency.
- `fx_rate` = aktuální CZK rate pro `currency` (CZK = 1.0).
- `notes` = `<YAHOO_TICKER> XTB import <SHARES> sh @ <PRICE> <NATIVE_CCY>` (parsuje to `refresh-prices.js`).

Push každý do `investment_snapshots`.

#### Cash snapshot (per currency — 1 row per xlsx file)

XTB free funds = aktuální zůstatek volných prostředků na sub-účtu, který koudy-finance jinak nikde netrackuje. **Bez tohohle bude celkový XTB platform total v dashboardu nižší než XTB app o 5–10 % (sedí jen pozice).**

Pro každý xlsx (= jedna měna):
- `cash_balance_native = SUM(Amount)` přes VŠECHNY data řádky v `Cash Operations` sheetu, kromě finálního `Total` řádku. To je matematicky ekvivalentní `Total` hodnotě — pokud je `Total` řádek čistá hodnota (ne formula), můžeš ji vzít přímo. Sum kombinuje kladné (Deposit + Dividend + Free funds interest) a záporné (Stock purchase + Withholding tax + Adjustment fee).
- `cash_balance_native_minor = round(cash_balance_native × 100)`
- `fx_rate` = aktuální CZK rate pro `currency` (CZK = 1.0).
- Extrahuj `<account_num>` z filename `(CZK|EUR|USD)_<digits>_...xlsx`.
- `notes` = `[XTB cash] <CCY> account <account_num>` (např. `[XTB cash] CZK account 50740529`). Prefix `[XTB cash]` je dedupe klíč v STEP 5b.

Push do `investment_snapshots` jako:
`{platform: 'XTB', currency, original_invested_minor: 0, original_current_minor: cash_balance_native_minor, fx_rate, notes: '[XTB cash] <CCY> account <account_num>'}`

Push i pokud `cash_balance_native_minor == 0` (zůstatek může klesnout na 0 po výběru a UPSERT to musí zaktualizovat).

### 2d. Investown (csv)

Otevři CSV přes Python `csv` modul. Decoduj jako `utf-8-sig` (kvůli BOM). Delimiter `;`. Header: `Datum;Časová zóna;Typ;Detail;Částka [CZK];Úvěr;Název projektu;Odkaz na projekt;Typ projektu`. Częscztková notace: `,` jako desetinný oddělovač (např. `3,91`).

Pro každý řádek routuj podle `Typ`:

| Investown `Typ`                  | Target          | Category                 | Knot                                           |
|----------------------------------|-----------------|--------------------------|------------------------------------------------|
| `Výnos`                          | `income_rows`   | `Investment - Interest`  | abs(`Částka [CZK]`)                            |
| `Částečný výnos`                 | `income_rows`   | `Investment - Interest`  | abs(`Částka [CZK]`)                            |
| `Bonusový výnos`                 | `income_rows`   | `Investment - Bonus`     | abs(`Částka [CZK]`)                            |
| `Smluvní pokuta`                 | `income_rows`   | `Investment - Penalty`   | abs(`Částka [CZK]`)                            |
| `Zákonné úroky z prodlení`       | `income_rows`   | `Investment - Penalty`   | abs(`Částka [CZK]`)                            |
| `Poplatek za předčasný prodej`   | `expense_rows`  | `Investment - Fee`       | abs(`Částka [CZK]`)                            |
| `Investice` / `Autoinvestice`    | SKIP (feeds snapshot — money locked into project) | — | —              |
| `Vklad peněz` / `Výběr peněz`    | SKIP (bank transfer)         | —            | —                                  |
| `Splacení jistiny` / `Částečné splacení jistiny` / `Odstoupení` / `Prodej` / `Vrácení peněz` / `Vrácení nabídky` / `Nabídka ke koupi` | SKIP (principal return / capital movement) | — | — |
| anything else                    | SKIP, log `Skipped (uncertain)` | —          | —                                  |

Pro každý zařazený řádek:
- **date**: prvních 10 znaků z `Datum` → `YYYY-MM-DD`
- **description**: `Název projektu` (např. `Vila Na Hřebenkách`); pokud prázdné, fallback `Detail`
- **original amount minor**: round(abs(`Částka [CZK]`) × 100) v haléřích (CSV je vždy CZK)
- **currency**: `"CZK"`
- **source**: `"investown"`

#### Snapshot calculation (1 row per CSV)

Spočítej z capital-movement řádků:
- `total_invested = abs(sum(Investice + Autoinvestice + Nabídka ke koupi))` — kolik šlo do projektů
- `total_returned = sum(Splacení jistiny + Částečné splacení jistiny + Odstoupení + Prodej + Vrácení peněz + Vrácení nabídky)` — kolik se vrátilo (jistina)
- `currently_invested = total_invested - total_returned` — pořád v projektech (haléř)
- `original_invested_minor = round(currently_invested * 100)`
- `original_current_minor = original_invested_minor` (Investown crowdfunding nezhodnocuje, výnosy tečou jako income; current ≈ invested)
- `fx_rate = 1.0`
- Push do `investment_snapshots` jako `{platform: 'Investown', currency: 'CZK', original_invested_minor, original_current_minor, fx_rate, notes: 'Investown crowdfunding (computed from CSV)'}`.

### Empty result

Pokud `expense_rows`, `income_rows` a `investment_snapshots` jsou všechny prázdné → STOP, sděl `Žádné transakce nenalezeny.` a NEvolej Notion.

---

## STEP 3 — Categorize (PDF only)

XTB rows už mají kategorii z routing tabulky v 2c. Aplikuj keyword pravidla jen na T212 a Moneta `description` (case-insensitive, first match wins):

| Keywords | Category |
|----------|----------|
| tesco, billa, albert, lidl, kaufland, penny, globus, rohlik, kosik, potraviny, makro | Jídlo |
| mcdonald, kfc, burger, pizza, restaurant, kavarna, cafe, hospoda, sushi, delivery, wolt, bolt food, dáme jídlo, starbucks, primirest | Jídlo |
| nájem, rent, nájemné, trvalý příkaz nájem, sjm bydlení | Bydlení |
| čez, e.on, innogy, eon, elektřina, plyn, voda, teplo, svj, správa bytu, sipo, inkaso čez, inkaso innogy, inkaso pre, inkaso prazska plynarenska | Bydlení |
| t-mobile, tmobile, o2, vodafone, internet, upc, o2 czech | Bydlení |
| mhd, dpp, dpmb, idpk, pid, ropid, metro, tram, bus, vlak, cd.cz, regiojet, flixbus, uber, bolt.eu, parkovací, parking | Doprava |
| benzín, benzin, čepro, orlen, shell, mol, robin oil, pohonné, ono benzina | Doprava |
| netflix, spotify, hbo, disney, apple.com, youtube premium, twitch, steam, xbox, playstation, nintendo | Zábava |
| kino, cinema, divadlo, concert, muzeum, bowling, escape | Zábava |
| lékárna, lekarna, dr.max, benu, vitamín, léky, nemocnice, doktor, zubař, vzp, pojišťovna | Zdraví |
| zara, hm, h&m, reserved, primark, nike, adidas, oblečení, oděvy, decathlon | Oblečení |
| letenka, airbnb, hotel, booking, expedia, cestování, dovolená | Cestování |
| udemy, coursera, kniha, amazon kindle, vzdělání, kurz, knihy luxor | Vzdělávání |
| github, digitalocean, aws, vercel, cloudflare, jetbrains, figma, adobe, anthropic, openai, hetzner | Technologie |
| alza, mall.cz, czc, datart, electro, electroworld | Technologie |
| *(no match)* | Ostatní |

První shoda shora dolů.

---

## STEP 3.5 — FX conversion (multi-currency rows only)

Pro každý non-CZK řádek (XTB EUR/USD):

1. Vytvoř unikátní set `(currency, date)` napříč `expense_rows` a `income_rows`.
2. Pro každý fetchni historický rate:
   ```bash
   curl -sf "https://api.frankfurter.app/<DATE>?from=<CURRENCY>&to=CZK" | jq -r '.rates.CZK'
   ```
   Pokud Frankfurter vrátí jiné datum (víkend → poslední pracovní den), je to OK — použij vrácený rate.
   Pokud Frankfurter request selže → použij dnešní rate jako fallback a poznamenej do summary.
3. Pro každý non-CZK řádek spočítej `amount_haler` = round(`original_amount_minor` × `fx_rate`). CZK řádky: `amount_haler` = `original_amount_minor`.
4. Pro `investment_snapshots` použij **dnešní** rate (snapshot je k dnešku).

---

## STEP 4 — Dedupe against existing rows

**CRITICAL:** Re-running the skill na stejný export musí být no-op.

Project: `~/WebstormProjects/koudy-finance`. DB binding: `koudy-finance-db`.

1. Z `expense_rows + income_rows` spočítej `min_date` a `max_date`.
2. Paralelně načti existující řádky z remote D1:
   ```bash
   cd ~/WebstormProjects/koudy-finance && npx wrangler d1 execute koudy-finance-db --remote --json --command "SELECT date, description, amount, original_amount, currency FROM expenses WHERE date BETWEEN 'MIN' AND 'MAX' AND account_type = 'personal'"
   ```
   ```bash
   cd ~/WebstormProjects/koudy-finance && npx wrangler d1 execute koudy-finance-db --remote --json --command "SELECT date, description, amount, original_amount, currency FROM income WHERE date BETWEEN 'MIN' AND 'MAX' AND account_type = 'personal'"
   ```
3. Postav dva Sety klíčů. Klíč: `${date}|${description}|${original_amount}|${currency}` (`original_amount` = uložený minor int, fallback na `amount` pokud `original_amount` je NULL).
4. Pro každý nový řádek vyrob klíč ve stejném formátu. Pokud už v Setu je → mark **duplicate, skip**.
5. Drž `to_insert_expenses`, `to_insert_income`, `skipped_duplicates`.

Pokud je všechno duplicate → NEpouštěj INSERT část STEP 5, ale stále spusť snapshot UPSERT (níže) a STEPy 6–8.

---

## STEP 5 — Import to dashboard

Použij přímý `wrangler d1 execute --remote` (Cloudflare Access blokuje POST API).

### 5a. Insert expenses & income

Postav jeden INSERT per tabulka, jen pokud je co.

```bash
cd ~/WebstormProjects/koudy-finance && npx wrangler d1 execute koudy-finance-db --remote --command "INSERT INTO expenses (amount, category, description, date, currency, original_amount, account_type) VALUES (20000, 'Jídlo', 'STARBUCKS APP', '2026-04-01', 'CZK', 20000, 'personal')"
```

```bash
cd ~/WebstormProjects/koudy-finance && npx wrangler d1 execute koudy-finance-db --remote --command "INSERT INTO income (amount, category, description, date, currency, original_amount, account_type) VALUES (8800, 'Investment - Dividend', 'MO.US Altria', '2026-04-30', 'USD', 355, 'personal')"
```

Escape jednoduchých uvozovek zdvojením (`Primirest's` → `Primirest''s`).

### 5b. Investment snapshots

Pro každý záznam v `investment_snapshots` UPSERT podle exact `notes` matche (ne jen `(platform, currency, date)`, jinak XTB cash + per-ticker řádky se navzájem přepisují).

1. Pro idempotenci hledej existující řádek **stejnou notes** napříč daty (cash snapshot je jeden per účet, přepiš nejnovější — netvoř nový řádek per den, jinak `cashflow.js` shadow rules dropnou starý cash):
   ```bash
   cd ~/WebstormProjects/koudy-finance && npx wrangler d1 execute koudy-finance-db --remote --json --command "SELECT id FROM investments WHERE platform = '<PLATFORM>' AND notes = '<NOTES>' AND account_type = 'personal' ORDER BY date DESC LIMIT 1"
   ```
   - XTB cash dedupe klíč: `notes` přesně `[XTB cash] <CCY> account <account_num>` (idempotentní per měna).
   - XTB per-ticker dedupe klíč: `notes` přesně `<TICKER> XTB import <SHARES> sh @ <PRICE> <CCY>` (nový nákup za den vytvoří nový řádek; re-import stejného exportu = no-op). Pro per-ticker má dedupe běžet jen pro `date = '<TODAY>'`.
   - Investown dedupe klíč: `notes` přesně `Investown crowdfunding (computed from CSV)`.
   - **Moneta cash dedupe klíč** (NEW): `notes` přesně `[Moneta cash] <account_label> <account_id>` (idempotentní per účet, napříč všemi uploady).
   - **T212 card dedupe klíč** (NEW): `notes` přesně `[T212 card] Paynetics ****<LAST4>` (idempotentní per kartu).

2. Pokud existuje → UPDATE. Sloupce co se updatují závisejí na typu snapshot:
   - **XTB cash / Investown / per-ticker** (legacy, cash žije v `current_value`):
     ```sql
     UPDATE investments SET invested_amount = <CZK_HALER>, current_value = <CZK_HALER>, original_invested_amount = <NATIVE_MINOR>, original_current_value = <NATIVE_MINOR>, fx_rate = <RATE>, date = '<TODAY>' WHERE id = <ID>
     ```
   - **Moneta cash / T212 Paynetics card** (NEW — cash žije v `cash_balance_minor`):
     ```sql
     UPDATE investments SET cash_balance_minor = <CASH_CZK_HALER>, original_cash_balance_minor = <CASH_NATIVE_MINOR>, date = '<TODAY>', invested_amount = 0, current_value = 0, original_invested_amount = 0, original_current_value = 0, fx_rate = <RATE> WHERE id = <ID>
     ```

3. Jinak INSERT. Vždy zahrnout `cash_balance_minor` a `original_cash_balance_minor` (defaultně 0 pro legacy snapshots):
   ```sql
   INSERT INTO investments (platform, invested_amount, current_value, date, currency, original_invested_amount, original_current_value, fx_rate, account_type, notes, cash_balance_minor, original_cash_balance_minor)
   VALUES ('<PLATFORM>', <CZK_HALER>, <CZK_HALER>, '<TODAY>', '<CCY>', <NATIVE_MINOR>, <NATIVE_MINOR>, <RATE>, 'personal', '<NOTES>', <CASH_CZK_HALER>, <CASH_NATIVE_MINOR>)
   ```
   Pro **Moneta cash / T212 Paynetics card**: `<CZK_HALER>` = 0, `<NATIVE_MINOR>` = 0, `<CASH_CZK_HALER>` = round(closing_balance × fx_rate × 100), `<CASH_NATIVE_MINOR>` = round(closing_balance × 100). Měna `<CCY>` = `'CZK'` pro Moneta a Paynetics (T212 karta je vždy CZK účet i když je T212 platforma EUR-based).

Pro XTB cash: `<CZK_HALER>` = round(`cash_balance_native_minor` × `fx_rate`); `invested_amount` ← 0 (cash má nulový cost basis), `current_value` ← `<CZK_HALER>`, `original_invested_amount` ← 0, `original_current_value` ← `cash_balance_native_minor`.

Pro XTB per-ticker: jeden řádek per ticker s `entry_price` + `price_currency` + position `notes` (parsuje `refresh-prices.js`).

Pro Investown: jeden aggregate řádek s `currency='CZK'` a notes `'Investown crowdfunding (computed from CSV)'`.

Pokud command selže, hlaš a pokračuj.

### 5c. Refresh Yahoo prices for XTB positions

**CRITICAL:** Position UPSERT v 5b nastavuje `current_value = invested_amount` (cost basis × today's fx) a tím **přepíše** předchozí Yahoo refresh-prices output. Bez následného refreshe by XTB platform total v dashboardu skočil o ~10–20 % dolů (chybí unrealized gains).

Po dokončení 5b (jen pokud byly XTB position snapshots), spusť Yahoo v8 refresh logic lokálně (endpoint `/api/investments/refresh-prices` je za Cloudflare Access, nelze přes curl):

1. Pull XTB position rows: `SELECT id, invested_amount, entry_price, notes FROM investments WHERE platform='XTB' AND entry_price IS NOT NULL AND date='<TODAY>' AND account_type='personal'`.
2. Per řádek: parse ticker z `notes` (regex `^([A-Z][A-Z0-9]*(?:\.[A-Z0-9]{1,4})?)\b`, blocklist `ETF/DCA/P2P/USD/EUR/CZK/GBP/CHF/INC/LTD/ATH/ATL/OTC`).
3. Per unikátní ticker fetchni Yahoo v8: `https://query2.finance.yahoo.com/v8/finance/chart/<TICKER>?interval=1d&range=1d` s `User-Agent: Mozilla/5.0 (compatible)`. Cena = `chart.result[0].meta.regularMarketPrice`.
4. Pro každý XTB row: `new_current = round(invested_amount × (live_price / entry_price))`, pak `UPDATE investments SET current_value = <new_current> WHERE id = <ID>`.
5. Pokud Yahoo vrátí `null` (tickerless / blocklist hit / 5xx), nech `current_value` na cost basis a poznamenej do summary.

Tahle 5c kopíruje logiku `functions/api/investments/refresh-prices.js`. Když ten endpoint změní vzorec, sync sem.

---

## STEP 6 — Print combined summary

```
✅ Importováno X řádků celkem
   • Expenses: A (B CZK)
   • Income:   C (D CZK)

Per source:
   • Trading 212: P řádků (Q CZK)
   • Moneta:      R řádků (S CZK)
   • XTB CZK:     T income / U expenses
   • XTB EUR:     T income / U expenses (V CZK po FX)
   • XTB USD:     T income / U expenses (V CZK po FX)

📊 Snapshots updated/inserted:
   • XTB CZK: positions = X CZK · cash = Y CZK
   • XTB EUR: positions = X CZK · cash = Y CZK (fx 25.10)
   • XTB USD: positions = X CZK · cash = Y CZK (fx 23.50)
   • Investown: invested = X CZK
   • Moneta běžný 123456789/0600: zůstatek = X CZK
   • Moneta spořicí 234567890/0600: zůstatek = X CZK
   • Moneta kreditní 345678901/0600: zůstatek = X CZK
   • T212 Paynetics ****1234: zůstatek karty = X CZK

⏭️  Přeskočeno N duplicit
🤔 Nejisté (přeskočeno): K
   <list>

Kategorie:
- Investment - Dividend: N (Z CZK)
- Jídlo: N (Z CZK)
- ...
```

Source breakdown ukazuj jen pokud je víc než 1 zdroj nenulový. Per-XTB-currency řádky jen když XTB se importovalo. Duplicate / uncertain řádky jen pokud > 0. Kategorie sort by total amount desc.

Pokud někde selhal INSERT:
```
⚠️ Selhalo F řádků — zkontroluj dashboard
```

---

## STEP 7 — Notion: mark old reminders as done

1. `mcp__notion__notion-search` na `"Bankovní výpisy"` AND `"T212 statement"` v calendar DB (`25e3cc985f39805cbbb1c2a801d72b4b`). Starší reminder měl T212 title; nové mají Bankovní výpisy.
2. Najdi **všechny** open (`Done = unchecked`) výpis-related stránky a označ je `Done = __YES__` přes `mcp__notion__notion-update-page`. Tím se automaticky zametou staré weekly remindery, které ještě visí.
3. Pokud nic nenajdeš, jdi dál tiše.

---

## STEP 8 — Notion: create monthly reminder with checklist

Statementy řešíme jednou měsíčně → další reminder = **první den následujícího měsíce**.

1. Spočítej `next_first`:
   - Z `currentDate` vezmi rok+měsíc, přidej 1 měsíc, den = `01`. Příklad: 2026-05-06 → `2026-06-01`; 2026-12-15 → `2027-01-01`.
2. Use `mcp__claude_ai_Notion__notion-create-pages` s `parent.type = 'data_source_id'` a `data_source_id = '25e3cc98-5f39-81de-9615-000b0dfc7193'` (= TO DO | Personal data source — NE database ID `25e3cc985f39805cbbb1c2a801d72b4b`, ten vrací 404 z `notion-create-pages`). **Content body** obsahuje checklist:
   - Properties:
     - `Branch` (title): `📊 Bankovní výpisy — nahrát (T212 + Moneta + XTB + Investown)`
     - `Date`: `next_first`
     - `Done`: `__NO__`
   - `content` (Markdown):
     ```markdown
     Statementy ke stažení a uploadu:

     - [ ] Trading 212 (Paynetics) — Statement.pdf + Statement 2.pdf
     - [ ] Moneta — běžný CZ účet
     - [ ] Moneta — spořící CZ účet
     - [ ] Moneta — kreditní CZ účet
     - [ ] XTB — CZK + EUR + USD účet (export ZIP)
     - [ ] Investown — CSV export ze settings

     Pak spustit `/koudy-t212-upload <paths>`.
     ```
3. Pokud `notion-create-pages` neumí `content` přímo, vytvoř stránku jen s properties a hned doplň body markdownem přes `mcp__claude_ai_Notion__notion-update-page`.
4. Confirm:
   > Měsíční připomínka přidána do Notion na **1. <měsíc> <rok>** s checklistem 6 účtů.

---

## Error handling

- Unrecognized format pro jeden file → report a skip ten file, continue with rest.
- Žádné transakce přes všechny files → STOP, NEvolej Notion.
- D1 INSERT failure → log a pokračuj s ostatními.
- FX API failure → fallback na dnešní rate, poznamenej do summary.
- Notion failure → ukaž error, navrhni check Notion integration.
