148 lines
4.8 KiB
Python
148 lines
4.8 KiB
Python
"""Klant 360° — geaggregeerde data uit NAS, CRM, retail, trends."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from app.db import fetch_all, fetch_one
|
|
from app.services import projects as projects_svc
|
|
|
|
|
|
def _serialize_row(row: dict | None) -> dict | None:
|
|
if not row:
|
|
return None
|
|
out = dict(row)
|
|
for k, v in list(out.items()):
|
|
if hasattr(v, "isoformat"):
|
|
out[k] = v.isoformat()
|
|
return out
|
|
|
|
|
|
def _serialize_rows(rows: list) -> list[dict]:
|
|
return [_serialize_row(r) for r in rows if r]
|
|
|
|
|
|
def get_client_360(client_id: int) -> dict[str, Any]:
|
|
client = fetch_one("SELECT * FROM clients WHERE id = %s", (client_id,))
|
|
if not client:
|
|
return {"ok": False, "error": "Client not found"}
|
|
|
|
projs = projects_svc.list_projects(client_id=client_id, limit=50)
|
|
links = fetch_all(
|
|
"""
|
|
SELECT dl.*, p.name AS project_name
|
|
FROM document_links dl
|
|
LEFT JOIN cockpit_projects p ON p.id = dl.project_id
|
|
WHERE dl.client_id = %s
|
|
ORDER BY dl.created_at DESC
|
|
""",
|
|
(client_id,),
|
|
)
|
|
paths = [l["storage_path"] for l in links if l.get("storage_path")]
|
|
docs: list[dict] = []
|
|
if paths:
|
|
placeholders = ",".join(["%s"] * len(paths))
|
|
docs = fetch_all(
|
|
f"""
|
|
SELECT filename, storage_path, doc_type, word_count, sentiment_label,
|
|
sentiment_compound, analyzed_at, user_labels
|
|
FROM document_analytics
|
|
WHERE storage_path IN ({placeholders})
|
|
OR storage_path LIKE ANY (
|
|
SELECT dl.storage_path || '/%%' FROM document_links dl
|
|
WHERE dl.client_id = %s AND dl.is_folder = TRUE
|
|
)
|
|
ORDER BY analyzed_at DESC NULLS LAST
|
|
LIMIT 80
|
|
""",
|
|
tuple(paths + [client_id]),
|
|
)
|
|
sentiment_rows = fetch_all(
|
|
"""
|
|
SELECT sentiment_label, COUNT(*) AS n, AVG(sentiment_compound) AS avg_c
|
|
FROM document_analytics da
|
|
WHERE EXISTS (
|
|
SELECT 1 FROM document_links dl
|
|
WHERE dl.client_id = %s
|
|
AND (da.storage_path = dl.storage_path
|
|
OR (dl.is_folder AND da.storage_path LIKE dl.storage_path || '/%%'))
|
|
)
|
|
GROUP BY sentiment_label
|
|
""",
|
|
(client_id,),
|
|
)
|
|
top_words = fetch_all(
|
|
"""
|
|
SELECT dwc.lemma, SUM(dwc.count) AS total
|
|
FROM document_word_counts dwc
|
|
JOIN document_analytics da ON da.id = dwc.document_id
|
|
WHERE EXISTS (
|
|
SELECT 1 FROM document_links dl
|
|
WHERE dl.client_id = %s
|
|
AND (da.storage_path = dl.storage_path
|
|
OR (dl.is_folder AND da.storage_path LIKE dl.storage_path || '/%%'))
|
|
)
|
|
AND NOT dwc.is_stopword
|
|
GROUP BY dwc.lemma
|
|
ORDER BY total DESC
|
|
LIMIT 25
|
|
""",
|
|
(client_id,),
|
|
)
|
|
deals = fetch_all(
|
|
"SELECT id, title, value, stage, next_action, deadline FROM deals WHERE client_id = %s ORDER BY updated_at DESC LIMIT 15",
|
|
(client_id,),
|
|
)
|
|
stores = fetch_all(
|
|
"""
|
|
SELECT s.id, s.name, s.chain, s.city, s.partnership_status, s.halal_certified
|
|
FROM client_supermarket_links l
|
|
JOIN supermarkets s ON s.id = l.supermarket_id
|
|
WHERE l.client_id = %s
|
|
LIMIT 30
|
|
""",
|
|
(client_id,),
|
|
)
|
|
trends = fetch_all(
|
|
"""
|
|
SELECT DATE_TRUNC('month', da.analyzed_at) AS month,
|
|
COUNT(*) AS docs,
|
|
AVG(da.sentiment_compound) AS avg_sentiment,
|
|
SUM(da.word_count) AS words
|
|
FROM document_analytics da
|
|
WHERE da.analyzed_at IS NOT NULL
|
|
AND EXISTS (
|
|
SELECT 1 FROM document_links dl
|
|
WHERE dl.client_id = %s
|
|
AND (da.storage_path = dl.storage_path
|
|
OR (dl.is_folder AND da.storage_path LIKE dl.storage_path || '/%%'))
|
|
)
|
|
GROUP BY 1
|
|
ORDER BY 1 DESC
|
|
LIMIT 12
|
|
""",
|
|
(client_id,),
|
|
)
|
|
for t in trends:
|
|
if hasattr(t.get("month"), "isoformat"):
|
|
t["month"] = t["month"].isoformat()
|
|
|
|
return {
|
|
"ok": True,
|
|
"client": _serialize_row(client),
|
|
"projects": projs,
|
|
"links": _serialize_rows(links),
|
|
"documents": _serialize_rows(docs),
|
|
"sentiment_breakdown": _serialize_rows(sentiment_rows),
|
|
"top_words": _serialize_rows(top_words),
|
|
"deals": _serialize_rows(deals),
|
|
"stores": _serialize_rows(stores),
|
|
"monthly_trends": trends,
|
|
"stats": {
|
|
"linked_paths": len(links),
|
|
"documents": len(docs),
|
|
"projects": len(projs),
|
|
"stores": len(stores),
|
|
"deals": len(deals),
|
|
},
|
|
}
|