Skip to content

Commit 6252ae6

Browse files
committed
Previous docking results
1 parent 47fda5a commit 6252ae6

8 files changed

Lines changed: 683 additions & 487 deletions

File tree

backend/routers/discoveries.py

Lines changed: 64 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,64 @@
1-
"""GET/POST/DELETE /api/discoveries."""
2-
3-
from fastapi import APIRouter, HTTPException
4-
import agents.OrchestratorAgent # Import module, not variable
5-
6-
from utils.db import get_discovery, list_discoveries, save_discovery
7-
8-
9-
router = APIRouter()
10-
11-
12-
@router.get("/discoveries")
13-
async def list_all():
14-
return await list_discoveries()
15-
16-
17-
@router.get("/discoveries/{discovery_id}")
18-
async def get_one(discovery_id: str):
19-
d = await get_discovery(discovery_id)
20-
if not d:
21-
raise HTTPException(status_code=404, detail="Discovery not found")
22-
return d
23-
24-
25-
@router.post("/discoveries/{session_id}/save")
26-
async def save_session(session_id: str):
27-
# Try Redis first (survives restarts), then fall back to in-memory
28-
state = await get_session_redis(session_id)
29-
if not state:
30-
state = agents.OrchestratorAgent._sessions.get(session_id)
31-
32-
if not state:
33-
raise HTTPException(status_code=404, detail="Session not found")
34-
35-
did = await save_discovery(state)
36-
if not did:
37-
from utils.db import get_engine
38-
engine = get_engine()
39-
detail = "DB not configured" if not engine else "DB insert failed"
40-
raise HTTPException(status_code=500, detail=detail)
41-
return {"discovery_id": did}
42-
43-
44-
@router.delete("/discoveries/{discovery_id}")
45-
async def delete_one(discovery_id: str):
46-
from sqlalchemy import text
47-
48-
from utils.db import get_engine
49-
50-
engine = get_engine()
51-
if not engine:
52-
raise HTTPException(status_code=503, detail="DB not configured")
53-
async with engine.begin() as conn:
54-
await conn.execute(text("DELETE FROM discoveries WHERE id = :id"), {"id": discovery_id})
55-
return {"deleted": discovery_id}
1+
"""GET/POST/DELETE /api/discoveries."""
2+
3+
from fastapi import APIRouter, HTTPException
4+
5+
from utils.db import get_discovery, list_discoveries, save_discovery
6+
7+
router = APIRouter()
8+
9+
10+
@router.get("/discoveries")
11+
async def list_all():
12+
return await list_discoveries()
13+
14+
15+
@router.get("/discoveries/{discovery_id}")
16+
async def get_one(discovery_id: str):
17+
d = await get_discovery(discovery_id)
18+
if not d:
19+
raise HTTPException(status_code=404, detail="Discovery not found")
20+
return d
21+
22+
23+
@router.post("/discoveries/{session_id}/save")
24+
async def save_session(session_id: str):
25+
import agents.OrchestratorAgent # Import module, not variable
26+
27+
state = agents.OrchestratorAgent._sessions.get(session_id)
28+
29+
# Not in live memory — try recovering from Neon
30+
if not state:
31+
try:
32+
from utils.db import get_session_by_session_id
33+
state = await get_session_by_session_id(session_id)
34+
except Exception:
35+
state = None
36+
37+
if not state:
38+
raise HTTPException(status_code=404, detail="Session not found")
39+
40+
did = await save_discovery(state)
41+
if not did:
42+
from utils.db import _build_dsn
43+
detail = "DB not configured" if not _build_dsn() else "DB insert failed"
44+
raise HTTPException(status_code=500, detail=detail)
45+
return {"discovery_id": did}
46+
47+
48+
@router.delete("/discoveries/{discovery_id}")
49+
async def delete_one(discovery_id: str):
50+
from utils.db import _get_conn, _build_dsn
51+
52+
if not _build_dsn():
53+
raise HTTPException(status_code=503, detail="DB not configured")
54+
55+
conn = await _get_conn()
56+
if not conn:
57+
raise HTTPException(status_code=503, detail="DB connection failed")
58+
try:
59+
await conn.execute(
60+
"DELETE FROM discoveries WHERE id = $1", discovery_id
61+
)
62+
finally:
63+
await conn.close()
64+
return {"deleted": discovery_id}

