Skip to content

CzechAI Reality Data Logic — Kanonická pravidla

Zdroj pravdy pro MASTER_LIVE.db. Všechny služby (scraper, pricer, hunter, dashboard, reporty) se drží těchto pravidel. Při rozporu mezi kódem a tímto dokumentem → kód je bug, ne dokument.

Verze: 1.0 Datum: 2026-04-05 DB: /opt/reality-pipeline/data/MASTER_LIVE.db (server 179) Udržuje: data_refiner.py (běží on-demand, obnovuje refined_* sloupce)


1. Kanonické hodnoty sloupců

1.1 property_type (vždy lowercase)

Hodnota Popis Aliases přijaté refinerem
byt Byt v osobním, družstevním, družstevním nebo společném vlastnictví BYT, byt
dum Rodinný dům, vila, chata, chalupa DUM, dum, DOM
pozemek Stavební, zemědělský, lesní, zahrada (>100 m² plochy) POZEMEK, pozemek, POZEMK, POZ
komercni Kancelář, obchod, restaurace, sklad, výrobna KOMERCNI, KOMERCNI-NEMOVITOST, komercni
garaz Garáž, garážové stání, malý objekt GARAZ, MALY-OBJEKT-NEBO-GARAZ
ostatni Chatka, mobilheim, modulární, nezařaditelné OSTATNI, MODULARNI, UNK, unknown, reality

Pokud prázdné: refiner se pokusí odvodit z titulku/popisu (fn infer_ptype_from_title). Když se nepodaří → zůstane prázdné → automaticky padá do refined_status='junk'.

1.2 transaction_type (vždy lowercase)

Hodnota Popis
prodej Prodej nemovitosti (price_asking = kupní cena)
pronajem Nájem (price_asking = měsíční nájem v Kč)
drazba Dražba, exekutorská aukce, výběrové řízení (cena = vyvolávací)

Aliases: PRODEJ/prodej, PRONAJEM/pronajem, DRAZBA/drazba — refiner mapuje na lowercase.

1.3 refined_status (nové, povinné)

Hodnota Význam Hunter používá?
ok Záznam prošel všemi kontrolami bez jediné úpravy ✅ ano
fixed Refiner opravil ≥1 field (typ normalized, amenity extracted, city trimmed…) ✅ ano
suspect Záznam má podezřelou hodnotu (extrémní plocha, extrémní cena) — neumazáno, ale flag ⚠️ jen když uživatel výslovně povolí
junk Nepoužitelné (prázdné everything, no_price_no_area, area_too_small) nikdy
duplicate Deduplikace: existuje jiný záznam se stejným title+area+price+city a novějším updated_at nikdy

Hunter filter ze zákona: sql WHERE refined_status IS NULL OR refined_status NOT IN ('junk', 'duplicate') (NULL znamená "ještě nebyl refined" — toleruje se, ale refiner by měl proběhnout pravidelně.)

1.4 refined_flags (JSON array)

Seznam tagů co refiner udělal. Pomáhá při debuggingu a auditingu.

Příklady tagů: - pt_normalized — property_type normalizován (BYT → byt) - pt_inferred — property_type odvozen z titulku (např. "prodej bytu 3+kk" → byt) - tt_normalized — transaction_type normalizován - city_trimmed — město mělo mezery/case problém - rooms_extracted — rooms extrahovány regexem z titulku/popisu - floor_extracted — podlaží extrahováno - year_extracted — rok výstavby extrahován - amenities_extracted — alespoň 1 amenita (výtah, balkon, garáž…) detekována - novostavba — slovo "novostavba" v textu - auction_detected_text — nalezen auction keyword ("dražba", "exekuce", "insolvence") - reclassified_as_rent — byl prodej ale price < 500k → přeznačen na pronajem - area_divide10 — area fixed (1125 m² → 112.5 m²) - area_too_large / area_too_small_byt / area_too_small_dum — suspect areas - is_active_null_fixed — is_active bylo NULL, nastaveno na 0 - empty_everything — žádný title, žádný desc, žádný image → junk - no_price_no_area → junk


2. Validační rozsahy (per property_type)

2.1 Plocha (area, m²)

Type Min Max Poznámka
byt 15 400 Nad 1000 → refiner dělí 10 (digit bug fix). 400-1000 → suspect.
dum 30 1500 Nad 5000 → refiner dělí 10.
pozemek 100 50,000 (Pozemky mají široký rozsah)
komercni 15 5,000
garaz 5 50

2.2 Cena (price_asking, Kč)

