HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1"""Shared API helpers: errors, pagination, date parsing, Redis response cache, admin auth, IP rate limits, serialisers.23Every route returns plain dicts; `AtlasJSONResponse` renders them with orjson (datetimes → ISO-8601 UTC, Decimal → float)."""4from __future__ import annotations56import hmac7import inspect8import logging9import os10import time11from collections import defaultdict, deque12from collections.abc import Awaitable, Callable13from datetime import UTC, date, datetime14from datetime import time as dtime15from decimal import Decimal16from functools import wraps17from typing import Any1819import orjson20from fastapi import Depends, HTTPException, Query, Request21from fastapi.responses import JSONResponse22from sqlalchemy.ext.asyncio import AsyncConnection2324from aiatlas.config import settings25from aiatlas.db import execute, fetch_one, jsonb, transaction26from aiatlas.services import cache2728log = logging.getLogger("aiatlas.api")2930# ------------------------------------------------------------------------------------------------------------------ errors & JSON313233class ApiError(HTTPException):34 """Always `{"detail": "..."}` — never a stack trace."""3536 def __init__(self, status: int, detail: str):37 super().__init__(status_code=status, detail=detail)383940def _json_default(obj: Any) -> Any:41 if isinstance(obj, Decimal):42 return float(obj)43 if isinstance(obj, (set, frozenset)):44 return sorted(obj, key=str)45 if isinstance(obj, bytes):46 return obj.decode("utf-8", "replace")47 return str(obj)484950_ORJSON_OPTS = orjson.OPT_NON_STR_KEYS | orjson.OPT_UTC_Z | orjson.OPT_NAIVE_UTC515253def dumps(value: Any) -> bytes:54 return orjson.dumps(value, default=_json_default, option=_ORJSON_OPTS)555657def normalize(value: Any) -> Any:58 """Round-trip through orjson so cached and fresh responses are byte-identical (datetimes → strings, Decimal → float)."""59 return orjson.loads(dumps(value))606162class AtlasJSONResponse(JSONResponse):63 media_type = "application/json"6465 def render(self, content: Any) -> bytes:66 return dumps(content)676869# ------------------------------------------------------------------------------------------------------------------ pagination & parsing707172class Pagination:73 def __init__(self, limit: int = Query(50, ge=1, le=200), offset: int = Query(0, ge=0)):74 self.limit = limit75 self.offset = offset767778PAGINATION = Depends() # `p: Pagination = PAGINATION` — FastAPI resolves the class from the annotation798081def page(items: list[Any], total: int, p: Pagination) -> dict[str, Any]:82 return {"items": items, "total": int(total), "limit": p.limit, "offset": p.offset}838485def parse_date(v: str | None, name: str = "date") -> date | None:86 """YYYY-MM-DD (or ISO datetime) → `date`. asyncpg needs real date objects — never cast text in SQL."""87 if v is None or v == "":88 return None89 s = v.strip().replace("Z", "+00:00")90 try:91 if len(s) == 10:92 return date.fromisoformat(s)93 return datetime.fromisoformat(s).astimezone(UTC).date()94 except ValueError as exc:95 raise ApiError(400, f"{name} must be YYYY-MM-DD, got {v!r}") from exc969798def parse_ts(v: str | None, name: str = "timestamp") -> datetime | None:99 """ISO-8601 (date or datetime) → aware UTC `datetime`."""100 if v is None or v == "":101 return None102 s = v.strip().replace("Z", "+00:00")103 try:104 dt = datetime.fromisoformat(s)105 except ValueError as exc:106 raise ApiError(400, f"{name} must be ISO-8601, got {v!r}") from exc107 if dt.tzinfo is None:108 dt = datetime.combine(dt.date(), dt.time() or dtime.min, UTC)109 return dt.astimezone(UTC)110111112def day_bounds(d: date) -> tuple[datetime, datetime]:113 start = datetime.combine(d, dtime.min, UTC)114 return start, datetime.combine(d, dtime.max, UTC).replace(microsecond=999999)115116117def csv(v: str | None) -> list[str]:118 return [x.strip() for x in (v or "").split(",") if x.strip()]119120121def num_expr(path: str) -> str:122 """Safe numeric cast of a JSON text attribute (`e.attributes->>'x'`)."""123 return f"(case when {path} ~ '^-?[0-9]+(\\.[0-9]+)?$' then ({path})::double precision end)"124125126def attr_num(key: str, alias: str = "e") -> str:127 return num_expr(f"{alias}.attributes->>'{key}'")128129130def flip_order(order_sql: str, order: str) -> str:131 """Apply `order=asc|desc` to the primary sort key of a canned ORDER BY fragment (keeps `nulls last`)."""132 if order not in ("asc", "desc"):133 return order_sql134 head, sep, tail = order_sql.partition(",")135 if order == "asc" and " desc" in head:136 head = head.replace(" desc", " asc", 1)137 elif order == "desc" and " asc" in head:138 head = head.replace(" asc", " desc", 1)139 return head + sep + tail140141142# ------------------------------------------------------------------------------------------------------------------ response cache143144145def cache_key(request: Request) -> str:146 items = sorted(request.query_params.multi_items())147 qs = "&".join(f"{k}={v}" for k, v in items)148 return f"{request.url.path}?{qs}" if qs else request.url.path149150151def cached(ttl_s: int) -> Callable[[Callable[..., Awaitable[Any]]], Callable[..., Awaitable[Any]]]:152 """Cache a GET route body in Redis (`aia:api:<path>?<sorted query>`). The route must accept `request: Request`."""153154 def deco(fn: Callable[..., Awaitable[Any]]) -> Callable[..., Awaitable[Any]]:155 @wraps(fn)156 async def wrapper(*args: Any, **kwargs: Any) -> Any:157 request = kwargs.get("request") or next((a for a in args if isinstance(a, Request)), None)158 key = cache_key(request) if request is not None else None159 if key is not None:160 hit = await cache.cache_get(key)161 if hit is not None:162 return hit163 value = normalize(await fn(*args, **kwargs))164 if key is not None:165 await cache.cache_set(key, value, ttl_s)166 return value167168 wrapper.__signature__ = inspect.signature(fn) # type: ignore[attr-defined]169 return wrapper170171 return deco172173174# ------------------------------------------------------------------------------------------------------------------ rate limiting175176_LIMITS: dict[str, tuple[int, float]] = {"search": (60, 60.0), "views": (1, 1.0), "admin": (240, 60.0), "admin-auth-failed": (10, 60.0), "default": (600, 60.0)}177_hits: dict[tuple[str, str], deque[float]] = defaultdict(deque)178179180def trust_proxy() -> bool:181 """`x-forwarded-for` is only honoured behind a trusted reverse proxy (`settings.trust_proxy` or env `AIA_TRUST_PROXY=1`)."""182 v = getattr(settings, "trust_proxy", None)183 if v is None:184 v = os.environ.get("AIA_TRUST_PROXY", "")185 return str(v).strip().lower() in ("1", "true", "yes", "on")186187188def client_ip(request: Request) -> str:189 if trust_proxy():190 fwd = request.headers.get("x-forwarded-for")191 if fwd:192 return fwd.split(",")[0].strip()193 return request.client.host if request.client else "unknown"194195196def _bucket_hit(ip: str, bucket: str, *, record: bool = True) -> bool:197 """Sliding-window counter. Returns True when the call is allowed (and records it), False when the bucket is full."""198 n, window = _LIMITS.get(bucket, _LIMITS["default"])199 now = time.monotonic()200 q = _hits[(ip, bucket)]201 while q and q[0] <= now - window:202 q.popleft()203 if len(q) >= n:204 return False205 if record:206 q.append(now)207 if len(_hits) > 20000:208 for k in [k for k, v in _hits.items() if not v or v[-1] < now - 120]:209 _hits.pop(k, None)210 return True211212213def rate_limit(bucket: str) -> Callable[[Request], None]:214 def dep(request: Request) -> None:215 n, window = _LIMITS.get(bucket, _LIMITS["default"])216 if not _bucket_hit(client_ip(request), bucket):217 raise ApiError(429, f"rate limit exceeded for {bucket}: {n} per {int(window)} s")218219 return dep220221222# ------------------------------------------------------------------------------------------------------------------ admin auth223224225def require_admin(request: Request) -> None:226 """Token check. Failed attempts are counted per IP (10/min → 429) so the token cannot be brute-forced; must run AFTER `rate_limit('admin')`."""227 ip = client_ip(request)228 if not _bucket_hit(ip, "admin-auth-failed", record=False):229 raise ApiError(429, "too many failed admin authentications; retry in a minute")230 expected = settings.admin_token231 if not expected:232 raise ApiError(503, "admin API disabled: AIA_ADMIN_TOKEN is not configured")233 token = request.headers.get("x-aia-admin-token") or ""234 if not hmac.compare_digest(token, expected):235 _bucket_hit(ip, "admin-auth-failed")236 raise ApiError(401, "invalid or missing x-aia-admin-token")237238239ADMIN_DEPENDENCIES = [Depends(rate_limit("admin")), Depends(require_admin)] # order matters: limit first, then auth240241242async def audit(action: str, target: str | None = None, payload: dict[str, Any] | None = None, ip: str | None = None, *, actor: str = "admin") -> None:243 """One row in `admin_audit_log` per admin action (best effort — never breaks the request)."""244 try:245 async with transaction() as conn:246 await execute(conn, "insert into admin_audit_log (actor, action, target, payload, ip) values (:a, :ac, :t, cast(:p as jsonb), :ip)",247 a=actor, ac=action, t=target, p=jsonb(payload or {}), ip=ip)248 except Exception: # noqa: BLE001249 log.warning("audit log write failed", extra={"action": action, "target": target})250251252# ------------------------------------------------------------------------------------------------------------------ entity serialisers253254ENTITY_FIELDS = ("id", "entity_type", "canonical_name", "slug", "description", "status", "organization_id", "attributes", "quality", "counts",255 "first_seen_at", "last_seen_at", "updated_at", "organization_name", "organization_slug")256257258def entity_cols(alias: str = "e", prefix: str = "") -> str:259 """Select-list for an entity alias (+ its organization alias `<alias>o`) with an optional column prefix."""260 org = f"{alias}o"261 cols = [f"{alias}.{c} as {prefix}{c}" for c in ENTITY_FIELDS[:13]]262 cols += [f"{org}.canonical_name as {prefix}organization_name", f"{org}.slug as {prefix}organization_slug"]263 return ", ".join(cols)264265266def entity_join(alias: str, on: str) -> str:267 return f"join entities {alias} on {alias}.id = {on} left join entities {alias}o on {alias}o.id = {alias}.organization_id"268269270ENTITY_COLS = entity_cols("e")271ENTITY_FROM = "entities e left join entities eo on eo.id = e.organization_id"272273SUMMARY_ATTRS: dict[str, tuple[str, ...]] = {274 "model": ("family", "openness", "license", "license_key", "parameter_count", "active_parameter_count", "context_length", "max_output_tokens", "modalities",275 "release_date", "status", "knowledge_cutoff", "hf_repo", "api_model_id", "reasoning", "tool_calling"),276 "artifact": ("openness", "license", "license_key", "parameter_count", "quant_format", "file_size_gb", "weights_dtype", "hf_repo", "release_date", "base_model"),277 "company": ("country", "founded", "website", "org_kind"),278 "organization": ("country", "founded", "website", "org_kind"),279 "lab": ("country", "founded", "website", "org_kind"),280 "university": ("country", "founded", "website", "org_kind"),281 "provider": ("website", "pricing_url"),282 "benchmark": ("category", "metric", "unit"),283 "hardware": ("kind", "memory_gb", "memory_bandwidth_gbs", "release_date", "manufacturer"),284 "framework": ("kind", "latest_version", "latest_release_at", "license", "license_key", "language", "metric.stars", "repository_url", "homepage"),285 "library": ("kind", "latest_version", "latest_release_at", "license", "license_key", "language", "metric.stars", "repository_url", "homepage"),286 "runtime": ("kind", "latest_version", "latest_release_at", "license", "license_key", "language", "metric.stars", "repository_url"),287 "repository": ("latest_version", "latest_release_at", "license", "language", "metric.stars"),288 "paper": ("authors", "published_at", "arxiv_id", "primary_category"),289 "dataset": ("license", "license_key", "modality", "modalities", "publisher", "size", "access", "hf_repo", "hf_dataset", "task", "languages", "release_date"),290}291292293def _fill_license_key(etype: str, attrs: dict[str, Any], out: dict[str, Any]) -> None:294 """Canonical `license_key` derived on read when the writer has not stored it yet (raw label → ontology key; unknown labels stay absent)."""295 if "license_key" not in out and attrs.get("license"):296 from aiatlas.ontology.licenses import normalize_license297298 key = normalize_license(str(attrs["license"]))299 if key:300 out["license_key"] = key301COMPANY_TYPES = ("company", "organization", "lab", "university")302TYPE_LABELS = {"model": "Models", "company": "Companies", "organization": "Organizations", "lab": "Labs", "university": "Universities",303 "provider": "Providers", "paper": "Papers", "benchmark": "Benchmarks", "hardware": "Hardware", "framework": "Frameworks",304 "dataset": "Datasets", "tool": "Tools", "repository": "Repositories", "regulation": "Regulation", "incident": "Incidents",305 "researcher": "Researchers", "agent": "Agents", "product": "Products", "runtime": "Runtimes", "conference": "Conferences",306 "artifact": "Artifacts", "model_family": "Model families", "license": "Licenses"}307308# Canonical model universe (API 1.1): model releases only — artifacts (checkpoints, quantisations, conversions) are `entity_type = 'artifact'`,309# folded evaluation variants carry `merged_into`. `include=artifacts` on /models restores the pre-1.1 universe.310MODEL_UNIVERSE = "e.entity_type = 'model' and e.merged_into is null"311MODEL_OR_ARTIFACT_UNIVERSE = "e.entity_type in ('model', 'artifact') and e.merged_into is null"312OPEN_CATEGORIES = ("open-weights", "open-source") # "open-*"313DOWNLOADABLE_CATEGORIES = ("open-weights", "open-source", "restricted-weights", "restricted") # weights can be downloaded (terms may restrict)314ARTIFACT_KINDS = ("checkpoint", "quantization", "conversion", "packaging")315316317def openness_values(raw: str | None) -> list[str]:318 """Expand the `openness=` filter vocabulary: `open` → open-weights + open-source (+ legacy `open`); `restricted` ↔ `restricted-weights`."""319 vals = [v.strip() for v in (raw or "").split(",") if v.strip()]320 out: list[str] = []321 for v in vals:322 if v == "open":323 out += [*OPEN_CATEGORIES, "open"]324 elif v in ("restricted", "restricted-weights"):325 out += ["restricted", "restricted-weights"]326 elif v == "downloadable":327 out += list(DOWNLOADABLE_CATEGORIES)328 else:329 out.append(v)330 return list(dict.fromkeys(out))331332333def is_open(openness: Any) -> bool:334 return str(openness or "") in OPEN_CATEGORIES335336337def summary_attributes(entity_type: str, attrs: dict[str, Any] | None) -> dict[str, Any]:338 attrs = attrs or {}339 keys = SUMMARY_ATTRS.get(entity_type)340 if keys is None:341 return {k: v for k, v in attrs.items() if isinstance(v, (int, float, bool)) or (isinstance(v, str) and len(v) <= 200)}342 out: dict[str, Any] = {}343 for k in keys:344 if k in attrs and attrs[k] not in (None, "", [], {}):345 v = attrs[k]346 out[k] = v[:5] if k == "authors" and isinstance(v, list) else v347 if "license_key" in keys:348 _fill_license_key(entity_type, attrs, out)349 if "kind" in keys and out.get("kind") and entity_type in ("framework", "library", "runtime"):350 from aiatlas.ontology.taxonomy import normalize_framework_kind351352 canon = normalize_framework_kind(out["kind"])353 if canon and canon != out["kind"]:354 out["kind_raw"], out["kind"] = out["kind"], canon355 return out356357358STATUS_VOCAB = ("active", "preview", "deprecated", "retired", "announced", "limited-availability", "unknown")359STATUS_MAP = {"available": "active", "ga": "active", "general-availability": "active", "released": "active", "live": "active", "beta": "preview",360 "alpha": "preview", "experimental": "preview", "coming-soon": "announced", "upcoming": "announced", "sunset": "retired", "discontinued": "retired",361 "archived": "retired", "legacy": "deprecated", "limited": "limited-availability", "merged": "retired"}362363364def normalize_status(value: Any) -> str:365 s = str(value or "").strip().lower().replace("_", "-").replace(" ", "-")366 s = STATUS_MAP.get(s, s)367 return s if s in STATUS_VOCAB else "unknown"368369370MAIN_ENTITY_TYPES = ("model", "company", "paper", "provider", "benchmark", "hardware", "framework", "dataset", "tool", "repository")371372EVENT_TYPE_LABELS: dict[str, tuple[str, int]] = { # event_type -> (human label, default importance 0–3)373 "NEW_MODEL": ("New model", 3), "MODEL_UPDATED": ("Model updated", 1), "PRICE_CHANGED": ("Price change", 2), "CONTEXT_CHANGED": ("Context window change", 2),374 "MAX_OUTPUT_CHANGED": ("Max output change", 1), "PARAMETERS_CHANGED": ("Parameter count change", 2), "KNOWLEDGE_CUTOFF_CHANGED": ("Knowledge cutoff change", 1),375 "RELEASE_DATE_CHANGED": ("Release date change", 1), "STATUS_CHANGED": ("Status change", 2), "LICENSE_CHANGED": ("License change", 2),376 "OPENNESS_CHANGED": ("Openness change", 2), "CAPABILITIES_CHANGED": ("Capabilities change", 1), "PROPERTY_CHANGED": ("Property change", 1),377 "DEPRECATION_ANNOUNCED": ("Deprecation announced", 2), "RETIREMENT_ANNOUNCED": ("Retirement announced", 2), "BENCHMARK_RESULT": ("Benchmark result", 1),378 "BENCHMARK_UPDATED": ("Benchmark updated", 1), "NEW_PAPER": ("New paper", 2), "NEW_COMPANY": ("New company", 2), "NEW_ORGANIZATION": ("New organization", 2),379 "NEW_LAB": ("New lab", 2), "NEW_UNIVERSITY": ("New university", 1), "NEW_PROVIDER": ("New provider", 2), "NEW_HARDWARE": ("New hardware", 2),380 "NEW_FRAMEWORK": ("New framework", 2), "NEW_LIBRARY": ("New library", 1), "NEW_BENCHMARK": ("New benchmark", 2), "NEW_DATASET": ("New dataset", 2),381 "NEW_REPOSITORY": ("New repository", 1), "NEW_TOOL": ("New tool", 1), "PROVIDER_LISTED": ("Listed by provider", 2), "PROVIDER_DELISTED": ("Delisted by provider", 2),382 "ANNOUNCEMENT": ("Announcement", 2), "RELEASE": ("Release", 2), "VERSION_RELEASED": ("Version released", 1), "DOCUMENT_CHANGED": ("Source document changed", 0),383 "ENTITY_MERGED": ("Duplicate merged", 1), "CLAIM_RETRACTED": ("Claim retracted", 1),384}385386387def event_type_label(event_type: str) -> str:388 known = EVENT_TYPE_LABELS.get(event_type)389 return known[0] if known else event_type.replace("_", " ").capitalize()390391392def event_type_importance(event_type: str) -> int:393 known = EVENT_TYPE_LABELS.get(event_type)394 return known[1] if known else 1395396397async def enrich_provenance(conn: AsyncConnection, *provenances: dict[str, Any] | None) -> None:398 """Add `source_name` (from `sources.name`) to every provenance entry that carries a `source_id` — in place."""399 ids = {v.get("source_id") for p in provenances if p for v in p.values() if isinstance(v, dict) and v.get("source_id")}400 if not ids:401 return402 from aiatlas.db import fetch_all403404 rows = await fetch_all(conn, "select id, name, domain, tier from sources where id = any(cast(:ids as text[]))", ids=sorted(ids))405 names = {r["id"]: r for r in rows}406 for p in provenances:407 for v in (p or {}).values():408 if isinstance(v, dict) and v.get("source_id") in names:409 src = names[v["source_id"]]410 v.setdefault("source_name", src["name"])411 v.setdefault("source_domain", src["domain"])412413414def org_of(row: dict[str, Any], prefix: str = "") -> dict[str, Any] | None:415 oid = row.get(f"{prefix}organization_id")416 if not oid:417 return None418 return {"id": oid, "slug": row.get(f"{prefix}organization_slug"), "name": row.get(f"{prefix}organization_name")}419420421def entity_summary(row: dict[str, Any], prefix: str = "") -> dict[str, Any] | None:422 g = row.get423 if not g(f"{prefix}id"):424 return None425 etype = g(f"{prefix}entity_type") or ""426 desc = g(f"{prefix}description")427 raw_status = g(f"{prefix}status")428 status = normalize_status(raw_status)429 return {"id": g(f"{prefix}id"), "entity_type": etype, "slug": g(f"{prefix}slug"), "name": g(f"{prefix}canonical_name"),430 "description": (desc[:280] if isinstance(desc, str) and len(desc) > 280 else desc), "status": status,431 **({"status_raw": raw_status} if raw_status and raw_status != status else {}),432 "organization": org_of(row, prefix), "attributes": summary_attributes(etype, g(f"{prefix}attributes")),433 "quality": g(f"{prefix}quality") or {}, "counts": g(f"{prefix}counts") or {},434 "first_seen_at": g(f"{prefix}first_seen_at"), "last_seen_at": g(f"{prefix}last_seen_at"), "updated_at": g(f"{prefix}updated_at")}435436437def change_event(row: dict[str, Any], prefix: str = "e_") -> dict[str, Any]:438 return {"id": row["id"], "event_type": row["event_type"], "category": row["category"], "property": row.get("property"),439 "old_value": row.get("old_value"), "new_value": row.get("new_value"), "summary": row.get("summary"), "importance": row.get("importance"),440 "observed_at": row.get("observed_at"), "effective_at": row.get("effective_at"), "source_url": row.get("source_url"),441 "connector_name": row.get("connector_name"), "entity": entity_summary(row, prefix), "meta": row.get("meta") or {}}442443444EVENT_COLS = ("ev.id, ev.event_type, ev.category, ev.property, ev.old_value, ev.new_value, ev.summary, ev.importance, ev.observed_at, ev.effective_at, "445 "ev.source_url, ev.connector_name, ev.meta, " + entity_cols("e", "e_"))446EVENT_FROM = "change_events ev left join entities e on e.id = ev.entity_id left join entities eo on eo.id = e.organization_id"447448449def price_row(row: dict[str, Any]) -> dict[str, Any]:450 return {"id": row["id"], "model": entity_summary(row, "m_"), "provider": entity_summary(row, "p_"), "provider_model_id": row.get("provider_model_id"),451 "input_per_mtok": row.get("input_per_mtok"), "output_per_mtok": row.get("output_per_mtok"), "cached_input_per_mtok": row.get("cached_input_per_mtok"),452 "cache_write_per_mtok": row.get("cache_write_per_mtok"), "batch_input_per_mtok": row.get("batch_input_per_mtok"),453 "batch_output_per_mtok": row.get("batch_output_per_mtok"), "per_image": row.get("per_image"), "per_request": row.get("per_request"),454 "currency": row.get("currency"), "context_length": row.get("context_length"), "max_output_tokens": row.get("max_output_tokens"),455 "features": row.get("features") or {}, "observed_at": row.get("observed_at"), "valid_from": row.get("valid_from"), "valid_to": row.get("valid_to"),456 "source_url": row.get("source_url"), "tier": row.get("tier")}457458459PRICE_COLS = ("p.id, p.provider_model_id, p.input_per_mtok, p.output_per_mtok, p.cached_input_per_mtok, p.cache_write_per_mtok, p.batch_input_per_mtok, "460 "p.batch_output_per_mtok, p.per_image, p.per_request, p.currency, p.context_length, p.max_output_tokens, p.features, p.observed_at, p.valid_from, "461 "p.valid_to, p.source_url, p.tier, " + entity_cols("m", "m_") + ", " + entity_cols("pv", "p_"))462PRICE_FROM = "prices p " + entity_join("m", "p.model_id") + " " + entity_join("pv", "p.provider_id")463464_NATIVE_UNIT_SUFFIXES = ("_per_mtok", "_per_1k_requests", "_per_mtok_hour", "_per_image", "_per_request", "_per_second", "_per_minute", "_per_hour", "_per_char")465466467def deployment_row(row: dict[str, Any]) -> dict[str, Any]:468 """`Deployment` (API 1.1): one model × provider × provider_model_id offer. Prices in USD per 1M tokens unless the key says otherwise;469 provider-specific priced features (`flex_input_per_mtok`, `search_grounding_per_1k_requests`…) are kept verbatim under `native_units`."""470 feats = dict(row.get("features") or {})471 native = {k: v for k, v in feats.items() if any(k.endswith(s) for s in _NATIVE_UNIT_SUFFIXES) or k == "per_request"}472 other = {k: v for k, v in feats.items() if k not in native}473 return {"id": row["id"], "model": entity_summary(row, "m_"), "provider": entity_summary(row, "p_"), "provider_model_id": row.get("provider_model_id"),474 "context_length": row.get("context_length"), "max_output_tokens": row.get("max_output_tokens"),475 "prices": {"input": row.get("input_per_mtok"), "cached_input": row.get("cached_input_per_mtok"), "cache_write": row.get("cache_write_per_mtok"),476 "output": row.get("output_per_mtok"), "batch_input": row.get("batch_input_per_mtok"), "batch_output": row.get("batch_output_per_mtok"),477 "per_image": row.get("per_image"), "per_request": row.get("per_request"), "currency": row.get("currency") or "USD", "unit": "USD per 1M tokens",478 "native_units": native},479 "features": other, "status": "active" if row.get("valid_to") is None else "delisted", "observed_at": row.get("observed_at"),480 "valid_from": row.get("valid_from"), "valid_to": row.get("valid_to"), "source_url": row.get("source_url"), "tier": row.get("tier")}481482483def result_row(row: dict[str, Any]) -> dict[str, Any]:484 return {"id": row["id"], "model": entity_summary(row, "m_"), "benchmark": entity_summary(row, "b_"), "score": row.get("score"), "metric": row.get("metric"),485 "unit": row.get("unit"), "higher_is_better": row.get("higher_is_better"), "config": row.get("config") or {}, "evaluated_at": row.get("evaluated_at"),486 "observed_at": row.get("observed_at"), "source_url": row.get("source_url"), "tier": row.get("tier"), "confidence": row.get("confidence"),487 "valid_to": row.get("valid_to")}488489490RESULT_COLS = ("r.id, r.score, r.metric, r.unit, r.higher_is_better, r.config, r.evaluated_at, r.observed_at, r.source_url, r.tier, r.confidence, r.valid_to, "491 + entity_cols("m", "m_") + ", " + entity_cols("b", "b_"))492RESULT_FROM = "benchmark_results r " + entity_join("m", "r.model_id") + " " + entity_join("b", "r.benchmark_id")493RESULT_ORDER = "case when r.higher_is_better then -r.score else r.score end, r.observed_at desc"494495496def claim_row(row: dict[str, Any]) -> dict[str, Any]:497 return {"id": row["id"], "property": row["property"], "value": row.get("value"), "unit": row.get("unit"), "tier": row.get("tier"),498 "confidence": row.get("confidence"), "status": row.get("status"), "extractor": row.get("extractor"), "observed_at": row.get("observed_at"),499 "effective_at": row.get("effective_at"), "valid_from": row.get("valid_from"), "valid_to": row.get("valid_to"), "source_url": row.get("source_url"),500 "source_name": row.get("source_name")}501502503CLAIM_COLS = ("c.id, c.property, c.value, c.unit, c.tier, c.confidence, c.status, c.extractor, c.observed_at, c.effective_at, c.valid_from, c.valid_to, "504 "c.source_url, s.name as source_name")505CLAIM_FROM = "claims c left join sources s on s.id = c.source_id"506507508# ------------------------------------------------------------------------------------------------------------------ entity resolution509510511ENTITY_EXTRA_COLS = "e.provenance, e.merged_into, e.family_id, e.canonical_id, e.artifact_kind, e.identity_confidence"512513514async def resolve_entity(conn: AsyncConnection, slug_or_id: str, types: tuple[str, ...] | None = None, *, aliases: bool = False) -> dict[str, Any]:515 """Slug first, then id (then alias when `aliases=True`); follows `merged_into` and records the hop in `redirected_from`516 (`{slug, id}` of the row the caller asked for) so the web layer can 301; 404 when absent or when the type does not match the mounted alias."""517 key = slug_or_id.strip()518 if not key or len(key) > 200:519 raise ApiError(404, "entity not found")520 sel = f"select {ENTITY_COLS}, {ENTITY_EXTRA_COLS} from {ENTITY_FROM}"521 row = await fetch_one(conn, f"{sel} where e.slug = :k or e.id = :k order by case when e.slug = :k then 0 else 1 end limit 1", k=key)522 if not row and aliases:523 row = await fetch_one(conn, f"{sel} where e.id = (select a.entity_id from entity_aliases a join entities x on x.id = a.entity_id "524 f"where (a.alias_norm = :n or lower(a.alias) = lower(:k)) {'and x.entity_type = any(cast(:types as text[]))' if types else ''} "525 f"order by x.merged_into is not null, x.updated_at desc limit 1)", k=key, n=_alias_norm(key), types=list(types or ()))526 asked = row527 hops = 0528 while row and row.get("merged_into") and hops < 5:529 row = await fetch_one(conn, f"{sel} where e.id = :k", k=row["merged_into"])530 hops += 1531 if not row:532 raise ApiError(404, "entity not found")533 if types and row["entity_type"] not in types:534 raise ApiError(404, f"entity {key!r} is a {row['entity_type']}, not one of {', '.join(types)}")535 if asked is not None and asked["id"] != row["id"]:536 row["redirected_from"] = {"slug": asked["slug"], "id": asked["id"], "entity_type": asked["entity_type"]}537 return row538539540def _alias_norm(s: str) -> str:541 try:542 from aiatlas.ids import normalize_alias543544 return normalize_alias(s)545 except Exception: # noqa: BLE001546 return "".join(ch for ch in s.lower() if ch.isalnum())547548549async def resolve_id(conn: AsyncConnection, slug_or_id: str | None, types: tuple[str, ...] | None = None) -> str | None:550 if not slug_or_id:551 return None552 return (await resolve_entity(conn, slug_or_id, types))["id"]553554555__all__ = [556 "ADMIN_DEPENDENCIES",557 "ARTIFACT_KINDS",558 "CLAIM_COLS",559 "CLAIM_FROM",560 "COMPANY_TYPES",561 "DOWNLOADABLE_CATEGORIES",562 "ENTITY_COLS",563 "ENTITY_EXTRA_COLS",564 "ENTITY_FROM",565 "EVENT_COLS",566 "EVENT_FROM",567 "EVENT_TYPE_LABELS",568 "MAIN_ENTITY_TYPES",569 "MODEL_OR_ARTIFACT_UNIVERSE",570 "MODEL_UNIVERSE",571 "OPEN_CATEGORIES",572 "PAGINATION",573 "PRICE_COLS",574 "PRICE_FROM",575 "RESULT_COLS",576 "RESULT_FROM",577 "RESULT_ORDER",578 "STATUS_VOCAB",579 "TYPE_LABELS",580 "ApiError",581 "AtlasJSONResponse",582 "Pagination",583 "attr_num",584 "audit",585 "cache_key",586 "cached",587 "change_event",588 "claim_row",589 "client_ip",590 "csv",591 "day_bounds",592 "deployment_row",593 "dumps",594 "enrich_provenance",595 "entity_cols",596 "entity_join",597 "entity_summary",598 "event_type_importance",599 "event_type_label",600 "flip_order",601 "is_open",602 "normalize",603 "normalize_status",604 "num_expr",605 "openness_values",606 "org_of",607 "page",608 "parse_date",609 "parse_ts",610 "price_row",611 "rate_limit",612 "require_admin",613 "resolve_entity",614 "resolve_id",615 "result_row",616 "trust_proxy",617]618