backend/routers/molecules.py

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,67 @@
1-
"""GET /api/molecules/{session_id}."""
2-
3-
from fastapi import APIRouter, HTTPException
4-
5-
router = APIRouter()
6-
7-
8-
@router.get("/molecules/{session_id}")
9-
async def get_molecules(session_id: str):
10-
from agents.OrchestratorAgent import _sessions
11-
12-
state = _sessions.get(session_id)
13-
if not state:
14-
raise HTTPException(status_code=404, detail="Session not found")
15-
return {
16-
"session_id": session_id,
17-
"query": state.get("query"),
18-
"mutation_context": state.get("mutation_context"),
19-
"literature": state.get("literature", []),
20-
"structures": state.get("structures", []),
21-
"pdb_content": state.get("pdb_content", ""),
22-
"binding_pocket": state.get("binding_pocket"),
23-
"pocket_detection_method": state.get("pocket_detection_method"),
24-
"pocket_delta": state.get("pocket_delta"),
25-
"generated_molecules": state.get("generated_molecules", []),
26-
"docking_results": state.get("docking_results", []),
27-
"selectivity_results": state.get("selectivity_results", []),
28-
"admet_profiles": state.get("admet_profiles", []),
29-
"toxicophore_highlights": state.get("toxicophore_highlights", []),
30-
"optimized_leads": state.get("optimized_leads", []),
31-
"evolution_tree": state.get("evolution_tree"),
32-
"similar_compounds": state.get("similar_compounds", []),
33-
"synergy_predictions": state.get("synergy_predictions", []),
34-
"clinical_trials": state.get("clinical_trials", []),
35-
"knowledge_graph": state.get("knowledge_graph"),
36-
"reasoning_trace": state.get("reasoning_trace"),
37-
"summary": state.get("summary"),
38-
"resistance_forecast": state.get("resistance_forecast"),
39-
"resistant_drugs": state.get("resistant_drugs", []),
40-
"recommended_drugs": state.get("recommended_drugs", []),
41-
"final_report": state.get("final_report"),
42-
"status": state.get("status"),
43-
"cancelled": state.get("cancelled", False),
44-
"agent_statuses": state.get("agent_statuses", {}),
45-
"execution_time_ms": state.get("execution_time_ms", 0),
46-
"langsmith_run_id": state.get("langsmith_run_id"),
47-
"llm_provider_used": state.get("llm_provider_used", "unknown"),
48-
}
1+
"""GET /api/molecules/{session_id}."""
2+
3+
from fastapi import APIRouter, HTTPException
4+
5+
router = APIRouter()
6+
7+
8+
@router.get("/molecules/{session_id}")
9+
async def get_molecules(session_id: str):
10+
import agents.OrchestratorAgent # Import module, not variable
11+
12+
state = agents.OrchestratorAgent._sessions.get(session_id)
13+
14+
# Not in memory (backend restarted) — try recovering from Neon
15+
if not state:
16+
try:
17+
from utils.db import get_session_by_session_id
18+
state = await get_session_by_session_id(session_id)
19+
except Exception:
20+
state = None
21+
22+
if not state:
23+
raise HTTPException(status_code=404, detail="Session not found")
24+
25+
return {
26+
"session_id": session_id,
27+
"query": state.get("query"),
28+
"mutation_context": state.get("mutation_context"),
29+
"literature": state.get("literature", []),
30+
"structures": state.get("structures", []),
31+
"pdb_content": state.get("pdb_content", ""),
32+
"binding_pocket": state.get("binding_pocket"),
33+
"pocket_detection_method": state.get("pocket_detection_method"),
34+
"pocket_delta": state.get("pocket_delta"),
35+
"generated_molecules": state.get("generated_molecules", []),
36+
"docking_results": state.get("docking_results", []),
37+
"selectivity_results": state.get("selectivity_results", []),
38+
"admet_profiles": state.get("admet_profiles", []),
39+
"toxicophore_highlights": state.get("toxicophore_highlights", []),
40+
"optimized_leads": state.get("optimized_leads", []),
41+
"evolution_tree": state.get("evolution_tree"),
42+
"similar_compounds": state.get("similar_compounds", []),
43+
"synergy_predictions": state.get("synergy_predictions", []),
44+
"clinical_trials": state.get("clinical_trials", []),
45+
"knowledge_graph": state.get("knowledge_graph"),
46+
"reasoning_trace": state.get("reasoning_trace"),
47+
"summary": state.get("summary"),
48+
"resistance_flags": state.get("resistance_flags", []),
49+
"resistance_forecast": state.get("resistance_forecast"),
50+
"resistant_drugs": state.get("resistant_drugs", []),
51+
"recommended_drugs": state.get("recommended_drugs", []),
52+
"md_results": state.get("md_results", []),
53+
"sa_scores": state.get("sa_scores", []),
54+
"synthesis_routes": state.get("synthesis_routes", []),
55+
"confidence": state.get("confidence"),
56+
"confidence_banner": state.get("confidence_banner"),
57+
"esm1v_score": state.get("esm1v_score"),
58+
"esm1v_confidence": state.get("esm1v_confidence"),
59+
"final_report": state.get("final_report"),
60+
"status": state.get("status"),
61+
"cancelled": state.get("cancelled", False),
62+
"agent_statuses": state.get("agent_statuses", {}),
63+
"execution_time_ms": state.get("execution_time_ms", 0),
64+
"langsmith_run_id": state.get("langsmith_run_id"),
65+
"llm_provider_used": state.get("llm_provider_used", "unknown"),
66+
"discovery_id": state.get("discovery_id"),
67+
}

