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; }