Initial commit
This commit is contained in:
277
src/components/ai-assistant/capabilities/admin.js
Normal file
277
src/components/ai-assistant/capabilities/admin.js
Normal file
@@ -0,0 +1,277 @@
|
||||
import { AlertTriangle, Eye, Gauge, Lightbulb, ScrollText, TrendingUp } from 'lucide-react';
|
||||
import { bullets, compose, heading, numbered, plural, verb } from '../insights';
|
||||
|
||||
/**
|
||||
* Admin capabilities — Control Center, Candidates Analysis, Activity.
|
||||
*
|
||||
* Admin answers are platform-wide rather than pipeline-specific: data integrity,
|
||||
* talent supply, audit reconciliation.
|
||||
*/
|
||||
|
||||
/* ── Control Center ─────────────────────────────────────────────────────── */
|
||||
|
||||
const platformHealth = (f) => {
|
||||
const checks = [
|
||||
{
|
||||
label: 'Data integrity',
|
||||
ok: f.applications.every((a) => a.job_posting_id),
|
||||
good: 'Every application is linked to a live posting.',
|
||||
bad: 'Some applications reference a posting that no longer exists.',
|
||||
},
|
||||
{
|
||||
label: 'Screening coverage',
|
||||
ok: f.standardizedPct >= 80,
|
||||
good: `${f.standardizedPct}% of applicants carry a score.`,
|
||||
bad: `Only ${f.standardizedPct}% of applicants are scored — ${plural(f.unscreened.length, 'record')} unstandardized.`,
|
||||
},
|
||||
{
|
||||
label: 'Interview integrity',
|
||||
ok: f.flagged.length === 0,
|
||||
good: 'No interviews flagged.',
|
||||
bad: `${plural(f.flagged.length, 'interview')} flagged below full integrity.`,
|
||||
},
|
||||
{
|
||||
label: 'Profile completeness',
|
||||
ok: f.unscoredProfiles.length <= f.profiles.length / 2,
|
||||
good: `${f.profiles.length - f.unscoredProfiles.length} of ${f.profiles.length} talent profiles have a career score.`,
|
||||
bad: `${plural(f.unscoredProfiles.length, 'profile')} of ${f.profiles.length} ${verb(f.unscoredProfiles.length, 'has', 'have')} no score, so ${verb(f.unscoredProfiles.length, 'it cannot', 'they cannot')} be matched or ranked.`,
|
||||
},
|
||||
{
|
||||
label: 'Throughput',
|
||||
ok: f.hired.length > 0,
|
||||
good: `${plural(f.hired.length, 'hire')} closed at a ${f.hireRate}% hire rate.`,
|
||||
bad: 'No hires closed yet, so there is no conversion baseline.',
|
||||
},
|
||||
];
|
||||
|
||||
const passing = checks.filter((c) => c.ok).length;
|
||||
|
||||
return compose(
|
||||
heading(`Platform health: ${passing}/${checks.length} checks passing`),
|
||||
'',
|
||||
checks.map((c) => `${c.ok ? '✓' : '⚠'} **${c.label}** — ${c.ok ? c.good : c.bad}`).join('\n'),
|
||||
'',
|
||||
heading('Scale'),
|
||||
bullets([
|
||||
`${plural(f.postings.length, 'position')}, ${f.openPositions.length} open`,
|
||||
`${plural(f.total, 'application')}`,
|
||||
`${plural(f.interviews.length, 'AI interview')}`,
|
||||
`${plural(f.profiles.length, 'talent profile')}, ${f.elite.length} Elite`,
|
||||
`${plural(f.activity.length, 'logged event')}`,
|
||||
]),
|
||||
'',
|
||||
passing === checks.length
|
||||
? 'Nothing needs intervention at the platform level.'
|
||||
: 'The failing checks are data-quality issues, not outages — they degrade matching quality rather than break anything.'
|
||||
);
|
||||
};
|
||||
|
||||
const recommendations = (f) => {
|
||||
const actions = [];
|
||||
|
||||
if (f.unscoredProfiles.length) {
|
||||
actions.push(`**Prompt ${plural(f.unscoredProfiles.length, 'worker')} to finish onboarding.** Without a career score they are invisible to matching, so employers cannot find them even when they fit.`);
|
||||
}
|
||||
if (f.unscreened.length) {
|
||||
actions.push(`**${plural(f.unscreened.length, 'application')} unscored.** Each one is a decision made without a standard.`);
|
||||
}
|
||||
if (f.starvedPositions.length) {
|
||||
actions.push(`**${plural(f.starvedPositions.length, 'posting')} with zero applicants** — ${f.starvedPositions.map((p) => p.title).join(', ')}. Check distribution and pay bands.`);
|
||||
}
|
||||
if (f.unratedStaff.length) {
|
||||
actions.push(`**${plural(f.unratedStaff.length, 'hire')} unrated.** Employer ratings are the only verified performance signal feeding scores; without them the scores stay theoretical.`);
|
||||
}
|
||||
if (f.flagged.length) {
|
||||
actions.push(`**Review ${plural(f.flagged.length, 'flagged interview')}** before those candidates progress.`);
|
||||
}
|
||||
|
||||
if (!actions.length) return 'No platform actions outstanding. Data is complete and nothing is flagged.';
|
||||
|
||||
return compose(heading('Recommended actions'), '', numbered(actions));
|
||||
};
|
||||
|
||||
/* ── Candidates Analysis ────────────────────────────────────────────────── */
|
||||
|
||||
const recruitmentInsights = (f) => {
|
||||
const bands = {
|
||||
Elite: f.profiles.filter((p) => (p.krow_score || 0) >= 90).length,
|
||||
Excellent: f.profiles.filter((p) => (p.krow_score || 0) >= 75 && (p.krow_score || 0) < 90).length,
|
||||
Solid: f.profiles.filter((p) => (p.krow_score || 0) >= 60 && (p.krow_score || 0) < 75).length,
|
||||
Building: f.profiles.filter((p) => (p.krow_score || 0) > 0 && (p.krow_score || 0) < 60).length,
|
||||
Unscored: f.unscoredProfiles.length,
|
||||
};
|
||||
|
||||
const verified = f.profiles.filter((p) => (p.capabilities || []).length).length;
|
||||
const topHeavy = bands.Elite + bands.Excellent;
|
||||
|
||||
return compose(
|
||||
heading('Talent supply'),
|
||||
'',
|
||||
bullets(Object.entries(bands).map(([band, count]) => `${band} — ${count}`)),
|
||||
'',
|
||||
heading('Pipeline'),
|
||||
bullets([
|
||||
`${plural(f.total, 'applicant')} averaging ${f.avgScore}`,
|
||||
`${plural(f.ranked.filter((a) => a.ai_score >= 80).length, 'candidate')} at 80+`,
|
||||
`${plural(verified, 'worker')} with at least one verified capability`,
|
||||
`${plural(f.eventCounts.apply_job || 0, 'application event')} logged`,
|
||||
]),
|
||||
'',
|
||||
bands.Unscored > topHeavy
|
||||
? `The pool is bottom-heavy: ${bands.Unscored} unscored against ${topHeavy} at Excellent or above. Supply is not the constraint — verification is.`
|
||||
: 'The scored portion outweighs the unscored, so verification is keeping pace with sign-ups.',
|
||||
'',
|
||||
heading('By role category'),
|
||||
bullets(
|
||||
f.byRole.slice().sort((a, b) => b.applied - a.applied)
|
||||
.map((r) => `${r.title} — ${plural(r.applied, 'applicant')}, ${r.qualified} at 70+`)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const candidateRiskAnalysis = (f) => {
|
||||
const risks = [];
|
||||
|
||||
if (f.missingCredentials.length) {
|
||||
risks.push(`**No credentials on file** — ${plural(f.missingCredentials.length, 'candidate')}. Compliance risk if placed on a role that requires certification.`);
|
||||
}
|
||||
if (f.narrowAvailability.length) {
|
||||
risks.push(`**Narrow availability** — ${plural(f.narrowAvailability.length, 'candidate')} listed one slot or none. Most likely to fall through on the day.`);
|
||||
}
|
||||
if (f.flagged.length) {
|
||||
risks.push(`**Interview integrity** — ${plural(f.flagged.length, 'interview')} below full integrity, usually suspiciously fast responses. Worth a human re-interview.`);
|
||||
}
|
||||
const noHistory = f.scored.filter((a) => !a.years_experience);
|
||||
if (noHistory.length) {
|
||||
risks.push(`**No recorded experience** — ${plural(noHistory.length, 'candidate')}. Not disqualifying, but unverifiable on paper.`);
|
||||
}
|
||||
if (f.singleCandidateRoles.length) {
|
||||
risks.push(`**Concentration risk** — ${f.singleCandidateRoles.map((e) => e.posting.title).join(', ')} ${verb(f.singleCandidateRoles.length, 'depends', 'depend')} on a single viable candidate.`);
|
||||
}
|
||||
|
||||
if (!risks.length) return 'No material risk flags: credentials, availability and interview integrity all check out across the scored pool.';
|
||||
|
||||
return compose(
|
||||
heading(`${plural(risks.length, 'risk flag')}`),
|
||||
'',
|
||||
numbered(risks),
|
||||
'',
|
||||
'_Flags to check, not judgements. Each is a question for a human, not a rejection._'
|
||||
);
|
||||
};
|
||||
|
||||
/* ── Activity ───────────────────────────────────────────────────────────── */
|
||||
|
||||
const auditSummary = (f) => {
|
||||
const count = (type) => f.activity.filter((e) => e.event_type === type).length;
|
||||
const hires = count('hire_candidate');
|
||||
const screens = count('screen_candidate');
|
||||
const creates = count('create_position');
|
||||
|
||||
return compose(
|
||||
heading('Audit summary'),
|
||||
'',
|
||||
bullets([
|
||||
`${plural(f.activity.length, 'event')} total`,
|
||||
`${plural(f.activity7d.length, 'event')} in the last 7 days`,
|
||||
`${plural(f.activity24h.length, 'event')} in the last 24 hours`,
|
||||
`${plural(new Set(f.activity.map((e) => e.user_email)).size, 'distinct user')}`,
|
||||
]),
|
||||
'',
|
||||
heading('Privileged actions'),
|
||||
bullets([
|
||||
`${plural(creates, 'position creation')}`,
|
||||
`${plural(screens, 'screening action')}`,
|
||||
`${plural(hires, 'hire')}`,
|
||||
]),
|
||||
'',
|
||||
heading('Reconciliation'),
|
||||
bullets([
|
||||
hires === f.staff.length
|
||||
? `All ${f.staff.length} people on staff have a matching hire event.`
|
||||
: `${f.staff.length} on staff but ${hires} hire ${verb(hires, 'event', 'events')} — ${Math.abs(f.staff.length - hires)} unexplained.`,
|
||||
screens < f.scored.length
|
||||
? `${f.scored.length} candidates carry scores but only ${plural(screens, 'screening event')} logged. Batch screening records one event per run, not per candidate.`
|
||||
: 'Screening events reconcile with scored candidates.',
|
||||
]),
|
||||
'',
|
||||
'_An audit trail is only as good as its gaps. The reconciliation above is what I would check first in a real review._'
|
||||
);
|
||||
};
|
||||
|
||||
const userActivityInsights = (f) => {
|
||||
const perUser = f.activity.reduce((acc, e) => {
|
||||
acc[e.user_email] ||= { name: e.user_name, role: e.account_type, count: 0, types: new Set() };
|
||||
acc[e.user_email].count += 1;
|
||||
acc[e.user_email].types.add(e.event_type);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const ranked = Object.entries(perUser).sort((a, b) => b[1].count - a[1].count);
|
||||
if (!ranked.length) return 'No activity to profile yet.';
|
||||
|
||||
const byRole = f.activity.reduce((acc, e) => {
|
||||
const role = e.account_type || 'unknown';
|
||||
acc[role] = (acc[role] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const [topEmail, top] = ranked[0];
|
||||
|
||||
return compose(
|
||||
heading('Most active accounts'),
|
||||
'',
|
||||
bullets(ranked.slice(0, 6).map(([email, d]) =>
|
||||
`**${d.name || email}** (${d.role || 'unknown'}) — ${plural(d.count, 'event')} across ${plural(d.types.size, 'action type')}`
|
||||
)),
|
||||
'',
|
||||
heading('By role'),
|
||||
bullets(Object.entries(byRole).sort((a, b) => b[1] - a[1])
|
||||
.map(([role, c]) => `${role.charAt(0).toUpperCase() + role.slice(1)} — ${plural(c, 'event')}`)),
|
||||
'',
|
||||
`${top.name || topEmail} accounts for ${top.count} of ${f.activity.length} events. On a live platform, one account dominating the log is worth confirming is a person and not an integration.`,
|
||||
'',
|
||||
f.activity24h.length === 0
|
||||
? 'No activity in the last day. Expected for a demo dataset; on a live platform it would be worth checking ingestion.'
|
||||
: 'Activity is flowing normally.'
|
||||
);
|
||||
};
|
||||
|
||||
/* ── Free-text responders ───────────────────────────────────────────────── */
|
||||
|
||||
const has = (q, ...words) => words.some((w) => q.includes(w));
|
||||
|
||||
export const respondControlCenter = (question, f) => {
|
||||
const q = question.toLowerCase();
|
||||
if (has(q, 'recommend', 'should', 'action', 'fix', 'improve')) return recommendations(f);
|
||||
return platformHealth(f);
|
||||
};
|
||||
|
||||
export const respondAdminCandidates = (question, f) => {
|
||||
const q = question.toLowerCase();
|
||||
if (has(q, 'risk', 'flag', 'concern', 'compliance')) return candidateRiskAnalysis(f);
|
||||
return recruitmentInsights(f);
|
||||
};
|
||||
|
||||
export const respondActivity = (question, f) => {
|
||||
const q = question.toLowerCase();
|
||||
if (has(q, 'user', 'who', 'active', 'behaviour', 'behavior', 'account')) return userActivityInsights(f);
|
||||
return auditSummary(f);
|
||||
};
|
||||
|
||||
/* ── Capability manifests ───────────────────────────────────────────────── */
|
||||
|
||||
export const CONTROL_CENTER_CAPABILITIES = [
|
||||
{ id: 'platform-health', label: 'Platform Health', icon: Gauge, run: platformHealth },
|
||||
{ id: 'recommendations', label: 'Recommendations', icon: Lightbulb, run: recommendations },
|
||||
];
|
||||
|
||||
export const ADMIN_CANDIDATE_CAPABILITIES = [
|
||||
{ id: 'recruitment-insights', label: 'Recruitment Insights', icon: TrendingUp, run: recruitmentInsights },
|
||||
{ id: 'candidate-risk', label: 'Candidate Risk Analysis', icon: AlertTriangle, run: candidateRiskAnalysis },
|
||||
];
|
||||
|
||||
export const ACTIVITY_CAPABILITIES = [
|
||||
{ id: 'audit-summary', label: 'Audit Summary', icon: ScrollText, run: auditSummary },
|
||||
{ id: 'user-activity', label: 'User Activity Insights', icon: Eye, run: userActivityInsights },
|
||||
];
|
||||
Reference in New Issue
Block a user