backend/routers/stream.py

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,56 @@
1-
"""GET /api/stream/{session_id} — SSE event stream."""
2-
3-
import asyncio
4-
import json
5-
6-
from fastapi import APIRouter
7-
from fastapi.responses import StreamingResponse
8-
9-
router = APIRouter()
10-
11-
12-
@router.get("/stream/{session_id}")
13-
async def stream(session_id: str):
14-
from agents.OrchestratorAgent import _sse_queues
15-
from agents.OrchestratorAgent import _sessions
16-
17-
async def event_generator():
18-
queue = _sse_queues.get(session_id)
19-
if not queue and session_id in _sessions:
20-
queue = asyncio.Queue()
21-
_sse_queues[session_id] = queue
22-
if not queue:
23-
yield f"data: {json.dumps({'event': 'error', 'message': 'Session not found'})}\n\n"
24-
return
25-
while True:
26-
try:
27-
event = await asyncio.wait_for(queue.get(), timeout=120)
28-
yield f"data: {json.dumps(event, default=str)}\n\n"
29-
if event.get("event") == "pipeline_complete":
30-
break
31-
except asyncio.TimeoutError:
32-
yield f"data: {json.dumps({'event': 'heartbeat'})}\n\n"
33-
34-
return StreamingResponse(
35-
event_generator(),
36-
media_type="text/event-stream",
37-
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
38-
)
1+
"""GET /api/stream/{session_id} — SSE event stream."""
2+
3+
import asyncio
4+
import json
5+
6+
from fastapi import APIRouter
7+
from fastapi.responses import StreamingResponse
8+
9+
router = APIRouter()
10+
11+
12+
@router.get("/stream/{session_id}")
13+
async def stream(session_id: str):
14+
from agents.OrchestratorAgent import _sessions, _sse_queues
15+
16+
async def event_generator():
17+
# ── Live session in memory ─────────────────────────────────────────
18+
queue = _sse_queues.get(session_id)
19+
if not queue and session_id in _sessions:
20+
queue = asyncio.Queue()
21+
_sse_queues[session_id] = queue
22+
23+
if queue:
24+
while True:
25+
try:
26+
event = await asyncio.wait_for(queue.get(), timeout=120)
27+
yield f"data: {json.dumps(event, default=str)}\n\n"
28+
if event.get("event") == "pipeline_complete":
29+
break
30+
except asyncio.TimeoutError:
31+
yield f"data: {json.dumps({'event': 'heartbeat'})}\n\n"
32+
return
33+
34+
# ── Session not in memory — try recovering from Neon ───────────────
35+
try:
36+
from utils.db import get_session_by_session_id
37+
38+
state = await get_session_by_session_id(session_id)
39+
if state and state.get("final_report"):
40+
# Emit pipeline_complete immediately so the frontend skips
41+
# the "running" view and goes straight to results.
42+
yield (
43+
f"data: {json.dumps({'event': 'pipeline_complete', 'data': state}, default=str)}\n\n"
44+
)
45+
return
46+
except Exception:
47+
pass
48+
49+
# ── Truly not found ────────────────────────────────────────────────
50+
yield f"data: {json.dumps({'event': 'not_found', 'message': 'Session not found'})}\n\n"
51+
52+
return StreamingResponse(
53+
event_generator(),
54+
media_type="text/event-stream",
55+
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
56+
)

0 commit comments

Comments
 (0)