PGŽ Sport Platform — Round 1+2 baseline (sport2.html + API)
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
"""Audit coverage matrix endpoint - colored heat-map data per klub."""
|
||||
from fastapi import APIRouter
|
||||
import psycopg2
|
||||
|
||||
DB = dict(host='localhost', port=5432, dbname='rinet_v3',
|
||||
user='rinet', password='R1net2026!SecureDB#v7')
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/audit/coverage-matrix")
|
||||
def coverage_matrix(min_size: int = 5, sport: str = None):
|
||||
"""Vrati matricu pokrivenosti po klubu × tipu podatka."""
|
||||
conn = psycopg2.connect(**DB); conn.autocommit = True
|
||||
cu = conn.cursor()
|
||||
|
||||
where = "WHERE k.aktivan AND k.region IN ('PGŽ', 'Rijeka', 'Primorje', 'Otoci')"
|
||||
if sport:
|
||||
where += f" AND k.sport ILIKE '%{sport}%'"
|
||||
|
||||
cu.execute(f"""
|
||||
SELECT
|
||||
k.id, k.naziv, k.sport, k.grad,
|
||||
(SELECT count(*) FROM pgz_sport.clanovi c WHERE c.klub_id=k.id) AS sportasa,
|
||||
(SELECT count(*) FROM pgz_sport.clanovi c WHERE c.klub_id=k.id AND c.verified) AS verified,
|
||||
(SELECT count(*) FROM pgz_sport.utakmice_log u WHERE u.za_klub_id=k.id) AS utakmica,
|
||||
(SELECT count(*) FROM pgz_sport.clan_sezona cs JOIN pgz_sport.clanovi c2 ON c2.id=cs.clan_id WHERE c2.klub_id=k.id) AS sezona,
|
||||
(SELECT count(*) FROM pgz_sport.klub_sezona ks WHERE ks.klub_id=k.id) AS trofeja,
|
||||
(SELECT count(*) FROM pgz_sport.clan_nagrada cn JOIN pgz_sport.clanovi c3 ON c3.id=cn.clan_id WHERE c3.klub_id=k.id) AS nagrada,
|
||||
CASE WHEN k.godisnjak_godine IS NOT NULL THEN array_length(k.godisnjak_godine, 1) ELSE 0 END AS god_hits
|
||||
FROM pgz_sport.klubovi k
|
||||
{where}
|
||||
ORDER BY (SELECT count(*) FROM pgz_sport.clanovi c WHERE c.klub_id=k.id) DESC
|
||||
LIMIT 200
|
||||
""")
|
||||
rows = []
|
||||
for r in cu.fetchall():
|
||||
sportasa = r[4] or 0
|
||||
if sportasa < min_size: continue
|
||||
verified = r[5] or 0
|
||||
rows.append({
|
||||
"klub_id": r[0], "naziv": r[1], "sport": r[2], "grad": r[3],
|
||||
"sportasa": sportasa, "verified": verified,
|
||||
"verified_pct": round(verified*100.0/sportasa) if sportasa else 0,
|
||||
"utakmica": r[6] or 0,
|
||||
"sezona": r[7] or 0,
|
||||
"trofeja": r[8] or 0,
|
||||
"nagrada": r[9] or 0,
|
||||
"god_hits": r[10] or 0,
|
||||
"score": min(100, (
|
||||
(verified*100/sportasa if sportasa else 0)*0.4 +
|
||||
(min(r[6] or 0, 50) * 2)*0.2 +
|
||||
(min(r[7] or 0, 30) * 3.3)*0.15 +
|
||||
(min(r[8] or 0, 20) * 5)*0.15 +
|
||||
(min(r[10] or 0, 10) * 10)*0.10
|
||||
))
|
||||
})
|
||||
|
||||
conn.close()
|
||||
return {"klubovi": rows, "count": len(rows), "filter_min_size": min_size, "filter_sport": sport}
|
||||
@@ -0,0 +1,56 @@
|
||||
"""Image proxy endpoint za HNS/HBS/external slike - rješava CORS + cache."""
|
||||
import hashlib, os, time
|
||||
from fastapi import APIRouter, HTTPException, Response
|
||||
from fastapi.responses import StreamingResponse
|
||||
import requests, redis
|
||||
|
||||
CACHE_DIR = "/var/cache/pgz-sport-img"
|
||||
os.makedirs(CACHE_DIR, exist_ok=True)
|
||||
ALLOWED_DOMAINS = ('hns.family', 'hns.hr', 'hbs.hr', 'hrvatski-bocarski-savez.hr',
|
||||
'rk-zamet.hr', 'hvs.hr', 'rezultati.hvs.hr', 'sport-pgz.hr')
|
||||
MAX_AGE = 86400 * 7 # 7 dana
|
||||
|
||||
try:
|
||||
rds = redis.Redis(host='localhost', port=6379, db=2)
|
||||
rds.ping()
|
||||
except: rds = None
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/img-proxy")
|
||||
def proxy_image(u: str):
|
||||
if not u.startswith(('http://', 'https://')):
|
||||
raise HTTPException(400, "Invalid URL")
|
||||
if not any(d in u for d in ALLOWED_DOMAINS):
|
||||
raise HTTPException(403, "Domain not allowed")
|
||||
|
||||
# Cache key
|
||||
h = hashlib.sha1(u.encode()).hexdigest()
|
||||
cf = os.path.join(CACHE_DIR, h)
|
||||
|
||||
# Disk cache check
|
||||
if os.path.exists(cf) and (time.time() - os.path.getmtime(cf)) < MAX_AGE:
|
||||
with open(cf+'.ct') as f: ct = f.read().strip()
|
||||
with open(cf, 'rb') as f: data = f.read()
|
||||
return Response(content=data, media_type=ct, headers={
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Cache-Control": "public, max-age=604800",
|
||||
"X-Proxy-Cache": "HIT"
|
||||
})
|
||||
|
||||
# Fetch from origin
|
||||
try:
|
||||
r = requests.get(u, timeout=10, headers={"User-Agent": "RiNET-Civic/1.0"})
|
||||
if r.status_code != 200:
|
||||
raise HTTPException(r.status_code, f"Origin returned {r.status_code}")
|
||||
ct = r.headers.get('content-type', 'image/jpeg')
|
||||
# Save to cache
|
||||
with open(cf, 'wb') as f: f.write(r.content)
|
||||
with open(cf+'.ct', 'w') as f: f.write(ct)
|
||||
return Response(content=r.content, media_type=ct, headers={
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Cache-Control": "public, max-age=604800",
|
||||
"X-Proxy-Cache": "MISS"
|
||||
})
|
||||
except requests.RequestException as e:
|
||||
raise HTTPException(502, f"Origin fetch failed: {e}")
|
||||
Reference in New Issue
Block a user