Initial commit

This commit is contained in:
2026-08-07 23:53:15 +05:30
commit 90f14a73b2
222 changed files with 35000 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
/**
* Dashboard fact sheet.
*
* Every assistant answer is derived from here, using the same formulas the
* dashboard components use — so the assistant can never state a figure that
* contradicts the card or chart sitting next to it.
*
* This module is pure: dashboard data in, derived facts out. No UI, no
* transport, no dependency on any AI product.
*/
export const pct = (n, d) => (d ? Math.round((n / d) * 100) : 0);
export const plural = (n, word, irregular) =>
`${n} ${n === 1 ? word : irregular || `${word}s`}`;
export const verb = (n, singular, plural_) => (n === 1 ? singular : plural_);
const avg = (values) =>
values.length ? Math.round(values.reduce((a, b) => a + b, 0) / values.length) : 0;
const DAY_MS = 1000 * 60 * 60 * 24;
/** `today` is injected rather than read from the clock so answers are stable. */
export function buildFacts({
applications = [],
postings = [],
interviews = [],
staff = [],
profiles = [],
activity = [],
courses = [],
profile = null,
today = new Date(),
}) {
const scored = applications.filter((a) => a.ai_score > 0);
const ranked = [...scored].sort((a, b) => b.ai_score - a.ai_score);
const byStatus = (...statuses) => applications.filter((a) => statuses.includes(a.status));
const unscreened = byStatus('applied');
const screened = byStatus('ai_screened', 'shortlisted', 'interview', 'hired');
const interviewing = byStatus('interview');
const hired = byStatus('hired');
const openPositions = postings.filter((p) => p.status === 'active');
const applicationsFor = (id) => applications.filter((a) => a.job_posting_id === id);
const completedInterviews = interviews.filter((i) => i.overall_interview_score > 0);
/* Time-to-hire — the created→updated span ImpactMetrics uses. */
const timeToHire = hired.length
? Math.round(avg(hired.map((a) =>
Math.max(1, (new Date(a.updated_date) - new Date(a.created_date)) / DAY_MS))))
: 0;
/* Open roles attracting nobody. */
const starvedPositions = openPositions.filter((p) => applicationsFor(p.id).length === 0);
/* Roles resting on a single candidate — one withdrawal and the role reopens. */
const singleCandidateRoles = openPositions
.map((p) => ({ posting: p, viable: applicationsFor(p.id).filter((a) => a.ai_score >= 60) }))
.filter((e) => e.viable.length === 1);
/* Strong candidates nobody has moved on — the most expensive kind of delay. */
const stalled = ranked.filter((a) => a.ai_score >= 80 && a.status === 'ai_screened');
const funnel = [
{ key: 'applied', label: 'Applied', count: applications.length },
{ key: 'ai_screened', label: 'AI Screened', count: screened.length },
{ key: 'shortlisted', label: 'Shortlisted', count: byStatus('shortlisted', 'interview', 'hired').length },
{ key: 'interview', label: 'Interviewed', count: interviewing.length + hired.length },
{ key: 'hired', label: 'Hired', count: hired.length },
];
const transitions = funnel.slice(1).map((stage, i) => ({
from: funnel[i].label,
to: stage.label,
rate: pct(stage.count, funnel[i].count),
lost: Math.max(0, funnel[i].count - stage.count),
}));
const bottleneck = [...transitions].sort((a, b) => a.rate - b.rate)[0] || null;
const byRole = openPositions.map((p) => {
const apps = applicationsFor(p.id);
const roleScored = apps.filter((a) => a.ai_score > 0);
return {
posting: p,
title: p.title,
category: p.role_category || 'Uncategorized',
applied: apps.length,
screened: apps.filter((a) => a.status !== 'applied').length,
hired: apps.filter((a) => a.status === 'hired').length,
avgScore: avg(roleScored.map((a) => a.ai_score)),
qualified: roleScored.filter((a) => a.ai_score >= 70).length,
};
});
const since = (days) => {
const cutoff = today.getTime() - days * DAY_MS;
return activity.filter((e) => new Date(e.created_date).getTime() >= cutoff);
};
const eventCounts = activity.reduce((acc, e) => {
acc[e.event_type] = (acc[e.event_type] || 0) + 1;
return acc;
}, {});
return {
today,
/* Applications */
applications,
total: applications.length,
scored,
ranked,
top: ranked.slice(0, 3),
weak: ranked.filter((a) => a.ai_score > 0 && a.ai_score < 40),
unscreened,
screened,
interviewing,
hired,
stalled,
avgScore: avg(scored.map((a) => a.ai_score)),
hiredAvgScore: avg(hired.map((a) => a.ai_score)),
missingCredentials: scored.filter((a) => !(a.certifications || []).length),
narrowAvailability: scored.filter((a) => (a.availability || []).length <= 1),
/* Positions */
postings,
openPositions,
starvedPositions,
singleCandidateRoles,
byRole,
applicationsFor,
/* Interviews */
interviews,
completedInterviews,
avgInterviewScore: avg(completedInterviews.map((i) => i.overall_interview_score)),
interviewCompletion: pct(completedInterviews.length, interviews.length),
flagged: interviews.filter((i) => (i.integrity_score ?? 100) < 100),
/* Hires */
staff,
unratedStaff: staff.filter((s) => !s.client_rating),
timeToHire,
hireRate: pct(hired.length, applications.length),
/* Funnel */
funnel,
transitions,
bottleneck,
/* Talent pool */
profiles,
elite: profiles.filter((p) => (p.krow_score || 0) >= 90),
unscoredProfiles: profiles.filter((p) => !(p.krow_score > 0)),
/* Activity */
activity,
activity7d: since(7),
activity24h: since(1),
eventCounts,
/* Signed-in worker (talent context only) */
profile,
courses,
/* Economics — mirrors ImpactMetrics */
costSaved: scored.length * 45 + interviews.length * 80,
hoursSaved: Math.round((scored.length * 15 + interviews.length * 30) / 60),
standardizedPct: pct(scored.length, applications.length),
};
}
/* ── Answer formatting ──────────────────────────────────────────────────── */
export const bullets = (items) => items.filter(Boolean).map((t) => `• ${t}`).join('\n');
export const numbered = (items) =>
items.filter(Boolean).map((t, i) => `${i + 1}. ${t}`).join('\n\n');
export const heading = (text) => `**${text}**`;
/** Joins answer blocks, dropping empties so templates can use conditionals. */
export const compose = (...blocks) => blocks.filter((b) => b !== null && b !== undefined && b !== '').join('\n');