Backend: new GET /analytics/applications-per-job/fetch (analytics.view) counting
inbox + manual-upload applications per job on the same basis as the funnel, with
zero-application open reqs included. Per-job group-bys added to Inbox_Messages,
Manual_UPLOAD_CANDIDATE; JobPosts.list_open_reqs for the zero-fill.
Frontend: Dashboard rewritten as a filterable summary (Week/Month/Quarter/Year +
department) - My Tasks, Today's Schedule and Quick Actions removed; 8 KPI tiles
(Hires and Offers Sent now shown); Applications per Job hero row-list; pipeline
card refed from /analytics/funnel so it obeys filters; Offer Book, Needs
Attention and Recent Activity cards added. ChartCard and time ranges extracted
to shared modules used by both Dashboard and Analytics.
Co-Authored-By: Claude Fable 5 <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>
- Introduced a new `MailboxSyncRun` model to manage mailbox synchronization jobs, allowing for tracking of sync status and results.
- Implemented API endpoints for starting mailbox sync and fetching sync status, enabling asynchronous processing of email synchronization.
- Updated the `Email` service to handle mailbox sync operations, including enqueueing tasks and managing sync results.
- Enhanced frontend components to initiate mailbox sync and display sync status, improving user experience and feedback during the sync process.
- Added necessary query keys and local storage management for tracking sync runs in the UI.
This commit enhances the application's email synchronization capabilities, providing a more robust and user-friendly experience for managing mailbox data.
- 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>