sajith-0701 commited on
Commit
8374980
·
1 Parent(s): 5837391
Files changed (1) hide show
  1. backend/services/interview_service.py +119 -11
backend/services/interview_service.py CHANGED
@@ -44,7 +44,7 @@ TOPIC_INITIAL_DB_QUESTIONS = 5
44
  TOPIC_INITIAL_ASK_COUNT = 4
45
  TOPIC_AI_FOLLOWUPS = 3
46
  TOPIC_DB_FOLLOWUPS = 2
47
- TOPIC_TOTAL_QUESTIONS = TOPIC_INITIAL_ASK_COUNT + TOPIC_AI_FOLLOWUPS + TOPIC_DB_FOLLOWUPS
48
 
49
  # Local process memory summary requested in workflow.
50
  _LOCAL_SUMMARIES: dict[str, str] = {}
@@ -263,6 +263,21 @@ def _build_resume_resilient_followup_question(session: dict, question_number: in
263
  return template.format(n=question_number, skill=skill, role=role_title)
264
 
265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  async def _enqueue_resume_followup_with_fallback(
267
  *,
268
  redis,
@@ -1569,27 +1584,47 @@ async def _post_submit_topic_processing(
1569
  session = await redis.hgetall(f"session:{session_id}")
1570
  if not session:
1571
  return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1572
  if session.get("topic_followups_generated", "0") == "1":
1573
  return
1574
 
1575
  qa_pairs = await get_session_qa(session_id)
1576
  excluded_questions = await _get_session_question_texts(redis, session_id)
1577
 
 
1578
  ai_items = await generate_topic_followup_batch(
1579
  topic_name=session.get("role_title", "Topic Interview"),
1580
  qa_pairs=qa_pairs,
1581
  excluded_questions=excluded_questions,
1582
- count=TOPIC_AI_FOLLOWUPS,
1583
  )
 
1584
  db_items = await _sample_topic_questions(
1585
  db=db,
1586
  topic_id=session.get("topic_id", ""),
1587
  excluded_questions=excluded_questions + [i.get("question", "") for i in ai_items],
1588
- limit=TOPIC_DB_FOLLOWUPS,
1589
  )
1590
 
1591
  topic_added = 0
1592
- for item in ai_items + db_items:
 
 
1593
  qid = await enqueue_question(
1594
  redis=redis,
1595
  session_id=session_id,
@@ -1601,23 +1636,69 @@ async def _post_submit_topic_processing(
1601
  )
1602
  if qid:
1603
  topic_added += 1
 
1604
 
1605
- generated_count = _safe_int(session.get("generated_count", 0)) + topic_added
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1606
  await _apply_generation_metric_delta(
1607
  db=db,
1608
  redis=redis,
1609
  session_id=session_id,
1610
  session=session,
1611
  metrics_delta={
1612
- "gemini_calls": 1,
1613
- "gemini_questions": len(ai_items),
1614
- "bank_questions": len(db_items),
1615
- "bank_shortfall": 0,
1616
  "generation_batches": 1,
1617
  },
1618
  generated_count=generated_count,
1619
- extra_redis_fields={"topic_followups_generated": "1"},
1620
- extra_db_fields={"topic_followups_generated": True},
 
 
 
 
 
 
1621
  )
1622
 
1623
  await flush_backlog_to_queue(
@@ -1717,6 +1798,14 @@ async def submit_answer(session_id: str, question_id: str, answer: str) -> dict:
1717
  {"$set": {"max_questions": max_questions}},
1718
  )
1719
 
 
 
 
 
 
 
 
 
1720
  _update_local_summary(session_id, current_question_text, answer)
1721
  await push_context_item(
1722
  redis=redis,
@@ -1827,6 +1916,25 @@ async def submit_answer(session_id: str, question_id: str, answer: str) -> dict:
1827
  )
1828
  next_question_id, q_data = await pop_next_question(redis, session_id)
1829
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1830
  if not next_question_id or not q_data:
1831
  await redis.hset(
1832
  f"session:{session_id}",
 
44
  TOPIC_INITIAL_ASK_COUNT = 4
45
  TOPIC_AI_FOLLOWUPS = 3
46
  TOPIC_DB_FOLLOWUPS = 2
47
+ TOPIC_TOTAL_QUESTIONS = 10
48
 
49
  # Local process memory summary requested in workflow.
50
  _LOCAL_SUMMARIES: dict[str, str] = {}
 
263
  return template.format(n=question_number, skill=skill, role=role_title)
264
 
265
 
266
+ def _build_topic_resilient_followup_question(session: dict, question_number: int, variant: int = 0) -> str:
267
+ topic_name = (session.get("role_title") or "this topic").strip()
268
+ index = max(0, question_number - 1) + max(0, variant)
269
+
270
+ templates = [
271
+ "Question {n}: Explain {topic} with a practical example from a production-like scenario.",
272
+ "Question {n}: What are the most common failure patterns in {topic}, and how would you detect them early?",
273
+ "Question {n}: Design a step-by-step implementation plan for {topic} with measurable checkpoints.",
274
+ "Question {n}: Compare two approaches in {topic}, including trade-offs in scalability, latency, and maintainability.",
275
+ "Question {n}: If a {topic} solution regressed after deployment, how would you triage and recover safely?",
276
+ ]
277
+ template = templates[index % len(templates)]
278
+ return template.format(n=question_number, topic=topic_name)
279
+
280
+
281
  async def _enqueue_resume_followup_with_fallback(
282
  *,
283
  redis,
 
1584
  session = await redis.hgetall(f"session:{session_id}")
1585
  if not session:
1586
  return
1587
+
1588
+ max_questions = max(
1589
+ TOPIC_TOTAL_QUESTIONS,
1590
+ _safe_int(session.get("max_questions", TOPIC_TOTAL_QUESTIONS)),
1591
+ )
1592
+ generated_count = _safe_int(session.get("generated_count", 0))
1593
+ remaining_needed = max(0, max_questions - generated_count)
1594
+
1595
+ if remaining_needed <= 0:
1596
+ await redis.hset(f"session:{session_id}", mapping={"topic_followups_generated": "1"})
1597
+ await db[SESSIONS].update_one(
1598
+ {"session_id": session_id},
1599
+ {"$set": {"topic_followups_generated": True, "max_questions": max_questions}},
1600
+ )
1601
+ return
1602
+
1603
  if session.get("topic_followups_generated", "0") == "1":
1604
  return
1605
 
1606
  qa_pairs = await get_session_qa(session_id)
1607
  excluded_questions = await _get_session_question_texts(redis, session_id)
1608
 
1609
+ ai_target = min(TOPIC_AI_FOLLOWUPS, remaining_needed)
1610
  ai_items = await generate_topic_followup_batch(
1611
  topic_name=session.get("role_title", "Topic Interview"),
1612
  qa_pairs=qa_pairs,
1613
  excluded_questions=excluded_questions,
1614
+ count=ai_target,
1615
  )
1616
+ db_target = max(0, remaining_needed - len(ai_items))
1617
  db_items = await _sample_topic_questions(
1618
  db=db,
1619
  topic_id=session.get("topic_id", ""),
1620
  excluded_questions=excluded_questions + [i.get("question", "") for i in ai_items],
1621
+ limit=db_target,
1622
  )
1623
 
1624
  topic_added = 0
1625
+ ai_added = 0
1626
+ db_added = 0
1627
+ for item in ai_items:
1628
  qid = await enqueue_question(
1629
  redis=redis,
1630
  session_id=session_id,
 
1636
  )
1637
  if qid:
1638
  topic_added += 1
1639
+ ai_added += 1
1640
 
1641
+ for item in db_items:
1642
+ qid = await enqueue_question(
1643
+ redis=redis,
1644
+ session_id=session_id,
1645
+ question=item.get("question", ""),
1646
+ difficulty=item.get("difficulty", "medium"),
1647
+ category=item.get("category", session.get("role_title", "topic")),
1648
+ ttl_seconds=SESSION_TTL,
1649
+ max_queue_size=MAX_QUEUE_SIZE,
1650
+ )
1651
+ if qid:
1652
+ topic_added += 1
1653
+ db_added += 1
1654
+
1655
+ fallback_added = 0
1656
+ fallback_variants = max(10, remaining_needed * 4)
1657
+ for variant in range(fallback_variants):
1658
+ if topic_added >= remaining_needed:
1659
+ break
1660
+
1661
+ next_question_number = generated_count + topic_added + 1
1662
+ fallback_text = _build_topic_resilient_followup_question(
1663
+ session=session,
1664
+ question_number=next_question_number,
1665
+ variant=variant,
1666
+ )
1667
+ qid = await enqueue_question(
1668
+ redis=redis,
1669
+ session_id=session_id,
1670
+ question=fallback_text,
1671
+ difficulty="medium",
1672
+ category="topic-fallback",
1673
+ ttl_seconds=SESSION_TTL,
1674
+ max_queue_size=MAX_QUEUE_SIZE,
1675
+ )
1676
+ if qid:
1677
+ topic_added += 1
1678
+ fallback_added += 1
1679
+
1680
+ generated_count += topic_added
1681
  await _apply_generation_metric_delta(
1682
  db=db,
1683
  redis=redis,
1684
  session_id=session_id,
1685
  session=session,
1686
  metrics_delta={
1687
+ "gemini_calls": 1 if ai_target > 0 else 0,
1688
+ "gemini_questions": ai_added,
1689
+ "bank_questions": db_added + fallback_added,
1690
+ "bank_shortfall": max(0, remaining_needed - topic_added),
1691
  "generation_batches": 1,
1692
  },
1693
  generated_count=generated_count,
1694
+ extra_redis_fields={
1695
+ "topic_followups_generated": "1",
1696
+ "max_questions": str(max_questions),
1697
+ },
1698
+ extra_db_fields={
1699
+ "topic_followups_generated": True,
1700
+ "max_questions": max_questions,
1701
+ },
1702
  )
1703
 
1704
  await flush_backlog_to_queue(
 
1798
  {"$set": {"max_questions": max_questions}},
1799
  )
1800
 
1801
+ if interview_type == "topic" and max_questions < TOPIC_TOTAL_QUESTIONS:
1802
+ max_questions = TOPIC_TOTAL_QUESTIONS
1803
+ await redis.hset(f"session:{session_id}", mapping={"max_questions": str(max_questions)})
1804
+ await db[SESSIONS].update_one(
1805
+ {"session_id": session_id},
1806
+ {"$set": {"max_questions": max_questions}},
1807
+ )
1808
+
1809
  _update_local_summary(session_id, current_question_text, answer)
1810
  await push_context_item(
1811
  redis=redis,
 
1916
  )
1917
  next_question_id, q_data = await pop_next_question(redis, session_id)
1918
 
1919
+ if (
1920
+ not next_question_id
1921
+ and interview_type == "topic"
1922
+ and answered_count < max_questions
1923
+ ):
1924
+ # Topic follow-up generation runs in background, so synchronously top-up once
1925
+ # before concluding interview to avoid premature completion around Q4.
1926
+ await _post_submit_topic_processing(
1927
+ session_id=session_id,
1928
+ answered_count=answered_count,
1929
+ )
1930
+ await flush_backlog_to_queue(
1931
+ redis=redis,
1932
+ session_id=session_id,
1933
+ ttl_seconds=SESSION_TTL,
1934
+ max_queue_size=MAX_QUEUE_SIZE,
1935
+ )
1936
+ next_question_id, q_data = await pop_next_question(redis, session_id)
1937
+
1938
  if not next_question_id or not q_data:
1939
  await redis.hset(
1940
  f"session:{session_id}",