compute stats for completion by zone, have base tags, split categories

This commit is contained in:
Tykayn 2025-05-26 23:51:46 +02:00 committed by tykayn
parent f15fec6d18
commit f69b7824af
16 changed files with 1257 additions and 118 deletions

View file

@ -6,7 +6,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use App\Entity\Place;
use App\Entity\Stats;
use App\Service\Motocultrice;
use Doctrine\ORM\EntityManagerInterface;
use function uuid_create;
@ -29,6 +29,48 @@ final class AdminController extends AbstractController
]);
}
#[Route('/admin/stats/{zip_code}', name: 'app_admin_stats')]
public function calculer_stats(string $zip_code): Response
{
// Récupérer tous les commerces de la zone
$commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $zip_code]);
// Récupérer les stats existantes pour la zone
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $zip_code]);
if(!$stats) {
$stats = new Stats();
$stats->setZone($zip_code);
}
// 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();
return $this->render('admin/stats.html.twig', [
'stats' => $stats,
'zip_code' => $zip_code,
'counters' => $calculatedStats['counters']
]);
}
#[Route('/admin/labourer/{zip_code}', name: 'app_admin_labourer')]
public function labourer_zone(string $zip_code): Response
{
@ -39,10 +81,71 @@ final class AdminController extends AbstractController
// Récupérer les commerces existants dans la base de données pour cette zone
$commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $zip_code]);
// Récupérer ou créer les stats pour cette zone
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $zip_code]);
if (!$stats) {
$stats = new Stats();
$stats->setZone($zip_code);
// for commerce, set stats
foreach ($commerces as $commerce) {
$commerce->setStats($stats);
$this->entityManager->persist($commerce);
$stats->addPlace($commerce);
}
// rebuild et persist
$stats->computeCompletionPercent();
$this->entityManager->persist($stats);
$this->entityManager->flush();
}
// Initialiser les compteurs
$counters = [
'avec_horaires' => 0,
'avec_adresse' => 0,
'avec_site' => 0,
'avec_accessibilite' => 0,
'avec_note' => 0
];
// Compter les différents critères pour chaque commerce
foreach ($commerces as $commerce) {
if ($commerce->hasOpeningHours()) {
$counters['avec_horaires']++;
}
if ($commerce->hasAddress()) {
$counters['avec_adresse']++;
}
if ($commerce->hasWebsite()) {
$counters['avec_site']++;
}
if ($commerce->hasWheelchair()) {
$counters['avec_accessibilite']++;
}
if ($commerce->hasNote()) {
$counters['avec_note']++;
}
$commerce->setStats($stats);
}
// Mettre à jour les statistiques
$stats->setPlacesCount(count($commerces));
$stats->setAvecHoraires($counters['avec_horaires']);
$stats->setAvecAdresse($counters['avec_adresse']);
$stats->setAvecSite($counters['avec_site']);
$stats->setAvecAccessibilite($counters['avec_accessibilite']);
$stats->setAvecNote($counters['avec_note']);
$stats->computeCompletionPercent();
$this->entityManager->persist($stats);
$this->entityManager->flush();
$osm_object_ids = [];
if ($commerces) {
if ($commerces) {
// Extraire les osm_object_ids des commerces existants
$osm_object_ids = array_map(function($commerce) {
return $commerce->getOsmId();
@ -53,7 +156,7 @@ final class AdminController extends AbstractController
$results = array_filter($results, function($commerce) use ($osm_object_ids) {
return !in_array($commerce['id'], $osm_object_ids);
});
// on crée un commerce pour chaque résultat qui reste
foreach ($results as $result) {
$commerce = new Place();
@ -65,11 +168,18 @@ final class AdminController extends AbstractController
->setUuidForUrl($this->motocultrice->uuid_create())
->setOptedOut(false)
->setDead(false)
->setNote($result['note'] ?? null)
->setModifiedDate(new \DateTime())
->setAskedHumainsSupport(false)
->setLastContactAttemptDate(null)
->setStats(null);
->setStats(null)
->setNote($result['tags'] && isset($result['tags']['note']) ? isset($result['tags']['note']) : null)
->setHasOpeningHours($result['tags'] && isset($result['tags']['opening_hours']) ? isset($result['tags']['opening_hours']) : null)
->setHasAddress(($result['tags'] && isset($result['tags']['address']) || $result['tags'] && isset($result['tags']['contact:address'])) ? isset($result['tags']['address']) : null)
->setHasWebsite($result['tags'] && isset($result['tags']['website']) ? $result['tags']['website'] : null)
->setHasWheelchair($result['tags'] && isset($result['tags']['wheelchair']) ? $result['tags']['wheelchair'] : null)
->setHasNote($result['tags'] && isset($result['tags']['note']) ? $result['tags']['note'] : null)
;
$this->entityManager->persist($commerce);
}
@ -77,7 +187,99 @@ final class AdminController extends AbstractController
return $this->render('admin/labourage_results.html.twig', [
'results' => $results,
'commerces' => $commerces,
'zone' => $zip_code,
]);
}
#[Route('/admin/delete/{id}', name: 'app_admin_delete')]
public function delete(int $id): Response
{
$commerce = $this->entityManager->getRepository(Place::class)->find($id);
$name = $commerce->getName();
$this->entityManager->remove($commerce);
$this->entityManager->flush();
$this->addFlash('success', 'Le lieu '.$name.' a été supprimé avec succès de OSM Mes commerces, mais pas dans OpenStreetMap.');
return $this->redirectToRoute('app_admin_dashboard');
}
#[Route('/admin/delete_by_zone/{zip_code}', name: 'app_admin_delete_by_zone')]
public function delete_by_zone(string $zip_code): Response
{
$commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $zip_code]);
foreach ($commerces as $commerce) {
$this->entityManager->remove($commerce);
}
$this->entityManager->flush();
$this->addFlash('success', 'Tous les commerces de la zone '.$zip_code.' ont été supprimés avec succès de OSM Mes commerces, mais pas dans OpenStreetMap.');
return $this->redirectToRoute('app_admin_dashboard');
}
#[Route('/admin/export', name: 'app_admin_export')]
public function export(): Response
{
$places = $this->entityManager->getRepository(Place::class)->findAll();
$csvData = [];
$csvData[] = [
'Nom',
'Email',
'Code postal',
'ID OSM',
'Type OSM',
'Date de modification',
'Date dernier contact',
'Note',
'Désabonné',
'Inactif',
'Support humain demandé',
'A des horaires',
'A une adresse',
'A un site web',
'A accessibilité',
'A une note'
];
foreach ($places as $place) {
$csvData[] = [
$place->getName(),
$place->getEmail(),
$place->getZipCode(),
$place->getOsmId(),
$place->getOsmKind(),
$place->getModifiedDate() ? $place->getModifiedDate()->format('Y-m-d H:i:s') : '',
$place->getLastContactAttemptDate() ? $place->getLastContactAttemptDate()->format('Y-m-d H:i:s') : '',
$place->getNote(),
$place->isOptedOut() ? 'Oui' : 'Non',
$place->isDead() ? 'Oui' : 'Non',
$place->isAskedHumainsSupport() ? 'Oui' : 'Non',
$place->hasOpeningHours() ? 'Oui' : 'Non',
$place->hasAddress() ? 'Oui' : 'Non',
$place->hasWebsite() ? 'Oui' : 'Non',
$place->hasWheelchair() ? 'Oui' : 'Non',
$place->hasNote() ? 'Oui' : 'Non'
];
}
$response = new Response();
$response->headers->set('Content-Type', 'text/csv');
$response->headers->set('Content-Disposition', 'attachment; filename="export_places.csv"');
$handle = fopen('php://temp', 'r+');
foreach ($csvData as $row) {
fputcsv($handle, $row, ';');
}
rewind($handle);
$response->setContent(stream_get_contents($handle));
fclose($handle);
return $response;
}
}

