5d60d33db1
Volledige Foodlinkk Command Center uitbreiding met social automatisering, reclamefolder filters, Proxmox monitoring en documentatie.
234 lines
8.8 KiB
Python
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
|