fix: l'observation la plus proche gagne — évite l'écrasement par zone distante

ImpactScoreService : ne pas écraser le score si la nouvelle observation est plus
loin que le score actuel (sauf après 6h de stale). Empêche Barbade d'écraser
la Guadeloupe avec 367km quand des sargasses sont à 8km.

SpotScoreController : horizons et bancs filtrés par proximité (<300km) du spot,
plus la dernière observation globale quelle que soit sa zone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gwadaking
2026-04-08 02:33:31 -04:00
parent a607fd3c99
commit 51ca910e11
2 changed files with 27 additions and 2 deletions

View File

@@ -107,7 +107,10 @@ class SpotScoreController extends AbstractController
JOIN sargassum_observation o ON o.id = f.source_observation_id JOIN sargassum_observation o ON o.id = f.source_observation_id
WHERE f.source_observation_id = ( WHERE f.source_observation_id = (
SELECT o2.id FROM sargassum_observation o2 SELECT o2.id FROM sargassum_observation o2
INNER JOIN sargassum_forecast ff ON ff.source_observation_id = o2.id INNER JOIN sargassum_forecast ff ON ff.source_observation_id = o2.id,
coastal_point cp3
WHERE cp3.id = :spotId
AND ST_Distance(o2.geometry::geography, cp3.geometry::geography) < 300000
ORDER BY o2.detected_at DESC LIMIT 1 ORDER BY o2.detected_at DESC LIMIT 1
) )
ORDER BY f.time_horizon ASC', ORDER BY f.time_horizon ASC',
@@ -226,7 +229,10 @@ class SpotScoreController extends AbstractController
FROM sargassum_observation o FROM sargassum_observation o
WHERE o.id = ( WHERE o.id = (
SELECT o2.id FROM sargassum_observation o2 SELECT o2.id FROM sargassum_observation o2
INNER JOIN sargassum_forecast ff ON ff.source_observation_id = o2.id INNER JOIN sargassum_forecast ff ON ff.source_observation_id = o2.id,
coastal_point cp3
WHERE cp3.id = :spotId
AND ST_Distance(o2.geometry::geography, cp3.geometry::geography) < 300000
ORDER BY o2.detected_at DESC LIMIT 1 ORDER BY o2.detected_at DESC LIMIT 1
) )
) dump ) dump

View File

@@ -267,6 +267,25 @@ class ImpactScoreService
/** @param array{score: int, level: string, distance: float|null, density: float, trend: string, etaHours: int|null} $data */ /** @param array{score: int, level: string, distance: float|null, density: float, trend: string, etaHours: int|null} $data */
private function persistScore(CoastalPoint $spot, array $data): void private function persistScore(CoastalPoint $spot, array $data): void
{ {
// "L'observation la plus proche gagne" : ne pas écraser un score avec une distance
// plus grande que le score actuel, sauf si celui-ci a plus de 6h (stale).
if ($data['distance'] !== null) {
$current = $this->connection->fetchAssociative(
'SELECT distance_to_nearest_sargassum AS dist, timestamp
FROM impact_score WHERE coastal_point_id = :id ORDER BY timestamp DESC LIMIT 1',
['id' => (string) $spot->getId()]
);
if ($current !== false && $current['dist'] !== null) {
$ageSeconds = (new \DateTimeImmutable())->getTimestamp()
- (new \DateTimeImmutable($current['timestamp']))->getTimestamp();
$isStale = $ageSeconds > 21_600; // 6h
$isCloser = $data['distance'] < (float) $current['dist'];
if (!$isStale && !$isCloser) {
return; // garder le score de l'observation plus proche
}
}
}
$s = new ImpactScore(); $s = new ImpactScore();
$s->setCoastalPoint($spot); $s->setCoastalPoint($spot);
$s->setScore($data['score']); $s->setScore($data['score']);