critères de followup plus amples

This commit is contained in:
Tykayn 2025-07-05 11:48:56 +02:00 committed by tykayn
parent b5b2880637
commit a5cd69961f
3 changed files with 250 additions and 85 deletions

View file

@ -0,0 +1,109 @@
<?php
namespace App\Command;
use App\Entity\Stats;
use App\Service\FollowUpService;
use App\Service\Motocultrice;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'app:regenerate-followups',
description: 'Régénère les followups pour une ville spécifique avec les nouveaux critères',
)]
class RegenerateFollowupsCommand extends Command
{
public function __construct(
private EntityManagerInterface $entityManager,
private FollowUpService $followUpService,
private Motocultrice $motocultrice
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addArgument('insee_code', InputArgument::REQUIRED, 'Code INSEE de la ville')
->addOption('delete-existing', 'd', InputOption::VALUE_NONE, 'Supprimer les followups existants avant de régénérer')
->addOption('disable-cleanup', 'c', InputOption::VALUE_NONE, 'Désactiver le nettoyage des followups redondants')
->setHelp('Cette commande régénère les followups pour une ville avec les nouveaux critères de completion.')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$inseeCode = $input->getArgument('insee_code');
$deleteExisting = $input->getOption('delete-existing');
$disableCleanup = $input->getOption('disable-cleanup');
$io->title('Régénération des followups pour ' . $inseeCode);
// Vérifier que la ville existe
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $inseeCode]);
if (!$stats) {
$io->error('Aucune stats trouvée pour le code INSEE ' . $inseeCode);
return Command::FAILURE;
}
$io->info('Ville trouvée : ' . $stats->getName());
// Supprimer les followups existants si demandé
if ($deleteExisting) {
$io->section('Suppression des followups existants');
$followups = $stats->getCityFollowUps();
$count = count($followups);
foreach ($followups as $followup) {
$this->entityManager->remove($followup);
}
$this->entityManager->flush();
$io->success($count . ' followups supprimés');
}
// Régénérer les followups
$io->section('Régénération des followups');
$io->note('Utilisation des nouveaux critères de completion plus réalistes');
try {
$this->followUpService->generateCityFollowUps(
$stats,
$this->motocultrice,
$this->entityManager,
$disableCleanup
);
$io->success('Followups régénérés avec succès');
// Afficher les résultats
$newFollowups = $stats->getCityFollowUps();
$io->section('Résultats');
$table = [];
foreach ($newFollowups as $followup) {
$table[] = [
$followup->getName(),
$followup->getMeasure(),
$followup->getDate()->format('Y-m-d H:i:s')
];
}
$io->table(['Nom', 'Valeur', 'Date'], $table);
} catch (\Exception $e) {
$io->error('Erreur lors de la régénération : ' . $e->getMessage());
return Command::FAILURE;
}
return Command::SUCCESS;
}
}

View file

