diff --git a/00 PictureCollector/migrate_add_wanted.py b/00 PictureCollector/migrate_add_wanted.py
new file mode 100644
index 0000000..20f6094
--- /dev/null
+++ b/00 PictureCollector/migrate_add_wanted.py
@@ -0,0 +1,28 @@
+"""
+migrate_add_wanted.py — přidá sloupec wanted BOOLEAN NOT NULL DEFAULT FALSE do tabulky photos.
+Bezpečné spustit vícekrát (idempotentní).
+"""
+
+import psycopg2
+
+DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
+ password="Vlado7309208104++", database="fotky_buzalkovi")
+
+SQL = """
+ALTER TABLE photos
+ ADD COLUMN IF NOT EXISTS wanted BOOLEAN NOT NULL DEFAULT FALSE;
+"""
+
+conn = psycopg2.connect(**DB)
+conn.autocommit = True
+cur = conn.cursor()
+
+print("Přidávám sloupec wanted…")
+cur.execute(SQL)
+print("Hotovo.")
+
+cur.execute("SELECT COUNT(*) FROM photos WHERE wanted = FALSE")
+n = cur.fetchone()[0]
+print(f"Fotek s wanted=FALSE: {n:,}")
+
+conn.close()
diff --git a/00 PictureCollector/migrate_category.py b/00 PictureCollector/migrate_category.py
new file mode 100644
index 0000000..67355b5
--- /dev/null
+++ b/00 PictureCollector/migrate_category.py
@@ -0,0 +1,31 @@
+"""
+migrate_category.py — přidá sloupec category do photos (idempotentní)
+a provede konkrétní UPDATE podle pravidel.
+"""
+import psycopg2
+
+DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
+ password="Vlado7309208104++", database="fotky_buzalkovi")
+
+conn = psycopg2.connect(**DB)
+conn.autocommit = True
+cur = conn.cursor()
+
+# 1. Přidat sloupec category pokud neexistuje
+cur.execute("""
+ ALTER TABLE photos
+ ADD COLUMN IF NOT EXISTS category VARCHAR(100);
+""")
+print("Sloupec category OK.")
+
+# 2. BolyMedia SG520 → wanted=TRUE, category='Fotopast'
+cur.execute("""
+ UPDATE photos
+ SET wanted = TRUE,
+ category = 'Fotopast'
+ WHERE camera_make = 'BolyMedia'
+ AND camera_model = 'SG520';
+""")
+print(f"SG520 aktualizováno: {cur.rowcount:,} řádků → wanted=TRUE, category='Fotopast'")
+
+conn.close()
diff --git a/00 PictureCollector/preview_sample.py b/00 PictureCollector/preview_sample.py
new file mode 100644
index 0000000..92ebf0f
--- /dev/null
+++ b/00 PictureCollector/preview_sample.py
@@ -0,0 +1,230 @@
+"""
+preview_sample.py — zobrazí náhled fotek podle ID z databáze.
+
+Použití:
+ python preview_sample.py 101 202 303 ...
+ python preview_sample.py --file ids.txt # jedno ID na řádek
+ python preview_sample.py --file ids.txt --thumb 400
+"""
+
+import argparse
+import base64
+import io
+import sys
+import tempfile
+import webbrowser
+from pathlib import Path
+
+import psycopg2
+from PIL import Image
+
+# ── DB ────────────────────────────────────────────────────────────────────────
+
+DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
+ password="Vlado7309208104++", database="fotky_buzalkovi")
+
+# ── Cesty ─────────────────────────────────────────────────────────────────────
+
+NAS_LINUX_PREFIX = "/mnt/user/ZalohaVsechObrazku/"
+NAS_WIN_UNC = r"\\Tower1\ZalohaVsechObrazku\\"
+
+
+def linux_to_win(p: str) -> Path:
+ rel = p.removeprefix(NAS_LINUX_PREFIX).replace("/", "\\")
+ return Path(NAS_WIN_UNC + rel)
+
+
+# ── Načtení dat z DB ──────────────────────────────────────────────────────────
+
+QUERY = """
+ SELECT
+ z.id,
+ z.cesta_zalohy,
+ z.velikost,
+ p.taken_at,
+ p.taken_at_source,
+ p.camera_make,
+ p.camera_model,
+ p.width,
+ p.height,
+ p.megapixels,
+ p.gps_lat,
+ p.gps_lon,
+ COALESCE(
+ (SELECT string_agg(e.error_code, ', ')
+ FROM photo_errors e WHERE e.photo_id = p.id),
+ ''
+ ) AS chyby
+ FROM zaloha_obrazku z
+ JOIN photos p ON p.zaloha_id = z.id
+ WHERE z.id = ANY(%s)
+"""
+
+
+def fetch_rows(ids: list[int]) -> list:
+ conn = psycopg2.connect(**DB)
+ cur = conn.cursor()
+ cur.execute(QUERY, (ids,))
+ rows = cur.fetchall()
+ conn.close()
+ # zachovej původní pořadí ID
+ order = {rid: i for i, rid in enumerate(ids)}
+ return sorted(rows, key=lambda r: order.get(r[0], 9999))
+
+
+# ── Thumbnail ─────────────────────────────────────────────────────────────────
+
+def make_thumb_b64(win_path: Path, size: int) -> str | None:
+ try:
+ img = Image.open(win_path)
+ img.thumbnail((size, size), Image.LANCZOS)
+ if img.mode not in ("RGB", "L"):
+ img = img.convert("RGB")
+ buf = io.BytesIO()
+ img.save(buf, format="JPEG", quality=82)
+ return base64.b64encode(buf.getvalue()).decode()
+ except Exception:
+ return None
+
+
+# ── HTML ──────────────────────────────────────────────────────────────────────
+
+def format_size(n) -> str:
+ if n is None:
+ return "?"
+ for unit in ("B", "kB", "MB", "GB"):
+ if n < 1024:
+ return f"{n:.0f} {unit}"
+ n /= 1024
+ return f"{n:.1f} GB"
+
+
+def render_html(rows: list, ids: list[int], thumb_size: int) -> str:
+ cards = []
+ ok = 0
+
+ for i, row in enumerate(rows, 1):
+ (rid, cesta, velikost, taken_at, taken_src,
+ make_, model, width, height, mp, lat, lon, chyby) = row
+
+ win_path = linux_to_win(cesta)
+ b64 = make_thumb_b64(win_path, thumb_size)
+
+ if b64:
+ ok += 1
+ img_tag = f'
'
+ card_cls = "card"
+ else:
+ img_tag = '
⚠ soubor nedostupný
zkontroluj přístup na Tower1
'
+ card_cls = "card broken"
+
+ camera = " ".join(filter(None, [make_, model])) or "neznámá"
+ date_str = taken_at.strftime("%Y-%m-%d %H:%M") if taken_at else "?"
+ res = f"{width}×{height}" if width and height else "?"
+ mp_str = f"{mp:.1f} Mpx" if mp else ""
+ gps_link = (
+ f'📍 {lat:.4f}, {lon:.4f}'
+ if lat and lon else ""
+ )
+ chyby_html = f'{chyby}
' if chyby else ""
+ path_short = str(win_path).replace(NAS_WIN_UNC, "…\\")
+
+ cards.append(f"""
+ """)
+
+ missing_ids = set(ids) - {r[0] for r in rows}
+ missing_note = ""
+ if missing_ids:
+ missing_note = f'ID nenalezena v DB: {sorted(missing_ids)}
'
+
+ return f"""
+
+
+
+Preview fotek ({len(rows)})
+
+
+
+Preview fotek
+
+ Celkem ID: {len(ids)} ·
+ Načteno z DB: {len(rows)} ·
+ Obrázky dostupné: {ok}/{len(rows)}
+
+{missing_note}
+
+{"".join(cards)}
+
+
+"""
+
+
+# ── Main ──────────────────────────────────────────────────────────────────────
+
+def main():
+ parser = argparse.ArgumentParser(description="Preview fotek podle ID z DB")
+ parser.add_argument("ids", nargs="*", type=int, help="ID fotek (zaloha_obrazku.id)")
+ parser.add_argument("--file", "-f", help="Soubor s ID (jedno na řádek)")
+ parser.add_argument("--thumb", "-t", type=int, default=320, help="Velikost thumbnailů v px (default 320)")
+ args = parser.parse_args()
+
+ ids = list(args.ids)
+
+ if args.file:
+ with open(args.file) as fh:
+ for line in fh:
+ line = line.strip()
+ if line and line.isdigit():
+ ids.append(int(line))
+
+ if not ids:
+ print("Žádná ID. Zadej je jako argumenty nebo přes --file.", file=sys.stderr)
+ sys.exit(1)
+
+ print(f"Načítám {len(ids)} fotek z DB…")
+ rows = fetch_rows(ids)
+ print(f"Generuji thumbnaily (--thumb {args.thumb}px)…")
+ html = render_html(rows, ids, args.thumb)
+
+ tmp = tempfile.NamedTemporaryFile(
+ suffix=".html", delete=False, mode="w", encoding="utf-8"
+ )
+ tmp.write(html)
+ tmp.close()
+ print(f"Otevírám: {tmp.name}")
+ webbrowser.open(f"file:///{tmp.name}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/00 PictureCollector/update_wanted.py b/00 PictureCollector/update_wanted.py
new file mode 100644
index 0000000..cd268fa
--- /dev/null
+++ b/00 PictureCollector/update_wanted.py
@@ -0,0 +1,136 @@
+"""
+update_wanted.py — označuje fotky wanted=TRUE a nastavuje category.
+Spusť a uprav seznam pravidel podle potřeby.
+"""
+import psycopg2
+
+DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
+ password="Vlado7309208104++", database="fotky_buzalkovi")
+
+conn = psycopg2.connect(**DB)
+conn.autocommit = True
+cur = conn.cursor()
+
+rules = [
+ # (popis, SQL WHERE, category)
+ # ── Samsung ──────────────────────────────────────────────────────────────
+ (
+ "Samsung SM-G935F / A520F / A525F / J500FN / I8190N / S7560 / S5230",
+ "camera_make IN ('samsung', 'SAMSUNG', 'Samsung') AND camera_model IN "
+ "('SM-G935F','SM-A520F','SM-A525F','SM-J500FN','GT-I8190N','GT-S7560','GT-S5230')",
+ "Rodina",
+ ),
+ # ── Profesionální / cizí ─────────────────────────────────────────────────
+ (
+ "Nikon D5 / D4 / D700 / D300 / D800 / D500",
+ "camera_make = 'NIKON CORPORATION' AND camera_model IN "
+ "('NIKON D5','NIKON D4','NIKON D700','NIKON D300','NIKON D800','NIKON D500')",
+ "Rodina",
+ ),
+ (
+ "Canon EOS-1D X III / 450D / 600D / R6 / 5D IV / 5D / 40D / 20D",
+ "camera_make = 'Canon' AND camera_model IN "
+ "('Canon EOS-1D X Mark III','Canon EOS 450D','Canon EOS 600D','Canon EOS R6',"
+ "'Canon EOS 5D Mark IV','Canon EOS 5D','Canon EOS 40D','Canon EOS 20D')",
+ "Rodina",
+ ),
+ (
+ "Sony ILCE-1 (Alpha 1)",
+ "camera_make = 'SONY' AND camera_model = 'ILCE-1'",
+ "Rodina",
+ ),
+ (
+ "Panasonic DC-S5",
+ "camera_make = 'Panasonic' AND camera_model = 'DC-S5'",
+ "Rodina",
+ ),
+ # ── Ostatní kompakty ─────────────────────────────────────────────────────
+ (
+ "Panasonic DMC-FZ5",
+ "camera_make = 'Panasonic' AND camera_model = 'DMC-FZ5'",
+ "Rodina",
+ ),
+ (
+ "Canon PowerShot S40",
+ "camera_make = 'Canon' AND camera_model = 'Canon PowerShot S40'",
+ "Rodina",
+ ),
+ (
+ "Canon PowerShot A40",
+ "camera_make = 'Canon' AND camera_model = 'Canon PowerShot A40'",
+ "Rodina",
+ ),
+ (
+ "Panasonic DMC-FX33",
+ "camera_make = 'Panasonic' AND camera_model = 'DMC-FX33'",
+ "Rodina",
+ ),
+ (
+ "NIKON D80",
+ "camera_make = 'NIKON CORPORATION' AND camera_model = 'NIKON D80'",
+ "Rodina",
+ ),
+ (
+ "GoPro HERO6 Black",
+ "camera_make = 'GoPro' AND camera_model = 'HERO6 Black'",
+ "Rodina",
+ ),
+ (
+ "Microsoft Lumia 930",
+ "camera_make = 'Microsoft' AND camera_model = 'Lumia 930'",
+ "Rodina",
+ ),
+ (
+ "Panasonic DMC-FX3",
+ "camera_make = 'Panasonic' AND camera_model = 'DMC-FX3'",
+ "Rodina",
+ ),
+ (
+ "Sony DSC-T77",
+ "camera_make = 'SONY' AND camera_model = 'DSC-T77'",
+ "Rodina",
+ ),
+ (
+ "Olympus C120/D380",
+ "camera_make = 'OLYMPUS OPTICAL CO.,LTD' AND camera_model = 'C120,D380'",
+ "Rodina",
+ ),
+ (
+ "Sony DSC-WX50",
+ "camera_make = 'SONY' AND camera_model = 'DSC-WX50'",
+ "Rodina",
+ ),
+ (
+ "Sony CYBERSHOT",
+ "camera_make = 'SONY' AND camera_model = 'CYBERSHOT'",
+ "Rodina",
+ ),
+ (
+ "Casio EX-Z6",
+ "camera_make = 'CASIO COMPUTER CO.,LTD.' AND camera_model = 'EX-Z6'",
+ "Rodina",
+ ),
+ (
+ "Olympus C765UZ",
+ "camera_make = 'OLYMPUS CORPORATION' AND camera_model = 'C765UZ'",
+ "Rodina",
+ ),
+ (
+ "Nikon E990",
+ "camera_make = 'NIKON' AND camera_model = 'E990'",
+ "Rodina",
+ ),
+]
+
+for popis, where, category in rules:
+ cur.execute(f"""
+ UPDATE photos
+ SET wanted = TRUE,
+ category = %s
+ WHERE {where}
+ AND (wanted = FALSE OR category IS DISTINCT FROM %s)
+ """, (category, category))
+ print(f"{popis}: {cur.rowcount:,} řádků → wanted=TRUE, category='{category}'")
+
+conn.close()
+print("Hotovo.")
diff --git a/00 PictureCollector/update_wanted_batch2.py b/00 PictureCollector/update_wanted_batch2.py
new file mode 100644
index 0000000..bcbb793
--- /dev/null
+++ b/00 PictureCollector/update_wanted_batch2.py
@@ -0,0 +1,58 @@
+"""update_wanted_batch2.py — označí další vlnu foťáků wanted=TRUE, category=Rodina"""
+import psycopg2
+
+DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
+ password="Vlado7309208104++", database="fotky_buzalkovi")
+
+# (make, model)
+CAMERAS = [
+ ("Canon", "Canon DIGITAL IXUS 30"),
+ ("Xiaomi", "2201117TY"),
+ ("SONY", "DSC-H70"),
+ ("Panasonic", "DMC-FZ20"),
+ ("Panasonic", "DMC-FZ50"),
+ ("Panasonic", "NV-GS180"),
+ ("EASTMAN KODAK COMPANY", "KODAK C875 ZOOM DIGITAL CAMERA"),
+ ("SONY", "D2005"),
+ ("OLYMPUS IMAGING CORP.", "XZ-10"),
+ ("OLYMPUS CORPORATION", "C460ZdelSol"),
+ ("Canon", "Canon EOS DIGITAL REBEL"),
+ ("HTC", "HTC HD2 T8585"),
+ ("OLYMPUS OPTICAL CO.,LTD", "C4040Z"),
+ ("NIKON", "E950"),
+ ("NIKON", "E5400"),
+ ("Canon", "Canon PowerShot S300"),
+ ("Emgeton", "Flexaret Mini"),
+ ("SONY", "DCR-PC115E"),
+ ("Canon", "Canon PowerShot A720 IS"),
+ ("SONY", "HDR-TG3E"),
+ ("Canon", "Canon PowerShot A75"),
+ ("OLYMPUS OPTICAL CO.,LTD", "C3030Z"),
+ ("FUJIFILM", "FinePix F20"),
+ ("vivo", "vivo X100 Pro"),
+ ("NIKON", "E3200"),
+ ("Canon", "Canon PowerShot S3 IS"),
+ ("Canon", "Canon PowerShot G3"),
+ ("OLYMPUS OPTICAL CO.,LTD", "X-2,C-50Z"),
+ ("Canon", "Canon PowerShot S2 IS"),
+ ("Canon", "Canon PowerShot S500"),
+ ("Canon", "Canon DIGITAL IXUS 400"),
+ ("OLYMPUS OPTICAL CO.,LTD", "C5050Z"),
+]
+
+conn = psycopg2.connect(**DB)
+conn.autocommit = True
+cur = conn.cursor()
+
+placeholders = ",".join(["(%s,%s)"] * len(CAMERAS))
+params = [v for pair in CAMERAS for v in pair]
+cur.execute(f"""
+ UPDATE photos
+ SET wanted = TRUE,
+ category = 'Rodina'
+ WHERE (camera_make, camera_model) IN ({placeholders})
+ AND wanted = FALSE
+""", params)
+
+print(f"Označeno: {cur.rowcount:,} řádků → wanted=TRUE, category='Rodina'")
+conn.close()
diff --git a/POSTUP.md b/POSTUP.md
new file mode 100644
index 0000000..75c9bf3
--- /dev/null
+++ b/POSTUP.md
@@ -0,0 +1,100 @@
+# FotkyBuzalkovi — pracovní deník
+
+> Živý dokument. Zapisujeme sem co jsme zjistili, co jsme rozhodli a co je na řadě.
+> Technická reference → `CONTEXT.md`, návrh architektury → `NAVRH.md`.
+
+---
+
+## Session 2026-06-04
+
+### Stav DB na začátku
+
+| Tabulka | Řádky |
+|---------|-------|
+| `zaloha_obrazku` | 1 717 182 |
+| `zdrojove_soubory` | 3 573 846 |
+| `photos` | 1 717 175 |
+| `photo_errors` | 3 185 319 |
+
+Všechny fotky mají `processing_status = 'pending'` — pipeline doběhla, další zpracování nezačalo.
+
+### Co jsme zjistili
+
+**Problém 1 — V záloze je spousta odpadu, ne jen rodinné fotky.**
+Pipeline sebrala vše co má příponu `.jpg/.jpeg` — včetně:
+- PhotoPrism cache thumbnailů (`appdata/photoprism/cache/`) — 229 521 ks, <10 kB
+- Plex / Immich cache
+- MP3 a LP obaly
+- DVD obaly, eBook obálky
+- ABC vystřihovánky (skenované na 1200 DPI → soubory 60–100 MB)
+- Reprodukce obrazů z torrentů (Raffael, Rembrandt... v muzejní kvalitě)
+- Stažené obrázky z webu (Dropbox/!!!Days/Stefajir/...)
+- Windows AppData (Kindle covers, .NET watermark...)
+
+**Problém 2 — Rok pořízení je hodně porušený.**
+- Rok 2024 má 985 754 fotek (>57 % všech) — zřejmě chybný fallback na mtime místo EXIF
+- Rok 1863, 2031–4501 — garbage v EXIF
+- Rok 2026 má 93 492 — suspektní
+
+**Sloupec `wanted`:**
+Přidán `photos.wanted BOOLEAN NOT NULL DEFAULT FALSE` — všech 1 717 175 fotek má FALSE.
+Účel: budeme označovat fotky které chceme zachovat / zpracovat.
+
+### Nástroje
+
+- `00 PictureCollector/preview_sample.py` — zobrazí náhled fotek podle ID
+ Použití: `python preview_sample.py 101 202 303 ...`
+ Claude vybere ID přes MCP dotazy, předá příkaz ke spuštění.
+
+- `00 PictureCollector/migrate_add_wanted.py` — přidal sloupec `wanted` (idempotentní)
+
+### Rozhodnutí
+
+#### Pravidla vyloučení cest — část 1 (2026-06-04)
+
+Tyto cesty **nechceme** — `wanted` zůstane FALSE, nezpracovávat:
+
+| Vzor cesty (obsahuje) | Důvod | Počet |
+|---|---|---|
+| `Torrents/Downloads/OOPS!!! International` | porno screenshoty | ~7 105 |
+| `Torrents/Downloads/Tampons Pads Period` | porno | ~9 600 |
+| `#ColdData/Porno/` | porno screenlists | — |
+| `Porno1/` | porno | ~2 730 |
+| `#ColdData/000 TORENT OBRAZKY/National Geographic Wallpapers` | stažené wallpapery | ~7 188 |
+| `#ColdData/000 TORENT OBRAZKY/[OnlyFans]` | OnlyFans | ~1 377 |
+| `#ColdData/000 TORENT OBRAZKY/Great Painters` | reprodukce obrazů | — |
+| `UltraCC/` a obsahuje `/jpg` | Hot Wheels katalog a jiné torrent obrázky | ~3 484 |
+| `Magentic/Runtime/UserPhotos/css` | webové ikonky | ~1 034 |
+| `.Icecream Ebook Reader/` | obrázky z epub knih | — |
+| `photoprism/sidecar/` | XMP sidecar soubory | — |
+
+> Otevřené: appdata/photoprism/cache, Immich thumbs, MP3/LP obaly, eBooks — vyřeší se v další části pravidel.
+
+### Schéma — nové sloupce v `photos`
+
+| Sloupec | Typ | Popis |
+|---|---|---|
+| `wanted` | `BOOLEAN NOT NULL DEFAULT FALSE` | chceme tuto fotku zachovat/zpracovat |
+| `category` | `VARCHAR(100)` | kategorie: Fotopast, Rodina, Skeny, … |
+
+### Označené kategorie
+
+| Kamera / kritérium | wanted | category | Počet |
+|---|---|---|---|
+| BolyMedia SG520 | TRUE | Fotopast | 42 688 |
+
+### Na řadě
+
+- [ ] Prozkoumat co přesně je v záloze — jaký podíl jsou skutečné rodinné fotky
+- [ ] Rozhodnout jak filtrovat odpad (path blacklist? size? absence kamery?)
+- [ ] Vyřešit problém s roky — proč 57 % fotek padá do 2024
+- [ ] Označit první várku fotek jako `wanted = TRUE`
+
+---
+
+## Backlog otevřených otázek
+
+1. Co s "sirotky" bez EXIF — `mtime` / odmítnout / označit?
+2. Při shodě `sha256_pixels` — přeskočit / sloučit metadata / uložit oba?
+3. Storage layout — nechat in-place / `archiv/YYYY/MM/` / content-addressable?
+4. Jak poznat "rodinná fotka" od odpadu bez ruční kontroly?