Files
foodlinkk-command-center/cockpit/app/services/analytics_data.py
T
Aissa 5d60d33db1 Platform bundle: marketing publish, IT ops, packaging, agents mesh.
Volledige Foodlinkk Command Center uitbreiding met social automatisering,
reclamefolder filters, Proxmox monitoring en documentatie.
2026-06-09 00:41:27 +00:00

234 lines
8.8 KiB
Python

"""Comprehensive analytics data from all DB tables with optional filters."""
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any, Optional
from app.db import fetch_all, fetch_one
def _safe_count(table: str, where: str = "", params: tuple = ()) -> int:
try:
clause = f" WHERE {where}" if where else ""
row = fetch_one(f"SELECT COUNT(*) AS c FROM {table}{clause}", params or None)
return int(row["c"]) if row else 0
except Exception:
return 0
def _iso_rows(rows: list) -> list:
for row in rows:
for key, val in list(row.items()):
if hasattr(val, "isoformat"):
row[key] = val.isoformat()
elif val is not None and type(val).__name__ == "Decimal":
row[key] = float(val)
return rows
def collect_analytics(filters: Optional[dict[str, Any]] = None) -> dict[str, Any]:
f = filters or {}
chain = f.get("chain") or None
province = f.get("province") or None
stage = f.get("stage") or None
agent = f.get("agent") or None
days = int(f.get("days") or 90)
data: dict[str, Any] = {
"generated_at": datetime.now(timezone.utc).isoformat(),
"filters": f,
}
data["kpis"] = {
"clients_total": _safe_count("clients"),
"clients_active": _safe_count("clients", "stage = 'active'"),
"deals_total": _safe_count("deals"),
"pipeline_eur": float(
(fetch_one("SELECT COALESCE(SUM(value),0) AS t FROM deals WHERE stage NOT IN ('won','lost')") or {}).get("t", 0)
),
"supermarkets": _safe_count("supermarkets"),
"wholesalers": _safe_count("wholesalers"),
"crm_partnerships": _safe_count("supermarkets", "partnership_status = 'active'"),
"rss_items": _safe_count("rss_items"),
"rss_bookmarks": _safe_count("rss_bookmarks"),
"agent_events": _safe_count("agent_events"),
"pending_approvals": _safe_count("agent_events", "status = 'needs_approval'"),
"promo_campaigns": _safe_count("promo_campaigns", "status = 'active'"),
"contacts_supermarket": _safe_count("supermarket_contacts"),
"contacts_wholesaler": _safe_count("wholesaler_contacts"),
"nas_docs": _safe_count("document_analytics"),
}
try:
data["clients_by_stage"] = fetch_all(
"SELECT stage, COUNT(*) AS cnt FROM clients GROUP BY stage ORDER BY cnt DESC"
)
except Exception:
data["clients_by_stage"] = []
try:
data["deals_by_stage"] = fetch_all(
"SELECT stage, COUNT(*) AS cnt, COALESCE(SUM(value),0) AS total FROM deals GROUP BY stage ORDER BY cnt DESC"
)
except Exception:
data["deals_by_stage"] = []
try:
data["events_by_agent"] = fetch_all(
"""SELECT agent_name, COUNT(*) AS cnt FROM agent_events
WHERE created_at >= NOW() - INTERVAL '%s days'
GROUP BY agent_name ORDER BY cnt DESC LIMIT 20""" % days
)
except Exception:
data["events_by_agent"] = []
store_where, store_params = [], []
if chain:
store_where.append("chain = %s")
store_params.append(chain)
if province:
store_where.append("province = %s")
store_params.append(province)
sw = (" WHERE " + " AND ".join(store_where)) if store_where else ""
try:
data["supermarkets_by_chain"] = fetch_all(
f"SELECT chain, COUNT(*) AS cnt FROM supermarkets{sw} GROUP BY chain ORDER BY cnt DESC LIMIT 15",
tuple(store_params) if store_params else None,
)
except Exception:
data["supermarkets_by_chain"] = []
try:
data["supermarkets_by_province"] = fetch_all(
f"SELECT province, COUNT(*) AS cnt FROM supermarkets{sw} AND province IS NOT NULL GROUP BY province ORDER BY cnt DESC LIMIT 12"
if store_where
else "SELECT province, COUNT(*) AS cnt FROM supermarkets WHERE province IS NOT NULL GROUP BY province ORDER BY cnt DESC LIMIT 12"
)
except Exception:
data["supermarkets_by_province"] = []
try:
data["partnership_breakdown"] = fetch_all(
"SELECT COALESCE(partnership_status,'none') AS status, COUNT(*) AS cnt FROM supermarkets GROUP BY partnership_status ORDER BY cnt DESC"
)
except Exception:
data["partnership_breakdown"] = []
try:
data["wholesalers_by_province"] = fetch_all(
"SELECT province, COUNT(*) AS cnt FROM wholesalers WHERE province IS NOT NULL GROUP BY province ORDER BY cnt DESC LIMIT 12"
)
except Exception:
data["wholesalers_by_province"] = []
try:
data["rss_by_category"] = fetch_all(
"""SELECT f.category, COUNT(i.id) AS cnt FROM rss_items i
JOIN rss_feeds f ON f.id = i.feed_id GROUP BY f.category ORDER BY cnt DESC"""
)
except Exception:
data["rss_by_category"] = []
try:
data["events_timeline"] = fetch_all(
"""SELECT DATE(created_at) AS day, COUNT(*) AS cnt FROM agent_events
WHERE created_at >= NOW() - INTERVAL '%s days'
GROUP BY DATE(created_at) ORDER BY day ASC""" % days
)
except Exception:
data["events_timeline"] = []
try:
data["milestones_by_status"] = fetch_all(
"SELECT status, COUNT(*) AS cnt FROM sales_milestones GROUP BY status ORDER BY cnt DESC"
)
except Exception:
data["milestones_by_status"] = []
try:
data["top_opportunities"] = fetch_all(
"""SELECT s.chain, s.city, ros.halal_opportunity_score
FROM retail_opportunity_scores ros JOIN supermarkets s ON s.id = ros.supermarket_id
ORDER BY ros.halal_opportunity_score DESC LIMIT 10"""
)
except Exception:
data["top_opportunities"] = []
try:
data["sentiment_distribution"] = fetch_all(
"SELECT sentiment_label, COUNT(*) AS cnt FROM document_analytics GROUP BY sentiment_label"
)
except Exception:
data["sentiment_distribution"] = []
try:
data["top_words"] = fetch_all(
"""SELECT lemma, SUM(count) AS total FROM document_word_counts
WHERE NOT is_stopword GROUP BY lemma ORDER BY total DESC LIMIT 15"""
)
except Exception:
data["top_words"] = []
try:
data["promo_by_chain"] = fetch_all(
"SELECT chain, COUNT(*) AS cnt FROM promo_campaigns WHERE status = 'active' GROUP BY chain ORDER BY cnt DESC"
)
except Exception:
data["promo_by_chain"] = []
deal_where = ""
deal_params: tuple = ()
if stage:
deal_where = " WHERE stage = %s"
deal_params = (stage,)
try:
data["recent_deals"] = fetch_all(
f"SELECT title, stage, value, updated_at FROM deals{deal_where} ORDER BY updated_at DESC LIMIT 10",
deal_params or None,
)
except Exception:
data["recent_deals"] = []
agent_where = f" WHERE created_at >= NOW() - INTERVAL '{days} days'"
if agent:
agent_where += " AND agent_name = %s"
try:
data["recent_events"] = fetch_all(
f"""SELECT agent_name, event_type, title, status, created_at FROM agent_events
{agent_where} ORDER BY created_at DESC LIMIT 25""",
(agent,),
)
except Exception:
data["recent_events"] = []
else:
try:
data["recent_events"] = fetch_all(
f"""SELECT agent_name, event_type, title, status, created_at FROM agent_events
{agent_where} ORDER BY created_at DESC LIMIT 25"""
)
except Exception:
data["recent_events"] = []
try:
data["filter_meta"] = {
"chains": fetch_all("SELECT DISTINCT chain FROM supermarkets WHERE chain IS NOT NULL ORDER BY chain"),
"provinces": fetch_all("SELECT DISTINCT province FROM supermarkets WHERE province IS NOT NULL ORDER BY province"),
"client_stages": fetch_all("SELECT DISTINCT stage FROM clients ORDER BY stage"),
"deal_stages": fetch_all("SELECT DISTINCT stage FROM deals ORDER BY stage"),
"agents": fetch_all("SELECT DISTINCT agent_name FROM agent_events ORDER BY agent_name"),
}
except Exception:
data["filter_meta"] = {}
for key in (
"clients_by_stage", "deals_by_stage", "events_by_agent", "supermarkets_by_chain",
"supermarkets_by_province", "partnership_breakdown", "wholesalers_by_province",
"rss_by_category", "events_timeline", "milestones_by_status", "top_opportunities",
"sentiment_distribution", "top_words", "promo_by_chain", "recent_deals", "recent_events",
):
if isinstance(data.get(key), list):
data[key] = _iso_rows(data[key])
return data