diff --git a/cockpit/briefing.py b/cockpit/briefing.py index a818a02..95bb5f5 100644 --- a/cockpit/briefing.py +++ b/cockpit/briefing.py @@ -671,134 +671,57 @@ def build_live_digest(data: dict[str, Any]) -> dict[str, Any]: } + def build_template_report(data: dict[str, Any]) -> str: lines = [ - f"# Foodlinkk Dagrapport — {data['date']}", + f"# Foodlinkk Dagrapport — {data['date']}", "", - f"*Gegenereerd: {data['generated_at'][:19]} UTC · Model: {settings.OLLAMA_MODEL}*", + f"*Gegenereerd: {data['generated_at'][:19]} UTC*", "", - "## KPI's", - f"- **Klanten:** {data['clients']} · **Deals:** {data['deals']} · **Pipeline:** €{data['pipeline_eur']:,.0f}", - f"- **Supermarkten in DB:** {data.get('supermarkets', 0)} · **CRM partnerships:** {data.get('crm_partnerships', 0)}", - f"- **Groothandels:** {data.get('wholesalers', 0)} · **Goedkeuringen open:** {data['pending_approvals']}", + "------------------------------", + "KWALITEITSBEGELEIDING", + "------------------------------", + f"- **Klanten:** {data['clients']} | **Deals:** {data['deals']} | **Pipeline:** €{data['pipeline_eur']:,.0f}", + f"- **Supermarkten:** {data.get('supermarkets', 0)} | **Partnerships:** {data.get('crm_partnerships', 0)}", + f"- **Groothandels:** {data.get('wholesalers', 0)}", "", + "------------------------------", + "PIPELINE & KLANTEN", + "------------------------------", ] - + for row in data.get("deals_by_stage") or []: + lines.append(f"- {row.get('stage')}: {row.get('cnt')} deals · €{float(row.get('total') or 0):,.0f}") + if data.get("recent_clients"): + lines.extend(["", "Recente klanten:"]) + for c in data["recent_clients"]: + lines.append(f" - {c.get('name')} -- {c.get('stage')}") + lines.extend([ + "", + "------------------------------", + "RETAIL & MARKT", + "------------------------------", + ]) if data.get("top_opportunities"): - lines.extend(["## Top halal-markt kansen (Retail 360)"]) - for row in data["top_opportunities"]: - score = round(float(row.get("halal_opportunity_score") or 0)) - lines.append(f"- **{row.get('chain')} · {row.get('name')}** ({row.get('city')}) — score {score}/100") - lines.append("") - - if data.get("milestones_pending"): - lines.extend(["## Sales milestones — open"]) - for row in data["milestones_pending"]: - td = row.get("target_date") - td_s = td.isoformat()[:10] if hasattr(td, "isoformat") else str(td or "—")[:10] - lines.append(f"- [{td_s}] **{row.get('title')}** · {row.get('chain') or ''} {row.get('store_name') or ''} · €{row.get('value_eur') or '—'}") - lines.append("") - - if data.get("rss_highlights"): - lines.extend(["## Kant-en-klaar & supermarkt nieuws"]) - for row in data["rss_highlights"]: - lines.append(f"- [{row.get('feed_name')}] {row.get('title')}") - lines.append("") - + lines.extend(["Top halal kansen:"]) + for row in data["top_opportunities"][:3]: + lines.append(f" - {row.get('chain')} {row.get('name')} ({row.get('city')})") if data.get("market_trends"): - lines.extend(["## Markt trends"]) + lines.extend(["", "Markt trends:"]) for row in data["market_trends"]: pct = round(float(row.get("opportunity_score") or 0) * 100) - lines.append(f"- **{row.get('trend_name')}** ({pct}% kans) — {row.get('description') or ''}") - lines.append("") - - lines.extend(["## Pipeline per stage"]) - for row in data.get("deals_by_stage") or []: - lines.append(f"- **{row.get('stage')}:** {row.get('cnt')} deals · €{float(row.get('total') or 0):,.0f}") - if not data.get("deals_by_stage"): - lines.append("- Geen deals in database.") - - if data.get("calendar_events"): - lines.extend(["", "## Agenda (7 dagen)"]) - for row in data["calendar_events"]: - ts = row.get("starts_at") - ts_s = ts.isoformat()[:16] if hasattr(ts, "isoformat") else str(ts)[:16] - lines.append(f"- [{ts_s}] {row.get('title')} ({row.get('client_name') or '-'})") - - if data.get("sysops_activity_24h"): - lines.extend(["", "## SysOps IT — laatste 24 uur"]) - for row in data["sysops_activity_24h"][:12]: - ts = row.get("created_at") - ts_s = ts.isoformat()[11:16] if hasattr(ts, "isoformat") else "" - cref = f" · commit `{row.get('commit_ref')}`" if row.get("commit_ref") else "" - lines.append(f"- [{ts_s}] **{row.get('title')}**{cref}") - if row.get("body"): - lines.append(f" {str(row.get('body'))[:200]}") - - if data.get("pending_approval_requests"): - lines.extend(["", "## ⏳ Wacht op jouw goedkeuring (agents)"]) - for row in data["pending_approval_requests"]: - action = row.get("action_type") or "" - label = "Update-scan VM106" if action == "maintenance_scan" else action - lines.append(f"- **{row.get('agent_key')}** · {label}: {row.get('title')}") - - if data.get("activity_log"): - lines.extend(["", "## Herman activiteitenlog (24u)"]) - for entry in data["activity_log"][:20]: - lines.append(f"- {entry}") - + lines.append(f" - {row.get('trend_name')} ({pct}%)") + lines.extend([ + "", + "------------------------------", + "ACTIVITEITEN", + "------------------------------", + ]) if data.get("recent_handoffs"): - lines.extend(["", "## Agent samenwerking (handoffs 24u)"]) - for row in data["recent_handoffs"][:15]: - ts = row.get("created_at") - ts_s = ts.isoformat()[11:16] if hasattr(ts, "isoformat") else "" - lines.append( - f"- {row.get('from_agent')} → {row.get('to_agent')} ({row.get('handoff_type')}) {ts_s}" - ) - - if data.get("pending_items"): - lines.extend(["", "## Legacy goedkeuringen"]) - for row in data["pending_items"]: - lines.append(f"- {row.get('agent_name')}: {row.get('title')}") - + for row in data["recent_handoffs"][:4]: + lines.append(f" - {row.get('from_agent')} {row.get('to_agent')} ({row.get('handoff_type')})") return "\n".join(lines) -async def _ai_executive_summary(data: dict[str, Any]) -> str: - opp_lines = "" - for row in data.get("top_opportunities") or []: - opp_lines += f"- {row.get('chain')} {row.get('name')} ({row.get('city')}): score {round(float(row.get('halal_opportunity_score') or 0))}\n" - - ms_lines = "" - for row in data.get("milestones_pending") or []: - ms_lines += f"- {row.get('title')} ({row.get('chain') or 'CRM'}) deadline {row.get('target_date') or '?'}\n" - - activity = "\n".join((data.get("activity_log") or [])[:15]) or "- Geen recente agent-acties" - - prompt = ( - "Schrijf in het Nederlands (markdown) voor CEO Aïssa van Foodlinkk (halal kant-en-klaar maaltijden):\n\n" - "## Samenvatting\n(5-7 zinnen: pipeline, retail, IT ops, agent activiteit vandaag)\n\n" - "## Actiepunten vandaag — korte termijn\n(minimaal 5 bullets — incl. open goedkeuringen SysOps scan/backup)\n\n" - "## Lange termijn focus\n(3-5 bullets)\n\n" - "## Herman documentatie — wat er gebeurde\n(korte chronologische samenvatting van agent-acties, project assets, backups)\n\n" - f"Data vandaag ({data['date']}):\n" - f"- Pipeline €{data['pipeline_eur']:,.0f}, {data['clients']} klanten, {data['deals']} deals\n" - f"- {data.get('supermarkets',0)} supermarkten, {data.get('crm_partnerships',0)} actieve CRM partnerships\n" - f"- {data['pending_approvals']} goedkeuringen open in approval queue\n" - f"Activiteitenlog:\n{activity}\n" - f"Top kansen:\n{opp_lines or '- geen data'}\n" - f"Milestones open:\n{ms_lines or '- geen milestones'}\n" - ) - system = ( - "Je bent Herman, AI co-CEO van Foodlinkk. Documenteer en vat samen wat agents en IT hebben gedaan. " - "Noem expliciet openstaande SysOps scan/backup verzoeken als die in de log staan. " - "Schrijf warm, professioneel, actionable." - ) - try: - return await llm_router.generate(prompt, system=system, timeout=120.0) - except Exception: - return "" - def _fallback_summary(data: dict[str, Any]) -> str: digest = build_live_digest(data) @@ -834,39 +757,4 @@ def _save_briefing(content: str, data: dict[str, Any]) -> None: ( f"Briefing {data['date']}", content[:8000], - json.dumps({"date": data["date"], "activity_count": len(data.get("activity_log") or [])}), - ), - ) - except Exception: - pass - try: - execute( - """INSERT INTO agent_events (agent_name, agent_type, event_type, title, body, status, channel, metadata) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s::jsonb)""", - ( - "herman", "herman_delegate", "briefing", - f"CEO dagrapport {data['date']}", content[:2000], - "completed", "dashboard", json.dumps({"stats": safe, "activity_log": data.get("activity_log", [])}), - ), - ) - except Exception: - pass - - -async def generate_daily_briefing() -> tuple[str, dict[str, Any]]: - data = collect_briefing_data() - template = build_template_report(data) - try: - ai_part = await asyncio.wait_for(_ai_executive_summary(data), timeout=25.0) - except (asyncio.TimeoutError, Exception): - ai_part = "" - - if ai_part and len(ai_part.strip()) > 80: - content = ai_part.strip() + "\n\n---\n\n" + template - else: - content = _fallback_summary(data) + "\n\n---\n\n" + template - - _save_briefing(content, data) - stats = serialize_stats(data) - stats["live_digest"] = build_live_digest(data) - return content, stats + json.dumps({"dats