cii-text-analysis/Lib/RawDataClassifier.php

128 lines
4.3 KiB
PHP
Raw Permalink Normal View History

2026-02-10 16:19:59 +01:00
<?php
declare(strict_types=1);
namespace Classifier\Lib;
use Classifier\Lib\Enum\MessageType;
use Classifier\Lib\MessageClassifier;
use Classifier\Lib\Model\ModelTrainer;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
class RawDataClassifier
{
const OUTPUT_DIR = __DIR__ . '/../../output';
const GOOD_FILE = self::OUTPUT_DIR . '/positive.php';
const BAD_FILE = self::OUTPUT_DIR . '/negative.php';
const IGNORE_FILE = self::OUTPUT_DIR . '/ignored.php';
private $strings = [];
private $io;
private $results = ['good' => [], 'bad' => [], 'ignored' => []];
private $master;
private $trainer;
public function __construct(array $strings, MessageClassifier $master, ModelTrainer $trainer)
{
$this->io = new SymfonyStyle(new ArgvInput(), new ConsoleOutput());
$this->master = $master;
$this->trainer = $trainer;
$this->loadExistingData();
$cleaned = array_unique(array_map(function ($v) {
return preg_replace('/\s+/', ' ', trim(mb_strtolower($v)));
}, $strings));
$alreadyDone = array_merge($this->results['good'], $this->results['bad'], $this->results['ignored']);
$this->strings = array_diff($cleaned, $alreadyDone);
if (!is_dir(self::OUTPUT_DIR)) {
mkdir(self::OUTPUT_DIR, 0777, true);
}
}
private function loadExistingData(): void
{
$files = ['good' => self::GOOD_FILE, 'bad' => self::BAD_FILE, 'ignored' => self::IGNORE_FILE];
foreach ($files as $key => $path) {
if (file_exists($path)) {
$this->results[$key] = include $path;
}
}
}
public function runQuestions(): void
{
$count = count($this->strings);
if ($count === 0) {
$this->io->success("Toutes les chaînes sont déjà traitées.");
$this->askForTraining();
return;
}
$this->io->title('Test & Labellisation Assistée par Machine');
foreach ($this->strings as $string) {
// L'algorithme fait une suggestion
$prediction = $this->master->classify($string);
$this->io->section("Message à analyser");
$this->io->text($string);
$this->io->note(sprintf(
"Suggestion : %s (via %s) - Confiance : %d%% - Détails : %s",
$prediction->decision->getValue(),
$prediction->method,
$prediction->confidence * 100,
implode(', ', array_map(function (object $d) {
return $d->word . " (" . round($d->score * 100, 2) . "%)";
}, $prediction->getDetails()))
));
$answer = $this->io->choice('Valider cette classification ?', [
'y' => 'Oui (Alerte)',
'n' => 'Non (Info)',
'i' => 'Ignorer',
'q' => 'Sauvegarder et Quitter'
], $prediction->decision->equals(MessageType::ALERTE()) ? 'y' : 'n');
if ($answer === 'q') {
break;
}
if ($answer === 'y') {
$this->results['good'][] = $string;
} elseif ($answer === 'i') {
$this->results['ignored'][] = $string;
} else {
$this->results['bad'][] = $string;
}
}
$this->saveResults();
$this->askForTraining();
}
private function askForTraining(): void
{
if ($this->io->confirm("Voulez-vous lancer l'apprentissage immédiat du modèle avec ces données ?", true)) {
$this->io->comment("Entraînement en cours...");
$this->trainer->trainFromData([
'ALERTE' => $this->results['good'],
'INFO' => $this->results['bad']
]);
$this->io->success("Modèle mis à jour et sauvegardé !");
}
}
private function saveResults(): void
{
foreach (['good' => self::GOOD_FILE, 'bad' => self::BAD_FILE, 'ignored' => self::IGNORE_FILE] as $key => $path) {
sort($this->results[$key]);
file_put_contents($path, "<?php\n\nreturn " . var_export($this->results[$key], true) . ";\n");
}
}
}