Add suitbuilder backend improvements and SSE streaming fix

- Add dedicated streaming proxy endpoint for real-time suitbuilder SSE updates
- Implement stable sorting with character_name and name tiebreakers for deterministic results
- Refactor locked items to locked slots supporting set_id and spell constraints
- Add Mag-SuitBuilder style branch pruning tracking variables
- Enhance search with phase updates and detailed progress logging
- Update design document with SSE streaming proxy fix details

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
erik 2026-02-05 19:14:07 +00:00
parent 8e70f88de1
commit e0265e261c
4 changed files with 655 additions and 222 deletions

41
main.py
View file

@ -18,7 +18,7 @@ import socket
import struct
from fastapi import FastAPI, Header, HTTPException, Query, WebSocket, WebSocketDisconnect, Request
from fastapi.responses import JSONResponse, Response
from fastapi.responses import JSONResponse, Response, StreamingResponse
from fastapi.routing import APIRoute
from fastapi.staticfiles import StaticFiles
from fastapi.encoders import jsonable_encoder
@ -2268,13 +2268,50 @@ async def test_inventory_route():
"""Test route to verify inventory proxy is working"""
return {"message": "Inventory proxy route is working"}
@app.post("/inv/suitbuilder/search")
async def proxy_suitbuilder_search(request: Request):
"""Stream suitbuilder search results - SSE requires streaming proxy."""
inventory_service_url = os.getenv('INVENTORY_SERVICE_URL', 'http://inventory-service:8000')
logger.info(f"Streaming proxy to suitbuilder search")
# Read body BEFORE creating generator (request context needed)
body = await request.body()
async def stream_response():
try:
# Use streaming request with long timeout for searches
async with httpx.AsyncClient(timeout=httpx.Timeout(300.0, connect=10.0)) as client:
async with client.stream(
method="POST",
url=f"{inventory_service_url}/suitbuilder/search",
content=body,
headers={"Content-Type": "application/json"}
) as response:
async for chunk in response.aiter_bytes():
yield chunk
except httpx.ReadTimeout:
yield b"event: error\ndata: {\"message\": \"Search timeout\"}\n\n"
except Exception as e:
logger.error(f"Streaming proxy error: {e}")
yield f"event: error\ndata: {{\"message\": \"Proxy error: {str(e)}\"}}\n\n".encode()
return StreamingResponse(
stream_response(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no" # Disable nginx buffering
}
)
@app.api_route("/inv/{path:path}", methods=["GET", "POST"])
async def proxy_inventory_service(path: str, request: Request):
"""Proxy all inventory service requests"""
try:
inventory_service_url = os.getenv('INVENTORY_SERVICE_URL', 'http://inventory-service:8000')
logger.info(f"Proxying to inventory service: {inventory_service_url}/{path}")
# Forward the request to inventory service (60s timeout for large queries)
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.request(