diff --git a/src/Controller/WikiController.php b/src/Controller/WikiController.php index 660b2218..b6c04b36 100644 --- a/src/Controller/WikiController.php +++ b/src/Controller/WikiController.php @@ -5,6 +5,7 @@ namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\HttpFoundation\Request; class WikiController extends AbstractController { @@ -23,13 +24,294 @@ class WikiController extends AbstractController $headers = array_shift($csvData); $wikiPages = []; + $missingTranslations = []; + + // First pass: collect all staleness scores to find min and max + $stalenessScores = []; foreach ($csvData as $row) { $page = array_combine($headers, $row); + if (isset($page['staleness_score']) && is_numeric($page['staleness_score'])) { + $stalenessScores[] = (float)$page['staleness_score']; + } + } + + // Find min and max scores for normalization + $minScore = !empty($stalenessScores) ? min($stalenessScores) : 0; + $maxScore = !empty($stalenessScores) ? max($stalenessScores) : 100; + + // Second pass: process pages and normalize scores + foreach ($csvData as $row) { + $page = array_combine($headers, $row); + + // Normalize staleness score to 0-100 range (0 = best, 100 = worst) + if (isset($page['staleness_score']) && is_numeric($page['staleness_score'])) { + $originalScore = (float)$page['staleness_score']; + + // Avoid division by zero + if ($maxScore > $minScore) { + $normalizedScore = ($originalScore - $minScore) / ($maxScore - $minScore) * 100; + } else { + $normalizedScore = 50; // Default to middle value if all scores are the same + } + + // Round to 2 decimal places + $page['staleness_score'] = round($normalizedScore, 2); + } + $wikiPages[$page['key']][$page['language']] = $page; } + // Identify pages missing French translations + foreach ($wikiPages as $key => $languages) { + if (isset($languages['en']) && !isset($languages['fr'])) { + $missingTranslations[$key] = $languages['en']; + } + } + + // Sort wiki pages by staleness score (descending) + uasort($wikiPages, function($a, $b) { + $scoreA = isset($a['en']) && isset($a['fr']) && isset($a['en']['staleness_score']) ? (float)$a['en']['staleness_score'] : 0; + $scoreB = isset($b['en']) && isset($b['fr']) && isset($b['en']['staleness_score']) ? (float)$b['en']['staleness_score'] : 0; + return $scoreB <=> $scoreA; + }); + return $this->render('admin/wiki.html.twig', [ 'wiki_pages' => $wikiPages, + 'missing_translations' => $missingTranslations, + ]); + } + + #[Route('/admin/wiki/compare/{key}', name: 'app_admin_wiki_compare')] + public function compare(string $key): Response + { + $csvFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/wiki_pages.csv'; + $jsonFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/outdated_pages.json'; + + if (!file_exists($csvFile)) { + $this->addFlash('error', 'Le fichier wiki_pages.csv n\'existe pas.'); + return $this->redirectToRoute('app_admin_index'); + } + + $csvData = array_map('str_getcsv', file($csvFile)); + $headers = array_shift($csvData); + + // Process CSV data to find the requested key + $enPage = null; + $frPage = null; + + foreach ($csvData as $row) { + $page = array_combine($headers, $row); + if ($page['key'] === $key) { + if ($page['language'] === 'en') { + $enPage = $page; + } elseif ($page['language'] === 'fr') { + $frPage = $page; + } + } + } + + // If English page doesn't exist, redirect back with error + if (!$enPage) { + $this->addFlash('error', 'La page wiki pour la clé "' . $key . '" n\'existe pas.'); + return $this->redirectToRoute('app_admin_wiki'); + } + + // Get detailed content comparison from JSON file + $detailedComparison = null; + $mediaDiff = 0; + + if (file_exists($jsonFile)) { + $jsonData = json_decode(file_get_contents($jsonFile), true); + + foreach ($jsonData as $page) { + if ($page['key'] === $key) { + $detailedComparison = [ + 'section_comparison' => $page['section_comparison'] ?? null, + 'link_comparison' => $page['link_comparison'] ?? null, + 'media_comparison' => $page['media_comparison'] ?? null + ]; + + $mediaDiff = $page['media_diff'] ?? 0; + break; + } + } + } + + // Calculate staleness score components + $scoreComponents = []; + + if ($frPage) { + // Calculate date difference in days + $dateDiff = 0; + if ($enPage['last_modified'] && $frPage['last_modified']) { + $enDate = \DateTime::createFromFormat('Y-m-d', $enPage['last_modified']); + $frDate = \DateTime::createFromFormat('Y-m-d', $frPage['last_modified']); + if ($enDate && $frDate) { + $dateDiff = ($enDate->getTimestamp() - $frDate->getTimestamp()) / (60 * 60 * 24); + } + } + + // Calculate content differences + $wordDiff = $enPage['word_count'] - $frPage['word_count']; + $sectionDiff = $enPage['sections'] - $frPage['sections']; + $linkDiff = $enPage['link_count'] - $frPage['link_count']; + + // Calculate score components + $dateComponent = abs($dateDiff) * 0.2; + $wordComponent = abs($wordDiff) / 100 * 0.5; + $sectionComponent = abs($sectionDiff) * 0.15; + $linkComponent = abs($linkDiff) / 10 * 0.15; + + $scoreComponents = [ + 'date' => [ + 'value' => $dateDiff, + 'weight' => 0.2, + 'component' => $dateComponent, + 'description' => 'Différence de date (en jours)' + ], + 'word' => [ + 'value' => $wordDiff, + 'weight' => 0.5, + 'component' => $wordComponent, + 'description' => 'Différence de nombre de mots' + ], + 'section' => [ + 'value' => $sectionDiff, + 'weight' => 0.15, + 'component' => $sectionComponent, + 'description' => 'Différence de nombre de sections' + ], + 'link' => [ + 'value' => $linkDiff, + 'weight' => 0.15, + 'component' => $linkComponent, + 'description' => 'Différence de nombre de liens' + ] + ]; + + // Add media component if available + if (isset($enPage['media_count']) && isset($frPage['media_count'])) { + $mediaComponent = abs($mediaDiff) / 5 * 0.1; + $scoreComponents['media'] = [ + 'value' => $mediaDiff, + 'weight' => 0.1, + 'component' => $mediaComponent, + 'description' => 'Différence de nombre d\'images' + ]; + + // Adjust other weights to maintain total of 1.0 + $scoreComponents['date']['weight'] = 0.2; + $scoreComponents['word']['weight'] = 0.45; + $scoreComponents['section']['weight'] = 0.15; + $scoreComponents['link']['weight'] = 0.1; + } + } + + // Create URL for new French page if it doesn't exist + $createFrUrl = null; + if (!$frPage) { + $createFrUrl = 'https://wiki.openstreetmap.org/wiki/FR:Key:' . $key; + } + + // Format section titles for copy functionality + $enSections = ''; + $frSections = ''; + + if ($detailedComparison && $detailedComparison['section_comparison']) { + // English sections + if ($enPage) { + $enSectionsList = []; + + // Add common sections + foreach ($detailedComparison['section_comparison']['common'] as $section) { + $enSectionsList[] = str_repeat('=', $section['en']['level']) . ' ' . + $section['en']['title'] . ' ' . + str_repeat('=', $section['en']['level']); + } + + // Add English-only sections + foreach ($detailedComparison['section_comparison']['en_only'] as $section) { + $enSectionsList[] = str_repeat('=', $section['level']) . ' ' . + $section['title'] . ' ' . + str_repeat('=', $section['level']) . ' (EN only)'; + } + + $enSections = implode("\n", $enSectionsList); + } + + // French sections + if ($frPage) { + $frSectionsList = []; + + // Add common sections + foreach ($detailedComparison['section_comparison']['common'] as $section) { + $frSectionsList[] = str_repeat('=', $section['fr']['level']) . ' ' . + $section['fr']['title'] . ' ' . + str_repeat('=', $section['fr']['level']); + } + + // Add French-only sections + foreach ($detailedComparison['section_comparison']['fr_only'] as $section) { + $frSectionsList[] = str_repeat('=', $section['level']) . ' ' . + $section['title'] . ' ' . + str_repeat('=', $section['level']) . ' (FR only)'; + } + + $frSections = implode("\n", $frSectionsList); + } + } + + // Format links for copy functionality + $enLinks = ''; + $frLinks = ''; + + if ($detailedComparison && $detailedComparison['link_comparison']) { + // English links + if ($enPage) { + $enLinksList = []; + + // Add common links + foreach ($detailedComparison['link_comparison']['common'] as $link) { + $enLinksList[] = $link['en']['text'] . ' - ' . $link['en']['href']; + } + + // Add English-only links + foreach ($detailedComparison['link_comparison']['en_only'] as $link) { + $enLinksList[] = $link['text'] . ' - ' . $link['href'] . ' (EN only)'; + } + + $enLinks = implode("\n", $enLinksList); + } + + // French links + if ($frPage) { + $frLinksList = []; + + // Add common links + foreach ($detailedComparison['link_comparison']['common'] as $link) { + $frLinksList[] = $link['fr']['text'] . ' - ' . $link['fr']['href']; + } + + // Add French-only links + foreach ($detailedComparison['link_comparison']['fr_only'] as $link) { + $frLinksList[] = $link['text'] . ' - ' . $link['href'] . ' (FR only)'; + } + + $frLinks = implode("\n", $frLinksList); + } + } + + return $this->render('admin/wiki_compare.html.twig', [ + 'key' => $key, + 'en_page' => $enPage, + 'fr_page' => $frPage, + 'score_components' => $scoreComponents, + 'create_fr_url' => $createFrUrl, + 'detailed_comparison' => $detailedComparison, + 'en_sections' => $enSections, + 'fr_sections' => $frSections, + 'en_links' => $enLinks, + 'fr_links' => $frLinks ]); } } \ No newline at end of file diff --git a/templates/admin/followup_theme_graph.html.twig b/templates/admin/followup_theme_graph.html.twig index c4833dfa..4a3907b9 100644 --- a/templates/admin/followup_theme_graph.html.twig +++ b/templates/admin/followup_theme_graph.html.twig @@ -7,6 +7,13 @@ +
Comparaison détaillée des pages wiki en français et en anglais pour la clé OSM "{{ key }}".
+ + {% if fr_page %} ++ Dernière modification: {{ en_page.last_modified }} +
++ Dernière modification: {{ fr_page.last_modified }} +
+{{ media.en.alt }}
#} +{#{{ media.alt }}
+{{ media.fr.alt }}
#} +{#{{ media.alt }}
+Texte | +URL | +
---|---|
{{ link.en.text }} | +{{ link.en.href|slice(0, 30) }}... | +
+ {{ detailed_comparison.link_comparison.common|length - 10 }} liens supplémentaires... + | +
Texte | +URL | +
---|---|
{{ link.text }} | +{{ link.href|slice(0, 30) }}... | +
+ {{ detailed_comparison.link_comparison.en_only|length - 10 }} liens supplémentaires... + | +
Texte | +URL | +
---|---|
{{ link.fr.text }} | +{{ link.fr.href|slice(0, 30) }}... | +
+ {{ detailed_comparison.link_comparison.common|length - 10 }} liens supplémentaires... + | +
Texte | +URL | +
---|---|
{{ link.text }} | +{{ link.href|slice(0, 30) }}... | +
+ {{ detailed_comparison.link_comparison.fr_only|length - 10 }} liens supplémentaires... + | +
La page wiki pour la clé "{{ key }}" n'existe pas en français.
+Vous pouvez contribuer en créant cette page sur le wiki OpenStreetMap.
++ Dernière modification: {{ en_page.last_modified }} +
+Vous pouvez créer la page française en suivant ces étapes :
+Le score de décrépitude est calculé en prenant en compte plusieurs facteurs, avec une pondération plus importante pour la différence de nombre de mots :
+ +Facteur | +Valeur | +Poids | +Contribution | +
---|---|---|---|
{{ component.description }} | +{{ component.value }} | +{{ component.weight * 100 }}% | +{{ component.component|round(2) }} | +
Score total | ++ {% set total_score = 0 %} + {% for key, component in score_components %} + {% set total_score = total_score + component.component %} + {% endfor %} + {{ total_score|round(2) }} + | +
Comment interpréter ce score :
+