1"""/timeline — events grouped by month (global, or one entity + what it develops)."""2from __future__ import annotations34from typing import Any56from fastapi import APIRouter, Query, Request78from aiatlas.api.common import COMPANY_TYPES, EVENT_COLS, EVENT_FROM, cached, change_event, csv, resolve_entity9from aiatlas.db import connection, fetch_all1011router = APIRouter(prefix="/api/v1/timeline", tags=["timeline"])121314@router.get("")15@cached(120)16async def timeline(request: Request, entity: str | None = None, year: int | None = Query(None, ge=1950, le=2100), category: str | None = None,17 importance_min: int | None = Query(None, ge=0, le=3), limit: int = Query(200, ge=1, le=1000)) -> dict[str, Any]:18 where = ["ev.event_type <> 'DOCUMENT_CHANGED'"]19 params: dict[str, Any] = {"lim": limit}20 async with connection() as conn:21 if entity:22 row = await resolve_entity(conn, entity)23 if row["entity_type"] in COMPANY_TYPES:24 where.append("(ev.entity_id = :eid or ev.entity_id in (select id from entities where organization_id = :eid union "25 "select object_id from relations where subject_id = :eid and predicate in ('develops','owns','operates','published') and valid_to is null))")26 else:27 where.append("ev.entity_id = :eid")28 params["eid"] = row["id"]29 if year:30 where.append("extract(year from coalesce(ev.effective_at, ev.observed_at)) = :year")31 params["year"] = year32 if category:33 where.append("ev.category = any(cast(:cats as text[]))")34 params["cats"] = csv(category)35 if importance_min is not None:36 where.append("ev.importance >= :imp")37 params["imp"] = importance_min38 rows = await fetch_all(conn, f"select to_char(coalesce(ev.effective_at, ev.observed_at) at time zone 'UTC', 'YYYY-MM') as month, {EVENT_COLS} from {EVENT_FROM} "39 f"where {' and '.join(where)} order by coalesce(ev.effective_at, ev.observed_at) desc, ev.id desc limit :lim", **params)40 groups: dict[str, list[dict[str, Any]]] = {}41 for r in rows:42 groups.setdefault(r["month"], []).append(change_event(r))43 return {"items": [{"month": m, "count": len(evs), "events": evs} for m, evs in groups.items()], "total": len(rows)}44