Fayza38 commited on
Commit
071fe94
·
verified ·
1 Parent(s): d7318ee

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +416 -104
main.py CHANGED
@@ -8,12 +8,14 @@ import uuid
8
  import cloudinary
9
  import cloudinary.uploader
10
  import firebase_admin
 
11
  from firebase_admin import credentials, firestore
12
  from fastapi import FastAPI, HTTPException, BackgroundTasks
13
  from pydantic import BaseModel
14
  from gradio_client import Client
15
  from google.cloud.firestore_v1.base_query import FieldFilter
16
  import edge_tts
 
17
  from typing import Optional, List
18
  from dotenv import load_dotenv
19
  from contextlib import asynccontextmanager
@@ -25,11 +27,13 @@ load_dotenv()
25
 
26
  if not firebase_admin._apps:
27
  fb_json = os.getenv("FIREBASE_JSON")
 
28
  if fb_json:
29
  cred_dict = json.loads(fb_json)
30
  cred = credentials.Certificate(cred_dict)
31
  else:
32
  cred = credentials.Certificate("serviceAccountKey.json")
 
33
  firebase_admin.initialize_app(cred)
34
 
35
  db = firestore.client()
@@ -45,221 +49,529 @@ HF_SPACE = "Fayza38/Question_and_answer_model"
45
  client = None
46
 
47
  # =========================================
48
- # 3. MODELS & CONSTANTS
49
  # =========================================
