Builder-Neekhil commited on
Commit
6980110
·
verified ·
1 Parent(s): 16cfce6

Add FastAPI main application

Browse files
Files changed (1) hide show
  1. main.py +234 -0
main.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ARCHAI Adaptive Assessment API
3
+ ==============================
4
+ FastAPI backend that plugs into the archai frontend.
5
+
6
+ Endpoints:
7
+ POST /api/v1/session/start → Initialize assessment
8
+ POST /api/v1/session/answer → Submit answer, get next question
9
+ GET /api/v1/session/{id} → Get current state/results
10
+ POST /api/v1/path/generate → Generate learning path with day/week/month actionables
11
+ GET /api/v1/questions → Get full question bank (for offline study)
12
+ GET /api/v1/health → Health check
13
+ """
14
+
15
+ from fastapi import FastAPI, HTTPException
16
+ from fastapi.middleware.cors import CORSMiddleware
17
+ from pydantic import BaseModel, Field
18
+ from typing import Optional, List, Dict, Any
19
+ from adaptive_engine import AdaptiveAssessmentEngine, engine, Dimension, DIMENSION_LABELS, DIMENSION_COLORS
20
+
21
+ app = FastAPI(
22
+ title="ARCHAI Adaptive Assessment Engine",
23
+ description="IRT-based adaptive AI readiness assessment with LLM-powered learning paths",
24
+ version="2.0.0",
25
+ docs_url="/docs",
26
+ redoc_url="/redoc",
27
+ )
28
+
29
+ # CORS for your Netlify frontend
30
+ app.add_middleware(
31
+ CORSMiddleware,
32
+ allow_origins=["*"], # In production, restrict to your domain
33
+ allow_credentials=True,
34
+ allow_methods=["*"],
35
+ allow_headers=["*"],
36
+ )
37
+
38
+ # ============================================================================
39
+ # REQUEST/RESPONSE SCHEMAS
40
+ # ============================================================================
41
+
42
+ class StartSessionResponse(BaseModel):
43
+ session_id: str
44
+ question: Optional[Dict[str, Any]]
45
+ progress: Dict[str, Any]
46
+ status: str
47
+
48
+ class SubmitAnswerRequest(BaseModel):
49
+ session_id: str
50
+ question_id: str
51
+ option_index: int = Field(ge=0, le=3)
52
+
53
+ class SubmitAnswerResponse(BaseModel):
54
+ session_id: str
55
+ question: Optional[Dict[str, Any]]
56
+ progress: Dict[str, Any]
57
+ interim_scores: Optional[Dict[str, int]]
58
+ status: str
59
+
60
+ class GeneratePathRequest(BaseModel):
61
+ session_id: str
62
+ persona_id: str
63
+ hours_per_week: int = Field(ge=1, le=40)
64
+ budget_usd: int = Field(ge=0)
65
+ hardware_id: Optional[str] = None
66
+ preference: Optional[str] = None # "local", "api", "both"
67
+
68
+ class GeneratePathResponse(BaseModel):
69
+ session_id: str
70
+ overall_score: int
71
+ stage: Dict[str, Any]
72
+ archetype: Dict[str, Any]
73
+ dimension_scores: Dict[str, int]
74
+ gaps: List[Dict[str, Any]]
75
+ strengths: List[Dict[str, Any]]
76
+ learning_path: Dict[str, List[Any]]
77
+ projections: Dict[str, Any]
78
+ meta: Dict[str, Any]
79
+
80
+ # ============================================================================
81
+ # API ENDPOINTS
82
+ # ============================================================================
83
+
84
+ @app.get("/api/v1/health")
85
+ def health():
86
+ return {
87
+ "status": "healthy",
88
+ "engine": "IRT-2PL adaptive",
89
+ "version": "2.0.0",
90
+ "features": ["adaptive_selection", "bayesian_knowledge_tracing", "structured_learning_paths"],
91
+ }
92
+
93
+ @app.post("/api/v1/session/start", response_model=StartSessionResponse)
94
+ def start_session():
95
+ """Start a new adaptive assessment session."""
96
+ result = engine.start_session()
97
+ return result
98
+
99
+ @app.post("/api/v1/session/answer", response_model=SubmitAnswerResponse)
100
+ def submit_answer(req: SubmitAnswerRequest):
101
+ """Submit an answer and get the next adaptive question."""
102
+ result = engine.submit_answer(req.session_id, req.question_id, req.option_index)
103
+ if "error" in result:
104
+ raise HTTPException(status_code=404, detail=result["error"])
105
+ return result
106
+
107
+ @app.get("/api/v1/session/{session_id}")
108
+ def get_session(session_id: str):
109
+ """Get current session state or final results."""
110
+ state = engine.sessions.get(session_id)
111
+ if not state:
112
+ raise HTTPException(status_code=404, detail="Session not found")
113
+
114
+ # If complete, return results
115
+ if len(state.asked_questions) > 0 and engine.selector.should_stop(state):
116
+ return engine._finalize(state)
117
+
118
+ # Otherwise return current state
119
+ dim_coverage = set()
120
+ for qid in state.asked_questions:
121
+ q = next((qq for qq in engine.question_bank if qq.id == qid), None)
122
+ if q:
123
+ dim_coverage.add(q.dimension.value)
124
+
125
+ return {
126
+ "session_id": session_id,
127
+ "status": "in_progress",
128
+ "progress": {
129
+ "asked": len(state.asked_questions),
130
+ "total": 12,
131
+ "dimensions_covered": list(dim_coverage),
132
+ },
133
+ "interim_scores": engine.tracer.get_dimension_scores(state),
134
+ "latent_abilities": {d.value: round(t, 2) for d, t in state.theta.items()},
135
+ }
136
+
137
+ @app.post("/api/v1/path/generate", response_model=GeneratePathResponse)
138
+ def generate_path(req: GeneratePathRequest):
139
+ """Generate a structured learning path with day/week/month actionables."""
140
+ result = engine.generate_path(
141
+ req.session_id,
142
+ req.persona_id,
143
+ req.hours_per_week,
144
+ req.budget_usd,
145
+ req.hardware_id,
146
+ req.preference,
147
+ )
148
+ if "error" in result:
149
+ raise HTTPException(status_code=404, detail=result["error"])
150
+ return result
151
+
152
+ @app.get("/api/v1/questions")
153
+ def get_questions():
154
+ """Get the full calibrated question bank for offline study."""
155
+ return {
156
+ "questions": [
157
+ {
158
+ "id": q.id,
159
+ "dimension": q.dimension.value,
160
+ "dimension_label": DIMENSION_LABELS.get(q.dimension, q.dimension.value),
161
+ "text": q.text,
162
+ "options": q.options,
163
+ "difficulty": round(q.difficulty, 2),
164
+ "discrimination": round(q.discrimination, 2),
165
+ "concept_tags": q.concept_tags,
166
+ }
167
+ for q in engine.question_bank
168
+ ],
169
+ "dimensions": [
170
+ {"id": d.value, "label": DIMENSION_LABELS.get(d, d.value), "color": DIMENSION_COLORS.get(d.value, "#14B8A6")}
171
+ for d in Dimension
172
+ ],
173
+ }
174
+
175
+ @app.get("/api/v1/dimensions")
176
+ def get_dimensions():
177
+ """Get dimension metadata for UI rendering."""
178
+ return {
179
+ "dimensions": [
180
+ {"id": d.value, "label": DIMENSION_LABELS.get(d, d.value), "color": DIMENSION_COLORS.get(d.value, "#14B8A6")}
181
+ for d in Dimension
182
+ ]
183
+ }
184
+
185
+ # ============================================================================
186
+ # COMPATIBILITY ENDPOINTS — archai v1 API mapping
187
+ # ============================================================================
188
+
189
+ @app.post("/api/v1/assessment/start")
190
+ def assessment_start_compat():
191
+ """Backward-compatible endpoint name."""
192
+ return start_session()
193
+
194
+ @app.post("/api/v1/assessment/answer")
195
+ def assessment_answer_compat(req: SubmitAnswerRequest):
196
+ """Backward-compatible endpoint name."""
197
+ return submit_answer(req)
198
+
199
+ @app.get("/api/v1/assessment/results/{session_id}")
200
+ def assessment_results_compat(session_id: str):
201
+ """Backward-compatible endpoint for getting results."""
202
+ return get_session(session_id)
203
+
204
+ # ============================================================================
205
+ # Root
206
+ # ============================================================================
207
+
208
+ @app.get("/")
209
+ def root():
210
+ return {
211
+ "service": "ARCHAI Adaptive Assessment Engine",
212
+ "version": "2.0.0",
213
+ "docs": "/docs",
214
+ "endpoints": {
215
+ "start": "POST /api/v1/session/start",
216
+ "answer": "POST /api/v1/session/answer",
217
+ "results": "GET /api/v1/session/{session_id}",
218
+ "path": "POST /api/v1/path/generate",
219
+ "questions": "GET /api/v1/questions",
220
+ "health": "GET /api/v1/health",
221
+ },
222
+ "adaptive_features": {
223
+ "irt_model": "2PL (Two-Parameter Logistic)",
224
+ "selection": "Fisher Information Maximization",
225
+ "tracing": "Bayesian Knowledge Updating",
226
+ "stopping": "Precision-based early stopping",
227
+ "question_count": "Adaptive 6-12 questions",
228
+ }
229
+ }
230
+
231
+
232
+ if __name__ == "__main__":
233
+ import uvicorn
234
+ uvicorn.run(app, host="0.0.0.0", port=7860)