HR-ATS-Portal/frontend/src/api/analytics.js

114 lines
3.4 KiB
JavaScript

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 = [
'CLOSED', '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 stays CLOSED and only
* REJECTED is Rejected. ONHOLD / APPROVED keep their own columns.
*/
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,
},
})
}