50
  TECH_CATEGORIES = {
51
- 0: "Security", 1: "BackEnd", 2: "Networking", 3: "FrontEnd",
52
- 4: "DataEngineering", 5: "WebDevelopment", 6: "FullStack",
53
- 7: "VersionControl", 8: "SystemDesign", 9: "MachineLearning",
54
- 10: "LanguagesAndFrameworks", 11: "DatabaseSystems",
55
- 12: "ArtificialIntelligence", 13: "SoftwareTesting",
56
- 14: "DistributedSystems", 15: "DevOps", 16: "LowLevelSystems",
57
- 17: "DatabaseAndSql", 18: "GeneralProgramming",
58
- 19: "DataStructures", 20: "Algorithms"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
60
 
61
- DIFFICULTY_MAP = {0: "Easy", 1: "Intermediate", 2: "Hard"}
62
 
63
  class GenerateSessionRequest(BaseModel):
64
  sessionId: str
65
- sessionType: int # 0: Behavioral, 1: Technical
66
- difficultyLevel: int = 0
67
- trackName: Optional[int] = None
 
 
 
 
 
 
 
 
 
68
 
69
  class CleanupRequest(BaseModel):
70
  audioUrls: List[str]
71
 
 
72
  # =========================================
73
  # 4. LIFESPAN MANAGEMENT
74
  # =========================================
75
  @asynccontextmanager
76
  async def lifespan(app: FastAPI):
77
  global client
 
78
  print("Connecting to Hugging Face Model...")
 
79
  try:
80
  loop = asyncio.get_event_loop()
81
- client = await loop.run_in_executor(None, lambda: Client(HF_SPACE))
 
 
 
 
 
82
  print("Model Connected Successfully!")
 
83
  except Exception as e:
84
  print(f"Model Connection Failed: {e}")
85
-
86
  yield
 
87
  print("Shutting down Intervision Service...")
88
 
89
- app = FastAPI(title="Intervision AI Question Service", lifespan=lifespan)
 
 
 
 
90
 
91
  # =========================================
92
- # 5. CORE LOGIC HELPERS
93
  # =========================================
94
- async def generate_audio(text, filename):
95
  try:
96
- communicate = edge_tts.Communicate(text, "en-US-GuyNeural", rate="-15%")
 
 
 
 
 
97
  await communicate.save(filename)
 
98
  upload_result = cloudinary.uploader.upload(
99
- filename, resource_type="video", folder="interview_audio"
 
 
100
  )
101
- if os.path.exists(filename): os.remove(filename)
 
 
 
102
  return upload_result["secure_url"]
 
103
  except Exception as e:
104
  print(f"Audio Generation Error: {e}")
105
- if os.path.exists(filename): os.remove(filename)
 
 
 
106
  return None
107
 
108
- async def safe_generate(prompt, retries=5):
109
- if client is None: raise Exception("AI Client is not initialized.")
 
 
 
110
  for attempt in range(retries):
111
  try:
112
  loop = asyncio.get_running_loop()
113
- return await loop.run_in_executor(None, lambda: client.predict(prompt=prompt, api_name="/generate_questions"))
 
 
 
 
 
 
 
 
 
 
114
  except Exception as e:
115
- if attempt == retries - 1: raise e
 
 
116
  await asyncio.sleep(5)
117
 
 
118
  def parse_question_output(raw_output: str):
119
- if not raw_output: return None, None
120
- text = raw_output.split("assistant")[-1].strip() if "assistant" in raw_output else raw_output
 
 
 
 
 
 
 
121
  if "Q:" in text and "A:" in text:
122
  try:
123
  parts = text.split("A:")
124
- q = parts[0].replace("Q:", "").strip()
125
- a = parts[1].split("<|im_end|>")[0].strip()
126
- return q, a
127
- except: return None, None
 
 
 
 
 
 
 
 
 
 
128
  return None, None
129
 
130
- async def refill_specific_pool(track_id: int, difficulty: int, count: int, session_type: int):
131
- while client is None: await asyncio.sleep(5)
132
-
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  if session_type == 0:
134
- prompt = "Generate ONE unique simple behavioral interview question. Format: Q: [Question] A: [Answer]"
135
  track_text = "Behavioral"
 
 
 
 
 
 
 
 
 
136
  else:
 
137
  track_text = TECH_CATEGORIES.get(track_id)
 
138
  level_text = DIFFICULTY_MAP.get(difficulty)
139
- prompt = f"Generate ONE unique {track_text} question for {level_text} level. Format: Q: [Question] A: [Answer]"
 
 
 
 
 
140
 
141
  success_count = 0
 
142
  while success_count < count:
143
  try:
144
  raw_output = await safe_generate(prompt)
145
- q, a = parse_question_output(raw_output)
146
- if q and a:
147
- audio_url = await generate_audio(q, f"{uuid.uuid4()}.mp3")
 
 
 
 
 
 
 
148
  if audio_url:
149
- db.collection("questions_pool").add({
 
150
  "session_type": session_type,
151
- "track_id": track_id if session_type == 1 else -1,
152
- "difficulty": difficulty if session_type == 1 else 0,
153
- "questionText": q,
154
- "questionIdealAnswer": a,
155
  "audio_url": audio_url,
156
  "created_at": firestore.SERVER_TIMESTAMP
157
- })
 
 
 
 
 
 
 
 
 
 
158
  success_count += 1
159
- print(f"Successfully added {track_text} question {success_count}/{count}")
 
 
 
 
 
 
160
  await asyncio.sleep(2)
 
161
  except Exception as e:
162
  print(f"Refill error: {e}")
163
  await asyncio.sleep(5)
164
 
165
  # =========================================
166
- # 6. API ENDPOINTS
167
  # =========================================
168
  @app.post("/generate-session")
169
- async def generate_session(request: GenerateSessionRequest, background_tasks: BackgroundTasks):
170
- s_type, t_id = request.sessionType, request.trackName
171
-
172
- # FORCED ADJUSTMENT: Behavioral always uses difficulty 0
173
- if s_type == 0:
174
- diff = 0
175
- else:
176
- diff = request.difficultyLevel
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
- query = db.collection("questions_pool").where(filter=FieldFilter("session_type", "==", s_type))
179
-
180
- if s_type == 1: # Technical
181
- if t_id is None: raise HTTPException(status_code=400, detail="trackName is required for technical sessions.")
182
- query = query.where(filter=FieldFilter("track_id", "==", t_id)).where(filter=FieldFilter("difficulty", "==", diff))
183
  else:
184
- # For behavioral, we filter specifically by the forced difficulty 0
185
- query = query.where(filter=FieldFilter("difficulty", "==", 0))
186
-
 
 
187
  docs = query.limit(10).get()
 
188
  final_questions = []
189
-
190
  for index, doc in enumerate(docs, start=1):
 
191
  data = doc.to_dict()
 
192
  final_questions.append({
193
- "question_id": index, "text": data["questionText"],
194
- "expected_answer": data["questionIdealAnswer"], "audio_url": data.get("audio_url", "")
 
 
195
  })
 
 
196
  db.collection("questions_pool").document(doc.id).delete()
197
 
 
 
 
198
  async def check_and_refill_background():
 
199
  snap = query.count().get()
200
  current_count = snap[0][0].value
 
201
  if current_count < 50:
202
- print(f"Stock for {('Behavioral' if s_type==0 else TECH_CATEGORIES[t_id])} is low ({current_count}). Refilling...")
203
- await refill_specific_pool(t_id if s_type == 1 else -1, diff, 50 - current_count, s_type)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
  background_tasks.add_task(check_and_refill_background)
206
-
207
  if not final_questions:
208
- raise HTTPException(status_code=503, detail="The question pool is empty. Please try again in a few minutes.")
209
-
210
- return {"session_id": request.sessionId, "questions": final_questions}
 
211
 
 
 
 
 
 
 
 
 
 
212
  @app.get("/admin/prefill-all")
213
  async def prefill_all(background_tasks: BackgroundTasks):
214
- """Checks and refills ALL tracks and behavioral questions to 50 items each."""
215
  async def run_sync():
 
216
  print("Starting Global Smart Prefill...")
217
-
218
- # 1. Sync Behavioral
219
- beh_snap = db.collection("questions_pool").where(filter=FieldFilter("session_type", "==", 0)).count().get()
220
- beh_count = beh_snap[0][0].value
221
- if beh_count < 50:
222
- print(f"Syncing Behavioral: adding {50-beh_count}")
223
- await refill_specific_pool(-1, 0, 50 - beh_count, 0)
224
-
225
- # 2. Sync Technical
226
- for t_id, t_name in TECH_CATEGORIES.items():
227
- for d_id, d_name in DIFFICULTY_MAP.items():
228
- query = db.collection("questions_pool")\
229
- .where(filter=FieldFilter("session_type", "==", 1))\
230
- .where(filter=FieldFilter("track_id", "==", t_id))\
231
- .where(filter=FieldFilter("difficulty", "==", d_id))
232
-
233
- snap = query.count().get()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  current = snap[0][0].value
 
235
  if current < 50:
236
- print(f"Syncing {t_name} ({d_name}): adding {50-current}")
237
- await refill_specific_pool(t_id, d_id, 50 - current, 1)
238
-
 
 
 
 
 
 
 
 
 
 
 
 
239
  print("Global Smart Prefill Completed!")
240
 
241
  background_tasks.add_task(run_sync)
242
- return {"message": "Global prefill process started in the background."}
243
 
 
 
 
 
 
 
 
 
244
  @app.post("/cleanup-audio")
245
- async def cleanup_audio(request: CleanupRequest, background_tasks: BackgroundTasks):
 
 
 
246
  def delete_job(urls):
 
247
  for url in urls:
248
  try:
249
- public_id = "interview_audio/" + url.split('/')[-1].split('.')[0]
250
- cloudinary.uploader.destroy(public_id, resource_type="video")
251
- except: pass
252
- background_tasks.add_task(delete_job, request.audioUrls)
253
- return {"message": "Cloudinary cleanup process initiated."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
 
 
 
255
  @app.get("/health")
256
  async def health():
257
- return {"status": "active", "ai_model_connected": client is not None}
 
 
 
 
258
 
259
  @app.get("/")
260
  async def root():
261
- return {"app": "Intervision AI Engine", "status": "Running"}
 
 
 
 
262
 
 
 
 
263
  if __name__ == "__main__":
 
264
  import uvicorn
265
- uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
 
 
 
 
 
 
 
8
  import cloudinary
9
  import cloudinary.uploader
10
  import firebase_admin
11
+
12
  from firebase_admin import credentials, firestore
13
  from fastapi import FastAPI, HTTPException, BackgroundTasks
14
  from pydantic import BaseModel
15
  from gradio_client import Client
16
  from google.cloud.firestore_v1.base_query import FieldFilter
17
  import edge_tts
18
+
19
  from typing import Optional, List
20
  from dotenv import load_dotenv
21
  from contextlib import asynccontextmanager
 
27
 
28
  if not firebase_admin._apps:
29
  fb_json = os.getenv("FIREBASE_JSON")
30
+
31
  if fb_json:
32
  cred_dict = json.loads(fb_json)
33
  cred = credentials.Certificate(cred_dict)
34
  else:
35
  cred = credentials.Certificate("serviceAccountKey.json")
36
+
37
  firebase_admin.initialize_app(cred)
38
 
39
  db = firestore.client()
 
49
  client = None
50
 
51
  # =========================================
52
+ # 3. CONSTANTS & MODELS
53
  # =========================================
54
  TECH_CATEGORIES = {
55
+ 0: "Security",
56
+ 1: "BackEnd",
57
+ 2: "Networking",
58
+ 3: "FrontEnd",
59
+ 4: "DataEngineering",
60
+ 5: "WebDevelopment",
61
+ 6: "FullStack",
62
+ 7: "VersionControl",
63
+ 8: "SystemDesign",
64
+ 9: "MachineLearning",
65
+ 10: "LanguagesAndFrameworks",
66
+ 11: "DatabaseSystems",
67
+ 12: "ArtificialIntelligence",
68
+ 13: "SoftwareTesting",
69
+ 14: "DistributedSystems",
70
+ 15: "DevOps",
71
+ 16: "LowLevelSystems",
72
+ 17: "DatabaseAndSql",
73
+ 18: "GeneralProgramming",
74
+ 19: "DataStructures",
75
+ 20: "Algorithms"
76
+ }
77
+
78
+ DIFFICULTY_MAP = {
79
+ 0: "Easy",
80
+ 1: "Intermediate",
81
+ 2: "Hard"
82
  }
83
 
 
84
 
85
  class GenerateSessionRequest(BaseModel):
86
  sessionId: str
87
+
88
+ # 0 = Behavioral
89
+ # 1 = Technical
90
+ sessionType: int
91
+
92
+ # IMPORTANT:
93
+ # difficultyLevel is ONLY used for technical sessions
94
+ difficultyLevel: Optional[int] = None
95
+
96
+ # ONLY required for technical sessions
97
+ trackName: Optional[int] = None
98
+
99
 
100
  class CleanupRequest(BaseModel):
101
  audioUrls: List[str]
102
 
103
+
104
  # =========================================
105
  # 4. LIFESPAN MANAGEMENT
106
  # =========================================
107
  @asynccontextmanager
108
  async def lifespan(app: FastAPI):
109
  global client
110
+
111
  print("Connecting to Hugging Face Model...")
112
+
113
  try:
114
  loop = asyncio.get_event_loop()
115
+
116
+ client = await loop.run_in_executor(
117
+ None,
118
+ lambda: Client(HF_SPACE)
119
+ )
120
+
121
  print("Model Connected Successfully!")
122
+
123
  except Exception as e:
124
  print(f"Model Connection Failed: {e}")
125
+
126
  yield
127
+
128
  print("Shutting down Intervision Service...")
129
 
130
+
131
+ app = FastAPI(
132
+ title="Intervision AI Question Service",
133
+ lifespan=lifespan
134
+ )
135
 
136
  # =========================================
137
+ # 5. HELPER FUNCTIONS
138
  # =========================================
139
+ async def generate_audio(text: str, filename: str):
140
  try:
141
+ communicate = edge_tts.Communicate(
142
+ text,
143
+ "en-US-GuyNeural",
144
+ rate="-15%"
145
+ )
146
+
147
  await communicate.save(filename)
148
+
149
  upload_result = cloudinary.uploader.upload(
150
+ filename,
151
+ resource_type="video",
152
+ folder="interview_audio"
153
  )
154
+
155
+ if os.path.exists(filename):
156
+ os.remove(filename)
157
+
158
  return upload_result["secure_url"]
159
+
160
  except Exception as e:
161
  print(f"Audio Generation Error: {e}")
162
+
163
+ if os.path.exists(filename):
164
+ os.remove(filename)
165
+
166
  return None
167
 
168
+
169
+ async def safe_generate(prompt: str, retries: int = 5):
170
+ if client is None:
171
+ raise Exception("AI Client is not initialized.")
172
+
173
  for attempt in range(retries):
174
  try:
175
  loop = asyncio.get_running_loop()
176
+
177
+ result = await loop.run_in_executor(
178
+ None,
179
+ lambda: client.predict(
180
+ prompt=prompt,
181
+ api_name="/generate_questions"
182
+ )
183
+ )
184
+
185
+ return result
186
+
187
  except Exception as e:
188
+ if attempt == retries - 1:
189
+ raise e
190
+
191
  await asyncio.sleep(5)
192
 
193
+
194
  def parse_question_output(raw_output: str):
195
+ if not raw_output:
196
+ return None, None
197
+
198
+ text = (
199
+ raw_output.split("assistant")[-1].strip()
200
+ if "assistant" in raw_output
201
+ else raw_output
202
+ )
203
+
204
  if "Q:" in text and "A:" in text:
205
  try:
206
  parts = text.split("A:")
207
+
208
+ question = parts[0].replace("Q:", "").strip()
209
+
210
+ answer = (
211
+ parts[1]
212
+ .split("<|im_end|>")[0]
213
+ .strip()
214
+ )
215
+
216
+ return question, answer
217
+
218
+ except Exception:
219
+ return None, None
220
+
221
  return None, None
222
 
223
+
224
+ # =========================================
225
+ # 6. REFILL QUESTION POOLS
226
+ # =========================================
227
+ async def refill_specific_pool(
228
+ track_id: Optional[int],
229
+ difficulty: Optional[int],
230
+ count: int,
231
+ session_type: int
232
+ ):
233
+ while client is None:
234
+ await asyncio.sleep(5)
235
+
236
+ # =====================================
237
+ # Behavioral Questions
238
+ # =====================================
239
  if session_type == 0:
240
+
241
  track_text = "Behavioral"
242
+
243
+ prompt = (
244
+ "Generate ONE unique behavioral interview question. "
245
+ "Format: Q: [Question] A: [Answer]"
246
+ )
247
+
248
+ # =====================================
249
+ # Technical Questions
250
+ # =====================================
251
  else:
252
+
253
  track_text = TECH_CATEGORIES.get(track_id)
254
+
255
  level_text = DIFFICULTY_MAP.get(difficulty)
256
+
257
+ prompt = (
258
+ f"Generate ONE unique {track_text} "
259
+ f"question for {level_text} level. "
260
+ f"Format: Q: [Question] A: [Answer]"
261
+ )
262
 
263
  success_count = 0
264
+
265
  while success_count < count:
266
  try:
267
  raw_output = await safe_generate(prompt)
268
+
269
+ question, answer = parse_question_output(raw_output)
270
+
271
+ if question and answer:
272
+
273
+ audio_url = await generate_audio(
274
+ question,
275
+ f"{uuid.uuid4()}.mp3"
276
+ )
277
+
278
  if audio_url:
279
+
280
+ question_data = {
281
  "session_type": session_type,
282
+ "questionText": question,
283
+ "questionIdealAnswer": answer,
 
 
284
  "audio_url": audio_url,
285
  "created_at": firestore.SERVER_TIMESTAMP
286
+ }
287
+
288
+ # =================================
289
+ # Technical ONLY
290
+ # =================================
291
+ if session_type == 1:
292
+ question_data["track_id"] = track_id
293
+ question_data["difficulty"] = difficulty
294
+
295
+ db.collection("questions_pool").add(question_data)
296
+
297
  success_count += 1
298
+
299
+ print(
300
+ f"Successfully added "
301
+ f"{track_text} question "
302
+ f"{success_count}/{count}"
303
+ )
304
+
305
  await asyncio.sleep(2)
306
+
307
  except Exception as e:
308
  print(f"Refill error: {e}")
309
  await asyncio.sleep(5)
310
 
311
  # =========================================
312
+ # 7. API ENDPOINTS
313
  # =========================================
314
  @app.post("/generate-session")
315
+ async def generate_session(
316
+ request: GenerateSessionRequest,
317
+ background_tasks: BackgroundTasks
318
+ ):
319
+ session_type = request.sessionType
320
+ track_id = request.trackName
321
+
322
+ # =====================================
323
+ # Behavioral Session
324
+ # =====================================
325
+ if session_type == 0:
326
+
327
+ query = db.collection("questions_pool").where(
328
+ filter=FieldFilter("session_type", "==", 0)
329
+ )
330
+
331
+ # =====================================
332
+ # Technical Session
333
+ # =====================================
334
+ elif session_type == 1:
335
+
336
+ if track_id is None:
337
+ raise HTTPException(
338
+ status_code=400,
339
+ detail="trackName is required for technical sessions."
340
+ )
341
+
342
+ if request.difficultyLevel is None:
343
+ raise HTTPException(
344
+ status_code=400,
345
+ detail="difficultyLevel is required for technical sessions."
346
+ )
347
+
348
+ difficulty = request.difficultyLevel
349
+
350
+ query = (
351
+ db.collection("questions_pool")
352
+ .where(filter=FieldFilter("session_type", "==", 1))
353
+ .where(filter=FieldFilter("track_id", "==", track_id))
354
+ .where(filter=FieldFilter("difficulty", "==", difficulty))
355
+ )
356
 
 
 
 
 
 
357
  else:
358
+ raise HTTPException(
359
+ status_code=400,
360
+ detail="Invalid sessionType."
361
+ )
362
+
363
  docs = query.limit(10).get()
364
+
365
  final_questions = []
366
+
367
  for index, doc in enumerate(docs, start=1):
368
+
369
  data = doc.to_dict()
370
+
371
  final_questions.append({
372
+ "question_id": index,
373
+ "text": data["questionText"],
374
+ "expected_answer": data["questionIdealAnswer"],
375
+ "audio_url": data.get("audio_url", "")
376
  })
377
+
378
+ # remove used question
379
  db.collection("questions_pool").document(doc.id).delete()
380
 
381
+ # =====================================
382
+ # BACKGROUND REFILL
383
+ # =====================================
384
  async def check_and_refill_background():
385
+
386
  snap = query.count().get()
387
  current_count = snap[0][0].value
388
+
389
  if current_count < 50:
390
+
391
+ if session_type == 0:
392
+
393
+ print(
394
+ f"Behavioral stock low "
395
+ f"({current_count}) -> refilling..."
396
+ )
397
+
398
+ await refill_specific_pool(
399
+ track_id=None,
400
+ difficulty=None,
401
+ count=50 - current_count,
402
+ session_type=0
403
+ )
404
+
405
+ else:
406
+
407
+ print(
408
+ f"{TECH_CATEGORIES[track_id]} stock low "
409
+ f"({current_count}) -> refilling..."
410
+ )
411
+
412
+ await refill_specific_pool(
413
+ track_id=track_id,
414
+ difficulty=difficulty,
415
+ count=50 - current_count,
416
+ session_type=1
417
+ )
418
 
419
  background_tasks.add_task(check_and_refill_background)
420
+
421
  if not final_questions:
422
+ raise HTTPException(
423
+ status_code=503,
424
+ detail="The question pool is empty. Please try again in a few minutes."
425
+ )
426
 
427
+ return {
428
+ "session_id": request.sessionId,
429
+ "questions": final_questions
430
+ }
431
+
432
+
433
+ # =========================================
434
+ # 8. ADMIN PREFILL
435
+ # =========================================
436
  @app.get("/admin/prefill-all")
437
  async def prefill_all(background_tasks: BackgroundTasks):
438
+
439
  async def run_sync():
440
+
441
  print("Starting Global Smart Prefill...")
442
+
443
+ # =================================
444
+ # Behavioral Questions
445
+ # =================================
446
+ behavioral_query = (
447
+ db.collection("questions_pool")
448
+ .where(filter=FieldFilter("session_type", "==", 0))
449
+ )
450
+
451
+ behavioral_snap = behavioral_query.count().get()
452
+
453
+ behavioral_count = behavioral_snap[0][0].value
454
+
455
+ if behavioral_count < 50:
456
+
457
+ needed = 50 - behavioral_count
458
+
459
+ print(f"Syncing Behavioral: adding {needed}")
460
+
461
+ await refill_specific_pool(
462
+ track_id=None,
463
+ difficulty=None,
464
+ count=needed,
465
+ session_type=0
466
+ )
467
+
468
+ # =================================
469
+ # Technical Questions
470
+ # =================================
471
+ for track_id, track_name in TECH_CATEGORIES.items():
472
+
473
+ for diff_id, diff_name in DIFFICULTY_MAP.items():
474
+
475
+ technical_query = (
476
+ db.collection("questions_pool")
477
+ .where(filter=FieldFilter("session_type", "==", 1))
478
+ .where(filter=FieldFilter("track_id", "==", track_id))
479
+ .where(filter=FieldFilter("difficulty", "==", diff_id))
480
+ )
481
+
482
+ snap = technical_query.count().get()
483
+
484
  current = snap[0][0].value
485
+
486
  if current < 50:
487
+
488
+ needed = 50 - current
489
+
490
+ print(
491
+ f"Syncing {track_name} "
492
+ f"({diff_name}): adding {needed}"
493
+ )
494
+
495
+ await refill_specific_pool(
496
+ track_id=track_id,
497
+ difficulty=diff_id,
498
+ count=needed,
499
+ session_type=1
500
+ )
501
+
502
  print("Global Smart Prefill Completed!")
503
 
504
  background_tasks.add_task(run_sync)
 
505
 
506
+ return {
507
+ "message": "Global prefill process started in the background."
508
+ }
509
+
510
+
511
+ # =========================================
512
+ # 9. CLEANUP AUDIO
513
+ # =========================================
514
  @app.post("/cleanup-audio")
515
+ async def cleanup_audio(
516
+ request: CleanupRequest,
517
+ background_tasks: BackgroundTasks
518
+ ):
519
  def delete_job(urls):
520
+
521
  for url in urls:
522
  try:
523
+ public_id = (
524
+ "interview_audio/"
525
+ + url.split("/")[-1].split(".")[0]
526
+ )
527
+
528
+ cloudinary.uploader.destroy(
529
+ public_id,
530
+ resource_type="video"
531
+ )
532
+
533
+ except Exception:
534
+ pass
535
+
536
+ background_tasks.add_task(
537
+ delete_job,
538
+ request.audioUrls
539
+ )
540
+
541
+ return {
542
+ "message": "Cloudinary cleanup process initiated."
543
+ }
544
+
545
 
546
+ # =========================================
547
+ # 10. HEALTH CHECK
548
+ # =========================================
549
  @app.get("/health")
550
  async def health():
551
+ return {
552
+ "status": "active",
553
+ "ai_model_connected": client is not None
554
+ }
555
+
556
 
557
  @app.get("/")
558
  async def root():
559
+ return {
560
+ "app": "Intervision AI Engine",
561
+ "status": "Running"
562
+ }
563
+
564
 
565
+ # =========================================
566
+ # 11. RUN SERVER
567
+ # =========================================
568
  if __name__ == "__main__":
569
+
570
  import uvicorn
571
+
572
+ uvicorn.run(
573
+ "main:app",
574
+ host="0.0.0.0",
575
+ port=8000,
576
+ reload=True
577
+ )