add trend UX + PHPStan level 6 clean (0 errors)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,8 @@ class SpotScoreController extends AbstractController
|
||||
|
||||
/**
|
||||
* Score à un instant précis (sans cache).
|
||||
*
|
||||
* @return array{value: int, level: string, distance: float|null, density: float|null, trend: string|null, computedAt: string}|null
|
||||
*/
|
||||
private function loadScoreAt(string $spotId, \DateTimeImmutable $at): ?array
|
||||
{
|
||||
@@ -81,6 +83,8 @@ class SpotScoreController extends AbstractController
|
||||
/**
|
||||
* Scores estimés par horizon à partir des forecasts les plus récents.
|
||||
* Retourne null si aucun forecast disponible.
|
||||
*
|
||||
* @return array<string, array{score: int, level: string, distanceKm: float, confidence: float, validAt: string}>|null
|
||||
*/
|
||||
private function loadHorizonScores(string $spotId): ?array
|
||||
{
|
||||
@@ -125,6 +129,10 @@ class SpotScoreController extends AbstractController
|
||||
return $horizons;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
* @return array{value: int, level: string, distance: float|null, density: float|null, trend: string|null, computedAt: string}
|
||||
*/
|
||||
private function formatScore(array $row): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -36,33 +36,38 @@ class CoastalPoint
|
||||
|
||||
#[ORM\Column(length: 150)]
|
||||
#[Groups(['spot:read'])]
|
||||
private ?string $name = null;
|
||||
private string $name;
|
||||
|
||||
#[ORM\Column(type: 'geometry', options: ['geometry_type' => 'POINT', 'srid' => 4326])]
|
||||
private mixed $geometry = null;
|
||||
|
||||
#[ORM\Column(length: 30)]
|
||||
#[Groups(['spot:read'])]
|
||||
private ?string $type = null;
|
||||
private string $type;
|
||||
|
||||
#[ORM\Column(length: 100)]
|
||||
#[Groups(['spot:read'])]
|
||||
private ?string $region = null;
|
||||
private string $region;
|
||||
|
||||
/** @var Collection<int, ImpactScore> */
|
||||
#[ORM\OneToMany(targetEntity: ImpactScore::class, mappedBy: 'coastalPoint')]
|
||||
private Collection $impactScores;
|
||||
|
||||
/** @var Collection<int, UserFeedback> */
|
||||
#[ORM\OneToMany(targetEntity: UserFeedback::class, mappedBy: 'coastalPoint')]
|
||||
private Collection $feedbacks;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->name = '';
|
||||
$this->type = '';
|
||||
$this->region = '';
|
||||
$this->impactScores = new ArrayCollection();
|
||||
$this->feedbacks = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?Uuid { return $this->id; }
|
||||
public function getName(): ?string { return $this->name; }
|
||||
public function getName(): string { return $this->name; }
|
||||
public function setName(string $name): static { $this->name = $name; return $this; }
|
||||
public function getGeometry(): mixed { return $this->geometry; }
|
||||
public function setGeometry(mixed $geometry): static { $this->geometry = $geometry; return $this; }
|
||||
@@ -82,9 +87,9 @@ class CoastalPoint
|
||||
preg_match('/POINT\([^ ]+ ([^)]+)\)/', (string) $this->geometry, $m);
|
||||
return isset($m[1]) ? (float) $m[1] : null;
|
||||
}
|
||||
public function getType(): ?string { return $this->type; }
|
||||
public function getType(): string { return $this->type; }
|
||||
public function setType(string $type): static { $this->type = $type; return $this; }
|
||||
public function getRegion(): ?string { return $this->region; }
|
||||
public function getRegion(): string { return $this->region; }
|
||||
public function setRegion(string $region): static { $this->region = $region; return $this; }
|
||||
|
||||
/** @return Collection<int, ImpactScore> */
|
||||
|
||||
@@ -19,13 +19,13 @@ class DataIngestionJob
|
||||
private ?Uuid $id = null;
|
||||
|
||||
#[ORM\Column(length: 50)]
|
||||
private ?string $source = null;
|
||||
private string $source;
|
||||
|
||||
#[ORM\Column(length: 100)]
|
||||
private ?string $tileId = null;
|
||||
private string $tileId;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $requestedAt = null;
|
||||
private \DateTimeImmutable $requestedAt;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?\DateTimeImmutable $processedAt = null;
|
||||
@@ -41,15 +41,17 @@ class DataIngestionJob
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->source = '';
|
||||
$this->tileId = '';
|
||||
$this->requestedAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?Uuid { return $this->id; }
|
||||
public function getSource(): ?string { return $this->source; }
|
||||
public function getSource(): string { return $this->source; }
|
||||
public function setSource(string $source): static { $this->source = $source; return $this; }
|
||||
public function getTileId(): ?string { return $this->tileId; }
|
||||
public function getTileId(): string { return $this->tileId; }
|
||||
public function setTileId(string $tileId): static { $this->tileId = $tileId; return $this; }
|
||||
public function getRequestedAt(): ?\DateTimeImmutable { return $this->requestedAt; }
|
||||
public function getRequestedAt(): \DateTimeImmutable { return $this->requestedAt; }
|
||||
public function getProcessedAt(): ?\DateTimeImmutable { return $this->processedAt; }
|
||||
public function setProcessedAt(?\DateTimeImmutable $processedAt): static { $this->processedAt = $processedAt; return $this; }
|
||||
public function getStatus(): string { return $this->status; }
|
||||
|
||||
@@ -29,16 +29,16 @@ class ImpactScore
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'impactScores')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?CoastalPoint $coastalPoint = null;
|
||||
private CoastalPoint $coastalPoint;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $timestamp = null;
|
||||
private \DateTimeImmutable $timestamp;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?int $score = null;
|
||||
private int $score;
|
||||
|
||||
#[ORM\Column(length: 20)]
|
||||
private ?string $level = null;
|
||||
private string $level;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?float $distanceToNearestSargassum = null;
|
||||
@@ -52,16 +52,18 @@ class ImpactScore
|
||||
public function __construct()
|
||||
{
|
||||
$this->timestamp = new \DateTimeImmutable();
|
||||
$this->score = 0;
|
||||
$this->level = 'low';
|
||||
}
|
||||
|
||||
public function getId(): ?Uuid { return $this->id; }
|
||||
public function getCoastalPoint(): ?CoastalPoint { return $this->coastalPoint; }
|
||||
public function setCoastalPoint(?CoastalPoint $coastalPoint): static { $this->coastalPoint = $coastalPoint; return $this; }
|
||||
public function getTimestamp(): ?\DateTimeImmutable { return $this->timestamp; }
|
||||
public function getCoastalPoint(): CoastalPoint { return $this->coastalPoint; }
|
||||
public function setCoastalPoint(CoastalPoint $coastalPoint): static { $this->coastalPoint = $coastalPoint; return $this; }
|
||||
public function getTimestamp(): \DateTimeImmutable { return $this->timestamp; }
|
||||
public function setTimestamp(\DateTimeImmutable $timestamp): static { $this->timestamp = $timestamp; return $this; }
|
||||
public function getScore(): ?int { return $this->score; }
|
||||
public function getScore(): int { return $this->score; }
|
||||
public function setScore(int $score): static { $this->score = $score; return $this; }
|
||||
public function getLevel(): ?string { return $this->level; }
|
||||
public function getLevel(): string { return $this->level; }
|
||||
public function setLevel(string $level): static { $this->level = $level; return $this; }
|
||||
public function getDistanceToNearestSargassum(): ?float { return $this->distanceToNearestSargassum; }
|
||||
public function setDistanceToNearestSargassum(?float $d): static { $this->distanceToNearestSargassum = $d; return $this; }
|
||||
|
||||
@@ -18,34 +18,37 @@ class PushSubscription
|
||||
private ?Uuid $id = null;
|
||||
|
||||
#[ORM\Column(type: 'text', unique: true)]
|
||||
private ?string $endpoint = null;
|
||||
private string $endpoint;
|
||||
|
||||
#[ORM\Column(type: 'text')]
|
||||
private ?string $authToken = null;
|
||||
private string $authToken;
|
||||
|
||||
#[ORM\Column(type: 'text')]
|
||||
private ?string $p256dhKey = null;
|
||||
private string $p256dhKey;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
|
||||
private ?CoastalPoint $coastalPoint = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
private \DateTimeImmutable $createdAt;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->endpoint = '';
|
||||
$this->authToken = '';
|
||||
$this->p256dhKey = '';
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?Uuid { return $this->id; }
|
||||
public function getEndpoint(): ?string { return $this->endpoint; }
|
||||
public function getEndpoint(): string { return $this->endpoint; }
|
||||
public function setEndpoint(string $endpoint): static { $this->endpoint = $endpoint; return $this; }
|
||||
public function getAuthToken(): ?string { return $this->authToken; }
|
||||
public function getAuthToken(): string { return $this->authToken; }
|
||||
public function setAuthToken(string $authToken): static { $this->authToken = $authToken; return $this; }
|
||||
public function getP256dhKey(): ?string { return $this->p256dhKey; }
|
||||
public function getP256dhKey(): string { return $this->p256dhKey; }
|
||||
public function setP256dhKey(string $p256dhKey): static { $this->p256dhKey = $p256dhKey; return $this; }
|
||||
public function getCoastalPoint(): ?CoastalPoint { return $this->coastalPoint; }
|
||||
public function setCoastalPoint(?CoastalPoint $coastalPoint): static { $this->coastalPoint = $coastalPoint; return $this; }
|
||||
public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; }
|
||||
public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; }
|
||||
}
|
||||
|
||||
@@ -30,54 +30,58 @@ class SargassumForecast
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'forecasts')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?SargassumObservation $sourceObservation = null;
|
||||
private SargassumObservation $sourceObservation;
|
||||
|
||||
#[ORM\Column(type: 'geometry', options: ['geometry_type' => 'MULTIPOLYGON', 'srid' => 4326])]
|
||||
private mixed $geometry = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $validAt = null;
|
||||
private \DateTimeImmutable $validAt;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $computedAt = null;
|
||||
private \DateTimeImmutable $computedAt;
|
||||
|
||||
#[ORM\Column(length: 50)]
|
||||
private ?string $modelVersion = null;
|
||||
private string $modelVersion;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?int $timeHorizon = null;
|
||||
private int $timeHorizon;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?float $confidence = null;
|
||||
private float $confidence;
|
||||
|
||||
#[ORM\Column(type: 'geometry', nullable: true, options: ['geometry_type' => 'POINT', 'srid' => 4326])]
|
||||
private mixed $driftVectorAvg = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
private \DateTimeImmutable $createdAt;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
$this->computedAt = new \DateTimeImmutable();
|
||||
$this->validAt = new \DateTimeImmutable();
|
||||
$this->computedAt = new \DateTimeImmutable();
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
$this->modelVersion = '';
|
||||
$this->timeHorizon = 0;
|
||||
$this->confidence = 0.0;
|
||||
}
|
||||
|
||||
public function getId(): ?Uuid { return $this->id; }
|
||||
public function getSourceObservation(): ?SargassumObservation { return $this->sourceObservation; }
|
||||
public function setSourceObservation(?SargassumObservation $sourceObservation): static { $this->sourceObservation = $sourceObservation; return $this; }
|
||||
public function getSourceObservation(): SargassumObservation { return $this->sourceObservation; }
|
||||
public function setSourceObservation(SargassumObservation $sourceObservation): static { $this->sourceObservation = $sourceObservation; return $this; }
|
||||
public function getGeometry(): mixed { return $this->geometry; }
|
||||
public function setGeometry(mixed $geometry): static { $this->geometry = $geometry; return $this; }
|
||||
public function getValidAt(): ?\DateTimeImmutable { return $this->validAt; }
|
||||
public function getValidAt(): \DateTimeImmutable { return $this->validAt; }
|
||||
public function setValidAt(\DateTimeImmutable $validAt): static { $this->validAt = $validAt; return $this; }
|
||||
public function getComputedAt(): ?\DateTimeImmutable { return $this->computedAt; }
|
||||
public function getComputedAt(): \DateTimeImmutable { return $this->computedAt; }
|
||||
public function setComputedAt(\DateTimeImmutable $computedAt): static { $this->computedAt = $computedAt; return $this; }
|
||||
public function getModelVersion(): ?string { return $this->modelVersion; }
|
||||
public function getModelVersion(): string { return $this->modelVersion; }
|
||||
public function setModelVersion(string $modelVersion): static { $this->modelVersion = $modelVersion; return $this; }
|
||||
public function getTimeHorizon(): ?int { return $this->timeHorizon; }
|
||||
public function getTimeHorizon(): int { return $this->timeHorizon; }
|
||||
public function setTimeHorizon(int $timeHorizon): static { $this->timeHorizon = $timeHorizon; return $this; }
|
||||
public function getConfidence(): ?float { return $this->confidence; }
|
||||
public function getConfidence(): float { return $this->confidence; }
|
||||
public function setConfidence(float $confidence): static { $this->confidence = $confidence; return $this; }
|
||||
public function getDriftVectorAvg(): mixed { return $this->driftVectorAvg; }
|
||||
public function setDriftVectorAvg(mixed $driftVectorAvg): static { $this->driftVectorAvg = $driftVectorAvg; return $this; }
|
||||
public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; }
|
||||
public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; }
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Repository\SargassumObservationRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Jsor\Doctrine\PostGIS\Types\PostGISType;
|
||||
use Symfony\Bridge\Doctrine\Types\UuidType;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
@@ -28,25 +27,25 @@ class SargassumObservation
|
||||
private mixed $bbox = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $detectedAt = null;
|
||||
private \DateTimeImmutable $detectedAt;
|
||||
|
||||
#[ORM\Column(length: 50)]
|
||||
private ?string $source = null;
|
||||
private string $source;
|
||||
|
||||
#[ORM\Column(length: 100)]
|
||||
private ?string $tileId = null;
|
||||
private string $tileId;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?float $cloudCoverage = null;
|
||||
private float $cloudCoverage;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?float $confidence = null;
|
||||
private float $confidence;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?float $afaiMean = null;
|
||||
private float $afaiMean;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?float $afaiStd = null;
|
||||
private float $afaiStd;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?float $coverageArea = null;
|
||||
@@ -55,15 +54,23 @@ class SargassumObservation
|
||||
private ?string $processingVersion = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
private \DateTimeImmutable $createdAt;
|
||||
|
||||
/** @var Collection<int, SargassumForecast> */
|
||||
#[ORM\OneToMany(targetEntity: SargassumForecast::class, mappedBy: 'sourceObservation')]
|
||||
private Collection $forecasts;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->forecasts = new ArrayCollection();
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
$this->detectedAt = new \DateTimeImmutable();
|
||||
$this->source = '';
|
||||
$this->tileId = '';
|
||||
$this->cloudCoverage = 0.0;
|
||||
$this->confidence = 0.0;
|
||||
$this->afaiMean = 0.0;
|
||||
$this->afaiStd = 0.0;
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
$this->forecasts = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?Uuid { return $this->id; }
|
||||
@@ -71,25 +78,25 @@ class SargassumObservation
|
||||
public function setGeometry(mixed $geometry): static { $this->geometry = $geometry; return $this; }
|
||||
public function getBbox(): mixed { return $this->bbox; }
|
||||
public function setBbox(mixed $bbox): static { $this->bbox = $bbox; return $this; }
|
||||
public function getDetectedAt(): ?\DateTimeImmutable { return $this->detectedAt; }
|
||||
public function getDetectedAt(): \DateTimeImmutable { return $this->detectedAt; }
|
||||
public function setDetectedAt(\DateTimeImmutable $detectedAt): static { $this->detectedAt = $detectedAt; return $this; }
|
||||
public function getSource(): ?string { return $this->source; }
|
||||
public function getSource(): string { return $this->source; }
|
||||
public function setSource(string $source): static { $this->source = $source; return $this; }
|
||||
public function getTileId(): ?string { return $this->tileId; }
|
||||
public function getTileId(): string { return $this->tileId; }
|
||||
public function setTileId(string $tileId): static { $this->tileId = $tileId; return $this; }
|
||||
public function getCloudCoverage(): ?float { return $this->cloudCoverage; }
|
||||
public function getCloudCoverage(): float { return $this->cloudCoverage; }
|
||||
public function setCloudCoverage(float $cloudCoverage): static { $this->cloudCoverage = $cloudCoverage; return $this; }
|
||||
public function getConfidence(): ?float { return $this->confidence; }
|
||||
public function getConfidence(): float { return $this->confidence; }
|
||||
public function setConfidence(float $confidence): static { $this->confidence = $confidence; return $this; }
|
||||
public function getAfaiMean(): ?float { return $this->afaiMean; }
|
||||
public function getAfaiMean(): float { return $this->afaiMean; }
|
||||
public function setAfaiMean(float $afaiMean): static { $this->afaiMean = $afaiMean; return $this; }
|
||||
public function getAfaiStd(): ?float { return $this->afaiStd; }
|
||||
public function getAfaiStd(): float { return $this->afaiStd; }
|
||||
public function setAfaiStd(float $afaiStd): static { $this->afaiStd = $afaiStd; return $this; }
|
||||
public function getCoverageArea(): ?float { return $this->coverageArea; }
|
||||
public function setCoverageArea(?float $coverageArea): static { $this->coverageArea = $coverageArea; return $this; }
|
||||
public function getProcessingVersion(): ?string { return $this->processingVersion; }
|
||||
public function setProcessingVersion(?string $processingVersion): static { $this->processingVersion = $processingVersion; return $this; }
|
||||
public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; }
|
||||
public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; }
|
||||
|
||||
/** @return Collection<int, SargassumForecast> */
|
||||
public function getForecasts(): Collection { return $this->forecasts; }
|
||||
|
||||
@@ -25,10 +25,10 @@ class UserFeedback
|
||||
private ?CoastalPoint $coastalPoint = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $timestamp = null;
|
||||
private \DateTimeImmutable $timestamp;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?bool $hasSeaweed = null;
|
||||
private bool $hasSeaweed;
|
||||
|
||||
#[ORM\Column(length: 20, nullable: true)]
|
||||
private ?string $density = null;
|
||||
@@ -38,7 +38,8 @@ class UserFeedback
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->timestamp = new \DateTimeImmutable();
|
||||
$this->timestamp = new \DateTimeImmutable();
|
||||
$this->hasSeaweed = false;
|
||||
}
|
||||
|
||||
public function getId(): ?Uuid { return $this->id; }
|
||||
@@ -46,9 +47,9 @@ class UserFeedback
|
||||
public function setLocation(mixed $location): static { $this->location = $location; return $this; }
|
||||
public function getCoastalPoint(): ?CoastalPoint { return $this->coastalPoint; }
|
||||
public function setCoastalPoint(?CoastalPoint $coastalPoint): static { $this->coastalPoint = $coastalPoint; return $this; }
|
||||
public function getTimestamp(): ?\DateTimeImmutable { return $this->timestamp; }
|
||||
public function getTimestamp(): \DateTimeImmutable { return $this->timestamp; }
|
||||
public function setTimestamp(\DateTimeImmutable $timestamp): static { $this->timestamp = $timestamp; return $this; }
|
||||
public function getHasSeaweed(): ?bool { return $this->hasSeaweed; }
|
||||
public function getHasSeaweed(): bool { return $this->hasSeaweed; }
|
||||
public function setHasSeaweed(bool $hasSeaweed): static { $this->hasSeaweed = $hasSeaweed; return $this; }
|
||||
public function getDensity(): ?string { return $this->density; }
|
||||
public function setDensity(?string $density): static { $this->density = $density; return $this; }
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Entity\CoastalPoint;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/** @extends ServiceEntityRepository<CoastalPoint> */
|
||||
class CoastalPointRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Entity\DataIngestionJob;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/** @extends ServiceEntityRepository<DataIngestionJob> */
|
||||
class DataIngestionJobRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Entity\ImpactScore;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/** @extends ServiceEntityRepository<ImpactScore> */
|
||||
class ImpactScoreRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Entity\PushSubscription;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/** @extends ServiceEntityRepository<PushSubscription> */
|
||||
class PushSubscriptionRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Entity\SargassumForecast;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/** @extends ServiceEntityRepository<SargassumForecast> */
|
||||
class SargassumForecastRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Entity\SargassumObservation;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/** @extends ServiceEntityRepository<SargassumObservation> */
|
||||
class SargassumObservationRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Entity\UserFeedback;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/** @extends ServiceEntityRepository<UserFeedback> */
|
||||
class UserFeedbackRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
|
||||
@@ -102,6 +102,7 @@ class DriftSimulationService
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** @return array{lat: float, lng: float}|null */
|
||||
private function getCentroid(string $observationId): ?array
|
||||
{
|
||||
$row = $this->connection->fetchAssociative(
|
||||
@@ -167,6 +168,7 @@ class DriftSimulationService
|
||||
/**
|
||||
* Reconstruit un MULTIPOLYGON depuis un nuage de points via PostGIS.
|
||||
*/
|
||||
/** @param array<int, array{lat: float, lng: float}> $points */
|
||||
private function reconstructPolygon(array $points): ?string
|
||||
{
|
||||
if (count($points) < 3) {
|
||||
@@ -196,6 +198,10 @@ class DriftSimulationService
|
||||
/**
|
||||
* Calcule le vecteur de dérive moyen (composantes lat/lng) sur une période.
|
||||
*/
|
||||
/**
|
||||
* @param array<int, array{speed: float, direction: float}> $windData
|
||||
* @return array{lat: float, lng: float}
|
||||
*/
|
||||
private function avgDriftVector(array $windData, int $fromHour, int $toHour): array
|
||||
{
|
||||
$count = $toHour - $fromHour;
|
||||
|
||||
@@ -49,6 +49,8 @@ class ImpactScoreService
|
||||
/**
|
||||
* Calcule et sauvegarde les scores pour tous les CoastalPoints
|
||||
* à partir d'une observation et de ses forecasts.
|
||||
*
|
||||
* @param array<int, SargassumForecast> $forecasts
|
||||
*/
|
||||
public function computeForObservation(SargassumObservation $observation, array $forecasts): void
|
||||
{
|
||||
@@ -87,6 +89,8 @@ class ImpactScoreService
|
||||
|
||||
/**
|
||||
* Retourne le score mis en cache pour un spot (TTL 3h).
|
||||
*
|
||||
* @return array{value: int, level: string, distance: float|null, density: float|null, trend: string|null, computedAt: string}|null
|
||||
*/
|
||||
public function getCachedScore(string $spotId): ?array
|
||||
{
|
||||
@@ -100,6 +104,7 @@ class ImpactScoreService
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** @return array{score: int, level: string, distance: float|null, density: float, trend: string} */
|
||||
private function computeScore(CoastalPoint $spot, string $obsId, ?SargassumForecast $forecastH6): array
|
||||
{
|
||||
$spotId = (string) $spot->getId();
|
||||
@@ -194,13 +199,21 @@ class ImpactScoreService
|
||||
|
||||
private function getPreviousDistance(string $spotId): ?float
|
||||
{
|
||||
// On cherche le score de la précédente "session" d'ingestion :
|
||||
// au moins 6h d'ancienneté pour éviter de comparer avec un recalcul immédiat,
|
||||
// et au plus 14 jours pour rester pertinent.
|
||||
$since = (new \DateTimeImmutable())->modify('-14 days')->format('Y-m-d H:i:s');
|
||||
$before = (new \DateTimeImmutable())->modify('-6 hours')->format('Y-m-d H:i:s');
|
||||
|
||||
$row = $this->connection->fetchAssociative(
|
||||
'SELECT distance_to_nearest_sargassum
|
||||
FROM impact_score
|
||||
WHERE coastal_point_id = :spotId
|
||||
AND timestamp <= :before
|
||||
AND timestamp >= :since
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1',
|
||||
['spotId' => $spotId]
|
||||
['spotId' => $spotId, 'before' => $before, 'since' => $since]
|
||||
);
|
||||
|
||||
return ($row !== false && $row['distance_to_nearest_sargassum'] !== null)
|
||||
@@ -208,6 +221,7 @@ class ImpactScoreService
|
||||
: null;
|
||||
}
|
||||
|
||||
/** @param array{score: int, level: string, distance: float|null, density: float, trend: string} $data */
|
||||
private function persistScore(CoastalPoint $spot, array $data): void
|
||||
{
|
||||
$s = new ImpactScore();
|
||||
@@ -221,6 +235,7 @@ class ImpactScoreService
|
||||
$this->em->persist($s);
|
||||
}
|
||||
|
||||
/** @return array{value: int, level: string, distance: float|null, density: float|null, trend: string|null, computedAt: string}|null */
|
||||
private function loadLatestScore(string $spotId): ?array
|
||||
{
|
||||
$row = $this->connection->fetchAssociative(
|
||||
@@ -265,6 +280,7 @@ class ImpactScoreService
|
||||
};
|
||||
}
|
||||
|
||||
/** @return array{score: int, level: string, distance: null, density: float, trend: string} */
|
||||
private function emptyScore(): array
|
||||
{
|
||||
return ['score' => 0, 'level' => 'low', 'distance' => null, 'density' => 0.0, 'trend' => 'stable'];
|
||||
@@ -291,10 +307,11 @@ class ImpactScoreService
|
||||
return ($row !== false && (int) $row['positive_count'] > 0) ? (float) self::FEEDBACK_BONUS : 0.0;
|
||||
}
|
||||
|
||||
/** @param array<int, SargassumForecast> $forecasts */
|
||||
private function findForecastByHorizon(array $forecasts, int $horizon): ?SargassumForecast
|
||||
{
|
||||
foreach ($forecasts as $f) {
|
||||
if ($f instanceof SargassumForecast && $f->getTimeHorizon() === $horizon) {
|
||||
if ($f->getTimeHorizon() === $horizon) {
|
||||
return $f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class IngestionService
|
||||
* Tente l'ingestion Sentinel-2, puis Sentinel-3 OLCI en fallback si la
|
||||
* couverture nuageuse est trop élevée ou si la scène S2 est indisponible.
|
||||
*/
|
||||
/** @param array<int, float> $bbox */
|
||||
public function ingestWithFallback(array $bbox, \DateTimeImmutable $date): void
|
||||
{
|
||||
try {
|
||||
@@ -41,6 +42,7 @@ class IngestionService
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<int, float> $bbox */
|
||||
public function ingest(
|
||||
array $bbox,
|
||||
\DateTimeImmutable $date,
|
||||
@@ -177,6 +179,9 @@ class IngestionService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{type: string, coordinates: array<int, mixed>}|null
|
||||
*/
|
||||
private function buildMultiPolygon(string $geojsonPath): ?array
|
||||
{
|
||||
if (!file_exists($geojsonPath)) {
|
||||
|
||||
@@ -48,6 +48,7 @@ class SentinelHubClient
|
||||
* Retourne la couverture nuageuse (%) de la meilleure scène disponible.
|
||||
* collection : 'sentinel-2-l2a' (défaut) ou 'sentinel-3-olci'
|
||||
*/
|
||||
/** @param array<int, float> $bbox */
|
||||
public function getCloudCoverage(array $bbox, \DateTimeImmutable $date, string $collection = 'sentinel-2-l2a'): float
|
||||
{
|
||||
// Sentinel-3 OLCI n'expose pas eo:cloud_cover dans le Catalog ; on renvoie 0
|
||||
@@ -83,6 +84,10 @@ class SentinelHubClient
|
||||
* calculé via evalscript AFAI/FAI, et le sauvegarde dans $outputPath.
|
||||
* collection : 'sentinel-2-l2a' (défaut) ou 'sentinel-3-olci'
|
||||
*/
|
||||
/**
|
||||
* @param array<int, float> $bbox
|
||||
* @return array{tileId: string, afaiMean: float, afaiStd: float}
|
||||
*/
|
||||
public function downloadAfaiBinary(
|
||||
array $bbox,
|
||||
\DateTimeImmutable $date,
|
||||
|
||||
Reference in New Issue
Block a user