"""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_TENANT = "local-demo-seed" DEMO_TENANT_KEY_ENV = "AI_PLATFORM_DEMO_TENANT_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 tenant_headers( client: httpx.Client, settings: Settings, existing_tenant_key: str | None, ) -> dict[str, str]: if existing_tenant_key: return {"Authorization": f"Bearer {existing_tenant_key}"} if settings.tenant_provisioning_secret is None: raise SystemExit( "Demo seed blocked: tenant provisioning secret and existing demo tenant key are required" ) response = client.post( "/v1/platform/tenant-keys", headers={ "Authorization": ("Bearer " + settings.tenant_provisioning_secret.get_secret_value()) }, json={"tenant_id": DEMO_TENANT}, ) if response.status_code == 409: raise SystemExit( "Demo seed blocked: the demo tenant already exists and its one-time key is unavailable; " f"set {DEMO_TENANT_KEY_ENV} to its existing key. No key was rotated or data changed." ) if response.status_code != 201: raise SystemExit( f"Demo seed blocked: tenant provisioning returned HTTP {response.status_code}" ) tenant_key = response.json().get("tenant_key") if not isinstance(tenant_key, str) or not tenant_key: raise SystemExit("Demo seed blocked: tenant provisioning response omitted its key") return {"Authorization": f"Bearer {tenant_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_tenant_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 = tenant_headers(client, settings, existing_tenant_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_tenant_key = os.environ.get(DEMO_TENANT_KEY_ENV) try: with httpx.Client(base_url=BASE_URL, timeout=5) as client: run_id, run = seed_demo(client, settings, existing_tenant_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()