From 539b4c094f3b0d49199374a118cb0f6fb83e09de Mon Sep 17 00:00:00 2001 From: Tykayn Date: Mon, 8 Sep 2025 18:40:08 +0200 Subject: [PATCH] change template panel left, create dashboard --- public/css/main.css | 61 ++++- src/Controller/WikiController.php | 225 +++++++++++++++- templates/admin/wiki.html.twig | 96 ++++++- .../admin/wiki_archived_proposals.html.twig | 5 +- templates/admin/wiki_compare.html.twig | 1 - templates/admin/wiki_create_french.html.twig | 1 - templates/admin/wiki_dashboard.html.twig | 254 ++++++++++++++++++ templates/admin/wiki_decrepitude.html.twig | 1 - .../admin/wiki_missing_translations.html.twig | 1 - templates/admin/wiki_osm_fr_groups.html.twig | 1 - ...wiki_pages_unavailable_in_french.html.twig | 1 - .../admin/wiki_random_suggestion.html.twig | 21 +- templates/admin/wiki_rankings.html.twig | 1 - templates/admin/wiki_recent_changes.html.twig | 55 ++-- .../admin/wiki_suspicious_deletions.html.twig | 1 - templates/admin/wiki_tag_proposals.html.twig | 3 +- templates/base.html.twig | 214 +++++++++------ templates/public/wiki.html.twig | 2 +- .../public/wiki_random_suggestion.html.twig | 20 +- .../public/wiki_recent_changes.html.twig | 54 ++-- wiki_compare/CHANGES_DEADEND_PAGES.md | 199 ++++++++++++++ wiki_compare/CHANGES_FIXES.md | 94 +++++++ .../__pycache__/wiki_compare.cpython-313.pyc | Bin 59521 -> 68550 bytes wiki_compare/wiki_compare.py | 222 ++++++++++++++- 24 files changed, 1367 insertions(+), 166 deletions(-) create mode 100644 templates/admin/wiki_dashboard.html.twig create mode 100644 wiki_compare/CHANGES_DEADEND_PAGES.md create mode 100644 wiki_compare/CHANGES_FIXES.md diff --git a/public/css/main.css b/public/css/main.css index c5f25cb..b51c2f1 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -1,4 +1,63 @@ /* Layout général */ +body { + overflow-x: hidden; +} + +/* Sidebar styles */ +.sidebar { + width: 250px; + min-height: 100vh; + position: fixed; + top: 0; + left: 0; + z-index: 100; + transition: all 0.3s; +} + +.sidebar-header { + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.sidebar .nav-link { + padding: 0.75rem 1rem; + border-radius: 0.25rem; + margin-bottom: 0.25rem; + transition: all 0.2s; +} + +.sidebar .nav-link:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.sidebar .nav-link.active { + background-color: rgba(255, 255, 255, 0.2); + font-weight: bold; +} + +.sidebar .nav-link i { + margin-right: 0.5rem; +} + +.content-wrapper { + margin-left: 250px; + width: calc(100% - 250px); + min-height: 100vh; +} + +/* For mobile devices */ +@media (max-width: 768px) { + .sidebar { + width: 100%; + position: relative; + min-height: auto; + } + + .content-wrapper { + margin-left: 0; + width: 100%; + } +} + .body-landing { background-color: rgb(255, 255, 255); min-height: 100vh; @@ -9,7 +68,7 @@ .main-header { background-color: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, .1); - padding: 1rem 0; + /*padding: 1rem 0;*/ margin-bottom: 2rem; } diff --git a/src/Controller/WikiController.php b/src/Controller/WikiController.php index 4cec428..843d794 100644 --- a/src/Controller/WikiController.php +++ b/src/Controller/WikiController.php @@ -205,9 +205,16 @@ class WikiController extends AbstractController if (isset($recentChangesData['recent_changes']) && is_array($recentChangesData['recent_changes'])) { $recentChanges = $recentChangesData['recent_changes']; $lastUpdated = isset($recentChangesData['last_updated']) ? $recentChangesData['last_updated'] : null; - + // Process team members statistics $teamMembers = $this->processTeamMembersStats($recentChanges); + + // Add avatar URLs to recent changes + foreach ($recentChanges as &$change) { + if (isset($change['user'])) { + $change['avatar_url'] = $this->fetchUserAvatar($change['user']); + } + } } // Check if the data is older than 1 hour @@ -241,6 +248,72 @@ class WikiController extends AbstractController ]); } + /** + * Fetch and cache user avatar from OSM Wiki + * + * @param string $username OSM Wiki username + * @return string|null URL of the avatar or null if not found + */ + private function fetchUserAvatar(string $username): ?string + { + // Create cache directory if it doesn't exist + $cacheDir = $this->getParameter('kernel.project_dir') . '/wiki_compare/avatar_cache'; + if (!file_exists($cacheDir)) { + mkdir($cacheDir, 0755, true); + } + + // Generate cache filename + $cacheFile = $cacheDir . '/' . md5($username) . '.json'; + + // Check if avatar is cached and not expired (7 days) + if (file_exists($cacheFile)) { + $cacheData = json_decode(file_get_contents($cacheFile), true); + $cacheTime = new \DateTime($cacheData['timestamp'] ?? '2000-01-01'); + $now = new \DateTime(); + $diff = $now->diff($cacheTime); + + // If cache is less than 7 days old, return cached avatar + if ($diff->days < 7 && isset($cacheData['avatar_url'])) { + return $cacheData['avatar_url']; + } + } + + // Avatar not cached or cache expired, fetch from wiki + $userPageUrl = "https://wiki.openstreetmap.org/wiki/User:" . urlencode($username); + try { + $response = file_get_contents($userPageUrl); + if ($response) { + // Parse HTML to find avatar + $avatarUrl = null; + + // Look for user avatar in the page + if (preg_match('/]*class="[^"]*userImage[^"]*"[^>]*src="([^"]+)"/', $response, $matches)) { + $avatarUrl = $matches[1]; + + // Make URL absolute if it's relative + if (strpos($avatarUrl, 'http') !== 0) { + $avatarUrl = 'https://wiki.openstreetmap.org' . $avatarUrl; + } + } + + // Cache the result + $cacheData = [ + 'username' => $username, + 'avatar_url' => $avatarUrl, + 'timestamp' => (new \DateTime())->format('c') + ]; + file_put_contents($cacheFile, json_encode($cacheData)); + + return $avatarUrl; + } + } catch (\Exception $e) { + // Log error but continue + error_log("Error fetching avatar for user $username: " . $e->getMessage()); + } + + return null; + } + /** * Process team members statistics from recent changes data * @@ -258,13 +331,17 @@ class WikiController extends AbstractController // Initialize user data if not exists if (!isset($teamMembers[$user])) { + // Fetch user avatar + $avatarUrl = $this->fetchUserAvatar($user); + $teamMembers[$user] = [ 'username' => $user, 'contributions' => 0, 'chars_added' => 0, 'chars_changed' => 0, 'chars_deleted' => 0, - 'user_url' => "https://wiki.openstreetmap.org/wiki/User:" . urlencode($user) + 'user_url' => "https://wiki.openstreetmap.org/wiki/User:" . urlencode($user), + 'avatar_url' => $avatarUrl ]; } @@ -628,9 +705,18 @@ class WikiController extends AbstractController // Also load the word-diff based suspicious pages for comparison if (file_exists($wordDiffFile)) { - $jsonData = json_decode(file_get_contents($wordDiffFile), true); - - foreach ($jsonData as $page) { + // Use memory-efficient approach to extract only the necessary data + $maxItems = 50; // Limit the number of items to prevent memory exhaustion + + // Extract regular_pages and specific_pages arrays + $regularPages = $this->extractJsonArrayByKey($wordDiffFile, 'regular_pages', $maxItems); + $specificPages = $this->extractJsonArrayByKey($wordDiffFile, 'specific_pages', $maxItems); + + // Combine them into a single array + $allPages = array_merge($regularPages, $specificPages); + + // Process each page to find suspicious deletions + foreach ($allPages as $page) { if (isset($page['fr_page']) && isset($page['en_page'])) { // Calculate deletion percentage $enWordCount = (int)$page['en_page']['word_count']; @@ -641,6 +727,11 @@ class WikiController extends AbstractController if ($wordDiff > 0 && $frWordCount > 0 && ($wordDiff / $enWordCount) > 0.3) { $page['deletion_percentage'] = round(($wordDiff / $enWordCount) * 100, 2); $wordDiffPages[] = $page; + + // Limit the number of suspicious pages to prevent memory issues + if (count($wordDiffPages) >= 20) { + break; + } } } } @@ -794,8 +885,18 @@ class WikiController extends AbstractController return $this->redirectToRoute('app_admin_wiki'); } - // Select a random page from the combined pages - $randomPage = $allPages[array_rand($allPages)]; + // Filter pages to ensure they have an en_page key + $validPages = array_filter($allPages, function($page) { + return isset($page['en_page']) && !empty($page['en_page']); + }); + + // If no valid pages, use any page but ensure we handle missing en_page in the template + if (empty($validPages)) { + $randomPage = $allPages[array_rand($allPages)]; + } else { + // Select a random page from the valid pages + $randomPage = $validPages[array_rand($validPages)]; + } return $this->render('admin/wiki_random_suggestion.html.twig', [ 'page' => $randomPage @@ -902,7 +1003,97 @@ EOT; ]); } - #[Route('/wiki/archived-proposals', name: 'app_admin_wiki_archived_proposals')] + #[Route('/wiki/dashboard', name: 'app_admin_wiki_dashboard')] + public function dashboard(): Response + { + // Get metrics data from JSON files + $metricsData = $this->getDashboardMetrics(); + + return $this->render('admin/wiki_dashboard.html.twig', [ + 'metrics' => $metricsData + ]); + } + + /** + * Get metrics data for the dashboard + * + * @return array Metrics data + */ + private function getDashboardMetrics(): array + { + $metrics = [ + 'average_scores' => [], + 'tracked_pages' => [], + 'orphaned_pages' => [], + 'uncategorized_pages' => [], + 'dates' => [] + ]; + + // Get data from outdated_pages.json for average scores and tracked pages + $outdatedPagesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/outdated_pages.json'; + if (file_exists($outdatedPagesFile)) { + $historyEntries = $this->extractJsonArrayByKey($outdatedPagesFile, 'history', 100); + + // Process history entries + foreach ($historyEntries as $date => $entry) { + if (isset($entry['global_metrics'])) { + $globalMetrics = $entry['global_metrics']; + + // Format date for display + $formattedDate = (new \DateTime($date))->format('Y-m-d'); + $metrics['dates'][] = $formattedDate; + + // Get average staleness score + $metrics['average_scores'][] = $globalMetrics['avg_staleness'] ?? 0; + + // Get number of tracked pages + $totalPages = $globalMetrics['total_pages'] ?? 0; + $metrics['tracked_pages'][] = $totalPages; + } + } + } + + // Get data from deadend_pages.json for uncategorized pages + $deadendPagesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/deadend_pages.json'; + if (file_exists($deadendPagesFile)) { + $historyEntries = $this->extractJsonArrayByKey($deadendPagesFile, 'history', 100); + + // Process history entries + foreach ($historyEntries as $date => $entry) { + // Format date for display + $formattedDate = (new \DateTime($date))->format('Y-m-d'); + + // If date already exists in metrics, use the same index + $dateIndex = array_search($formattedDate, $metrics['dates']); + if ($dateIndex === false) { + $dateIndex = count($metrics['dates']); + $metrics['dates'][] = $formattedDate; + + // Add placeholder values for other metrics if this is a new date + if (!isset($metrics['average_scores'][$dateIndex])) { + $metrics['average_scores'][$dateIndex] = 0; + } + if (!isset($metrics['tracked_pages'][$dateIndex])) { + $metrics['tracked_pages'][$dateIndex] = 0; + } + } + + // Get number of uncategorized pages + $uncategorizedCount = isset($entry['pages']) ? count($entry['pages']) : 0; + $metrics['uncategorized_pages'][$dateIndex] = $uncategorizedCount; + } + } + + // Sort dates and reindex arrays + array_multisort($metrics['dates'], SORT_ASC, + $metrics['average_scores'], + $metrics['tracked_pages'], + $metrics['uncategorized_pages']); + + return $metrics; + } + + #[Route('/wiki/archived-proposals', name: 'app_admin_wiki_archived_proposals')] public function archivedProposals(\Symfony\Component\HttpFoundation\Request $request): Response { $jsonFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/archived_proposals.json'; @@ -1233,6 +1424,20 @@ EOT; $keysWithoutWiki = $keysWithoutWikiData; } } + + // Load deadend pages (pages starting with "France" from the DeadendPages list) + $deadendPages = []; + $categorizedPages = []; + $deadendPagesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/deadend_pages.json'; + if (file_exists($deadendPagesFile)) { + $deadendPagesData = json_decode(file_get_contents($deadendPagesFile), true); + if (isset($deadendPagesData['pages']) && is_array($deadendPagesData['pages'])) { + $deadendPages = $deadendPagesData['pages']; + } + if (isset($deadendPagesData['categorized_pages']) && is_array($deadendPagesData['categorized_pages'])) { + $categorizedPages = $deadendPagesData['categorized_pages']; + } + } return $this->render('admin/wiki.html.twig', [ 'wiki_pages' => $wikiPages, @@ -1244,7 +1449,9 @@ EOT; 'staleness_stats' => $stalenessStats, 'wiki_pages_stats' => $wikiPagesStats, 'available_translations' => $availableTranslations, - 'keys_without_wiki' => $keysWithoutWiki + 'keys_without_wiki' => $keysWithoutWiki, + 'deadend_pages' => $deadendPages, + 'categorized_pages' => $categorizedPages ]); } diff --git a/templates/admin/wiki.html.twig b/templates/admin/wiki.html.twig index c8fef3a..076b08b 100644 --- a/templates/admin/wiki.html.twig +++ b/templates/admin/wiki.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Pages Wiki OpenStreetMap

Outil de qualité des des pages wiki OpenStreetMap en français et en anglais pour les clés OSM @@ -570,6 +569,101 @@

{% endif %} + {% if deadend_pages is defined and deadend_pages|length > 0 %} +
+
+

Pages "France" sans catégorie ({{ deadend_pages|length }})

+
+
+

Ces pages wiki commençant par "France" n'ont pas de catégorie. Vous pouvez contribuer en ajoutant des catégories à ces pages.

+
+ + + + + + + + + + {% for page in deadend_pages %} + + + + + + {% endfor %} + +
TitreCatégories suggéréesActions
+ {{ page.title }} + + {% if page.suggested_categories is defined and page.suggested_categories|length > 0 %} + {% for category in page.suggested_categories %} + {{ category }} + {% endfor %} + {% else %} + Aucune suggestion + {% endif %} + + +
+
+
+
+ {% endif %} + + {% if categorized_pages is defined and categorized_pages|length > 0 %} +
+
+

Pages "France" récemment catégorisées ({{ categorized_pages|length }})

+
+
+

Ces pages wiki commençant par "France" ont été récemment catégorisées et ne sont plus dans la liste des pages sans catégorie.

+
+ + + + + + + + + {% for page in categorized_pages %} + + + + + {% endfor %} + +
TitreActions
+
+
+ {{ page.title }} + Catégorisée +
+
+
+ +
+
+
+
+ {% endif %} +

le score de fraîcheur prend en compte d'avantage la différence entre le nombre de mots que l'ancienneté de modification. diff --git a/templates/admin/wiki_archived_proposals.html.twig b/templates/admin/wiki_archived_proposals.html.twig index 72fbfd2..7b6b1b0 100644 --- a/templates/admin/wiki_archived_proposals.html.twig +++ b/templates/admin/wiki_archived_proposals.html.twig @@ -128,8 +128,7 @@ {% block body %}

- {% include 'admin/_wiki_navigation.html.twig' %} - +

Propositions archivées OpenStreetMap

Analyse des votes sur les propositions archivées du wiki OSM

@@ -182,7 +181,7 @@
-
{{ statistics.total_proposals }}
+
{{ statistics.total_proposals|default(proposals|length) }}
Propositions analysées
diff --git a/templates/admin/wiki_compare.html.twig b/templates/admin/wiki_compare.html.twig index 5d3f8c0..98cdbf2 100644 --- a/templates/admin/wiki_compare.html.twig +++ b/templates/admin/wiki_compare.html.twig @@ -103,7 +103,6 @@ }
- {% include 'admin/_wiki_navigation.html.twig' %}
diff --git a/templates/admin/wiki_create_french.html.twig b/templates/admin/wiki_create_french.html.twig index 7960164..a98044c 100644 --- a/templates/admin/wiki_create_french.html.twig +++ b/templates/admin/wiki_create_french.html.twig @@ -67,7 +67,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Créer une traduction française pour "{{ key }}"

Utilisez cette page pour traduire la page wiki en français. La page anglaise est affichée à gauche pour référence, et le formulaire d'édition de la page française est à droite.

diff --git a/templates/admin/wiki_dashboard.html.twig b/templates/admin/wiki_dashboard.html.twig new file mode 100644 index 0000000..2fb7e59 --- /dev/null +++ b/templates/admin/wiki_dashboard.html.twig @@ -0,0 +1,254 @@ +{% extends 'base.html.twig' %} + +{% block title %}Tableau de bord - Wiki OSM{% endblock %} + +{% block body %} +
+

Tableau de bord - Wiki OSM

+

Suivi de l'évolution des métriques du wiki OpenStreetMap

+ +
+
+
+
+

Évolution du score moyen de décrépitude

+
+
+ +
+
+
+
+
+
+

Évolution du nombre de pages suivies

+
+
+ +
+
+
+
+ +
+
+
+
+

Évolution des pages sans catégorie

+
+
+ +
+
+
+
+
+
+

Statistiques actuelles

+
+
+
+
+
+
+

Score moyen

+

+ {% if metrics.average_scores|length > 0 %} + {{ metrics.average_scores|last|number_format(1) }} + {% else %} + 0 + {% endif %} +

+
+
+
+
+
+
+

Pages suivies

+

+ {% if metrics.tracked_pages|length > 0 %} + {{ metrics.tracked_pages|last }} + {% else %} + 0 + {% endif %} +

+
+
+
+
+
+
+

Pages sans catégorie

+

+ {% if metrics.uncategorized_pages|length > 0 %} + {{ metrics.uncategorized_pages|last }} + {% else %} + 0 + {% endif %} +

+
+
+
+
+
+
+

Dernière mise à jour

+

+ {% if metrics.dates|length > 0 %} + {{ metrics.dates|last }} + {% else %} + - + {% endif %} +

+
+
+
+
+
+
+
+
+ +
+
+
+
+

Données brutes

+
+
+
+ + + + + + + + + + + {% for i in 0..(metrics.dates|length - 1) %} + + + + + + + {% endfor %} + +
DateScore moyenPages suiviesPages sans catégorie
{{ metrics.dates[i] }}{{ metrics.average_scores[i]|default(0)|number_format(1) }}{{ metrics.tracked_pages[i]|default(0) }}{{ metrics.uncategorized_pages[i]|default(0) }}
+
+
+
+
+
+
+{% endblock %} + +{% block javascripts %} + {{ parent() }} + + +{% endblock %} \ No newline at end of file diff --git a/templates/admin/wiki_decrepitude.html.twig b/templates/admin/wiki_decrepitude.html.twig index 72284e2..63fbf1f 100644 --- a/templates/admin/wiki_decrepitude.html.twig +++ b/templates/admin/wiki_decrepitude.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Évolution des scores de décrépitude

diff --git a/templates/admin/wiki_missing_translations.html.twig b/templates/admin/wiki_missing_translations.html.twig index 5698544..b099445 100644 --- a/templates/admin/wiki_missing_translations.html.twig +++ b/templates/admin/wiki_missing_translations.html.twig @@ -4,7 +4,6 @@ {% block body %}

- {% include 'admin/_wiki_navigation.html.twig' %}

Pages Wiki françaises sans traduction anglaise

Liste des pages françaises du wiki OSM qui n'ont pas de traduction en anglais.

diff --git a/templates/admin/wiki_osm_fr_groups.html.twig b/templates/admin/wiki_osm_fr_groups.html.twig index b314eb3..0faa2ec 100644 --- a/templates/admin/wiki_osm_fr_groups.html.twig +++ b/templates/admin/wiki_osm_fr_groups.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Groupes OSM-FR

Liste des groupes de travail et des groupes locaux d'OpenStreetMap France.

diff --git a/templates/admin/wiki_pages_unavailable_in_french.html.twig b/templates/admin/wiki_pages_unavailable_in_french.html.twig index eb212ff..e1b68ae 100644 --- a/templates/admin/wiki_pages_unavailable_in_french.html.twig +++ b/templates/admin/wiki_pages_unavailable_in_french.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Pages Wiki non disponibles en français

Liste des pages du wiki OSM qui n'ont pas de traduction française, groupées par langue d'origine.

diff --git a/templates/admin/wiki_random_suggestion.html.twig b/templates/admin/wiki_random_suggestion.html.twig index 2bce17e..7392066 100644 --- a/templates/admin/wiki_random_suggestion.html.twig +++ b/templates/admin/wiki_random_suggestion.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Suggestion de page Wiki à améliorer

Voici une page wiki qui a besoin d'être améliorée.

@@ -25,28 +24,34 @@

Version anglaise

- Dernière modification: {{ page.en_page.last_modified }} + Dernière modification: {{ page.en_page is defined and page.en_page.last_modified is defined ? page.en_page.last_modified : 'Non disponible' }}

  • Sections - {{ page.en_page.sections }} + {{ page.en_page is defined ? page.en_page.sections|default(0) : 0 }}
  • Mots - {{ page.en_page.word_count|default(0) }} + {{ page.en_page is defined ? page.en_page.word_count|default(0) : 0 }}
  • Liens - {{ page.en_page.link_count|default(0) }} + {{ page.en_page is defined ? page.en_page.link_count|default(0) : 0 }}
- - Voir la page anglaise - + {% if page.en_page is defined and page.en_page.url is defined %} + + Voir la page anglaise + + {% else %} + + {% endif %}
diff --git a/templates/admin/wiki_rankings.html.twig b/templates/admin/wiki_rankings.html.twig index 8ce10af..a9eb72a 100644 --- a/templates/admin/wiki_rankings.html.twig +++ b/templates/admin/wiki_rankings.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Évolution des classements Wiki OSM

diff --git a/templates/admin/wiki_recent_changes.html.twig b/templates/admin/wiki_recent_changes.html.twig index eb41254..907cd7b 100644 --- a/templates/admin/wiki_recent_changes.html.twig +++ b/templates/admin/wiki_recent_changes.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Changements récents Wiki OpenStreetMap

Liste des changements récents dans l'espace de noms français du wiki OpenStreetMap.

@@ -26,18 +25,27 @@ {% for member in team_members %}
- - {{ member.username }} - - {{ member.contributions }} -
- +{{ member.chars_added }} - {% if member.chars_changed > 0 %} - ~{{ member.chars_changed }} - {% endif %} - {% if member.chars_deleted > 0 %} - -{{ member.chars_deleted }} - {% endif %} + {% if member.avatar_url is defined and member.avatar_url %} + {{ member.username }} + {% else %} +
+ {{ member.username|first|upper }} +
+ {% endif %} +
+ + {{ member.username }} + + {{ member.contributions }} +
+ +{{ member.chars_added }} + {% if member.chars_changed > 0 %} + ~{{ member.chars_changed }} + {% endif %} + {% if member.chars_deleted > 0 %} + -{{ member.chars_deleted }} + {% endif %} +
@@ -83,13 +91,22 @@ {{ change.timestamp }} - {% if change.user_url %} - + {{ change.comment }} diff --git a/templates/admin/wiki_suspicious_deletions.html.twig b/templates/admin/wiki_suspicious_deletions.html.twig index 8e1b444..12779aa 100644 --- a/templates/admin/wiki_suspicious_deletions.html.twig +++ b/templates/admin/wiki_suspicious_deletions.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Pages Wiki avec suppressions suspectes

diff --git a/templates/admin/wiki_tag_proposals.html.twig b/templates/admin/wiki_tag_proposals.html.twig index ae8d980..d074898 100644 --- a/templates/admin/wiki_tag_proposals.html.twig +++ b/templates/admin/wiki_tag_proposals.html.twig @@ -64,8 +64,7 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %} - +

