Add live CEO briefing, Wereldexport CRM, halal engine, and realtime cockpit.
Ship export intel with contact filters and CRM push, Herman live digest with Telegram briefing, halal recommendation engine, revenue cockpit, and dashboard polling/WebSocket fixes.
This commit is contained in:
@@ -9,7 +9,9 @@ from fastapi.responses import StreamingResponse
|
||||
from app.config import settings
|
||||
from app.db import execute, fetch_all, fetch_one
|
||||
from app.services.briefing import (
|
||||
build_live_digest,
|
||||
collect_briefing_data,
|
||||
format_live_digest_text,
|
||||
generate_daily_briefing,
|
||||
serialize_stats,
|
||||
stream_daily_briefing,
|
||||
@@ -57,9 +59,9 @@ async def server_time():
|
||||
|
||||
|
||||
@router.get("/herman/briefing/stats")
|
||||
async def herman_briefing_stats():
|
||||
"""Live stats from DB — always fresh for dashboard panels."""
|
||||
stats = serialize_stats(collect_briefing_data())
|
||||
async def herman_briefing_stats(light: bool = False):
|
||||
"""Live stats from DB — light=1 skips halal engine for snelle poll-refresh."""
|
||||
stats = serialize_stats(collect_briefing_data(skip_halal=light))
|
||||
bookmarks = []
|
||||
bookmark_map = {}
|
||||
try:
|
||||
@@ -86,7 +88,20 @@ async def herman_briefing_stats():
|
||||
)
|
||||
except Exception:
|
||||
stats["rss_live"] = []
|
||||
return {"ok": True, "stats": stats, "bookmarks": bookmarks, "at": datetime.utcnow().isoformat()}
|
||||
return {"ok": True, "stats": stats, "bookmarks": bookmarks, "live_digest": build_live_digest(stats), "at": datetime.utcnow().isoformat()}
|
||||
|
||||
|
||||
@router.get("/herman/briefing/live-text")
|
||||
async def herman_briefing_live_text():
|
||||
"""Compacte live samenvatting voor Telegram / bots."""
|
||||
stats = serialize_stats(collect_briefing_data())
|
||||
digest = build_live_digest(stats)
|
||||
return {
|
||||
"ok": True,
|
||||
"text": format_live_digest_text(digest),
|
||||
"live_digest": digest,
|
||||
"at": datetime.utcnow().isoformat(),
|
||||
}
|
||||
|
||||
|
||||
@router.post("/herman/briefing")
|
||||
|
||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
import json
|
||||
|
||||
from app.db import fetch_all, fetch_one
|
||||
from app.services.briefing import collect_briefing_data, serialize_stats
|
||||
from app.services.briefing import build_live_digest, collect_briefing_data, serialize_stats
|
||||
|
||||
router = APIRouter(tags=["dashboard"])
|
||||
|
||||
@@ -32,8 +32,14 @@ def _safe_sum(table: str, column: str, where: str = "") -> float:
|
||||
|
||||
|
||||
def _briefing_payload(briefing: dict | None) -> dict:
|
||||
"""Always use live DB stats; briefing text may be cached."""
|
||||
payload: dict = {"content": None, "stats": serialize_stats(collect_briefing_data()), "created_at": None}
|
||||
"""Live stats + live digest; opgeslagen briefing-tekst alleen als AI-rapport."""
|
||||
live_stats = serialize_stats(collect_briefing_data())
|
||||
payload: dict = {
|
||||
"content": None,
|
||||
"stats": live_stats,
|
||||
"live_digest": build_live_digest(live_stats),
|
||||
"created_at": None,
|
||||
}
|
||||
if not briefing:
|
||||
return payload
|
||||
|
||||
|
||||
@@ -85,8 +85,10 @@ async def api_entity(entity_id: int):
|
||||
@router.get("/api/export-intel/contacts")
|
||||
async def api_contacts(
|
||||
country: Optional[str] = None,
|
||||
region: Optional[str] = None,
|
||||
entity_type: Optional[str] = None,
|
||||
has_email: Optional[bool] = None,
|
||||
crm_linked: Optional[bool] = None,
|
||||
q: Optional[str] = None,
|
||||
limit: int = 200,
|
||||
offset: int = 0,
|
||||
@@ -94,18 +96,40 @@ async def api_contacts(
|
||||
params: dict[str, Any] = {"limit": limit, "offset": offset}
|
||||
if country:
|
||||
params["country"] = country
|
||||
if region:
|
||||
params["region"] = region
|
||||
if entity_type:
|
||||
params["entity_type"] = entity_type
|
||||
if has_email is not None:
|
||||
params["has_email"] = has_email
|
||||
if crm_linked is not None:
|
||||
params["crm_linked"] = crm_linked
|
||||
if q:
|
||||
params["q"] = q
|
||||
return await _proxy("GET", "/contacts", params=params)
|
||||
|
||||
|
||||
@router.get("/api/export-intel/contacts/export.csv")
|
||||
async def api_contacts_export(country: Optional[str] = None, entity_type: Optional[str] = None):
|
||||
params = {k: v for k, v in {"country": country, "entity_type": entity_type}.items() if v}
|
||||
async def api_contacts_export(
|
||||
country: Optional[str] = None,
|
||||
region: Optional[str] = None,
|
||||
entity_type: Optional[str] = None,
|
||||
has_email: Optional[bool] = None,
|
||||
crm_linked: Optional[bool] = None,
|
||||
q: Optional[str] = None,
|
||||
):
|
||||
params = {
|
||||
k: v
|
||||
for k, v in {
|
||||
"country": country,
|
||||
"region": region,
|
||||
"entity_type": entity_type,
|
||||
"has_email": has_email,
|
||||
"crm_linked": crm_linked,
|
||||
"q": q,
|
||||
}.items()
|
||||
if v is not None and v != ""
|
||||
}
|
||||
url = f"{TOOLS}/export-intel/contacts/export.csv"
|
||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||
r = await client.get(url, params=params)
|
||||
|
||||
@@ -34,6 +34,17 @@ def register_recommendation_routes(router: APIRouter) -> None:
|
||||
async def reco_dismiss(rec_id: int):
|
||||
return await _proxy("POST", f"/recommendations/{rec_id}/dismiss")
|
||||
|
||||
@router.get("/recommendations/halal/live")
|
||||
async def halal_reco_live(limit: int = 5, refresh: bool = False):
|
||||
q = f"/recommendations/halal/live?limit={limit}"
|
||||
if refresh:
|
||||
q += "&refresh=true"
|
||||
return await _proxy("GET", q)
|
||||
|
||||
@router.post("/recommendations/halal/refresh")
|
||||
async def halal_reco_refresh(limit: int = 5):
|
||||
return await _proxy("POST", f"/recommendations/halal/refresh?limit={limit}")
|
||||
|
||||
@router.post("/research/run")
|
||||
async def research_run():
|
||||
return await _proxy("POST", "/research/run")
|
||||
|
||||
Reference in New Issue
Block a user