"""Analytics router. GET /api/analytics/summary -> today revenue/orders/profit/margin/aov/conversion GET /api/analytics/top-products GET /api/analytics/supplier-performance """ from collections import defaultdict from datetime import datetime, time, timezone from fastapi import APIRouter, Depends from sqlalchemy import func from sqlalchemy.orm import Session from app.database import get_db from app.routers.auth import require_admin from app.models import Order, Supplier, SupplierPerformance router = APIRouter(dependencies=[Depends(require_admin)]) EXCLUDED = ["cancelled", "refunded"] def _f(value) -> float: try: return float(value or 0) except (TypeError, ValueError): return 0.0 @router.get("/summary") def summary(db: Session = Depends(get_db)): today_start = datetime.combine(datetime.now(timezone.utc).date(), time.min) today_q = db.query(Order).filter( Order.created_at >= today_start, ~Order.status.in_(EXCLUDED) ) all_q = db.query(Order).filter(~Order.status.in_(EXCLUDED)) def _kpis(query): orders = query.all() revenue = sum(_f(o.retail_total) for o in orders) profit = sum(_f(o.profit) for o in orders) count = len(orders) return { "revenue": round(revenue, 2), "orders": count, "profit": round(profit, 2), "gross_profit": round(revenue - profit, 2), "margin": round(profit / revenue * 100, 2) if revenue else 0.0, "aov": round(revenue / count, 2) if count else 0.0, # no traffic/visitor tracking table yet — conversion is not measurable "conversion": 0.0, } refunds_today = ( db.query(func.count(Order.id)) .filter(Order.created_at >= today_start, Order.status.in_(["cancelled", "refunded"])) .scalar() or 0 ) return { "today": {**_kpis(today_q), "refunds": refunds_today}, "all_time": _kpis(all_q), } @router.get("/top-products") def top_products(limit: int = 10, db: Session = Depends(get_db)): orders = ( db.query(Order) .filter(~Order.status.in_(EXCLUDED)) .order_by(Order.created_at.desc()) .limit(500) .all() ) stats = defaultdict(lambda: {"sku": None, "units": 0, "revenue": 0.0}) for order in orders: for item in order.items or []: pid = item.get("product_id") if not pid: continue entry = stats[pid] entry["sku"] = item.get("sku") entry["units"] += int(item.get("qty") or 0) entry["revenue"] += float(item.get("unit_price") or 0) * int(item.get("qty") or 0) ranked = sorted(stats.items(), key=lambda kv: (-kv[1]["revenue"], -kv[1]["units"])) return [ {"product_id": pid, "sku": s["sku"], "units": s["units"], "revenue": round(s["revenue"], 2)} for pid, s in ranked[:limit] ] @router.get("/supplier-performance") def supplier_performance(db: Session = Depends(get_db)): out = [] for supplier in db.query(Supplier).all(): perf = ( db.query(SupplierPerformance) .filter(SupplierPerformance.supplier_id == supplier.id) .first() ) orders = ( db.query(Order) .filter(Order.supplier_id == supplier.id, ~Order.status.in_(EXCLUDED)) .all() ) out.append( { "supplier_id": str(supplier.id), "name": supplier.name, "adapter_type": supplier.adapter_type, "status": supplier.status, "reliability_score": _f(supplier.reliability_score), "fulfillment_rate": _f(perf.fulfillment_rate) if perf else 100.0, "avg_shipping_days": _f(perf.avg_shipping_days) if perf and perf.avg_shipping_days is not None else None, "cancellation_rate": _f(perf.cancellation_rate) if perf else 0.0, "stock_accuracy": _f(perf.stock_accuracy) if perf else 100.0, "return_rate": _f(perf.return_rate) if perf else 0.0, "orders": len(orders), "profit": round(sum(_f(o.profit) for o in orders), 2), } ) return out