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:
@@ -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", "")
|
||||
|
||||
Reference in New Issue
Block a user