feat: diffusion turbulente + recalcul forcé — polygones qui évoluent dans le temps
- DriftSimulationService : ajout diffusion turbulente 600m/h (marche aléatoire indépendante par point) → les patches se dispersent et déforment au fil des horizons au lieu d'une simple translation rigide avec les alizés constants - SAMPLE_POINTS 30→40, CONCAVITY 0.8→0.7, MODEL_VERSION 1.0→1.1 - ComputeForecastsCommand : option --force pour supprimer et recalculer les forecasts existants (utile après changement de modèle) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,32 +29,60 @@ class ComputeForecastsCommand extends Command
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addOption('limit', 'l', InputOption::VALUE_OPTIONAL,
|
||||
'Nombre max d\'observations à traiter', 5);
|
||||
$this
|
||||
->addOption('limit', 'l', InputOption::VALUE_OPTIONAL,
|
||||
'Nombre max d\'observations à traiter', 5)
|
||||
->addOption('force', null, InputOption::VALUE_NONE,
|
||||
'Supprime les forecasts existants et les recalcule (utile après changement de modèle)');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$limit = (int) $input->getOption('limit');
|
||||
$force = (bool) $input->getOption('force');
|
||||
|
||||
$io->title('Calcul forecasts + ImpactScores');
|
||||
|
||||
// Observations récentes sans forecast
|
||||
$observations = $this->em->getRepository(SargassumObservation::class)
|
||||
// Charge les N observations cibles (avec ou sans forecasts existants)
|
||||
$qb = $this->em->getRepository(SargassumObservation::class)
|
||||
->createQueryBuilder('o')
|
||||
->leftJoin('o.forecasts', 'f')
|
||||
->where('f.id IS NULL')
|
||||
->orderBy('o.detectedAt', 'DESC')
|
||||
->setMaxResults($limit)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
->setMaxResults($limit);
|
||||
|
||||
if (!$force) {
|
||||
$qb->leftJoin('o.forecasts', 'f')->where('f.id IS NULL');
|
||||
}
|
||||
|
||||
$observations = $qb->getQuery()->getResult();
|
||||
|
||||
if (empty($observations)) {
|
||||
$io->info('Aucune observation sans forecast trouvée.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
if ($force) {
|
||||
$ids = array_map(fn($o) => (string) $o->getId(), $observations);
|
||||
$io->note(sprintf('--force : recalcul pour %d observation(s)...', count($ids)));
|
||||
|
||||
$this->em->getConnection()->executeStatement(
|
||||
'DELETE FROM sargassum_forecast WHERE source_observation_id IN (:ids)',
|
||||
['ids' => $ids],
|
||||
['ids' => \Doctrine\DBAL\ArrayParameterType::STRING]
|
||||
);
|
||||
// ImpactScore n'a pas de FK vers l'observation — on purge tout et on recalcule
|
||||
$this->em->getConnection()->executeStatement('DELETE FROM impact_score');
|
||||
$this->em->clear();
|
||||
|
||||
// Recharge les observations détachées
|
||||
$observations = $this->em->getRepository(SargassumObservation::class)
|
||||
->createQueryBuilder('o')
|
||||
->where('o.id IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
$io->progressStart(count($observations));
|
||||
$errors = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user