mirror of
https://forge.chapril.org/tykayn/osm-commerces
synced 2025-10-04 17:04:53 +02:00
use insee codes to generate all cities measures from osmium and osm pbf
This commit is contained in:
parent
fa346d522f
commit
b28f8eac63
3 changed files with 633 additions and 6 deletions
|
@ -1646,6 +1646,367 @@ final class AdminController extends AbstractController
|
||||||
$this->addFlash('success', "Création des Stats manquantes terminée : $createdCount communes ajoutées, $skippedCount déjà existantes, $errorCount erreurs.");
|
$this->addFlash('success', "Création des Stats manquantes terminée : $createdCount communes ajoutées, $skippedCount déjà existantes, $errorCount erreurs.");
|
||||||
return $this->redirectToRoute('app_admin');
|
return $this->redirectToRoute('app_admin');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/admin/create-stats-from-insee-csv', name: 'app_admin_create_stats_from_insee_csv')]
|
||||||
|
public function createStatsFromInseeCsv(): Response
|
||||||
|
{
|
||||||
|
$this->actionLogger->log('admin/create_stats_from_insee_csv', []);
|
||||||
|
|
||||||
|
$csvFile = 'communes_france.csv';
|
||||||
|
if (!file_exists($csvFile)) {
|
||||||
|
$this->addFlash('error', 'Le fichier CSV des communes n\'existe pas. Veuillez exécuter le script fetch_communes.py pour le générer.');
|
||||||
|
return $this->redirectToRoute('app_admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
$statsRepo = $this->entityManager->getRepository(Stats::class);
|
||||||
|
$createdCount = 0;
|
||||||
|
$skippedCount = 0;
|
||||||
|
$errorCount = 0;
|
||||||
|
|
||||||
|
// Ouvrir le fichier CSV
|
||||||
|
$handle = fopen($csvFile, 'r');
|
||||||
|
if (!$handle) {
|
||||||
|
$this->addFlash('error', 'Impossible d\'ouvrir le fichier CSV des communes.');
|
||||||
|
return $this->redirectToRoute('app_admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lire l'en-tête pour déterminer les indices des colonnes
|
||||||
|
$header = fgetcsv($handle);
|
||||||
|
$indices = array_flip($header);
|
||||||
|
|
||||||
|
// Vérifier que les colonnes nécessaires existent
|
||||||
|
$requiredColumns = ['code', 'nom'];
|
||||||
|
foreach ($requiredColumns as $column) {
|
||||||
|
if (!isset($indices[$column])) {
|
||||||
|
$this->addFlash('error', "La colonne '$column' est manquante dans le fichier CSV.");
|
||||||
|
fclose($handle);
|
||||||
|
return $this->redirectToRoute('app_admin');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traiter chaque ligne du CSV
|
||||||
|
while (($data = fgetcsv($handle)) !== false) {
|
||||||
|
try {
|
||||||
|
$inseeCode = $data[$indices['code']];
|
||||||
|
|
||||||
|
// Vérifier si une Stats existe déjà pour ce code INSEE
|
||||||
|
$existingStat = $statsRepo->findOneBy(['zone' => $inseeCode]);
|
||||||
|
if ($existingStat) {
|
||||||
|
$skippedCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer un nouvel objet Stats
|
||||||
|
$stat = new Stats();
|
||||||
|
$stat->setZone($inseeCode)
|
||||||
|
->setDateCreated(new \DateTime())
|
||||||
|
->setDateModified(new \DateTime())
|
||||||
|
->setKind('insee_csv'); // Utiliser 'insee_csv' comme source
|
||||||
|
|
||||||
|
// Ajouter le nom si disponible
|
||||||
|
if (isset($indices['nom']) && !empty($data[$indices['nom']])) {
|
||||||
|
$stat->setName($data[$indices['nom']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter la population si disponible
|
||||||
|
if (isset($indices['population']) && !empty($data[$indices['population']])) {
|
||||||
|
$stat->setPopulation((int)$data[$indices['population']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter les codes postaux si disponibles
|
||||||
|
if (isset($indices['codesPostaux']) && !empty($data[$indices['codesPostaux']])) {
|
||||||
|
$stat->setCodesPostaux($data[$indices['codesPostaux']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter le SIREN si disponible
|
||||||
|
if (isset($indices['siren']) && !empty($data[$indices['siren']])) {
|
||||||
|
$stat->setSiren((int)$data[$indices['siren']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter le code EPCI si disponible
|
||||||
|
if (isset($indices['codeEpci']) && !empty($data[$indices['codeEpci']])) {
|
||||||
|
$stat->setCodeEpci((int)$data[$indices['codeEpci']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ne pas faire de labourage des objets avant la sauvegarde
|
||||||
|
|
||||||
|
// Persister l'objet Stats
|
||||||
|
$this->entityManager->persist($stat);
|
||||||
|
$createdCount++;
|
||||||
|
|
||||||
|
// Flush tous les 100 objets pour éviter de surcharger la mémoire
|
||||||
|
if ($createdCount % 100 === 0) {
|
||||||
|
$this->entityManager->flush();
|
||||||
|
$this->entityManager->clear(Stats::class);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$errorCount++;
|
||||||
|
$this->actionLogger->log('error_create_stats_from_insee_csv', [
|
||||||
|
'insee_code' => $inseeCode ?? 'unknown',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush les derniers objets
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
fclose($handle);
|
||||||
|
|
||||||
|
$this->addFlash('success', "Création des Stats depuis le CSV INSEE terminée : $createdCount communes ajoutées, $skippedCount déjà existantes, $errorCount erreurs.");
|
||||||
|
return $this->redirectToRoute('app_admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/admin/retrieve-city-polygons', name: 'app_admin_retrieve_city_polygons')]
|
||||||
|
public function retrieveCityPolygons(): Response
|
||||||
|
{
|
||||||
|
$this->actionLogger->log('admin/retrieve_city_polygons', []);
|
||||||
|
|
||||||
|
// Vérifier que le dossier polygons existe, sinon le créer
|
||||||
|
$polygonsDir = __DIR__ . '/../../counting_osm_objects/polygons';
|
||||||
|
if (!is_dir($polygonsDir)) {
|
||||||
|
mkdir($polygonsDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer toutes les Stats
|
||||||
|
$statsRepo = $this->entityManager->getRepository(Stats::class);
|
||||||
|
$allStats = $statsRepo->findAll();
|
||||||
|
|
||||||
|
$totalCount = count($allStats);
|
||||||
|
$existingCount = 0;
|
||||||
|
$createdCount = 0;
|
||||||
|
$errorCount = 0;
|
||||||
|
|
||||||
|
// Pour chaque Stats, récupérer le polygone si nécessaire
|
||||||
|
foreach ($allStats as $stat) {
|
||||||
|
$inseeCode = $stat->getZone();
|
||||||
|
if (!$inseeCode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$polygonFile = $polygonsDir . '/commune_' . $inseeCode . '.poly';
|
||||||
|
|
||||||
|
// Vérifier si le polygone existe déjà
|
||||||
|
if (file_exists($polygonFile)) {
|
||||||
|
$existingCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Utiliser le script Python existant pour récupérer le polygone
|
||||||
|
$command = 'cd ' . __DIR__ . '/../../counting_osm_objects && python3 get_poly.py ' . $inseeCode;
|
||||||
|
$output = [];
|
||||||
|
$returnVar = 0;
|
||||||
|
exec($command, $output, $returnVar);
|
||||||
|
|
||||||
|
if ($returnVar === 0 && file_exists($polygonFile)) {
|
||||||
|
$createdCount++;
|
||||||
|
} else {
|
||||||
|
$errorCount++;
|
||||||
|
$this->actionLogger->log('error_retrieve_city_polygon', [
|
||||||
|
'insee_code' => $inseeCode,
|
||||||
|
'error' => 'Failed to retrieve polygon: ' . implode("\n", $output)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$errorCount++;
|
||||||
|
$this->actionLogger->log('error_retrieve_city_polygon', [
|
||||||
|
'insee_code' => $inseeCode,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addFlash('success', "Récupération des polygones terminée : $createdCount polygones créés, $existingCount déjà existants, $errorCount erreurs sur un total de $totalCount communes.");
|
||||||
|
return $this->redirectToRoute('app_admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/admin/extract-insee-zones', name: 'app_admin_extract_insee_zones')]
|
||||||
|
public function extractInseeZones(): Response
|
||||||
|
{
|
||||||
|
$this->actionLogger->log('admin/extract_insee_zones', []);
|
||||||
|
|
||||||
|
// Vérifier que le fichier france-latest.osm.pbf existe
|
||||||
|
$francePbfFile = __DIR__ . '/../../france-latest.osm.pbf';
|
||||||
|
if (!file_exists($francePbfFile)) {
|
||||||
|
$this->addFlash('error', 'Le fichier france-latest.osm.pbf n\'existe pas. Veuillez le télécharger depuis https://download.geofabrik.de/europe/france.html');
|
||||||
|
return $this->redirectToRoute('app_admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier que le dossier polygons existe
|
||||||
|
$polygonsDir = __DIR__ . '/../../counting_osm_objects/polygons';
|
||||||
|
if (!is_dir($polygonsDir)) {
|
||||||
|
$this->addFlash('error', 'Le dossier des polygones n\'existe pas. Veuillez d\'abord exécuter l\'action "Récupérer les polygones des villes".');
|
||||||
|
return $this->redirectToRoute('app_admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer le dossier pour les extractions JSON si nécessaire
|
||||||
|
$extractsDir = __DIR__ . '/../../insee_extracts';
|
||||||
|
if (!is_dir($extractsDir)) {
|
||||||
|
mkdir($extractsDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer toutes les Stats
|
||||||
|
$statsRepo = $this->entityManager->getRepository(Stats::class);
|
||||||
|
$allStats = $statsRepo->findAll();
|
||||||
|
|
||||||
|
$totalCount = count($allStats);
|
||||||
|
$existingCount = 0;
|
||||||
|
$createdCount = 0;
|
||||||
|
$errorCount = 0;
|
||||||
|
|
||||||
|
// Pour chaque Stats, extraire les données si nécessaire
|
||||||
|
foreach ($allStats as $stat) {
|
||||||
|
$inseeCode = $stat->getZone();
|
||||||
|
if (!$inseeCode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$polygonFile = $polygonsDir . '/commune_' . $inseeCode . '.poly';
|
||||||
|
$extractPbfFile = $extractsDir . '/commune_' . $inseeCode . '.osm.pbf';
|
||||||
|
$extractJsonFile = $extractsDir . '/commune_' . $inseeCode . '.json';
|
||||||
|
|
||||||
|
// Vérifier si le polygone existe
|
||||||
|
if (!file_exists($polygonFile)) {
|
||||||
|
$this->actionLogger->log('error_extract_insee_zone', [
|
||||||
|
'insee_code' => $inseeCode,
|
||||||
|
'error' => 'Polygon file does not exist'
|
||||||
|
]);
|
||||||
|
$errorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si l'extraction JSON existe déjà
|
||||||
|
if (file_exists($extractJsonFile)) {
|
||||||
|
$existingCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Étape 1: Extraire les données de france-latest.osm.pbf vers un fichier PBF pour la zone
|
||||||
|
$extractCommand = 'osmium extract -p ' . $polygonFile . ' ' . $francePbfFile . ' -o ' . $extractPbfFile;
|
||||||
|
$output = [];
|
||||||
|
$returnVar = 0;
|
||||||
|
exec($extractCommand, $output, $returnVar);
|
||||||
|
|
||||||
|
if ($returnVar !== 0 || !file_exists($extractPbfFile)) {
|
||||||
|
$this->actionLogger->log('error_extract_insee_zone', [
|
||||||
|
'insee_code' => $inseeCode,
|
||||||
|
'error' => 'Failed to extract PBF: ' . implode("\n", $output)
|
||||||
|
]);
|
||||||
|
$errorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Étape 2: Convertir le fichier PBF en JSON
|
||||||
|
$exportCommand = 'osmium export ' . $extractPbfFile . ' -f json -o ' . $extractJsonFile;
|
||||||
|
$output = [];
|
||||||
|
$returnVar = 0;
|
||||||
|
exec($exportCommand, $output, $returnVar);
|
||||||
|
|
||||||
|
if ($returnVar === 0 && file_exists($extractJsonFile)) {
|
||||||
|
$createdCount++;
|
||||||
|
} else {
|
||||||
|
$this->actionLogger->log('error_extract_insee_zone', [
|
||||||
|
'insee_code' => $inseeCode,
|
||||||
|
'error' => 'Failed to export to JSON: ' . implode("\n", $output)
|
||||||
|
]);
|
||||||
|
$errorCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supprimer le fichier PBF intermédiaire pour économiser de l'espace
|
||||||
|
if (file_exists($extractPbfFile)) {
|
||||||
|
unlink($extractPbfFile);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$errorCount++;
|
||||||
|
$this->actionLogger->log('error_extract_insee_zone', [
|
||||||
|
'insee_code' => $inseeCode,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addFlash('success', "Extraction des zones INSEE terminée : $createdCount extractions créées, $existingCount déjà existantes, $errorCount erreurs sur un total de $totalCount communes.");
|
||||||
|
return $this->redirectToRoute('app_admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/admin/process-insee-extracts', name: 'app_admin_process_insee_extracts')]
|
||||||
|
public function processInseeExtracts(): Response
|
||||||
|
{
|
||||||
|
$this->actionLogger->log('admin/process_insee_extracts', []);
|
||||||
|
|
||||||
|
// Vérifier que le dossier des extractions existe
|
||||||
|
$extractsDir = __DIR__ . '/../../insee_extracts';
|
||||||
|
if (!is_dir($extractsDir)) {
|
||||||
|
$this->addFlash('error', 'Le dossier des extractions n\'existe pas. Veuillez d\'abord exécuter l\'action "Extraire les données des zones INSEE".');
|
||||||
|
return $this->redirectToRoute('app_admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer toutes les Stats
|
||||||
|
$statsRepo = $this->entityManager->getRepository(Stats::class);
|
||||||
|
$allStats = $statsRepo->findAll();
|
||||||
|
|
||||||
|
$totalCount = count($allStats);
|
||||||
|
$processedCount = 0;
|
||||||
|
$skippedCount = 0;
|
||||||
|
$errorCount = 0;
|
||||||
|
|
||||||
|
// Pour chaque Stats, traiter les données si nécessaire
|
||||||
|
foreach ($allStats as $stat) {
|
||||||
|
$inseeCode = $stat->getZone();
|
||||||
|
if (!$inseeCode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$extractJsonFile = $extractsDir . '/commune_' . $inseeCode . '.json';
|
||||||
|
|
||||||
|
// Vérifier si l'extraction JSON existe
|
||||||
|
if (!file_exists($extractJsonFile)) {
|
||||||
|
$this->actionLogger->log('error_process_insee_extract', [
|
||||||
|
'insee_code' => $inseeCode,
|
||||||
|
'error' => 'JSON extract file does not exist'
|
||||||
|
]);
|
||||||
|
$errorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Utiliser la Motocultrice pour traiter les données
|
||||||
|
$result = $this->motocultrice->labourer($inseeCode);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
// Mettre à jour la date de labourage
|
||||||
|
$stat->setDateLabourageDone(new \DateTime());
|
||||||
|
$this->entityManager->persist($stat);
|
||||||
|
$processedCount++;
|
||||||
|
|
||||||
|
// Flush tous les 10 objets pour éviter de surcharger la mémoire
|
||||||
|
if ($processedCount % 10 === 0) {
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->actionLogger->log('error_process_insee_extract', [
|
||||||
|
'insee_code' => $inseeCode,
|
||||||
|
'error' => 'Failed to process extract with Motocultrice'
|
||||||
|
]);
|
||||||
|
$errorCount++;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$errorCount++;
|
||||||
|
$this->actionLogger->log('error_process_insee_extract', [
|
||||||
|
'insee_code' => $inseeCode,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush les derniers objets
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$this->addFlash('success', "Traitement des extractions INSEE terminé : $processedCount communes traitées, $skippedCount ignorées, $errorCount erreurs sur un total de $totalCount communes.");
|
||||||
|
return $this->redirectToRoute('app_admin');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complète les données manquantes d'un objet Stats (coordonnées, budget, etc.)
|
* Complète les données manquantes d'un objet Stats (coordonnées, budget, etc.)
|
||||||
|
|
|
@ -5,7 +5,7 @@ jour ou de traductions, et publier des suggestions sur Mastodon pour encourager
|
||||||
|
|
||||||
## Vue d'ensemble
|
## Vue d'ensemble
|
||||||
|
|
||||||
Le projet comprend huit scripts principaux :
|
Le projet comprend neuf scripts principaux :
|
||||||
|
|
||||||
1. **wiki_compare.py** : Récupère les 10 clés OSM les plus utilisées, compare leurs pages wiki en anglais et en
|
1. **wiki_compare.py** : Récupère les 10 clés OSM les plus utilisées, compare leurs pages wiki en anglais et en
|
||||||
français, et identifie celles qui ont besoin de mises à jour.
|
français, et identifie celles qui ont besoin de mises à jour.
|
||||||
|
@ -13,17 +13,19 @@ Le projet comprend huit scripts principaux :
|
||||||
message sur Mastodon pour suggérer sa mise à jour.
|
message sur Mastodon pour suggérer sa mise à jour.
|
||||||
3. **suggest_translation.py** : Identifie les pages wiki anglaises qui n'ont pas de traduction française et publie une
|
3. **suggest_translation.py** : Identifie les pages wiki anglaises qui n'ont pas de traduction française et publie une
|
||||||
suggestion de traduction sur Mastodon.
|
suggestion de traduction sur Mastodon.
|
||||||
4. **detect_suspicious_deletions.py** : Analyse les changements récents du wiki OSM pour détecter les suppressions
|
4. **propose_translation.py** : Sélectionne une page wiki (par défaut la première) et utilise Ollama avec le modèle
|
||||||
|
"mistral:7b" pour proposer une traduction, qui est sauvegardée dans le fichier outdated_pages.json.
|
||||||
|
5. **detect_suspicious_deletions.py** : Analyse les changements récents du wiki OSM pour détecter les suppressions
|
||||||
suspectes (plus de 20 caractères) et les enregistre dans un fichier JSON pour affichage sur le site web.
|
suspectes (plus de 20 caractères) et les enregistre dans un fichier JSON pour affichage sur le site web.
|
||||||
5. **fetch_proposals.py** : Récupère les propositions de tags OSM en cours de vote et les propositions récemment modifiées,
|
6. **fetch_proposals.py** : Récupère les propositions de tags OSM en cours de vote et les propositions récemment modifiées,
|
||||||
et les enregistre dans un fichier JSON pour affichage sur le site web. Les données sont mises en cache pendant une heure
|
et les enregistre dans un fichier JSON pour affichage sur le site web. Les données sont mises en cache pendant une heure
|
||||||
pour éviter des requêtes trop fréquentes au serveur wiki.
|
pour éviter des requêtes trop fréquentes au serveur wiki.
|
||||||
6. **find_untranslated_french_pages.py** : Identifie les pages wiki françaises qui n'ont pas de traduction en anglais
|
7. **find_untranslated_french_pages.py** : Identifie les pages wiki françaises qui n'ont pas de traduction en anglais
|
||||||
et les enregistre dans un fichier JSON pour affichage sur le site web. Les données sont mises en cache pendant une heure.
|
et les enregistre dans un fichier JSON pour affichage sur le site web. Les données sont mises en cache pendant une heure.
|
||||||
7. **find_pages_unavailable_in_french.py** : Scrape la catégorie des pages non disponibles en français, gère la pagination
|
8. **find_pages_unavailable_in_french.py** : Scrape la catégorie des pages non disponibles en français, gère la pagination
|
||||||
pour récupérer toutes les pages, les groupe par préfixe de langue et priorise les pages commençant par "En:". Les données
|
pour récupérer toutes les pages, les groupe par préfixe de langue et priorise les pages commençant par "En:". Les données
|
||||||
sont mises en cache pendant une heure.
|
sont mises en cache pendant une heure.
|
||||||
8. **fetch_osm_fr_groups.py** : Récupère les informations sur les groupes de travail et les groupes locaux d'OSM-FR
|
9. **fetch_osm_fr_groups.py** : Récupère les informations sur les groupes de travail et les groupes locaux d'OSM-FR
|
||||||
depuis la section #Pages_des_groupes_locaux et les enregistre dans un fichier JSON pour affichage sur le site web.
|
depuis la section #Pages_des_groupes_locaux et les enregistre dans un fichier JSON pour affichage sur le site web.
|
||||||
Les données sont mises en cache pendant une heure.
|
Les données sont mises en cache pendant une heure.
|
||||||
|
|
||||||
|
@ -42,6 +44,15 @@ Installez les dépendances requises :
|
||||||
pip install requests beautifulsoup4
|
pip install requests beautifulsoup4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Pour utiliser le script propose_translation.py, vous devez également installer Ollama :
|
||||||
|
|
||||||
|
1. Installez Ollama en suivant les instructions sur [ollama.ai](https://ollama.ai/)
|
||||||
|
2. Téléchargez le modèle "mistral:7b" :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ollama pull mistral:7b
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
### Mastodon API
|
### Mastodon API
|
||||||
|
@ -111,6 +122,28 @@ Pour simuler la publication sans réellement poster sur Mastodon (mode test) :
|
||||||
./suggest_translation.py --dry-run
|
./suggest_translation.py --dry-run
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Proposer une traduction avec Ollama
|
||||||
|
|
||||||
|
Pour sélectionner une page wiki (par défaut la première du fichier outdated_pages.json) et générer une proposition de traduction avec Ollama :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./propose_translation.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Pour traduire une page spécifique en utilisant sa clé :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./propose_translation.py --page type
|
||||||
|
```
|
||||||
|
|
||||||
|
Note : Ce script nécessite que Ollama soit installé et exécuté localement avec le modèle "mistral:7b" disponible. Pour installer Ollama, suivez les instructions sur [ollama.ai](https://ollama.ai/). Pour télécharger le modèle "mistral:7b", exécutez :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ollama pull mistral:7b
|
||||||
|
```
|
||||||
|
|
||||||
|
Le script enregistre la traduction proposée dans la propriété "proposed_translation" de l'entrée correspondante dans le fichier outdated_pages.json.
|
||||||
|
|
||||||
### Détecter les suppressions suspectes
|
### Détecter les suppressions suspectes
|
||||||
|
|
||||||
Pour analyser les changements récents du wiki OSM et détecter les suppressions suspectes :
|
Pour analyser les changements récents du wiki OSM et détecter les suppressions suspectes :
|
||||||
|
|
233
wiki_compare/propose_translation.py
Executable file
233
wiki_compare/propose_translation.py
Executable file
|
@ -0,0 +1,233 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
propose_translation.py
|
||||||
|
|
||||||
|
This script reads the outdated_pages.json file, selects a wiki page (by default the first one),
|
||||||
|
and uses Ollama with the "mistral:7b" model to propose a translation of the page.
|
||||||
|
The translation is saved in the "proposed_translation" property of the JSON file.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python propose_translation.py [--page KEY]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--page KEY Specify the key of the page to translate (default: first page in the file)
|
||||||
|
|
||||||
|
Output:
|
||||||
|
- Updated outdated_pages.json file with proposed translations
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
OUTDATED_PAGES_FILE = "outdated_pages.json"
|
||||||
|
OLLAMA_API_URL = "http://localhost:11434/api/generate"
|
||||||
|
OLLAMA_MODEL = "mistral:7b"
|
||||||
|
|
||||||
|
def load_outdated_pages():
|
||||||
|
"""
|
||||||
|
Load the outdated pages from the JSON file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: List of dictionaries containing outdated page information
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(OUTDATED_PAGES_FILE, 'r', encoding='utf-8') as f:
|
||||||
|
pages = json.load(f)
|
||||||
|
logger.info(f"Successfully loaded {len(pages)} pages from {OUTDATED_PAGES_FILE}")
|
||||||
|
return pages
|
||||||
|
except (IOError, json.JSONDecodeError) as e:
|
||||||
|
logger.error(f"Error loading pages from {OUTDATED_PAGES_FILE}: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def save_to_json(data, filename):
|
||||||
|
"""
|
||||||
|
Save data to a JSON file
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Data to save
|
||||||
|
filename (str): Name of the file
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
logger.info(f"Data saved to {filename}")
|
||||||
|
except IOError as e:
|
||||||
|
logger.error(f"Error saving data to {filename}: {e}")
|
||||||
|
|
||||||
|
def fetch_wiki_page_content(url):
|
||||||
|
"""
|
||||||
|
Fetch the content of a wiki page
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str): URL of the wiki page
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Content of the wiki page
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
soup = BeautifulSoup(response.text, 'html.parser')
|
||||||
|
|
||||||
|
# Get the main content
|
||||||
|
content = soup.select_one('#mw-content-text')
|
||||||
|
if content:
|
||||||
|
# Remove script and style elements
|
||||||
|
for script in content.select('script, style'):
|
||||||
|
script.extract()
|
||||||
|
|
||||||
|
# Remove .languages elements
|
||||||
|
for languages_elem in content.select('.languages'):
|
||||||
|
languages_elem.extract()
|
||||||
|
|
||||||
|
# Get text
|
||||||
|
text = content.get_text(separator=' ', strip=True)
|
||||||
|
return text
|
||||||
|
else:
|
||||||
|
logger.warning(f"Could not find content in page: {url}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error(f"Error fetching wiki page content: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def translate_with_ollama(text, model=OLLAMA_MODEL):
|
||||||
|
"""
|
||||||
|
Translate text using Ollama
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text (str): Text to translate
|
||||||
|
model (str): Ollama model to use
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Translated text
|
||||||
|
"""
|
||||||
|
prompt = f"""
|
||||||
|
Tu es un traducteur professionnel spécialisé dans la traduction de documentation technique de l'anglais vers le français.
|
||||||
|
Traduis le texte suivant de l'anglais vers le français. Conserve le formatage et la structure du texte original.
|
||||||
|
Ne traduis pas les noms propres, les URLs, et les termes techniques spécifiques à OpenStreetMap.
|
||||||
|
|
||||||
|
Texte à traduire:
|
||||||
|
{text}
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"Sending request to Ollama with model {model}")
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"model": model,
|
||||||
|
"prompt": prompt,
|
||||||
|
"stream": False
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(OLLAMA_API_URL, json=payload)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
result = response.json()
|
||||||
|
translation = result.get('response', '')
|
||||||
|
|
||||||
|
logger.info(f"Successfully received translation from Ollama")
|
||||||
|
return translation
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error(f"Error translating with Ollama: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def select_page_for_translation(pages, key=None):
|
||||||
|
"""
|
||||||
|
Select a page for translation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pages (list): List of dictionaries containing page information
|
||||||
|
key (str): Key of the page to select (if None, select the first page)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Selected page or None if no suitable page found
|
||||||
|
"""
|
||||||
|
if not pages:
|
||||||
|
logger.warning("No pages found that need translation")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if key:
|
||||||
|
# Find the page with the specified key
|
||||||
|
for page in pages:
|
||||||
|
if page.get('key') == key:
|
||||||
|
logger.info(f"Selected page for key '{key}' for translation")
|
||||||
|
return page
|
||||||
|
|
||||||
|
logger.warning(f"No page found with key '{key}'")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
# Select the first page
|
||||||
|
selected_page = pages[0]
|
||||||
|
logger.info(f"Selected first page (key '{selected_page['key']}') for translation")
|
||||||
|
return selected_page
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function to execute the script"""
|
||||||
|
parser = argparse.ArgumentParser(description="Propose a translation for an OSM wiki page using Ollama")
|
||||||
|
parser.add_argument("--page", help="Key of the page to translate (default: first page in the file)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
logger.info("Starting propose_translation.py")
|
||||||
|
|
||||||
|
# Load pages
|
||||||
|
pages = load_outdated_pages()
|
||||||
|
if not pages:
|
||||||
|
logger.error("No pages found. Run wiki_compare.py first.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Select a page for translation
|
||||||
|
selected_page = select_page_for_translation(pages, args.page)
|
||||||
|
if not selected_page:
|
||||||
|
logger.error("Could not select a page for translation.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Get the English page URL
|
||||||
|
en_url = selected_page.get('en_page', {}).get('url')
|
||||||
|
if not en_url:
|
||||||
|
logger.error(f"No English page URL found for key '{selected_page['key']}'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Fetch the content of the English page
|
||||||
|
logger.info(f"Fetching content from {en_url}")
|
||||||
|
content = fetch_wiki_page_content(en_url)
|
||||||
|
if not content:
|
||||||
|
logger.error(f"Could not fetch content from {en_url}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Translate the content
|
||||||
|
logger.info(f"Translating content for key '{selected_page['key']}'")
|
||||||
|
translation = translate_with_ollama(content)
|
||||||
|
if not translation:
|
||||||
|
logger.error("Could not translate content")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Save the translation in the JSON file
|
||||||
|
logger.info(f"Saving translation for key '{selected_page['key']}'")
|
||||||
|
selected_page['proposed_translation'] = translation
|
||||||
|
|
||||||
|
# Save the updated data back to the file
|
||||||
|
save_to_json(pages, OUTDATED_PAGES_FILE)
|
||||||
|
|
||||||
|
logger.info("Script completed successfully")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Loading…
Add table
Add a link
Reference in a new issue