@ -96,47 +96,93 @@ class FollowUpService
$completed = [];
if ($type === 'fire_hydrant') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['ref'] ?? null);
$tags = $el['tags'] ?? [];
// Considérer comme complet si au moins un critère supplémentaire est rempli
return !empty($tags['ref'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null)
|| !empty($tags['colour'] ?? 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);
$tags = $el['tags'] ?? [];
return !empty($tags['charging_station:output'] ?? null)
|| !empty($tags['capacity'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'toilets') {
$completed = array_filter($data['objects'], function($el) {
return ($el['tags']['wheelchair'] ?? null) === 'yes';
$tags = $el['tags'] ?? [];
return ($tags['wheelchair'] ?? null) === 'yes'
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null)
|| !empty($tags['access'] ?? null);
});
} elseif ($type === 'bus_stop') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['shelter'] ?? null);
$tags = $el['tags'] ?? [];
// Considérer comme complet si au moins un de ces critères est rempli
return !empty($tags['shelter'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null)
|| !empty($tags['network'] ?? null);
});
} elseif ($type === 'defibrillator') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['indoor'] ?? null);
$tags = $el['tags'] ?? [];
// Considérer comme complet si au moins un de ces critères est rempli
return !empty($tags['indoor'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null)
|| !empty($tags['access'] ?? null);
});
} elseif ($type === 'camera') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['surveillance:type'] ?? null);
$tags = $el['tags'] ?? [];
return !empty($tags['surveillance:type'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'recycling') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['recycling_type'] ?? null);
$tags = $el['tags'] ?? [];
return !empty($tags['recycling_type'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'substation') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['substation'] ?? null);
$tags = $el['tags'] ?? [];
return !empty($tags['substation'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'laboratory') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['website'] ?? null) || !empty($el['tags']['contact:website'] ?? null);
$tags = $el['tags'] ?? [];
return !empty($tags['website'] ?? null)
|| !empty($tags['contact:website'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['phone'] ?? 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);
$tags = $el['tags'] ?? [];
// Considérer comme complet si au moins un de ces critères est rempli
return !empty($tags['ref:UAI'] ?? null)
|| !empty($tags['isced:level'] ?? null)
|| !empty($tags['school:FR'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'police') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['phone'] ?? null) || !empty($el['tags']['website'] ?? null);
$tags = $el['tags'] ?? [];
return !empty($tags['phone'] ?? null)
|| !empty($tags['website'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'healthcare') {
$completed = array_filter($data['objects'], function($el) {
@ -145,40 +191,69 @@ class FollowUpService
|| !empty($tags['contact:phone'] ?? null)
|| !empty($tags['phone'] ?? null)
|| !empty($tags['email'] ?? null)
|| !empty($tags['contact:email'] ?? null);
|| !empty($tags['contact:email'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'bicycle_parking') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['capacity'] ?? null) || !empty($el['tags']['covered'] ?? null);
$tags = $el['tags'] ?? [];
return !empty($tags['capacity'] ?? null)
|| !empty($tags['covered'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'advertising_board') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['operator'] ?? null) || !empty($el['tags']['contact:phone'] ?? null);
$tags = $el['tags'] ?? [];
return !empty($tags['operator'] ?? null)
|| !empty($tags['contact:phone'] ?? null)
|| !empty($tags['name'] ?? null);
});
} elseif ($type === 'building') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['name'] ?? null) || !empty($el['tags']['ref'] ?? null);
$tags = $el['tags'] ?? [];
return !empty($tags['name'] ?? null)
|| !empty($tags['ref'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'email') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['name'] ?? null) || !empty($el['tags']['phone'] ?? null);
$tags = $el['tags'] ?? [];
return !empty($tags['name'] ?? null)
|| !empty($tags['phone'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'bench') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['material'] ?? null) || !empty($el['tags']['backrest'] ?? null);
$tags = $el['tags'] ?? [];
return !empty($tags['material'] ?? null)
|| !empty($tags['backrest'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'waste_basket') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['waste'] ?? null) || !empty($el['tags']['recycling_type'] ?? null);
$tags = $el['tags'] ?? [];
return !empty($tags['waste'] ?? null)
|| !empty($tags['recycling_type'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'street_lamp') {
$completed = array_filter($data['objects'], function($el) {
return !empty($el['tags']['lamp_type'] ?? null) || !empty($el['tags']['height'] ?? null);
$tags = $el['tags'] ?? [];
return !empty($tags['lamp_type'] ?? null)
|| !empty($tags['height'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'drinking_water') {
$completed = array_filter($data['objects'], function($el) {
// Complet si le tag "covered" ou "ref" est présent
return !empty($el['tags']['covered'] ?? null) || !empty($el['tags']['ref'] ?? null);
$tags = $el['tags'] ?? [];
return !empty($tags['covered'] ?? null)
|| !empty($tags['ref'] ?? null)
|| !empty($tags['name'] ?? null)
|| !empty($tags['operator'] ?? null);
});
} elseif ($type === 'tree') {
$completed = array_filter($data['objects'], function($el) {

View file

@ -1,65 +1,46 @@
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4 rounded shadow-sm">
<div class="container-fluid">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" href="{{ path('app_public_index') }}">
<i class="bi bi-house-fill"></i>
{{ 'display.home'|trans }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ path('app_public_dashboard') }}">
<i class="bi bi-bar-chart-fill"></i>
{{ 'display.stats'|trans }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ path('app_public_ask_for_help') }}">
<i class="bi bi-envelope-fill"></i>
{{ 'display.contact_humans'|trans }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ path('app_public_closed_commerces') }}">
<i class="bi bi-x-circle-fill"></i>
{{ 'display.closed_commerces'|trans }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ path('app_public_places_with_note') }}">
<i class="bi bi-file-earmark-text"></i>
{{ 'display.places_with_note'|trans }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ path('app_public_latest_changes') }}">
<i class="bi bi-clock-fill"></i>
{{ 'display.latest_changes'|trans }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ path('admin_fraicheur_histogramme') }}">
<i class="bi bi-clock-history"></i>
Fraîcheur de ladonnée
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ path('app_admin_podium_contributeurs_osm') }}">
<i class="bi bi-trophy-fill"></i>
Podium des contributeurs OSM
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ path('admin_followup_global_graph') }}">
<i class="bi bi-globe"></i>
Suivi global OSM
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ path('app_admin_import_stats') }}">
<i class="bi bi-upload"></i>
Import Stats
</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" href="{{ path('app_public_index') }}">
<i class="bi bi-house-fill"></i>
{{ 'display.home'|trans }}
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="dataDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-bar-chart-fill"></i> Données
</a>
<ul class="dropdown-menu" aria-labelledby="dataDropdown">
<li><a class="dropdown-item" href="{{ path('app_public_dashboard') }}"><i class="bi bi-bar-chart-fill"></i> {{ 'display.stats'|trans }}</a></li>
<li><a class="dropdown-item" href="{{ path('app_public_closed_commerces') }}"><i class="bi bi-x-circle-fill"></i> {{ 'display.closed_commerces'|trans }}</a></li>
<li><a class="dropdown-item" href="{{ path('app_public_places_with_note') }}"><i class="bi bi-file-earmark-text"></i> {{ 'display.places_with_note'|trans }}</a></li>
<li><a class="dropdown-item" href="{{ path('app_public_latest_changes') }}"><i class="bi bi-clock-fill"></i> {{ 'display.latest_changes'|trans }}</a></li>
<li><a class="dropdown-item" href="{{ path('admin_fraicheur_histogramme') }}"><i class="bi bi-clock-history"></i> Fraîcheur de la donnée</a></li>
<li><a class="dropdown-item" href="/api/v1/stats/export?pretty=1"><i class="bi bi-download"></i> Export JSON des villes</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ path('app_public_ask_for_help') }}">
<i class="bi bi-envelope-fill"></i>
{{ 'display.contact_humans'|trans }}
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="adminDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-gear"></i> Admin
</a>
<ul class="dropdown-menu" aria-labelledby="adminDropdown">
<li><a class="dropdown-item" href="{{ path('app_admin_podium_contributeurs_osm') }}"><i class="bi bi-trophy-fill"></i> Podium des contributeurs OSM</a></li>
<li><a class="dropdown-item" href="{{ path('admin_followup_global_graph') }}"><i class="bi bi-globe"></i> Suivi global OSM</a></li>
<li><a class="dropdown-item" href="{{ path('app_admin_import_stats') }}"><i class="bi bi-upload"></i> Import Stats</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>