Files
politics-tracking/backend/politrack/openapi_ts.py
T
Simon DiesenreiterandClaude Opus 5.5 9da5a1dc48 Politrack: track Austrian politicians from public sources
Initial import of Politrack, a self-hosted service that tracks Austrian
politicians from federal to municipal level - roles, votes, money flows,
statements, meetings, social media and legal proceedings - with every claim
linked to its sources.

- backend: FastAPI + Postgres/pgvector; adapter framework (fetch -> parse ->
  load -> enrich) with robots.txt/TDM-aware fetching, a raw-response archive
  for reparsing, and a claim/evidence model with corroboration
- sources: Parliament, Wikidata, EU Parliament/Commission, Council of
  Ministers, Rechnungshof party reports and donations, procurement, RTR media
  transparency, political ad libraries, WKStA, news feeds, Wikipedia edits,
  Bluesky, Mastodon, public Telegram channels, YouTube and X (official API)
- web: Next.js frontend in German and English with typed API client
- mcp: MCP server for AI agents and RAG
- docs: architecture, data model, sources, legal notes, Unraid deployment and
  contributor guides; Docker Compose setup for Unraid

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:47:14 +01:00

80 lines
2.9 KiB
Python

"""Generate TypeScript types from the OpenAPI schema (no npm needed).
Supports the JSON-Schema subset FastAPI/Pydantic emit: objects, arrays, enums/Literals, anyOf with null,
$ref, additionalProperties, primitive formats. Output: one `export type X = {...}` per component schema
plus `Paths` mapping GET endpoints to their response types.
"""
from __future__ import annotations
from typing import Any
HEADER = "// Generated by `politrack openapi --ts` from the FastAPI schema. Do not edit by hand.\n\n"
def ts_type(schema: dict[str, Any]) -> str:
if "$ref" in schema:
return schema["$ref"].rsplit("/", 1)[-1]
if "const" in schema:
return _literal(schema["const"])
if "enum" in schema:
return " | ".join(_literal(v) for v in schema["enum"])
for key in ("anyOf", "oneOf"):
if key in schema:
parts = []
for sub in schema[key]:
part = ts_type(sub)
if part not in parts:
parts.append(part)
return " | ".join(parts)
kind = schema.get("type")
if kind == "array":
inner = ts_type(schema.get("items", {}))
return f"({inner})[]" if "|" in inner else f"{inner}[]"
if kind == "object":
if "properties" in schema:
return _object(schema)
extra = schema.get("additionalProperties")
value = ts_type(extra) if isinstance(extra, dict) and extra else "unknown"
return f"Record<string, {value}>"
if kind in ("integer", "number"):
return "number"
if kind == "boolean":
return "boolean"
if kind == "string":
return "string"
if kind == "null":
return "null"
return "unknown"
def _literal(value: Any) -> str:
return f"'{value}'" if isinstance(value, str) else ("null" if value is None else str(value).lower())
def _object(schema: dict[str, Any], indent: str = "") -> str:
required = set(schema.get("required", []))
lines = ["{"]
for name, prop in schema.get("properties", {}).items():
optional = "" if name in required else "?"
lines.append(f"{indent} {name}{optional}: {ts_type(prop)};")
lines.append(f"{indent}}}")
return "\n".join(lines)
def to_typescript(openapi: dict[str, Any]) -> str:
out = [HEADER]
for name, schema in sorted(openapi.get("components", {}).get("schemas", {}).items()):
if name in ("HTTPValidationError", "ValidationError"):
continue
out.append(f"export type {name} = {ts_type(schema)};\n")
out.append("\nexport type Paths = {")
for path, ops in sorted(openapi.get("paths", {}).items()):
get = ops.get("get")
if not get:
continue
content = get.get("responses", {}).get("200", {}).get("content", {}).get("application/json", {})
out.append(f" '{path}': {ts_type(content.get('schema', {})) if content.get('schema') else 'unknown'};")
out.append("};\n")
return "\n".join(out)