feat: /api/v2/analiza/* endpoints - sport analytics backend

This commit is contained in:
Damir Radulic
2026-05-16 00:28:12 +02:00
parent 7ca5d7d94e
commit aca5051418
1355 changed files with 321891 additions and 4128 deletions
+129
View File
@@ -0,0 +1,129 @@
from dotenv import load_dotenv
load_dotenv('/opt/rinet-gpu/.env.master')
# auto-added by patch_scrapers_with_dotenv.sh
"""
Objekti Router — sportski objekti PGZ
Created: 2026-05-09 v4.0-final pre-K3s
Endpoints: GET /api/v2/objekti/list (paginated), /api/v2/objekti/{id}
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import Optional, List
import os, json
from psycopg2.extras import RealDictCursor
import psycopg2
router = APIRouter(prefix="/api/v2/objekti", tags=["objekti"])
def get_conn():
return psycopg2.connect(
host=os.getenv("DB_HOST", "10.10.0.2"),
port=int(os.getenv("DB_PORT", "6432")),
dbname=os.getenv("DB_NAME", "rinet_v3"),
user=os.getenv("DB_USER", "rinet"),
password=os.environ["DB_PASSWORD"]
)
@router.get("/list")
def list_objekti(
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=200),
grad: Optional[str] = None,
tip: Optional[str] = None,
region: Optional[str] = None,
search: Optional[str] = None,
):
"""List sportski objekti sa filtriranjem"""
where = ["aktivan = true"]
params = []
if grad:
where.append("grad ILIKE %s")
params.append(f"%{grad}%")
if tip:
where.append("tip = %s")
params.append(tip)
if region:
where.append("region ILIKE %s")
params.append(f"%{region}%")
if search:
where.append("fts @@ plainto_tsquery(\'simple\', %s)")
params.append(search)
where_sql = " AND ".join(where) if where else "true"
conn = get_conn()
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
# Count
cur.execute(f"SELECT COUNT(*) FROM pgz_sport.objekti WHERE {where_sql}", params)
total = cur.fetchone()["count"]
# Data
cur.execute(f"""
SELECT id, naziv, tip, vlasnik, upravitelj, adresa, grad, region,
kapacitet, godina_izgradnje, geo_lat, geo_lng, koristi_se_za,
email, telefon, web, aktivan, pristupacan_invalidi
FROM pgz_sport.objekti
WHERE {where_sql}
ORDER BY grad, naziv
LIMIT %s OFFSET %s
""", params + [limit, skip])
items = cur.fetchall()
return {
"items": items,
"total": total,
"skip": skip,
"limit": limit
}
finally:
conn.close()
@router.get("/{objekt_id}")
def get_objekt(objekt_id: int):
"""Detail jednog objekta"""
conn = get_conn()
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT * FROM pgz_sport.objekti WHERE id = %s", (objekt_id,))
r = cur.fetchone()
if not r:
raise HTTPException(status_code=404, detail="Objekt not found")
return r
finally:
conn.close()
@router.get("/stats/by-tip")
def stats_by_tip():
"""Stats: count objekata po tip"""
conn = get_conn()
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("""
SELECT tip, COUNT(*) as count
FROM pgz_sport.objekti
WHERE aktivan = true
GROUP BY tip
ORDER BY 2 DESC
""")
return {"items": cur.fetchall()}
finally:
conn.close()
@router.get("/stats/by-region")
def stats_by_region():
"""Stats: count objekata po region"""
conn = get_conn()
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("""
SELECT region, COUNT(*) as count
FROM pgz_sport.objekti
WHERE aktivan = true AND region IS NOT NULL
GROUP BY region
ORDER BY 2 DESC
""")
return {"items": cur.fetchall()}
finally:
conn.close()