Files
crm-ms1/v4_ci4/app/Models/RadarModel.php

211 lines
6.9 KiB
PHP
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.

<?php
namespace App\Models;
use Config\Database;
/**
* IDEE-5 / MSOP-4 / MSOP-3 — radar_threads + skills + analyse IA.
*/
class RadarModel
{
private function db()
{
return Database::connect();
}
public function tablesExist(): bool
{
$row = $this->db()->query("SHOW TABLES LIKE 'radar_threads'")->getRowArray();
return ! empty($row);
}
public function hasAnalysisColumns(): bool
{
static $cached = null;
if ($cached !== null) {
return $cached;
}
$row = $this->db()->query("SHOW COLUMNS FROM radar_threads LIKE 'analysis'")->getRowArray();
$cached = ! empty($row);
return $cached;
}
public function listExceptions(): array
{
return $this->db()->table('radar_exceptions')
->where('active', 1)
->orderBy('kind', 'ASC')
->orderBy('value', 'ASC')
->get()
->getResultArray();
}
public function listThreads(): array
{
$rows = $this->db()->table('radar_threads')
->orderBy('received_at', 'DESC')
->orderBy('id', 'DESC')
->get()
->getResultArray();
if ($rows === []) {
return [];
}
$ids = array_column($rows, 'id');
$skillRows = $this->db()->table('radar_thread_skills')
->whereIn('thread_id', $ids)
->get()
->getResultArray();
$byThread = [];
foreach ($skillRows as $s) {
$byThread[(int) $s['thread_id']][] = $s['skill'];
}
foreach ($rows as &$row) {
$id = (int) $row['id'];
$row['skills'] = $byThread[$id] ?? [];
$row['client_url'] = ! empty($row['client_id'])
? '/index.php/clients/form/' . (int) $row['client_id']
: '';
$row['projet_url'] = ! empty($row['projet_id'])
? '/index.php/projets/form/' . (int) $row['projet_id']
: '';
}
unset($row);
return $rows;
}
/**
* Historique dossards pour le même expéditeur (hors fil courant), du plus ancien au plus récent.
*
* @return list<array<string, mixed>>
*/
public function listPriorDossardThreads(string $fromEmail, string $excludeGmailThreadId, int $limit = 5): array
{
$fromEmail = \strtolower(\trim($fromEmail));
if ($fromEmail === '') {
return [];
}
$sql = 'SELECT t.subject, t.summary, t.received_at'
. ($this->hasAnalysisColumns() ? ', t.analysis' : ', NULL AS analysis')
. ' FROM radar_threads t
INNER JOIN radar_thread_skills s ON s.thread_id = t.id AND s.skill = ?
WHERE t.from_email = ?
AND t.gmail_thread_id <> ?
AND t.is_example = 0
ORDER BY t.received_at ASC, t.id ASC
LIMIT ' . (int) $limit;
return $this->db()->query($sql, ['dossards', $fromEmail, $excludeGmailThreadId])->getResultArray();
}
/**
* @param array<string, mixed> $data
* @param list<string> $skills
* @return array{op:string,id:int}
*/
public function upsertThread(array $data, array $skills = []): array
{
$db = $this->db();
$existing = $db->table('radar_threads')
->where('gmail_thread_id', $data['gmail_thread_id'])
->get()
->getRowArray();
if ($existing) {
$update = [
'mailbox' => $data['mailbox'],
'subject' => $data['subject'],
'from_email' => $data['from_email'],
'from_name' => $data['from_name'],
'gmail_url' => $data['gmail_url'],
'summary' => $data['summary'],
'status' => $data['status'],
'confidence' => $data['confidence'],
'received_at' => $data['received_at'],
'is_example' => 0,
];
// Ne pas écraser une analyse IA déjà présente avec le seul snippet.
if ($this->hasAnalysisColumns() && ! empty($existing['analysis'])) {
unset($update['confidence']);
}
$db->table('radar_threads')->where('id', $existing['id'])->update($update);
$threadId = (int) $existing['id'];
$op = 'update';
} else {
$insert = [
'gmail_thread_id' => $data['gmail_thread_id'],
'mailbox' => $data['mailbox'],
'subject' => $data['subject'],
'from_email' => $data['from_email'],
'from_name' => $data['from_name'],
'gmail_url' => $data['gmail_url'],
'summary' => $data['summary'],
'status' => $data['status'],
'confidence' => $data['confidence'],
'is_example' => (int) ($data['is_example'] ?? 0),
'received_at' => $data['received_at'],
'proposed_client_no' => $data['proposed_client_no'] ?? '',
];
$db->table('radar_threads')->insert($insert);
$threadId = (int) $db->insertID();
$op = 'insert';
}
foreach ($skills as $skill) {
$skill = \strtolower(\trim($skill));
if ($skill === '') {
continue;
}
$exists = $db->table('radar_thread_skills')
->where('thread_id', $threadId)
->where('skill', $skill)
->countAllResults();
if ($exists === 0) {
$db->table('radar_thread_skills')->insert([
'thread_id' => $threadId,
'skill' => $skill,
]);
}
}
return ['op' => $op, 'id' => $threadId];
}
public function saveAnalysis(int $threadId, string $analysis, string $delta, int $confidence): void
{
if (! $this->hasAnalysisColumns()) {
// Fallback : ranger lanalyse dans summary si colonnes absentes.
$this->db()->table('radar_threads')->where('id', $threadId)->update([
'summary' => \mb_substr($analysis, 0, 1000),
'confidence' => $confidence,
]);
return;
}
$this->db()->table('radar_threads')->where('id', $threadId)->update([
'analysis' => $analysis,
'analysis_delta' => $delta,
'confidence' => $confidence,
]);
}
public function needsAnalysis(int $threadId): bool
{
if (! $this->hasAnalysisColumns()) {
return true;
}
$row = $this->db()->table('radar_threads')->select('analysis')->where('id', $threadId)->get()->getRowArray();
return empty($row['analysis']);
}
}