Bromeo777 commited on
Commit
5d1ada4
·
verified ·
1 Parent(s): a218999

Add app\api\v1\proposai.py

Browse files
Files changed (1) hide show
  1. app//api//v1//proposai.py +136 -0
app//api//v1//proposai.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app/api/v1/proposai.py
2
+ import asyncio
3
+ import hashlib
4
+ import time
5
+ from typing import List
6
+
7
+ from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, status
8
+ from sqlalchemy.ext.asyncio import AsyncSession
9
+ from sqlalchemy import select, func
10
+
11
+ from app.api import deps
12
+ from app.schemas.proposal import (
13
+ ProposalCreate,
14
+ ProposalResponse,
15
+ ProposalUpdate,
16
+ SpecificAimsRequest,
17
+ SpecificAimsResponse,
18
+ SeedPaperRef
19
+ )
20
+ from app.services.proposai.engine import ProposAIEngine
21
+ from app.tasks.proposai_generation import trigger_proposai_task
22
+ from app.models.proposal import Proposal, ProposalStatus, FunderCache
23
+
24
+ router = APIRouter()
25
+ engine = ProposAIEngine()
26
+
27
+
28
+ @router.post("/init", response_model=ProposalResponse, status_code=status.HTTP_201_CREATED)
29
+ async def init_strategic_proposal(
30
+ req: ProposalCreate,
31
+ db: AsyncSession = Depends(deps.get_db),
32
+ current_user=Depends(deps.get_current_active_user)
33
+ ):
34
+ """
35
+ Initiates the strategic proposal development workflow.
36
+
37
+ Performs real-time:
38
+ 1. Gap Detection: Identifies 'white space' in the research landscape.
39
+ 2. Funder Matching: Aligns research question with NIH/global requirements.
40
+ """
41
+ start_time = time.time()
42
+
43
+ # Prepare Seed Metadata
44
+ seed_refs = [SeedPaperRef(doi=doi, title="Context Paper") for doi in req.seed_papers_list]
45
+
46
+ # Run Instant Intelligence (Gaps and Funders)
47
+ gaps_task = engine.find_gaps(db, req.research_question, seed_refs)
48
+ funders_task = engine.match_funders(db, req.research_question, req.target_agencies)
49
+
50
+ gap_analysis, funder_matches = await asyncio.gather(gaps_task, funders_task)
51
+
52
+ # Initialize Proposal Record
53
+ proposal_id = hashlib.sha256(
54
+ f"{current_user.id}:{req.title}:{time.time()}".encode()
55
+ ).hexdigest()[:16]
56
+
57
+ new_proposal = Proposal(
58
+ id=proposal_id,
59
+ user_id=current_user.id,
60
+ title=req.title,
61
+ research_question=req.research_question,
62
+ status=ProposalStatus.DRAFT.value
63
+ )
64
+ new_proposal.set_seed_papers_list(req.seed_papers_list)
65
+ new_proposal.set_foa_matches_list([f.foa_number for f in funder_matches])
66
+
67
+ db.add(new_proposal)
68
+ await db.commit()
69
+ await db.refresh(new_proposal)
70
+
71
+ # Assemble Response
72
+ return ProposalResponse(
73
+ **new_proposal.__dict__,
74
+ gap_analysis=gap_analysis,
75
+ funder_matches_list=funder_matches,
76
+ latency_ms=int((time.time() - start_time) * 1000)
77
+ )
78
+
79
+
80
+ @router.post("/generate-aims", status_code=status.HTTP_202_ACCEPTED)
81
+ async def generate_specific_aims(
82
+ req: SpecificAimsRequest,
83
+ background_tasks: BackgroundTasks,
84
+ db: AsyncSession = Depends(deps.get_db),
85
+ current_user=Depends(deps.get_current_active_user)
86
+ ):
87
+ """
88
+ Triggers the 5-part research proposal architecture generation.
89
+ Delegates heavy compute (Specific Aims generation) to background workers.
90
+ """
91
+ # Verify proposal ownership
92
+ result = await db.execute(
93
+ select(Proposal).where(Proposal.id == req.proposal_id, Proposal.user_id == current_user.id)
94
+ )
95
+ proposal = result.scalar_one_or_none()
96
+ if not proposal:
97
+ raise HTTPException(status_code=404, detail="Proposal record not found")
98
+
99
+ # Enqueue background task
100
+ background_tasks.add_task(
101
+ trigger_proposai_task,
102
+ proposal_id=proposal.id,
103
+ hypothesis=req.hypothesis,
104
+ innovation_claim=req.innovation_claim
105
+ )
106
+
107
+ return {"proposal_id": proposal.id, "status": "generating"}
108
+
109
+
110
+ @router.get("/{proposal_id}", response_model=ProposalResponse)
111
+ async def get_proposal_status(
112
+ proposal_id: str,
113
+ db: AsyncSession = Depends(deps.get_db),
114
+ current_user=Depends(deps.get_current_active_user)
115
+ ):
116
+ """Retrieves the current state and results of a proposal development job."""
117
+ result = await db.execute(
118
+ select(Proposal).where(Proposal.id == proposal_id, Proposal.user_id == current_user.id)
119
+ )
120
+ proposal = result.scalar_one_or_none()
121
+ if not proposal:
122
+ raise HTTPException(status_code=404, detail="Proposal not found")
123
+
124
+ return proposal
125
+
126
+
127
+ @router.get("/health/engine")
128
+ async def get_proposai_health(db: AsyncSession = Depends(deps.get_db)):
129
+ """System health check for ProposAI caches and model connectivity."""
130
+ funder_count = await db.scalar(select(func.count()).select_from(FunderCache))
131
+ return {
132
+ "status": "ok",
133
+ "funder_cache_size": funder_count,
134
+ "compute_mode": "hybrid_delegation",
135
+ "fallback_available": True
136
+ }