Prodej: | Type | Min | Max | Akce pokud mimo | |---|---:|---:|---| | byt | 500,000 | 100,000,000 | <500k → reclassify jako pronajem. >200M → suspect. | | dum | 800,000 | 200,000,000 | — | | pozemek | 50,000 | 100,000,000 | — | | komercni | 500,000 | 500,000,000 | — |

Pronájem: | Type | Min | Max (Kč/měsíc) | |---|---:|---:| | byt | 5,000 | 200,000 | | dum | 10,000 | 500,000 | | komercni | 3,000 | 1,000,000 |

Nula nebo záporná cena: pokud i area = 0 → junk. Jinak suspect.


3. Data Quality Pipeline (pořadí operací)

┌───────────────────────────────────────────────────────┐ │ 1. SCRAPER (134, various PM2 jobs) │ │ → raw rows → MASTER_LIVE.db │ │ Zodpovědnost: získat title, url, price, area, │ │ city, property_type, image_url, description │ ├───────────────────────────────────────────────────────┤ │ 2. DATA_WATCHDOG (179, PM2 #148) │ │ Každých 5 min: pokud >1000 unpriced → spouští │ │ mass_price_v2.py (s BATCH TRANSAKCEMI!) │ ├───────────────────────────────────────────────────────┤ │ 3. MASS_PRICE_V2 (triggered by watchdog) │ │ → V15f API → fills market_price, price_se2 │ │ KRITICKÉ: BEGIN/COMMIT per 500 rows, NIKDY │ │ isolation_level=None │ ├───────────────────────────────────────────────────────┤ │ 4. DATA_REFINER (on-demand, ~25s) │ │ → normalizes types, extracts rooms/floor/year, │ │ sets refined_status, refined_flags │ │ Spouštění: ručně nebo z cronu (1×/den doporučeno) │ ├───────────────────────────────────────────────────────┤ │ 5. HUNTER (179, PM2 #149 + #151 daemon) │ │ → čte JEN refined_status IN (NULL, 'ok', 'fixed') │ │ → V15f valuace → Claude Sonnet 4.6 verdikt │ │ → link validation → report HTML │ └───────────────────────────────────────────────────────┘

Pravidla konkurence: - Hunter čte only, nikdy neaktualizuje reality_master - Scraper píše přes vlastní connection s WAL + BEGIN/COMMIT - Data-refiner získá exkluzivní lock na DB přes batch transakce - Nikdo nepoužívá isolation_level=None (autocommit mode) — způsobuje corruption


4. Extrakční regexy (data_refiner.py)

4.1 Rooms (místnosti)

regex \b(?:(\d+)\s*\+\s*(kk|KK|1|2))\b|\b(garsoni[eé]ra|garsonka|atelier)\b Formát výstupu: 1+kk, 2+kk, 3+1, 4+1, 1+kk (garsonka), atelier

4.2 Floor (podlaží)

regex \b(p[řr][íi]zem[íi]|(\d+)\s*\.\s*(?:patro|NP|podla[žz][íi])|mezonet|sklep|suter[éen]n|podkrov[íi])\b Výstup: 0 (přízemí), -1 (sklep/suterén), 1..N, mezonet, podkroví

4.3 Year (rok výstavby)

regex \b(1[89]\d{2}|20[0-3]\d)\b Vezme nejvyšší rok v rozmezí 1850-2035.

4.4 Amenity keywords (case-insensitive substring)

Flag Keywords
elevator výtah, vytah
balcony balkon, balkón
terrace terasa, teras
cellar sklep, sklad
parking parkován, parkovan, parkovací, parkoviste
garage garáž, garaz
garden zahrada, zahrádk
mezonet mezonet

4.5 Auction signals

dražba, drazba, výběrové řízení, vyberove rizeni, insolven, exekuc, konkurs, nucený prodej → Nastaví is_auction = 1 + flag auction_detected_text.


5. Deduplication rules

Primární klíč: url (UNIQUE constraint v DB).

Sekundární fingerprint: LOWER(title) | area | price_asking | LOWER(city) — pokud existují 2+ záznamy se stejným fingerprintem, ponecháme ten s nejnovějším updated_at (ostatní dostanou refined_status='duplicate' a is_active=0).

Proč NE hash celého řádku: scraper může znovu naskenovat stejný inzerát s mírně jinými fields (title přepis, rooms přidané) a vytvořit technicky jiný hash, ale sémanticky stejný inzerát.


6. Hunter Agent filter (ZÁVAZNÉ)

Každá hunter query MUSÍ obsahovat tyto podmínky:

sql WHERE is_active = 1 AND transaction_type = 'prodej' -- canonical lowercase AND (is_auction = 0 OR is_auction IS NULL) AND (refined_status IS NULL OR refined_status NOT IN ('junk', 'duplicate')) AND price_asking > 0 AND area > 0 AND LENGTH(COALESCE(title, '')) >= 10 AND url NOT LIKE '%bazos%' -- low-quality portal

Plus per-type area limits (viz sekce 2.1).

Pro property_types filter použít IN list s kanonickými lowercase hodnotami: sql AND property_type IN ('byt', 'dum', 'pozemek', 'komercni')


7. Monitoring & SLA

7.1 Data quality metriky (aim)

Metrika Cíl Aktuální (2026-04-05)
% refined_status = junk < 20 % 34 % ⚠️
% prázdný description < 50 % 94 % 🚨
% prázdný city < 5 % 30 % ⚠️
% prázdný image_url < 20 % 34 % ⚠️
% prázdný gps_lat < 30 % 52 % ⚠️
Duplicity (fingerprint) < 1 % 1.4 % ✅

Akce při překročení: zpátky do scraperu, zjistit proč chybí data (regresní bug).

7.2 Alerting

  • data-watchdog má pravidelný loop, mohl by reportovat quality metriky do Prometheus/Telegram.
  • hunter-daemon logy summary každý cyklus (už existuje).

8. Zakázáno 🚫

  1. Nikdy nepoužívej isolation_level=None na MASTER_LIVE.db — způsobuje corruption při concurrent WAL access. Vždy BEGIN/COMMIT batche.
  2. Nikdy nedělej DELETE FROM reality_master — místo toho is_active = 0 + refined_status='duplicate'/'junk'.
  3. Nikdy nepiš do DB z hunter-agent — hunter je read-only.
  4. Nikdy neinvaliduj refined_status bez spuštění refineru znovu.
  5. Nikdy nemíchej property_type uppercase s lowercase — kanonická forma je lowercase only.
  6. Nikdy nepřidávej nové property_type hodnoty bez aktualizace tohoto dokumentu a PTYPE_MAP v data_refiner.py.
  7. Nikdy nefiltruj pouze is_active=1 bez také refined_status NOT IN ('junk','duplicate') — junk řádky mohou mít is_active=1 z legacy stavu.

9. Kde co leží

Komponent Cesta Server
DB /opt/reality-pipeline/data/MASTER_LIVE.db 179
Scraper /opt/reality-scraper-next/NOVY_SCRAPER_KURVA/ + /opt/reality-pipeline/ 134
Pricer /opt/reality-pipeline/mass_price_v2.py (fixed 2026-04-05) 179
Watchdog /opt/reality-pipeline/data_watchdog.py (PM2 #148) 179
Refiner /opt/hunter-agent/data_refiner.py (on-demand) 179
Hunter API /opt/hunter-agent/{hunter,pipeline,llm_router,valuator,enrichers,deep_enrichers}.py 179
Hunter daemon /opt/hunter-agent/daemon.py (PM2 #151) 179
Dashboard /opt/hunter-agent/dashboard.html 179
DATA_LOGIC.md /opt/hunter-agent/DATA_LOGIC.md (tento dokument) 179

10. Jak spustit refiner (cheatsheet)

```bash

Stop writers

ssh root@46.224.121.179 "pm2 stop hunter-agent hunter-daemon data-watchdog && pkill -9 -f mass_price_v2"

Backup

ssh root@46.224.121.179 "cp /opt/reality-pipeline/data/MASTER_LIVE.db /opt/reality-pipeline/data/MASTER_LIVE.db.pre_refine_$(date +%Y%m%d)"

Run refiner (~25s)

ssh root@46.224.121.179 "cd /opt/hunter-agent && python3 data_refiner.py"

Restart writers

ssh root@46.224.121.179 "pm2 start hunter-agent hunter-daemon data-watchdog" ```

Cron doporučený: každou noc v 3:00 (po scraper cyklu): cron 0 3 * * * root cd /opt/hunter-agent && python3 data_refiner.py >> /var/log/data_refiner.log 2>&1


11. Changelog

  • 2026-04-05 v1.0 — First version. Created after massive data audit revealed:
  • 94% missing description
  • 33k missing property_type
  • 5.6k rentals mislabeled as sales
  • 713 bytů with area > 1000 m²
  • 3675 duplicates
  • MASTER_LIVE.db corrupted twice by mass_price_v2.py (autocommit bug)
  • Fixed: added refined_status, refined_flags, canonical property_type/transaction_type, regex extractors, dedup, proper transactions in pricer.