- inbox_messages.ats_score/ats_band written on every completed inbox score;
inbox.ats_id always points at the current ats_results row
- ats_results now holds the supersede-chained history for BOTH inbox and
upload scores (new candidate_id link, inbox_id nullable for uploads)
- migrations/manual/*.sql apply automatically at startup, tracked once per
database in manual_migrations - developers just pull and boot
- 002_backfill_inbox_ats.sql backfills pre-existing scores
- Add Candidate modal: Matching-style role picker above the CV dropzone,
and the CV is scored via /candidate/score after creation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Conflict resolutions:
- backend/job/app.py: dropped duplicate pydantic import (already present below)
- backend/job/candidate/views.py: union of both sides — kept Talha's
_recommendation/_scores_by_message helpers alongside main's manual-upload
create_candidate, merged import lists and module-level config
- frontend/src/lib/apiClient.js: both sides made the same FormData fix;
kept main's version that reuses the shared multipart const
- frontend/src/screens/Candidates.jsx: kept Talha's live-data screen and
ported main's AddCandidate modal onto it — server POST via
candidatesApi.createManual with live job posts, seed-shaped buildRow
replaced by a candidates query invalidation; seed-only BulkAssign dropped
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The decoded attachment is already the CV and the job post is already the JD, so
scoring now fires from the data we have:
- After the agent matches an inbox message, score_message_against_job runs
inline in the match task - against the assigned job if one is set, else the
top suggested job. Failures log and never fail the match.
- When a recruiter assigns a job post, the new inbox.score_message task is
queued in the background so the request never waits on OpenAI.
Idempotent by (message, job): an already-completed score is never paid for
twice. Rows are attributed to the job post owner since background tasks have
no request user. CV Import and the profile Score-with-ATS button remain as
manual fallbacks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Main stubbed ai_score/recommendation as None in the candidate-profile payload;
fill them from the scoring engine's candidates table, joined by inbox message
(one batched query for the list, assigned-job-preferred for the detail). The
detail payload also gains matched/missing keywords, the critique, and which job
the score was against.
Frontend: TalentPool cards and the profile hero prefer the real score over the
seed placeholder, and the profile grows a Score-with-ATS button (shown when the
candidate has an assigned job and an inbox message) that runs the existing
score_inbox pipeline and repaints via the detail refetch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Union resolution, same policy as the previous merge — both feature sets kept:
- app.py: scoring routes kept; main's richer GET /job/fetch (search/top/skip/ids)
adopted, with the dual JOB_BOARD_VIEW-or-CANDIDATES_VIEW permission restored so
recruiters with only candidate rights keep the CV Import job picker.
- candidate/models.py: Candidates (scoring) coexists with main's new Interviews,
Notes, Activity, Feedback models.
- candidate/serializers.py: serialize_candidate kept alongside main's
detail-mode serialize_candidate_profile.
- CandidateProfile.jsx: main's rich tabbed profile (interviews/notes/feedback/
activity) is now THE CandidateProfile, used by TalentPool; the scored-CV modal
moved to ScoredCandidateProfile.jsx, used by Candidates.jsx. The two payload
shapes share almost no fields, hence two components.
Backend imports verified; frontend builds clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both sides had rewritten the candidate surface, so the merge keeps the two
features side by side instead of picking one:
- GET /candidate/fetch stays main's paginated inbox-profile listing
(CandidateView); the scoring leaderboard moved to GET /candidate/scored/fetch
and the frontend listCandidates() now points there.
- backend candidate views/serializers keep both CandidateScoring and
CandidateView, serialize_candidate and serialize_candidate_profile.
- Candidates.jsx stays the scored table (Talha); TalentPool.jsx takes main's
profile card grid; AtsMatch is exported from Candidates for TalentPool's
modal and tolerates a missing jobTitle.
- CandidateProfile hides the Scored/Failed badge for rows that were never
scored (talent-pool profiles).
- queryKeys.js: dropped a duplicated candidates block the auto-merge produced.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Covers the whole system as it now runs: architecture (agent routing vs ATS
scoring), repo layout, clean-machine quick start incl. the fresh-DB enum
workaround, the backend scoring API surface with behavior guarantees, the
engine internals summary, QA status (28/28 audit, 23/23 integration), data
handling, and the honest gap/roadmap table.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backend: GET /job/fetch (active job posts, job-board OR candidate viewers) and
/candidate/fetch now works unscoped for the cross-job pool. Frontend: apiClient
gains FormData support; new api/candidates.js with a shared snake->camel view
mapper; CvImport is a real upload->score flow (job selector, PDF multipart to
/candidate/score, per-file results, no more simulation); TalentPool and
Candidates render the persisted pool with job/skill/source/ATS filters; the ATS
modal shows the real critique and matched/missing skills; CandidateProfile
keeps only tabs the backend can back. Screens hide affordances with no backing
column instead of rendering placeholders (Inbox precedent).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Embeds the tested bulk-ats engine (pip install -e ., app.services.*) rather
than duplicating it. New Candidates table (auto-migrated) persists one row per
CV per job, deduped on (job_id, content_sha256) so re-scores update in place.
CandidateScoring service extracts (pypdf off-loop), despaces, scores with
bounded concurrency + cache priming via llm_setup's shared client, and
isolates per-file failures as failed rows. Routes: /candidate/score (uploads),
/candidate/score_inbox (decoded attachments), /candidate/fetch,
/candidate/fetch_by_id. Complements the agent inbox-match flow: it routes,
this scores.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>