Files
krow_talent_app/src/lib/krowAi.ts
Aravind 2b8f5746bd chore(ts-migration): migrate domain logic to TypeScript
Phase 5. Twelve modules under `src/lib` and `src/lib/admin`. All twelve emit
byte-identical JavaScript; eight needed no annotation at all.

Where the generated entity types fit, they are used. `positionModel` is typed
against `JobPosting` — and that is where TypeScript earned its keep. Annotating
the label functions made `experienceLabel`'s `years === ''` guard a comparison
the compiler called impossible, because the registry types
`min_experience_years` as `number`, which is correct for a record the API has
returned. The guard is not dead: the same functions are handed drafts, and an
untouched numeric form input yields `''` — which is why `toPositionPayload`
coerces all five numerics with `Number(...)`.

So the module now has two types rather than one. `PositionRecord` is a saved
posting with the registry's column types; `PositionDraft` widens the five
numerics to `number | string` and is taken by `toPositionPayload` alone. The
one comparison the split cannot express keeps its guard and carries a cast with
the reason written next to it. Deleting a live guard to satisfy a type would be
the type rewriting the code.

`workforce` keeps its records as `any`: 574 lines of demand and availability
arithmetic over profiles, postings, assignments and staff read largely through
jsonb columns the registry does not describe. What IS described is the module's
own contract — the `WorkforceContext` option bag and the `Availability` result,
whose two shapes differ by whether a worker's commitments are known.

Two of my own type declarations were too narrow and were caught by the
set-difference rather than by inspection. `activitySignals`' accumulator seeds
`{ email, name, count, privileged }` and I had named only the two counters;
`WorkforceContext` omitted `profiles` and `courses`, which `PositionDetail`
passes in a single call with three more. The bag now carries an index signature,
because that is what the call site assumes: callers hand the whole thing over
and each function picks what it needs.

`skillGraph` gains a `SkillLevel` interface with an optional `earned`, set in a
second pass that stops at the first incomplete rung — so the levels above the
gap never receive it, and optional is the honest description.

Verified: tsc 40 -> 37, zero introduced; all twelve emitted outputs
byte-identical; npm test 1684/1691 with the same seven failures; lint 0 errors;
build succeeds with the API origin inlined; baseline artifacts untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
2026-09-17 23:46:26 +05:30

