update owliver skill

This commit is contained in:
2026-08-14 17:33:16 +05:30
parent cccada9bd2
commit 4fdfb90326
58 changed files with 8015 additions and 1339 deletions

View File

@@ -1,5 +1,5 @@
import {
Activity, AlertTriangle, ArrowLeftRight, Award, BellRing, Clock, Eye, Filter, Gauge,
Activity, AlertTriangle, ArrowLeftRight, Award, BellRing, Clock, Eye, FileText, Filter, Gauge,
GitBranch, Layers, Lightbulb, Lock, ScrollText, ShieldAlert, ShieldCheck, Star, Target,
TrendingUp, UserCheck, Users,
} from 'lucide-react';
@@ -962,6 +962,41 @@ const positionsNeedingAttention = (f) => {
);
};
/**
* Positions that were saved but never published.
*
* A draft is a real record with real values on it and no way for anyone to
* apply to it, which makes it the one thing on this page that is genuinely
* unfinished. Each one is offered with the control that finishes it: the same
* authoring form, told which draft it is continuing, with everything already
* entered still in place.
*
* Read from `postings` by status — no list of drafts is kept anywhere, and a
* position stops appearing here the moment it is published.
*/
const positionDrafts = (f) => {
const drafts = (f.postings || []).filter((p) => p.status === 'draft');
if (!drafts.length) {
return answer('Nothing is sitting in draft — every position in the workspace has been published or closed.');
}
return doc(
heading(`${plural(drafts.length, 'position')} still in draft`, 'Saved, but not published'),
insights(drafts.map((p) => ({
tone: 'warning',
title: p.title || 'Untitled position',
body: [
p.company,
p.location,
'Not published yet, so nobody can apply and no screening runs.',
].filter(Boolean).join(' · '),
action: { label: 'Continue to save', to: `/admin/positions/new?draft=${encodeURIComponent(p.id)}` },
}))),
note('Continuing reopens the same form with everything already entered — the vetting weights included. Publishing it there is what makes it an active position.')
);
};
/** Which role to fill first. */
const hiringPriority = (f) => {
const rows = f.byRole
@@ -1259,6 +1294,7 @@ export const respondControlCenter = (question, f) => {
export const respondPositions = (question, f) => {
const q = question.toLowerCase();
if (has(q, 'draft', 'unfinished', 'not published', 'continue')) return positionDrafts(f);
if (has(q, 'attention', 'urgent', 'problem', 'wrong', 'risk')) return positionsNeedingAttention(f);
if (has(q, 'first', 'priorit', 'which position', 'should i hire')) return hiringPriority(f);
if (has(q, 'bottleneck', 'funnel', 'drop', 'conversion')) return pipelineHealth(f);
@@ -1317,6 +1353,7 @@ export const CONTROL_CENTER_CAPABILITIES = [
];
export const POSITIONS_CAPABILITIES = [
{ id: 'position-drafts', label: 'Unfinished Drafts', icon: FileText, run: positionDrafts },
{ id: 'position-strength', label: 'Pipeline Strength', icon: TrendingUp, run: positionStrength },
{ id: 'positions-attention', label: 'Needs Attention', icon: BellRing, run: positionsNeedingAttention },
{ id: 'hiring-priority', label: 'What To Fill First', icon: Target, run: hiringPriority },
@@ -1935,3 +1972,164 @@ export const PROFILE_CAPABILITIES = [
{ id: 'profile-activity', label: 'My Recent Activity', icon: ScrollText, run: profileActivity },
{ id: 'profile-actions', label: 'What I Can Do Here', icon: Lightbulb, run: profileActions },
];
/* ── Create Position ─────────────────────────────────────────────────────
The authoring surface. Every answer here is about the role being specified,
read against what this workspace has already posted — so the guidance is the
deployment's own history rather than an industry number written into a file. */
/** The average of a numeric field across postings that state it. */
const across = (postings, read) => {
const values = postings.map(read).filter((n) => Number.isFinite(n) && n > 0);
return values.length ? Math.round(values.reduce((a, b) => a + b, 0) / values.length) : 0;
};
/**
* What the five vetting criteria mean, and how this workspace usually weights
* them.
*
* The typical column is counted from the positions already posted, so it says
* what this operator does rather than what a template suggests.
*/
const vettingWeights = (f) => {
const posted = f.postings.filter((p) => p.vetting_criteria);
const keys = ['experience', 'english', 'reliability', 'certifications', 'availability'];
const meaning = {
experience: 'Years in the role, weighted against the minimum this position asks for',
english: 'Spoken level, checked in the AI interview',
reliability: 'Attendance and completion history on previous assignments',
certifications: 'Whether the credentials this role requires are held and current',
availability: 'How much of the shift pattern the candidate can actually cover',
};
if (!posted.length) {
return doc(
heading('AI vetting weights', 'What each criterion scores'),
list(keys.map((k) => `**${CRITERIA_LABELS[k]}** — ${meaning[k]}`)),
note('The five must total 100%. No position has been posted yet, so there is no house pattern to compare against.')
);
}
const typical = keys.map((key) => ({
criterion: CRITERIA_LABELS[key],
typical: `${across(posted, (p) => Number(p.vetting_criteria?.[key]))}%`,
scores: meaning[key],
}));
return doc(
heading('AI vetting weights', `Typical across ${plural(posted.length, 'posted position')}`),
table(
[
{ key: 'criterion', label: 'Criterion' },
{ key: 'typical', label: 'Usually', align: 'right' },
{ key: 'scores', label: 'What it scores' },
],
typical
),
note('These are this workspace’s own averages, not a recommendation. The five weights on this form must total 100%.')
);
};
/**
* What comparable roles in this workspace ask for.
*
* Grouped by role category, which is the only comparison the data supports —
* pay and experience are not transferable between a chef and a security post.
*/
const positionBenchmarks = (f) => {
const posted = f.postings.filter((p) => p.role_category);
if (!posted.length) {
return doc(
text('No positions have been posted yet, so there is nothing to benchmark this one against.'),
note('Once a few roles exist I can compare pay, experience and requirements by category.')
);
}
const byCategory = new Map();
posted.forEach((p) => {
const entry = byCategory.get(p.role_category) || { category: p.role_category, list: [] };
entry.list.push(p);
byCategory.set(p.role_category, entry);
});
const rows = [...byCategory.values()]
.sort((a, b) => b.list.length - a.list.length)
.slice(0, 6)
.map(({ category, list: roles }) => ({
category,
posted: roles.length,
pay: across(roles, (p) => Number(p.pay_range_min))
? `$${across(roles, (p) => Number(p.pay_range_min))}–${across(roles, (p) => Number(p.pay_range_max))}/hr`
: '—',
experience: `${across(roles, (p) => Number(p.min_experience_years))} yrs`,
}));
return doc(
heading('What comparable roles ask for', `${plural(posted.length, 'position')} on file`),
table(
[
{ key: 'category', label: 'Category' },
{ key: 'posted', label: 'Posted', align: 'right' },
{ key: 'pay', label: 'Pay', align: 'right' },
{ key: 'experience', label: 'Min experience', align: 'right' },
],
rows
),
note('Averages across positions already posted in this workspace.')
);
};
/** The credentials this workspace actually asks for, ranked by how often. */
const positionRequirements = (f) => {
const counts = new Map();
f.postings.forEach((p) => {
(p.certifications_required || []).forEach((c) => counts.set(c, (counts.get(c) || 0) + 1));
});
if (!counts.size) {
return doc(
text('No position on file requires a certification, so there is no pattern to follow here.'),
note('Add only the credentials this role genuinely needs — every one narrows the pool.')
);
}
const ranked = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8);
return doc(
heading('Credentials this workspace asks for', `${plural(counts.size, 'certification')} in use`),
meters(ranked.map(([label, count]) => ({
label,
value: count,
max: f.postings.length || count,
}))),
note('Ranked by how many positions require them. Each requirement narrows the pool it is added to.')
);
};
/** How the form's own steps fit together, for someone new to it. */
const positionSpecSteps = () => doc(
heading('Specifying a position'),
list([
'Role category and job title — what the matching engine files this under',
'Client, requirements and pay — what the posting states',
'Certifications and required skills — what a person must hold',
'Generate Job Description with AI — writes the prose from the fields above',
'AI vetting weights — how the screening score is composed, totalling 100%',
], { ordered: true }),
note('Nothing is posted until you choose Save as Draft or Publish.')
);
export const respondCreatePosition = (question, f) => {
const q = question.toLowerCase();
if (has(q, 'weight', 'vetting', 'criteria', 'scoring', 'score')) return vettingWeights(f);
if (has(q, 'certification', 'credential', 'requirement', 'skill')) return positionRequirements(f);
if (has(q, 'pay', 'rate', 'salary', 'benchmark', 'compare', 'experience', 'typical')) return positionBenchmarks(f);
if (has(q, 'how do i', 'step', 'what do i', 'fill', 'form', 'explain')) return positionSpecSteps(f);
return positionSpecSteps(f);
};
export const CREATE_POSITION_CAPABILITIES = [
{ id: 'vetting-weights', label: 'Explain Vetting Weights', icon: Gauge, run: vettingWeights },
{ id: 'position-benchmarks', label: 'Compare With Similar Roles', icon: ArrowLeftRight, run: positionBenchmarks },
{ id: 'position-requirements', label: 'Credentials In Use', icon: ShieldCheck, run: positionRequirements },
{ id: 'position-spec-steps', label: 'How This Form Works', icon: Lightbulb, run: positionSpecSteps },
];