add trend UX + PHPStan level 6 clean (0 errors)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,8 @@
|
|||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"php-http/discovery": true,
|
"php-http/discovery": true,
|
||||||
"symfony/flex": true,
|
"symfony/flex": true,
|
||||||
"symfony/runtime": true
|
"symfony/runtime": true,
|
||||||
|
"phpstan/extension-installer": true
|
||||||
},
|
},
|
||||||
"bump-after-update": true,
|
"bump-after-update": true,
|
||||||
"sort-packages": true
|
"sort-packages": true
|
||||||
@@ -86,5 +87,11 @@
|
|||||||
"allow-contrib": false,
|
"allow-contrib": false,
|
||||||
"require": "7.4.*"
|
"require": "7.4.*"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/extension-installer": "^1.4",
|
||||||
|
"phpstan/phpstan": "^2.1",
|
||||||
|
"phpstan/phpstan-doctrine": "^2.0",
|
||||||
|
"phpstan/phpstan-symfony": "^2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
256
backend/composer.lock
generated
256
backend/composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "c960cf2dbc66a8c555b534d43072fb46",
|
"content-hash": "c745217057712f8ff1cb5cc0fc379c1b",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "api-platform/doctrine-common",
|
"name": "api-platform/doctrine-common",
|
||||||
@@ -8943,7 +8943,259 @@
|
|||||||
"time": "2022-01-30T20:08:53+00:00"
|
"time": "2022-01-30T20:08:53+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [],
|
"packages-dev": [
|
||||||
|
{
|
||||||
|
"name": "phpstan/extension-installer",
|
||||||
|
"version": "1.4.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/phpstan/extension-installer.git",
|
||||||
|
"reference": "85e90b3942d06b2326fba0403ec24fe912372936"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936",
|
||||||
|
"reference": "85e90b3942d06b2326fba0403ec24fe912372936",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"composer-plugin-api": "^2.0",
|
||||||
|
"php": "^7.2 || ^8.0",
|
||||||
|
"phpstan/phpstan": "^1.9.0 || ^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"composer/composer": "^2.0",
|
||||||
|
"php-parallel-lint/php-parallel-lint": "^1.2.0",
|
||||||
|
"phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0"
|
||||||
|
},
|
||||||
|
"type": "composer-plugin",
|
||||||
|
"extra": {
|
||||||
|
"class": "PHPStan\\ExtensionInstaller\\Plugin"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"PHPStan\\ExtensionInstaller\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "Composer plugin for automatic installation of PHPStan extensions",
|
||||||
|
"keywords": [
|
||||||
|
"dev",
|
||||||
|
"static analysis"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/phpstan/extension-installer/issues",
|
||||||
|
"source": "https://github.com/phpstan/extension-installer/tree/1.4.3"
|
||||||
|
},
|
||||||
|
"time": "2024-09-04T20:21:43+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phpstan/phpstan",
|
||||||
|
"version": "2.1.46",
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a193923fc2d6325ef4e741cf3af8c3e8f54dbf25",
|
||||||
|
"reference": "a193923fc2d6325ef4e741cf3af8c3e8f54dbf25",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.4|^8.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"phpstan/phpstan-shim": "*"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"phpstan",
|
||||||
|
"phpstan.phar"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "PHPStan - PHP Static Analysis Tool",
|
||||||
|
"keywords": [
|
||||||
|
"dev",
|
||||||
|
"static analysis"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"docs": "https://phpstan.org/user-guide/getting-started",
|
||||||
|
"forum": "https://github.com/phpstan/phpstan/discussions",
|
||||||
|
"issues": "https://github.com/phpstan/phpstan/issues",
|
||||||
|
"security": "https://github.com/phpstan/phpstan/security/policy",
|
||||||
|
"source": "https://github.com/phpstan/phpstan-src"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/ondrejmirtes",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/phpstan",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-04-01T09:25:14+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phpstan/phpstan-doctrine",
|
||||||
|
"version": "2.0.20",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/phpstan/phpstan-doctrine.git",
|
||||||
|
"reference": "72f4f7a02d6c98d9101e8616e0488bc0a785196d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/72f4f7a02d6c98d9101e8616e0488bc0a785196d",
|
||||||
|
"reference": "72f4f7a02d6c98d9101e8616e0488bc0a785196d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.4 || ^8.0",
|
||||||
|
"phpstan/phpstan": "^2.1.34"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"doctrine/collections": "<1.0",
|
||||||
|
"doctrine/common": "<2.7",
|
||||||
|
"doctrine/mongodb-odm": "<1.2",
|
||||||
|
"doctrine/orm": "<2.5",
|
||||||
|
"doctrine/persistence": "<1.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"cache/array-adapter": "^1.1",
|
||||||
|
"composer/semver": "^3.3.2",
|
||||||
|
"cweagans/composer-patches": "^1.7.3",
|
||||||
|
"doctrine/annotations": "^2.0",
|
||||||
|
"doctrine/collections": "^1.6 || ^2.1",
|
||||||
|
"doctrine/common": "^2.7 || ^3.0",
|
||||||
|
"doctrine/dbal": "^3.3.8",
|
||||||
|
"doctrine/lexer": "^2.0 || ^3.0",
|
||||||
|
"doctrine/mongodb-odm": "^2.4.3",
|
||||||
|
"doctrine/orm": "^2.16.0",
|
||||||
|
"doctrine/persistence": "^2.2.1 || ^3.4.3",
|
||||||
|
"gedmo/doctrine-extensions": "^3.8",
|
||||||
|
"nesbot/carbon": "^2.49",
|
||||||
|
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^2.0.2",
|
||||||
|
"phpstan/phpstan-phpunit": "^2.0.8",
|
||||||
|
"phpstan/phpstan-strict-rules": "^2.0",
|
||||||
|
"phpunit/phpunit": "^9.6.20",
|
||||||
|
"ramsey/uuid": "^4.2",
|
||||||
|
"symfony/cache": "^5.4",
|
||||||
|
"symfony/uid": "^5.4 || ^6.4 || ^7.3"
|
||||||
|
},
|
||||||
|
"type": "phpstan-extension",
|
||||||
|
"extra": {
|
||||||
|
"phpstan": {
|
||||||
|
"includes": [
|
||||||
|
"extension.neon",
|
||||||
|
"rules.neon"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"PHPStan\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "Doctrine extensions for PHPStan",
|
||||||
|
"keywords": [
|
||||||
|
"static analysis"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/phpstan/phpstan-doctrine/issues",
|
||||||
|
"source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.20"
|
||||||
|
},
|
||||||
|
"time": "2026-03-13T13:44:51+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phpstan/phpstan-symfony",
|
||||||
|
"version": "2.0.15",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/phpstan/phpstan-symfony.git",
|
||||||
|
"reference": "9b85ab476969b87bbe2253b69e265a9359b2f395"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/9b85ab476969b87bbe2253b69e265a9359b2f395",
|
||||||
|
"reference": "9b85ab476969b87bbe2253b69e265a9359b2f395",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-simplexml": "*",
|
||||||
|
"php": "^7.4 || ^8.0",
|
||||||
|
"phpstan/phpstan": "^2.1.13"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/framework-bundle": "<3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||||
|
"phpstan/phpstan-phpunit": "^2.0.8",
|
||||||
|
"phpstan/phpstan-strict-rules": "^2.0",
|
||||||
|
"phpunit/phpunit": "^9.6",
|
||||||
|
"psr/container": "1.1.2",
|
||||||
|
"symfony/config": "^5.4 || ^6.1",
|
||||||
|
"symfony/console": "^5.4 || ^6.1",
|
||||||
|
"symfony/dependency-injection": "^5.4 || ^6.1",
|
||||||
|
"symfony/form": "^5.4 || ^6.1",
|
||||||
|
"symfony/framework-bundle": "^5.4 || ^6.1",
|
||||||
|
"symfony/http-foundation": "^5.4 || ^6.1",
|
||||||
|
"symfony/messenger": "^5.4",
|
||||||
|
"symfony/polyfill-php80": "^1.24",
|
||||||
|
"symfony/serializer": "^5.4",
|
||||||
|
"symfony/service-contracts": "^2.2.0"
|
||||||
|
},
|
||||||
|
"type": "phpstan-extension",
|
||||||
|
"extra": {
|
||||||
|
"phpstan": {
|
||||||
|
"includes": [
|
||||||
|
"extension.neon",
|
||||||
|
"rules.neon"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"PHPStan\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Lukáš Unger",
|
||||||
|
"email": "looky.msc@gmail.com",
|
||||||
|
"homepage": "https://lookyman.net"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony Framework extensions and rules for PHPStan",
|
||||||
|
"keywords": [
|
||||||
|
"static analysis"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
|
||||||
|
"source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.15"
|
||||||
|
},
|
||||||
|
"time": "2026-02-26T10:15:59+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"stability-flags": {},
|
"stability-flags": {},
|
||||||
|
|||||||
8
backend/phpstan.neon
Normal file
8
backend/phpstan.neon
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
parameters:
|
||||||
|
level: 6
|
||||||
|
paths:
|
||||||
|
- src
|
||||||
|
symfony:
|
||||||
|
containerXmlPath: var/cache/dev/App_KernelDevDebugContainer.xml
|
||||||
|
doctrine:
|
||||||
|
objectManagerLoader: tests/bootstrap_phpstan.php
|
||||||
@@ -63,6 +63,8 @@ class SpotScoreController extends AbstractController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Score à un instant précis (sans cache).
|
* 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
|
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.
|
* Scores estimés par horizon à partir des forecasts les plus récents.
|
||||||
* Retourne null si aucun forecast disponible.
|
* 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
|
private function loadHorizonScores(string $spotId): ?array
|
||||||
{
|
{
|
||||||
@@ -125,6 +129,10 @@ class SpotScoreController extends AbstractController
|
|||||||
return $horizons;
|
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
|
private function formatScore(array $row): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -36,33 +36,38 @@ class CoastalPoint
|
|||||||
|
|
||||||
#[ORM\Column(length: 150)]
|
#[ORM\Column(length: 150)]
|
||||||
#[Groups(['spot:read'])]
|
#[Groups(['spot:read'])]
|
||||||
private ?string $name = null;
|
private string $name;
|
||||||
|
|
||||||
#[ORM\Column(type: 'geometry', options: ['geometry_type' => 'POINT', 'srid' => 4326])]
|
#[ORM\Column(type: 'geometry', options: ['geometry_type' => 'POINT', 'srid' => 4326])]
|
||||||
private mixed $geometry = null;
|
private mixed $geometry = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 30)]
|
#[ORM\Column(length: 30)]
|
||||||
#[Groups(['spot:read'])]
|
#[Groups(['spot:read'])]
|
||||||
private ?string $type = null;
|
private string $type;
|
||||||
|
|
||||||
#[ORM\Column(length: 100)]
|
#[ORM\Column(length: 100)]
|
||||||
#[Groups(['spot:read'])]
|
#[Groups(['spot:read'])]
|
||||||
private ?string $region = null;
|
private string $region;
|
||||||
|
|
||||||
|
/** @var Collection<int, ImpactScore> */
|
||||||
#[ORM\OneToMany(targetEntity: ImpactScore::class, mappedBy: 'coastalPoint')]
|
#[ORM\OneToMany(targetEntity: ImpactScore::class, mappedBy: 'coastalPoint')]
|
||||||
private Collection $impactScores;
|
private Collection $impactScores;
|
||||||
|
|
||||||
|
/** @var Collection<int, UserFeedback> */
|
||||||
#[ORM\OneToMany(targetEntity: UserFeedback::class, mappedBy: 'coastalPoint')]
|
#[ORM\OneToMany(targetEntity: UserFeedback::class, mappedBy: 'coastalPoint')]
|
||||||
private Collection $feedbacks;
|
private Collection $feedbacks;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
$this->name = '';
|
||||||
|
$this->type = '';
|
||||||
|
$this->region = '';
|
||||||
$this->impactScores = new ArrayCollection();
|
$this->impactScores = new ArrayCollection();
|
||||||
$this->feedbacks = new ArrayCollection();
|
$this->feedbacks = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?Uuid { return $this->id; }
|
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 setName(string $name): static { $this->name = $name; return $this; }
|
||||||
public function getGeometry(): mixed { return $this->geometry; }
|
public function getGeometry(): mixed { return $this->geometry; }
|
||||||
public function setGeometry(mixed $geometry): static { $this->geometry = $geometry; return $this; }
|
public function setGeometry(mixed $geometry): static { $this->geometry = $geometry; return $this; }
|
||||||
@@ -82,9 +87,9 @@ class CoastalPoint
|
|||||||
preg_match('/POINT\([^ ]+ ([^)]+)\)/', (string) $this->geometry, $m);
|
preg_match('/POINT\([^ ]+ ([^)]+)\)/', (string) $this->geometry, $m);
|
||||||
return isset($m[1]) ? (float) $m[1] : null;
|
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 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; }
|
public function setRegion(string $region): static { $this->region = $region; return $this; }
|
||||||
|
|
||||||
/** @return Collection<int, ImpactScore> */
|
/** @return Collection<int, ImpactScore> */
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ class DataIngestionJob
|
|||||||
private ?Uuid $id = null;
|
private ?Uuid $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 50)]
|
#[ORM\Column(length: 50)]
|
||||||
private ?string $source = null;
|
private string $source;
|
||||||
|
|
||||||
#[ORM\Column(length: 100)]
|
#[ORM\Column(length: 100)]
|
||||||
private ?string $tileId = null;
|
private string $tileId;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?\DateTimeImmutable $requestedAt = null;
|
private \DateTimeImmutable $requestedAt;
|
||||||
|
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private ?\DateTimeImmutable $processedAt = null;
|
private ?\DateTimeImmutable $processedAt = null;
|
||||||
@@ -41,15 +41,17 @@ class DataIngestionJob
|
|||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
$this->source = '';
|
||||||
|
$this->tileId = '';
|
||||||
$this->requestedAt = new \DateTimeImmutable();
|
$this->requestedAt = new \DateTimeImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?Uuid { return $this->id; }
|
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 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 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 getProcessedAt(): ?\DateTimeImmutable { return $this->processedAt; }
|
||||||
public function setProcessedAt(?\DateTimeImmutable $processedAt): static { $this->processedAt = $processedAt; return $this; }
|
public function setProcessedAt(?\DateTimeImmutable $processedAt): static { $this->processedAt = $processedAt; return $this; }
|
||||||
public function getStatus(): string { return $this->status; }
|
public function getStatus(): string { return $this->status; }
|
||||||
|
|||||||
@@ -29,16 +29,16 @@ class ImpactScore
|
|||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'impactScores')]
|
#[ORM\ManyToOne(inversedBy: 'impactScores')]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
private ?CoastalPoint $coastalPoint = null;
|
private CoastalPoint $coastalPoint;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?\DateTimeImmutable $timestamp = null;
|
private \DateTimeImmutable $timestamp;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?int $score = null;
|
private int $score;
|
||||||
|
|
||||||
#[ORM\Column(length: 20)]
|
#[ORM\Column(length: 20)]
|
||||||
private ?string $level = null;
|
private string $level;
|
||||||
|
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private ?float $distanceToNearestSargassum = null;
|
private ?float $distanceToNearestSargassum = null;
|
||||||
@@ -52,16 +52,18 @@ class ImpactScore
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->timestamp = new \DateTimeImmutable();
|
$this->timestamp = new \DateTimeImmutable();
|
||||||
|
$this->score = 0;
|
||||||
|
$this->level = 'low';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?Uuid { return $this->id; }
|
public function getId(): ?Uuid { return $this->id; }
|
||||||
public function getCoastalPoint(): ?CoastalPoint { return $this->coastalPoint; }
|
public function getCoastalPoint(): CoastalPoint { return $this->coastalPoint; }
|
||||||
public function setCoastalPoint(?CoastalPoint $coastalPoint): static { $this->coastalPoint = $coastalPoint; return $this; }
|
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 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 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 setLevel(string $level): static { $this->level = $level; return $this; }
|
||||||
public function getDistanceToNearestSargassum(): ?float { return $this->distanceToNearestSargassum; }
|
public function getDistanceToNearestSargassum(): ?float { return $this->distanceToNearestSargassum; }
|
||||||
public function setDistanceToNearestSargassum(?float $d): static { $this->distanceToNearestSargassum = $d; return $this; }
|
public function setDistanceToNearestSargassum(?float $d): static { $this->distanceToNearestSargassum = $d; return $this; }
|
||||||
|
|||||||
@@ -18,34 +18,37 @@ class PushSubscription
|
|||||||
private ?Uuid $id = null;
|
private ?Uuid $id = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'text', unique: true)]
|
#[ORM\Column(type: 'text', unique: true)]
|
||||||
private ?string $endpoint = null;
|
private string $endpoint;
|
||||||
|
|
||||||
#[ORM\Column(type: 'text')]
|
#[ORM\Column(type: 'text')]
|
||||||
private ?string $authToken = null;
|
private string $authToken;
|
||||||
|
|
||||||
#[ORM\Column(type: 'text')]
|
#[ORM\Column(type: 'text')]
|
||||||
private ?string $p256dhKey = null;
|
private string $p256dhKey;
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
|
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
|
||||||
private ?CoastalPoint $coastalPoint = null;
|
private ?CoastalPoint $coastalPoint = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?\DateTimeImmutable $createdAt = null;
|
private \DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
$this->endpoint = '';
|
||||||
|
$this->authToken = '';
|
||||||
|
$this->p256dhKey = '';
|
||||||
$this->createdAt = new \DateTimeImmutable();
|
$this->createdAt = new \DateTimeImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?Uuid { return $this->id; }
|
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 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 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 setP256dhKey(string $p256dhKey): static { $this->p256dhKey = $p256dhKey; return $this; }
|
||||||
public function getCoastalPoint(): ?CoastalPoint { return $this->coastalPoint; }
|
public function getCoastalPoint(): ?CoastalPoint { return $this->coastalPoint; }
|
||||||
public function setCoastalPoint(?CoastalPoint $coastalPoint): static { $this->coastalPoint = $coastalPoint; return $this; }
|
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\ManyToOne(inversedBy: 'forecasts')]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
private ?SargassumObservation $sourceObservation = null;
|
private SargassumObservation $sourceObservation;
|
||||||
|
|
||||||
#[ORM\Column(type: 'geometry', options: ['geometry_type' => 'MULTIPOLYGON', 'srid' => 4326])]
|
#[ORM\Column(type: 'geometry', options: ['geometry_type' => 'MULTIPOLYGON', 'srid' => 4326])]
|
||||||
private mixed $geometry = null;
|
private mixed $geometry = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?\DateTimeImmutable $validAt = null;
|
private \DateTimeImmutable $validAt;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?\DateTimeImmutable $computedAt = null;
|
private \DateTimeImmutable $computedAt;
|
||||||
|
|
||||||
#[ORM\Column(length: 50)]
|
#[ORM\Column(length: 50)]
|
||||||
private ?string $modelVersion = null;
|
private string $modelVersion;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?int $timeHorizon = null;
|
private int $timeHorizon;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?float $confidence = null;
|
private float $confidence;
|
||||||
|
|
||||||
#[ORM\Column(type: 'geometry', nullable: true, options: ['geometry_type' => 'POINT', 'srid' => 4326])]
|
#[ORM\Column(type: 'geometry', nullable: true, options: ['geometry_type' => 'POINT', 'srid' => 4326])]
|
||||||
private mixed $driftVectorAvg = null;
|
private mixed $driftVectorAvg = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?\DateTimeImmutable $createdAt = null;
|
private \DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->createdAt = new \DateTimeImmutable();
|
$this->validAt = new \DateTimeImmutable();
|
||||||
$this->computedAt = 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 getId(): ?Uuid { return $this->id; }
|
||||||
public function getSourceObservation(): ?SargassumObservation { return $this->sourceObservation; }
|
public function getSourceObservation(): SargassumObservation { return $this->sourceObservation; }
|
||||||
public function setSourceObservation(?SargassumObservation $sourceObservation): static { $this->sourceObservation = $sourceObservation; return $this; }
|
public function setSourceObservation(SargassumObservation $sourceObservation): static { $this->sourceObservation = $sourceObservation; return $this; }
|
||||||
public function getGeometry(): mixed { return $this->geometry; }
|
public function getGeometry(): mixed { return $this->geometry; }
|
||||||
public function setGeometry(mixed $geometry): static { $this->geometry = $geometry; return $this; }
|
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 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 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 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 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 setConfidence(float $confidence): static { $this->confidence = $confidence; return $this; }
|
||||||
public function getDriftVectorAvg(): mixed { return $this->driftVectorAvg; }
|
public function getDriftVectorAvg(): mixed { return $this->driftVectorAvg; }
|
||||||
public function setDriftVectorAvg(mixed $driftVectorAvg): static { $this->driftVectorAvg = $driftVectorAvg; return $this; }
|
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\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Jsor\Doctrine\PostGIS\Types\PostGISType;
|
|
||||||
use Symfony\Bridge\Doctrine\Types\UuidType;
|
use Symfony\Bridge\Doctrine\Types\UuidType;
|
||||||
use Symfony\Component\Uid\Uuid;
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
|
||||||
@@ -28,25 +27,25 @@ class SargassumObservation
|
|||||||
private mixed $bbox = null;
|
private mixed $bbox = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?\DateTimeImmutable $detectedAt = null;
|
private \DateTimeImmutable $detectedAt;
|
||||||
|
|
||||||
#[ORM\Column(length: 50)]
|
#[ORM\Column(length: 50)]
|
||||||
private ?string $source = null;
|
private string $source;
|
||||||
|
|
||||||
#[ORM\Column(length: 100)]
|
#[ORM\Column(length: 100)]
|
||||||
private ?string $tileId = null;
|
private string $tileId;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?float $cloudCoverage = null;
|
private float $cloudCoverage;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?float $confidence = null;
|
private float $confidence;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?float $afaiMean = null;
|
private float $afaiMean;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?float $afaiStd = null;
|
private float $afaiStd;
|
||||||
|
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private ?float $coverageArea = null;
|
private ?float $coverageArea = null;
|
||||||
@@ -55,15 +54,23 @@ class SargassumObservation
|
|||||||
private ?string $processingVersion = null;
|
private ?string $processingVersion = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?\DateTimeImmutable $createdAt = null;
|
private \DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
|
/** @var Collection<int, SargassumForecast> */
|
||||||
#[ORM\OneToMany(targetEntity: SargassumForecast::class, mappedBy: 'sourceObservation')]
|
#[ORM\OneToMany(targetEntity: SargassumForecast::class, mappedBy: 'sourceObservation')]
|
||||||
private Collection $forecasts;
|
private Collection $forecasts;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->forecasts = new ArrayCollection();
|
$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->createdAt = new \DateTimeImmutable();
|
||||||
|
$this->forecasts = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?Uuid { return $this->id; }
|
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 setGeometry(mixed $geometry): static { $this->geometry = $geometry; return $this; }
|
||||||
public function getBbox(): mixed { return $this->bbox; }
|
public function getBbox(): mixed { return $this->bbox; }
|
||||||
public function setBbox(mixed $bbox): static { $this->bbox = $bbox; return $this; }
|
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 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 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 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 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 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 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 setAfaiStd(float $afaiStd): static { $this->afaiStd = $afaiStd; return $this; }
|
||||||
public function getCoverageArea(): ?float { return $this->coverageArea; }
|
public function getCoverageArea(): ?float { return $this->coverageArea; }
|
||||||
public function setCoverageArea(?float $coverageArea): static { $this->coverageArea = $coverageArea; return $this; }
|
public function setCoverageArea(?float $coverageArea): static { $this->coverageArea = $coverageArea; return $this; }
|
||||||
public function getProcessingVersion(): ?string { return $this->processingVersion; }
|
public function getProcessingVersion(): ?string { return $this->processingVersion; }
|
||||||
public function setProcessingVersion(?string $processingVersion): static { $this->processingVersion = $processingVersion; return $this; }
|
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> */
|
/** @return Collection<int, SargassumForecast> */
|
||||||
public function getForecasts(): Collection { return $this->forecasts; }
|
public function getForecasts(): Collection { return $this->forecasts; }
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ class UserFeedback
|
|||||||
private ?CoastalPoint $coastalPoint = null;
|
private ?CoastalPoint $coastalPoint = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?\DateTimeImmutable $timestamp = null;
|
private \DateTimeImmutable $timestamp;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?bool $hasSeaweed = null;
|
private bool $hasSeaweed;
|
||||||
|
|
||||||
#[ORM\Column(length: 20, nullable: true)]
|
#[ORM\Column(length: 20, nullable: true)]
|
||||||
private ?string $density = null;
|
private ?string $density = null;
|
||||||
@@ -39,6 +39,7 @@ class UserFeedback
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->timestamp = new \DateTimeImmutable();
|
$this->timestamp = new \DateTimeImmutable();
|
||||||
|
$this->hasSeaweed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?Uuid { return $this->id; }
|
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 setLocation(mixed $location): static { $this->location = $location; return $this; }
|
||||||
public function getCoastalPoint(): ?CoastalPoint { return $this->coastalPoint; }
|
public function getCoastalPoint(): ?CoastalPoint { return $this->coastalPoint; }
|
||||||
public function setCoastalPoint(?CoastalPoint $coastalPoint): static { $this->coastalPoint = $coastalPoint; return $this; }
|
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 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 setHasSeaweed(bool $hasSeaweed): static { $this->hasSeaweed = $hasSeaweed; return $this; }
|
||||||
public function getDensity(): ?string { return $this->density; }
|
public function getDensity(): ?string { return $this->density; }
|
||||||
public function setDensity(?string $density): static { $this->density = $density; return $this; }
|
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\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/** @extends ServiceEntityRepository<CoastalPoint> */
|
||||||
class CoastalPointRepository extends ServiceEntityRepository
|
class CoastalPointRepository extends ServiceEntityRepository
|
||||||
{
|
{
|
||||||
public function __construct(ManagerRegistry $registry)
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Entity\DataIngestionJob;
|
|||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/** @extends ServiceEntityRepository<DataIngestionJob> */
|
||||||
class DataIngestionJobRepository extends ServiceEntityRepository
|
class DataIngestionJobRepository extends ServiceEntityRepository
|
||||||
{
|
{
|
||||||
public function __construct(ManagerRegistry $registry)
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Entity\ImpactScore;
|
|||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/** @extends ServiceEntityRepository<ImpactScore> */
|
||||||
class ImpactScoreRepository extends ServiceEntityRepository
|
class ImpactScoreRepository extends ServiceEntityRepository
|
||||||
{
|
{
|
||||||
public function __construct(ManagerRegistry $registry)
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Entity\PushSubscription;
|
|||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/** @extends ServiceEntityRepository<PushSubscription> */
|
||||||
class PushSubscriptionRepository extends ServiceEntityRepository
|
class PushSubscriptionRepository extends ServiceEntityRepository
|
||||||
{
|
{
|
||||||
public function __construct(ManagerRegistry $registry)
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Entity\SargassumForecast;
|
|||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/** @extends ServiceEntityRepository<SargassumForecast> */
|
||||||
class SargassumForecastRepository extends ServiceEntityRepository
|
class SargassumForecastRepository extends ServiceEntityRepository
|
||||||
{
|
{
|
||||||
public function __construct(ManagerRegistry $registry)
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Entity\SargassumObservation;
|
|||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/** @extends ServiceEntityRepository<SargassumObservation> */
|
||||||
class SargassumObservationRepository extends ServiceEntityRepository
|
class SargassumObservationRepository extends ServiceEntityRepository
|
||||||
{
|
{
|
||||||
public function __construct(ManagerRegistry $registry)
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Entity\UserFeedback;
|
|||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/** @extends ServiceEntityRepository<UserFeedback> */
|
||||||
class UserFeedbackRepository extends ServiceEntityRepository
|
class UserFeedbackRepository extends ServiceEntityRepository
|
||||||
{
|
{
|
||||||
public function __construct(ManagerRegistry $registry)
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ class DriftSimulationService
|
|||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** @return array{lat: float, lng: float}|null */
|
||||||
private function getCentroid(string $observationId): ?array
|
private function getCentroid(string $observationId): ?array
|
||||||
{
|
{
|
||||||
$row = $this->connection->fetchAssociative(
|
$row = $this->connection->fetchAssociative(
|
||||||
@@ -167,6 +168,7 @@ class DriftSimulationService
|
|||||||
/**
|
/**
|
||||||
* Reconstruit un MULTIPOLYGON depuis un nuage de points via PostGIS.
|
* 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
|
private function reconstructPolygon(array $points): ?string
|
||||||
{
|
{
|
||||||
if (count($points) < 3) {
|
if (count($points) < 3) {
|
||||||
@@ -196,6 +198,10 @@ class DriftSimulationService
|
|||||||
/**
|
/**
|
||||||
* Calcule le vecteur de dérive moyen (composantes lat/lng) sur une période.
|
* 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
|
private function avgDriftVector(array $windData, int $fromHour, int $toHour): array
|
||||||
{
|
{
|
||||||
$count = $toHour - $fromHour;
|
$count = $toHour - $fromHour;
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ class ImpactScoreService
|
|||||||
/**
|
/**
|
||||||
* Calcule et sauvegarde les scores pour tous les CoastalPoints
|
* Calcule et sauvegarde les scores pour tous les CoastalPoints
|
||||||
* à partir d'une observation et de ses forecasts.
|
* à partir d'une observation et de ses forecasts.
|
||||||
|
*
|
||||||
|
* @param array<int, SargassumForecast> $forecasts
|
||||||
*/
|
*/
|
||||||
public function computeForObservation(SargassumObservation $observation, array $forecasts): void
|
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).
|
* 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
|
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
|
private function computeScore(CoastalPoint $spot, string $obsId, ?SargassumForecast $forecastH6): array
|
||||||
{
|
{
|
||||||
$spotId = (string) $spot->getId();
|
$spotId = (string) $spot->getId();
|
||||||
@@ -194,13 +199,21 @@ class ImpactScoreService
|
|||||||
|
|
||||||
private function getPreviousDistance(string $spotId): ?float
|
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(
|
$row = $this->connection->fetchAssociative(
|
||||||
'SELECT distance_to_nearest_sargassum
|
'SELECT distance_to_nearest_sargassum
|
||||||
FROM impact_score
|
FROM impact_score
|
||||||
WHERE coastal_point_id = :spotId
|
WHERE coastal_point_id = :spotId
|
||||||
|
AND timestamp <= :before
|
||||||
|
AND timestamp >= :since
|
||||||
ORDER BY timestamp DESC
|
ORDER BY timestamp DESC
|
||||||
LIMIT 1',
|
LIMIT 1',
|
||||||
['spotId' => $spotId]
|
['spotId' => $spotId, 'before' => $before, 'since' => $since]
|
||||||
);
|
);
|
||||||
|
|
||||||
return ($row !== false && $row['distance_to_nearest_sargassum'] !== null)
|
return ($row !== false && $row['distance_to_nearest_sargassum'] !== null)
|
||||||
@@ -208,6 +221,7 @@ class ImpactScoreService
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param array{score: int, level: string, distance: float|null, density: float, trend: string} $data */
|
||||||
private function persistScore(CoastalPoint $spot, array $data): void
|
private function persistScore(CoastalPoint $spot, array $data): void
|
||||||
{
|
{
|
||||||
$s = new ImpactScore();
|
$s = new ImpactScore();
|
||||||
@@ -221,6 +235,7 @@ class ImpactScoreService
|
|||||||
$this->em->persist($s);
|
$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
|
private function loadLatestScore(string $spotId): ?array
|
||||||
{
|
{
|
||||||
$row = $this->connection->fetchAssociative(
|
$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
|
private function emptyScore(): array
|
||||||
{
|
{
|
||||||
return ['score' => 0, 'level' => 'low', 'distance' => null, 'density' => 0.0, 'trend' => 'stable'];
|
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;
|
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
|
private function findForecastByHorizon(array $forecasts, int $horizon): ?SargassumForecast
|
||||||
{
|
{
|
||||||
foreach ($forecasts as $f) {
|
foreach ($forecasts as $f) {
|
||||||
if ($f instanceof SargassumForecast && $f->getTimeHorizon() === $horizon) {
|
if ($f->getTimeHorizon() === $horizon) {
|
||||||
return $f;
|
return $f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class IngestionService
|
|||||||
* Tente l'ingestion Sentinel-2, puis Sentinel-3 OLCI en fallback si la
|
* 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.
|
* 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
|
public function ingestWithFallback(array $bbox, \DateTimeImmutable $date): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -41,6 +42,7 @@ class IngestionService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param array<int, float> $bbox */
|
||||||
public function ingest(
|
public function ingest(
|
||||||
array $bbox,
|
array $bbox,
|
||||||
\DateTimeImmutable $date,
|
\DateTimeImmutable $date,
|
||||||
@@ -177,6 +179,9 @@ class IngestionService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{type: string, coordinates: array<int, mixed>}|null
|
||||||
|
*/
|
||||||
private function buildMultiPolygon(string $geojsonPath): ?array
|
private function buildMultiPolygon(string $geojsonPath): ?array
|
||||||
{
|
{
|
||||||
if (!file_exists($geojsonPath)) {
|
if (!file_exists($geojsonPath)) {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class SentinelHubClient
|
|||||||
* Retourne la couverture nuageuse (%) de la meilleure scène disponible.
|
* Retourne la couverture nuageuse (%) de la meilleure scène disponible.
|
||||||
* collection : 'sentinel-2-l2a' (défaut) ou 'sentinel-3-olci'
|
* 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
|
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
|
// 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.
|
* calculé via evalscript AFAI/FAI, et le sauvegarde dans $outputPath.
|
||||||
* collection : 'sentinel-2-l2a' (défaut) ou 'sentinel-3-olci'
|
* 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(
|
public function downloadAfaiBinary(
|
||||||
array $bbox,
|
array $bbox,
|
||||||
\DateTimeImmutable $date,
|
\DateTimeImmutable $date,
|
||||||
|
|||||||
@@ -70,6 +70,15 @@
|
|||||||
"config/packages/nelmio_cors.yaml"
|
"config/packages/nelmio_cors.yaml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"phpstan/phpstan": {
|
||||||
|
"version": "2.1",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes-contrib",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767"
|
||||||
|
}
|
||||||
|
},
|
||||||
"symfony/console": {
|
"symfony/console": {
|
||||||
"version": "7.4",
|
"version": "7.4",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
|||||||
13
backend/tests/bootstrap_phpstan.php
Normal file
13
backend/tests/bootstrap_phpstan.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
use App\Kernel;
|
||||||
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
// Variables minimales pour booter le kernel sans vraie DB
|
||||||
|
$_ENV['DATABASE_URL'] = $_ENV['DATABASE_URL'] ?? 'postgresql://user:pass@localhost:5432/phpstan_dummy?serverVersion=16';
|
||||||
|
$_ENV['APP_SECRET'] = $_ENV['APP_SECRET'] ?? 'phpstan-dummy-secret';
|
||||||
|
$_SERVER['DATABASE_URL'] = $_ENV['DATABASE_URL'];
|
||||||
|
$_SERVER['APP_SECRET'] = $_ENV['APP_SECRET'];
|
||||||
|
|
||||||
|
$kernel = new Kernel('dev', true);
|
||||||
|
$kernel->boot();
|
||||||
|
return $kernel->getContainer()->get('doctrine')->getManager();
|
||||||
@@ -124,6 +124,25 @@
|
|||||||
.gauge--medium .spot-panel__gauge-fill { background: #eab308; }
|
.gauge--medium .spot-panel__gauge-fill { background: #eab308; }
|
||||||
.gauge--high .spot-panel__gauge-fill { background: #ef4444; }
|
.gauge--high .spot-panel__gauge-fill { background: #ef4444; }
|
||||||
|
|
||||||
|
/* Trend */
|
||||||
|
.spot-panel__trend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 9px 14px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px solid rgba(0,0,0,0.12);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.trend--bad { background: #fee2e2; color: #b91c1c; }
|
||||||
|
.trend--neutral { background: #f1f5f9; color: #475569; }
|
||||||
|
.trend--good { background: #dcfce7; color: #15803d; }
|
||||||
|
|
||||||
|
.spot-panel__trend-icon { font-size: 18px; line-height: 1; }
|
||||||
|
.spot-panel__trend-label { font-family: 'Fredoka', sans-serif; }
|
||||||
|
|
||||||
/* Details */
|
/* Details */
|
||||||
.spot-panel__details {
|
.spot-panel__details {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ import './SpotPanel.css';
|
|||||||
const LEVEL_LABEL = { low: 'OK', medium: 'Risque', high: 'Impact' };
|
const LEVEL_LABEL = { low: 'OK', medium: 'Risque', high: 'Impact' };
|
||||||
const TYPE_ICON = { beach: '🏖', port: '⚓', surf: '🏄', fishing: '🎣' };
|
const TYPE_ICON = { beach: '🏖', port: '⚓', surf: '🏄', fishing: '🎣' };
|
||||||
|
|
||||||
|
const TREND_CONFIG = {
|
||||||
|
increasing: { icon: '📈', label: 'Situation en dégradation', cls: 'trend--bad' },
|
||||||
|
stable: { icon: '➡️', label: 'Situation stable', cls: 'trend--neutral' },
|
||||||
|
decreasing: { icon: '📉', label: 'Amélioration en cours', cls: 'trend--good' },
|
||||||
|
};
|
||||||
|
|
||||||
const HORIZON_LABELS = { now: 'Maintenant', h6: '+6h', h12: '+12h', h24: '+24h', h48: '+48h' };
|
const HORIZON_LABELS = { now: 'Maintenant', h6: '+6h', h12: '+12h', h24: '+24h', h48: '+48h' };
|
||||||
|
|
||||||
export default function SpotPanel({ spot, activeStep, onClose }) {
|
export default function SpotPanel({ spot, activeStep, onClose }) {
|
||||||
@@ -82,13 +88,20 @@ export default function SpotPanel({ spot, activeStep, onClose }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{activeStep === 'now' && displayScore.trend && (() => {
|
||||||
|
const t = TREND_CONFIG[displayScore.trend] ?? TREND_CONFIG.stable;
|
||||||
|
return (
|
||||||
|
<div className={`spot-panel__trend ${t.cls}`}>
|
||||||
|
<span className="spot-panel__trend-icon">{t.icon}</span>
|
||||||
|
<span className="spot-panel__trend-label">{t.label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
|
||||||
<dl className="spot-panel__details">
|
<dl className="spot-panel__details">
|
||||||
{displayScore.distance != null && (
|
{displayScore.distance != null && (
|
||||||
<><dt>Distance sargasses</dt><dd>{displayScore.distance} km</dd></>
|
<><dt>Distance sargasses</dt><dd>{displayScore.distance} km</dd></>
|
||||||
)}
|
)}
|
||||||
{activeStep === 'now' && displayScore.trend && (
|
|
||||||
<><dt>Tendance</dt><dd>{displayScore.trend}</dd></>
|
|
||||||
)}
|
|
||||||
{activeStep === 'now' && displayScore.computedAt && (
|
{activeStep === 'now' && displayScore.computedAt && (
|
||||||
<><dt>Calculé le</dt>
|
<><dt>Calculé le</dt>
|
||||||
<dd>{new Date(displayScore.computedAt).toLocaleString('fr-FR')}</dd></>
|
<dd>{new Date(displayScore.computedAt).toLocaleString('fr-FR')}</dd></>
|
||||||
|
|||||||
Reference in New Issue
Block a user