Files
doormilxpress_astryx/src/lib/provingGround.js
2026-08-07 23:53:15 +05:30

78 lines
4.7 KiB
JavaScript

import { base44 } from '@/api/base44Client';
const CHALLENGE_EVAL_SCHEMA = {
type: 'object',
properties: {
verdict: { type: 'string', enum: ['verified', 'needs_work', 'failed'] },
score: { type: 'number', description: '0-100 weighted confidence score' },
rubric: { type: 'object', additionalProperties: { type: 'number' }, description: 'criterion -> 0-100' },
feedback: { type: 'string', description: '2-3 sentences, direct to the worker' },
strengths: { type: 'array', items: { type: 'string' } },
concerns: { type: 'array', items: { type: 'string' } }
}
};
/** Real-work unlock gate — shifts, reliability, and badges must be earned first. */
export function isUnlocked(course, profile = {}) {
const req = course?.unlock_requirements;
if (!req) return { unlocked: true, reasons: [] };
const reasons = [];
const shifts = Number(profile.shifts_completed) || 0;
const rel = Number(profile.reliability_score) || 0;
const badges = (profile.earned_badges || []).map((b) => b.name);
if (req.min_shifts && shifts < req.min_shifts) reasons.push(`${req.min_shifts} shifts completed (you have ${shifts})`);
if (req.min_reliability && rel < req.min_reliability) reasons.push(`Reliability ${req.min_reliability}+ (you have ${rel})`);
(req.required_badges || []).forEach((b) => { if (!badges.includes(b)) reasons.push(`Badge: ${b}`); });
return { unlocked: reasons.length === 0, reasons };
}
/** One sharp follow-up question during a roleplay challenge. */
export async function challengeFollowUp(course, history) {
const convo = history.map((m) => `${m.role === 'user' ? 'Worker' : 'KROW'}: ${m.content}`).join('\n');
const prompt = `You are "KROW", running a short proving-ground challenge for the skill "${course?.proof_skill || course?.title}".
Challenge scenario: ${course?.challenge?.prompt || course?.description}
You already posed the scenario. Now ask ONE sharp follow-up question that tests whether the worker can actually perform under pressure. Keep it under 25 words. Do not praise. Just ask.
Conversation so far:
${convo}
Ask your follow-up now. Respond with only the question.`;
const res = await base44.integrations.Core.InvokeLLM({ prompt, model: 'gemini_3_flash' });
return typeof res === 'string' ? res : res.text || String(res);
}
/** Evaluate a worker's proof — transcript for roleplay, attached media for photo/video,
* identified hazards for photo_identify (with the scene image attached). */
export async function evaluateChallenge(course, { type, mediaUrl, transcript, identified, workerName }) {
const ch = course?.challenge || {};
const skill = course?.proof_skill || course?.title;
const criteria = (ch.rubric || []).map((r) => r.criterion).join(', ') || 'overall_performance';
const rubricInstr = (ch.rubric || []).length
? `Score each criterion 0-100 in the "rubric" object: ${criteria}.`
: 'Score "overall_performance" 0-100 in the rubric object.';
let mediaPart;
const call = { response_json_schema: CHALLENGE_EVAL_SCHEMA, model: 'claude_sonnet_4_6' };
if (type === 'photo_identify') {
const list = (identified || []).map((h) => `- "${h.label}" at (${Math.round(h.x * 100)}%, ${Math.round(h.y * 100)}%)`).join('\n') || '(no hazards marked)';
mediaPart = `The worker was shown a kitchen photo and asked to identify cross-contamination and food-safety hazards. They marked these hazards:\n${list}\n\nAnalyze the attached kitchen photo and judge whether they identified the REAL risks (e.g. raw meat next to ready-to-eat food, same board for raw and cooked, soiled towels on food surfaces, food left in the temperature danger zone, unwashed hands). Reward correct, specific identifications; penalize misses and false positives.`;
if (mediaUrl) call.file_urls = [mediaUrl];
} else if (type === 'photo' || type === 'video') {
mediaPart = `The worker uploaded a ${type} demonstrating the challenge. Analyze the attached ${type} carefully and judge whether they actually performed the skill correctly.`;
if (mediaUrl) call.file_urls = [mediaUrl];
} else {
mediaPart = `Worker's responses (transcript):\n"""\n${transcript || '(no response)'}\n"""`;
}
call.prompt = `You are KROW's Proving Ground evaluator. A worker named ${workerName || 'the worker'} is proving the skill "${skill}" via a ${type} challenge.
Challenge: ${ch.prompt || course?.description || 'Demonstrate the skill.'}
${mediaPart}
${rubricInstr}
Compute a weighted score (0-100). verdict: "verified" if score>=70 and clearly competent, "needs_work" if 50-69, "failed" if <50.
Give feedback (2-3 sentences, direct and specific to the worker), strengths, and concerns.
Be rigorous — employers will trust this evidence. Do not inflate.`;
const res = await base44.integrations.Core.InvokeLLM(call);
return res;
}