"""Case lifecycle: create a new case (generate, or load by ID) and fetch a public view. New Case generates a fresh, solvable mystery via the in-process LLM (buffered one-ahead); if generation is unavailable (no weights / load failure) it falls back to the GRAYMOOR-3107 golden case so the loop is always playable. Generated cases are persisted by Case ID, so a shared ID reloads the identical mystery. The sealed solution is never in any response here. """ from __future__ import annotations from fastapi import APIRouter, HTTPException from ..persistence.golden import golden_exists, load_golden from ..persistence.run_store import create_run from .dto import CaseResponse, NewCaseRequest from .public_view import golden_to_public from .runtime import RUNTIME router = APIRouter(prefix="/api", tags=["case"]) DEFAULT_CASE_ID = "GRAYMOOR-3107" def _golden_response(case_id: str) -> CaseResponse: run = create_run(case_id) return CaseResponse(case_id=case_id, run_id=run.run_id, case=golden_to_public(load_golden(case_id))) @router.post("/case", response_model=CaseResponse) def new_case(req: NewCaseRequest) -> CaseResponse: if req.case_id: if golden_exists(req.case_id): return _golden_response(req.case_id) loaded = RUNTIME.load_generated_run(req.case_id) if loaded is not None: public, run_id = loaded return CaseResponse(case_id=public.id, run_id=run_id, case=public) return _golden_response(DEFAULT_CASE_ID) generated = RUNTIME.new_generated_run() if generated is not None: public, run_id = generated return CaseResponse(case_id=public.id, run_id=run_id, case=public) return _golden_response(DEFAULT_CASE_ID) @router.get("/case/{case_id}", response_model=CaseResponse) def get_case(case_id: str) -> CaseResponse: if golden_exists(case_id): return _golden_response(case_id) loaded = RUNTIME.load_generated_run(case_id) if loaded is not None: public, run_id = loaded return CaseResponse(case_id=public.id, run_id=run_id, case=public) raise HTTPException(status_code=404, detail=f"case not found: {case_id}")