mirror of
https://forge.chapril.org/tykayn/osm-commerces
synced 2025-10-04 17:04:53 +02:00
graph détaillé, listing clés courantes
This commit is contained in:
parent
b771aea541
commit
9eb08073d0
7 changed files with 213 additions and 48 deletions
35
migrations/Version20250712121647.php
Normal file
35
migrations/Version20250712121647.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250712121647 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE place CHANGE email email VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_0900_ai_ci`, CHANGE note note LONGTEXT CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_0900_ai_ci`, CHANGE name name VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_0900_ai_ci`, CHANGE note_content note_content VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_0900_ai_ci`, CHANGE street street VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_0900_ai_ci`, CHANGE housenumber housenumber VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_0900_ai_ci`, CHANGE siret siret VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_0900_ai_ci`, CHANGE osm_user osm_user VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_0900_ai_ci`, CHANGE email_content email_content LONGTEXT CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_0900_ai_ci`
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE place CHANGE email email VARCHAR(255) DEFAULT NULL, CHANGE note note VARCHAR(255) DEFAULT NULL, CHANGE name name VARCHAR(255) DEFAULT NULL, CHANGE note_content note_content VARCHAR(255) DEFAULT NULL, CHANGE street street VARCHAR(255) DEFAULT NULL, CHANGE housenumber housenumber VARCHAR(255) DEFAULT NULL, CHANGE siret siret VARCHAR(255) DEFAULT NULL, CHANGE osm_user osm_user VARCHAR(255) DEFAULT NULL, CHANGE email_content email_content LONGTEXT DEFAULT NULL
|
||||
SQL);
|
||||
}
|
||||
}
|
|
@ -114,36 +114,61 @@ class FollowUpController extends AbstractController
|
|||
$themes = \App\Service\FollowUpService::getFollowUpThemes();
|
||||
$all_completion_data = [];
|
||||
$latest_diffs = [];
|
||||
// Définir les bornes de période
|
||||
$now = new \DateTime();
|
||||
$periods = [
|
||||
'7j' => (clone $now)->modify('-7 days'),
|
||||
'30j' => (clone $now)->modify('-30 days'),
|
||||
'6mois' => (clone $now)->modify('-6 months'),
|
||||
];
|
||||
foreach ($themes as $type => $label) {
|
||||
$all_completion_data[$type] = $series[$type . '_completion'] ?? [];
|
||||
// Calcul du diff sur 7 jours pour le nombre et la complétion
|
||||
$count_series = $series[$type . '_count'] ?? [];
|
||||
$completion_series = $series[$type . '_completion'] ?? [];
|
||||
$count_now = count($count_series) ? $count_series[count($count_series)-1]['value'] : null;
|
||||
$count_7d = null;
|
||||
foreach (array_reverse($count_series) as $point) {
|
||||
$date = \DateTime::createFromFormat('Y-m-d', $point['date']);
|
||||
if ($date && $date <= (new \DateTime('-7 days'))) {
|
||||
$count_7d = $point['value'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
$completion_now = count($completion_series) ? $completion_series[count($completion_series)-1]['value'] : null;
|
||||
$completion_7d = null;
|
||||
foreach (array_reverse($completion_series) as $point) {
|
||||
$date = \DateTime::createFromFormat('Y-m-d', $point['date']);
|
||||
if ($date && $date <= (new \DateTime('-7 days'))) {
|
||||
$completion_7d = $point['value'];
|
||||
// Fonction utilitaire pour trouver la valeur la plus proche avant ou égale à une date
|
||||
$findValueAtOrBefore = function($series, \DateTime $date) {
|
||||
$val = null;
|
||||
foreach (array_reverse($series) as $point) {
|
||||
$ptDate = \DateTime::createFromFormat('Y-m-d', $point['date']);
|
||||
if ($ptDate && $ptDate <= $date) {
|
||||
$val = $point['value'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $val;
|
||||
};
|
||||
// Valeurs aux bornes
|
||||
$val_now = count($count_series) ? $count_series[count($count_series)-1]['value'] : null;
|
||||
$val_7j = $findValueAtOrBefore($count_series, $periods['7j']);
|
||||
$val_30j = $findValueAtOrBefore($count_series, $periods['30j']);
|
||||
$val_6mois = $findValueAtOrBefore($count_series, $periods['6mois']);
|
||||
// Différences exclusives
|
||||
$diff_7j = ($val_now !== null && $val_7j !== null) ? $val_now - $val_7j : null;
|
||||
$diff_30j = ($val_7j !== null && $val_30j !== null) ? $val_7j - $val_30j : null;
|
||||
$diff_6mois = ($val_30j !== null && $val_6mois !== null) ? $val_30j - $val_6mois : null;
|
||||
// Idem pour la complétion
|
||||
$comp_now = count($completion_series) ? $completion_series[count($completion_series)-1]['value'] : null;
|
||||
$comp_7j = $findValueAtOrBefore($completion_series, $periods['7j']);
|
||||
$comp_30j = $findValueAtOrBefore($completion_series, $periods['30j']);
|
||||
$comp_6mois = $findValueAtOrBefore($completion_series, $periods['6mois']);
|
||||
$comp_diff_7j = ($comp_now !== null && $comp_7j !== null) ? $comp_now - $comp_7j : null;
|
||||
$comp_diff_30j = ($comp_7j !== null && $comp_30j !== null) ? $comp_7j - $comp_30j : null;
|
||||
$comp_diff_6mois = ($comp_30j !== null && $comp_6mois !== null) ? $comp_30j - $comp_6mois : null;
|
||||
$latest_diffs[$type] = [
|
||||
'count_now' => $count_now,
|
||||
'count_7d' => $count_7d,
|
||||
'count_diff' => ($count_now !== null && $count_7d !== null) ? $count_now - $count_7d : null,
|
||||
'completion_now' => $completion_now,
|
||||
'completion_7d' => $completion_7d,
|
||||
'completion_diff' => ($completion_now !== null && $completion_7d !== null) ? $completion_now - $completion_7d : null,
|
||||
'count_now' => $val_now,
|
||||
'count_7j' => $val_7j,
|
||||
'count_30j' => $val_30j,
|
||||
'count_6mois' => $val_6mois,
|
||||
'count_diff_7j' => $diff_7j,
|
||||
'count_diff_30j' => $diff_30j,
|
||||
'count_diff_6mois' => $diff_6mois,
|
||||
'completion_now' => $comp_now,
|
||||
'completion_7j' => $comp_7j,
|
||||
'completion_30j' => $comp_30j,
|
||||
'completion_6mois' => $comp_6mois,
|
||||
'completion_diff_7j' => $comp_diff_7j,
|
||||
'completion_diff_30j' => $comp_diff_30j,
|
||||
'completion_diff_6mois' => $comp_diff_6mois,
|
||||
'label' => $label,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ class Place
|
|||
#[ORM\Column]
|
||||
private ?bool $dead = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true, options: ['charset' => 'utf8mb4'])]
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true, options: ['charset' => 'utf8mb4'])]
|
||||
private ?string $note = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
|
|
|
@ -26,13 +26,15 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th>Thème</th>
|
||||
<th>Évolution du nombre</th>
|
||||
<th>Évolution de la complétion</th>
|
||||
<th>Évolution 7j</th>
|
||||
<th>Évolution 30j</th>
|
||||
<th>Évolution 6 mois</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for type, diff in latest_diffs %}
|
||||
{% if diff.count_diff is not null and diff.count_diff != 0 %}
|
||||
{% set has_period_change = (diff.count_diff_7j != 0 or diff.count_diff_30j != 0 or diff.count_diff_6mois != 0) %}
|
||||
{% if has_period_change %}
|
||||
{% set has_change = true %}
|
||||
<tr>
|
||||
<td>
|
||||
|
@ -41,28 +43,73 @@
|
|||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if diff.count_diff > 0 %}
|
||||
{% if diff.count_diff_7j > 0 %}
|
||||
<i class="bi bi-arrow-up text-success"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-arrow-down text-danger"></i>
|
||||
{% endif %}
|
||||
{{ diff.count_diff > 0 ? '+' ~ diff.count_diff : diff.count_diff }} objets
|
||||
</td>
|
||||
<td>
|
||||
{% if diff.completion_diff > 0 %}
|
||||
<i class="bi bi-arrow-up text-success"></i>
|
||||
{% elseif diff.completion_diff < 0 %}
|
||||
{% elseif diff.count_diff_7j < 0 %}
|
||||
<i class="bi bi-arrow-down text-danger"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-arrow-right text-secondary"></i>
|
||||
{% endif %}
|
||||
{{ diff.completion_diff > 0 ? '+' ~ diff.completion_diff : diff.completion_diff }}%
|
||||
{{ diff.count_diff_7j > 0 ? '+' ~ diff.count_diff_7j : diff.count_diff_7j }} objets
|
||||
<br>
|
||||
<span class="small text-muted">Complétion :
|
||||
{% if diff.completion_diff_7j > 0 %}
|
||||
<i class="bi bi-arrow-up text-success"></i>
|
||||
{% elseif diff.completion_diff_7j < 0 %}
|
||||
<i class="bi bi-arrow-down text-danger"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-arrow-right text-secondary"></i>
|
||||
{% endif %}
|
||||
{{ diff.completion_diff_7j > 0 ? '+' ~ diff.completion_diff_7j : diff.completion_diff_7j }}%
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if diff.count_diff_30j > 0 %}
|
||||
<i class="bi bi-arrow-up text-success"></i>
|
||||
{% elseif diff.count_diff_30j < 0 %}
|
||||
<i class="bi bi-arrow-down text-danger"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-arrow-right text-secondary"></i>
|
||||
{% endif %}
|
||||
{{ diff.count_diff_30j > 0 ? '+' ~ diff.count_diff_30j : diff.count_diff_30j }} objets
|
||||
<br>
|
||||
<span class="small text-muted">Complétion :
|
||||
{% if diff.completion_diff_30j > 0 %}
|
||||
<i class="bi bi-arrow-up text-success"></i>
|
||||
{% elseif diff.completion_diff_30j < 0 %}
|
||||
<i class="bi bi-arrow-down text-danger"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-arrow-right text-secondary"></i>
|
||||
{% endif %}
|
||||
{{ diff.completion_diff_30j > 0 ? '+' ~ diff.completion_diff_30j : diff.completion_diff_30j }}%
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if diff.count_diff_6mois > 0 %}
|
||||
<i class="bi bi-arrow-up text-success"></i>
|
||||
{% elseif diff.count_diff_6mois < 0 %}
|
||||
<i class="bi bi-arrow-down text-danger"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-arrow-right text-secondary"></i>
|
||||
{% endif %}
|
||||
{{ diff.count_diff_6mois > 0 ? '+' ~ diff.count_diff_6mois : diff.count_diff_6mois }} objets
|
||||
<br>
|
||||
<span class="small text-muted">Complétion :
|
||||
{% if diff.completion_diff_6mois > 0 %}
|
||||
<i class="bi bi-arrow-up text-success"></i>
|
||||
{% elseif diff.completion_diff_6mois < 0 %}
|
||||
<i class="bi bi-arrow-down text-danger"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-arrow-right text-secondary"></i>
|
||||
{% endif %}
|
||||
{{ diff.completion_diff_6mois > 0 ? '+' ~ diff.completion_diff_6mois : diff.completion_diff_6mois }}%
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if not has_change %}
|
||||
<tr><td colspan="3" class="text-muted">Aucun changement significatif cette semaine.</td></tr>
|
||||
<tr><td colspan="4" class="text-muted">Aucun changement significatif cette semaine.</td></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -101,6 +101,23 @@
|
|||
.btn-josm {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
/* Bouton flottant suggestion desktop */
|
||||
.suggestion-float-btn {
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
right: 32px;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||
display: none;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.suggestion-float-btn { display: block; }
|
||||
.suggestion-footer-btn { display: none !important; }
|
||||
}
|
||||
@media (max-width: 767.98px) {
|
||||
.suggestion-float-btn { display: none !important; }
|
||||
.suggestion-footer-btn { display: block; width: 100%; margin: 0; border-radius: 0; }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -167,6 +184,9 @@
|
|||
<a href="{{ path('admin_street_completion', {'insee_code': stats.zone}) }}" class="btn btn-outline-success">
|
||||
<i class="bi bi-signpost"></i> Complétion des rues
|
||||
</a>
|
||||
<a href="https://forum.openstreetmap.fr/t/osm-mon-commerce/34403/11" class="btn btn-outline-info ms-auto d-none d-md-inline-block suggestion-float-btn" target="_blank" rel="noopener">
|
||||
<i class="bi bi-chat-dots"></i> Faire une suggestion
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if josm_url %}
|
||||
|
@ -254,6 +274,29 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card mt-5">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-tags"></i> Statistiques des tags utilisés dans les objets trouvés
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div id="tags-stats-block">
|
||||
<table class="table table-sm table-bordered mb-0" id="tags-stats-table" style="max-width:600px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tag</th>
|
||||
<th>Nombre d'occurrences</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td colspan="2" class="text-muted">Chargement...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="https://forum.openstreetmap.fr/t/osm-mon-commerce/34403/11" class="btn btn-info suggestion-footer-btn mt-4 mb-2" target="_blank" rel="noopener" style="display:none;">
|
||||
<i class="bi bi-chat-dots"></i> Faire une suggestion
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
|
@ -329,6 +372,8 @@
|
|||
.then(data => {
|
||||
if (!data.elements || data.elements.length === 0) {
|
||||
document.getElementById('themeMap').innerHTML = '<div class="alert alert-warning">Aucun objet trouvé via Overpass pour ce thème.</div>';
|
||||
// Vider le tableau des tags
|
||||
document.querySelector('#tags-stats-table tbody').innerHTML = '<tr><td colspan="2" class="text-muted">Aucun objet trouvé</td></tr>';
|
||||
return;
|
||||
}
|
||||
// Centrage carte
|
||||
|
@ -401,6 +446,25 @@
|
|||
.setPopup(new maplibregl.Popup({ offset: 18 }).setHTML(popupHtml))
|
||||
.addTo(mapInstance);
|
||||
});
|
||||
// --- Statistiques des tags ---
|
||||
const tagCounts = {};
|
||||
data.elements.forEach(e => {
|
||||
if (e.tags) {
|
||||
Object.entries(e.tags).forEach(([k, v]) => {
|
||||
if (!tagCounts[k]) tagCounts[k] = 0;
|
||||
tagCounts[k]++;
|
||||
});
|
||||
}
|
||||
});
|
||||
const tbody = document.querySelector('#tags-stats-table tbody');
|
||||
if (Object.keys(tagCounts).length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="2" class="text-muted">Aucun tag trouvé</td></tr>';
|
||||
} else {
|
||||
tbody.innerHTML = Object.entries(tagCounts)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([k, v]) => `<tr><td><code>${k}</code></td><td>${v}</td></tr>`)
|
||||
.join('');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
document.getElementById('themeMap').innerHTML = '<div class="alert alert-danger">Erreur lors de la requête Overpass : ' + err + '</div>';
|
||||
|
|
|
@ -14,10 +14,6 @@
|
|||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="bi bi-geo-alt"></i> Labourage d'une nouvelle ville</h3>
|
||||
<p class="mb-0">Entrez le code INSEE de votre ville pour l'ajouter à notre base de données</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% include 'public/labourage-form.html.twig' %}
|
||||
</div>
|
||||
|
|
|
@ -152,7 +152,5 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<script src="{{ asset('js/table-sortable.js') }}"></script>
|
||||
<script>document.querySelectorAll('table.table-sort').forEach(t => window.TableSortable && TableSortable.initTable(t));</script>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Add table
Add a link
Reference in a new issue