SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
2.8 KB · 52 lines python
Raw Blame History
1"""/timeline — events grouped by month (global, or one entity + what it develops). API 1.1: `occurred_at`, `is_backfill = false` by default."""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), include_backfill: int = Query(0, ge=0, le=1),18                   date_field: str = Query("occurred", pattern="^(occurred|observed)$")) -> dict[str, Any]:19    col = "ev.observed_at" if date_field == "observed" else "ev.occurred_at"20    where = ["ev.event_type <> 'DOCUMENT_CHANGED'"]21    params: dict[str, Any] = {"lim": limit}22    if not include_backfill:23        where.append("ev.is_backfill = false")24    async with connection() as conn:25        if entity:26            row = await resolve_entity(conn, entity)27            if row["entity_type"] in COMPANY_TYPES:28                where.append("(ev.entity_id = :eid or ev.entity_id in (select id from entities where organization_id = :eid union "29                             "select object_id from relations where subject_id = :eid and predicate in ('develops','owns','operates','published') and valid_to is null))")30            else:31                where.append("ev.entity_id = :eid")32            params["eid"] = row["id"]33        if year:34            where.append(f"extract(year from {col}) = :year")35            params["year"] = year36        if category:37            where.append("ev.category = any(cast(:cats as text[]))")38            params["cats"] = csv(category)39        if importance_min is not None:40            where.append("ev.importance >= :imp")41            params["imp"] = importance_min42        rows = await fetch_all(conn, f"select to_char({col} at time zone 'UTC', 'YYYY-MM') as month, {EVENT_COLS}, ev.occurred_at, ev.is_backfill, ev.group_key from {EVENT_FROM} "43                                     f"where {' and '.join(where)} order by {col} desc, ev.id desc limit :lim", **params)44    groups: dict[str, list[dict[str, Any]]] = {}45    for r in rows:46        ev = change_event(r)47        ev["occurred_at"] = r.get("occurred_at")48        ev["is_backfill"] = r.get("is_backfill")49        groups.setdefault(r["month"], []).append(ev)50    return {"items": [{"month": m, "count": len(evs), "events": evs} for m, evs in groups.items()], "total": len(rows), "date_field": date_field,51            "include_backfill": bool(include_backfill)}52