Propositions de tags OSM

Liste des propositions de tags OpenStreetMap actuellement en cours de vote ou récemment modifiées.

diff --git a/templates/base.html.twig b/templates/base.html.twig index 8dfdb04..a0c4dd6 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -21,97 +21,141 @@ {% endblock %} -
-
-
- +
+ +
- - - -
- {% block body %}{% endblock %} -
- - +
{% block javascripts %} {{ encore_entry_script_tags('app') }} diff --git a/templates/public/wiki.html.twig b/templates/public/wiki.html.twig index 346a915..a816bb5 100644 --- a/templates/public/wiki.html.twig +++ b/templates/public/wiki.html.twig @@ -4,7 +4,7 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %} +

Pages Wiki OpenStreetMap

Outil de qualité des des pages wiki OpenStreetMap en français et en anglais pour les clés OSM diff --git a/templates/public/wiki_random_suggestion.html.twig b/templates/public/wiki_random_suggestion.html.twig index 1a10cba..0e1718f 100644 --- a/templates/public/wiki_random_suggestion.html.twig +++ b/templates/public/wiki_random_suggestion.html.twig @@ -25,28 +25,34 @@

Version anglaise

- Dernière modification: {{ page.en_page.last_modified }} + Dernière modification: {{ page.en_page is defined and page.en_page.last_modified is defined ? page.en_page.last_modified : 'Non disponible' }}

  • Sections - {{ page.en_page.sections }} + {{ page.en_page is defined ? page.en_page.sections|default(0) : 0 }}
  • Mots - {{ page.en_page.word_count|default(0) }} + {{ page.en_page is defined ? page.en_page.word_count|default(0) : 0 }}
  • Liens - {{ page.en_page.link_count|default(0) }} + {{ page.en_page is defined ? page.en_page.link_count|default(0) : 0 }}
