Deploy to S3 / checks (push) Failing after 2m24sDetails
Deploy to S3 / deploy (push) Has been skippedDetails
The bank used to be write-only: a CV uploaded with no job carried only its
full text, so nothing could search or rank it. Now the employment agent's
extraction (title, company, education, plus new skills and years_experience,
both clamped to what the resume actually states) is stored on the row, and
the bank is ranked against a job the moment that job opens.
- New CV Bank screen at /cvbank replaces Talent Pool; the inline bank card
moves out of CV Import. One table, two populations: speculative uploads,
and silver medalists (rejected applicants scoring >= CV_BANK_SILVER_FLOOR,
read live from their application rather than copied).
- matching/ranking.py: the tier-1 keyword ranker moves out of
talent/plugins.py so Find Talent and the bank share one implementation;
talent/plugins.py re-exports it and its numbers are unchanged.
- Taskiq tasks in job.candidate.bank_tasks: backfill profiles for CVs
banked before extraction existed, and rank the bank when a job opens so
recruiters are told about matches above CV_BANK_SUGGEST_THRESHOLD.
Retention (CV_BANK_RETENTION_MONTHS) is stamped on the row at upload; the
sweep flags expired rows and never deletes.
- Migrations 029 (bank profile columns) and 030 (per-job bank matches).
- Routes: POST /candidate/cv-bank/score, GET /candidate/cv-bank/suggestions.
- README: The CV Bank, plus the retention and deletion policy.
Also in this change:
- Inbox, Sheet Forms: has_linkedin / has_resume filters, tri-valued so
"no link" is a real filter and NULL rows are kept in it; tab badge counts
now narrow with the list and the search box.
- Hiring-manager candidate rows carry the ATS score and band.
- Tests: analytics dashboard merge logic, employment extraction clamps,
form-data filters, manager candidate serializer, CV Bank mapper.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Dashboard/chart audit (senior-analyst pass over the live app):
- Pipeline bars were all 0% while the doughnut showed candidates: the
percentage base was the first stage count (PROCESS, empty). Bars are
now each stage share of the active total, REJECTED is excluded
(matching the Analytics card), and stages sort in pipeline order.
- KPI trend chips no longer show a green up-arrow beside an empty delta;
for lower-is-better metrics (time to hire, cost per hire) the colour
tracks goodness while the arrow tracks the data direction.
- Closed Jobs tile no longer adds hires to closed requisitions and no
longer hardcodes an up arrow; Offers Accepted drops the hires-series
sparkline that plotted the wrong metric.
- charts.js: integer-friendly Y ticks (no more 0,1,1,2,2), edge-hugging
X labels (last month was clipped), and label thinning on packed axes.
Find Talent: nav item and page title renamed from Talent to match the
action button and to distinguish it from Talent Pool.
Already-applied matching (Find Talent x ATS):
- linkedin_utils.py: shared /in/<slug> extraction that survives PDF
mangling (percent-escapes, no scheme, trailing punctuation, /pub/).
- inbox_messages.linkedin_slug + manual_upload_candidate.linkedin_slug
(indexed; empty = scanned-none, NULL = unscanned) written as CVs are
processed and lazily backfilled in bounded batches at fetch time.
- talent/matching.py annotates profile payloads with already_applied
(source, status, job, same_job, applications); profile cards and the
detail modal show a green Already-applied / amber In-ATS-other-job
chip.
Verified live: seeded CV mention matched its sourced profile with zero
false positives across 50 real Apify profiles; 57 backend tests and the
frontend build + smoke suites pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- New backend/tasks/ package (offer/ house style): tasks table
(title/status/priority/due_date/assignee_id -> users.id, optional
inbox_id/job_post_id links, soft delete), routes /tasks/fetch|create|
update|delete guarded by new tasks.* permission tags
- Assignees validated against users+roles: must exist, not deleted, not
candidate-role; omitted assignee defaults to the caller; responses carry
assignee_name/assignee_role from one batched join
- TASKS permission module (104 -> 112 tags); manual/004_tasks_rbac.sql
seeds the tags + tasks_management bundle onto 6 staff roles (auto-applies
at startup)
- Tasks.jsx cut over from seed to live: real create/complete/reopen with
optimistic flip, two-click delete in the detail modal, assignee picker
listing real users with roles; live sidebar badge (open-task total);
route now requires tasks.view
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>