up commande de labourage

This commit is contained in:
Tykayn 2025-08-02 10:59:49 +02:00 committed by tykayn
parent da60f964ab
commit 6134381422
2 changed files with 38 additions and 365 deletions

View file

@ -142,8 +142,18 @@ class ProcessLabourageQueueCommand extends Command
}
}
$stats->setDateLabourageDone(new \DateTime());
// update completion
$stats->computeCompletionPercent();
$io->info('Pourcentage de complétion: ' . $stats->getCompletionPercent());
$this->entityManager->persist($stats);
$this->entityManager->flush();
$io->info('Récupération des followups de cette ville...');
$this->followUpService->generateCityFollowUps($stats, $this->motocultrice, $this->entityManager);
$this->entityManager->persist($stats);
$this->entityManager->flush();
$io->success("Labourage terminé : $processedCount nouveaux lieux, $updatedCount lieux mis à jour.");
return Command::SUCCESS;
}

View file

@ -42,7 +42,7 @@ class PublicController extends AbstractController
$data = $this->motocultrice->get_osm_object_data($type, $id);
// Récupérer le code postal depuis les tags, sinon mettre -1
$zipCode = isset($data['tags_converted']['addr:postcode']) ? (int)$data['tags_converted']['addr:postcode'] : -1;
$place_name = $data['tags_converted']['name'];
$place_name = $data['tags_converted']['name'] ?? 'Commerce';
// Vérifier si une Place existe déjà avec le même osm_kind et osmId
$existingPlace = $this->entityManager->getRepository(Place::class)->findOneBy([
@ -96,10 +96,10 @@ class PublicController extends AbstractController
$debug = '';
if ($this->getParameter('kernel.environment') !== 'prod') {
$debug = 'Bonjour, nous sommes des bénévoles d\'OpenStreetMap France et nous vous proposons de modifier les informations de votre commerce. Voici votre lien unique de modification: ' . $this->generateUrl('app_public_edit', [
'zipcode' => $zipCode,
'name' => $place_name,
'uuid' => $place->getUuidForUrl()
], true);
'zipcode' => $zipCode,
'name' => $place_name,
'uuid' => $place->getUuidForUrl()
], true);
}
$this->addFlash('success', 'Un email vous sera envoyé avec le lien de modification. ' . $debug);
}
@ -112,147 +112,16 @@ class PublicController extends AbstractController
->to($destinataire)
->subject('Votre lien de modification OpenStreetMap')
->text('Bonjour, nous sommes des bénévoles d\'OpenStreetMap France et nous vous proposons de modifier les informations de votre commerce. Voici votre lien unique de modification: ' . $this->generateUrl('app_public_edit', [
'zipcode' => $zipCode,
'name' => $place_name,
'uuid' => $existingPlace ? $existingPlace->getUuidForUrl() : $place->getUuidForUrl()
], true));
'zipcode' => $zipCode,
'name' => $place_name,
'uuid' => $existingPlace ? $existingPlace->getUuidForUrl() : $place->getUuidForUrl()
], true));
$this->mailer->send($message);
return $this->redirectToRoute('app_public_index');
}
#[Route('/api/demande/create', name: 'app_public_create_demande', methods: ['POST'])]
public function createDemande(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true);
if (!isset($data['businessName']) || empty($data['businessName'])) {
return new JsonResponse(['success' => false, 'message' => 'Le nom du commerce est requis'], 400);
}
// Create a new Demande
$demande = new Demande();
$demande->setQuery($data['businessName']);
$demande->setStatus('new');
$demande->setCreatedAt(new \DateTime());
// Save the INSEE code if provided
if (isset($data['insee']) && !empty($data['insee'])) {
$demande->setInsee((int)$data['insee']);
}
// Save the OSM object type if provided
if (isset($data['osmObjectType']) && !empty($data['osmObjectType'])) {
$demande->setOsmObjectType($data['osmObjectType']);
}
// Save the OSM ID if provided
if (isset($data['osmId']) && !empty($data['osmId'])) {
$demande->setOsmId($data['osmId']);
}
// Check if a Place exists with the same OSM ID and type
$place = null;
if ($demande->getOsmId() && $demande->getOsmObjectType()) {
$existingPlace = $this->entityManager->getRepository(Place::class)->findOneBy([
'osm_kind' => $demande->getOsmObjectType(),
'osmId' => $demande->getOsmId()
]);
if ($existingPlace) {
// Link the Place UUID to the Demande
$demande->setPlaceUuid($existingPlace->getUuidForUrl());
$demande->setPlace($existingPlace);
$place = $existingPlace;
} else {
// Create a new Place if one doesn't exist
$place = new Place();
$place->setOsmId((string)$demande->getOsmId());
$place->setOsmKind($demande->getOsmObjectType());
// Get OSM data from Overpass API
$commerce_overpass = $this->motocultrice->get_osm_object_data($demande->getOsmObjectType(), $demande->getOsmId());
if ($commerce_overpass) {
// Update the Place with OSM data
$place->update_place_from_overpass_data($commerce_overpass);
// Link the Place to the Demande
$demande->setPlaceUuid($place->getUuidForUrl());
$demande->setPlace($place);
// Persist the Place
$this->entityManager->persist($place);
}
}
// Link the Place to a Stat object using the INSEE code
if ($place && $demande->getInsee()) {
$stats = $place->getStats();
if (!$stats) {
$stats_exist = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $demande->getInsee()]);
if ($stats_exist) {
$stats = $stats_exist;
} else {
$stats = new Stats();
$zipcode = (string)$demande->getInsee();
$stats->setZone($zipcode);
$place->setZipCode($zipcode);
$this->entityManager->persist($stats);
}
$stats->addPlace($place);
$place->setStats($stats);
}
}
}
if (!$place->getUuidForUrl()) {
$place->setUuidForUrl(uniqid());
}
if (!$place->getZipCode()) {
$place->setZipCode("00000");
}
$this->entityManager->persist($demande);
$this->entityManager->flush();
return new JsonResponse([
'success' => true,
'message' => 'Demande créée avec succès',
'id' => $demande->getId()
]);
}
#[Route('/api/demande/{id}/email', name: 'app_public_update_demande_email', methods: ['POST'])]
public function updateDemandeEmail(int $id, Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true);
if (!isset($data['email']) || empty($data['email'])) {
return new JsonResponse(['success' => false, 'message' => 'L\'email est requis'], 400);
}
$demande = $this->entityManager->getRepository(Demande::class)->find($id);
if (!$demande) {
return new JsonResponse(['success' => false, 'message' => 'Demande non trouvée'], 404);
}
$demande->setEmail($data['email']);
$demande->setStatus('email_provided');
$this->entityManager->persist($demande);
$this->entityManager->flush();
return new JsonResponse([
'success' => true,
'message' => 'Email ajouté avec succès'
]);
}
#[Route('/', name: 'app_public_index')]
public function index(): Response
{
@ -260,19 +129,19 @@ class PublicController extends AbstractController
// Préparer les données pour la carte
$citiesForMap = [];
foreach ($stats as $stat) {
if ($stat->getZone() && $stat->getZone() !== 'undefined' && preg_match('/^\d+$/', $stat->getZone()) && $stat->getZone() !== '00000') {
$cityName = $stat->getName() ?: $stat->getZone();
// Utiliser les coordonnées stockées si disponibles
if ($stat->getLat() && $stat->getLon()) {
$citiesForMap[] = [
'name' => $cityName,
'zone' => $stat->getZone(),
'coordinates' => [
'lat' => (float)$stat->getLat(),
'lon' => (float)$stat->getLon()
'lat' => (float) $stat->getLat(),
'lon' => (float) $stat->getLon()
],
'placesCount' => $stat->getPlacesCount(),
'completionPercent' => $stat->getCompletionPercent(),
@ -298,38 +167,38 @@ class PublicController extends AbstractController
{
// Cache simple pour éviter trop d'appels API
$cacheKey = 'city_coords_' . $inseeCode;
// Vérifier le cache (ici on utilise une approche simple)
// En production, vous pourriez utiliser le cache Symfony
$query = urlencode($cityName . ', France');
$url = "https://nominatim.openstreetmap.org/search?q={$query}&format=json&limit=1&countrycodes=fr";
try {
// Ajouter un délai pour respecter les limites de l'API Nominatim
usleep(100000); // 0.1 seconde entre les appels
$context = stream_context_create([
'http' => [
'timeout' => 5, // Timeout de 5 secondes
'user_agent' => 'OSM-Commerces/1.0'
]
]);
$response = file_get_contents($url, false, $context);
if ($response === false) {
error_log("DEBUG: Échec de récupération des coordonnées pour $cityName ($inseeCode)");
return null;
}
$data = json_decode($response, true);
if (!empty($data) && isset($data[0]['lat']) && isset($data[0]['lon'])) {
error_log("DEBUG: Coordonnées trouvées pour $cityName ($inseeCode): " . $data[0]['lat'] . ", " . $data[0]['lon']);
return [
'lat' => (float)$data[0]['lat'],
'lon' => (float)$data[0]['lon']
'lat' => (float) $data[0]['lat'],
'lon' => (float) $data[0]['lon']
];
} else {
error_log("DEBUG: Aucune coordonnée trouvée pour $cityName ($inseeCode)");
@ -337,7 +206,7 @@ class PublicController extends AbstractController
} catch (\Exception $e) {
error_log("DEBUG: Exception lors de la récupération des coordonnées pour $cityName ($inseeCode): " . $e->getMessage());
}
return null;
}
@ -400,7 +269,7 @@ class PublicController extends AbstractController
{
$this->actionLogger->log('dashboard', []);
$stats_repo = $this->entityManager->getRepository(Stats::class)->findAll();
$stats_for_chart = [];
@ -429,74 +298,6 @@ class PublicController extends AbstractController
]);
}
#[Route('/api/dashboard/regression', name: 'api_dashboard_regression', methods: ['POST'])]
public function saveRegressionData(Request $request): JsonResponse
{
$this->actionLogger->log('save_regression_data', []);
// Récupérer les données de la requête
$data = json_decode($request->getContent(), true);
if (!isset($data['angle']) || !isset($data['slope']) || !isset($data['intercept'])) {
return new JsonResponse([
'success' => false,
'message' => 'Données de régression incomplètes'
], Response::HTTP_BAD_REQUEST);
}
// Récupérer les stats globales (zone 00000)
$statsGlobal = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => '00000']);
if (!$statsGlobal) {
// Créer les stats globales si elles n'existent pas
$statsGlobal = new Stats();
$statsGlobal->setZone('00000');
$statsGlobal->setName('toutes les villes');
$this->entityManager->persist($statsGlobal);
$this->entityManager->flush();
}
// Créer un nouveau followup pour la régression linéaire
$followup = new CityFollowUp();
$followup->setName('regression_angle');
$followup->setMeasure($data['angle']);
$followup->setDate(new \DateTime());
$followup->setStats($statsGlobal);
$this->entityManager->persist($followup);
// Créer un followup pour la pente
$followupSlope = new CityFollowUp();
$followupSlope->setName('regression_slope');
$followupSlope->setMeasure($data['slope']);
$followupSlope->setDate(new \DateTime());
$followupSlope->setStats($statsGlobal);
$this->entityManager->persist($followupSlope);
// Créer un followup pour l'ordonnée à l'origine
$followupIntercept = new CityFollowUp();
$followupIntercept->setName('regression_intercept');
$followupIntercept->setMeasure($data['intercept']);
$followupIntercept->setDate(new \DateTime());
$followupIntercept->setStats($statsGlobal);
$this->entityManager->persist($followupIntercept);
$this->entityManager->flush();
return new JsonResponse([
'success' => true,
'message' => 'Données de régression enregistrées avec succès',
'followup' => [
'id' => $followup->getId(),
'name' => $followup->getName(),
'measure' => $followup->getMeasure(),
'date' => $followup->getDate()->format('Y-m-d H:i:s')
]
]);
}
#[Route('/modify/{osm_object_id}/{version}/{changesetID}', name: 'app_public_submit')]
public function submit($osm_object_id, $version, $changesetID): Response
{
@ -857,7 +658,7 @@ class PublicController extends AbstractController
public function publicFollowupGraph(string $insee_code, string $theme): Response
{
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
if (!$stats) {
if (!$stats) {
$this->addFlash('error', '13 Aucune stats trouvée pour ce code INSEE.');
return $this->redirectToRoute('app_public_index');
}
@ -872,7 +673,7 @@ class PublicController extends AbstractController
$followups = $stats->getCityFollowUps();
$countData = [];
$completionData = [];
foreach ($followups as $fu) {
if ($fu->getName() === $theme . '_count') {
$countData[] = [
@ -912,7 +713,7 @@ class PublicController extends AbstractController
if ($place) {
return $this->redirectToRoute('app_public_edit', [
'zipcode' => $place->getZipCode(),
'name' => $place->getName() ? $place->getName() : '?',
'name' => $place->getName() !== '' ? $place->getName() : '?',
'uuid' => $place->getUuidForUrl()
]);
} else {
@ -1055,142 +856,4 @@ class PublicController extends AbstractController
'places_6mois' => $places_6mois,
]);
}
#[Route('/rss/demandes', name: 'app_public_rss_demandes')]
public function rssDemandes(): Response
{
$demandes = $this->entityManager->getRepository(Demande::class)->findAllOrderedByCreatedAt();
$content = $this->renderView('public/rss/demandes.xml.twig', [
'demandes' => $demandes,
'base_url' => $this->getParameter('router.request_context.host'),
]);
$response = new Response($content);
$response->headers->set('Content-Type', 'application/rss+xml');
return $response;
}
#[Route('/rss/city/{insee_code}/demandes', name: 'app_public_rss_city_demandes')]
public function rssCityDemandes(string $insee_code): Response
{
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
if (!$stats) {
throw $this->createNotFoundException('Ville non trouvée');
}
// Récupérer les demandes pour cette ville
$demandes = $this->entityManager->getRepository(Demande::class)
->createQueryBuilder('d')
->where('d.insee = :insee')
->setParameter('insee', $insee_code)
->orderBy('d.createdAt', 'DESC')
->getQuery()
->getResult();
$content = $this->renderView('public/rss/city_demandes.xml.twig', [
'demandes' => $demandes,
'city' => $stats,
'base_url' => $this->getParameter('router.request_context.host'),
]);
$response = new Response($content);
$response->headers->set('Content-Type', 'application/rss+xml');
return $response;
}
#[Route('/rss/city/{insee_code}/themes', name: 'app_public_rss_city_themes')]
public function rssCityThemes(string $insee_code): Response
{
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
if (!$stats) {
throw $this->createNotFoundException('Ville non trouvée');
}
// Récupérer les changements thématiques pour cette ville
$followups = $stats->getCityFollowUps();
$themeChanges = [];
foreach ($followups as $followup) {
$name = $followup->getName();
if (str_ends_with($name, '_count')) {
$type = substr($name, 0, -6);
if (!isset($themeChanges[$type])) {
$themeChanges[$type] = [];
}
$themeChanges[$type][] = $followup;
}
}
// Trier les changements par date pour chaque thème
foreach ($themeChanges as &$changes) {
usort($changes, function ($a, $b) {
return $b->getDate() <=> $a->getDate();
});
}
$content = $this->renderView('public/rss/city_themes.xml.twig', [
'themeChanges' => $themeChanges,
'city' => $stats,
'base_url' => $this->getParameter('router.request_context.host'),
'followup_labels' => \App\Service\FollowUpService::getFollowUpThemes(),
]);
$response = new Response($content);
$response->headers->set('Content-Type', 'application/rss+xml');
return $response;
}
#[Route('/city/{insee_code}/demandes', name: 'app_public_city_demandes')]
public function cityDemandes(string $insee_code): Response
{
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
if (!$stats) {
throw $this->createNotFoundException('Ville non trouvée');
}
// Récupérer les demandes pour cette ville
$demandes = $this->entityManager->getRepository(Demande::class)
->createQueryBuilder('d')
->where('d.insee = :insee')
->setParameter('insee', $insee_code)
->orderBy('d.createdAt', 'DESC')
->getQuery()
->getResult();
return $this->render('public/city_demandes.html.twig', [
'demandes' => $demandes,
'city' => $stats,
]);
}
#[Route('/cities', name: 'app_public_cities')]
public function cities(): Response
{
$stats = $this->entityManager->getRepository(Stats::class)->findAll();
// Prepare data for the map
$citiesForMap = [];
foreach ($stats as $stat) {
if ($stat->getZone() !== 'undefined' && preg_match('/^\d+$/', $stat->getZone())) {
$citiesForMap[] = [
'name' => $stat->getName(),
'zone' => $stat->getZone(),
'lat' => $stat->getLat(),
'lon' => $stat->getLon(),
'placesCount' => $stat->getPlacesCount(),
'completionPercent' => $stat->getCompletionPercent(),
];
}
}
return $this->render('public/cities.html.twig', [
'stats' => $stats,
'citiesForMap' => $citiesForMap,
'maptiler_token' => $_ENV['MAPTILER_TOKEN'] ?? null,
]);
}
}