feat(ai): optimize candidate analysis and implement batch processing
This commit is contained in:
@@ -73,7 +73,7 @@ class AIAnalysisService
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the AI API (using a placeholder for now, or direct Http call).
|
||||
* Call the AI API.
|
||||
*/
|
||||
protected function callAI(Candidate $candidate, string $cvText, ?string $letterText, ?string $provider = null)
|
||||
{
|
||||
@@ -83,30 +83,49 @@ class AIAnalysisService
|
||||
$jobDesc = $candidate->jobPosition->description;
|
||||
$requirements = implode(", ", $candidate->jobPosition->requirements ?? []);
|
||||
|
||||
$prompt = "Tu es un expert en recrutement technique. Analyse le CV (et la lettre de motivation si présente) d'un candidat pour le poste de '{$jobTitle}' attache une grande importance aux compétences techniques et à l'expérience du candidat, mais aussi à sa capacité à s'intégrer dans une équipe et à sa motivation.
|
||||
$prompt = "Tu es un expert en recrutement technique. Ton rôle est d'analyser le profil d'un candidat pour le poste de '{$jobTitle}'.";
|
||||
|
||||
DESCRIPTION DU POSTE:
|
||||
{$jobDesc}
|
||||
if (!$candidate->jobPosition->ai_prompt) {
|
||||
$prompt .= " Attache une grande importance aux compétences techniques et à l'expérience, mais aussi à la capacité d'intégration et à la motivation.
|
||||
|
||||
DESCRIPTION DU POSTE:
|
||||
{$jobDesc}
|
||||
|
||||
COMPÉTENCES REQUISES:
|
||||
{$requirements}
|
||||
|
||||
CONTENU DU CV:
|
||||
{$cvText}
|
||||
CONTENU DE LA LETTRE DE MOTIVATION:
|
||||
" . ($letterText ?? "Non fournie") . "
|
||||
|
||||
Fournis une analyse structurée en JSON avec les clés suivantes:
|
||||
- match_score: note de 0 à 100
|
||||
- summary: résumé de 3-4 phrases sur le profil
|
||||
- strengths: liste des points forts par rapport au poste
|
||||
- gaps: liste des compétences manquantes ou points de vigilance
|
||||
- verdict: une conclusion (Favorable, Très Favorable, Réservé, Défavorable)";
|
||||
} else {
|
||||
// Context injection for the custom prompt
|
||||
$prompt .= "
|
||||
|
||||
CONTEXTE DU POSTE:
|
||||
{$jobDesc}
|
||||
|
||||
COMPÉTENCES REQUISES:
|
||||
{$requirements}
|
||||
|
||||
CONTENU DU CV DU CANDIDAT:
|
||||
{$cvText}
|
||||
|
||||
CONTENU DE LA LETTRE DE MOTIVATION:
|
||||
" . ($letterText ?? "Non fournie") . "
|
||||
|
||||
CONSIGNES D'ANALYSE SPÉCIFIQUES:
|
||||
" . $candidate->jobPosition->ai_prompt;
|
||||
}
|
||||
|
||||
COMPÉTENCES REQUISES:
|
||||
{$requirements}
|
||||
|
||||
CONTENU DU CV:
|
||||
{$cvText}
|
||||
CONTENU DE LA LETTRE DE MOTIVATION:
|
||||
" . ($letterText ?? "Non fournie") . "
|
||||
|
||||
CONTEXTE ADDITIONNEL & INSTRUCTIONS PARTICULIÈRES:
|
||||
" . ($candidate->jobPosition->ai_prompt ?? "Aucune instruction spécifique.") . "
|
||||
|
||||
Fournis une analyse structurée en JSON avec les clés suivantes:
|
||||
- match_score: note de 0 à 100
|
||||
- summary: résumé de 3-4 phrases sur le profil et la ville d'origine du candidat
|
||||
- strengths: liste des points forts par rapport au poste
|
||||
- gaps: liste des compétences manquantes ou points de vigilance
|
||||
- verdict: une conclusion (Favorable, Très Favorable, Réservé, Défavorable)
|
||||
|
||||
Réponds UNIQUEMENT en JSON pur.";
|
||||
$prompt .= "\n\nRéponds UNIQUEMENT en JSON pur, sans texte avant ou après. Assure-toi que le JSON est valide.";
|
||||
|
||||
$analysis = match ($provider) {
|
||||
'openai' => $this->callOpenAI($prompt),
|
||||
@@ -115,11 +134,53 @@ class AIAnalysisService
|
||||
default => $this->callOllama($prompt),
|
||||
};
|
||||
|
||||
// Inject metadata for display and tracking
|
||||
$analysis['provider'] = $provider;
|
||||
$analysis['analyzed_at'] = now()->toIso8601String();
|
||||
// Normalize keys for frontend compatibility
|
||||
$normalized = $this->normalizeAnalysis($analysis);
|
||||
|
||||
return $analysis;
|
||||
// Inject metadata
|
||||
$normalized['provider'] = $provider;
|
||||
$normalized['analyzed_at'] = now()->toIso8601String();
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the AI response keys to ensure frontend compatibility.
|
||||
*/
|
||||
protected function normalizeAnalysis(array $data): array
|
||||
{
|
||||
$normalized = $data;
|
||||
|
||||
// Map custom keys to standard keys if they exist
|
||||
if (isset($data['score_global']) && !isset($data['match_score'])) {
|
||||
$normalized['match_score'] = $data['score_global'];
|
||||
}
|
||||
|
||||
if (isset($data['recommandation']) && !isset($data['verdict'])) {
|
||||
$normalized['verdict'] = $data['recommandation'];
|
||||
}
|
||||
|
||||
if (isset($data['synthese']) && !isset($data['summary'])) {
|
||||
$normalized['summary'] = $data['synthese'];
|
||||
}
|
||||
|
||||
if (isset($data['points_vigilance']) && !isset($data['gaps'])) {
|
||||
// Handle if points_vigilance is a list of objects (as in user's prompt)
|
||||
if (is_array($data['points_vigilance']) && isset($data['points_vigilance'][0]) && is_array($data['points_vigilance'][0])) {
|
||||
$normalized['gaps'] = array_map(fn($i) => ($i['type'] ?? '') . ': ' . ($i['description'] ?? ''), $data['points_vigilance']);
|
||||
} else {
|
||||
$normalized['gaps'] = $data['points_vigilance'];
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure default keys exist even if empty
|
||||
$normalized['match_score'] = $normalized['match_score'] ?? 0;
|
||||
$normalized['summary'] = $normalized['summary'] ?? "Pas de résumé généré.";
|
||||
$normalized['verdict'] = $normalized['verdict'] ?? "Indéterminé";
|
||||
$normalized['strengths'] = $normalized['strengths'] ?? [];
|
||||
$normalized['gaps'] = $normalized['gaps'] ?? [];
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
protected function callOllama(string $prompt)
|
||||
@@ -181,7 +242,7 @@ class AIAnalysisService
|
||||
'content-type' => 'application/json'
|
||||
])->timeout(60)->post('https://api.anthropic.com/v1/messages', [
|
||||
'model' => 'claude-3-5-sonnet-20240620',
|
||||
'max_tokens' => 1024,
|
||||
'max_tokens' => 2048,
|
||||
'messages' => [['role' => 'user', 'content' => $prompt]]
|
||||
]);
|
||||
|
||||
@@ -229,7 +290,7 @@ class AIAnalysisService
|
||||
{
|
||||
return [
|
||||
'match_score' => 75,
|
||||
'summary' => "Analyse simulée (IA non connectée ou erreur API). Le candidat semble avoir une solide expérience mais certains points techniques doivent être vérifiés.",
|
||||
'summary' => "Analyse simulée (IA non connectée ou erreur API). Le candidat peut avoir un profil intéressant mais une vérification manuelle est nécessaire.",
|
||||
'strengths' => ["Expérience pertinente", "Bonne présentation"],
|
||||
'gaps' => ["Compétences spécifiques à confirmer"],
|
||||
'verdict' => "Favorable"
|
||||
|
||||
Reference in New Issue
Block a user