fix: resolve /api/lab/result 500 and /api/lab/precons/install-batch 400
- GET /api/lab/result: Starlette's JSONResponse uses allow_nan=False,
so NaN/Infinity floats from simulation output cause a ValueError crash.
Add _sanitize_floats() to replace non-finite floats with null before
returning. Also wrap json.load in try/except for better error reporting.
- POST /api/lab/precons/install-batch: Frontend sends {"names": [...]}
but backend expected {"fileNames": [...]}. Accept both field names.
- POST /api/lab/precons/install: Same mismatch — frontend sends {"name"}
but backend expected {"fileName"}. Accept both field names.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
5550c27744
commit
71c90a588f
2 files changed
+24
-6
No files matched your search
+20
-2
@@ -19,6 +19,7 @@ from __future__ import annotations
|
||||
|
||||
import glob
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
@@ -52,6 +53,19 @@ from routes.shared import (
|
||||
router = APIRouter(tags=["lab"])
|
||||
|
||||
|
||||
def _sanitize_floats(obj):
|
||||
"""Replace NaN/Infinity with None so JSONResponse (allow_nan=False) won't crash."""
|
||||
if isinstance(obj, float):
|
||||
if math.isnan(obj) or math.isinf(obj):
|
||||
return None
|
||||
return obj
|
||||
if isinstance(obj, dict):
|
||||
return {k: _sanitize_floats(v) for k, v in obj.items()}
|
||||
if isinstance(obj, list):
|
||||
return [_sanitize_floats(v) for v in obj]
|
||||
return obj
|
||||
|
||||
|
||||
@router.post("/api/lab/start", response_model=StartResponse)
|
||||
async def start_batch(req: StartRequest, background_tasks: BackgroundTasks):
|
||||
"""Start a new batch simulation run."""
|
||||
@@ -162,8 +176,12 @@ async def get_result(batchId: Optional[str] = None):
|
||||
raise HTTPException(500, state.error)
|
||||
if not state.result_path or not os.path.exists(state.result_path):
|
||||
raise HTTPException(500, "Result file not found")
|
||||
with open(state.result_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
try:
|
||||
with open(state.result_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
raise HTTPException(500, f"Failed to read result file: {e}")
|
||||
data = _sanitize_floats(data)
|
||||
return JSONResponse(content=data)
|
||||
|
||||
|
||||
|
||||
+4
-4
@@ -31,9 +31,9 @@ async def list_precons():
|
||||
@router.post("/api/lab/precons/install")
|
||||
async def install_precon(req: dict):
|
||||
"""Install a precon deck to the Forge decks directory.
|
||||
Body: {"fileName": "Elven_Empire.dck"}
|
||||
Body: {"fileName": "Elven_Empire.dck"} or {"name": "Elven_Empire.dck"}
|
||||
"""
|
||||
file_name = req.get("fileName", "")
|
||||
file_name = req.get("fileName", "") or req.get("name", "")
|
||||
if not file_name:
|
||||
raise HTTPException(400, "fileName is required")
|
||||
|
||||
@@ -60,9 +60,9 @@ async def install_precon(req: dict):
|
||||
@router.post("/api/lab/precons/install-batch")
|
||||
async def install_precons_batch(req: dict):
|
||||
"""Install multiple precon decks at once.
|
||||
Body: {"fileNames": ["Elven_Empire.dck", "Necron_Dynasties.dck"]}
|
||||
Body: {"fileNames": ["Elven_Empire.dck", ...]} or {"names": ["Elven_Empire.dck", ...]}
|
||||
"""
|
||||
file_names = req.get("fileNames", [])
|
||||
file_names = req.get("fileNames", []) or req.get("names", [])
|
||||
if not file_names:
|
||||
raise HTTPException(400, "fileNames list is required")
|
||||
|
||||
|
||||
Reference in new issue
Block a user