import { request } from '../lib/apiClient' import { toStageCounts } from './pipeline' /** * Dashboard analytics aggregates — backend/analytics/app.py. * Permissioned with require_permission(ANALYTICS_VIEW). */ export function kpis({ fromDate, toDate, department, recruiterId } = {}) { return request('/analytics/kpis/fetch', { params: { from_date: fromDate, to_date: toDate, department, recruiter_id: recruiterId, }, }) } export function funnel({ fromDate, toDate, department, recruiterId } = {}) { return request('/analytics/funnel/fetch', { params: { from_date: fromDate, to_date: toDate, department, recruiter_id: recruiterId, }, }) } /** Board column order used by the pipeline page. Rejected is last so callers * that drop outcomes can slice it off without re-sorting. */ const BOARD_STAGE_ORDER = [ 'Shortlist', 'Screening', 'Assessment', 'Interview', 'Offer', 'Approved', 'Hired', 'On Hold', 'Rejected', ] /** * Fold /analytics/funnel/fetch rows (11 enum statuses) onto the pipeline * columns. Same mapping the board uses, so CLOSED is Rejected and ONHOLD / * APPROVED keep their own columns rather than folding into Screening / Hired. */ export function toBoardStageRows(funnelRows, { includeRejected = false } = {}) { const folded = toStageCounts( Object.fromEntries((funnelRows || []).map((r) => [r.stage, r.count || 0])), ) const order = includeRejected ? BOARD_STAGE_ORDER : BOARD_STAGE_ORDER.filter((s) => s !== 'Rejected') return order.map((stage) => ({ stage, count: folded[stage] || 0 })) } export function hiringTrend({ months = 7, fromDate, toDate, department, recruiterId } = {}) { return request('/analytics/hiring-trend/fetch', { params: { months, from_date: fromDate, to_date: toDate, department, recruiter_id: recruiterId, }, }) } export function sourcePerformance({ fromDate, toDate, department, recruiterId } = {}) { return request('/analytics/source-performance/fetch', { params: { from_date: fromDate, to_date: toDate, department, recruiter_id: recruiterId, }, }) } /** * Applications received per job post — inbox + manual upload, the same two * sources the funnel counts, so this card and the pipeline card always agree. * Open reqs with zero applications are included; that emptiness is the signal. */ export function applicationsPerJob({ top = 10, fromDate, toDate, department, recruiterId } = {}) { return request('/analytics/applications-per-job/fetch', { params: { top, from_date: fromDate, to_date: toDate, department, recruiter_id: recruiterId, }, }) } /** * Natural-language analytics. The backend maps the question onto one whitelisted * analytics intent, runs the same governed query the dashboard uses, and returns * { answer, intent, params, data }. 503 means the AI service is unreachable — * the charts on this screen are unaffected. */ export function ask(question) { return request('/analytics/ask', { method: 'POST', body: { question } }) } export function recruiterPerformance({ top = 5, fromDate, toDate, department, recruiterId } = {}) { return request('/analytics/recruiter-performance/fetch', { params: { top, from_date: fromDate, to_date: toDate, department, recruiter_id: recruiterId, }, }) }