- - Voir la page anglaise - + {% if page.en_page is defined and page.en_page.url is defined %} + + Voir la page anglaise + + {% else %} + + {% endif %}
diff --git a/templates/public/wiki_recent_changes.html.twig b/templates/public/wiki_recent_changes.html.twig index eb41254..7794f17 100644 --- a/templates/public/wiki_recent_changes.html.twig +++ b/templates/public/wiki_recent_changes.html.twig @@ -26,18 +26,27 @@ {% for member in team_members %}
- - {{ member.username }} - - {{ member.contributions }} -
- +{{ member.chars_added }} - {% if member.chars_changed > 0 %} - ~{{ member.chars_changed }} - {% endif %} - {% if member.chars_deleted > 0 %} - -{{ member.chars_deleted }} - {% endif %} + {% if member.avatar_url is defined and member.avatar_url %} + {{ member.username }} + {% else %} +
+ {{ member.username|first|upper }} +
+ {% endif %} +
+ + {{ member.username }} + + {{ member.contributions }} +
+ +{{ member.chars_added }} + {% if member.chars_changed > 0 %} + ~{{ member.chars_changed }} + {% endif %} + {% if member.chars_deleted > 0 %} + -{{ member.chars_deleted }} + {% endif %} +
@@ -83,13 +92,22 @@ {{ change.timestamp }} - {% if change.user_url %} - + {{ change.comment }} diff --git a/wiki_compare/CHANGES_DEADEND_PAGES.md b/wiki_compare/CHANGES_DEADEND_PAGES.md new file mode 100644 index 0000000..2bfdef6 --- /dev/null +++ b/wiki_compare/CHANGES_DEADEND_PAGES.md @@ -0,0 +1,199 @@ +# Ajout de la fonctionnalité de suivi des pages "France" sans catégorie + +## Description + +Cette fonctionnalité permet de suivre les pages du wiki OpenStreetMap commençant par "France" qui n'ont pas de catégorie (pages sans issues). Ces pages sont identifiées à partir de la liste des "DeadendPages" du wiki OSM. La fonctionnalité suggère également des catégories pour ces pages et suit leur évolution lorsqu'elles sont catégorisées. + +## Modifications apportées + +### 1. Ajout de constantes dans `wiki_compare.py` + +```python +WIKI_DEADEND_PAGES_URL = "https://wiki.openstreetmap.org/w/index.php?title=Special:DeadendPages&limit=500&offset=1000" +DEADEND_PAGES_FILE = "deadend_pages.json" +``` + +### 2. Ajout de la fonction `suggest_categories` dans `wiki_compare.py` + +Cette fonction analyse le titre et le contenu d'une page pour suggérer des catégories pertinentes : + +```python +def suggest_categories(page_title, page_url): + """ + Suggest categories for an uncategorized page based on its title and content + + Args: + page_title (str): Title of the page + page_url (str): URL of the page + + Returns: + list: List of suggested categories + """ + # Logique de suggestion de catégories basée sur le titre et le contenu de la page +``` + +### 3. Ajout de la fonction `fetch_deadend_pages` dans `wiki_compare.py` + +Cette fonction récupère les pages commençant par "France" depuis la liste des DeadendPages : + +```python +def fetch_deadend_pages(): + """ + Fetch pages starting with "France" from the DeadendPages list + + Returns: + list: List of dictionaries containing page information + """ + # Logique de récupération des pages depuis la liste des DeadendPages + # Filtrage des pages commençant par "France" + # Suggestion de catégories pour chaque page +``` + +### 4. Modification de la fonction `main` dans `wiki_compare.py` + +Ajout d'une section pour traiter les pages sans catégorie et suivre leur évolution : + +```python +# Fetch pages starting with "France" from the DeadendPages list +deadend_pages = fetch_deadend_pages() + +if deadend_pages: + # Load existing deadend pages data to compare with history + existing_data = load_json_data(DEADEND_PAGES_FILE) + + # Initialize history if it doesn't exist + if 'history' not in existing_data: + existing_data['history'] = {} + + # Get the most recent history entry + sorted_timestamps = sorted(existing_data.get('history', {}).keys()) + previous_pages = [] + if sorted_timestamps: + latest_timestamp = sorted_timestamps[-1] + previous_pages = existing_data['history'][latest_timestamp].get('pages', []) + + # Find pages that were in the previous list but are no longer in the current list + previous_urls = [page['url'] for page in previous_pages] + current_urls = [page['url'] for page in deadend_pages] + + categorized_pages = [] + for url in previous_urls: + if url not in current_urls: + # Find the page in previous_pages + for page in previous_pages: + if page['url'] == url: + # This page is no longer in the DeadendPages list, which means it has been categorized + categorized_pages.append(page) + break + + # Create a timestamp for the current data + current_timestamp = datetime.now().isoformat() + + # Create the history entry + history_entry = { + 'pages': deadend_pages, + 'categorized_pages': categorized_pages + } + + # Add the entry to history with timestamp as key + existing_data['history'][current_timestamp] = history_entry + + # Update the current data + existing_data['pages'] = deadend_pages + existing_data['categorized_pages'] = categorized_pages + existing_data['last_updated'] = current_timestamp + + # Save the updated data + save_to_json(existing_data, DEADEND_PAGES_FILE) +``` + +### 5. Modification du contrôleur `WikiController.php` + +Ajout du chargement des données des pages sans catégorie : + +```php +// Load deadend pages (pages starting with "France" from the DeadendPages list) +$deadendPages = []; +$categorizedPages = []; +$deadendPagesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/deadend_pages.json'; +if (file_exists($deadendPagesFile)) { + $deadendPagesData = json_decode(file_get_contents($deadendPagesFile), true); + if (isset($deadendPagesData['pages']) && is_array($deadendPagesData['pages'])) { + $deadendPages = $deadendPagesData['pages']; + } + if (isset($deadendPagesData['categorized_pages']) && is_array($deadendPagesData['categorized_pages'])) { + $categorizedPages = $deadendPagesData['categorized_pages']; + } +} +``` + +Ajout des données à la vue : + +```php +return $this->render('admin/wiki.html.twig', [ + // Autres données... + 'deadend_pages' => $deadendPages, + 'categorized_pages' => $categorizedPages +]); +``` + +### 6. Ajout de sections dans le template `admin/wiki.html.twig` + +Ajout d'une section pour afficher les pages sans catégorie : + +```twig +{% if deadend_pages is defined and deadend_pages|length > 0 %} +
+
+

