fix: use real candidate count, remove stage2 cap, add full error logging
Browse files
backend/src/matching/stage2.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
|
|
| 1 |
from typing import Any
|
| 2 |
from ..ml.reranker import rerank
|
| 3 |
|
|
|
|
|
|
|
| 4 |
|
| 5 |
def _compute_gaps(jd: dict, candidate: dict) -> list[dict]:
|
| 6 |
gaps = []
|
|
@@ -102,7 +105,13 @@ async def stage2_rerank(jd: dict, shortlist: list[dict]) -> list[dict]:
|
|
| 102 |
|
| 103 |
from fastapi.concurrency import run_in_threadpool
|
| 104 |
from .reranker import rerank
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
weights = jd.get("custom_weights", {})
|
| 108 |
results = _recompute_final_score(shortlist, reranker_scores, weights)
|
|
@@ -110,4 +119,5 @@ async def stage2_rerank(jd: dict, shortlist: list[dict]) -> list[dict]:
|
|
| 110 |
for cand in results:
|
| 111 |
cand["gaps"] = _compute_gaps(jd, cand)
|
| 112 |
|
| 113 |
-
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
from typing import Any
|
| 3 |
from ..ml.reranker import rerank
|
| 4 |
|
| 5 |
+
logger = logging.getLogger(__name__)
|
| 6 |
+
|
| 7 |
|
| 8 |
def _compute_gaps(jd: dict, candidate: dict) -> list[dict]:
|
| 9 |
gaps = []
|
|
|
|
| 105 |
|
| 106 |
from fastapi.concurrency import run_in_threadpool
|
| 107 |
from .reranker import rerank
|
| 108 |
+
try:
|
| 109 |
+
logger.info(f"[Stage 2] Starting neural reranking of {len(passages)} candidates...")
|
| 110 |
+
reranker_scores = await run_in_threadpool(rerank, jd_query, passages)
|
| 111 |
+
logger.info(f"[Stage 2] Reranking complete. Got {len(reranker_scores)} scores.")
|
| 112 |
+
except Exception as exc:
|
| 113 |
+
logger.exception(f"[Stage 2] FATAL: Neural reranker crashed — {exc}")
|
| 114 |
+
raise
|
| 115 |
|
| 116 |
weights = jd.get("custom_weights", {})
|
| 117 |
results = _recompute_final_score(shortlist, reranker_scores, weights)
|
|
|
|
| 119 |
for cand in results:
|
| 120 |
cand["gaps"] = _compute_gaps(jd, cand)
|
| 121 |
|
| 122 |
+
# Return ALL candidates — no arbitrary cap
|
| 123 |
+
return results
|
backend/src/routers/matching.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
| 1 |
import uuid
|
| 2 |
import json
|
|
|
|
| 3 |
import redis.asyncio as redis
|
| 4 |
from datetime import datetime, timezone
|
| 5 |
from fastapi import APIRouter, Depends, HTTPException, Request, Query
|
| 6 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 7 |
from sqlalchemy import select, delete
|
| 8 |
|
|
|
|
|
|
|
| 9 |
from ..database import get_db
|
| 10 |
from ..config import get_settings
|
| 11 |
from ..models.jd import JobDescription
|
|
@@ -115,7 +118,8 @@ async def trigger_match(
|
|
| 115 |
inserted_mrs.append(mr)
|
| 116 |
|
| 117 |
await db.commit()
|
| 118 |
-
except Exception:
|
|
|
|
| 119 |
await db.rollback()
|
| 120 |
raise
|
| 121 |
|
|
|
|
| 1 |
import uuid
|
| 2 |
import json
|
| 3 |
+
import logging
|
| 4 |
import redis.asyncio as redis
|
| 5 |
from datetime import datetime, timezone
|
| 6 |
from fastapi import APIRouter, Depends, HTTPException, Request, Query
|
| 7 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 8 |
from sqlalchemy import select, delete
|
| 9 |
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
from ..database import get_db
|
| 13 |
from ..config import get_settings
|
| 14 |
from ..models.jd import JobDescription
|
|
|
|
| 118 |
inserted_mrs.append(mr)
|
| 119 |
|
| 120 |
await db.commit()
|
| 121 |
+
except Exception as exc:
|
| 122 |
+
logger.exception(f"[trigger_match] FATAL ERROR for JD {jd_id}: {exc}")
|
| 123 |
await db.rollback()
|
| 124 |
raise
|
| 125 |
|
frontend/src/app/pipeline/page.tsx
CHANGED
|
@@ -173,14 +173,24 @@ export default function PipelinePage() {
|
|
| 173 |
|
| 174 |
const runMatches = async (jdIds: string[], sessionId: string, currentState: PipelineState) => {
|
| 175 |
let pendingJds = [...jdIds];
|
| 176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
const pollMatches = async () => {
|
| 178 |
try {
|
| 179 |
const stillPending: string[] = [];
|
| 180 |
-
|
| 181 |
for (const jdId of pendingJds) {
|
| 182 |
try {
|
| 183 |
-
|
|
|
|
| 184 |
} catch (e: any) {
|
| 185 |
if (e.message === "202_ACCEPTED") {
|
| 186 |
stillPending.push(jdId);
|
|
|
|
| 173 |
|
| 174 |
const runMatches = async (jdIds: string[], sessionId: string, currentState: PipelineState) => {
|
| 175 |
let pendingJds = [...jdIds];
|
| 176 |
+
|
| 177 |
+
// Fetch the ACTUAL number of ingested candidates — never hardcode a cap
|
| 178 |
+
let candidateCount = 100; // safe fallback
|
| 179 |
+
try {
|
| 180 |
+
const countRes = await api.candidateCount(sessionId);
|
| 181 |
+
candidateCount = countRes.count;
|
| 182 |
+
} catch (_) {
|
| 183 |
+
// If count fetch fails, fall back to 100
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
const pollMatches = async () => {
|
| 187 |
try {
|
| 188 |
const stillPending: string[] = [];
|
| 189 |
+
|
| 190 |
for (const jdId of pendingJds) {
|
| 191 |
try {
|
| 192 |
+
// Pass EXACT candidate count as both Stage 1 and Stage 2 top_k
|
| 193 |
+
await api.triggerMatch(jdId, sessionId, candidateCount, candidateCount);
|
| 194 |
} catch (e: any) {
|
| 195 |
if (e.message === "202_ACCEPTED") {
|
| 196 |
stillPending.push(jdId);
|