107 lines
3.4 KiB
PHP
107 lines
3.4 KiB
PHP
<?php
|
|
|
|
namespace App\Libraries\Radar;
|
|
|
|
use App\Libraries\Ai\AiClient;
|
|
use App\Libraries\Ai\AiException;
|
|
use App\Libraries\Ai\AiFactory;
|
|
|
|
/**
|
|
* MSOP-3 — analyse métier dossards + delta.
|
|
* Utilise la couche IA plateforme (AiClient), pas un vendor direct.
|
|
*/
|
|
class RadarAnalyzeService
|
|
{
|
|
public function __construct(private ?AiClient $ai = null)
|
|
{
|
|
$this->ai ??= AiFactory::client();
|
|
}
|
|
|
|
public function isEnabled(): bool
|
|
{
|
|
return $this->ai->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 === [];
|
|
$system = <<<'SYS'
|
|
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}
|
|
SYS;
|
|
|
|
$prompt = <<<PROMPT
|
|
- 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;
|
|
|
|
try {
|
|
$out = $this->ai->complete($prompt, ['system' => $system]);
|
|
} catch (AiException $e) {
|
|
throw new \RuntimeException($e->getMessage(), (int) $e->getCode(), $e);
|
|
}
|
|
|
|
$text = $out->text;
|
|
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';
|
|
}
|
|
}
|