diff --git a/backend/g_sheet/views.py b/backend/g_sheet/views.py index 3f60fb8..7080edd 100644 --- a/backend/g_sheet/views.py +++ b/backend/g_sheet/views.py @@ -423,10 +423,10 @@ class SheetFormData(Sheet): post["band"]=hit.get("band") assigned_score=score_by_job.get(str(aid)) if aid else None if assigned_score and assigned_score.get("overall_score") is not None: - item["ats_score"]=assigned_score.get("overall_score") + item["ats_score"]=round(float(assigned_score.get("overall_score"))) else: nums=[s.get("overall_score") for s in scores if s.get("overall_score") is not None] - item["ats_score"]=max(nums) if nums else None + item["ats_score"]=round(float(max(nums))) if nums else None return items async def get_form_data( diff --git a/frontend/src/screens/Inbox.jsx b/frontend/src/screens/Inbox.jsx index ccb4492..3557eba 100644 --- a/frontend/src/screens/Inbox.jsx +++ b/frontend/src/screens/Inbox.jsx @@ -166,6 +166,13 @@ function formReceivedAt(entryDate, entryTime) { return d } +/** Same numeric gate as email `ats_score` / pipeline ScoreChip. */ +function asAtsScore(value) { + if (value == null || value === '') return null + const n = Number(value) + return Number.isFinite(n) ? n : null +} + /** * GET /sheet/form-data/fetch row → the same list/detail shape the email channel * uses for name / avatar / position / source / time, plus form-only profile fields. @@ -179,12 +186,12 @@ function mapFormRow(row) { ? String(row.assigned_job_post_id) : (row.job_post_id ? String(row.job_post_id) : null) const assignedScore = atsResults.find((s) => String(s.job_post_id) === String(assignedId)) - const numericScores = atsResults - .map((s) => Number(s.overall_score)) - .filter((n) => Number.isFinite(n)) - const atsScore = row.ats_score - ?? assignedScore?.overall_score - ?? (numericScores.length ? Math.max(...numericScores) : null) + const fromResults = atsResults.map((s) => asAtsScore(s.overall_score)).filter((n) => n != null) + const fromJobs = jobPosts.map((p) => asAtsScore(p.overall_score)).filter((n) => n != null) + const atsScore = asAtsScore(row.ats_score) + ?? asAtsScore(assignedScore?.overall_score) + ?? (fromResults.length ? Math.max(...fromResults) : null) + ?? (fromJobs.length ? Math.max(...fromJobs) : null) const state = row.processing_state || 'unread' return { kind: 'form', @@ -380,7 +387,7 @@ async function fetchApplications(params) { linkedinSlug: row.linkedin_slug || '', linkedinUrl: row.linkedin_url || '', resumeText: row.resume_text || '', - atsScore: row.ats_score, + atsScore: asAtsScore(row.ats_score), phone: row.phone, experience: row.experience, recruiter: row.recruiter, @@ -869,7 +876,19 @@ export default function Inbox() { enabled: Boolean(selectedId), }) - const selectedRow = inbox.find((i) => i.id === selectedId) + const sidebar = useMemo(() => { + if (!isForms) return list + const detail = detailQuery.data + const detailScore = asAtsScore(detail?.atsScore) + if (!detail?.id || detailScore == null) return list + return list.map((row) => ( + String(row.id) === String(detail.id) + ? { ...row, atsScore: asAtsScore(row.atsScore) ?? detailScore } + : row + )) + }, [isForms, list, detailQuery.data]) + + const selectedRow = sidebar.find((i) => i.id === selectedId) const selected = selectedRow || detailQuery.data ? { ...selectedRow, ...(detailQuery.data ?? {}) } : null @@ -1164,7 +1183,7 @@ export default function Inbox() { {isForms ? 'No form responses in this sheet tab.' : 'No applications in this view.'} ) : ( - list.map((i) => ( + sidebar.map((i) => (