# HANDOFF — Sport Scraping Pipeline za PGŽ **Datum:** 03.05.2026 14:10 | **Verzija:** 8.0 | **Autor:** Damir + Claude --- ## 0. TL;DR za novu sesiju Damir hoće **brutalno efikasan multi-savez scraping pipeline** za PGŽ: - **Sve klubove** (~525) iz **svih saveza** (HVS, HNS, HRS, HKS, HOS, HBS, HŠS, HJS, HJK, HAS, HKaratS, HTS, HSTS, HSA, HOO...) - **Sve osobe** sa svim ulogama (igrač, predsjednik, tajnik, trener, fizioterapeut, liječnik, team manager...) - **Identifikator** za svaku osobu (OIB, ili `external_id` ako nema) - **Inteligentno dedup** — bez 5x istog kluba u varijantama imena - **Postojeća arhitektura** — vLLM Qwen 7B + BGE-M3 embedder (NE pokretat dodatne GPU stvari) **3-strike rule aktivan**. Damir hvata **lažiranje** = manual mode. --- ## 1. STACK — što JE i što NIJE ### ✅ Active i radi | Servis | Port | Kako koristiti | |--------|------|---------------| | **vLLM Qwen 7B AWQ** | 8001 | `POST /v1/chat/completions` OpenAI-kompatibilan, max_tokens 8192, **GPU 18.2/20.5GB PUN** | | **BGE-M3 embedder** | 9879 | `POST /api/embeddings` body `{"model":"bge-m3","input":["text1","text2"]}` (LIST!) | | **Qdrant** | 6333 | 30+ collections: `pgz_universe`, `pgz_sport_v1` (32K chunks), `entities_v2`, `udruge_v2` | | **PgBouncer** | 6432 | DB `rinet_v3`, user `rinet`, pwd `R1net2026!SecureDB#v7` | | **Bridge API** | 9877 | `POST /bridge/exec` X-API-KEY rinet-yS4ZnKlwUqsjk | ### ❌ NE koristi (broken/full) - **F10 LoRA :8765** — server živ ali endpoint vraća 404, NE OpenAI-kompatibilan, ostavi - **Ollama :11434** — `panic: $HOME is not defined` (env problem) - **Anthropic API** — Damirov $$, koristi za final compose, NE za batch ekstrakciju ### Pravila pri korištenju vLLM - **GPU je već PUN** (Qwen 7B 18.2/20.5GB) - Ne pokretat 2. LLM proces ili novi embed batch - Qwen 7B context 8192 tokena — chunk text na ~5500 znakova (1.5K tokena) - `response_format: {type: "json_object"}` radi - **Throughput**: ~10 paralelnih requesta sa 30s avg = ~50K req/sat --- ## 2. KLJUČNI BREAKTHROUGH — HVS JWT TOKEN Damir dao primjere `https://hvs.hr/igrac/6893/` i `/igrac/43303/`. Network sniff otkrio: ``` POST https://rezultati.hvs.hr/api/?rubrika={rubrika}&id={id} Headers: Content-Type: application/x-www-form-urlencoded Origin: https://hvs.hr Referer: https://hvs.hr/ Body: token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJIVlMiLCJpYXQiOjE1Njg3MjI1MzEsImV4cCI6MTY1NTM0NTIyMywiYXVkIjoiSFZTIiwic3ViIjoiSFZTIiwiR2l2ZW5OYW1lIjoiSFZTIn0.iteM3Hewl0ugQVEDqPdg_7hHwRTxnSeFVg59vPH25uU ``` **Rubrike** koje rade: - `person&id={id}` → `{data: {fname, lname, club, date, position, image, country, games, goals, ...}, players3: {...suigrači}}` - `team&id={id}` → `{info: {name, gender, category, logo}, utakmice: [...], stat: {A,B}}` - `persongames&limit=10&id={id}` → utakmice osobe - `sticky&id={id}` → highlights **Performans**: 50K player ID-eva u ~50s (parallel=20). 795 PGZ vaterpolo osoba inserted. **Implementacija**: `/tmp/hvs_mass.py` (saved). Replicirati pattern za druge saveze. **Ovaj pattern treba pokušati za druge saveze**: - `rezultati.hbs.hr` (boćanje) - `rezultati.hks-cbf.hr` (košarka) - `rezultati.hrs.hr` (rukomet — ali ima Zoraxy gateway = blokirano) - `rezultati.hjs.hr` (jedrenje) - `rezultati.has.hr` (atletika) - itd. --- ## 3. DAMIROV SEED LIST (najvažnije!) ### 3.1 Krovni / županijski savezi ``` Zajednica sportova PGŽ: https://sport-pgz.hr/ (scraped, 18 godišnjaka downloaded) Sportska zajednica Rijeke: http://rss.hr/ Nogometni savez PGŽ: https://www.nspgz.hr/ Šahovski savez PGŽ: https://www.sah-pgz.hr/ ← KLUB LIST: /klubovi/ Pikado savez PGŽ: http://www.pikado-pgz.hr Parasport Rijeka: https://www.ssoi-rijeka.hr/ Parasport PGŽ: https://ssoi-pgz.hr/ Školski sport PGŽ: http://www.savezssd-pgz.hr/ ``` ### 3.2 Nacionalni savezi ``` HOO: https://www.hoo.hr/ ← scraped 478 PDF, 1447 docs in pgz_sport HNS: https://hns.family/ ← /hr/sportski-djelatnici/ ima registar HRS: https://hrs.hr/ ← api.hrs.hr blokiran Zoraxy gateway HKS-CBF: https://hks-cbf.hr/ ← natjecanja.hks-cbf.hr ima statistike HVS: https://hvs.hr/ ← API token gore ✓ DONE HŠS: https://hrvatski-sahovski-savez.hr/ HSSRM: https://www.hssrm.hr/ (sport ribolov na moru) ``` ### 3.3 PGŽ klubovi s URL-om (Damir potvrdio!) ``` NOGOMET: HNK Rijeka: https://nk-rijeka.hr/ (već u DB) HNK Orijent 1919: https://nk-orijent.hr/ (već u DB ima drugi URL) NK Krk 1940: https://nkkrk.hr/ (već u DB ima drugi) NK Opatija: https://nkopatija.hr/ (DB ima Wiki URL — fix) NK Grobničan: https://nk-grobnican.hr/ (NEW) NK Pomorac: https://nk-pomorac.hr/ (NEW) NK Naprijed Hreljin: https://nk-naprijed.hr/ (NEW) NK Turbina Tribalj: http://www.nk-turbina-tribalj.hr/ (NEW) NK Klana: https://nk-klana.hr/ (updated) NK Mune: https://nk-mune.hr/ (DB ima Wiki — fix) NK Crikvenica: https://nk-crikvenica.hr/ (DB ima semafor — fix) RUKOMET: RK Kozala: https://rk-kozala.hr/ (NEW) RK Zamet: https://rk-zamet.hr/ (DB ima drugi) ŽRK Zamet: https://zrk-zamet.hr/ (updated) RK Viškovo: https://rk-viskovo.hr/ (updated) RK Omišalj: https://rk-omisalj.hr/ (updated) RK Murvica: https://rk-murvica.hr/ (updated) KOŠARKA: KK Kvarner 2010: https://kkkvarner2010.hr/ (NEW — pazi NIJE AK Kvarner!) KK Škrljevo: https://kk-skrljevo.hr/ (updated) KK Ri-Basket: https://ri-basket.hr/ (NEW) KK Kastav: https://kkkastav.hr/ (updated) KK Kraljevica: https://kk-kraljevica.hr/ (NEW) VATERPOLO: VK Primorje EB: https://vaterpolo-primorje.hr/ ← SLUŽBENI! ŠD Primorje 08: http://www.primorje08.hr (updated) OSTALO: PK Primorje: https://pk-primorje.hr/ (plivanje, NEW) AK Kvarner: https://akkvarner.hr/ (atletika, DB ima krivi Wiki — FIX) HAOK Rijeka: https://haok-rijeka.hr/ (odbojka, updated) ŠAHOVSKI: Lista: https://www.sah-pgz.hr/klubovi/ ← scrape ovo! Najveći: ŠK Rijeka, ŠK Kvarner, ŠK Crikvenica, ŠK Lošinj, ŠK Kastav, ŠK Krk, ŠK Viškovo ``` ### 3.4 Lige PGZ klubova - Nogomet: SuperSport HNL, Prva NL, Druga NL, Treća NL Zapad - Košarka: Favbet Premijer liga, Prva muška liga - Rukomet: Paket24 Premijer, 1. HRL - Vaterpolo: Prvenstvo HR - Šah: Hrvatska šahovska liga --- ## 4. 6-fazni Scraping Pipeline (Damirov plan) ``` LAYER 1: Seed Sources ← Damirov seed list gore LAYER 2: Discovery Engine ← crawler od seed-a, prati linkove "klub", "članice" LAYER 3: Entity Extraction ← LLM (vLLM Qwen 7B), regex + BS4 LAYER 4: Enrichment ← Google query, FB/IG, WHOIS, MX, schema.org LAYER 5: Validation ← HTTP, SSL, MX, SMTP, robots.txt LAYER 6: Graph DB / Dedup ← Neo4j ili `civic.entity_connections` + fuzzy + embeddings ``` ### Stack koji JA koristim (ne Damirov ideal): - **Python 3.12** + **Playwright sync_api** (asyn ne radi - DIS shadow probleme su rješene) - **httpx** (kad sync) ili **urllib.request** (kad lokalno) - **BeautifulSoup4** + **selectolax** za HTML - **Redis :6379** (postoji) za queue ako treba - **vLLM Qwen 7B :8001** za LLM extract - **BGE-M3 :9879** za embeddings ### KRITIČNO: shadow `dis.py` bug — RJEŠENO Bilo: `/tmp/dis.py` (stara skripta) shadowala Python `dis` module u svakoj sesiji koja `cd /tmp`. Crash svih Playwright skripti. **Fixed**: `mv /tmp/dis.py /tmp/dis.py.shadow_DELETED && rm -rf /tmp/__pycache__`. Ako se ponovi — provjeri prvo! --- ## 5. ENTITY DEDUP STRATEGIJA (koju Damir zahtijeva!) **Problem**: u DB imamo `VK Primorje`, `VK Primorja` (typo), `Vaterpolski klub Primorje Erste Bank`, `VK Primorje EB` — sve isti klub. **Rješenje (multi-step)**: 1. **Naive normalizacija**: lowercase, ukloni dijakritike, ukloni "klub|sportski|udruga|hnk|nk|vk|kk|rk", razdvoji riječi 2. **Levenshtein/RapidFuzz**: sa pragom > 85 = isti klub 3. **BGE-M3 cosine similarity**: encode klub naziv + grad + sport, threshold > 0.92 4. **LLM final adjudication**: "Jesu li ovi nazivi isti klub? A: X, B: Y" — Qwen 7B yes/no 5. **Cluster i merge**: ostavi onaj sa najpopunjenijim poljima (web, OIB, predsjednik), prebaci `clanovi.klub_id` na master **Ne pokrenu blanket — Damir je već imao 5 backup tabela**: - `klubovi_premerge_20260503` ← prije današnjih merge-ova - `klubovi_premerge_20260503b` ← prije VK merge-ova (ova sesija) - `klubovi_premerge_20260503c` ← prije POŠK fix-a - `klubovi_dedup_20260502`, `klubovi_dedup_v2_*`, `klubovi_dedup_v3_*` --- ## 6. CURRENT STATE — što JE U DB-u (verified 14:10) ```sql -- pgz_sport.klubovi 525 ukupno, region='PGŽ' aktivni: 503 BEZ web URL-a 19 Wikipedia URL (treba prebaci na pravi) 11 pravi web URL ✓ (HNK Rijeka, NK Orijent, NK Krk, AK Liburnija, RK Zamet, MK Kvarner, AK Liburnija, JK Klub Sušačana...) + 9 updated u ovoj sesiji s pravim URL-om iz seed liste -- pgz_sport.clanovi (1808 ukupno PGZ) 527 vaterpolo igrači (HVS API) 405 boćari (HBS scrape, kategorija fixed) 745 nogomet+ostali (mass enrich, kategorija=NULL ili igrac) 6 uprava+stozer -- pgz_sport.savezi (15 PGŽ aktivnih) 3 vaterpolski (1 merged ✓) + HVS, HBS, HKS, HRS, HOO, HNS, HŠS, HSSRM (insert iz seed) -- pgz_sport.dokumenti (3379 ukupno) 1447 hoo (Playwright scrape) 288 pgz_sport 260 rss_hr 294 savez_hbs 106 savez_hks 73 pravilnik (stari ZS PGŽ) + 18 godišnjaci sport-pgz.hr (NEW) -- Schema extensions (this session) clanovi.external_id TEXT ← za HVS: "hvs:igrac:{id}" clanovi.savez_izvor TEXT ← "HVS","HBS","klub_web","godisnjak" clanovi.profile_url TEXT clanovi.uloga_detalj TEXT ← "trener vrata", "lijevo krilo" clanovi.metadata JSONB + uloga_katalog tablica sa 49 uloga grupiranih u 8 grupa ``` ### Schema problemi za fix u sljedećoj sesiji: ```sql ALTER TABLE pgz_sport.klubovi ADD COLUMN IF NOT EXISTS izvor_unosa TEXT; ALTER TABLE pgz_sport.natjecanja ADD COLUMN IF NOT EXISTS razina_natjecanja TEXT; ALTER TABLE pgz_sport.natjecanja ADD COLUMN IF NOT EXISTS web TEXT; ``` ### Crime list (krivi URL-ovi u DB): - `NK Opatija` (id=3840) → Wiki URL umjesto nkopatija.hr - `NK Mune` (id=2201) → Wiki URL umjesto nk-mune.hr - `NK Crikvenica` (id=2421) → semafor.hns.family umjesto nk-crikvenica.hr - `AK Kvarner` (id=3746) → KK Kvarner Wikipedia (krivo zbog fuzzy match — ovo POTPUNO drugi klub) --- ## 7. ŠTO NE FUNKCIONIRA (proven dead ends) ❌ **DuckDuckGo HTML search** — vraća 0 rezultata, blokira IP. Ne koristi. ❌ **Sudreg subjekti pull** — 100K subjekata po 'tvrtke' endpointu vraća samo 3 sport (loš pristup) ❌ **HRS api.hrs.hr** — Zoraxy reverse proxy = no public endpoint ❌ **HKS hks.hr** — base URL ne odgovara (treba probat hks-cbf.hr) ❌ **Većina saveze /klubovi/ /registar/** URL-ovi — 404 ❌ **WordPress REST `/wp-json/wp/v2/{cpt}`** — postoji samo na HVS, HBS, HRS (Jetpack), HTS, HTriS ✅ **Što JE radilo**: - HVS rezultati API JWT token (mass scrape brutalno brz) - HOO Playwright `/dokumenti/29|30|/page/X` (Damirov scraper, 478 PDF) - HBS direktan scrape — svi klubovi + 405 boćara - sport-pgz.hr Playwright crawl (18 godišnjak PDF) - LLM klub-web enrich preko `/api/v2/enrich/klub-web` --- ## 8. NEXT SESSION TO-DO (priority order) ### A) ENTITY DEDUP (Damir hard-blocking issue) 1. Napraviti `pgz_sport.klub_dedup_engine` — modul s 4-step: - Naive normalize - RapidFuzz match (instalirat: `pip install rapidfuzz`) - BGE-M3 cosine sim (preko `:9879/api/embeddings`) - vLLM yes/no adjudication 2. Run preko 525 PGZ klubova → grupe duplikata 3. Damir review (ne auto-merge!) → merge approved ### B) GODISNJAK LLM EXTRACT (Damir prioritet 1) Skripta `/tmp/godisnjak_llm.py` već napisana: - vLLM primary, Groq fallback - 18 godišnjaka × ~5500 chars chunks ≈ 1500-2000 chunks - Parallel=10 prema vLLM - ⚠️ **GPU je PUN** — vLLM već 18.2/20.5GB. Možda treba `max_workers=5` umjesto 10. - Output: JSON osobe/klubovi/savezi - Insert u `clanovi` s `savez_izvor='godisnjak'`, `metadata.year=2007..2022` ### C) DAMIR SEED LIST FULL CRAWL 1. Fix schema: `ALTER TABLE klubovi ADD COLUMN izvor_unosa TEXT` 2. Fix krive URL-ove (Wiki/Semafor → pravi): ```sql UPDATE klubovi SET web='https://nkopatija.hr/' WHERE id=3840; UPDATE klubovi SET web='https://nk-mune.hr/' WHERE id=2201; UPDATE klubovi SET web='https://nk-crikvenica.hr/' WHERE id=2421; UPDATE klubovi SET web='https://akkvarner.hr/' WHERE id=3746 AND naziv='AK Kvarner'; ``` 3. Insert 27 klubova iz Damirovog seed-a (15 NEW + 12 update web) 4. Mass klub-web enrich za 30+ klubova s URL-om ### D) HVS-PATTERN SAVEZ API DISCOVERY Sniffinrali smo ali nije temeljito. Pokušati: - `rezultati.hbs.hr/api/?rubrika=person&id=X` (boćanje) - `rezultati.hks-cbf.hr/api/?...` (košarka) - `rezultati.hns-cff.hr/api/?...` (nogomet — ali HNS ima drugačiji stack) - Pokušati Damirov `https://semafor.hns.family/klubovi/{id}` koji već postoji u DB ### E) NETWORK XHR sniff za **specifične klub stranice** Kad idemo na `https://nkkrk.hr/igraci/` koji XHR pokrene? Ako klub ima JS-bound popis igrača, to treba sniff. ### F) NSPGZ.hr — službena PGZ nogomet web Damir potvrdio. Njihov registar = svi PGZ nogometni klubovi + igrači? ### G) sah-pgz.hr/klubovi/ direktan scrape Konkretan URL od Damira = lista šahovskih klubova PGŽ. Brza pobjeda. ### H) Šahovski savez HR registar hrvatski-sahovski-savez.hr ima sigurno registar igrača. --- ## 9. CREDENTIALS / Refs ```bash # Bridge URL=https://api.rinet.one/bridge/exec KEY=rinet-yS4ZnKlwUqsjk # DB HOST=localhost PORT=6432 DB=rinet_v3 USER=rinet PASS=R1net2026!SecureDB#v7 # vLLM (jedini LLM — pazi na GPU!) VLLM=http://localhost:8001/v1/chat/completions MODEL="Qwen/Qwen2.5-7B-Instruct-AWQ" MAX_TOKENS=8192 # Embedder (pazi: input mora biti LIST!) EMBED=http://localhost:9879/api/embeddings BODY={"model":"bge-m3","input":["text"]} # Qdrant QDRANT=http://10.10.0.2:6333 COLLECTION=pgz_sport_v1 # Cloud LLMs (fallback) GROQ_API_KEY=SET (in /opt/rinet-gpu/.env.master) DEEPSEEK_API_KEY=SET # Sudreg SUDREG_CLIENT_ID + SUDREG_CLIENT_SECRET (u .env.master) TOKEN_URL=https://sudreg-data.gov.hr/api/oauth/token # HVS rezultati JWT (gore) TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJIVlMi... # Telegram TG_BOT=8535797835:AAFItT-92jzZ9NWFafLxn0dLa1_n2s-JE5Y TG_CHAT=7969491558 ``` --- ## 10. KRITIČNA UPOZORENJA za novu sesiju 1. **3-strike rule active** — ne lažirati "complete" tvrdnje, uvijek `find /opt -name X` ili API check 2. **GPU 18.2/20.5GB pun** — vLLM uzima sve. Ne pokrećeš 2. LLM proces. `max_workers=5` za batch. 3. **Embedder input MORA biti LIST** (`["text"]`, ne `"text"`) 4. **`/tmp/dis.py`** šta ako se ponovo pojavi → Python module crash, briši odmah 5. **NIKAD srpski/crnogorski u outputu** — `_lang_fix` filter aktivan 6. **POŠK je iz Splita** — ne PGŽ! (3893 marked SDŽ, 177 igrača re-locira) 7. **Stack file headers** — svaki .py file ima `# Fajl/Verzija/Datum/Autor/Lokacija/Svrha` 8. **Web URL discovery preko Google/DDG** = NE RADI, blokira IP. Koristiti **Damirov seed list**. 9. **Damir hoće FUZZY DEDUP** — bez 5x istog kluba u varijantama --- ## 11. NESPREMNE SKRIPTE (saved u /tmp/) ``` /tmp/godisnjak_llm.py — LLM ekstraktor 18 godišnjaka (NIJE pokrenut, GPU full!) /tmp/seed_pgz.py — Damirov seed list insert (delovi failed — schema fix) /tmp/sudreg_mass.py — Sudreg PGZ pull (loš approach, ne re-run) /tmp/hvs_mass.py — HVS API mass scrape (✓ SUCCESS, 795 osoba) /tmp/hvs_teams.py — HVS team probe (✓ 52 PGZ momčadi) /tmp/sport_pgz_scrape.py — sport-pgz.hr scrape (✓ 142 PDF found) /tmp/godisnjak_pull.py — 18 godišnjak download + DB insert (✓ SUCCESS) /tmp/quickfix.py — POŠK + savez merge (✓ done) /tmp/fix_kat.py — kategorija fix za boćare (✓ done) Damirov scraper (referenca): /opt/rinet-gpu/sport_pipeline/scrapers/hoo_pw_fetch.py ← sync_playwright pattern /opt/rinet-gpu/sport_pipeline/scrapers/_common.py ← upsert_doc() pattern ``` --- ## 12. POZICIJA — pre-handoff Damir je rekao "Napravi jebeno dobre scrapere, bez podataka smo ništa!". HVS je dobar (745 osoba). Ostali sportovi minimalno (1234 mass enrich, kategorija nedodjeljena). Trenutno **1808 PGZ osoba** u DB. Damir hoće mnogo više. Sljedeća sesija mora: 1. Dovršiti **godišnjak LLM ekstrakcija** (potencijal: 5K-10K osoba) 2. **Damir seed list klub-web mass scrape** (potencijal: 30 klubova × ~30 osoba = 900) 3. **NSPGZ.hr scrape** (svi PGZ nogometni klubovi) 4. **sah-pgz.hr/klubovi/** direktan scrape Svaki novi savez treba **Layer 1-6 pipeline** kao što je Damir predložio. --- ## END HANDOFF Pokreni novu sesiju s ovim file-om u Project Knowledge. Damir nastavlja sa njim.