HNS+UI: 4 nova endpointa + multi-sport schema (M2M kategorije + player_stats)

Endpoints:
- GET /api/v2/enrich-sources — sport→source mapping
- GET /api/v2/klubovi/priority-sort — financirani/godišnjak prvi
- GET /api/v2/clan/{id}/kategorije — many-to-many kategorije
- GET /api/v2/clan/{id}/full — kompletna slika (profil+kategorije+sezone+utakmice+stats)
- POST /api/v2/export/klubovi — XLSX export selektiranih

Schema:
- pgz_sport.clan_kategorije (M2M: igrač u juniorskoj+seniorskoj)
- pgz_sport.player_stats (multi-sport: nogomet/košarka/rukomet/odbojka/vaterpolo)
- pgz_sport.klub_roster (multi-source)
- pgz_sport.enrichment_sources (sport→izvor)
- View: v_pgz_priority_klubovi (financiran || u_godisnjaku)
- View: v_klubovi_priority_sort (priority sort)

Sport harvesters scaffold:
- scripts/sport_harvesters/__base.py (SportHarvester class)
- hks_basketball.py, hrs_handball.py, hos_volleyball.py, hvs_waterpolo.py
This commit is contained in:
2026-05-05 10:42:49 +02:00
parent c68fd4471e
commit 9fb512932a
10 changed files with 4765 additions and 0 deletions
+111
View File
@@ -18,6 +18,7 @@ Changes (2026-05-05, sub-agent W5):
from fastapi import FastAPI, HTTPException, Query, Body, Header, Depends, UploadFile, File, Form, Request
import json
import time
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional, List
@@ -2072,6 +2073,116 @@ def dashboard_hns_coverage():
return stats[0] if stats else {}
@app.get("/api/v2/enrich-sources")
def enrich_sources():
"""Sport→source mapping za frontend Obogati podatke dugme."""
rows = fetch("SELECT * FROM pgz_sport.enrichment_sources ORDER BY sport")
return {"sources": rows}
@app.get("/api/v2/clan/{clan_id}/kategorije")
def clan_kategorije(clan_id: int):
"""Kategorije igrača (M2M)."""
rows = fetch("""
SELECT kategorija, sezona, klub_id, source, source_url, scraped_at
FROM pgz_sport.clan_kategorije WHERE clan_id = %s
ORDER BY sezona DESC, kategorija
""", (clan_id,))
return {"clan_id": clan_id, "kategorije": rows}
@app.get("/api/v2/klubovi/priority-sort")
def klubovi_priority_sort(sport: str = None, limit: int = 500):
"""Klubovi sortirani: priority (financirani || godišnjak) prvi."""
where = ""
params = []
if sport:
where = " WHERE sport = %s"
params.append(sport)
rows = fetch(f"""
SELECT k.*, k.priority_label,
(SELECT count(*) FROM pgz_sport.clanovi WHERE klub_id = k.id) AS sportasa,
(SELECT count(*) FROM pgz_sport.hns_klub_roster WHERE klub_id = k.id) AS hns_roster,
(SELECT sum(iznos) FROM pgz_sport.potpore_nositelji WHERE klub_id = k.id OR naziv_kluba ILIKE k.naziv) AS potpora_ukupno
FROM pgz_sport.v_klubovi_priority_sort k
{where}
ORDER BY priority, potpora_ukupno DESC NULLS LAST, naziv
LIMIT %s
""", tuple(params) + (limit,))
return {"count": len(rows), "rows": rows}
@app.get("/api/v2/clan/{clan_id}/full")
def clan_full(clan_id: int):
"""Punu sliku igrača: profil + kategorije + sezone + utakmice + potpore."""
profile = fetch("SELECT * FROM pgz_sport.clanovi WHERE id = %s", (clan_id,))
if not profile: return {"error": "not_found"}
p = profile[0]
kategorije = fetch("SELECT * FROM pgz_sport.clan_kategorije WHERE clan_id = %s ORDER BY sezona DESC", (clan_id,))
seasons = fetch("SELECT * FROM pgz_sport.hns_player_seasons WHERE clan_id = %s ORDER BY sezona DESC", (clan_id,))
matches = fetch("SELECT * FROM pgz_sport.hns_player_matches WHERE clan_id = %s ORDER BY datum DESC NULLS LAST LIMIT 30", (clan_id,))
multi_stats = fetch("SELECT * FROM pgz_sport.player_stats WHERE clan_id = %s ORDER BY sezona DESC", (clan_id,))
return {
"profile": p,
"kategorije": kategorije,
"hns_seasons": seasons,
"hns_matches": matches,
"multi_sport_stats": multi_stats,
"stats": {
"total_seasons": len(seasons),
"total_matches": len(matches),
"total_kategorije": len(kategorije),
}
}
@app.post("/api/v2/export/klubovi")
def export_klubovi(req: dict):
"""Export klubova kao XLSX."""
import io
try:
from openpyxl import Workbook
except ImportError:
return {"error": "openpyxl not installed"}
ids = req.get("ids", [])
if not ids:
return {"error": "no ids"}
rows = fetch("""
SELECT k.id, k.naziv, k.sport, k.razina, k.oib, k.grad,
k.financiran, k.u_godisnjaku, k.priority_label,
(SELECT count(*) FROM pgz_sport.clanovi WHERE klub_id = k.id) AS sportasa,
(SELECT sum(iznos) FROM pgz_sport.potpore_nositelji WHERE klub_id = k.id OR naziv_kluba ILIKE k.naziv) AS potpora
FROM pgz_sport.v_klubovi_priority_sort k
WHERE k.id = ANY(%s)
ORDER BY k.priority, k.naziv
""", (ids,))
wb = Workbook()
ws = wb.active
ws.title = "Klubovi"
if rows:
headers = list(rows[0].keys())
ws.append([h.replace('_',' ').title() for h in headers])
for r in rows:
ws.append([r.get(h) for h in headers])
buf = io.BytesIO()
wb.save(buf)
buf.seek(0)
from fastapi.responses import StreamingResponse
return StreamingResponse(
buf,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={"Content-Disposition": f"attachment; filename=klubovi_export_{int(time.time())}.xlsx"}
)
@app.get("/")
def root(request: Request):
host = request.headers.get("host", "")