- 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.
- Added a new `CandidateHistory` model to maintain an append-only audit log for candidate actions.
- Introduced `HistoryRecorder` functionality to log significant events across various modules, including interviews, feedback, and notes.
- Updated existing methods to include `current_user` for tracking who performed actions.
- Enhanced the `EMAIL_URL` configuration in `docker-compose.yml` to allow for environment variable overrides.
- Updated frontend components to support fetching and displaying candidate history.
This commit improves the application's ability to track changes and actions related to candidates, enhancing accountability and transparency.
- Updated the interview model to include `graph_event_id` and `web_link` for calendar integration.
- Added methods for setting calendar events and retrieving job titles associated with interviews.
- Modified serializers to include new fields in the interview response.
- Enhanced the job post model to include applicant counts.
- Updated frontend components to handle new fields and improve user experience with calendar event management.
- Introduced image upload functionality in job forms with validation for file types and sizes.
- Improved dashboard and calendar UI for better data presentation.
This commit streamlines interview scheduling and job management processes, enhancing overall application functionality.
- GET /jobs/export (jobs.export): styled .xlsx via openpyxl — branded
banner, dark-green frozen header with auto-filter, zebra rows, color-
coded status, bulleted requirements/nice-to-have, date formatting.
Honors the same filters as /jobs/fetch.
- Export button on the Jobs screen now downloads the file through the
authenticated downloadFile helper, carrying the active UI filters,
with an Exporting... busy state (was a fake success toast).
- openpyxl pinned in backend requirements.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deploy to S3 / deploy (push) Successful in 33sDetails
- New POST /job/assist-field (job_board.create OR jobs.edit): per-field
AI fix/suggest via backend/job_assist package on the shared llm_call.
Suggest is gated on title + experience anchors; decisive typo repair;
provider failures return a generic 503.
- AiFieldAssist component: sparkle button per field with fix/suggest,
loading, preview-before-apply, abort on close; wired into JobForm and
EditJobForm with per-field suggest hints.
- Create Job no longer requires a Buffer channel: without channel_id or
platform the backend saves an internal-only requisition (platform
internal) and never calls Buffer; publishing stays on the Job Board.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Assignees restricted to recruiter-role users (role id resolved from the
roles table per request, not hardcoded); server 422s any other role on
create and update
- New GET /tasks/assignees/fetch: active recruiter users for the picker,
separate from /users/fetch so assigning never needs rbac_users.view
- Creation gated to system_administrator / hr_administrator / recruiter:
allowed role ids searched from the DB and compared to the callers
role_id, on top of the tasks.create permission tag
- manual/005_tasks_rbac_restrict.sql: permission DB follows suit -
hiring_manager / department_head / ceo swap tasks_management for a
view-only tasks_viewer bundle (idempotent, auto-applies at boot)
- UI: assignee dropdown lists recruiters from the new endpoint with
required-field validation; recruiters get an assign-to-me default; the
New Task button honours the creator roles
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>
- 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>
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>