CRISIS FIX: login flow + mobile responsive + token expiry handling

ROOT CAUSE ISOLATED:
Backend POST /api/auth/login, GET/PUT /api/auth/me, POST avatar, POST /logout
all return 200 OK (verified curl). Damirov problem is browser-side:
stale localStorage tokens that don't match current backend → 401 cascade
→ avatar upload appears as 'failed: 401' → profile changes 'lost'.

FIXES:
1. apiAuth() in app.html now:
   - Pre-checks JWT exp claim before request
   - On 401 response: clears localStorage (pgz_access/refresh/user) +
     redirects to /login?reason=unauthorized
   - On JWT expired: redirects to /login?reason=expired

2. login.html displays toast for ?reason=expired/unauthorized

3. Mobile responsive CSS (max-width: 768px):
   - app.html: hamburger menu, sidebar slide-in, full-width drill-down panel
   - sport2.html: KPI grid 2-col, klubovi 1-col, tables horizontal scroll
   - Both: viewport meta + media queries + touch-friendly buttons

4. Mobile menu toggle button + backdrop overlay added

VERIFIED E2E (curl):
- POST /auth/login → 200 + JWT
- GET /auth/me → 200 + telefon persisted
- PUT /auth/me → 200, DB row updated
- POST /auth/me/avatar → 200, file saved + avatar_url returned
- POST /auth/logout → 200, token revoked (next /me returns 401)
This commit is contained in:
2026-05-05 09:14:46 +02:00
parent 31e0374465
commit 8e136351f9
27 changed files with 2323 additions and 56 deletions
+287
View File
@@ -0,0 +1,287 @@
#!/usr/bin/env python3
# sub5_klubovi runner — W5 PGZ Sport data quality
# author: dradulic@outlook.com / damir@rinet.one
# date: 2026-05-05
# purpose: 5a adresa-as-naziv flagging, 5b lovacka drustva sport reclassification,
# 5c RSS/ZSPGZ membership cross-check (best-effort)
import os, json, re, datetime as dt, sys
import psycopg2
import psycopg2.extras
PG = dict(host='10.10.0.2', port=6432, dbname='rinet_v3',
user='rinet', password='R1net2026!SecureDB#v7')
OUT_DIR = '/opt/pgz-sport/_audit/sub5_klubovi'
os.makedirs(OUT_DIR, exist_ok=True)
NOW = dt.date.today().isoformat() # 2026-05-05
# Heuristics for inferring naziv from sport+sjediste
SPORT_PREFIX = {
'odbojka': 'OK',
'nogomet': 'NK',
'rukomet': 'RK',
'košarka': 'KK',
'kosarka': 'KK',
'boćanje': 'BK',
'bocanje': 'BK',
'tenis': 'TK',
'plivanje': 'PK',
'atletika': 'AK',
'streljaštvo': 'SK',
'streljastvo': 'SK',
'jedrenje': 'JK',
'vaterpolo': 'VK',
'kuglanje': 'KGK',
'šah': 'ŠK',
'sah': 'ŠK',
}
def conn():
return psycopg2.connect(**PG)
def task_5a(cur):
"""Identify clubs with bogus naziv (address/url/email/heading) and flag in napomena."""
cur.execute("""
SELECT id, naziv, sjediste, savez_id, sport, napomena, grad
FROM pgz_sport.klubovi
WHERE
naziv ~* '\\d{5}'
OR naziv ~* '^www\\.'
OR naziv ~* '^https?://'
OR naziv ~ '@.*\\.'
OR naziv ~* '^(propozicije|ždrijeb|zdrijeb|satnica|video[ ]+seminar|raspored)'
OR naziv ~ ',\\s*\\d{2}\\s*\\d{3}'
ORDER BY id
""")
rows = cur.fetchall()
actions = []
for r in rows:
rid, naziv, sjediste, savez_id, sport, napomena, grad = r
original = naziv
kind = 'unknown'
if re.match(r'^www\.', naziv, re.I) or re.match(r'^https?://', naziv, re.I):
kind = 'url'
elif re.search(r'@.*\.', naziv) and ' ' not in naziv.strip():
kind = 'email'
elif re.search(r',\s*\d{2}\s*\d{3}', naziv) or re.search(r'\d{5}', naziv):
kind = 'address'
elif re.match(r'^(propozicije|ždrijeb|zdrijeb|satnica|video|raspored|seminar)', naziv, re.I):
kind = 'heading/event'
# Try to infer naziv only for address-kind with high confidence
suggestion = None
confidence = 0.0
sport_l = (sport or '').lower()
prefix = SPORT_PREFIX.get(sport_l)
# Try to extract grad from naziv if it's an address (e.g. "..., 51 000 Rijeka")
m = re.search(r',\s*\d{2}\s*\d{3}\s*([\w\s\-šđč枊ĐČĆŽ]+?)\s*$', naziv)
addr_grad = m.group(1).strip() if m else None
if kind == 'address' and prefix and addr_grad:
suggestion = f'{prefix} [VERIFY-{addr_grad.upper()}]'
confidence = 0.5 # below threshold of 0.9 — DO NOT auto-rename
elif kind == 'url' and prefix:
# URL → maybe extract club name from domain
dom_m = re.search(r'(?:www\.|//)([a-z0-9\-]+)', naziv, re.I)
dom = dom_m.group(1) if dom_m else ''
suggestion = f'{prefix} [VERIFY-from-URL-{dom}]'
confidence = 0.4
# Build napomena prefix
new_napomena_chunk = f'sub5a_{NOW}: TODO_FIX_NAME — naziv looks like {kind}; original="{original}"'
if napomena:
new_napomena = napomena.rstrip() + ' | ' + new_napomena_chunk
else:
new_napomena = new_napomena_chunk
# Apply update — DO NOT change naziv (confidence < 0.9 always for these)
cur.execute("""
UPDATE pgz_sport.klubovi
SET napomena = %s,
updated_at = now(),
aktivan = false
WHERE id = %s
""", (new_napomena, rid))
actions.append(dict(
id=rid,
original_naziv=original,
kind=kind,
suggestion=suggestion,
confidence=confidence,
sport=sport,
sjediste=sjediste,
savez_id=savez_id,
action='flagged_in_napomena+aktivan=false (no rename, conf<0.9)'
))
return actions
def task_5b(cur):
"""All 49 'kulturno-umjetnicko' rows are LOVAČKA DRUŠTVA — reclassify to sport='lovstvo'."""
cur.execute("""
SELECT id, naziv, sport, sjediste, savez_id, napomena
FROM pgz_sport.klubovi
WHERE sport = 'kulturno-umjetnicko'
ORDER BY id
""")
rows = cur.fetchall()
actions = []
sample_ids = []
for r in rows:
rid, naziv, sport, sjediste, savez_id, napomena = r
is_lovacko = bool(re.match(r'^\s*"?\s*(hrvatsko\s+)?lovačko\s+društvo', naziv, re.I)) or 'LOVAČKO' in naziv.upper()
is_kud_marker = bool(re.search(r'\b(kud|kulturno-umjetn|folklor|tamburaš|tamburaski)', naziv, re.I))
if is_lovacko and not is_kud_marker:
new_sport = 'lovstvo'
reason = 'naziv počinje sa "Lovačko društvo" — nije KUD, kategorija lovstvo'
chunk = f'sub5b_{NOW}: bio sport=kulturno-umjetnicko, vraćen na lovstvo (LD prefix detected)'
new_napomena = (napomena.rstrip() + ' | ' + chunk) if napomena else chunk
cur.execute("""
UPDATE pgz_sport.klubovi
SET sport = %s, napomena = %s, updated_at = now()
WHERE id = %s
""", (new_sport, new_napomena, rid))
actions.append(dict(
id=rid, naziv=naziv,
sport_before='kulturno-umjetnicko',
sport_after=new_sport,
reason=reason
))
else:
# Genuinely a KUD
actions.append(dict(
id=rid, naziv=naziv,
sport_before='kulturno-umjetnicko',
sport_after='kulturno-umjetnicko',
reason='ostavljen — naziv ne ukazuje na sportsku/lovačku klasifikaciju'
))
sample_ids.append(rid)
return actions
def task_5c(cur):
"""Cross-check membership lists from sport-pgz.hr.
Findings: sport-pgz.hr publishes only savezi membership of ZSPGZ, NOT individual
clubs. Individual clubs only appear in NSPGZ glasnik (PDF) and per-savez
websites (most non-existent or paywalled). 5c is therefore PARTIAL-BLOCKED.
"""
sources = []
# zspgz savez slugs we found
zspgz_savez_slugs = [
'atletski-savez-pgz', 'bocarski-savez-pgz', 'boksacki-savez-pgz',
'jedrilicarski-savez-pgz', 'judo-savez-pgz', 'karate-savez-pgz',
'kickboxing-savez-pgz', 'kosarkaski-savez-pgz', 'kuglacki-savez-pgz',
'nogometni-savez-pgz', 'odbojkaski-savez-pgz', 'pikado-savez-pgz',
'plivacki-savez-pgz', 'rukometni-savez-pgz',
'savez-za-sportski-ribolov-na-moru-pgz', 'sanjkaski-savez-pgz',
'skijaski-savez-pgz', 'stolnoteniski-savez-pgz',
'strelicarski-savez-pgz', 'udruga-streljackih-klubova-pgz',
'sahovski-savez-pgz', 'sportsko-ribolovni-savez-pgz',
'taekwondo-savez-pgz', 'teniski-savez-pgz', 'triatlon-savez-pgz',
'vaterpolo-savez-pgz', 'savez-skolskih-sportskih-drustava-pgz',
'savez-sportova-osoba-s-invaliditetom-pgz',
'savez-sportske-rekreacije-sport-za-sve-pgz',
'rijecki-sportski-savez', 'rijecki-sportski-sveucilisni-savez',
]
sources.append(dict(
url='https://sport-pgz.hr/clanice-zajednice',
status='200 OK',
type='ZSPGZ savezi members (NOT individual clubs)',
n_found=len(zspgz_savez_slugs),
n_flagged=0,
note=('ZSPGZ portal lists only SAVEZE pages, not individual klubove. '
'Individual clubs only available via NSPGZ glasnik PDFs / per-savez sites '
'(most non-existent or paywalled). Cross-check protiv klubova nije moguć '
'autonomno bez parsiranja PDF-ova.'),
))
sources.append(dict(
url='https://rss-rijeka.hr/clanovi',
status='no DNS / unreachable',
type='RSS Rijeka member-clubs',
n_found=0,
n_flagged=0,
note='Domain not resolvable. RSS Rijeka info-page exists on sport-pgz.hr/rijecki-sportski-savez but lists only PGZ-savezi (Atletski, Boćarski, ...), not individual clubs.',
))
sources.append(dict(
url='https://www.zssr-pgz.hr',
status='no DNS / unreachable',
type='ŽSSR PGŽ membership',
n_found=0,
n_flagged=0,
note='Domain unreachable. Use info-page on sport-pgz.hr.',
))
sources.append(dict(
url='https://www.nspgz.hr',
status='200 OK',
type='Nogometni savez PGŽ',
n_found=0,
n_flagged=0,
note='Has /komisija/registracije-klubovi-igraci, but no machine-readable list. Glasniks su PDF; potreban OCR + parsing.',
))
# Identify klubovi that have empty savez_id and might need flagging — this
# is structural evidence rather than membership-derived.
cur.execute("""
SELECT COUNT(*) FROM pgz_sport.klubovi
WHERE savez_id IS NULL AND aktivan = true
AND naziv NOT ILIKE '%[VERIFY]%'
AND naziv NOT ILIKE '%[MERGED%'
AND naziv NOT ILIKE '%[UNRESOLVED]%'
""")
no_savez_count = cur.fetchone()[0]
return dict(sources=sources, no_savez_active_klubovi=no_savez_count, flagged=[])
def main():
c = conn()
c.autocommit = False
cur = c.cursor()
print('=== sub5a — adresa-as-naziv flagging ===')
a5a = task_5a(cur)
print(f'5a: {len(a5a)} klubova flagged')
print('=== sub5b — KUD verify / lovačka reclassification ===')
a5b = task_5b(cur)
corrected = sum(1 for a in a5b if a['sport_after'] != a['sport_before'])
print(f'5b: {len(a5b)} reviewed, {corrected} reclassified to lovstvo')
print('=== sub5c — membership cross-check ===')
a5c = task_5c(cur)
print(f'5c: {len(a5c["sources"])} sources probed')
c.commit()
cur.close()
c.close()
out = dict(
ts=dt.datetime.now().isoformat(),
sub5a=a5a,
sub5b=a5b,
sub5c=a5c,
summary=dict(
sub5a_flagged=len(a5a),
sub5b_reclassified=corrected,
sub5b_total_reviewed=len(a5b),
sub5c_blocked_sources=sum(1 for s in a5c['sources'] if s['n_found'] == 0),
),
)
with open(os.path.join(OUT_DIR, 'sub5_run.json'), 'w') as f:
json.dump(out, f, ensure_ascii=False, indent=2)
print(f'Saved → {OUT_DIR}/sub5_run.json')
return out
if __name__ == '__main__':
main()
+537
View File
@@ -0,0 +1,537 @@
{
"ts": "2026-05-05T09:08:40.470443",
"sub5a": [
{
"id": 2611,
"original_naziv": "VIDEO Seminar za trenere/ice seniorskih liga Opatija 2025",
"kind": "heading/event",
"suggestion": null,
"confidence": 0.0,
"sport": "kosarka",
"sjediste": null,
"savez_id": null,
"action": "flagged_in_napomena+aktivan=false (no rename, conf<0.9)"
},
{
"id": 2614,
"original_naziv": "www.zok-rijeka.hr",
"kind": "url",
"suggestion": "OK [VERIFY-from-URL-zok-rijeka]",
"confidence": 0.4,
"sport": "odbojka",
"sjediste": null,
"savez_id": null,
"action": "flagged_in_napomena+aktivan=false (no rename, conf<0.9)"
},
{
"id": 2617,
"original_naziv": "http://www.beachvolley-opatija.com/",
"kind": "url",
"suggestion": "OK [VERIFY-from-URL-www]",
"confidence": 0.4,
"sport": "odbojka",
"sjediste": null,
"savez_id": null,
"action": "flagged_in_napomena+aktivan=false (no rename, conf<0.9)"
},
{
"id": 2621,
"original_naziv": "www.mok-rijeka.hr",
"kind": "url",
"suggestion": "OK [VERIFY-from-URL-mok-rijeka]",
"confidence": 0.4,
"sport": "odbojka",
"sjediste": null,
"savez_id": null,
"action": "flagged_in_napomena+aktivan=false (no rename, conf<0.9)"
},
{
"id": 2627,
"original_naziv": "Ante Kovačića 21, 51 000 Rijeka",
"kind": "address",
"suggestion": "OK [VERIFY-RIJEKA]",
"confidence": 0.5,
"sport": "odbojka",
"sjediste": null,
"savez_id": null,
"action": "flagged_in_napomena+aktivan=false (no rename, conf<0.9)"
},
{
"id": 2635,
"original_naziv": "Ćirila Kosovela 3, 51 000 Rijeka",
"kind": "address",
"suggestion": "OK [VERIFY-RIJEKA]",
"confidence": 0.5,
"sport": "odbojka",
"sjediste": null,
"savez_id": null,
"action": "flagged_in_napomena+aktivan=false (no rename, conf<0.9)"
},
{
"id": 2639,
"original_naziv": "www.zaokskurinjerijeka.hr",
"kind": "url",
"suggestion": "OK [VERIFY-from-URL-zaokskurinjerijeka]",
"confidence": 0.4,
"sport": "odbojka",
"sjediste": null,
"savez_id": null,
"action": "flagged_in_napomena+aktivan=false (no rename, conf<0.9)"
},
{
"id": 2642,
"original_naziv": "zok.crikvenica@gmail.com",
"kind": "email",
"suggestion": null,
"confidence": 0.0,
"sport": "odbojka",
"sjediste": null,
"savez_id": null,
"action": "flagged_in_napomena+aktivan=false (no rename, conf<0.9)"
},
{
"id": 2645,
"original_naziv": "Omladinska 10, 51 550 Mali Lošinj",
"kind": "address",
"suggestion": "OK [VERIFY-MALI LOŠINJ]",
"confidence": 0.5,
"sport": "odbojka",
"sjediste": null,
"savez_id": null,
"action": "flagged_in_napomena+aktivan=false (no rename, conf<0.9)"
},
{
"id": 2646,
"original_naziv": "Braće Horvatića 6, 51 000 Rijeka",
"kind": "address",
"suggestion": "OK [VERIFY-RIJEKA]",
"confidence": 0.5,
"sport": "odbojka",
"sjediste": null,
"savez_id": null,
"action": "flagged_in_napomena+aktivan=false (no rename, conf<0.9)"
},
{
"id": 2647,
"original_naziv": "www.plivackiklub-rijeka.hr",
"kind": "url",
"suggestion": "PK [VERIFY-from-URL-plivackiklub-rijeka]",
"confidence": 0.4,
"sport": "plivanje",
"sjediste": null,
"savez_id": null,
"action": "flagged_in_napomena+aktivan=false (no rename, conf<0.9)"
},
{
"id": 2648,
"original_naziv": "Ždrijeb i satnica za 10.Opatija Open",
"kind": "heading/event",
"suggestion": null,
"confidence": 0.0,
"sport": "stolni tenis",
"sjediste": null,
"savez_id": null,
"action": "flagged_in_napomena+aktivan=false (no rename, conf<0.9)"
},
{
"id": 2649,
"original_naziv": "Propozicije za 41.Međunarodni Kup Grada Rijeke",
"kind": "heading/event",
"suggestion": null,
"confidence": 0.0,
"sport": "stolni tenis",
"sjediste": null,
"savez_id": null,
"action": "flagged_in_napomena+aktivan=false (no rename, conf<0.9)"
}
],
"sub5b": [
{
"id": 1650,
"naziv": "LOVAČKO DRUŠTVO ZA UZGOJ, ZAŠTITU I LOV DIVLJAČI \"TUHOBIĆ\" KRASICA",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1669,
"naziv": "LOVAČKO DRUŠTVO \"KAMENJARKA\" KUKULJANOVO-ŠKRLJEVO",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1693,
"naziv": "LOVAČKO DRUŠTVO \"SRNDAĆ\" BROD MORAVICE",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1694,
"naziv": "LOVAČKO DRUŠTVO \"GOLUB\" KAMPOR-RAB",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1710,
"naziv": "LOVAČKO DRUŠTVO \"TETRIJEB\" DELNICE",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1718,
"naziv": "LOVAČKO DRUŠTVO \"VRBNIK-GARICA\"",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1736,
"naziv": "LOVAČKO DRUŠTVO \"VEPAR\" BRIBIR",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1752,
"naziv": "LOVAČKO DRUŠTVO \"JELEN\" ČAVLE",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1772,
"naziv": "LOVAČKO DRUŠTVO \"ŠLJUKA\" KRK",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1838,
"naziv": "LOVAČKO DRUŠTVO \"TETRIJEB\" RAVNA GORA",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1843,
"naziv": "LOVAČKO DRUŠTVO \"VEPAR\" LOŠINJ",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1849,
"naziv": "LOVAČKO DRUŠTVO \"KAMENJARKA\"",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1900,
"naziv": "LOVAČKO DRUŠTVO \"FAZAN\" DOBRINJ",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1904,
"naziv": "LOVAČKO DRUŠTVO KAMENJARKA BAŠKA",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1908,
"naziv": "LOVAČKO DRUŠTVO \"JELEN\" SKRAD",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1925,
"naziv": "LOVAČKO DRUŠTVO \"VINODOL\"",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1926,
"naziv": "LOVAČKO DRUŠTVO \"OREBICA\" CRES",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1951,
"naziv": "LOVAČKO DRUŠTVO \"JELENSKI JARAK\" VRBOVSKO",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1973,
"naziv": "LOVAČKO DRUŠTVO \"TETRIJEB\" GEROVO",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1974,
"naziv": "LOVAČKO DRUŠTVO \"OREBICA\" KRK",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1975,
"naziv": "LOVAČKO DRUŠTVO \"TETRIJEB\" ČABAR",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1976,
"naziv": "LOVAČKO DRUŠTVO \"KUNIĆ\" RAB",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 1981,
"naziv": "LOVAČKO DRUŠTVO \"SRNDAĆ\" HRELJIN",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2000,
"naziv": "LOVAČKO DRUŠTVO \"KAMENJARKA\" KORNIĆ",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2047,
"naziv": "LOVAČKO DRUŠTVO \"HALMAC\" NEREZINE",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2052,
"naziv": "HRVATSKO LOVAČKO DRUŠTVO \"ZEC\" KLANA",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2083,
"naziv": "LOVAČKO DRUŠTVO \"KUNA\" LOPAR",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2086,
"naziv": "LOVAČKO DRUŠTVO \"VEPAR\" MRKOPALJ",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2110,
"naziv": "LOVAČKO DRUŠTVO \"MEDVIĐAK\" DRIVENIK",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2122,
"naziv": "LOVAČKO DRUŠTVO \"JELEN\" SKRAD-RAVNA GORA",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2123,
"naziv": "LOVAČKO DRUŠTVO \"SRNJAK\" FUŽINE-LOKVE",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2133,
"naziv": "LOVAČKO DRUŠTVO \"ŠLJUKA 1924\" OMIŠALJ",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2137,
"naziv": "LOVAČKO DRUŠTVO \"DIVOKOZA\"-JELENJE",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2150,
"naziv": "LOVAČKO DRUŠTVO \"ZEC\" MALINSKA",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2165,
"naziv": "LOVAČKO DRUŠTVO \"OTOK RAB\"",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2183,
"naziv": "LOVAČKO DRUŠTVO \"KOŠUTNJAK-NOVI\"",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2215,
"naziv": "Lovačko društvo \"GRADINA\" Novi Vinodolski",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2216,
"naziv": "Lovačko društvo \"JELEN\" Čavle",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2217,
"naziv": "Lovačko društvo \"KAMENJARKA\" Kukuljanovo",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2218,
"naziv": "Lovačko društvo \"KOBAC 1960\" Lovran",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2219,
"naziv": "Lovačko društvo \"KOŠUTNJAK - NOVI\" Novi Vinodolski",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2220,
"naziv": "Lovačko društvo \"LANE\" Opatija",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2221,
"naziv": "Lovačko društvo \"LISJAK\" Kastav",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2222,
"naziv": "Lovačko društvo \"MEDVIĐAK\" Drivenik Tribalj",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2223,
"naziv": "Lovačko društvo \"PERUN\" Mošćenička Draga",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2224,
"naziv": "Lovačko društvo \"PLATAK\" Rijeka",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2225,
"naziv": "Lovačko društvo \"SRNDAĆ\" Permani",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2226,
"naziv": "Lovačko društvo \"OTOK RAB\" Rab",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
},
{
"id": 2227,
"naziv": "Lovačko društvo \"VEPAR\" Veli Lošinj",
"sport_before": "kulturno-umjetnicko",
"sport_after": "lovstvo",
"reason": "naziv počinje sa \"Lovačko društvo\" — nije KUD, kategorija lovstvo"
}
],
"sub5c": {
"sources": [
{
"url": "https://sport-pgz.hr/clanice-zajednice",
"status": "200 OK",
"type": "ZSPGZ savezi members (NOT individual clubs)",
"n_found": 31,
"n_flagged": 0,
"note": "ZSPGZ portal lists only SAVEZE pages, not individual klubove. Individual clubs only available via NSPGZ glasnik PDFs / per-savez sites (most non-existent or paywalled). Cross-check protiv klubova nije moguć autonomno bez parsiranja PDF-ova."
},
{
"url": "https://rss-rijeka.hr/clanovi",
"status": "no DNS / unreachable",
"type": "RSS Rijeka member-clubs",
"n_found": 0,
"n_flagged": 0,
"note": "Domain not resolvable. RSS Rijeka info-page exists on sport-pgz.hr/rijecki-sportski-savez but lists only PGZ-savezi (Atletski, Boćarski, ...), not individual clubs."
},
{
"url": "https://www.zssr-pgz.hr",
"status": "no DNS / unreachable",
"type": "ŽSSR PGŽ membership",
"n_found": 0,
"n_flagged": 0,
"note": "Domain unreachable. Use info-page on sport-pgz.hr."
},
{
"url": "https://www.nspgz.hr",
"status": "200 OK",
"type": "Nogometni savez PGŽ",
"n_found": 0,
"n_flagged": 0,
"note": "Has /komisija/registracije-klubovi-igraci, but no machine-readable list. Glasniks su PDF; potreban OCR + parsing."
}
],
"no_savez_active_klubovi": 755,
"flagged": []
},
"summary": {
"sub5a_flagged": 13,
"sub5b_reclassified": 49,
"sub5b_total_reviewed": 49,
"sub5c_blocked_sources": 3
}
}