mirror of
https://forge.chapril.org/tykayn/osm-commerces
synced 2025-10-04 17:04:53 +02:00
add measure on checking advanced graph
This commit is contained in:
parent
46f8b3f6ab
commit
1659864efb
3 changed files with 133 additions and 32 deletions
|
@ -2,13 +2,12 @@
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Entity\Stats;
|
|
||||||
use App\Entity\CityFollowUp;
|
use App\Entity\CityFollowUp;
|
||||||
use App\Service\Motocultrice;
|
use App\Entity\Stats;
|
||||||
use App\Service\FollowUpService;
|
use App\Service\FollowUpService;
|
||||||
|
use App\Service\Motocultrice;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
@ -25,10 +24,11 @@ class FollowUpController extends AbstractController
|
||||||
public function recordCityFollowUp(
|
public function recordCityFollowUp(
|
||||||
EntityManagerInterface $em,
|
EntityManagerInterface $em,
|
||||||
\Symfony\Component\HttpFoundation\Request $request
|
\Symfony\Component\HttpFoundation\Request $request
|
||||||
): Response {
|
): Response
|
||||||
|
{
|
||||||
$insee_code = $request->request->get('insee_code');
|
$insee_code = $request->request->get('insee_code');
|
||||||
$measure_label = $request->request->get('measure_label');
|
$measure_label = $request->request->get('measure_label');
|
||||||
$measure_value = $request->request->getFloat('measure_value');
|
$measure_value = (float)$request->request->get('measure_value');
|
||||||
|
|
||||||
if (!$insee_code || !$measure_label || $measure_value === null) {
|
if (!$insee_code || !$measure_label || $measure_value === null) {
|
||||||
return $this->json([
|
return $this->json([
|
||||||
|
@ -48,9 +48,9 @@ class FollowUpController extends AbstractController
|
||||||
// Check if the same measure was recorded less than an hour ago
|
// Check if the same measure was recorded less than an hour ago
|
||||||
$oneHourAgo = new \DateTime('-1 hour');
|
$oneHourAgo = new \DateTime('-1 hour');
|
||||||
$recentFollowUp = $em->getRepository(CityFollowUp::class)
|
$recentFollowUp = $em->getRepository(CityFollowUp::class)
|
||||||
->findRecentByStatsAndName($stats, $measure_label, $oneHourAgo);
|
->findRecentByStatsAndName($stats->getId(), $measure_label, $oneHourAgo);
|
||||||
|
|
||||||
if ($recentFollowUp) {
|
if ($recentFollowUp && $recentFollowUp->getMeasure() == $measure_value) {
|
||||||
return $this->json([
|
return $this->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'A measure with the same label was recorded less than an hour ago',
|
'message' => 'A measure with the same label was recorded less than an hour ago',
|
||||||
|
@ -86,7 +86,8 @@ class FollowUpController extends AbstractController
|
||||||
|
|
||||||
|
|
||||||
#[Route('/admin/followup/{insee_code}/delete', name: 'admin_followup_delete', requirements: ['insee_code' => '\\d+'])]
|
#[Route('/admin/followup/{insee_code}/delete', name: 'admin_followup_delete', requirements: ['insee_code' => '\\d+'])]
|
||||||
public function deleteFollowups(string $insee_code, EntityManagerInterface $em): Response {
|
public function deleteFollowups(string $insee_code, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
$stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
$stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
||||||
if (!$stats) {
|
if (!$stats) {
|
||||||
$this->addFlash('error', '9 Aucune stats trouvée pour ce code INSEE.');
|
$this->addFlash('error', '9 Aucune stats trouvée pour ce code INSEE.');
|
||||||
|
@ -106,7 +107,8 @@ class FollowUpController extends AbstractController
|
||||||
string $insee_code,
|
string $insee_code,
|
||||||
Motocultrice $motocultrice,
|
Motocultrice $motocultrice,
|
||||||
EntityManagerInterface $em
|
EntityManagerInterface $em
|
||||||
): Response {
|
): Response
|
||||||
|
{
|
||||||
$stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
$stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
||||||
if (!$stats) {
|
if (!$stats) {
|
||||||
$this->addFlash('error', '10 Aucune stats trouvée pour ce code INSEE.');
|
$this->addFlash('error', '10 Aucune stats trouvée pour ce code INSEE.');
|
||||||
|
@ -122,7 +124,8 @@ class FollowUpController extends AbstractController
|
||||||
string $insee_code,
|
string $insee_code,
|
||||||
EntityManagerInterface $em,
|
EntityManagerInterface $em,
|
||||||
Motocultrice $motocultrice
|
Motocultrice $motocultrice
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
$stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
$stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
||||||
if (!$stats) {
|
if (!$stats) {
|
||||||
$this->addFlash('error', '11 Aucune stats trouvée pour ce code INSEE.');
|
$this->addFlash('error', '11 Aucune stats trouvée pour ce code INSEE.');
|
||||||
|
@ -168,7 +171,7 @@ class FollowUpController extends AbstractController
|
||||||
$all_points = array_slice($all_points, 0, 20);
|
$all_points = array_slice($all_points, 0, 20);
|
||||||
// Tri par date dans chaque série
|
// Tri par date dans chaque série
|
||||||
foreach ($series as &$points) {
|
foreach ($series as &$points) {
|
||||||
usort($points, function($a, $b) {
|
usort($points, function ($a, $b) {
|
||||||
return strtotime($a['date']) <=> strtotime($b['date']);
|
return strtotime($a['date']) <=> strtotime($b['date']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -189,7 +192,7 @@ class FollowUpController extends AbstractController
|
||||||
$count_series = $series[$type . '_count'] ?? [];
|
$count_series = $series[$type . '_count'] ?? [];
|
||||||
$completion_series = $series[$type . '_completion'] ?? [];
|
$completion_series = $series[$type . '_completion'] ?? [];
|
||||||
// Fonction utilitaire pour trouver la valeur la plus proche avant ou égale à une date
|
// Fonction utilitaire pour trouver la valeur la plus proche avant ou égale à une date
|
||||||
$findValueAtOrBefore = function($series, \DateTime $date) {
|
$findValueAtOrBefore = function ($series, \DateTime $date) {
|
||||||
$val = null;
|
$val = null;
|
||||||
foreach (array_reverse($series) as $point) {
|
foreach (array_reverse($series) as $point) {
|
||||||
$ptDate = \DateTime::createFromFormat('Y-m-d', $point['date']);
|
$ptDate = \DateTime::createFromFormat('Y-m-d', $point['date']);
|
||||||
|
@ -201,7 +204,7 @@ class FollowUpController extends AbstractController
|
||||||
return $val;
|
return $val;
|
||||||
};
|
};
|
||||||
// Valeurs aux bornes
|
// Valeurs aux bornes
|
||||||
$val_now = count($count_series) ? $count_series[count($count_series)-1]['value'] : null;
|
$val_now = count($count_series) ? $count_series[count($count_series) - 1]['value'] : null;
|
||||||
$val_7j = $findValueAtOrBefore($count_series, $periods['7j']);
|
$val_7j = $findValueAtOrBefore($count_series, $periods['7j']);
|
||||||
$val_30j = $findValueAtOrBefore($count_series, $periods['30j']);
|
$val_30j = $findValueAtOrBefore($count_series, $periods['30j']);
|
||||||
$val_6mois = $findValueAtOrBefore($count_series, $periods['6mois']);
|
$val_6mois = $findValueAtOrBefore($count_series, $periods['6mois']);
|
||||||
|
@ -210,7 +213,7 @@ class FollowUpController extends AbstractController
|
||||||
$diff_30j = ($val_7j !== null && $val_30j !== null) ? $val_7j - $val_30j : 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;
|
$diff_6mois = ($val_30j !== null && $val_6mois !== null) ? $val_30j - $val_6mois : null;
|
||||||
// Idem pour la complétion
|
// Idem pour la complétion
|
||||||
$comp_now = count($completion_series) ? $completion_series[count($completion_series)-1]['value'] : null;
|
$comp_now = count($completion_series) ? $completion_series[count($completion_series) - 1]['value'] : null;
|
||||||
$comp_7j = $findValueAtOrBefore($completion_series, $periods['7j']);
|
$comp_7j = $findValueAtOrBefore($completion_series, $periods['7j']);
|
||||||
$comp_30j = $findValueAtOrBefore($completion_series, $periods['30j']);
|
$comp_30j = $findValueAtOrBefore($completion_series, $periods['30j']);
|
||||||
$comp_6mois = $findValueAtOrBefore($completion_series, $periods['6mois']);
|
$comp_6mois = $findValueAtOrBefore($completion_series, $periods['6mois']);
|
||||||
|
@ -252,7 +255,8 @@ class FollowUpController extends AbstractController
|
||||||
public function followupAll(
|
public function followupAll(
|
||||||
EntityManagerInterface $em,
|
EntityManagerInterface $em,
|
||||||
Motocultrice $motocultrice
|
Motocultrice $motocultrice
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
$statsList = $em->getRepository(Stats::class)->findAll();
|
$statsList = $em->getRepository(Stats::class)->findAll();
|
||||||
foreach ($statsList as $stats) {
|
foreach ($statsList as $stats) {
|
||||||
$this->followUpService->generateCityFollowUps($stats, $motocultrice, $em);
|
$this->followUpService->generateCityFollowUps($stats, $motocultrice, $em);
|
||||||
|
@ -262,14 +266,16 @@ class FollowUpController extends AbstractController
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/admin/followup/global', name: 'admin_followup_global')]
|
#[Route('/admin/followup/global', name: 'admin_followup_global')]
|
||||||
public function followupGlobal(EntityManagerInterface $em) {
|
public function followupGlobal(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
$this->followUpService->generateGlobalFollowUps($em);
|
$this->followUpService->generateGlobalFollowUps($em);
|
||||||
$this->addFlash('success', 'Suivi global généré pour toutes les villes.');
|
$this->addFlash('success', 'Suivi global généré pour toutes les villes.');
|
||||||
return $this->redirectToRoute('admin_followup_global_graph');
|
return $this->redirectToRoute('admin_followup_global_graph');
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/admin/followup/global/graph', name: 'admin_followup_global_graph')]
|
#[Route('/admin/followup/global/graph', name: 'admin_followup_global_graph')]
|
||||||
public function followupGlobalGraph(EntityManagerInterface $em) {
|
public function followupGlobalGraph(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
$stats = $em->getRepository(Stats::class)->findOneBy(['zone' => '00000']);
|
$stats = $em->getRepository(Stats::class)->findOneBy(['zone' => '00000']);
|
||||||
if (!$stats) {
|
if (!$stats) {
|
||||||
$this->addFlash('error', 'Aucun suivi global trouvé.');
|
$this->addFlash('error', 'Aucun suivi global trouvé.');
|
||||||
|
@ -288,7 +294,7 @@ class FollowUpController extends AbstractController
|
||||||
}
|
}
|
||||||
// Tri par date dans chaque série
|
// Tri par date dans chaque série
|
||||||
foreach ($series as &$points) {
|
foreach ($series as &$points) {
|
||||||
usort($points, function($a, $b) {
|
usort($points, function ($a, $b) {
|
||||||
return strtotime($a['date']) <=> strtotime($b['date']);
|
return strtotime($a['date']) <=> strtotime($b['date']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -308,7 +314,8 @@ class FollowUpController extends AbstractController
|
||||||
string $theme,
|
string $theme,
|
||||||
EntityManagerInterface $em,
|
EntityManagerInterface $em,
|
||||||
Motocultrice $motocultrice
|
Motocultrice $motocultrice
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
$stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
$stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
||||||
if (!$stats) {
|
if (!$stats) {
|
||||||
$this->addFlash('error', '12 Aucune stats trouvée pour ce code INSEE.');
|
$this->addFlash('error', '12 Aucune stats trouvée pour ce code INSEE.');
|
||||||
|
@ -353,6 +360,7 @@ class FollowUpController extends AbstractController
|
||||||
'icon' => FollowUpService::getFollowUpIcons()[$theme] ?? 'bi-question-circle',
|
'icon' => FollowUpService::getFollowUpIcons()[$theme] ?? 'bi-question-circle',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/admin/followup/unused-stores', name: 'admin_followup_unused_stores')]
|
#[Route('/admin/followup/unused-stores', name: 'admin_followup_unused_stores')]
|
||||||
public function unusedStores(): Response
|
public function unusedStores(): Response
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,7 +17,7 @@ class CityFollowUpRepository extends ServiceEntityRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function findRecentByStatsAndName(Stats $stats, string $name, \DateTime $since): ?CityFollowUp
|
public function findRecentByStatsAndName(string $stats, string $name, \DateTime $since): ?CityFollowUp
|
||||||
{
|
{
|
||||||
return $this->createQueryBuilder('c')
|
return $this->createQueryBuilder('c')
|
||||||
->andWhere('c.stats = :stats')
|
->andWhere('c.stats = :stats')
|
||||||
|
@ -29,7 +29,6 @@ class CityFollowUpRepository extends ServiceEntityRepository
|
||||||
->orderBy('c.date', 'DESC')
|
->orderBy('c.date', 'DESC')
|
||||||
->setMaxResults(1)
|
->setMaxResults(1)
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->getOneOrNullResult()
|
->getOneOrNullResult();
|
||||||
;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -527,6 +527,77 @@
|
||||||
if (average_completion && current_completion) {
|
if (average_completion && current_completion) {
|
||||||
current_completion.textContent = average_completion.toFixed(2) + ' %';
|
current_completion.textContent = average_completion.toFixed(2) + ' %';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send measurement to /api/city-followup
|
||||||
|
const insee_code = '{{ stats.zone }}';
|
||||||
|
const theme = '{{ theme }}';
|
||||||
|
|
||||||
|
// Prepare data for the API request
|
||||||
|
const measureData = new FormData();
|
||||||
|
measureData.append('insee_code', insee_code);
|
||||||
|
measureData.append('measure_label', theme + '_count');
|
||||||
|
measureData.append('measure_value', count_objects);
|
||||||
|
|
||||||
|
// Send count measurement
|
||||||
|
fetch('/api/city-followup', {
|
||||||
|
method: 'POST',
|
||||||
|
body: measureData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(result => {
|
||||||
|
console.log('Count measurement saved:', result);
|
||||||
|
if (result.success) {
|
||||||
|
// Add the new measurement to the chart data
|
||||||
|
const newMeasurement = {
|
||||||
|
date: result.follow_up.date,
|
||||||
|
value: result.follow_up.measure
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add to the global countData array
|
||||||
|
if (Array.isArray(window.countData)) {
|
||||||
|
window.countData.push(newMeasurement);
|
||||||
|
// Update the chart
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error saving count measurement:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send completion measurement
|
||||||
|
if (average_completion) {
|
||||||
|
const completionData = new FormData();
|
||||||
|
completionData.append('insee_code', insee_code);
|
||||||
|
completionData.append('measure_label', theme + '_completion');
|
||||||
|
completionData.append('measure_value', average_completion);
|
||||||
|
|
||||||
|
fetch('/api/city-followup', {
|
||||||
|
method: 'POST',
|
||||||
|
body: completionData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(result => {
|
||||||
|
console.log('Completion measurement saved:', result);
|
||||||
|
if (result.success) {
|
||||||
|
// Add the new measurement to the chart data
|
||||||
|
const newMeasurement = {
|
||||||
|
date: result.follow_up.date,
|
||||||
|
value: result.follow_up.measure
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add to the global completionData array
|
||||||
|
if (Array.isArray(window.completionData)) {
|
||||||
|
window.completionData.push(newMeasurement);
|
||||||
|
// Update the chart
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error saving completion measurement:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
const tbody = document.querySelector('#tags-stats-table tbody');
|
const tbody = document.querySelector('#tags-stats-table tbody');
|
||||||
if (Object.keys(tagCounts).length === 0) {
|
if (Object.keys(tagCounts).length === 0) {
|
||||||
tbody.innerHTML = '<tr><td colspan="2" class="text-muted">Aucun tag trouvé</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="2" class="text-muted">Aucun tag trouvé</td></tr>';
|
||||||
|
@ -551,8 +622,9 @@
|
||||||
const countData = {{ count_data|json_encode|raw }};
|
const countData = {{ count_data|json_encode|raw }};
|
||||||
console.log('Count data:', countData);
|
console.log('Count data:', countData);
|
||||||
|
|
||||||
window.countData = countData
|
window.countData = countData;
|
||||||
const completionData = {{ completion_data|json_encode|raw }};
|
const completionData = {{ completion_data|json_encode|raw }};
|
||||||
|
window.completionData = completionData;
|
||||||
console.log('Completion data:', completionData);
|
console.log('Completion data:', completionData);
|
||||||
|
|
||||||
// Current metrics from server
|
// Current metrics from server
|
||||||
|
@ -694,6 +766,28 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Function to update the chart with new data
|
||||||
|
function updateChart() {
|
||||||
|
// Get the chart instance
|
||||||
|
const chartInstance = Chart.getChart('themeChart');
|
||||||
|
if (!chartInstance) return;
|
||||||
|
|
||||||
|
// Update the datasets
|
||||||
|
chartInstance.data.datasets[0].data = Array.isArray(window.countData)
|
||||||
|
? window.countData.map(d => ({x: new Date(d.date), y: d.value}))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (Array.isArray(window.completionData)) {
|
||||||
|
chartInstance.data.datasets[1].data = window.completionData.map(d => ({
|
||||||
|
x: new Date(d.date),
|
||||||
|
y: d.value
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the chart
|
||||||
|
chartInstance.update();
|
||||||
|
}
|
||||||
|
|
||||||
// Initialiser les statistiques
|
// Initialiser les statistiques
|
||||||
updateStats();
|
updateStats();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue