'\\d+'])] public function deleteFollowups(string $insee_code, EntityManagerInterface $em): Response { $stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); if (!$stats) { $this->addFlash('error', 'Aucune stats trouvée pour ce code INSEE.'); return $this->redirectToRoute('app_admin'); } $followups = $stats->getCityFollowUps(); foreach ($followups as $fu) { $em->remove($fu); } $em->flush(); $this->addFlash('success', 'Tous les suivis ont été supprimés pour cette ville.'); return $this->redirectToRoute('admin_followup_graph', ['insee_code' => $insee_code]); } #[Route('/admin/followup/{insee_code}', name: 'admin_followup', requirements: ['insee_code' => '\\d+'])] public function followup( string $insee_code, Motocultrice $motocultrice, EntityManagerInterface $em ): Response { // Récupérer la stats de la ville $stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); if (!$stats) { $this->addFlash('error', 'Aucune stats trouvée pour ce code INSEE.'); return $this->redirectToRoute('app_admin'); } // Ne plus supprimer les anciens suivis ! // $followups = $stats->getCityFollowUps(); // foreach ($followups as $fu) { // $em->remove($fu); // } // $em->flush(); // Récupérer les objets OSM $elements = $motocultrice->followUpCity($insee_code); // Séparer les objets par type $types = [ 'fire_hydrant' => [ 'label' => 'Bornes incendie', 'objects' => array_filter($elements, fn($el) => ($el['tags']['emergency'] ?? null) === 'fire_hydrant') ], 'charging_station' => [ 'label' => 'Bornes de recharge', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'charging_station') ], 'toilets' => [ 'label' => 'Toilettes publiques', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'toilets') ], 'bus_stop' => [ 'label' => 'Arrêts de bus', 'objects' => array_filter($elements, fn($el) => ($el['tags']['highway'] ?? null) === 'bus_stop') ], 'defibrillator' => [ 'label' => 'Défibrillateurs', 'objects' => array_filter($elements, fn($el) => ($el['tags']['emergency'] ?? null) === 'defibrillator') ], 'camera' => [ 'label' => 'Caméras de surveillance', 'objects' => array_filter($elements, fn($el) => ($el['tags']['man_made'] ?? null) === 'surveillance') ], 'recycling' => [ 'label' => 'Points de recyclage', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'recycling') ], 'substation' => [ 'label' => 'Sous-stations électriques', 'objects' => array_filter($elements, fn($el) => ($el['tags']['power'] ?? null) === 'substation') ], 'laboratory' => [ 'label' => 'Laboratoires d\'analyse', 'objects' => array_filter($elements, fn($el) => ($el['tags']['healthcare'] ?? null) === 'laboratory') ], 'school' => [ 'label' => 'Écoles', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'school') ], 'police' => [ 'label' => 'Commissariats', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'police') ], 'healthcare' => [ 'label' => 'Lieux de santé', 'objects' => array_filter($elements, function($el) { return isset($el['tags']['healthcare']) || ($el['tags']['amenity'] ?? null) === 'doctors' || ($el['tags']['amenity'] ?? null) === 'pharmacy' || ($el['tags']['amenity'] ?? null) === 'hospital' || ($el['tags']['amenity'] ?? null) === 'clinic' || ($el['tags']['amenity'] ?? null) === 'social_facility'; }) ], ]; $now = new \DateTime(); foreach ($types as $type => $data) { // Suivi du nombre $followupCount = new CityFollowUp(); $followupCount->setName($type . '_count') ->setMeasure(count($data['objects'])) ->setDate($now) ->setStats($stats); $em->persist($followupCount); // Suivi de la complétion personnalisé (exemples) $completed = []; if ($type === 'fire_hydrant') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['ref'] ?? null); }); } elseif ($type === 'charging_station') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['charging_station:output'] ?? null) && !empty($el['tags']['capacity'] ?? null); }); } elseif ($type === 'toilets') { $completed = array_filter($data['objects'], function($el) { return ($el['tags']['wheelchair'] ?? null) === 'yes'; }); } elseif ($type === 'bus_stop') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['shelter'] ?? null); }); } elseif ($type === 'defibrillator') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['indoor'] ?? null); }); } elseif ($type === 'camera') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['surveillance:type'] ?? null); }); } elseif ($type === 'recycling') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['recycling_type'] ?? null); }); } elseif ($type === 'substation') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['substation'] ?? null); }); } elseif ($type === 'laboratory') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['website'] ?? null) || !empty($el['tags']['contact:website'] ?? null); }); } elseif ($type === 'school') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['ref:UAI'] ?? null) && !empty($el['tags']['isced:level'] ?? null) && !empty($el['tags']['school:FR'] ?? null); }); } elseif ($type === 'police') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['phone'] ?? null) || !empty($el['tags']['website'] ?? null); }); } elseif ($type === 'healthcare') { $completed = array_filter($data['objects'], function($el) { $tags = $el['tags'] ?? []; return !empty($tags['name'] ?? null) || !empty($tags['contact:phone'] ?? null) || !empty($tags['phone'] ?? null) || !empty($tags['email'] ?? null) || !empty($tags['contact:email'] ?? null); }); } $completion = count($data['objects']) > 0 ? round(count($completed) / count($data['objects']) * 100) : 0; $followupCompletion = new CityFollowUp(); $followupCompletion->setName($type . '_completion') ->setMeasure($completion) ->setDate($now) ->setStats($stats); $em->persist($followupCompletion); } $em->flush(); $this->addFlash('success', 'Suivi enregistré pour la ville.'); return $this->redirectToRoute('admin_followup_graph', ['insee_code' => $insee_code]); } #[Route('/admin/followup/{insee_code}/graph', name: 'admin_followup_graph', requirements: ['insee_code' => '\\d+'])] public function followupGraph( string $insee_code, EntityManagerInterface $em, Motocultrice $motocultrice ) { $stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); if (!$stats) { $this->addFlash('error', 'Aucune stats trouvée pour ce code INSEE.'); return $this->redirectToRoute('app_admin'); } $followups = $stats->getCityFollowUps(); if ($followups->isEmpty()) { // Générer les followup comme dans l'action followup // (ne pas supprimer les anciens, car il n'y en a pas) $elements = $motocultrice->followUpCity($insee_code); // Séparer les objets par type $types = [ 'fire_hydrant' => [ 'label' => 'Bornes incendie', 'objects' => array_filter($elements, fn($el) => ($el['tags']['emergency'] ?? null) === 'fire_hydrant') ], 'charging_station' => [ 'label' => 'Bornes de recharge', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'charging_station') ], 'toilets' => [ 'label' => 'Toilettes publiques', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'toilets') ], 'bus_stop' => [ 'label' => 'Arrêts de bus', 'objects' => array_filter($elements, fn($el) => ($el['tags']['highway'] ?? null) === 'bus_stop') ], 'defibrillator' => [ 'label' => 'Défibrillateurs', 'objects' => array_filter($elements, fn($el) => ($el['tags']['emergency'] ?? null) === 'defibrillator') ], 'camera' => [ 'label' => 'Caméras de surveillance', 'objects' => array_filter($elements, fn($el) => ($el['tags']['man_made'] ?? null) === 'surveillance') ], 'recycling' => [ 'label' => 'Points de recyclage', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'recycling') ], 'substation' => [ 'label' => 'Sous-stations électriques', 'objects' => array_filter($elements, fn($el) => ($el['tags']['power'] ?? null) === 'substation') ], 'laboratory' => [ 'label' => 'Laboratoires d\'analyse', 'objects' => array_filter($elements, fn($el) => ($el['tags']['healthcare'] ?? null) === 'laboratory') ], 'school' => [ 'label' => 'Écoles', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'school') ], 'police' => [ 'label' => 'Commissariats', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'police') ], 'healthcare' => [ 'label' => 'Lieux de santé', 'objects' => array_filter($elements, function($el) { return isset($el['tags']['healthcare']) || ($el['tags']['amenity'] ?? null) === 'doctors' || ($el['tags']['amenity'] ?? null) === 'pharmacy' || ($el['tags']['amenity'] ?? null) === 'hospital' || ($el['tags']['amenity'] ?? null) === 'clinic' || ($el['tags']['amenity'] ?? null) === 'social_facility'; }) ], ]; $now = new \DateTime(); foreach ($types as $type => $data) { // Suivi du nombre $followupCount = new CityFollowUp(); $followupCount->setName($type . '_count') ->setMeasure(count($data['objects'])) ->setDate($now) ->setStats($stats); $em->persist($followupCount); // Suivi de la complétion personnalisé (exemples) $completed = []; if ($type === 'fire_hydrant') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['ref'] ?? null); }); } elseif ($type === 'charging_station') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['charging_station:output'] ?? null) && !empty($el['tags']['capacity'] ?? null); }); } elseif ($type === 'toilets') { $completed = array_filter($data['objects'], function($el) { return ($el['tags']['wheelchair'] ?? null) === 'yes'; }); } elseif ($type === 'bus_stop') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['shelter'] ?? null); }); $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['indoor'] ?? null); }); } elseif ($type === 'camera') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['surveillance:type'] ?? null); }); } elseif ($type === 'recycling') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['recycling_type'] ?? null); }); } elseif ($type === 'substation') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['substation'] ?? null); }); } elseif ($type === 'laboratory') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['website'] ?? null) || !empty($el['tags']['contact:website'] ?? null); }); } elseif ($type === 'school') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['ref:UAI'] ?? null) && !empty($el['tags']['isced:level'] ?? null) && !empty($el['tags']['school:FR'] ?? null); }); } elseif ($type === 'police') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['phone'] ?? null) || !empty($el['tags']['website'] ?? null); }); } elseif ($type === 'healthcare') { $completed = array_filter($data['objects'], function($el) { $tags = $el['tags'] ?? []; return !empty($tags['name'] ?? null) || !empty($tags['contact:phone'] ?? null) || !empty($tags['phone'] ?? null) || !empty($tags['email'] ?? null) || !empty($tags['contact:email'] ?? null); }); } $completion = count($data['objects']) > 0 ? round(count($completed) / count($data['objects']) * 100) : 0; $followupCompletion = new CityFollowUp(); $followupCompletion->setName($type . '_completion') ->setMeasure($completion) ->setDate($now) ->setStats($stats); $em->persist($followupCompletion); } $em->flush(); // Recharger les followups $followups = $stats->getCityFollowUps(); } $followups = $followups->toArray(); usort($followups, fn($a, $b) => $a->getDate() <=> $b->getDate()); // Grouper par type $series = []; foreach ($followups as $fu) { $series[$fu->getName()][] = [ 'date' => $fu->getDate()->format('c'), 'value' => $fu->getMeasure(), 'name' => $fu->getName(), ]; } return $this->render('admin/followup_graph.html.twig', [ 'stats' => $stats, 'series' => $series ]); } #[Route('/admin/followup/all', name: 'admin_followup_all')] public function followupAll( EntityManagerInterface $em, Motocultrice $motocultrice ) { $statsList = $em->getRepository(Stats::class)->findAll(); $now = new \DateTime(); foreach ($statsList as $stats) { // Ne plus supprimer les anciens suivis ! // foreach ($stats->getCityFollowUps() as $fu) { // $em->remove($fu); // } // Générer les followups OSM $insee_code = $stats->getZone(); $elements = $motocultrice->followUpCity($insee_code); $types = [ 'fire_hydrant' => [ 'label' => 'Bornes incendie', 'objects' => array_filter($elements, fn($el) => ($el['tags']['emergency'] ?? null) === 'fire_hydrant') ], 'charging_station' => [ 'label' => 'Bornes de recharge', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'charging_station') ], 'toilets' => [ 'label' => 'Toilettes publiques', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'toilets') ], 'bus_stop' => [ 'label' => 'Arrêts de bus', 'objects' => array_filter($elements, fn($el) => ($el['tags']['highway'] ?? null) === 'bus_stop') ], 'defibrillator' => [ 'label' => 'Défibrillateurs', 'objects' => array_filter($elements, fn($el) => ($el['tags']['emergency'] ?? null) === 'defibrillator') ], 'camera' => [ 'label' => 'Caméras de surveillance', 'objects' => array_filter($elements, fn($el) => ($el['tags']['man_made'] ?? null) === 'surveillance') ], 'recycling' => [ 'label' => 'Points de recyclage', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'recycling') ], 'substation' => [ 'label' => 'Sous-stations électriques', 'objects' => array_filter($elements, fn($el) => ($el['tags']['power'] ?? null) === 'substation') ], 'laboratory' => [ 'label' => 'Laboratoires d\'analyse', 'objects' => array_filter($elements, fn($el) => ($el['tags']['healthcare'] ?? null) === 'laboratory') ], 'school' => [ 'label' => 'Écoles', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'school') ], 'police' => [ 'label' => 'Commissariats', 'objects' => array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'police') ], 'healthcare' => [ 'label' => 'Lieux de santé', 'objects' => array_filter($elements, function($el) { return isset($el['tags']['healthcare']) || ($el['tags']['amenity'] ?? null) === 'doctors' || ($el['tags']['amenity'] ?? null) === 'pharmacy' || ($el['tags']['amenity'] ?? null) === 'hospital' || ($el['tags']['amenity'] ?? null) === 'clinic' || ($el['tags']['amenity'] ?? null) === 'social_facility'; }) ], ]; foreach ($types as $type => $data) { // Suivi du nombre $followupCount = new CityFollowUp(); $followupCount->setName($type . '_count') ->setMeasure(count($data['objects'])) ->setDate($now) ->setStats($stats); $em->persist($followupCount); // Suivi de la complétion personnalisé (exemples) $completed = []; if ($type === 'fire_hydrant') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['ref'] ?? null); }); } elseif ($type === 'charging_station') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['charging_station:output'] ?? null) && !empty($el['tags']['capacity'] ?? null); }); } elseif ($type === 'toilets') { $completed = array_filter($data['objects'], function($el) { return ($el['tags']['wheelchair'] ?? null) === 'yes'; }); } elseif ($type === 'bus_stop') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['shelter'] ?? null); }); $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['indoor'] ?? null); }); } elseif ($type === 'camera') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['surveillance:type'] ?? null); }); } elseif ($type === 'recycling') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['recycling_type'] ?? null); }); } elseif ($type === 'substation') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['substation'] ?? null); }); } elseif ($type === 'laboratory') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['website'] ?? null) || !empty($el['tags']['contact:website'] ?? null); }); } elseif ($type === 'school') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['ref:UAI'] ?? null) && !empty($el['tags']['isced:level'] ?? null) && !empty($el['tags']['school:FR'] ?? null); }); } elseif ($type === 'police') { $completed = array_filter($data['objects'], function($el) { return !empty($el['tags']['phone'] ?? null) || !empty($el['tags']['website'] ?? null); }); } elseif ($type === 'healthcare') { $completed = array_filter($data['objects'], function($el) { $tags = $el['tags'] ?? []; return !empty($tags['name'] ?? null) || !empty($tags['contact:phone'] ?? null) || !empty($tags['phone'] ?? null) || !empty($tags['email'] ?? null) || !empty($tags['contact:email'] ?? null); }); } $completion = count($data['objects']) > 0 ? round(count($completed) / count($data['objects']) * 100) : 0; $followupCompletion = new CityFollowUp(); $followupCompletion->setName($type . '_completion') ->setMeasure($completion) ->setDate($now) ->setStats($stats); $em->persist($followupCompletion); } // Ajout du suivi sur le nombre de Places $followupPlaces = new CityFollowUp(); $followupPlaces->setName('places_count') ->setMeasure($stats->getPlacesCount() ?? 0) ->setDate($now) ->setStats($stats); $em->persist($followupPlaces); // Ajout du suivi sur la complétion moyenne $followupCompletion = new CityFollowUp(); $followupCompletion->setName('places_completion') ->setMeasure($stats->getCompletionPercent() ?? 0) ->setDate($now) ->setStats($stats); $em->persist($followupCompletion); } $em->flush(); $this->addFlash('success', 'Suivi généré pour toutes les villes.'); return $this->redirectToRoute('app_admin'); } #[Route('/admin/followup/global', name: 'admin_followup_global')] public function followupGlobal(EntityManagerInterface $em) { // Récupérer ou créer l'objet Stats global $statsGlobal = $em->getRepository(Stats::class)->findOneBy(['zone' => '00000']); if (!$statsGlobal) { $statsGlobal = new Stats(); $statsGlobal->setZone('00000'); $statsGlobal->setName('toutes les villes'); $em->persist($statsGlobal); $em->flush(); } $now = new \DateTime(); $themes = [ 'fire_hydrant', 'charging_station', 'toilets', 'bus_stop', 'defibrillator', 'camera', 'recycling', 'substation', 'laboratory', 'school', 'police', 'healthcare', 'places' ]; $allStats = $em->getRepository(Stats::class)->findAll(); // Exclure l'objet global $allStats = array_filter($allStats, fn($s) => $s->getZone() !== '00000'); $cityCount = count($allStats); $globalCompletionSum = 0; $globalCompletionCount = 0; // Pour chaque thème, somme des counts et moyenne des completions foreach ($themes as $theme) { $sumCount = 0; $sumCompletion = 0; $nbCompletion = 0; foreach ($allStats as $stats) { $latestCount = null; $latestCompletion = null; foreach ($stats->getCityFollowUps() as $fu) { if ($fu->getName() === $theme . '_count') { if ($latestCount === null || $fu->getDate() > $latestCount->getDate()) { $latestCount = $fu; } } if ($fu->getName() === $theme . '_completion') { if ($latestCompletion === null || $fu->getDate() > $latestCompletion->getDate()) { $latestCompletion = $fu; } } } if ($latestCount) $sumCount += $latestCount->getMeasure(); if ($latestCompletion) { $sumCompletion += $latestCompletion->getMeasure(); $nbCompletion++; } } // Ajout du CityFollowUp global pour le count $fuCount = new CityFollowUp(); $fuCount->setName($theme . '_count') ->setMeasure($sumCount) ->setDate($now) ->setStats($statsGlobal); $em->persist($fuCount); // Ajout du CityFollowUp global pour la complétion $completionAvg = $nbCompletion > 0 ? round($sumCompletion / $nbCompletion, 1) : 0; $fuCompletion = new CityFollowUp(); $fuCompletion->setName($theme . '_completion') ->setMeasure($completionAvg) ->setDate($now) ->setStats($statsGlobal); $em->persist($fuCompletion); // Pour la complétion globale moyenne if ($theme !== 'places' && $nbCompletion > 0) { $globalCompletionSum += $completionAvg; $globalCompletionCount++; } } // Suivi du nombre total de lieux (places_count) $sumPlaces = 0; foreach ($allStats as $stats) { $sumPlaces += $stats->getPlacesCount() ?? 0; } $fuPlaces = new CityFollowUp(); $fuPlaces->setName('places_count') ->setMeasure($sumPlaces) ->setDate($now) ->setStats($statsGlobal); $em->persist($fuPlaces); // Suivi de la complétion globale moyenne $globalCompletionAvg = $globalCompletionCount > 0 ? round($globalCompletionSum / $globalCompletionCount, 1) : 0; $fuGlobalCompletion = new CityFollowUp(); $fuGlobalCompletion->setName('global_completion_average') ->setMeasure($globalCompletionAvg) ->setDate($now) ->setStats($statsGlobal); $em->persist($fuGlobalCompletion); // Suivi du nombre de villes $fuCityCount = new CityFollowUp(); $fuCityCount->setName('city_count') ->setMeasure($cityCount) ->setDate($now) ->setStats($statsGlobal); $em->persist($fuCityCount); $em->flush(); $this->addFlash('success', 'Suivi global généré pour toutes les villes.'); return $this->redirectToRoute('admin_followup_global_graph'); } #[Route('/admin/followup/global/graph', name: 'admin_followup_global_graph')] public function followupGlobalGraph(EntityManagerInterface $em) { $stats = $em->getRepository(Stats::class)->findOneBy(['zone' => '00000']); if (!$stats) { $this->addFlash('error', 'Aucun suivi global trouvé.'); return $this->redirectToRoute('app_admin'); } $followups = $stats->getCityFollowUps(); $followups = $followups->toArray(); usort($followups, fn($a, $b) => $a->getDate() <=> $b->getDate()); // Grouper par type $series = []; foreach ($followups as $fu) { $series[$fu->getName()][] = [ 'date' => $fu->getDate()->format('c'), 'value' => $fu->getMeasure(), 'name' => $fu->getName(), ]; } return $this->render('admin/followup_global_graph.html.twig', [ 'stats' => $stats, 'series' => $series ]); } }