add history in articles measures
This commit is contained in:
parent
1ed74c2e2f
commit
381f378db4
9 changed files with 1678 additions and 195 deletions
|
@ -44,6 +44,49 @@ class WikiController extends AbstractController
|
||||||
'json_exists' => file_exists($outdatedPagesFile)
|
'json_exists' => file_exists($outdatedPagesFile)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the evolution of page rankings over time
|
||||||
|
*/
|
||||||
|
#[Route('/wiki/rankings', name: 'app_admin_wiki_rankings')]
|
||||||
|
public function pageRankings(): Response
|
||||||
|
{
|
||||||
|
$rankingsFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/page_rankings.json';
|
||||||
|
|
||||||
|
$timestamps = [];
|
||||||
|
$pages = [];
|
||||||
|
$globalMetrics = [];
|
||||||
|
$lastUpdated = null;
|
||||||
|
|
||||||
|
if (file_exists($rankingsFile)) {
|
||||||
|
// Load the rankings data
|
||||||
|
try {
|
||||||
|
$rankingsData = json_decode(file_get_contents($rankingsFile), true);
|
||||||
|
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE) {
|
||||||
|
$timestamps = $rankingsData['timestamps'] ?? [];
|
||||||
|
$pages = $rankingsData['pages'] ?? [];
|
||||||
|
$globalMetrics = $rankingsData['global_metrics'] ?? [];
|
||||||
|
|
||||||
|
// Get the last timestamp as last_updated
|
||||||
|
if (!empty($timestamps)) {
|
||||||
|
$lastUpdated = end($timestamps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Log the error
|
||||||
|
error_log("Error loading rankings data: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('admin/wiki_rankings.html.twig', [
|
||||||
|
'timestamps' => $timestamps,
|
||||||
|
'pages' => $pages,
|
||||||
|
'global_metrics' => $globalMetrics,
|
||||||
|
'last_updated' => $lastUpdated,
|
||||||
|
'json_exists' => file_exists($rankingsFile)
|
||||||
|
]);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Detects incorrect heading hierarchies in a list of sections
|
* Detects incorrect heading hierarchies in a list of sections
|
||||||
* For example, h4 directly under h2 without h3 in between
|
* For example, h4 directly under h2 without h3 in between
|
||||||
|
@ -1306,6 +1349,9 @@ EOT;
|
||||||
$detailedComparison = null;
|
$detailedComparison = null;
|
||||||
$mediaDiff = 0;
|
$mediaDiff = 0;
|
||||||
$historyData = null;
|
$historyData = null;
|
||||||
|
$prevPage = null;
|
||||||
|
$nextPage = null;
|
||||||
|
$stalenessDistribution = null;
|
||||||
|
|
||||||
if (file_exists($jsonFile)) {
|
if (file_exists($jsonFile)) {
|
||||||
// Use memory-efficient approach to extract only the necessary data
|
// Use memory-efficient approach to extract only the necessary data
|
||||||
|
@ -1376,6 +1422,91 @@ EOT;
|
||||||
|
|
||||||
// Combine them into a single array
|
// Combine them into a single array
|
||||||
$allPages = array_merge($regularPages, $specificPages);
|
$allPages = array_merge($regularPages, $specificPages);
|
||||||
|
|
||||||
|
// Sort pages by staleness score (descending)
|
||||||
|
usort($allPages, function($a, $b) {
|
||||||
|
$scoreA = $a['staleness_score'] ?? 0;
|
||||||
|
$scoreB = $b['staleness_score'] ?? 0;
|
||||||
|
return $scoreB <=> $scoreA; // Descending order
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find the current page index in the sorted array
|
||||||
|
$currentIndex = -1;
|
||||||
|
foreach ($allPages as $index => $page) {
|
||||||
|
if (isset($page['key']) && $page['key'] === $key) {
|
||||||
|
$currentIndex = $index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine previous and next pages
|
||||||
|
if ($currentIndex > 0) {
|
||||||
|
$prevPage = $allPages[$currentIndex - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($currentIndex < count($allPages) - 1 && $currentIndex >= 0) {
|
||||||
|
$nextPage = $allPages[$currentIndex + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create staleness score distribution data for histogram
|
||||||
|
$stalenessScores = [];
|
||||||
|
foreach ($allPages as $page) {
|
||||||
|
if (isset($page['staleness_score'])) {
|
||||||
|
$stalenessScores[] = $page['staleness_score'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($stalenessScores)) {
|
||||||
|
// Calculate statistics
|
||||||
|
$min = min($stalenessScores);
|
||||||
|
$max = max($stalenessScores);
|
||||||
|
$avg = array_sum($stalenessScores) / count($stalenessScores);
|
||||||
|
$median = $this->calculateMedian($stalenessScores);
|
||||||
|
|
||||||
|
// Create histogram bins (10 bins)
|
||||||
|
$binCount = 10;
|
||||||
|
$binSize = ($max - $min) / $binCount;
|
||||||
|
$bins = [];
|
||||||
|
$binLabels = [];
|
||||||
|
|
||||||
|
// Initialize bins
|
||||||
|
for ($i = 0; $i < $binCount; $i++) {
|
||||||
|
$bins[$i] = 0;
|
||||||
|
$binStart = $min + ($i * $binSize);
|
||||||
|
$binEnd = $binStart + $binSize;
|
||||||
|
$binLabels[$i] = round($binStart, 1) . ' - ' . round($binEnd, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count scores in each bin
|
||||||
|
foreach ($stalenessScores as $score) {
|
||||||
|
$binIndex = min($binCount - 1, floor(($score - $min) / $binSize));
|
||||||
|
$bins[$binIndex]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find which bin the current page falls into
|
||||||
|
$currentPageScore = 0;
|
||||||
|
foreach ($allPages as $page) {
|
||||||
|
if (isset($page['key']) && $page['key'] === $key && isset($page['staleness_score'])) {
|
||||||
|
$currentPageScore = $page['staleness_score'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentPageBin = min($binCount - 1, floor(($currentPageScore - $min) / $binSize));
|
||||||
|
|
||||||
|
$stalenessDistribution = [
|
||||||
|
'scores' => $stalenessScores,
|
||||||
|
'min' => $min,
|
||||||
|
'max' => $max,
|
||||||
|
'avg' => $avg,
|
||||||
|
'median' => $median,
|
||||||
|
'bins' => $bins,
|
||||||
|
'binLabels' => $binLabels,
|
||||||
|
'currentPageScore' => $currentPageScore,
|
||||||
|
'currentPageBin' => $currentPageBin,
|
||||||
|
'totalPages' => count($stalenessScores)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// Find the page with the matching key
|
// Find the page with the matching key
|
||||||
foreach ($allPages as $page) {
|
foreach ($allPages as $page) {
|
||||||
|
@ -1792,7 +1923,10 @@ EOT;
|
||||||
'fr_sections' => $frSections,
|
'fr_sections' => $frSections,
|
||||||
'en_links' => $enLinks,
|
'en_links' => $enLinks,
|
||||||
'fr_links' => $frLinks,
|
'fr_links' => $frLinks,
|
||||||
'history_data' => $historyData
|
'history_data' => $historyData,
|
||||||
|
'prev_page' => $prevPage,
|
||||||
|
'next_page' => $nextPage,
|
||||||
|
'staleness_distribution' => $stalenessDistribution
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2207,4 +2341,30 @@ EOT;
|
||||||
{
|
{
|
||||||
return $this->extractJsonArrayByKey($filePath, 'specific_pages', $maxPages);
|
return $this->extractJsonArrayByKey($filePath, 'specific_pages', $maxPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the median value of an array of numbers
|
||||||
|
*
|
||||||
|
* @param array $array Array of numbers
|
||||||
|
* @return float The median value
|
||||||
|
*/
|
||||||
|
private function calculateMedian(array $array): float
|
||||||
|
{
|
||||||
|
sort($array);
|
||||||
|
$count = count($array);
|
||||||
|
|
||||||
|
if ($count === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$middle = floor($count / 2);
|
||||||
|
|
||||||
|
if ($count % 2 === 0) {
|
||||||
|
// Even number of elements, average the two middle values
|
||||||
|
return ($array[$middle - 1] + $array[$middle]) / 2;
|
||||||
|
} else {
|
||||||
|
// Odd number of elements, return the middle value
|
||||||
|
return $array[$middle];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -56,6 +56,11 @@
|
||||||
<i class="bi bi-graph-up"></i> Scores de décrépitude
|
<i class="bi bi-graph-up"></i> Scores de décrépitude
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if app.request.get('_route') == 'app_admin_wiki_rankings' %}active{% endif %}" href="{{ path('app_admin_wiki_rankings') }}">
|
||||||
|
<i class="bi bi-bar-chart-line"></i> Évolution des classements
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -105,9 +105,32 @@
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<div>
|
||||||
|
{% if prev_page is defined and prev_page is not null %}
|
||||||
|
<a href="{{ path('app_admin_wiki_compare', {'key': prev_page.key}) }}" class="btn btn-outline-primary">
|
||||||
|
<i class="bi bi-arrow-left"></i> Page précédente: {{ prev_page.key }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-outline-secondary" disabled>
|
||||||
|
<i class="bi bi-arrow-left"></i> Pas de page précédente
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{% if next_page is defined and next_page is not null %}
|
||||||
|
<a href="{{ path('app_admin_wiki_compare', {'key': next_page.key}) }}" class="btn btn-outline-primary">
|
||||||
|
Page suivante: {{ next_page.key }} <i class="bi bi-arrow-right"></i>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-outline-secondary" disabled>
|
||||||
|
Pas de page suivante <i class="bi bi-arrow-right"></i>
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h1>Comparaison Wiki OpenStreetMap - {{ key }}
|
<h1>Comparaison Wiki OpenStreetMap - {{ key }}
|
||||||
|
|
||||||
|
|
||||||
{% if en_page.is_specific_page is defined and en_page.is_specific_page %}
|
{% if en_page.is_specific_page is defined and en_page.is_specific_page %}
|
||||||
<a href="{{ fr_page.url|default('https://wiki.openstreetmap.org/wiki/FR:' ~ key) }}">fr</a>
|
<a href="{{ fr_page.url|default('https://wiki.openstreetmap.org/wiki/FR:' ~ key) }}">fr</a>
|
||||||
<a href="{{ en_page.url }}">en</a>
|
<a href="{{ en_page.url }}">en</a>
|
||||||
|
@ -117,7 +140,6 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
{% if en_page.is_specific_page is defined and en_page.is_specific_page %}
|
{% if en_page.is_specific_page is defined and en_page.is_specific_page %}
|
||||||
Comparaison détaillée des pages wiki en français et en anglais pour "{{ key }}".
|
Comparaison détaillée des pages wiki en français et en anglais pour "{{ key }}".
|
||||||
|
@ -159,6 +181,63 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if history_data is defined and history_data is not empty %}
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h2>Évolution du classement</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<canvas id="rankingEvolutionChart" width="800" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Score de décrépitude</th>
|
||||||
|
<th>Différence de mots</th>
|
||||||
|
<th>Différence de sections</th>
|
||||||
|
<th>Différence de liens</th>
|
||||||
|
<th>Différence d'images</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for entry in history_data %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ entry.date }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="progress" style="height: 20px;">
|
||||||
|
{% set score = entry.metrics.staleness_score %}
|
||||||
|
{% set score_class = score > 70 ? 'bg-danger' : (score > 40 ? 'bg-warning' : 'bg-success') %}
|
||||||
|
<div class="progress-bar {{ score_class }}" role="progressbar"
|
||||||
|
style="width: {{ score }}%;"
|
||||||
|
aria-valuenow="{{ score }}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
{{ score }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{{ entry.metrics.word_diff }}</td>
|
||||||
|
<td>{{ entry.metrics.section_diff }}</td>
|
||||||
|
<td>{{ entry.metrics.link_diff }}</td>
|
||||||
|
<td>{{ entry.metrics.media_diff }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# suggestions de grammalecte #}
|
{# suggestions de grammalecte #}
|
||||||
{% if fr_page is defined and fr_page is not null %}
|
{% if fr_page is defined and fr_page is not null %}
|
||||||
{% if detailed_comparison is defined and detailed_comparison is not null and detailed_comparison.section_comparison is defined and detailed_comparison.section_comparison is not null %}
|
{% if detailed_comparison is defined and detailed_comparison is not null and detailed_comparison.section_comparison is defined and detailed_comparison.section_comparison is not null %}
|
||||||
|
@ -225,6 +304,59 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h3>Sections communes</h3>
|
||||||
|
<span class="badge bg-light text-dark">
|
||||||
|
{% if detailed_comparison['section_comparison']['common'] is defined %}
|
||||||
|
{{ detailed_comparison['section_comparison']['common']|length }} sections communes
|
||||||
|
{% else %}
|
||||||
|
0 sections communes
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="bg-primary text-white">Section anglaise</th>
|
||||||
|
<th class="bg-info text-white">Section française</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% if detailed_comparison['section_comparison']['common'] is defined and detailed_comparison['section_comparison']['common'] is iterable %}
|
||||||
|
{% for section in detailed_comparison['section_comparison']['common'] %}
|
||||||
|
<tr>
|
||||||
|
<td class="title-level-{{ section.en.level|default(1) }}">
|
||||||
|
<span class="badge bg-secondary">h{{ section.en.level|default(1) }}</span>
|
||||||
|
{% if section.en.title is defined and section.en.title is not empty %}
|
||||||
|
<span class="section-title">{{ section.en.title }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="title-level-{{ section.fr.level|default(1) }}">
|
||||||
|
<span class="badge bg-secondary">h{{ section.fr.level|default(1) }}</span>
|
||||||
|
{% if section.fr.title is defined and section.fr.title is not empty %}
|
||||||
|
<span class="section-title">{{ section.fr.title }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="text-center">Aucune section commune trouvée</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="text-center mt-3">
|
<div class="text-center mt-3">
|
||||||
{% if detailed_comparison is defined and detailed_comparison is not null and detailed_comparison.section_comparison is defined and detailed_comparison.section_comparison is not null %}
|
{% if detailed_comparison is defined and detailed_comparison is not null and detailed_comparison.section_comparison is defined and detailed_comparison.section_comparison is not null %}
|
||||||
<button class="btn btn-outline-secondary copy-btn" data-content="different-sections">
|
<button class="btn btn-outline-secondary copy-btn" data-content="different-sections">
|
||||||
|
@ -519,142 +651,311 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h3>Images communes</h3>
|
||||||
|
<span class="badge bg-light text-dark">
|
||||||
|
{% if detailed_comparison.media_comparison.common is defined %}
|
||||||
|
{{ detailed_comparison.media_comparison.common|length }} images communes
|
||||||
|
{% else %}
|
||||||
|
0 images communes
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
{% if detailed_comparison.media_comparison.common is defined and detailed_comparison.media_comparison.common is iterable %}
|
||||||
|
{% for media in detailed_comparison.media_comparison.common %}
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5>Version anglaise</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<img src="{{ media.en.src }}" class="img-fluid"
|
||||||
|
alt="{{ media.en.alt }}"
|
||||||
|
style="max-height: 150px; object-fit: contain;">
|
||||||
|
<p class="card-text small mt-2">{{ media.en.alt }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header bg-info text-white">
|
||||||
|
<h5>Version française</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<img src="{{ media.fr.src }}" class="img-fluid"
|
||||||
|
alt="{{ media.fr.alt }}"
|
||||||
|
style="max-height: 150px; object-fit: contain;">
|
||||||
|
<p class="card-text small mt-2">{{ media.fr.alt }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
Aucune image commune trouvée entre les versions anglaise et française.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# {% if detailed_comparison and detailed_comparison.link_comparison %} #}
|
{% if detailed_comparison and detailed_comparison.link_comparison %}
|
||||||
{# <div class="card mb-4"> #}
|
<div class="card mb-4">
|
||||||
{# <div class="card-header"> #}
|
<div class="card-header">
|
||||||
{# <h2>Comparaison des liens</h2> #}
|
<h2>Comparaison des liens</h2>
|
||||||
{# </div> #}
|
</div>
|
||||||
{# <div class="card-body"> #}
|
<div class="card-body">
|
||||||
{# <div class="row"> #}
|
<div class="row">
|
||||||
{# <div class="col-md-6"> #}
|
<div class="col-md-6">
|
||||||
{# <div class="card h-100"> #}
|
<div class="card h-100">
|
||||||
{# <div class="card-header bg-primary text-white"> #}
|
<div class="card-header bg-primary text-white">
|
||||||
{# <h3>Liens en anglais</h3> #}
|
<h3>Liens uniquement en anglais</h3>
|
||||||
{# <span class="badge bg-light text-dark">{{ en_page.link_count }} liens</span> #}
|
<span class="badge bg-light text-dark">
|
||||||
{# </div> #}
|
{% if detailed_comparison.link_comparison.en_only is defined %}
|
||||||
{# <div class="card-body"> #}
|
{{ detailed_comparison.link_comparison.en_only|length }} liens
|
||||||
|
{% else %}
|
||||||
|
0 liens
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Texte du lien</th>
|
||||||
|
<th>URL</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% if detailed_comparison.link_comparison.en_only is defined and detailed_comparison.link_comparison.en_only is iterable %}
|
||||||
|
{% for link in detailed_comparison.link_comparison.en_only %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ link.text }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ link.href }}" target="_blank" class="small">
|
||||||
|
{{ link.href|slice(0, 30) }}{% if link.href|length > 30 %}...{% endif %}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="text-center">Aucun lien uniquement en anglais</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header bg-info text-white">
|
||||||
|
<h3>Liens uniquement en français</h3>
|
||||||
|
<span class="badge bg-light text-dark">
|
||||||
|
{% if detailed_comparison.link_comparison.fr_only is defined %}
|
||||||
|
{{ detailed_comparison.link_comparison.fr_only|length }} liens
|
||||||
|
{% else %}
|
||||||
|
0 liens
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Texte du lien</th>
|
||||||
|
<th>URL</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% if detailed_comparison.link_comparison.fr_only is defined and detailed_comparison.link_comparison.fr_only is iterable %}
|
||||||
|
{% for link in detailed_comparison.link_comparison.fr_only %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ link.text }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ link.href }}" target="_blank" class="small">
|
||||||
|
{{ link.href|slice(0, 30) }}{% if link.href|length > 30 %}...{% endif %}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="text-center">Aucun lien uniquement en français</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h3>Liens communs</h3>
|
||||||
|
<span class="badge bg-light text-dark">
|
||||||
|
{% if detailed_comparison.link_comparison.common is defined %}
|
||||||
|
{{ detailed_comparison.link_comparison.common|length }} liens communs
|
||||||
|
{% else %}
|
||||||
|
0 liens communs
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="bg-primary text-white">Texte EN</th>
|
||||||
|
<th class="bg-primary text-white">URL EN</th>
|
||||||
|
<th class="bg-info text-white">Texte FR</th>
|
||||||
|
<th class="bg-info text-white">URL FR</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% if detailed_comparison.link_comparison.common is defined and detailed_comparison.link_comparison.common is iterable %}
|
||||||
|
{% for link in detailed_comparison.link_comparison.common %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ link.en.text }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ link.en.href }}" target="_blank" class="small">
|
||||||
|
{{ link.en.href|slice(0, 30) }}{% if link.en.href|length > 30 %}...{% endif %}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ link.fr.text }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ link.fr.href }}" target="_blank" class="small">
|
||||||
|
{{ link.fr.href|slice(0, 30) }}{% if link.fr.href|length > 30 %}...{% endif %}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center">Aucun lien commun trouvé</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if detailed_comparison and detailed_comparison.category_comparison %}
|
||||||
{# <h4>Comparaison des liens côte à côte</h4> #}
|
<div class="card mb-4">
|
||||||
{# <div class="table-responsive"> #}
|
<div class="card-header">
|
||||||
{# <table class="table table-sm"> #}
|
<h2>Comparaison des catégories</h2>
|
||||||
{# <thead> #}
|
</div>
|
||||||
{# <tr> #}
|
<div class="card-body">
|
||||||
{# <th class="bg-primary text-white">Texte EN</th> #}
|
<div class="row">
|
||||||
{# <th class="bg-primary text-white">URL EN</th> #}
|
<div class="col-md-6">
|
||||||
{# <th class="bg-info text-white">Texte FR</th> #}
|
<div class="card h-100">
|
||||||
{# <th class="bg-info text-white">URL FR</th> #}
|
<div class="card-header bg-primary text-white">
|
||||||
{# </tr> #}
|
<h3>Catégories uniquement en anglais</h3>
|
||||||
{# </thead> #}
|
<span class="badge bg-light text-dark">
|
||||||
{# <tbody> #}
|
{% if detailed_comparison.category_comparison.en_only is defined %}
|
||||||
{# {% set en_links = detailed_comparison.link_comparison.en_only %} #}
|
{{ detailed_comparison.category_comparison.en_only|length }} catégories
|
||||||
{# {% set fr_links = detailed_comparison.link_comparison.fr_only %} #}
|
{% else %}
|
||||||
{# {% set max_links = max(en_links|length, fr_links|length) %} #}
|
0 catégories
|
||||||
|
{% endif %}
|
||||||
{# {% for i in 0..(max_links - 1) %} #}
|
</span>
|
||||||
{# <tr> #}
|
</div>
|
||||||
{# {% if i < en_links|length %} #}
|
<div class="card-body">
|
||||||
{# <td class="bg-light">{{ en_links[i].text }}</td> #}
|
<ul class="list-group">
|
||||||
{# <td class="bg-light"><a href="{{ en_links[i].href }}" #}
|
{% if detailed_comparison.category_comparison.en_only is defined and detailed_comparison.category_comparison.en_only is iterable and detailed_comparison.category_comparison.en_only|length > 0 %}
|
||||||
{# target="_blank" #}
|
{% for category in detailed_comparison.category_comparison.en_only %}
|
||||||
{# class="small">{{ en_links[i].href|slice(0, 30) }} #}
|
<li class="list-group-item list-group-item-warning">{{ category }}</li>
|
||||||
{# ...</a></td> #}
|
{% endfor %}
|
||||||
{# {% else %} #}
|
{% else %}
|
||||||
{# <td class="bg-light"></td> #}
|
<li class="list-group-item text-center">Aucune catégorie uniquement en anglais</li>
|
||||||
{# <td class="bg-light"></td> #}
|
{% endif %}
|
||||||
{# {% endif %} #}
|
</ul>
|
||||||
|
</div>
|
||||||
{# {% if i < fr_links|length %} #}
|
</div>
|
||||||
{# <td>{{ fr_links[i].text }}</td> #}
|
</div>
|
||||||
{# <td><a href="{{ fr_links[i].href }}" target="_blank" #}
|
<div class="col-md-6">
|
||||||
{# class="small">{{ fr_links[i].href|slice(0, 30) }} #}
|
<div class="card h-100">
|
||||||
{# ...</a></td> #}
|
<div class="card-header bg-info text-white">
|
||||||
{# {% else %} #}
|
<h3>Catégories uniquement en français</h3>
|
||||||
{# <td></td> #}
|
<span class="badge bg-light text-dark">
|
||||||
{# <td></td> #}
|
{% if detailed_comparison.category_comparison.fr_only is defined %}
|
||||||
{# {% endif %} #}
|
{{ detailed_comparison.category_comparison.fr_only|length }} catégories
|
||||||
{# </tr> #}
|
{% else %}
|
||||||
{# {% endfor %} #}
|
0 catégories
|
||||||
{# </tbody> #}
|
{% endif %}
|
||||||
{# </table> #}
|
</span>
|
||||||
{# </div> #}
|
</div>
|
||||||
{# </div> #}
|
<div class="card-body">
|
||||||
{# </div> #}
|
<ul class="list-group">
|
||||||
{# </div> #}
|
{% if detailed_comparison.category_comparison.fr_only is defined and detailed_comparison.category_comparison.fr_only is iterable and detailed_comparison.category_comparison.fr_only|length > 0 %}
|
||||||
{# </div> #}
|
{% for category in detailed_comparison.category_comparison.fr_only %}
|
||||||
{# </div> #}
|
<li class="list-group-item list-group-item-info">{{ category }}</li>
|
||||||
{# </div> #}
|
{% endfor %}
|
||||||
{# {% endif %} #}
|
{% else %}
|
||||||
|
<li class="list-group-item text-center">Aucune catégorie uniquement en français</li>
|
||||||
{# {% if detailed_comparison and detailed_comparison.category_comparison %} #}
|
{% endif %}
|
||||||
{# <div class="card mb-4"> #}
|
</ul>
|
||||||
{# <div class="card-header"> #}
|
</div>
|
||||||
{# <h2>Comparaison des catégories</h2> #}
|
</div>
|
||||||
{# </div> #}
|
</div>
|
||||||
{# <div class="card-body"> #}
|
</div>
|
||||||
{# <div class="row"> #}
|
|
||||||
{# <div class="col-md-6"> #}
|
<div class="row mt-4">
|
||||||
{# <div class="card h-100"> #}
|
<div class="col-12">
|
||||||
{# <div class="card-header bg-primary text-white"> #}
|
<div class="card">
|
||||||
{# <h3>Catégories en anglais</h3> #}
|
<div class="card-header bg-success text-white">
|
||||||
{# <span class="badge bg-light text-dark"> #}
|
<h3>Catégories communes</h3>
|
||||||
{# {{ (detailed_comparison.category_comparison.en_only|length + detailed_comparison.category_comparison.common|length) }} catégories #}
|
<span class="badge bg-light text-dark">
|
||||||
{# </span> #}
|
{% if detailed_comparison.category_comparison.common is defined %}
|
||||||
{# </div> #}
|
{{ detailed_comparison.category_comparison.common|length }} catégories communes
|
||||||
{# <div class="card-body"> #}
|
{% else %}
|
||||||
{# <h4>Catégories communes #}
|
0 catégories communes
|
||||||
{# ({{ detailed_comparison.category_comparison.common|length }})</h4> #}
|
{% endif %}
|
||||||
{# <ul class="list-group mb-3"> #}
|
</span>
|
||||||
{# {% for category in detailed_comparison.category_comparison.common %} #}
|
</div>
|
||||||
{# <li class="list-group-item">{{ category }}</li> #}
|
<div class="card-body">
|
||||||
{# {% endfor %} #}
|
<ul class="list-group">
|
||||||
{# </ul> #}
|
{% if detailed_comparison.category_comparison.common is defined and detailed_comparison.category_comparison.common is iterable and detailed_comparison.category_comparison.common|length > 0 %}
|
||||||
|
{% for category in detailed_comparison.category_comparison.common %}
|
||||||
{# <h4>Catégories uniquement en anglais #}
|
<li class="list-group-item">{{ category }}</li>
|
||||||
{# ({{ detailed_comparison.category_comparison.en_only|length }})</h4> #}
|
{% endfor %}
|
||||||
{# <ul class="list-group"> #}
|
{% else %}
|
||||||
{# {% for category in detailed_comparison.category_comparison.en_only %} #}
|
<li class="list-group-item text-center">Aucune catégorie commune trouvée</li>
|
||||||
{# <li class="list-group-item list-group-item-warning">{{ category }}</li> #}
|
{% endif %}
|
||||||
{# {% endfor %} #}
|
</ul>
|
||||||
{# </ul> #}
|
</div>
|
||||||
{# </div> #}
|
</div>
|
||||||
{# </div> #}
|
</div>
|
||||||
{# </div> #}
|
</div>
|
||||||
{# <div class="col-md-6"> #}
|
</div>
|
||||||
{# <div class="card h-100"> #}
|
</div>
|
||||||
{# <div class="card-header bg-info text-white"> #}
|
{% endif %}
|
||||||
{# <h3>Catégories en français</h3> #}
|
|
||||||
{# <span class="badge bg-light text-dark"> #}
|
|
||||||
{# {{ (detailed_comparison.category_comparison.fr_only|length + detailed_comparison.category_comparison.common|length) }} catégories #}
|
|
||||||
{# </span> #}
|
|
||||||
{# </div> #}
|
|
||||||
{# <div class="card-body"> #}
|
|
||||||
{# <h4>Catégories communes #}
|
|
||||||
{# ({{ detailed_comparison.category_comparison.common|length }})</h4> #}
|
|
||||||
{# <ul class="list-group mb-3"> #}
|
|
||||||
{# {% for category in detailed_comparison.category_comparison.common %} #}
|
|
||||||
{# <li class="list-group-item">{{ category }}</li> #}
|
|
||||||
{# {% endfor %} #}
|
|
||||||
{# </ul> #}
|
|
||||||
|
|
||||||
{# <h4>Catégories uniquement en français #}
|
|
||||||
{# ({{ detailed_comparison.category_comparison.fr_only|length }})</h4> #}
|
|
||||||
{# <ul class="list-group"> #}
|
|
||||||
{# {% for category in detailed_comparison.category_comparison.fr_only %} #}
|
|
||||||
{# <li class="list-group-item list-group-item-info">{{ category }}</li> #}
|
|
||||||
{# {% endfor %} #}
|
|
||||||
{# </ul> #}
|
|
||||||
{# </div> #}
|
|
||||||
{# </div> #}
|
|
||||||
{# </div> #}
|
|
||||||
{# </div> #}
|
|
||||||
{# </div> #}
|
|
||||||
{# </div> #}
|
|
||||||
{# {% endif %} #}
|
|
||||||
{# {% else %} #}
|
{# {% else %} #}
|
||||||
{# <div class="card mb-4"> #}
|
{# <div class="card mb-4"> #}
|
||||||
{# <div class="card-header bg-warning text-dark"> #}
|
{# <div class="card-header bg-warning text-dark"> #}
|
||||||
|
@ -796,6 +1097,41 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if staleness_distribution is defined and staleness_distribution is not null %}
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Répartition des scores de décrépitude</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p>Ce graphique montre la répartition des scores de décrépitude pour toutes les pages wiki et où se situe la page courante :</p>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<canvas id="distributionChart" width="400" height="200"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<p><strong>Statistiques de décrépitude :</strong></p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<ul>
|
||||||
|
<li><strong>Nombre total de pages :</strong> {{ staleness_distribution.totalPages }}</li>
|
||||||
|
<li><strong>Score minimum :</strong> {{ staleness_distribution.min|round(2) }}</li>
|
||||||
|
<li><strong>Score maximum :</strong> {{ staleness_distribution.max|round(2) }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<ul>
|
||||||
|
<li><strong>Score moyen :</strong> {{ staleness_distribution.avg|round(2) }}</li>
|
||||||
|
<li><strong>Score médian :</strong> {{ staleness_distribution.median|round(2) }}</li>
|
||||||
|
<li><strong>Score de cette page :</strong> {{ staleness_distribution.currentPageScore|round(2) }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if history_data is defined and history_data|length > 0 %}
|
{% if history_data is defined and history_data|length > 0 %}
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
@ -1078,6 +1414,162 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create ranking evolution chart if the element exists
|
||||||
|
const rankingEvolutionChartElement = document.getElementById('rankingEvolutionChart');
|
||||||
|
const historyData = {{ history_data|json_encode|raw }};
|
||||||
|
if (rankingEvolutionChartElement && historyData && historyData.length > 0) {
|
||||||
|
// Format the data for the chart
|
||||||
|
const dates = historyData.map(entry => entry.date);
|
||||||
|
const stalenessScores = historyData.map(entry => entry.metrics.staleness_score || 0);
|
||||||
|
const wordDiffs = historyData.map(entry => entry.metrics.word_diff || 0);
|
||||||
|
const sectionDiffs = historyData.map(entry => entry.metrics.section_diff || 0);
|
||||||
|
const linkDiffs = historyData.map(entry => entry.metrics.link_diff || 0);
|
||||||
|
|
||||||
|
// Create the chart
|
||||||
|
new Chart(rankingEvolutionChartElement, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: dates,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Score de décrépitude',
|
||||||
|
data: stalenessScores,
|
||||||
|
borderColor: 'rgba(255, 99, 132, 1)',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||||
|
fill: false,
|
||||||
|
tension: 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Différence de mots / 10',
|
||||||
|
data: wordDiffs.map(val => val / 10),
|
||||||
|
borderColor: 'rgba(54, 162, 235, 1)',
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||||
|
fill: false,
|
||||||
|
tension: 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Différence de sections',
|
||||||
|
data: sectionDiffs,
|
||||||
|
borderColor: 'rgba(255, 206, 86, 1)',
|
||||||
|
backgroundColor: 'rgba(255, 206, 86, 0.2)',
|
||||||
|
fill: false,
|
||||||
|
tension: 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Différence de liens',
|
||||||
|
data: linkDiffs,
|
||||||
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||||
|
fill: false,
|
||||||
|
tension: 0.1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Évolution des métriques au fil du temps'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create distribution histogram if the element exists
|
||||||
|
const distributionChartElement = document.getElementById('distributionChart');
|
||||||
|
if (distributionChartElement) {
|
||||||
|
// Get staleness distribution data from the template
|
||||||
|
const distributionData = {{ staleness_distribution|json_encode|raw }};
|
||||||
|
|
||||||
|
if (distributionData) {
|
||||||
|
// Prepare data for the histogram
|
||||||
|
const labels = distributionData.binLabels;
|
||||||
|
const counts = distributionData.bins;
|
||||||
|
const currentPageBin = distributionData.currentPageBin;
|
||||||
|
|
||||||
|
// Create colors array with the current page bin highlighted
|
||||||
|
const colors = counts.map((_, index) =>
|
||||||
|
index === currentPageBin ? 'rgba(255, 99, 132, 0.8)' : 'rgba(54, 162, 235, 0.6)'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the histogram
|
||||||
|
const ctx = distributionChartElement.getContext('2d');
|
||||||
|
const distributionChart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Nombre de pages',
|
||||||
|
data: counts,
|
||||||
|
backgroundColor: colors,
|
||||||
|
borderColor: colors.map(color => color.replace('0.6', '1').replace('0.8', '1')),
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Nombre de pages'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Score de décrépitude'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Répartition des scores de décrépitude (la page courante est en rouge)'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
title: function(tooltipItems) {
|
||||||
|
const item = tooltipItems[0];
|
||||||
|
const binRange = item.label;
|
||||||
|
return `Score: ${binRange}`;
|
||||||
|
},
|
||||||
|
label: function(context) {
|
||||||
|
let label = 'Nombre de pages: ' + context.raw;
|
||||||
|
if (context.dataIndex === currentPageBin) {
|
||||||
|
label += ' (inclut la page courante)';
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
},
|
||||||
|
afterLabel: function(context) {
|
||||||
|
if (context.dataIndex === currentPageBin) {
|
||||||
|
return `Score de cette page: ${distributionData.currentPageScore.toFixed(2)}`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create history chart if the element exists
|
// Create history chart if the element exists
|
||||||
const historyChartElement = document.getElementById('historyChart');
|
const historyChartElement = document.getElementById('historyChart');
|
||||||
if (historyChartElement) {
|
if (historyChartElement) {
|
||||||
|
|
|
@ -84,50 +84,72 @@ python3 wiki_compare.py</code></pre>
|
||||||
{# {{ page.reason }}#}
|
{# {{ page.reason }}#}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{% if page.word_diff > 0 %}
|
{% if page.word_diff is defined %}
|
||||||
<span class="badge bg-danger">{{ page.word_diff }}</span>
|
{% if page.word_diff > 0 %}
|
||||||
{% elseif page.word_diff < 0 %}
|
<span class="badge bg-danger">{{ page.word_diff }}</span>
|
||||||
<span class="badge bg-success">{{ page.word_diff }}</span>
|
{% elseif page.word_diff < 0 %}
|
||||||
|
<span class="badge bg-success">{{ page.word_diff }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">0</span>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-secondary">0</span>
|
<span class="badge bg-secondary">N/A</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{% if page.section_diff > 0 %}
|
{% if page.section_diff is defined %}
|
||||||
<span class="badge bg-danger">{{ page.section_diff }}</span>
|
{% if page.section_diff > 0 %}
|
||||||
{% elseif page.section_diff < 0 %}
|
<span class="badge bg-danger">{{ page.section_diff }}</span>
|
||||||
<span class="badge bg-success">{{ page.section_diff }}</span>
|
{% elseif page.section_diff < 0 %}
|
||||||
|
<span class="badge bg-success">{{ page.section_diff }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">0</span>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-secondary">0</span>
|
<span class="badge bg-secondary">N/A</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{% if page.link_diff > 0 %}
|
{% if page.link_diff is defined %}
|
||||||
<span class="badge bg-danger">{{ page.link_diff }}</span>
|
{% if page.link_diff > 0 %}
|
||||||
{% elseif page.link_diff < 0 %}
|
<span class="badge bg-danger">{{ page.link_diff }}</span>
|
||||||
<span class="badge bg-success">{{ page.link_diff }}</span>
|
{% elseif page.link_diff < 0 %}
|
||||||
|
<span class="badge bg-success">{{ page.link_diff }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">0</span>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-secondary">0</span>
|
<span class="badge bg-secondary">N/A</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="progress" style="height: 20px;">
|
{% if page.staleness_score is defined %}
|
||||||
{% set score_class = page.staleness_score > 70 ? 'bg-danger' : (page.staleness_score > 40 ? 'bg-warning' : 'bg-success') %}
|
<div class="progress" style="height: 20px;">
|
||||||
<div class="progress-bar {{ score_class }}" role="progressbar"
|
{% set score_class = page.staleness_score > 70 ? 'bg-danger' : (page.staleness_score > 40 ? 'bg-warning' : 'bg-success') %}
|
||||||
style="width: {{ page.staleness_score }}%;"
|
<div class="progress-bar {{ score_class }}" role="progressbar"
|
||||||
aria-valuenow="{{ page.staleness_score }}"
|
style="width: {{ page.staleness_score }}%;"
|
||||||
aria-valuemin="0"
|
aria-valuenow="{{ page.staleness_score }}"
|
||||||
aria-valuemax="100">
|
aria-valuemin="0"
|
||||||
{{ page.staleness_score }}
|
aria-valuemax="100">
|
||||||
|
{{ page.staleness_score }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">N/A</span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<a href="{{ page.en_page.url }}" target="_blank"
|
{% if page.url is defined and page.url %}
|
||||||
class="btn btn-sm btn-outline-primary" title="Version anglaise">
|
<a href="{{ page.url }}" target="_blank"
|
||||||
<i class="bi bi-translate"></i> EN
|
class="btn btn-sm btn-outline-primary" title="Version anglaise">
|
||||||
</a>
|
<i class="bi bi-translate"></i> EN
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" disabled>
|
||||||
|
<i class="bi bi-translate"></i> EN (URL manquante)
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
{% if page.fr_page is defined and page.fr_page %}
|
{% if page.fr_page is defined and page.fr_page %}
|
||||||
{% if page.fr_page.url is defined %}
|
{% if page.fr_page.url is defined %}
|
||||||
<a href="{{ page.fr_page.url }}" target="_blank"
|
<a href="{{ page.fr_page.url }}" target="_blank"
|
||||||
|
@ -207,9 +229,9 @@ python3 wiki_compare.py</code></pre>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
{% if page.en_page.description_img_url is defined and page.en_page.description_img_url %}
|
{% if page.description_img_url is defined and page.description_img_url %}
|
||||||
<div class="me-3">
|
<div class="me-3">
|
||||||
<img src="{{ page.en_page.description_img_url }}"
|
<img src="{{ page.description_img_url }}"
|
||||||
alt="{% if page.key is defined %}{{ page.key }}{% elseif page.title is defined %}{{ page.title }}{% else %}Image{% endif %}"
|
alt="{% if page.key is defined %}{{ page.key }}{% elseif page.title is defined %}{{ page.title }}{% else %}Image{% endif %}"
|
||||||
style="max-width: 80px; max-height: 60px; object-fit: contain;">
|
style="max-width: 80px; max-height: 60px; object-fit: contain;">
|
||||||
</div>
|
</div>
|
||||||
|
@ -223,23 +245,33 @@ python3 wiki_compare.py</code></pre>
|
||||||
{# {{ page.reason }}#}
|
{# {{ page.reason }}#}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="progress" style="height: 20px;">
|
{% if page.staleness_score is defined %}
|
||||||
{% set score_class = page.staleness_score > 70 ? 'bg-danger' : (page.staleness_score > 40 ? 'bg-warning' : 'bg-success') %}
|
<div class="progress" style="height: 20px;">
|
||||||
<div class="progress-bar {{ score_class }}" role="progressbar"
|
{% set score_class = page.staleness_score > 70 ? 'bg-danger' : (page.staleness_score > 40 ? 'bg-warning' : 'bg-success') %}
|
||||||
style="width: {{ page.staleness_score }}%;"
|
<div class="progress-bar {{ score_class }}" role="progressbar"
|
||||||
aria-valuenow="{{ page.staleness_score }}"
|
style="width: {{ page.staleness_score }}%;"
|
||||||
aria-valuemin="0"
|
aria-valuenow="{{ page.staleness_score }}"
|
||||||
aria-valuemax="100">
|
aria-valuemin="0"
|
||||||
{{ page.staleness_score }}
|
aria-valuemax="100">
|
||||||
|
{{ page.staleness_score }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">N/A</span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<a href="{{ page.en_page.url }}" target="_blank"
|
{% if page.url is defined and page.url %}
|
||||||
class="btn btn-sm btn-outline-primary" title="Version anglaise">
|
<a href="{{ page.url }}" target="_blank"
|
||||||
<i class="bi bi-translate"></i> EN
|
class="btn btn-sm btn-outline-primary" title="Version anglaise">
|
||||||
</a>
|
<i class="bi bi-translate"></i> EN
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" disabled>
|
||||||
|
<i class="bi bi-translate"></i> EN (URL manquante)
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
{% if page.fr_page is defined and page.fr_page %}
|
{% if page.fr_page is defined and page.fr_page %}
|
||||||
{% if page.fr_page.url is defined %}
|
{% if page.fr_page.url is defined %}
|
||||||
<a href="{{ page.fr_page.url }}" target="_blank"
|
<a href="{{ page.fr_page.url }}" target="_blank"
|
||||||
|
@ -320,21 +352,26 @@ python3 wiki_compare.py</code></pre>
|
||||||
{% else %}
|
{% else %}
|
||||||
labels.push("Page sans clé");
|
labels.push("Page sans clé");
|
||||||
{% endif %}
|
{% endif %}
|
||||||
scores.push({{ page.staleness_score }});
|
{% if page.staleness_score is defined %}
|
||||||
|
scores.push({{ page.staleness_score }});
|
||||||
|
|
||||||
// Set color based on score
|
// Set color based on score
|
||||||
{% if page.staleness_score > 80 %}
|
{% if page.staleness_score > 80 %}
|
||||||
colors.push('rgba(220, 53, 69, 0.7)'); // danger (red)
|
colors.push('rgba(220, 53, 69, 0.7)'); // danger (red)
|
||||||
{% elseif page.staleness_score > 60 %}
|
{% elseif page.staleness_score > 60 %}
|
||||||
colors.push('rgba(232, 113, 55, 0.7)'); // dark orange
|
colors.push('rgba(232, 113, 55, 0.7)'); // dark orange
|
||||||
{% elseif page.staleness_score > 40 %}
|
{% elseif page.staleness_score > 40 %}
|
||||||
colors.push('rgba(255, 153, 0, 0.7)'); // orange
|
colors.push('rgba(255, 153, 0, 0.7)'); // orange
|
||||||
{% elseif page.staleness_score > 20 %}
|
{% elseif page.staleness_score > 20 %}
|
||||||
colors.push('rgba(255, 193, 7, 0.7)'); // warning (yellow)
|
colors.push('rgba(255, 193, 7, 0.7)'); // warning (yellow)
|
||||||
{% elseif page.staleness_score > 10 %}
|
{% elseif page.staleness_score > 10 %}
|
||||||
colors.push('rgba(140, 195, 38, 0.7)'); // light green
|
colors.push('rgba(140, 195, 38, 0.7)'); // light green
|
||||||
|
{% else %}
|
||||||
|
colors.push('rgba(25, 135, 84, 0.7)'); // success (green)
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
colors.push('rgba(25, 135, 84, 0.7)'); // success (green)
|
scores.push(0);
|
||||||
|
colors.push('rgba(108, 117, 125, 0.7)'); // secondary (gray)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
|
566
templates/admin/wiki_rankings.html.twig
Normal file
566
templates/admin/wiki_rankings.html.twig
Normal file
|
@ -0,0 +1,566 @@
|
||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Évolution des classements Wiki OSM{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="container mt-4">
|
||||||
|
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h1>Évolution des classements Wiki OSM</h1>
|
||||||
|
{% if last_updated %}
|
||||||
|
<div class="text-muted">
|
||||||
|
Dernière mise à jour: {{ last_updated|date('d/m/Y H:i') }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if not json_exists %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<i class="bi bi-exclamation-triangle"></i> Aucune donnée de classement n'est disponible. Veuillez exécuter le script de scraping pour générer les données.
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<!-- Global Metrics Section -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h2>Métriques globales</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<canvas id="globalMetricsChart" width="400" height="200"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<canvas id="stalenessDistributionChart" width="400" height="200"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h3>Évolution des métriques globales</h3>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Pages totales</th>
|
||||||
|
<th>Score moyen</th>
|
||||||
|
<th>Sections (moy.)</th>
|
||||||
|
<th>Mots (moy.)</th>
|
||||||
|
<th>Liens (moy.)</th>
|
||||||
|
<th>Images (moy.)</th>
|
||||||
|
<th>Catégories (moy.)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for timestamp in timestamps %}
|
||||||
|
{% if global_metrics[timestamp] is defined %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ timestamp|date('d/m/Y') }}</td>
|
||||||
|
<td>{{ global_metrics[timestamp].total_pages }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="progress" style="height: 20px;">
|
||||||
|
{% set score = global_metrics[timestamp].avg_staleness %}
|
||||||
|
{% set score_class = score > 70 ? 'bg-danger' : (score > 40 ? 'bg-warning' : 'bg-success') %}
|
||||||
|
<div class="progress-bar {{ score_class }}" role="progressbar"
|
||||||
|
style="width: {{ score }}%;"
|
||||||
|
aria-valuenow="{{ score }}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
{{ score }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{{ global_metrics[timestamp].avg_sections }}</td>
|
||||||
|
<td>{{ global_metrics[timestamp].avg_words }}</td>
|
||||||
|
<td>{{ global_metrics[timestamp].avg_links }}</td>
|
||||||
|
<td>{{ global_metrics[timestamp].avg_images }}</td>
|
||||||
|
<td>{{ global_metrics[timestamp].avg_categories }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Page Rankings Section -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h2>Classement des pages</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">Filtrer</span>
|
||||||
|
<input type="text" id="pageFilter" class="form-control" placeholder="Rechercher une page...">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<select id="metricSelector" class="form-select">
|
||||||
|
<option value="staleness_score">Score de décrépitude</option>
|
||||||
|
<option value="word_diff">Différence de mots</option>
|
||||||
|
<option value="section_diff">Différence de sections</option>
|
||||||
|
<option value="link_diff">Différence de liens</option>
|
||||||
|
<option value="media_diff">Différence d'images</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<select id="sortOrder" class="form-select">
|
||||||
|
<option value="desc">Décroissant</option>
|
||||||
|
<option value="asc">Croissant</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover" id="pagesTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Page</th>
|
||||||
|
<th>Score actuel</th>
|
||||||
|
<th>Évolution</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for key, page in pages %}
|
||||||
|
<tr data-page-key="{{ key }}">
|
||||||
|
<td>{{ page.title }}</td>
|
||||||
|
<td>
|
||||||
|
{% set latest_timestamp = timestamps|last %}
|
||||||
|
{% if page.metrics[latest_timestamp] is defined %}
|
||||||
|
{% set latest_score = page.metrics[latest_timestamp].staleness_score %}
|
||||||
|
<div class="progress" style="height: 20px;">
|
||||||
|
{% set score_class = latest_score > 70 ? 'bg-danger' : (latest_score > 40 ? 'bg-warning' : 'bg-success') %}
|
||||||
|
<div class="progress-bar {{ score_class }}" role="progressbar"
|
||||||
|
style="width: {{ latest_score }}%;"
|
||||||
|
aria-valuenow="{{ latest_score }}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
{{ latest_score }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">N/A</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<canvas class="trend-chart" data-page-key="{{ key }}" width="200" height="50"></canvas>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ path('app_admin_wiki_compare', {'key': key}) }}" class="btn btn-sm btn-outline-primary">
|
||||||
|
<i class="bi bi-arrows-angle-expand"></i> Comparer
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Page Detail Modal -->
|
||||||
|
<div class="modal fade" id="pageDetailModal" tabindex="-1" aria-labelledby="pageDetailModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="pageDetailModalLabel">Détails de la page</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<h3 id="modalPageTitle"></h3>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<canvas id="pageDetailChart" width="700" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h4>Historique des métriques</h4>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm" id="pageMetricsTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Score</th>
|
||||||
|
<th>Mots</th>
|
||||||
|
<th>Sections</th>
|
||||||
|
<th>Liens</th>
|
||||||
|
<th>Images</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- Filled dynamically by JavaScript -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
<a href="#" id="comparePageBtn" class="btn btn-primary">Comparer</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascripts %}
|
||||||
|
{{ parent() }}
|
||||||
|
{% if json_exists %}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Data from controller
|
||||||
|
const timestamps = {{ timestamps|json_encode|raw }};
|
||||||
|
const pages = {{ pages|json_encode|raw }};
|
||||||
|
const globalMetrics = {{ global_metrics|json_encode|raw }};
|
||||||
|
|
||||||
|
// Format dates for display
|
||||||
|
const formatDates = timestamps.map(ts => {
|
||||||
|
const date = new Date(ts);
|
||||||
|
return date.toLocaleDateString('fr-FR');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Global metrics chart
|
||||||
|
const globalMetricsCtx = document.getElementById('globalMetricsChart').getContext('2d');
|
||||||
|
const globalMetricsData = {
|
||||||
|
labels: formatDates,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Score moyen',
|
||||||
|
data: timestamps.map(ts => globalMetrics[ts]?.avg_staleness || 0),
|
||||||
|
borderColor: 'rgba(255, 99, 132, 1)',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||||
|
fill: false,
|
||||||
|
tension: 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Sections (moy.)',
|
||||||
|
data: timestamps.map(ts => globalMetrics[ts]?.avg_sections || 0),
|
||||||
|
borderColor: 'rgba(54, 162, 235, 1)',
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||||
|
fill: false,
|
||||||
|
tension: 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Mots (moy. / 10)',
|
||||||
|
data: timestamps.map(ts => (globalMetrics[ts]?.avg_words || 0) / 10),
|
||||||
|
borderColor: 'rgba(255, 206, 86, 1)',
|
||||||
|
backgroundColor: 'rgba(255, 206, 86, 0.2)',
|
||||||
|
fill: false,
|
||||||
|
tension: 0.1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
new Chart(globalMetricsCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: globalMetricsData,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Évolution des métriques globales'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Staleness distribution chart
|
||||||
|
if (timestamps.length > 0) {
|
||||||
|
const latestTimestamp = timestamps[timestamps.length - 1];
|
||||||
|
const latestDistribution = globalMetrics[latestTimestamp]?.staleness_distribution || {};
|
||||||
|
|
||||||
|
const stalenessDistCtx = document.getElementById('stalenessDistributionChart').getContext('2d');
|
||||||
|
const stalenessDistData = {
|
||||||
|
labels: Object.keys(latestDistribution),
|
||||||
|
datasets: [{
|
||||||
|
label: 'Nombre de pages',
|
||||||
|
data: Object.values(latestDistribution),
|
||||||
|
backgroundColor: [
|
||||||
|
'rgba(25, 135, 84, 0.7)',
|
||||||
|
'rgba(140, 195, 38, 0.7)',
|
||||||
|
'rgba(255, 193, 7, 0.7)',
|
||||||
|
'rgba(255, 153, 0, 0.7)',
|
||||||
|
'rgba(232, 113, 55, 0.7)',
|
||||||
|
'rgba(220, 53, 69, 0.7)'
|
||||||
|
],
|
||||||
|
borderColor: [
|
||||||
|
'rgba(25, 135, 84, 1)',
|
||||||
|
'rgba(140, 195, 38, 1)',
|
||||||
|
'rgba(255, 193, 7, 1)',
|
||||||
|
'rgba(255, 153, 0, 1)',
|
||||||
|
'rgba(232, 113, 55, 1)',
|
||||||
|
'rgba(220, 53, 69, 1)'
|
||||||
|
],
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
new Chart(stalenessDistCtx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: stalenessDistData,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Distribution des scores de décrépitude'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create small trend charts for each page
|
||||||
|
const trendCharts = document.querySelectorAll('.trend-chart');
|
||||||
|
trendCharts.forEach(canvas => {
|
||||||
|
const pageKey = canvas.dataset.pageKey;
|
||||||
|
const pageData = pages[pageKey];
|
||||||
|
|
||||||
|
if (pageData && pageData.metrics) {
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const metricValues = timestamps.map(ts => {
|
||||||
|
return pageData.metrics[ts]?.staleness_score || null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter out null values
|
||||||
|
const validData = metricValues.filter(val => val !== null);
|
||||||
|
|
||||||
|
// Calculate trend (increasing or decreasing)
|
||||||
|
let trendColor = 'rgba(75, 192, 192, 1)'; // Default: neutral
|
||||||
|
if (validData.length >= 2) {
|
||||||
|
const firstValid = validData[0];
|
||||||
|
const lastValid = validData[validData.length - 1];
|
||||||
|
if (lastValid > firstValid) {
|
||||||
|
trendColor = 'rgba(255, 99, 132, 1)'; // Red: getting worse
|
||||||
|
} else if (lastValid < firstValid) {
|
||||||
|
trendColor = 'rgba(75, 192, 192, 1)'; // Green: getting better
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: formatDates,
|
||||||
|
datasets: [{
|
||||||
|
data: metricValues,
|
||||||
|
borderColor: trendColor,
|
||||||
|
backgroundColor: trendColor.replace('1)', '0.2)'),
|
||||||
|
tension: 0.1,
|
||||||
|
pointRadius: 0,
|
||||||
|
borderWidth: 2
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
display: false,
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Page filtering
|
||||||
|
const pageFilter = document.getElementById('pageFilter');
|
||||||
|
const pagesTable = document.getElementById('pagesTable');
|
||||||
|
|
||||||
|
pageFilter.addEventListener('input', function() {
|
||||||
|
const filterText = this.value.toLowerCase();
|
||||||
|
const rows = pagesTable.querySelectorAll('tbody tr');
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
const pageKey = row.dataset.pageKey;
|
||||||
|
const pageTitle = pages[pageKey]?.title.toLowerCase() || '';
|
||||||
|
|
||||||
|
if (pageTitle.includes(filterText) || pageKey.toLowerCase().includes(filterText)) {
|
||||||
|
row.style.display = '';
|
||||||
|
} else {
|
||||||
|
row.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Metric selector and sorting
|
||||||
|
const metricSelector = document.getElementById('metricSelector');
|
||||||
|
const sortOrder = document.getElementById('sortOrder');
|
||||||
|
|
||||||
|
function sortPages() {
|
||||||
|
const metric = metricSelector.value;
|
||||||
|
const order = sortOrder.value;
|
||||||
|
const tbody = pagesTable.querySelector('tbody');
|
||||||
|
const rows = Array.from(tbody.querySelectorAll('tr'));
|
||||||
|
|
||||||
|
// Get the latest timestamp
|
||||||
|
const latestTs = timestamps[timestamps.length - 1];
|
||||||
|
|
||||||
|
rows.sort((a, b) => {
|
||||||
|
const keyA = a.dataset.pageKey;
|
||||||
|
const keyB = b.dataset.pageKey;
|
||||||
|
|
||||||
|
const valueA = pages[keyA]?.metrics[latestTs]?.[metric] || 0;
|
||||||
|
const valueB = pages[keyB]?.metrics[latestTs]?.[metric] || 0;
|
||||||
|
|
||||||
|
return order === 'asc' ? valueA - valueB : valueB - valueA;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear and re-append rows
|
||||||
|
rows.forEach(row => tbody.appendChild(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
metricSelector.addEventListener('change', sortPages);
|
||||||
|
sortOrder.addEventListener('change', sortPages);
|
||||||
|
|
||||||
|
// Initial sort
|
||||||
|
sortPages();
|
||||||
|
|
||||||
|
// Page detail modal
|
||||||
|
const pageDetailModal = new bootstrap.Modal(document.getElementById('pageDetailModal'));
|
||||||
|
const modalPageTitle = document.getElementById('modalPageTitle');
|
||||||
|
const comparePageBtn = document.getElementById('comparePageBtn');
|
||||||
|
const pageMetricsTable = document.getElementById('pageMetricsTable');
|
||||||
|
let pageDetailChart = null;
|
||||||
|
|
||||||
|
// Add click event to table rows
|
||||||
|
const tableRows = pagesTable.querySelectorAll('tbody tr');
|
||||||
|
tableRows.forEach(row => {
|
||||||
|
row.addEventListener('click', function() {
|
||||||
|
const pageKey = this.dataset.pageKey;
|
||||||
|
const pageData = pages[pageKey];
|
||||||
|
|
||||||
|
if (pageData) {
|
||||||
|
// Set modal title and compare button link
|
||||||
|
modalPageTitle.textContent = pageData.title;
|
||||||
|
comparePageBtn.href = `/wiki/compare/${pageKey}`;
|
||||||
|
|
||||||
|
// Fill metrics table
|
||||||
|
const tbody = pageMetricsTable.querySelector('tbody');
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
timestamps.forEach(ts => {
|
||||||
|
if (pageData.metrics[ts]) {
|
||||||
|
const metrics = pageData.metrics[ts];
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
|
||||||
|
// Format date
|
||||||
|
const date = new Date(ts);
|
||||||
|
const formattedDate = date.toLocaleDateString('fr-FR');
|
||||||
|
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${formattedDate}</td>
|
||||||
|
<td>${metrics.staleness_score || 0}</td>
|
||||||
|
<td>${metrics.word_diff || 0}</td>
|
||||||
|
<td>${metrics.section_diff || 0}</td>
|
||||||
|
<td>${metrics.link_diff || 0}</td>
|
||||||
|
<td>${metrics.media_diff || 0}</td>
|
||||||
|
`;
|
||||||
|
|
||||||
|
tbody.appendChild(row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create detailed chart
|
||||||
|
const ctx = document.getElementById('pageDetailChart').getContext('2d');
|
||||||
|
|
||||||
|
// Destroy previous chart if exists
|
||||||
|
if (pageDetailChart) {
|
||||||
|
pageDetailChart.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
pageDetailChart = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: formatDates,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Score de décrépitude',
|
||||||
|
data: timestamps.map(ts => pageData.metrics[ts]?.staleness_score || null),
|
||||||
|
borderColor: 'rgba(255, 99, 132, 1)',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||||
|
fill: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Différence de mots / 10',
|
||||||
|
data: timestamps.map(ts => (pageData.metrics[ts]?.word_diff || 0) / 10),
|
||||||
|
borderColor: 'rgba(54, 162, 235, 1)',
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||||
|
fill: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Différence de sections',
|
||||||
|
data: timestamps.map(ts => pageData.metrics[ts]?.section_diff || null),
|
||||||
|
borderColor: 'rgba(255, 206, 86, 1)',
|
||||||
|
backgroundColor: 'rgba(255, 206, 86, 0.2)',
|
||||||
|
fill: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Différence de liens',
|
||||||
|
data: timestamps.map(ts => pageData.metrics[ts]?.link_diff || null),
|
||||||
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||||
|
fill: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: `Évolution des métriques pour ${pageData.title}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
pageDetailModal.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
|
@ -116,7 +116,8 @@ SPECIFIC_PAGES = [
|
||||||
"Mapping_private_information",
|
"Mapping_private_information",
|
||||||
"Any_tags_you_like",
|
"Any_tags_you_like",
|
||||||
"Organised_Editing/Best_Practices",
|
"Organised_Editing/Best_Practices",
|
||||||
"Map_features"
|
"Map_features",
|
||||||
|
"Wiki"
|
||||||
]
|
]
|
||||||
|
|
||||||
def fetch_desynchronized_pages():
|
def fetch_desynchronized_pages():
|
||||||
|
@ -280,12 +281,104 @@ def save_to_json(data, filename):
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.error(f"Error saving data to {filename}: {e}")
|
logger.error(f"Error saving data to {filename}: {e}")
|
||||||
|
|
||||||
|
def calculate_global_metrics(data):
|
||||||
|
"""
|
||||||
|
Calculate global metrics for all pages in the data
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Data containing regular_pages and specific_pages
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Dictionary with global metrics
|
||||||
|
"""
|
||||||
|
# Combine regular and specific pages for global metrics
|
||||||
|
all_pages = data.get('regular_pages', []) + data.get('specific_pages', [])
|
||||||
|
|
||||||
|
# Initialize metrics
|
||||||
|
metrics = {
|
||||||
|
'total_pages': len(all_pages),
|
||||||
|
'avg_sections': 0,
|
||||||
|
'avg_words': 0,
|
||||||
|
'avg_links': 0,
|
||||||
|
'avg_images': 0,
|
||||||
|
'avg_categories': 0,
|
||||||
|
'avg_staleness': 0,
|
||||||
|
'pages_with_en_fr': 0,
|
||||||
|
'pages_missing_fr': 0,
|
||||||
|
'staleness_distribution': {
|
||||||
|
'0-20': 0,
|
||||||
|
'21-40': 0,
|
||||||
|
'41-60': 0,
|
||||||
|
'61-80': 0,
|
||||||
|
'81-100': 0,
|
||||||
|
'100+': 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Skip if no pages
|
||||||
|
if not all_pages:
|
||||||
|
return metrics
|
||||||
|
|
||||||
|
# Calculate totals
|
||||||
|
total_sections = 0
|
||||||
|
total_words = 0
|
||||||
|
total_links = 0
|
||||||
|
total_images = 0
|
||||||
|
total_categories = 0
|
||||||
|
total_staleness = 0
|
||||||
|
|
||||||
|
for page in all_pages:
|
||||||
|
# Count pages with/without French version
|
||||||
|
if page.get('fr_page'):
|
||||||
|
metrics['pages_with_en_fr'] += 1
|
||||||
|
else:
|
||||||
|
metrics['pages_missing_fr'] += 1
|
||||||
|
|
||||||
|
# Add to staleness distribution
|
||||||
|
staleness = page.get('staleness_score', 0)
|
||||||
|
total_staleness += staleness
|
||||||
|
|
||||||
|
if staleness <= 20:
|
||||||
|
metrics['staleness_distribution']['0-20'] += 1
|
||||||
|
elif staleness <= 40:
|
||||||
|
metrics['staleness_distribution']['21-40'] += 1
|
||||||
|
elif staleness <= 60:
|
||||||
|
metrics['staleness_distribution']['41-60'] += 1
|
||||||
|
elif staleness <= 80:
|
||||||
|
metrics['staleness_distribution']['61-80'] += 1
|
||||||
|
elif staleness <= 100:
|
||||||
|
metrics['staleness_distribution']['81-100'] += 1
|
||||||
|
else:
|
||||||
|
metrics['staleness_distribution']['100+'] += 1
|
||||||
|
|
||||||
|
# Add to totals
|
||||||
|
total_sections += page.get('section_diff', 0) if 'section_diff' in page else 0
|
||||||
|
total_words += page.get('word_diff', 0) if 'word_diff' in page else 0
|
||||||
|
total_links += page.get('link_diff', 0) if 'link_diff' in page else 0
|
||||||
|
total_images += page.get('media_diff', 0) if 'media_diff' in page else 0
|
||||||
|
|
||||||
|
# Count categories if available
|
||||||
|
if page.get('category_comparison'):
|
||||||
|
cat_count = len(page['category_comparison'].get('en_only', []))
|
||||||
|
total_categories += cat_count
|
||||||
|
|
||||||
|
# Calculate averages
|
||||||
|
metrics['avg_sections'] = round(total_sections / len(all_pages), 2)
|
||||||
|
metrics['avg_words'] = round(total_words / len(all_pages), 2)
|
||||||
|
metrics['avg_links'] = round(total_links / len(all_pages), 2)
|
||||||
|
metrics['avg_images'] = round(total_images / len(all_pages), 2)
|
||||||
|
metrics['avg_categories'] = round(total_categories / len(all_pages), 2)
|
||||||
|
metrics['avg_staleness'] = round(total_staleness / len(all_pages), 2)
|
||||||
|
|
||||||
|
return metrics
|
||||||
|
|
||||||
def save_with_history(data, filename):
|
def save_with_history(data, filename):
|
||||||
"""
|
"""
|
||||||
Save data to a JSON file while preserving history
|
Save data to a JSON file while preserving history
|
||||||
|
|
||||||
This function loads existing data from the file (if it exists),
|
This function loads existing data from the file (if it exists),
|
||||||
adds the new data to the history, and saves the updated data back to the file.
|
adds the new data to the history, and saves the updated data back to the file.
|
||||||
|
It also calculates global metrics for the current data.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data: New data to save
|
data: New data to save
|
||||||
|
@ -301,11 +394,15 @@ def save_with_history(data, filename):
|
||||||
# Initialize history if it doesn't exist
|
# Initialize history if it doesn't exist
|
||||||
if 'history' not in existing_data:
|
if 'history' not in existing_data:
|
||||||
existing_data['history'] = {}
|
existing_data['history'] = {}
|
||||||
|
|
||||||
|
# Calculate global metrics for the current data
|
||||||
|
global_metrics = calculate_global_metrics(data)
|
||||||
|
|
||||||
# Add current regular_pages and specific_pages to history
|
# Add current regular_pages, specific_pages, and global metrics to history
|
||||||
history_entry = {
|
history_entry = {
|
||||||
'regular_pages': data.get('regular_pages', []),
|
'regular_pages': data.get('regular_pages', []),
|
||||||
'specific_pages': data.get('specific_pages', [])
|
'specific_pages': data.get('specific_pages', []),
|
||||||
|
'global_metrics': global_metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add the entry to history with timestamp as key
|
# Add the entry to history with timestamp as key
|
||||||
|
@ -314,6 +411,7 @@ def save_with_history(data, filename):
|
||||||
# Update the current data
|
# Update the current data
|
||||||
existing_data['regular_pages'] = data.get('regular_pages', [])
|
existing_data['regular_pages'] = data.get('regular_pages', [])
|
||||||
existing_data['specific_pages'] = data.get('specific_pages', [])
|
existing_data['specific_pages'] = data.get('specific_pages', [])
|
||||||
|
existing_data['global_metrics'] = global_metrics
|
||||||
existing_data['last_updated'] = current_timestamp
|
existing_data['last_updated'] = current_timestamp
|
||||||
|
|
||||||
# Save the updated data
|
# Save the updated data
|
||||||
|
@ -321,10 +419,119 @@ def save_with_history(data, filename):
|
||||||
json.dump(existing_data, f, indent=2, ensure_ascii=False)
|
json.dump(existing_data, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
logger.info(f"Data with history saved to {filename}")
|
logger.info(f"Data with history saved to {filename}")
|
||||||
|
|
||||||
|
# Also save a separate ranking history file
|
||||||
|
save_ranking_history(existing_data, "page_rankings.json")
|
||||||
|
|
||||||
except (IOError, json.JSONDecodeError) as e:
|
except (IOError, json.JSONDecodeError) as e:
|
||||||
logger.error(f"Error saving data with history to {filename}: {e}")
|
logger.error(f"Error saving data with history to {filename}: {e}")
|
||||||
# Fallback to regular save if there's an error
|
# Fallback to regular save if there's an error
|
||||||
save_to_json(data, filename)
|
save_to_json(data, filename)
|
||||||
|
|
||||||
|
def save_ranking_history(data, filename):
|
||||||
|
"""
|
||||||
|
Save ranking history to a separate JSON file
|
||||||
|
|
||||||
|
This function extracts ranking data from the history and saves it in a format
|
||||||
|
optimized for displaying ranking evolution over time.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Data containing history entries
|
||||||
|
filename (str): Name of the file to save rankings
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Initialize ranking data structure
|
||||||
|
ranking_data = {
|
||||||
|
'timestamps': [],
|
||||||
|
'pages': {},
|
||||||
|
'global_metrics': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract history entries
|
||||||
|
history = data.get('history', {})
|
||||||
|
|
||||||
|
# Sort timestamps chronologically
|
||||||
|
sorted_timestamps = sorted(history.keys())
|
||||||
|
ranking_data['timestamps'] = sorted_timestamps
|
||||||
|
|
||||||
|
# Process each page to track its metrics over time
|
||||||
|
all_page_keys = set()
|
||||||
|
|
||||||
|
# First, collect all unique page keys across all history entries
|
||||||
|
for timestamp in sorted_timestamps:
|
||||||
|
entry = history[timestamp]
|
||||||
|
|
||||||
|
# Add global metrics for this timestamp
|
||||||
|
if 'global_metrics' in entry:
|
||||||
|
ranking_data['global_metrics'][timestamp] = entry['global_metrics']
|
||||||
|
|
||||||
|
# Collect page keys from regular pages
|
||||||
|
for page in entry.get('regular_pages', []):
|
||||||
|
all_page_keys.add(page['key'])
|
||||||
|
|
||||||
|
# Collect page keys from specific pages
|
||||||
|
for page in entry.get('specific_pages', []):
|
||||||
|
all_page_keys.add(page['key'])
|
||||||
|
|
||||||
|
# Initialize data structure for each page
|
||||||
|
for page_key in all_page_keys:
|
||||||
|
ranking_data['pages'][page_key] = {
|
||||||
|
'title': page_key,
|
||||||
|
'metrics': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fill in metrics for each page at each timestamp
|
||||||
|
for timestamp in sorted_timestamps:
|
||||||
|
entry = history[timestamp]
|
||||||
|
|
||||||
|
# Process regular pages
|
||||||
|
for page in entry.get('regular_pages', []):
|
||||||
|
page_key = page['key']
|
||||||
|
|
||||||
|
# Extract metrics we want to track
|
||||||
|
metrics = {
|
||||||
|
'staleness_score': page.get('staleness_score', 0),
|
||||||
|
'word_diff': page.get('word_diff', 0),
|
||||||
|
'section_diff': page.get('section_diff', 0),
|
||||||
|
'link_diff': page.get('link_diff', 0),
|
||||||
|
'media_diff': page.get('media_diff', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Store metrics for this timestamp
|
||||||
|
ranking_data['pages'][page_key]['metrics'][timestamp] = metrics
|
||||||
|
|
||||||
|
# Store page title if available
|
||||||
|
if 'en_page' in page and page['en_page']:
|
||||||
|
ranking_data['pages'][page_key]['title'] = page['en_page'].get('page_title', page_key)
|
||||||
|
|
||||||
|
# Process specific pages
|
||||||
|
for page in entry.get('specific_pages', []):
|
||||||
|
page_key = page['key']
|
||||||
|
|
||||||
|
# Extract metrics we want to track
|
||||||
|
metrics = {
|
||||||
|
'staleness_score': page.get('staleness_score', 0),
|
||||||
|
'word_diff': page.get('word_diff', 0),
|
||||||
|
'section_diff': page.get('section_diff', 0),
|
||||||
|
'link_diff': page.get('link_diff', 0),
|
||||||
|
'media_diff': page.get('media_diff', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Store metrics for this timestamp
|
||||||
|
ranking_data['pages'][page_key]['metrics'][timestamp] = metrics
|
||||||
|
|
||||||
|
# Store page title if available
|
||||||
|
if 'en_page' in page and page['en_page']:
|
||||||
|
ranking_data['pages'][page_key]['title'] = page['en_page'].get('page_title', page_key)
|
||||||
|
|
||||||
|
# Save the ranking data
|
||||||
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(ranking_data, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
logger.info(f"Ranking history saved to {filename}")
|
||||||
|
|
||||||
|
except (IOError, json.JSONDecodeError) as e:
|
||||||
|
logger.error(f"Error saving ranking history to {filename}: {e}")
|
||||||
|
|
||||||
def check_grammar_with_grammalecte(text):
|
def check_grammar_with_grammalecte(text):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -12,6 +12,8 @@ Key:harassment_prevention,en,https://wiki.openstreetmap.org/wiki/Key:harassment_
|
||||||
Key:harassment_prevention,fr,https://wiki.openstreetmap.org/wiki/FR:Key:harassment_prevention,2025-07-03,15,328,83,14,66.72,https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
|
Key:harassment_prevention,fr,https://wiki.openstreetmap.org/wiki/FR:Key:harassment_prevention,2025-07-03,15,328,83,14,66.72,https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
|
||||||
Proposal process,en,https://wiki.openstreetmap.org/wiki/Proposal process,2025-08-13,46,5292,202,4,172.34,https://wiki.openstreetmap.org/w/images/thumb/c/c2/Save_proposal_first.png/761px-Save_proposal_first.png
|
Proposal process,en,https://wiki.openstreetmap.org/wiki/Proposal process,2025-08-13,46,5292,202,4,172.34,https://wiki.openstreetmap.org/w/images/thumb/c/c2/Save_proposal_first.png/761px-Save_proposal_first.png
|
||||||
Proposal process,fr,https://wiki.openstreetmap.org/wiki/FR:Proposal process,2023-09-22,15,0,0,0,172.34,
|
Proposal process,fr,https://wiki.openstreetmap.org/wiki/FR:Proposal process,2023-09-22,15,0,0,0,172.34,
|
||||||
|
Outil de Manipulation et d'Organisation,en,https://wiki.openstreetmap.org/wiki/Outil de Manipulation et d'Organisation,2025-09-02,9,0,0,0,0.6,
|
||||||
|
Outil de Manipulation et d'Organisation,fr,https://wiki.openstreetmap.org/wiki/FR:Outil de Manipulation et d'Organisation,2025-09-02,13,0,0,0,0.6,
|
||||||
Automated_Edits_code_of_conduct,en,https://wiki.openstreetmap.org/wiki/Automated_Edits_code_of_conduct,2025-07-26,19,0,0,0,23.1,
|
Automated_Edits_code_of_conduct,en,https://wiki.openstreetmap.org/wiki/Automated_Edits_code_of_conduct,2025-07-26,19,0,0,0,23.1,
|
||||||
Automated_Edits_code_of_conduct,fr,https://wiki.openstreetmap.org/wiki/FR:Automated_Edits_code_of_conduct,2025-04-03,17,0,0,0,23.1,
|
Automated_Edits_code_of_conduct,fr,https://wiki.openstreetmap.org/wiki/FR:Automated_Edits_code_of_conduct,2025-04-03,17,0,0,0,23.1,
|
||||||
Key:cuisine,en,https://wiki.openstreetmap.org/wiki/Key:cuisine,2025-07-23,17,3422,693,303,107.73,https://upload.wikimedia.org/wikipedia/commons/thumb/f/f0/Food_montage.jpg/200px-Food_montage.jpg
|
Key:cuisine,en,https://wiki.openstreetmap.org/wiki/Key:cuisine,2025-07-23,17,3422,693,303,107.73,https://upload.wikimedia.org/wikipedia/commons/thumb/f/f0/Food_montage.jpg/200px-Food_montage.jpg
|
||||||
|
@ -44,6 +46,8 @@ Any_tags_you_like,fr,https://wiki.openstreetmap.org/wiki/FR:Any_tags_you_like,20
|
||||||
Organised_Editing/Best_Practices,en,https://wiki.openstreetmap.org/wiki/Organised_Editing/Best_Practices,2025-07-18,16,501,10,1,100,https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/Ambox_warning_pn.svg/40px-Ambox_warning_pn.svg.png
|
Organised_Editing/Best_Practices,en,https://wiki.openstreetmap.org/wiki/Organised_Editing/Best_Practices,2025-07-18,16,501,10,1,100,https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/Ambox_warning_pn.svg/40px-Ambox_warning_pn.svg.png
|
||||||
Map_features,en,https://wiki.openstreetmap.org/wiki/Map_features,2025-07-21,125,21926,4255,2222,507.98,https://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/Bar_MXCT.JPG/100px-Bar_MXCT.JPG
|
Map_features,en,https://wiki.openstreetmap.org/wiki/Map_features,2025-07-21,125,21926,4255,2222,507.98,https://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/Bar_MXCT.JPG/100px-Bar_MXCT.JPG
|
||||||
Map_features,fr,https://wiki.openstreetmap.org/wiki/FR:Map_features,2018-12-27,103,23159,5516,3062,507.98,https://wiki.openstreetmap.org/w/images/c/c4/Aerialway_gondola_render.png
|
Map_features,fr,https://wiki.openstreetmap.org/wiki/FR:Map_features,2018-12-27,103,23159,5516,3062,507.98,https://wiki.openstreetmap.org/w/images/c/c4/Aerialway_gondola_render.png
|
||||||
|
Wiki,en,https://wiki.openstreetmap.org/wiki/Wiki,2025-02-24,16,669,40,1,302.87,https://wiki.openstreetmap.org/w/images/thumb/b/b7/OpenStreetMap_Wiki_MainPage.png/300px-OpenStreetMap_Wiki_MainPage.png
|
||||||
|
Wiki,fr,https://wiki.openstreetmap.org/wiki/FR:Wiki,2021-01-04,14,645,37,1,302.87,https://wiki.openstreetmap.org/w/images/thumb/b/b7/OpenStreetMap_Wiki_MainPage.png/300px-OpenStreetMap_Wiki_MainPage.png
|
||||||
https://wiki.openstreetmap.org/wiki/FR:Quality_Assurance,fr,https://wiki.openstreetmap.org/wiki/FR:Quality_Assurance,2015-05-16,16,0,0,0,0,
|
https://wiki.openstreetmap.org/wiki/FR:Quality_Assurance,fr,https://wiki.openstreetmap.org/wiki/FR:Quality_Assurance,2015-05-16,16,0,0,0,0,
|
||||||
https://wiki.openstreetmap.org/wiki/Quality_Assurance,en,https://wiki.openstreetmap.org/wiki/Quality_Assurance,2025-06-01,19,0,0,0,100,
|
https://wiki.openstreetmap.org/wiki/Quality_Assurance,en,https://wiki.openstreetmap.org/wiki/Quality_Assurance,2025-06-01,19,0,0,0,100,
|
||||||
https://wiki.openstreetmap.org/wiki/FR:Nominatim/Installation,fr,https://wiki.openstreetmap.org/wiki/FR:Nominatim/Installation,2016-08-22,32,0,0,0,0,
|
https://wiki.openstreetmap.org/wiki/FR:Nominatim/Installation,fr,https://wiki.openstreetmap.org/wiki/FR:Nominatim/Installation,2016-08-22,32,0,0,0,0,
|
||||||
|
|
|
Loading…
Add table
Add a link
Reference in a new issue