Pages "France" sans catégorie ({{ deadend_pages|length }})

+
+
+

Ces pages wiki commençant par "France" n'ont pas de catégorie. Vous pouvez contribuer en ajoutant des catégories à ces pages.

+
+ + +
+
+
+
+{% endif %} +``` + +Ajout d'une section pour afficher les pages récemment catégorisées : + +```twig +{% if categorized_pages is defined and categorized_pages|length > 0 %} +
+
+

Pages "France" récemment catégorisées ({{ categorized_pages|length }})

+
+
+

Ces pages wiki commençant par "France" ont été récemment catégorisées et ne sont plus dans la liste des pages sans catégorie.

+
+ + +
+
+
+
+{% endif %} +``` + +## Fonctionnalités + +1. **Scraping des pages sans catégorie** : Récupération des pages commençant par "France" depuis la liste des DeadendPages du wiki OSM. +2. **Suggestion de catégories** : Analyse du titre et du contenu des pages pour suggérer des catégories pertinentes. +3. **Suivi des pages catégorisées** : Détection des pages qui ont été catégorisées depuis le dernier scraping. +4. **Affichage dans l'interface** : Affichage des pages sans catégorie et des pages récemment catégorisées dans l'interface utilisateur. + +## Utilisation + +1. Le script `wiki_compare.py` est exécuté périodiquement pour mettre à jour les données. +2. Les utilisateurs peuvent consulter les pages sans catégorie et les pages récemment catégorisées dans l'interface. +3. Les utilisateurs peuvent cliquer sur les boutons d'action pour voir les pages et ajouter des catégories. + +## Avantages + +1. **Amélioration de la qualité du wiki** : Aide à identifier et à catégoriser les pages qui devraient avoir des catégories. +2. **Suivi des progrès** : Permet de suivre les progrès dans la catégorisation des pages. +3. **Suggestion de catégories** : Facilite le travail des contributeurs en suggérant des catégories pertinentes. \ No newline at end of file diff --git a/wiki_compare/CHANGES_FIXES.md b/wiki_compare/CHANGES_FIXES.md new file mode 100644 index 0000000..0a6da53 --- /dev/null +++ b/wiki_compare/CHANGES_FIXES.md @@ -0,0 +1,94 @@ +# Corrections apportées à QualiWiki + +Ce document décrit les corrections apportées pour résoudre les problèmes mentionnés dans la description des issues. + +## 1. Correction de l'erreur dans wiki_archived_proposals.html.twig + +### Problème +``` +Key "total_proposals" for sequence/mapping with keys "0, 1, 2, 3, ..." does not exist in admin/wiki_archived_proposals.html.twig at line 193. +``` + +### Solution +Ajout d'une valeur par défaut pour `total_proposals` dans le template : + +```twig +
{{ statistics.total_proposals|default(proposals|length) }}
+``` + +Cette modification permet d'utiliser le nombre de propositions dans le tableau `proposals` comme valeur par défaut si `statistics.total_proposals` n'est pas défini. + +## 2. Implémentation d'un panneau latéral sombre pour la navigation + +### Modifications +- Ajout d'un panneau latéral sombre dans `templates/base.html.twig` +- Ajout de styles CSS dans `public/css/main.css` pour le panneau latéral +- Réorganisation de la structure HTML pour intégrer le panneau latéral +- Ajout de liens vers toutes les sections principales de l'application + +### Avantages +- Navigation plus claire et plus accessible +- Meilleure organisation visuelle de l'application +- Accès rapide à toutes les fonctionnalités principales + +## 3. Création d'une page dashboard avec graphiques + +### Fonctionnalités +- Ajout d'une méthode `dashboard` dans `WikiController.php` +- Création d'un template `admin/wiki_dashboard.html.twig` +- Implémentation de graphiques montrant : + - L'évolution du score moyen de décrépitude + - L'évolution du nombre de pages suivies + - L'évolution du nombre de pages sans catégorie + +### Données suivies +- Score moyen de décrépitude des pages +- Nombre de pages suivies +- Nombre de pages sans catégorie (pages orphelines) + +## 4. Correction de l'erreur dans wiki_random_suggestion.html.twig + +### Problème +``` +Key "en_page" for sequence/mapping with keys "title, level" does not exist in admin/wiki_random_suggestion.html.twig at line 28. +``` + +### Solution +- Ajout de vérifications pour l'existence de `en_page` dans le template +- Ajout de valeurs par défaut pour les propriétés de `en_page` +- Filtrage des pages pour s'assurer qu'elles ont une clé `en_page` dans le contrôleur + +## 5. Ajout d'avatars utilisateurs dans la page des changements récents + +### Fonctionnalités +- Ajout d'une fonction `fetchUserAvatar` dans `WikiController.php` pour récupérer et mettre en cache les avatars des utilisateurs +- Mise à jour des templates `admin/wiki_recent_changes.html.twig` et `public/wiki_recent_changes.html.twig` pour afficher les avatars +- Mise en cache des avatars pour éviter de les récupérer à chaque fois + +## 6. Correction de l'erreur de mémoire dans la page des suppressions suspectes + +### Problème +``` +Error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 175260864 bytes) +``` + +### Solution +- Modification de la méthode `suspiciousDeletions` dans `WikiController.php` pour utiliser une approche plus efficace en mémoire +- Utilisation de la méthode `extractJsonArrayByKey` au lieu de `json_decode(file_get_contents())` pour traiter le fichier JSON +- Limitation du nombre d'éléments extraits à 50 avec le paramètre `$maxItems` +- Ajout d'une limite pour arrêter le traitement après avoir trouvé 20 pages suspectes + +## 7. Implémentation du suivi des pages "France" sans catégorie + +### Fonctionnalités +- Ajout de constantes pour l'URL des DeadendPages et le fichier de sortie dans `wiki_compare.py` +- Création d'une fonction `fetch_deadend_pages` pour récupérer les pages commençant par "France" depuis la liste des DeadendPages +- Création d'une fonction `suggest_categories` pour suggérer des catégories pour les pages sans catégorie +- Modification de la fonction `main` pour traiter et sauvegarder les données des DeadendPages +- Mise à jour du contrôleur pour charger et afficher les données des DeadendPages +- Ajout de sections dans le template pour afficher les pages sans catégorie et les pages récemment catégorisées + +### Avantages +- Suivi des pages qui devraient avoir des catégories +- Suggestion de catégories pour faciliter le travail des contributeurs +- Suivi des progrès dans la catégorisation des pages \ No newline at end of file diff --git a/wiki_compare/__pycache__/wiki_compare.cpython-313.pyc b/wiki_compare/__pycache__/wiki_compare.cpython-313.pyc index f3255ad28cc07228e973d508a793e9fb7150058e..e443c452891c09b9e96c21c8ec1797e8c18f27b8 100644 GIT binary patch delta 14260 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 Ph@Sl32J@Ts0^olG+9tM< diff --git a/wiki_compare/wiki_compare.py b/wiki_compare/wiki_compare.py index 74fcfe5..01bd90a 100755 --- a/wiki_compare/wiki_compare.py +++ b/wiki_compare/wiki_compare.py @@ -61,10 +61,12 @@ WIKI_BASE_URL_EN = "https://wiki.openstreetmap.org/wiki/Key:" WIKI_BASE_URL_FR = "https://wiki.openstreetmap.org/wiki/FR:Key:" WIKI_BASE_URL = "https://wiki.openstreetmap.org/wiki/" WIKI_CATEGORY_URL = "https://wiki.openstreetmap.org/wiki/Category:FR:Traductions_d%C3%A9synchronis%C3%A9es" +WIKI_DEADEND_PAGES_URL = "https://wiki.openstreetmap.org/w/index.php?title=Special:DeadendPages&limit=500&offset=1000" TOP_KEYS_FILE = "top_keys.json" KEYS_WITHOUT_WIKI_FILE = "keys_without_wiki.json" WIKI_PAGES_CSV = "wiki_pages.csv" OUTDATED_PAGES_FILE = "outdated_pages.json" +DEADEND_PAGES_FILE = "deadend_pages.json" STALENESS_HISTOGRAM_FILE = "staleness_histogram.png" # Number of wiki pages to examine NUM_WIKI_PAGES = 2 @@ -154,6 +156,161 @@ def fetch_desynchronized_pages(): logger.error(f"Error fetching category page: {e}") return [] +def suggest_categories(page_title, page_url): + """ + Suggest categories for an uncategorized page based on its title and content + + Args: + page_title (str): Title of the page + page_url (str): URL of the page + + Returns: + list: List of suggested categories + """ + logger.info(f"Suggesting categories for page: {page_title}") + + suggested_categories = [] + + # Common categories for French OSM wiki pages + common_categories = [ + "Documentation OSM en français", + "Cartographie", + "Contributeurs", + "Développeurs", + "Éléments cartographiés", + "Imports", + "Logiciels", + "Projets", + "Rencontres", + "Utilisateurs" + ] + + # Add geography-related categories for pages about France + if "France" in page_title: + suggested_categories.append("France") + + # Check for specific regions or departments + regions = [ + "Auvergne-Rhône-Alpes", "Bourgogne-Franche-Comté", "Bretagne", + "Centre-Val de Loire", "Corse", "Grand Est", "Hauts-de-France", + "Île-de-France", "Normandie", "Nouvelle-Aquitaine", + "Occitanie", "Pays de la Loire", "Provence-Alpes-Côte d'Azur" + ] + + for region in regions: + if region in page_title: + suggested_categories.append(region) + + # Try to fetch the page content to make better suggestions + try: + response = requests.get(page_url) + response.raise_for_status() + + soup = BeautifulSoup(response.text, 'html.parser') + + # Get the main content + content = soup.select_one('#mw-content-text') + if content: + text = content.get_text(separator=' ', strip=True).lower() + + # Check for keywords related to common categories + if any(keyword in text for keyword in ["carte", "cartographie", "mapper"]): + suggested_categories.append("Cartographie") + + if any(keyword in text for keyword in ["contribuer", "contributeur", "éditer"]): + suggested_categories.append("Contributeurs") + + if any(keyword in text for keyword in ["développeur", "programmer", "code", "api"]): + suggested_categories.append("Développeurs") + + if any(keyword in text for keyword in ["tag", "clé", "valeur", "élément", "nœud", "way", "relation"]): + suggested_categories.append("Éléments cartographiés") + + if any(keyword in text for keyword in ["import", "données", "dataset"]): + suggested_categories.append("Imports") + + if any(keyword in text for keyword in ["logiciel", "application", "outil"]): + suggested_categories.append("Logiciels") + + if any(keyword in text for keyword in ["projet", "initiative"]): + suggested_categories.append("Projets") + + if any(keyword in text for keyword in ["rencontre", "réunion", "événement", "conférence"]): + suggested_categories.append("Rencontres") + + if any(keyword in text for keyword in ["utiliser", "utilisateur", "usage"]): + suggested_categories.append("Utilisateurs") + + except requests.exceptions.RequestException as e: + logger.warning(f"Error fetching page content for category suggestions: {e}") + # If we can't fetch the content, suggest common categories based on title only + if "projet" in page_title.lower(): + suggested_categories.append("Projets") + elif "logiciel" in page_title.lower() or "application" in page_title.lower(): + suggested_categories.append("Logiciels") + elif "rencontre" in page_title.lower() or "réunion" in page_title.lower(): + suggested_categories.append("Rencontres") + + # Always suggest the general French documentation category + suggested_categories.append("Documentation OSM en français") + + # Remove duplicates while preserving order + seen = set() + unique_categories = [] + for cat in suggested_categories: + if cat not in seen: + seen.add(cat) + unique_categories.append(cat) + + logger.info(f"Suggested {len(unique_categories)} categories for {page_title}: {', '.join(unique_categories)}") + return unique_categories + +def fetch_deadend_pages(): + """ + Fetch pages starting with "France" from the DeadendPages list + + Returns: + list: List of dictionaries containing page information + """ + logger.info(f"Fetching pages from DeadendPages list: {WIKI_DEADEND_PAGES_URL}") + + try: + response = requests.get(WIKI_DEADEND_PAGES_URL) + response.raise_for_status() + + soup = BeautifulSoup(response.text, 'html.parser') + + # Find all links in the DeadendPages list + page_links = [] + for link in soup.select('.mw-spcontent li a'): + href = link.get('href', '') + title = link.get_text(strip=True) + + # Skip if it's not a wiki page or if it's a special page + if not href.startswith('/wiki/') or 'Special:' in href: + continue + + # Filter pages that start with "France" + if title.startswith('France'): + # Get the full URL + full_url = 'https://wiki.openstreetmap.org' + href + + # Suggest categories for this page + suggested_categories = suggest_categories(title, full_url) + + page_links.append({ + 'title': title, + 'url': full_url, + 'suggested_categories': suggested_categories + }) + + logger.info(f"Found {len(page_links)} pages starting with 'France' in the DeadendPages list") + return page_links + + except requests.exceptions.RequestException as e: + logger.error(f"Error fetching DeadendPages list: {e}") + return [] + def fetch_top_keys(limit=NUM_WIKI_PAGES): """ Fetch the most used OSM keys from TagInfo API @@ -1365,10 +1522,11 @@ def main(): 3. Fetches and processes wiki pages for these keys 4. Processes specific wiki pages listed in SPECIFIC_PAGES 5. Processes pages from the FR:Traductions_désynchronisées category - 6. Calculates staleness scores for all pages - 7. Generates a histogram of staleness scores - 8. Saves the results to CSV and JSON files - 9. Prints a list of pages that need updating + 6. Processes pages starting with "France" from the DeadendPages list + 7. Calculates staleness scores for all pages + 8. Generates a histogram of staleness scores + 9. Saves the results to CSV and JSON files + 10. Prints a list of pages that need updating """ # Parse command-line arguments parser = argparse.ArgumentParser(description='Compare OpenStreetMap wiki pages in English and French.') @@ -1404,6 +1562,62 @@ def main(): logger.info(f"Saved {len(keys_without_wiki)} keys without wiki pages to {KEYS_WITHOUT_WIKI_FILE}") else: logger.warning("No keys without wiki pages were fetched.") + + # Fetch pages starting with "France" from the DeadendPages list + deadend_pages = fetch_deadend_pages() + + if deadend_pages: + # Load existing deadend pages data to compare with history + existing_data = load_json_data(DEADEND_PAGES_FILE) + + # Initialize history if it doesn't exist + if 'history' not in existing_data: + existing_data['history'] = {} + + # Get the most recent history entry + sorted_timestamps = sorted(existing_data.get('history', {}).keys()) + previous_pages = [] + if sorted_timestamps: + latest_timestamp = sorted_timestamps[-1] + previous_pages = existing_data['history'][latest_timestamp].get('pages', []) + + # Find pages that were in the previous list but are no longer in the current list + previous_urls = [page['url'] for page in previous_pages] + current_urls = [page['url'] for page in deadend_pages] + + categorized_pages = [] + for url in previous_urls: + if url not in current_urls: + # Find the page in previous_pages + for page in previous_pages: + if page['url'] == url: + # This page is no longer in the DeadendPages list, which means it has been categorized + categorized_pages.append(page) + break + + # Create a timestamp for the current data + current_timestamp = datetime.now().isoformat() + + # Create the history entry + history_entry = { + 'pages': deadend_pages, + 'categorized_pages': categorized_pages + } + + # Add the entry to history with timestamp as key + existing_data['history'][current_timestamp] = history_entry + + # Update the current data + existing_data['pages'] = deadend_pages + existing_data['categorized_pages'] = categorized_pages + existing_data['last_updated'] = current_timestamp + + # Save the updated data + save_to_json(existing_data, DEADEND_PAGES_FILE) + logger.info(f"Saved {len(deadend_pages)} deadend pages to {DEADEND_PAGES_FILE}") + logger.info(f"Found {len(categorized_pages)} pages that have been categorized since the last run") + else: + logger.warning("No deadend pages were fetched.") # Fetch wiki pages for each key wiki_pages = []