84 lines
6.2 KiB
TypeScript
84 lines
6.2 KiB
TypeScript
// Server-side "AI draft" for breadcrumb fields. The Anthropic API key lives here
|
|
// (from an env var) and never reaches the browser.
|
|
|
|
import Anthropic from '@anthropic-ai/sdk';
|
|
import { env } from '$env/dynamic/private';
|
|
|
|
export const REQUIRED_FIELDS = ['driver', 'cap', 'metric', 'method', 'baseline'] as const;
|
|
export const ADOPTION_WORDS =
|
|
/implement|adopt|roll ?out|have a tool|tool exists|usage|active users?|users? active/i;
|
|
|
|
export interface DraftResult {
|
|
domain: string;
|
|
driver: string;
|
|
cap: string;
|
|
metric: string;
|
|
method: string;
|
|
baseline: string;
|
|
}
|
|
|
|
const METHOD_KB = `Domain playbook (anchor methodology -> standard metrics/methods/baselines):
|
|
1 PROGRESS VISIBILITY - Earned Value Mgmt, rules of credit. Metrics: claiming accuracy (reported % vs field-verified % within tolerance, by phase; method: monthly spot audit of 30+ sampled assets; baseline: first audit); forecast reliability (forecast vs actual completion; baseline: last project's reports). Never use "% of assets with a status" (adoption in disguise).
|
|
2 DATA FRESHNESS / ENTRY BURDEN - lean data flow, single source of truth. Metrics: entry latency (% of updates within 24h of completion; method: system timestamp vs completion date, weekly; baseline: audit paper-to-digital lag on current project); touch count (systems the same record is entered into; method: trace 10 records end to end; baseline: pre-pilot trace); non-productive data minutes/person/day (method+baseline: structured field poll pre-pilot, repeat midpoint).
|
|
3 CONSTRAINT READINESS / WORK RELEASE - CII AWP IR 272-2, constraint-free IWP release (drawings, material, tools, labor, access, predecessors, permits, quality docs, scaffolding). Metrics: % releases issued constraint-free (method: release-gate checklist audit weekly, later constraint stop = miss; baseline: audit last month of work starts from daily logs); stand-down count/week (method: foreman log with fixed reason list; baseline: 2-week logging exercise); tool time % (method: activity sampling study; baseline: pre-pilot study; propose only when stakes justify cost).
|
|
4 PLANNING / RESOURCES / IDLE - Last Planner System. Metrics: PPC (% weekly commitments completed as promised; method: log at weekly planning, score next week, publish trend; baseline: first 4 weeks of logging); reasons-for-variance distribution (fixed-list coding of misses); idle hours/crew-week (timecard or foreman reason codes; baseline: 2-week coding).
|
|
5 SCOPE CONTROL / CHANGE - PMBOK change control, CII change research. Metrics: unauthorized-work hours/dollars at time of performance (method: monthly reconciliation of field records vs CO log; baseline: run reconciliation on current project); change capture latency days (field record date vs log date); change cycle time (log timestamps; baseline: last project's log).
|
|
6 QUALITY / REWORK - first-time quality, CII rework research (2-6% direct cost where measured). Metrics: rework % of craft hours (method: dedicated rework cost code + reason coding, monthly; baseline: create the code, first month is baseline); first-pass yield at QC gates; out-of-rev incidents (QC walkdown coding).
|
|
7 KNOWLEDGE / FEEDBACK - CII lessons-learned, estimate feedback. Metrics: closeout compliance % within 60 days (method: closeout checklist audit; baseline: audit last 4-6 projects); % estimates referencing prior actuals (estimating file audit); punch closure median days (issue log timestamps; if untimestamped, that gap is the finding).
|
|
GATES: driver = business end in cost/schedule/quality/safety/predictability terms, never a tool. capability = function not product, no vendor names. metric = outcome ratio/rate, NEVER "implemented/adopted/rolled out/active users/have a tool". method = data source + comparison + cadence. baseline = a number or the named pre-pilot study that produces one (cheapest defensible: poll < record trace < log audit < 2-week coding < sampling study). Set targets after baseline, not before. Each field under ~30 words except method/baseline.`;
|
|
|
|
function parseJson(text: string): Record<string, unknown> {
|
|
const clean = String(text).replace(/```json|```/g, '').trim();
|
|
const candidates = ['[', '{'].map((ch) => {
|
|
const i = clean.indexOf(ch);
|
|
return i === -1 ? Infinity : i;
|
|
});
|
|
const start = Math.min(...candidates);
|
|
return JSON.parse(start === Infinity ? clean : clean.slice(start));
|
|
}
|
|
|
|
/** True when the AI feature is configured. Used to disable the button gracefully. */
|
|
export function aiConfigured(): boolean {
|
|
return Boolean(env.ANTHROPIC_API_KEY);
|
|
}
|
|
|
|
export async function draftBreadcrumb(prob: string, related: string[]): Promise<DraftResult> {
|
|
const apiKey = env.ANTHROPIC_API_KEY;
|
|
if (!apiKey) throw new Error('AI is not configured on this server (missing ANTHROPIC_API_KEY).');
|
|
if (!prob.trim()) throw new Error('Write the problem statement first.');
|
|
|
|
const client = new Anthropic({ apiKey });
|
|
const rel = related.length
|
|
? `\nRelated issues in this cluster:\n${related.map((r) => '- ' + r).join('\n')}`
|
|
: '';
|
|
const prompt = `You populate breadcrumb trails for construction field problems at an industrial I&C contractor, using best-in-class execution methodology.\n\n${METHOD_KB}\n\nProblem statement:\n${prob}${rel}\n\nClassify the problem into one domain above, then draft the five fields per that domain's standard metrics, methods, and baselines, obeying every GATE. Respond ONLY with a JSON object, no prose, no markdown fences:\n{"domain":"...","driver":"...","cap":"...","metric":"...","method":"...","baseline":"..."}`;
|
|
|
|
const message = await client.messages.create({
|
|
model: env.ANTHROPIC_MODEL || 'claude-sonnet-5',
|
|
max_tokens: 1000,
|
|
messages: [{ role: 'user', content: prompt }]
|
|
});
|
|
|
|
const text = message.content
|
|
.filter((block): block is Anthropic.TextBlock => block.type === 'text')
|
|
.map((block) => block.text)
|
|
.join('\n');
|
|
|
|
const obj = parseJson(text);
|
|
for (const f of REQUIRED_FIELDS) {
|
|
if (!obj[f]) throw new Error('AI returned an incomplete draft; try again.');
|
|
}
|
|
if (ADOPTION_WORDS.test(String(obj.metric))) {
|
|
throw new Error('Draft failed the adoption gate; try again.');
|
|
}
|
|
|
|
return {
|
|
domain: String(obj.domain ?? 'methodology'),
|
|
driver: String(obj.driver),
|
|
cap: String(obj.cap),
|
|
metric: String(obj.metric),
|
|
method: String(obj.method),
|
|
baseline: String(obj.baseline)
|
|
};
|
|
}
|