"""Seed one local rules-only demo run through the public HTTP API.""" import json import os import time from pathlib import Path from typing import Any from urllib.parse import urlsplit import httpx from ai_platform.infrastructure.config.settings import Settings from ai_platform.presentation.http.schemas import RunRequest FIXTURE = Path(__file__).resolve().parents[1] / "tests" / "fixtures" / "mock_omni_run.json" BASE_URL = "http://127.0.0.1:8000" DEMO_APP_KEY_ENV = "AI_PLATFORM_DEMO_APP_API_KEY" POLL_ATTEMPTS = 100 POLL_INTERVAL_SECONDS = 0.1 def load_payload() -> dict[str, Any]: return json.loads(FIXTURE.read_text()) def validate_local_target(settings: Settings) -> None: database_name = urlsplit(settings.database_url).path.rstrip("/").rsplit("/", 1)[-1] database_host = urlsplit(settings.database_url).hostname if settings.environment != "development": raise SystemExit("Refusing demo seed: AI_PLATFORM_ENVIRONMENT must be development") if database_name != "ai_platform" or database_host not in { "127.0.0.1", "localhost", "::1", "postgres", }: raise SystemExit( "Refusing demo seed: API target must use the local ai_platform development database" ) def app_headers(existing_app_key: str | None) -> dict[str, str]: if not existing_app_key: raise SystemExit( f"Demo seed blocked: set {DEMO_APP_KEY_ENV} to an operator-issued app key; " "no credential was created or rotated" ) return {"Authorization": f"Bearer {existing_app_key}"} def read_run(client: httpx.Client, run_id: str, headers: dict[str, str]) -> dict[str, Any]: for _ in range(POLL_ATTEMPTS): response = client.get(f"/v1/lead-scoring/runs/{run_id}", headers=headers) if response.status_code != 200: raise SystemExit( f"Demo seed blocked: run readback returned HTTP {response.status_code}" ) run = response.json() if run.get("status") in {"completed", "failed"}: return run time.sleep(POLL_INTERVAL_SECONDS) raise SystemExit("Demo seed blocked: run did not reach a terminal state") def persisted_snapshot(run: dict[str, Any]) -> dict[str, Any] | None: snapshot = run.get("snapshot") if isinstance(snapshot, dict): return snapshot request = run.get("request") if isinstance(request, dict) and isinstance(request.get("snapshot"), dict): return request["snapshot"] return None def seed_demo( client: httpx.Client, settings: Settings, existing_app_key: str | None = None, ) -> tuple[str, dict[str, Any]]: payload = load_payload() if payload["scoring_config"]["ai_weight"] != 0: raise SystemExit("Demo seed requires a rules-only fixture; it must not claim AI success") headers = app_headers(existing_app_key) response = client.post( "/v1/lead-scoring/runs", headers={**headers, "Idempotency-Key": payload["request_id"]}, json=payload, ) if response.status_code not in {200, 202}: raise SystemExit(f"Demo seed blocked: run admission returned HTTP {response.status_code}") run_id = response.json().get("run_id") if not isinstance(run_id, str) or not run_id: raise SystemExit("Demo seed blocked: run admission response omitted run_id") run = read_run(client, run_id, headers) if run.get("status") != "completed": raise SystemExit("Demo seed blocked: the persisted run did not complete") if run.get("ai_score") is not None or run.get("degraded") is not False: raise SystemExit("Demo seed blocked: rules-only run returned an unexpected AI result") expected_snapshot = RunRequest.model_validate(payload).model_dump(mode="json")["snapshot"] if persisted_snapshot(run) != expected_snapshot: raise SystemExit( "Demo seed blocked: run readback did not return the persisted conversation snapshot" ) return run_id, run def main() -> None: settings = Settings.load() validate_local_target(settings) existing_app_key = os.environ.get(DEMO_APP_KEY_ENV) try: with httpx.Client(base_url=BASE_URL, timeout=5) as client: run_id, run = seed_demo(client, settings, existing_app_key) except httpx.HTTPError as exc: raise SystemExit( "Demo seed blocked: local backend at 127.0.0.1:8000 is unavailable" ) from exc snapshot = load_payload()["snapshot"] conversations = len(snapshot["conversations"]) messages = sum(len(conversation["messages"]) for conversation in snapshot["conversations"]) print( f"Seeded or reused local demo run {run_id}: status={run['status']}, " f"score={run.get('final_score')}, persisted conversations={conversations}, messages={messages}." ) print("AI execution was not requested; the run uses deterministic rules only.") if __name__ == "__main__": main()