up historique

This commit is contained in:
Tykayn 2025-06-21 11:28:31 +02:00 committed by tykayn
parent ad4170db14
commit c274fd6a63
12 changed files with 448 additions and 616 deletions

View file

@ -11,6 +11,7 @@ use App\Entity\Stats;
use App\Entity\StatsHistory;
use App\Service\Motocultrice;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use function uuid_create;
final class AdminController extends AbstractController
@ -234,102 +235,18 @@ final class AdminController extends AbstractController
public function calculer_stats(string $insee_code): Response
{
// Récupérer tous les commerces de la zone
$commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
$commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code, 'dead' => false]);
// Récupérer les stats existantes pour la zone
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
if(!$stats) {
// Si aucune stat n'existe, on en crée une vide pour éviter les erreurs, mais sans la sauvegarder
$stats = new Stats();
$stats->setZone($insee_code);
$stats->setName('Nouvelle zone non labourée');
}
$urls = $stats->getAllCTCUrlsMap();
// Calculer les statistiques
$calculatedStats = $this->motocultrice->calculateStats($commerces);
// Mettre à jour les stats pour la zone donnée
$stats->setPlacesCount($calculatedStats['places_count']);
$stats->setAvecHoraires($calculatedStats['counters']['avec_horaires']);
$stats->setAvecAdresse($calculatedStats['counters']['avec_adresse']);
$stats->setAvecSite($calculatedStats['counters']['avec_site']);
$stats->setAvecAccessibilite($calculatedStats['counters']['avec_accessibilite']);
$stats->setAvecNote($calculatedStats['counters']['avec_note']);
$stats->setCompletionPercent($calculatedStats['completion_percent']);
// Associer les stats à chaque commerce
foreach ($commerces as $commerce) {
$commerce->setStats($stats);
$this->entityManager->persist($commerce);
}
$this->entityManager->persist($stats);
$this->entityManager->flush();
$stats->computeCompletionPercent();
// Calculer les statistiques de fraîcheur des données OSM
$timestamps = [];
foreach ($stats->getPlaces() as $place) {
if ($place->getOsmDataDate()) {
$timestamps[] = $place->getOsmDataDate()->getTimestamp();
}
}
if (!empty($timestamps)) {
// Date la plus ancienne (min)
$minTimestamp = min($timestamps);
$stats->setOsmDataDateMin(new \DateTime('@' . $minTimestamp));
// Date la plus récente (max)
$maxTimestamp = max($timestamps);
$stats->setOsmDataDateMax(new \DateTime('@' . $maxTimestamp));
// Date moyenne
$avgTimestamp = array_sum($timestamps) / count($timestamps);
$stats->setOsmDataDateAvg(new \DateTime('@' . (int)$avgTimestamp));
}
if($stats->getDateCreated() == null) {
$stats->setDateCreated(new \DateTime());
}
$stats->setDateModified(new \DateTime());
// Créer un historique des statistiques
$statsHistory = new StatsHistory();
$statsHistory->setDate(new \DateTime())
->setStats($stats);
// Compter les Places avec email et SIRET
$placesWithEmail = 0;
$placesWithSiret = 0;
foreach ($stats->getPlaces() as $place) {
if ($place->getEmail() && $place->getEmail() !== '') {
$placesWithEmail++;
}
if ($place->getSiret() && $place->getSiret() !== '') {
$placesWithSiret++;
}
}
$statsHistory->setPlacesCount($stats->getPlaces()->count())
->setOpeningHoursCount($stats->getAvecHoraires())
->setAddressCount($stats->getAvecAdresse())
->setWebsiteCount($stats->getAvecSite())
->setSiretCount($placesWithSiret)
->setEmailsCount($placesWithEmail)
// ->setAccessibiliteCount($stats->getAvecAccessibilite())
// ->setNoteCount($stats->getAvecNote())
->setCompletionPercent($stats->getCompletionPercent())
->setStats($stats);
$this->entityManager->persist($statsHistory);
$this->entityManager->persist($stats);
$this->entityManager->flush();
$urls = $stats->getAllCTCUrlsMap();
$statsHistory = $this->entityManager->getRepository(StatsHistory::class)
->createQueryBuilder('sh')
->where('sh.stats = :stats')
@ -339,13 +256,54 @@ final class AdminController extends AbstractController
->getQuery()
->getResult();
/*
// La page de statistiques ne doit pas modifier les données, seulement les afficher.
// La mise à jour des statistiques se fait lors du labourage.
// Calculer les statistiques
$calculatedStats = $this->motocultrice->calculateStats($commerces);
// Mettre à jour les stats pour la zone donnée
$stats->setPlacesCount($calculatedStats['places_count']);
// ... (plus de setters) ...
$stats->setCompletionPercent($calculatedStats['completion_percent']);
// ... (boucle foreach sur commerces) ...
$stats->computeCompletionPercent();
$this->entityManager->persist($stats);
$this->entityManager->flush();
*/
// Données pour le graphique des modifications par trimestre
$modificationsByQuarter = [];
foreach ($stats->getPlaces() as $commerce) {
if ($commerce->getOsmDataDate()) {
$date = $commerce->getOsmDataDate();
$year = $date->format('Y');
$quarter = ceil($date->format('n') / 3);
$key = $year . '-Q' . $quarter;
if (!isset($modificationsByQuarter[$key])) {
$modificationsByQuarter[$key] = 0;
}
$modificationsByQuarter[$key]++;
}
}
ksort($modificationsByQuarter); // Trier par clé (année-trimestre)
$overpass_query = $this->motocultrice->get_query_places($insee_code);
$overpass_query_url = "https://overpass-turbo.eu/?Q=" . urlencode($overpass_query);
return $this->render('admin/stats.html.twig', [
'stats' => $stats,
'insee_code' => $insee_code,
'query_places' => $this->motocultrice->get_query_places($insee_code),
'counters' => $calculatedStats['counters'],
'commerces' => $commerces,
'urls' => $urls,
'query_places' => $overpass_query,
'overpass_query' => $overpass_query,
'overpass_query_url' => $overpass_query_url,
'modificationsByQuarter' => json_encode($modificationsByQuarter),
'maptiler_token' => $_ENV['MAPTILER_TOKEN'],
'mapbox_token' => $_ENV['MAPBOX_TOKEN'],
'statsHistory' => $statsHistory,
'CTC_urls' => $urls,
]);
@ -396,8 +354,9 @@ final class AdminController extends AbstractController
* récupérer les commerces de la zone selon le code INSEE, créer les nouveaux lieux, et mettre à jour les existants
*/
#[Route('/admin/labourer/{insee_code}', name: 'app_admin_labourer')]
public function labourer(string $insee_code, bool $updateExisting = true): Response
public function labourer(Request $request, string $insee_code, bool $updateExisting = true): Response
{
$deleteMissing = $request->query->getBoolean('deleteMissing', true);
// Vérifier si le code INSEE est valide (composé uniquement de chiffres)
if (!ctype_digit($insee_code) || $insee_code == 'undefined' || $insee_code == '') {
@ -503,12 +462,14 @@ final class AdminController extends AbstractController
}
}
// Supprimer les lieux qui ne sont plus dans la réponse Overpass
$db_places = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
foreach ($db_places as $db_place) {
if (!in_array($db_place->getOsmId(), $overpass_osm_ids)) {
$this->entityManager->remove($db_place);
$deletedCount++;
// Supprimer les lieux qui ne sont plus dans la réponse Overpass, si activé
if ($deleteMissing) {
$db_places = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
foreach ($db_places as $db_place) {
if (!in_array($db_place->getOsmId(), $overpass_osm_ids)) {
$this->entityManager->remove($db_place);
$deletedCount++;
}
}
}
@ -652,16 +613,35 @@ final class AdminController extends AbstractController
#[Route('/admin/delete_by_zone/{insee_code}', name: 'app_admin_delete_by_zone')]
public function delete_by_zone(string $insee_code): Response
{
$commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
foreach ($commerces as $commerce) {
$this->entityManager->remove($commerce);
}
$this->entityManager->remove($stats);
$this->entityManager->flush();
$this->addFlash('success', 'Tous les commerces de la zone '.$insee_code.' ont été supprimés avec succès de OSM Mes commerces, mais pas dans OpenStreetMap.');
if (!$stats) {
$this->addFlash('error', 'Aucune statistique trouvée pour la zone ' . $insee_code);
return $this->redirectToRoute('app_public_dashboard');
}
try {
// 1. Supprimer tous les StatsHistory associés
foreach ($stats->getStatsHistories() as $history) {
$this->entityManager->remove($history);
}
// 2. Supprimer tous les Places associées
foreach ($stats->getPlaces() as $place) {
$this->entityManager->remove($place);
}
// 3. Supprimer l'objet Stats lui-même
$this->entityManager->remove($stats);
// 4. Appliquer les changements à la base de données
$this->entityManager->flush();
$this->addFlash('success', 'La zone ' . $insee_code . ' et toutes les données associées ont été supprimées avec succès.');
} catch (\Exception $e) {
$this->addFlash('error', 'Une erreur est survenue lors de la suppression de la zone ' . $insee_code . ': ' . $e->getMessage());
}
return $this->redirectToRoute('app_public_dashboard');
}

View file

@ -173,17 +173,28 @@ class PublicController extends AbstractController
public function dashboard(): Response
{
$stats = $this->entityManager->getRepository(Stats::class)->findAll();
$stats_repo = $this->entityManager->getRepository(Stats::class)->findAll();
$stats_for_chart = [];
foreach ($stats_repo as $stat) {
if ($stat->getPlacesCount() > 0 && $stat->getName() !== null && $stat->getPopulation() > 0) {
$stats_for_chart[] = [
'name' => $stat->getName(),
'placesCount' => $stat->getPlacesCount(),
'completionPercent' => $stat->getCompletionPercent(),
'population' => $stat->getPopulation(),
];
}
}
// Compter le nombre total de lieux
$placesCount = $this->entityManager->getRepository(Place::class)->count([]);
return $this->render('public/dashboard.html.twig', [
'controller_name' => 'PublicController',
'mapbox_token' => $_ENV['MAPBOX_TOKEN'],
'maptiler_token' => $_ENV['MAPTILER_TOKEN'],
'stats' => $stats,
'mapbox_token' => $_ENV['MAPBOX_TOKEN'] ?? null,
'maptiler_token' => $_ENV['MAPTILER_TOKEN'] ?? null,
'stats' => json_encode($stats_for_chart),
'stats_list' => $stats_repo,
'places_count' => $placesCount,
]);
}

View file

@ -3,8 +3,7 @@
namespace App\Service;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityManagerInterface;
class Motocultrice
{
private $overpassApiUrl = 'https://overpass-api.de/api/interpreter';