96 lines
3.1 KiB
PHP
96 lines
3.1 KiB
PHP
<?php
|
||
|
||
namespace App\Libraries\Radar;
|
||
|
||
/**
|
||
* MSOP-3 — analyse métier dossards + delta vs mails précédents du même expéditeur.
|
||
* L’IA signale ; elle n’approuve jamais (pas de Go / tampon).
|
||
*/
|
||
class RadarAnalyzeService
|
||
{
|
||
public function __construct(private GeminiClient $gemini)
|
||
{
|
||
}
|
||
|
||
public function isEnabled(): bool
|
||
{
|
||
return $this->gemini->isConfigured();
|
||
}
|
||
|
||
/**
|
||
* @param list<array{subject:string,summary:string,analysis:?string,received_at:?string}> $priors
|
||
* @return array{analysis:string, delta:string, confidence:int}
|
||
*/
|
||
public function analyzeDossards(
|
||
string $subject,
|
||
string $fromEmail,
|
||
string $body,
|
||
array $priors,
|
||
): array {
|
||
$hist = '';
|
||
foreach ($priors as $i => $p) {
|
||
$n = $i + 1;
|
||
$hist .= "--- Mail dossards #{$n} ({$p['received_at']}) ---\n"
|
||
. 'Sujet: ' . ($p['subject'] ?? '') . "\n"
|
||
. 'Analyse/snippet: ' . (($p['analysis'] ?: $p['summary']) ?? '') . "\n\n";
|
||
}
|
||
|
||
$isFirst = $priors === [];
|
||
$prompt = <<<PROMPT
|
||
Tu es l'assistant Radar du CRM MS1 Timing (chronométrage / dossards).
|
||
Tu SIGNALES seulement. Tu n'APPROUVES JAMAIS une impression ni un Go.
|
||
Réponds UNIQUEMENT en JSON valide UTF-8, sans markdown autour, schéma :
|
||
{"analysis":"string","delta":"string","confidence":0}
|
||
|
||
- analysis : analyse métier dossards courte (FR), points utiles : logos, séquences, imprimeur, délais, fichiers, questions ouvertes, signaux faibles vs forts. Pas de copier-coller du mail. Max ~1200 caractères.
|
||
- delta : si 1er mail → chaîne vide "". Sinon : ce qui change / stable / nouveau / contradictoire vs l'historique. Max ~800 caractères.
|
||
- confidence : entier 0-100 (fiabilité de ton analyse, pas un feu vert).
|
||
|
||
Contexte :
|
||
Expéditeur : {$fromEmail}
|
||
Sujet : {$subject}
|
||
Premier mail dossards pour cet expéditeur : {$this->boolFr($isFirst)}
|
||
|
||
Historique dossards déjà en Radar (du plus ancien au plus récent) :
|
||
{$hist}
|
||
|
||
Mail courant (corps) :
|
||
{$body}
|
||
PROMPT;
|
||
|
||
$out = $this->gemini->generate($prompt);
|
||
$text = $out['text'];
|
||
// Extraire JSON si le modèle ajoute du bruit
|
||
if (\preg_match('/\{.*\}/s', $text, $m)) {
|
||
$text = $m[0];
|
||
}
|
||
$json = \json_decode($text, true);
|
||
if (! \is_array($json)) {
|
||
return [
|
||
'analysis' => \mb_substr($out['text'], 0, 2000),
|
||
'delta' => $isFirst ? '' : 'Delta non structuré — voir analyse.',
|
||
'confidence' => 40,
|
||
];
|
||
}
|
||
|
||
$analysis = \trim((string) ($json['analysis'] ?? ''));
|
||
$delta = \trim((string) ($json['delta'] ?? ''));
|
||
$conf = (int) ($json['confidence'] ?? 50);
|
||
$conf = \max(0, \min(100, $conf));
|
||
if ($isFirst) {
|
||
$delta = '';
|
||
}
|
||
|
||
return [
|
||
'analysis' => \mb_substr($analysis !== '' ? $analysis : $out['text'], 0, 4000),
|
||
'delta' => \mb_substr($delta, 0, 2000),
|
||
'confidence' => $conf,
|
||
];
|
||
}
|
||
|
||
private function boolFr(bool $v): string
|
||
{
|
||
return $v ? 'oui' : 'non';
|
||
}
|
||
}
|