PGŽ Sport Platform — Round 1+2 baseline (sport2.html + API)

This commit is contained in:
Damir Radulić
2026-05-04 23:39:08 +02:00
commit a7ec0a86be
1820 changed files with 694455 additions and 0 deletions
@@ -0,0 +1,414 @@
# 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.