SysOps: voice-agy-webbuilder-backup — 2026-06-23 10:04 UTC

This commit is contained in:
sysops
2026-06-23 10:04:23 +00:00
parent 3bf15c4850
commit 26fe76afdd
165 changed files with 47427 additions and 1264 deletions
+147
View File
@@ -0,0 +1,147 @@
"""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),
},
}