View file

@ -40,12 +40,31 @@ class PublicController extends AbstractController
{
$place = $this->entityManager->getRepository(Place::class)->findOneBy(['uuid_for_url' => $uuid]);
if (!$place) {
$this->addFlash('warning', 'Ce lien de modification n\'existe pas.');
return $this->redirectToRoute('app_public_index');
}
$commerce = $this->motocultrice->get_osm_object_data($place->getOsmKind(), $place->getOsmId());
if ($place->getOsmKind() === 'relation') {
$this->addFlash('warning', 'Les objets OSM de type "relation" ne sont pas gérés dans cet outil.');
return $this->redirectToRoute('app_public_index');
}
// récupérer les tags de base
$base_tags = $this->motocultrice->base_tags;
$base_tags = array_fill_keys($base_tags, '');
$commerce_overpass = $this->motocultrice->get_osm_object_data($place->getOsmKind(), $place->getOsmId());
// Fusionner les tags de base avec les tags existants
$commerce_overpass['tags_converted'] = array_merge($base_tags, $commerce_overpass['tags_converted']);
// Trier les tags par ordre alphabétique des clés
ksort($commerce_overpass['tags_converted']);
return $this->render('public/edit.html.twig', [
'commerce' => $commerce,
'commerce_overpass' => $commerce_overpass,
'name' => $name,
'commerce' => $place,
'osm_kind' => $place->getOsmKind(),
"mapbox_token" => $_ENV['MAPBOX_TOKEN'],
"maptiler_token" => $_ENV['MAPTILER_TOKEN'],
@ -57,7 +76,8 @@ class PublicController extends AbstractController
{
// get stats
$stats = $this->entityManager->getRepository(Stats::class)->findAll();
$places = $this->entityManager->getRepository(Place::class)->findAll();
$places = $this->entityManager->getRepository(Place::class)->findBy([], ['zip_code' => 'ASC', 'name' => 'ASC']);
return $this->render('public/dashboard.html.twig', [
'controller_name' => 'PublicController',
'stats' => $stats,
@ -108,6 +128,9 @@ class PublicController extends AbstractController
// Récupérer le token OSM depuis les variables d'environnement
$osm_api_token = $_ENV['APP_OSM_BEARER'];
$exception = false;
$exception_message = "";
try {
$client = new Client();
@ -186,6 +209,8 @@ class PublicController extends AbstractController
}
} catch (\Exception $e) {
$status = "Erreur lors de la communication avec l'API OSM: " . $e->getMessage();
$exception = true;
$exception_message = $e->getMessage();
// Debug de la réponse en cas d'erreur
if (method_exists($e, 'getResponse')) {
var_dump($e->getResponse()->getBody()->getContents());
@ -199,6 +224,8 @@ class PublicController extends AbstractController
'controller_name' => 'PublicController',
'commerce' => $commerce,
'status' => $status,
'exception' => $exception,
'exception_message' => $exception_message,
'mapbox_token' => $_ENV['MAPBOX_TOKEN'],
'maptiler_token' => $_ENV['MAPTILER_TOKEN'],
]);