ui changes

This commit is contained in:
2026-08-10 19:36:45 +05:30
parent 409dffe938
commit 5cd49a1655
49 changed files with 7988 additions and 470 deletions

View File

@@ -21,6 +21,13 @@ const avg = (values) =>
const DAY_MS = 1000 * 60 * 60 * 24;
/**
* Events an auditor looks at first: they change who is employed or what is being
* hired for. Kept here so the fact sheet, the Activity page's severity column and
* the assistant's security answers all agree on what "privileged" means.
*/
export const PRIVILEGED_EVENTS = ['hire_candidate', 'create_position'];
/** `today` is injected rather than read from the clock so answers are stable. */
export function buildFacts({
applications = [],
@@ -105,6 +112,68 @@ export function buildFacts({
return acc;
}, {});
/**
* Out-of-pattern activity.
*
* Detected here rather than in the assistant so the greeting, the answer and
* any future page section all count the same things. "Two unusual patterns"
* has to mean the same two everywhere it is said.
*
* Each signal is a deviation from this platform's own baseline, not a verdict:
* on a live deployment most of them resolve to an integration or a busy
* afternoon, and the copy that renders them says so.
*/
const activitySignals = (() => {
const privileged = activity.filter((e) => PRIVILEGED_EVENTS.includes(e.event_type));
const perUser = activity.reduce((acc, e) => {
acc[e.user_email] ||= { email: e.user_email, name: e.user_name, count: 0, privileged: 0 };
acc[e.user_email].count += 1;
if (PRIVILEGED_EVENTS.includes(e.event_type)) acc[e.user_email].privileged += 1;
return acc;
}, {});
const accounts = Object.values(perUser).sort((a, b) => b.count - a.count);
const busiest = accounts[0] || null;
const busiestShare = busiest ? pct(busiest.count, activity.length) : 0;
/* A burst is more than three actions from one account inside one hour. */
const perAccountHour = activity.reduce((acc, e) => {
const key = `${e.user_email}|${String(e.created_date).slice(0, 13)}`;
acc[key] = (acc[key] || 0) + 1;
return acc;
}, {});
const bursts = Object.values(perAccountHour).filter((n) => n > 3).length;
const offHours = activity.filter((e) => {
const hour = new Date(e.created_date).getHours();
return hour < 6 || hour >= 22;
});
const privilegedShare = pct(privileged.length, activity.length);
/* The flags, in the order they are worth reading. A count of these is what
the greeting reports, so anything added here changes that number. */
const flags = [
activity.length > 0 && busiestShare >= 50 && 'concentration',
bursts > 0 && 'burst',
offHours.length > 0 && 'off-hours',
activity.length > 0 && since(1).length === 0 && 'silent',
privilegedShare > 30 && 'privileged-share',
].filter(Boolean);
return {
accounts,
busiest,
busiestShare,
bursts,
offHours,
privileged,
privilegedShare,
flags,
};
})();
return {
today,
@@ -125,6 +194,22 @@ export function buildFacts({
missingCredentials: scored.filter((a) => !(a.certifications || []).length),
narrowAvailability: scored.filter((a) => (a.availability || []).length <= 1),
/**
* Candidates a human should look at more closely — either because there is
* no score to judge them on, or because the record has a gap the score does
* not reflect. Deliberately not "weak candidates": every one of these is
* under-assessed rather than assessed and found wanting.
*/
needsDeeperReview: applications.filter((a) => {
if (!(a.ai_score > 0)) return true;
const flaggedInterview = interviews.some(
(i) => i.application_id === a.id && (i.integrity_score ?? 100) < 100
);
return flaggedInterview
|| !(a.certifications || []).length
|| !a.years_experience;
}),
/* Positions */
postings,
openPositions,
@@ -161,6 +246,7 @@ export function buildFacts({
activity7d: since(7),
activity24h: since(1),
eventCounts,
activitySignals,
/* Signed-in worker (talent context only) */
profile,