Ship Herman chat, Hermes KPI explorer, and intelligence analytics.

Adds floating Herman assistant with voice, Hermes technical KPI dashboard with file explorer, and a wins/kansen analytics pulse with Hermes findings hooks. Includes optional local HTTPS via Caddy for mic permissions (public certs only).
This commit is contained in:
Aissa
2026-08-01 17:50:45 +00:00
parent 62239f0a4e
commit 5e43b6b246
28 changed files with 2686 additions and 1149 deletions
+34 -4
View File
@@ -1,17 +1,28 @@
from pathlib import Path
from typing import Optional
from typing import Any, Optional
from fastapi import APIRouter, Query, Request
from fastapi import APIRouter, Body, Query, Request
from fastapi.responses import JSONResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel, Field
from app.services.analytics_data import collect_analytics
from app.services.analytics_data import add_finding, collect_analytics, list_hermes_findings
router = APIRouter(prefix="/analytics", tags=["analytics"])
BASE_DIR = Path(__file__).resolve().parent.parent.parent
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
class FindingIn(BaseModel):
kind: str = Field(default="insight", description="win|opportunity|attention|insight")
title: str
body: str = ""
score: float | None = None
source: str = "hermes"
href: str | None = None
meta: dict[str, Any] | None = None
@router.get("")
async def analytics_page(request: Request):
initial = collect_analytics({})
@@ -30,5 +41,24 @@ async def analytics_api(
days: int = Query(90, ge=7, le=365),
):
filters = {"chain": chain, "province": province, "stage": stage, "agent": agent, "days": days}
filters = {k: v for k, v in filters.items() if v is not None and v != ""}
return JSONResponse(collect_analytics(filters))
@router.get("/api/findings")
async def analytics_findings(limit: int = Query(20, ge=1, le=100)):
return {"items": list_hermes_findings(limit)}
@router.post("/api/findings")
async def analytics_findings_create(payload: FindingIn = Body(...)):
"""Hermes (of admin) kan bevindingen pushen naar het intelligence dashboard."""
row = add_finding(
kind=payload.kind,
title=payload.title,
body=payload.body,
score=payload.score,
source=payload.source,
href=payload.href,
meta=payload.meta,
)
return {"ok": True, "finding": row}