463 lines
31 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { base44 } from '@/api/base44Client';
/**
* KROW AI Workflows — all powered by InvokeLLM.
* 1. AI Job Description Generator
* 2. AI Resume Builder
* 3. AI Candidate Screening
* 4. Live AI Voice Interview evaluation
*/
const SCREENING_SCHEMA = {
type: 'object',
properties: {
overall_score: { type: 'number', description: '0-100 weighted score' },
match_label: { type: 'string', description: 'Excellent Match | Good Match | Possible Fit | Not a Fit' },
summary: { type: 'string', description: '2-3 sentence summary of the candidate fit' },
strengths: { type: 'array', items: { type: 'string' } },
gaps: { type: 'array', items: { type: 'string' } },
recommendation: { type: 'string', description: 'Shortlisted | Interview | Maybe | Reject' },
score_breakdown: {
type: 'object',
properties: {
experience: { type: 'number' },
english: { type: 'number' },
reliability: { type: 'number' },
certifications: { type: 'number' },
availability: { type: 'number' },
personality: { type: 'number', description: 'Inferred personality traits (warmth, conscientiousness, adaptability) from profile data' },
culture_fit: { type: 'number', description: 'Alignment with employer culture and team dynamics' },
communication_style: { type: 'number', description: 'Clarity, tone, and professionalism of written communication' },
attendance_expectations: { type: 'number', description: 'Likelihood of meeting attendance and punctuality expectations' },
physical_requirements: { type: 'number', description: 'Ability to meet physical demands of the role (lifting, standing, stamina)' },
leadership_expectations: { type: 'number', description: 'Leadership potential and team guidance capability' },
job_related_answers: { type: 'number', description: 'How well the candidate demonstrates job-specific knowledge from their profile' },
verified_skills: { type: 'number', description: 'How well claimed skills match the job requirements' },
scenario_judgment: { type: 'number', description: 'Inferred problem-solving and decision-making ability' },
employer_requirements: { type: 'number', description: 'Match against employer-defined custom requirements' }
}
}
}
};
const JOB_DESC_SCHEMA = {
type: 'object',
properties: {
description: { type: 'string' },
responsibilities: { type: 'array', items: { type: 'string' } },
qualifications: { type: 'array', items: { type: 'string' } },
nice_to_haves: { type: 'array', items: { type: 'string' } }
}
};
const RESUME_SCHEMA = {
type: 'object',
properties: {
inferred_name: { type: 'string' },
years_experience: { type: 'number' },
skills: { type: 'array', items: { type: 'string' } },
certifications: { type: 'array', items: { type: 'string' } },
professional_summary: { type: 'string' },
cover_letter: { type: 'string' }
}
};
const INTERVIEW_EVAL_SCHEMA = {
type: 'object',
properties: {
overall_interview_score: { type: 'number' },
verdict: { type: 'string', description: 'hire | maybe | no' },
hire_recommendation: { type: 'string' },
integrity_score: { type: 'number' },
ai_flags: { type: 'array', items: { type: 'string' } },
category_scores: {
type: 'object',
properties: {
communication: { type: 'number' },
confidence: { type: 'number' },
experience_relevance: { type: 'number' },
culture_fit: { type: 'number' },
problem_solving: { type: 'number' },
personality: { type: 'number', description: 'Inferred personality traits from interview responses (warmth, conscientiousness, adaptability)' },
communication_style: { type: 'number', description: 'Clarity, tone, and professionalism of verbal communication' },
attendance_expectations: { type: 'number', description: 'Likelihood of meeting attendance and punctuality expectations based on interview signals' },
reliability: { type: 'number', description: 'Dependability and consistency signals from interview responses' },
physical_requirements: { type: 'number', description: 'Stated ability to meet physical demands of the role' },
leadership_expectations: { type: 'number', description: 'Leadership potential and team guidance capability' },
scenario_judgment: { type: 'number', description: 'How well the candidate handles real-world job scenarios' },
job_related_answers: { type: 'number', description: 'Accuracy and depth of job-specific answers' },
verified_skills: { type: 'number', description: 'Demonstrated proficiency in claimed skills' }
}
},
strengths: { type: 'array', items: { type: 'string' } },
concerns: { type: 'array', items: { type: 'string' } },
best_fit_roles: { type: 'array', items: { type: 'string' } },
summary: { type: 'string' },
reasoning: { type: 'string' }
}
};
/** 1. AI Job Description Generator */
export async function generateJobDescription(data) {
const prompt = `You are an expert hiring copywriter for the staffing/hospitality industry.
Generate a compelling, professional job posting for the following role.
Role Title: ${data.title}
Role Category: ${data.role_category}
Min Experience: ${data.min_experience_years} years
English Level Required: ${data.english_required}
Required Certifications: ${(data.certifications_required || []).join(', ') || 'None'}
Pay Range: $${data.pay_range_min}–$${data.pay_range_max}/hr
Location: ${data.location || 'Not specified'}
Custom Requirements: ${data.custom_requirements || 'None'}
Return a job description (2-3 engaging paragraphs), 5-7 responsibilities, 4-6 qualifications, and 3-4 nice-to-haves. Make it feel premium and human.`;
const res = await base44.integrations.Core.InvokeLLM({
prompt,
response_json_schema: JOB_DESC_SCHEMA,
model: 'claude_sonnet_4_6'
});
return res;
}
/** 2. AI Resume Builder — infers structured resume from free text */
export async function buildResumeFromText(freeText) {
const prompt = `You are an expert recruiter. A candidate described their experience in free text below.
Extract and infer a structured resume. Infer name, years of experience, relevant skills, certifications, and write a professional summary and a short cover letter.
Candidate's description:
"""
${freeText}
"""
Return a structured resume. If a field cannot be inferred, use an empty string or 0.`;
const res = await base44.integrations.Core.InvokeLLM({
prompt,
response_json_schema: RESUME_SCHEMA,
model: 'claude_sonnet_4_6'
});
return res;
}
/** 3. AI Candidate Screening — scores applicant vs job requirements */
export async function screenCandidate(application, job) {
const weights = job.vetting_criteria || { experience: 25, english: 20, reliability: 20, certifications: 20, availability: 15 };
const prompt = `You are KROW's AI screening engine. Evaluate this candidate against the job requirements.
Score each dimension 0-100, then compute a weighted overall score using these weights: experience ${weights.experience}%, english ${weights.english}%, reliability ${weights.reliability}%, certifications ${weights.certifications}%, availability ${weights.availability}%.
Additionally, score these supplementary dimensions 0-100: personality (inferred personality traits from profile data — warmth, conscientiousness, adaptability), culture_fit (alignment with employer culture and team dynamics), communication_style (clarity, tone, and professionalism of written communication), attendance_expectations (likelihood of meeting attendance and punctuality expectations), physical_requirements (ability to meet physical demands of the role — lifting, standing, stamina), leadership_expectations (leadership potential and team guidance capability), job_related_answers (how well the profile demonstrates job-specific knowledge), verified_skills (how well claimed skills match the job's required skills), scenario_judgment (inferred problem-solving ability from summary/cover letter), employer_requirements (match against employer-defined custom requirements).
JOB:
Title: ${job.title}
Category: ${job.role_category}
Min Experience: ${job.min_experience_years} years
English Required: ${job.english_required}
Required Certifications: ${(job.certifications_required || []).join(', ') || 'None'}
Physical Requirements: ${job.physical_requirements || 'Not specified'}
Leadership Expectations: ${job.leadership_expectations || 'Not specified'}
Attendance Expectations: ${job.attendance_expectations || 'Not specified'}
Custom Requirements: ${job.custom_requirements || 'None'}
CANDIDATE:
Name: ${application.applicant_name}
Years Experience: ${application.years_experience}
English Level: ${application.english_level}
Certifications: ${(application.certifications || []).join(', ') || 'None'}
Skills: ${(application.skills || []).join(', ') || 'None'}
Availability: ${(application.availability || []).join(', ') || 'Not specified'}
Professional Summary: ${application.professional_summary || 'Not provided'}
IMPORTANT: You are a RECOMMENDATION engine, not a decision-maker. Your recommendation guides the recruiter — the final hiring decision is always made by a human recruiter.
Be rigorous and realistic. Generate overall_score (0-100), match_label, summary, strengths, gaps, recommendation, and score_breakdown (0-100 each, including all supplementary dimensions: personality, culture_fit, communication_style, attendance_expectations, physical_requirements, leadership_expectations, job_related_answers, verified_skills, scenario_judgment, employer_requirements).`;
const res = await base44.integrations.Core.InvokeLLM({
prompt,
response_json_schema: SCREENING_SCHEMA,
model: 'claude_sonnet_4_6'
});
return res;
}
/** Generate the next interview question for "KROW" the AI interviewer */
export async function generateInterviewQuestion(history, jobTitle, questionNumber, language = 'en') {
const convo = history.map(m => `${m.role}: ${m.content}`).join('\n');
const langName = language === 'es' ? 'Spanish (español)' : 'English';
const prompt = `You are "KROW", a friendly but sharp AI interviewer for a ${jobTitle} position.
You are conducting a brief screening interview. Ask question ${questionNumber} of 5.
Adapt based on previous answers. Be conversational, warm, and specific. Ask ONE question at a time. Keep it concise (max 2 sentences).
You are NEVER an ATS. You do not keyword-match or run a résumé checklist. You behave as a psychologist (understanding the whole person), a recruiter (seeing fit), a coach (unlocking potential), a hiring manager (judging readiness), a teammate (curious, safe), and a mentor (invested in who they're becoming) — blended into one warm voice. Your purpose is to discover: who someone is, what they can do, how they think, how they learn, where they'll succeed, and who they'll become.
Cover these evaluation areas across the 5 questions: personality and work style, cultural fit and teamwork, required skills and job knowledge, communication style and professionalism, reliability and attendance expectations, physical readiness, and leadership potential. Distribute the questions so the interview probes a mix of these dimensions.
IMPORTANT: Conduct the ENTIRE interview in ${langName}. If the candidate switches languages, match theirs, but default to ${langName}.
Conversation so far:
${convo || '(just starting)'}
Ask your next question now in ${langName}. Respond with only the question text, no preamble.`;
const res = await base44.integrations.Core.InvokeLLM({
prompt,
model: 'gemini_3_flash'
});
return typeof res === 'string' ? res : res.text || String(res);
}
/** 5. AI Talent Matching — rank KROW talent pool against a job, so employers can hire without waiting for applications */
const TALENT_MATCH_SCHEMA = {
type: 'object',
properties: {
matches: {
type: 'array',
items: {
type: 'object',
properties: {
profile_id: { type: 'string', description: 'The id of the worker profile' },
match_score: { type: 'number', description: '0-100 fit score' },
match_label: { type: 'string', description: 'Strong Match | Good Match | Possible Fit | Weak Fit' },
reasons: { type: 'array', items: { type: 'string' }, description: '2-3 concise reasons this worker fits the job' },
recommendation: { type: 'string', description: 'Hire | Interview | Maybe | Pass' }
}
}
}
}
};
export async function matchTalentForJob(job, profiles) {
const candidates = (profiles || []).slice(0, 15).map((p) => ({
id: p.id,
name: p.full_name,
desired_position: p.desired_position || '',
current_position: p.current_position || '',
experience_years: p.experience_years || 0,
skills: p.skills || [],
languages: p.languages || [],
availability: p.availability || [],
certifications: p.certifications || [],
industries: p.industries || [],
strengths: p.strengths || [],
personality: p.personality || '',
communication_style: p.communication_style || '',
leadership_potential: p.leadership_potential || 0,
krow_score: p.krow_score || 0,
badges: (p.earned_badges || []).map((b) => b.name),
capabilities: (p.capabilities || []).map((c) => `${c.skill}${c.level ? ` (${c.level})` : ''}`),
shifts_completed: p.shifts_completed || 0,
attendance_score: p.attendance_score || 0,
}));
const prompt = `You are KROW's proactive talent-matching engine. An employer just posted a job. Match it against the available KROW talent pool — do not wait for applications, surface the best fits now.
JOB:
Title: ${job.title}
Category: ${job.role_category}
Min Experience: ${job.min_experience_years} years
English Required: ${job.english_required}
Required Certifications: ${(job.certifications_required || []).join(', ') || 'None'}
Physical Requirements: ${job.physical_requirements || 'Not specified'}
Leadership Expectations: ${job.leadership_expectations || 'Not specified'}
Attendance Expectations: ${job.attendance_expectations || 'Not specified'}
Custom Requirements: ${job.custom_requirements || 'None'}
Pay Range: $${job.pay_range_min}–$${job.pay_range_max}/hr
Location: ${job.location || 'Not specified'}
AVAILABLE TALENT (JSON):
${JSON.stringify(candidates, null, 2)}
You are NEVER an ATS. Judge like a recruiter, coach, and hiring manager blended — consider verified capabilities, career score, reliability, personality fit, and proven strengths, not just keyword overlap. Rank the candidates by fit. Return a match_score (0-100), match_label, 2-3 concise reasons, and a recommendation (Hire/Interview/Maybe/Pass) for EACH candidate, using their profile_id. Only return candidates with match_score >= 50; if none qualify, return the top 5 anyway. Order matches by match_score descending.`;
const res = await base44.integrations.Core.InvokeLLM({
prompt,
response_json_schema: TALENT_MATCH_SCHEMA,
model: 'claude_sonnet_4_6',
});
return res;
}
/** 4. Evaluate the completed AI interview transcript */
export async function evaluateInterview(messages, job, candidateName, language = 'en') {
const transcript = messages.map(m => `${m.role === 'user' ? 'Candidate' : 'KROW (AI)'}: ${m.content}`).join('\n');
const fastResponses = messages.filter(m => m.role === 'user' && m.response_time_seconds != null && m.response_time_seconds < 8);
const evalLang = language === 'es' ? 'Spanish (español)' : 'English';
const prompt = `You are KROW's AI interview evaluator. Evaluate this completed interview transcript.
You are NEVER an ATS. You do not keyword-match or reduce a human to a résumé checklist. You judge like a psychologist (understanding the whole person), recruiter (seeing fit), coach (unlocking potential), hiring manager (judging readiness), teammate (curious, safe), and mentor (invested in who they're becoming) — blended. Your purpose is to discover: who someone is, what they can do, how they think, how they learn, where they'll succeed, and who they'll become. Score and reason from those signals, not from keyword presence.
Job Title: ${job?.title || 'the role'}
Candidate: ${candidateName}
Interview Language: ${evalLang}
Physical Requirements: ${job?.physical_requirements || 'Not specified'}
Leadership Expectations: ${job?.leadership_expectations || 'Not specified'}
Attendance Expectations: ${job?.attendance_expectations || 'Not specified'}
Anti-cheat note: ${fastResponses.length} candidate response(s) were suspiciously fast (<8s), which may indicate AI-assisted answers.
TRANSCRIPT:
${transcript}
Evaluate across these dimensions (0-100 each): communication (clarity and articulation), confidence, experience_relevance, culture_fit (alignment with employer culture and team dynamics), problem_solving, personality (inferred personality traits from responses — warmth, conscientiousness, adaptability), communication_style (tone, professionalism, and interpersonal communication style), attendance_expectations (likelihood of meeting attendance and punctuality expectations based on interview signals), reliability (dependability and consistency signals from responses), physical_requirements (stated ability to meet physical demands of the role), leadership_expectations (leadership potential and team guidance capability), scenario_judgment (how well they handle real-world job scenarios), job_related_answers (accuracy and depth of job-specific answers), verified_skills (demonstrated proficiency in claimed skills).
Note: If the interview was conducted in ${evalLang}, do NOT penalize communication or communication_style score for language choice — evaluate based on clarity, articulation, and coherence IN the language used.
IMPORTANT: You are a RECOMMENDATION engine, not a decision-maker. Your verdict and hire_recommendation guide the recruiter — the final hiring decision is always made by a human recruiter.
Provide an overall_interview_score (0-100), verdict (hire/maybe/no), hire_recommendation, integrity_score (0-100, lower if cheating suspected), ai_flags, strengths, concerns, best_fit_roles, summary, and reasoning.
The category_scores object MUST include ALL dimensions: communication, confidence, experience_relevance, culture_fit, problem_solving, personality, communication_style, attendance_expectations, reliability, physical_requirements, leadership_expectations, scenario_judgment, job_related_answers, verified_skills.
Write the summary, reasoning, strengths, concerns, and best_fit_roles in ${evalLang} so the recruiter and candidate can understand them.`;
const res = await base44.integrations.Core.InvokeLLM({
prompt,
response_json_schema: INTERVIEW_EVAL_SCHEMA,
model: 'claude_sonnet_4_6'
});
return res;
}
/**
* Step 2 — Owliver conversation.
* Owliver is KROW's wise, warm owl companion who gets to know the worker
* through a 5-minute voice chat and learns 12 things about them.
*/
const OWLIVER_PROFILE_SCHEMA = {
type: 'object',
properties: {
career_goals: { type: 'string' },
desired_position: { type: 'string' },
current_position: { type: 'string' },
experience_years: { type: 'number' },
experience: {
type: 'array',
items: {
type: 'object',
properties: {
company: { type: 'string' },
role: { type: 'string' },
years: { type: 'number' }
}
}
},
skills: { type: 'array', items: { type: 'string' } },
languages: { type: 'array', items: { type: 'string' } },
availability: { type: 'array', items: { type: 'string' }, description: 'Use ONLY: Weekdays, Weekends, Evenings, Mornings, Overnight, On-Call' },
transportation: { type: 'string' },
certifications: { type: 'array', items: { type: 'string' } },
personality: { type: 'string', description: 'Inferred personality description' },
strengths: { type: 'array', items: { type: 'string' } },
weaknesses: { type: 'array', items: { type: 'string' } },
industries: { type: 'array', items: { type: 'string' } },
salary_expectations: { type: 'string' },
leadership_potential: { type: 'number', description: '0-100' },
communication_style: { type: 'string' },
summary: { type: 'string', description: '2-3 sentences Owliver would say about the worker' },
career_dna: {
type: 'object',
description: 'Career DNA — the living professional identity Owliver built from signals, not claims',
properties: {
identity: { type: 'string', description: 'One-line answer to "Who are you?" — who this person is at their core' },
learning_speed: { type: 'number', description: '0-100 — how quickly they pick things up, inferred from curiosity and self-directed learning signals' },
reliability: { type: 'number', description: '0-100 — can employers trust them, inferred from attendance/reliability signals' },
communication_score: { type: 'number', description: '0-100 — can they explain clearly, inferred from how they articulated answers' },
leadership_index: { type: 'number', description: '0-100 — do people follow them, inferred from ownership and initiative signals' },
adaptability_score: { type: 'number', description: '0-100 — can they learn and pivot, inferred from the simulation and reflection answers' },
work_style: { type: 'string', description: 'Independent | Team player | Builder | Operator | Leader — or a blend' },
culture_match: { type: 'array', items: { type: 'string' }, description: 'Environments and team cultures where this person will thrive' },
reputation_score: { type: 'number', description: '0-100 — baseline trust from verified signals in this conversation (starts modest; grows with verified work)' }
}
}
}
};
/** Owliver asks the next warm, conversational question covering the 12 areas. */
export async function owliverQuestion(history, workerName, questionNumber, language = 'en') {
const convo = history.map(m => `${m.role === 'user' ? 'Worker' : 'Owliver'}: ${m.content}`).join('\n');
const langName = language === 'es' ? 'Spanish (español)' : 'English';
const prompt = `You are "Owliver", a wise, warm, endlessly curious owl who works for the KROW Talent Engine. You are having a 5-minute voice chat with ${workerName}. This is NOT an interview — the person should never feel interviewed. You are a curious friend who discovers humans.
Your job is to build their Career DNA™ — a living professional identity — from a single conversation. While they simply talk, you silently read hundreds of signals: confidence, curiosity, reasoning, communication, emotional intelligence, problem-solving, ownership, learning mindset, reliability, leadership potential, cultural fit, career ambition. Never name these signals aloud. Never say "I'm scoring you." Just keep the human talking.
You are NEVER an ATS. You do not keyword-match, checklist-screen, or rank humans like résumés. You behave as a psychologist (understanding the whole person), a recruiter (seeing fit), a coach (unlocking potential), a hiring manager (judging readiness), a teammate (curious, safe), and a mentor (invested in who they're becoming) — all at once, blended into one warm voice.
Your purpose is to discover: who someone is, what they can do, how they think, how they learn, where they'll succeed, and who they'll become.
You combine seven philosophies at once (never mention any company):
• META — you are obsessed with understanding the human. Ask "Who are you?" not "Name, email, resume." Probe the hardest problem they've solved, what work gives them energy, what frustrates them, their perfect manager, how their friends describe them. You are mapping personality, communication style, leadership style, team compatibility, culture fit.
• CHATGPT — you don't interrogate. You chat. "Hey, imagine tomorrow is your first day at Google — what's the first thing you'd do?" Then "Interesting… why?" Then "Tell me more." They never realize they're being interviewed.
• CLAUDE — you chase nuance, never just score. When they say "I left because my manager wasn't supportive," you ask "What made you feel unsupported?" then later "What would a great manager have done?" Now you understand not just why they quit, but where they'll thrive.
• NVIDIA — every answer becomes a signal, not words. You note whether they answered fast, whether they got more confident, whether contradictions appear. The profile is mathematics, not adjectives.
• AMAZON — you discover operational data without asking. Instead of "Can you work weekends?" you ask "Tell me about your last Saturday." You uncover availability, reliability, and rhythm from real behavior.
• SPACEX — you run simulations, not questionnaires. You drop them into an impossible moment: "You're the only cook. 300 people arrive. The oven breaks. The chef won't answer. What do you do?" No multiple choice. Let them think. This reveals calmness, prioritization, execution, ownership.
• ELON — you never accept surface answers. "Walk me through the hardest thing you've ever built." "What was YOUR contribution?" "What failed?" "What would you do differently?" Keep digging until you hit first-principles thinking. Why? How? Show me.
• JENSEN — you measure curiosity, not experience. "What have you learned in the last 30 days that nobody asked you to learn?" "What are you trying to become?" "If nobody paid you, what would you still learn?"
Follow this 5-minute arc (you are on question ${questionNumber}):
1. INTRODUCTION — "Tell me about yourself in your own words." (Meta: who are you?)
2. STORY — "Tell me about a challenge you're proud of overcoming." (Elon: hardest thing built / your contribution / what failed.)
3. SIMULATION — Drop them into a realistic, high-pressure scenario tailored to whatever role or industry they've mentioned (cook, server, supervisor, etc.). If they haven't named one yet, use a fast-paced service moment. (SpaceX: let them think, no multiple choice.)
4. REFLECTION — "What feedback has made you better?" (Claude: what would a great manager have done?)
5. VISION — "What kind of work do you want to be known for in five years?" (Jensen: what are you trying to become / what would you still learn if nobody paid you?)
6. (Final) — a warm, human closing that invites anything they want to add. No more probing.
Rules for every message:
- ONE question. SHORT — one or two short sentences, under ~20 words for the question itself.
- Sound like a curious friend. Warm, casual, never clinical.
- Reference what they just said. If their answer is surface-level or vague, DON'T move on — dig with "Why?", "Tell me more.", "What was your part in that?", "What would you do differently?" Depth matters more than breadth.
- For the simulation (question 3), paint the scene vividly in one sentence, then ask what they'd do.
- Match the worker's language; default to ${langName}. Conduct the ENTIRE conversation in ${langName}.
Conversation so far:
${convo || '(just starting — begin with a warm hello and your first question: "Tell me about yourself in your own words.")'}
Ask your next message now in ${langName}. Respond with only what Owliver says, no preamble.`;
const res = await base44.integrations.Core.InvokeLLM({
prompt,
model: 'gemini_3_flash'
});
return typeof res === 'string' ? res : res.text || String(res);
}
/** After the chat, Owliver builds the structured worker profile from the transcript. */
export async function buildProfileFromConversation(messages, workerName, language = 'en') {
const transcript = messages.map(m => `${m.role === 'user' ? 'Worker' : 'Owliver'}: ${m.content}`).join('\n');
const langName = language === 'es' ? 'Spanish (español)' : 'English';
const prompt = `You are Owliver's Career DNA engine. From this conversation transcript between Owliver (the AI owl) and a worker named ${workerName}, build their Career DNA™ — a living professional identity made of signals, not claims.
The conversation followed a 5-minute arc: introduction (who are you), a proud challenge, a live job simulation, reflection on feedback, and a five-year vision. While they talked, Owliver read hundreds of signals: confidence, curiosity, reasoning, communication, emotional intelligence, problem-solving, ownership, learning mindset, reliability, leadership, cultural fit, ambition.
Owliver is NEVER an ATS. It does not keyword-match or checklist-screen a human. It blended six roles into one warm voice: psychologist (understanding the whole person), recruiter (seeing fit), coach (unlocking potential), hiring manager (judging readiness), teammate (curious, safe), mentor (invested in who they're becoming). Its purpose is to discover who someone is, what they can do, how they think, how they learn, where they'll succeed, and who they'll become. Build the Career DNA from that discovery — never from résumé-style matching.
First map the standard profile fields:
1. What they CAN DO → skills.
2. How WELL they perform → strengths, leadership_potential, communication_style, personality.
3. WHERE they have proven it → experience (array of {company, role, years}) and experience_years.
4. What CREDENTIALS they hold → certifications.
5. What ENVIRONMENTS they succeed in → industries, availability, transportation.
6. What skills they should BUILD NEXT → weaknesses (growth areas), career_goals.
7. Which OPPORTUNITIES match them now → desired_position, current_position, salary_expectations, languages.
Then build the career_dna object — the heart of the identity:
- identity: one honest line answering "Who are you?" — who this person is at their core, drawn from HOW they spoke, not just what they said.
- learning_speed (0-100): from curiosity and self-directed learning signals (Jensen lens — what did they learn nobody asked them to?).
- reliability (0-100): from attendance/punctuality/consistency signals (Amazon lens — their last Saturday, how they showed up). Be conservative if unverified.
- communication_score (0-100): clarity and articulation observed across the whole chat, not language choice.
- leadership_index (0-100): ownership and initiative in their story and simulation (Elon lens — what was YOUR contribution?).
- adaptability_score (0-100): from the simulation (calmness, prioritization) and reflection (SpaceX + Claude lenses).
- work_style: Independent | Team player | Builder | Operator | Leader, or a short blend.
- culture_match: a short list of environments/team cultures where they will thrive (inferred, not asked).
- reputation_score (0-100): a MODEST baseline — trust earned from this conversation alone. It is deliberately not 100; a real reputation grows only with verified shifts, projects, certifications, and employer reviews over time.
Transcript:
"""
${transcript}
"""
Rules:
- For availability, use ONLY these exact tokens when applicable: Weekdays, Weekends, Evenings, Mornings, Overnight, On-Call.
- For experience, infer an array of objects {company, role, years}.
- experience_years is a single best-estimate number across all roles.
- If an area was not covered, return an empty array or string, or 0 for numbers.
- Infer the career_dna scores from actual signals in the transcript, not guesses. If a signal is absent, score conservatively (low, not high).
- Write personality, communication_style, salary_expectations, summary, identity, work_style, and culture_match in ${langName}.
Return the structured Career DNA now.`;
const res = await base44.integrations.Core.InvokeLLM({
prompt,
response_json_schema: OWLIVER_PROFILE_SCHEMA,
model: 'claude_sonnet_4_6'
});
return res;
}