mirror of
https://forge.chapril.org/tykayn/osm-commerces
synced 2025-11-19 23:00:36 +01:00
calcul progression
This commit is contained in:
parent
2bbc7153c6
commit
c81affd3e3
8 changed files with 275 additions and 50 deletions
|
|
@ -446,6 +446,13 @@ final class AdminController extends AbstractController
|
||||||
if ($count) $latestFollowups['places']['count'] = $count;
|
if ($count) $latestFollowups['places']['count'] = $count;
|
||||||
if ($completion) $latestFollowups['places']['completion'] = $completion;
|
if ($completion) $latestFollowups['places']['completion'] = $completion;
|
||||||
|
|
||||||
|
// Calculer la progression sur 7 jours pour chaque type
|
||||||
|
$progression7Days = [];
|
||||||
|
foreach ($types as $type) {
|
||||||
|
$progression7Days[$type] = \App\Service\FollowUpService::calculate7DayProgression($stats, $type);
|
||||||
|
}
|
||||||
|
$progression7Days['places'] = \App\Service\FollowUpService::calculate7DayProgression($stats, 'places');
|
||||||
|
|
||||||
return $this->render('admin/stats.html.twig', [
|
return $this->render('admin/stats.html.twig', [
|
||||||
'stats' => $stats,
|
'stats' => $stats,
|
||||||
'commerces' => $commerces,
|
'commerces' => $commerces,
|
||||||
|
|
@ -460,6 +467,7 @@ final class AdminController extends AbstractController
|
||||||
'latestFollowups' => $latestFollowups,
|
'latestFollowups' => $latestFollowups,
|
||||||
'followup_labels' => \App\Service\FollowUpService::getFollowUpThemes(),
|
'followup_labels' => \App\Service\FollowUpService::getFollowUpThemes(),
|
||||||
'followup_icons' => \App\Service\FollowUpService::getFollowUpIcons(),
|
'followup_icons' => \App\Service\FollowUpService::getFollowUpIcons(),
|
||||||
|
'progression7Days' => $progression7Days,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -530,6 +538,7 @@ final class AdminController extends AbstractController
|
||||||
public function labourer(Request $request, string $insee_code, bool $updateExisting = true): Response
|
public function labourer(Request $request, string $insee_code, bool $updateExisting = true): Response
|
||||||
{
|
{
|
||||||
$deleteMissing = $request->query->getBoolean('deleteMissing', true);
|
$deleteMissing = $request->query->getBoolean('deleteMissing', true);
|
||||||
|
$disableFollowUpCleanup = $request->query->getBoolean('disableFollowUpCleanup', false);
|
||||||
|
|
||||||
$this->actionLogger->log('labourer', ['insee_code' => $insee_code]);
|
$this->actionLogger->log('labourer', ['insee_code' => $insee_code]);
|
||||||
|
|
||||||
|
|
@ -890,7 +899,7 @@ final class AdminController extends AbstractController
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
// Générer les suivis (followups) après la mise à jour des Places
|
// Générer les suivis (followups) après la mise à jour des Places
|
||||||
$this->followUpService->generateCityFollowUps($stats, $this->motocultrice, $this->entityManager);
|
$this->followUpService->generateCityFollowUps($stats, $this->motocultrice, $this->entityManager, $disableFollowUpCleanup);
|
||||||
|
|
||||||
$message = 'Labourage terminé avec succès. ' . $processedCount . ' nouveaux lieux traités.';
|
$message = 'Labourage terminé avec succès. ' . $processedCount . ' nouveaux lieux traités.';
|
||||||
if ($updateExisting) {
|
if ($updateExisting) {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
class FollowUpService
|
class FollowUpService
|
||||||
{
|
{
|
||||||
public function generateCityFollowUps(Stats $stats, Motocultrice $motocultrice, EntityManagerInterface $em): void
|
public function generateCityFollowUps(Stats $stats, Motocultrice $motocultrice, EntityManagerInterface $em, bool $disableCleanup = false): void
|
||||||
{
|
{
|
||||||
$insee_code = $stats->getZone();
|
$insee_code = $stats->getZone();
|
||||||
$elements = $motocultrice->followUpCity($insee_code) ?? [];
|
$elements = $motocultrice->followUpCity($insee_code) ?? [];
|
||||||
|
|
@ -71,13 +71,13 @@ class FollowUpService
|
||||||
} else {
|
} else {
|
||||||
$objects = [];
|
$objects = [];
|
||||||
}
|
}
|
||||||
$types[$type] = [
|
$types[$type] = ['objects' => $objects, 'label' => $label];
|
||||||
'label' => $label,
|
|
||||||
'objects' => $objects
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
$types['places'] = ['objects' => [], 'label' => 'Lieux'];
|
||||||
|
|
||||||
$now = new \DateTime();
|
$now = new \DateTime();
|
||||||
$persisted = 0;
|
$persisted = 0;
|
||||||
|
|
||||||
foreach ($types as $type => $data) {
|
foreach ($types as $type => $data) {
|
||||||
// Suivi du nombre
|
// Suivi du nombre
|
||||||
$followupCount = new CityFollowUp();
|
$followupCount = new CityFollowUp();
|
||||||
|
|
@ -153,30 +153,27 @@ class FollowUpService
|
||||||
});
|
});
|
||||||
} elseif ($type === 'advertising_board') {
|
} elseif ($type === 'advertising_board') {
|
||||||
$completed = array_filter($data['objects'], function($el) {
|
$completed = array_filter($data['objects'], function($el) {
|
||||||
// On considère complet si le tag "operator" ou "ref" est présent
|
return !empty($el['tags']['operator'] ?? null) || !empty($el['tags']['contact:phone'] ?? null);
|
||||||
return !empty($el['tags']['operator'] ?? null) || !empty($el['tags']['ref'] ?? null);
|
|
||||||
});
|
});
|
||||||
} elseif ($type === 'building') {
|
} elseif ($type === 'building') {
|
||||||
$completed = array_filter($data['objects'], function($el) {
|
$completed = array_filter($data['objects'], function($el) {
|
||||||
// Complet si ref:FR:RNB est rempli
|
return !empty($el['tags']['name'] ?? null) || !empty($el['tags']['ref'] ?? null);
|
||||||
return !empty($el['tags']['ref:FR:RNB'] ?? null);
|
|
||||||
});
|
});
|
||||||
} elseif ($type === 'email') {
|
} elseif ($type === 'email') {
|
||||||
$completed = $data['objects']; // Possède déjà un email ou contact:email
|
$completed = array_filter($data['objects'], function($el) {
|
||||||
|
return !empty($el['tags']['name'] ?? null) || !empty($el['tags']['phone'] ?? null);
|
||||||
|
});
|
||||||
} elseif ($type === 'bench') {
|
} elseif ($type === 'bench') {
|
||||||
$completed = array_filter($data['objects'], function($el) {
|
$completed = array_filter($data['objects'], function($el) {
|
||||||
// Complet si le tag "material" ou "backrest" est présent
|
|
||||||
return !empty($el['tags']['material'] ?? null) || !empty($el['tags']['backrest'] ?? null);
|
return !empty($el['tags']['material'] ?? null) || !empty($el['tags']['backrest'] ?? null);
|
||||||
});
|
});
|
||||||
} elseif ($type === 'waste_basket') {
|
} elseif ($type === 'waste_basket') {
|
||||||
$completed = array_filter($data['objects'], function($el) {
|
$completed = array_filter($data['objects'], function($el) {
|
||||||
// Complet si le tag "material" ou "ref" est présent
|
return !empty($el['tags']['waste'] ?? null) || !empty($el['tags']['recycling_type'] ?? null);
|
||||||
return !empty($el['tags']['material'] ?? null) || !empty($el['tags']['ref'] ?? null);
|
|
||||||
});
|
});
|
||||||
} elseif ($type === 'street_lamp') {
|
} elseif ($type === 'street_lamp') {
|
||||||
$completed = array_filter($data['objects'], function($el) {
|
$completed = array_filter($data['objects'], function($el) {
|
||||||
// Complet si le tag "lamp_type" ou "ref" est présent
|
return !empty($el['tags']['lamp_type'] ?? null) || !empty($el['tags']['height'] ?? null);
|
||||||
return !empty($el['tags']['lamp_type'] ?? null) || !empty($el['tags']['ref'] ?? null);
|
|
||||||
});
|
});
|
||||||
} elseif ($type === 'drinking_water') {
|
} elseif ($type === 'drinking_water') {
|
||||||
$completed = array_filter($data['objects'], function($el) {
|
$completed = array_filter($data['objects'], function($el) {
|
||||||
|
|
@ -226,7 +223,8 @@ class FollowUpService
|
||||||
$em->flush();
|
$em->flush();
|
||||||
$em->clear();
|
$em->clear();
|
||||||
|
|
||||||
// Suppression des mesures redondantes (même valeur consécutive, sauf la dernière)
|
// Suppression des mesures redondantes (même valeur consécutive, sauf la dernière) - désactivée si $disableCleanup est true
|
||||||
|
if (!$disableCleanup) {
|
||||||
$repo = $em->getRepository(CityFollowUp::class);
|
$repo = $em->getRepository(CityFollowUp::class);
|
||||||
foreach (array_keys(self::getFollowUpThemes()) as $type) {
|
foreach (array_keys(self::getFollowUpThemes()) as $type) {
|
||||||
foreach (['_count', '_completion'] as $suffix) {
|
foreach (['_count', '_completion'] as $suffix) {
|
||||||
|
|
@ -259,6 +257,7 @@ class FollowUpService
|
||||||
$em->flush();
|
$em->flush();
|
||||||
$em->clear();
|
$em->clear();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function generateGlobalFollowUps(EntityManagerInterface $em): void
|
public function generateGlobalFollowUps(EntityManagerInterface $em): void
|
||||||
{
|
{
|
||||||
|
|
@ -428,7 +427,121 @@ class FollowUpService
|
||||||
'street_lamp' => 'nwr["highway"="street_lamp"](area.searchArea);',
|
'street_lamp' => 'nwr["highway"="street_lamp"](area.searchArea);',
|
||||||
'drinking_water' => 'nwr["amenity"="drinking_water"](area.searchArea);',
|
'drinking_water' => 'nwr["amenity"="drinking_water"](area.searchArea);',
|
||||||
'tree' => 'nwr["natural"="tree"](area.searchArea);',
|
'tree' => 'nwr["natural"="tree"](area.searchArea);',
|
||||||
'places' => ''
|
'power_pole' => 'nwr["power"="pole"](area.searchArea);',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule la progression sur 7 jours pour un type donné
|
||||||
|
*/
|
||||||
|
public static function calculate7DayProgression(Stats $stats, string $type): array
|
||||||
|
{
|
||||||
|
$followups = $stats->getCityFollowUps();
|
||||||
|
$now = new \DateTime();
|
||||||
|
$refDate = clone $now;
|
||||||
|
$refDate->modify('-7 days');
|
||||||
|
|
||||||
|
// Récupérer toutes les mesures pour ce type
|
||||||
|
$countData = [];
|
||||||
|
$completionData = [];
|
||||||
|
|
||||||
|
foreach ($followups as $fu) {
|
||||||
|
if ($fu->getName() === $type . '_count') {
|
||||||
|
$countData[] = [
|
||||||
|
'date' => $fu->getDate(),
|
||||||
|
'value' => $fu->getMeasure()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ($fu->getName() === $type . '_completion') {
|
||||||
|
$completionData[] = [
|
||||||
|
'date' => $fu->getDate(),
|
||||||
|
'value' => $fu->getMeasure()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trier par date
|
||||||
|
usort($countData, fn($a, $b) => $a['date'] <=> $b['date']);
|
||||||
|
usort($completionData, fn($a, $b) => $a['date'] <=> $b['date']);
|
||||||
|
|
||||||
|
$countDelta = self::calculateDelta($countData, $refDate);
|
||||||
|
$completionDelta = self::calculateDelta($completionData, $refDate);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'count' => $countDelta,
|
||||||
|
'completion' => $completionDelta
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule le delta pour une série de données
|
||||||
|
*/
|
||||||
|
private static function calculateDelta(array $data, \DateTime $refDate): ?float
|
||||||
|
{
|
||||||
|
if (empty($data)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$last = end($data)['value'];
|
||||||
|
|
||||||
|
// Chercher la mesure exacte à la date de référence
|
||||||
|
$exactRef = null;
|
||||||
|
foreach (array_reverse($data) as $point) {
|
||||||
|
if ($point['date'] <= $refDate) {
|
||||||
|
$exactRef = $point['value'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si on a trouvé une mesure exacte, l'utiliser
|
||||||
|
if ($exactRef !== null) {
|
||||||
|
return $last - $exactRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sinon, chercher les deux mesures les plus proches pour faire une interpolation
|
||||||
|
$beforeRef = null;
|
||||||
|
$afterRef = null;
|
||||||
|
$beforeDate = null;
|
||||||
|
$afterDate = null;
|
||||||
|
|
||||||
|
// Chercher la mesure juste avant la date de référence
|
||||||
|
foreach (array_reverse($data) as $point) {
|
||||||
|
if ($point['date'] < $refDate) {
|
||||||
|
$beforeRef = $point['value'];
|
||||||
|
$beforeDate = $point['date'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chercher la mesure juste après la date de référence
|
||||||
|
foreach ($data as $point) {
|
||||||
|
if ($point['date'] > $refDate) {
|
||||||
|
$afterRef = $point['value'];
|
||||||
|
$afterDate = $point['date'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si on a les deux mesures, faire une interpolation linéaire
|
||||||
|
if ($beforeRef !== null && $afterRef !== null && $beforeDate !== null && $afterDate !== null) {
|
||||||
|
$timeDiff = $afterDate->getTimestamp() - $beforeDate->getTimestamp();
|
||||||
|
$refTimeDiff = $refDate->getTimestamp() - $beforeDate->getTimestamp();
|
||||||
|
$ratio = $refTimeDiff / $timeDiff;
|
||||||
|
$interpolatedRef = $beforeRef + ($afterRef - $beforeRef) * $ratio;
|
||||||
|
return $last - $interpolatedRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si on n'a qu'une mesure avant, l'utiliser
|
||||||
|
if ($beforeRef !== null) {
|
||||||
|
return $last - $beforeRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si on n'a qu'une mesure après, l'utiliser
|
||||||
|
if ($afterRef !== null) {
|
||||||
|
return $last - $afterRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si aucune mesure n'est disponible, retourner null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ class Motocultrice
|
||||||
|
|
||||||
public $overpass_base_places = '
|
public $overpass_base_places = '
|
||||||
(
|
(
|
||||||
nw["amenity"~"^(restaurant|fast_food|cafe|fuel|pharmacy|bank|bar|hospital|post_office|clinic|pub|car_wash|ice_cream|driving_school|cinema|car_rental|nightclub|bureau_de_change|studio|internet_cafe|money_transfer|casino|vehicle_inspection|frozen_food|boat_rental|coworking_space|workshop|personal_service|camping|dancing_school|training|ski_school|ski_rental|dive_centre|driver_training|nursing_home|funeral_hall|doctors|dentist|theatre|kindergarten|language_school|stripclub|veterinary|convenience|supermarket|clothes|hairdresser|car_repair|bakery|beauty|car|hardware|mobile_phone|butcher|furniture|car_parts|alcohol|florist|scooter|variety_store|electronics|shoes|optician|jewelry|mall|gift|doityourself|greengrocer|books|bicycle|chemist|department_store|laundry|travel_agency|stationery|pet|sports|confectionery|tyres|cosmetics|computer|tailor|tobacco|storage_rental|dry_cleaning|trade|copyshop|motorcycle|funeral_directors|beverages|newsagent|garden_centre|massage|pastry|interior_decoration|general|deli|toys|houseware|wine|seafood|pawnbroker|tattoo|paint|wholesale|photo|second_hand|bed|kitchen|outdoor|fabric|antiques|coffee|gas|e-cigarette|perfumery|craft|hearing_aids|money_lender|appliance|electrical|tea|motorcycle_repair|boutique|baby_goods|bag|musical_instrument|dairy|pet_grooming|music|carpet|rental|fashion_accessories|cheese|chocolate|medical_supply|leather|sewing|cannabis|locksmith|games|video_games|hifi|window_blind|caravan|tool_hire|household_linen|bathroom_furnishing|shoe_repair|watches|nutrition_supplements|fishing|erotic|frame|grocery|boat|repair|weapons|gold_buyer|lighting|pottery|security|groundskeeping|herbalist|curtain|health_food|flooring|printer_ink|anime|camera|scuba_diving|candles|printing|garden_furniture|food|estate_agent|insurance|it|accountant|employment_agency|tax_advisor|financial|advertising_agency|logistics|newspaper|financial_advisor|consulting|travel_agent|coworking|moving_company|lawyer|architect|construction_company|credit_broker|graphic_design|property_management|cleaning)$"](area.searchArea);
|
nw["amenity"~"^(library|townhall|restaurant|fast_food|cafe|fuel|pharmacy|bank|bar|hospital|post_office|clinic|pub|car_wash|ice_cream|driving_school|cinema|car_rental|nightclub|bureau_de_change|studio|internet_cafe|money_transfer|casino|vehicle_inspection|frozen_food|boat_rental|coworking_space|workshop|personal_service|camping|dancing_school|training|ski_school|ski_rental|dive_centre|driver_training|nursing_home|funeral_hall|doctors|dentist|theatre|kindergarten|language_school|stripclub|veterinary|convenience|supermarket|clothes|hairdresser|car_repair|bakery|beauty|car|hardware|mobile_phone|butcher|furniture|car_parts|alcohol|florist|scooter|variety_store|electronics|shoes|optician|jewelry|mall|gift|doityourself|greengrocer|books|bicycle|chemist|department_store|laundry|travel_agency|stationery|pet|sports|confectionery|tyres|cosmetics|computer|tailor|tobacco|storage_rental|dry_cleaning|trade|copyshop|motorcycle|funeral_directors|beverages|newsagent|garden_centre|massage|pastry|interior_decoration|general|deli|toys|houseware|wine|seafood|pawnbroker|tattoo|paint|wholesale|photo|second_hand|bed|kitchen|outdoor|fabric|antiques|coffee|gas|e-cigarette|perfumery|craft|hearing_aids|money_lender|appliance|electrical|tea|motorcycle_repair|boutique|baby_goods|bag|musical_instrument|dairy|pet_grooming|music|carpet|rental|fashion_accessories|cheese|chocolate|medical_supply|leather|sewing|cannabis|locksmith|games|video_games|hifi|window_blind|caravan|tool_hire|household_linen|bathroom_furnishing|shoe_repair|watches|nutrition_supplements|fishing|erotic|frame|grocery|boat|repair|weapons|gold_buyer|lighting|pottery|security|groundskeeping|herbalist|curtain|health_food|flooring|printer_ink|anime|camera|scuba_diving|candles|printing|garden_furniture|food|estate_agent|insurance|it|accountant|employment_agency|tax_advisor|financial|advertising_agency|logistics|newspaper|financial_advisor|consulting|travel_agent|coworking|moving_company|lawyer|architect|construction_company|credit_broker|graphic_design|property_management|cleaning)$"](area.searchArea);
|
||||||
nw["shop"]["shop"!~"vacant"](area.searchArea);
|
nw["shop"]["shop"!~"vacant"](area.searchArea);
|
||||||
nw["tourism"~"^(hotel|hostel|motel|wilderness_hut|yes|chalet|gallery|guest_house|museum|zoo|theme_park|aquarium|alpine_hut|apartment)$"](area.searchArea);
|
nw["tourism"~"^(hotel|hostel|motel|wilderness_hut|yes|chalet|gallery|guest_house|museum|zoo|theme_park|aquarium|alpine_hut|apartment)$"](area.searchArea);
|
||||||
nw["healthcare"](area.searchArea);
|
nw["healthcare"](area.searchArea);
|
||||||
|
|
|
||||||
|
|
@ -101,28 +101,99 @@ if (canvas) {
|
||||||
// Affichage de la progression sur une semaine
|
// Affichage de la progression sur une semaine
|
||||||
function getDelta(data, days) {
|
function getDelta(data, days) {
|
||||||
if (!data.length) return null;
|
if (!data.length) return null;
|
||||||
|
|
||||||
const now = new Date(data[data.length - 1].x);
|
const now = new Date(data[data.length - 1].x);
|
||||||
const refDate = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
|
const refDate = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
|
||||||
let ref = null;
|
const last = data[data.length - 1].y;
|
||||||
|
|
||||||
|
// Chercher la mesure exacte à la date de référence
|
||||||
|
let exactRef = null;
|
||||||
for (let i = data.length - 1; i >= 0; i--) {
|
for (let i = data.length - 1; i >= 0; i--) {
|
||||||
const d = new Date(data[i].x);
|
const d = new Date(data[i].x);
|
||||||
if (d <= refDate) {
|
if (d <= refDate) {
|
||||||
ref = data[i].y;
|
exactRef = data[i].y;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const last = data[data.length - 1].y;
|
|
||||||
return ref !== null ? last - ref : null;
|
// Si on a trouvé une mesure exacte, l'utiliser
|
||||||
|
if (exactRef !== null) {
|
||||||
|
return last - exactRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sinon, chercher les deux mesures les plus proches pour faire une interpolation
|
||||||
|
let beforeRef = null;
|
||||||
|
let afterRef = null;
|
||||||
|
let beforeDate = null;
|
||||||
|
let afterDate = null;
|
||||||
|
|
||||||
|
// Chercher la mesure juste avant la date de référence
|
||||||
|
for (let i = data.length - 1; i >= 0; i--) {
|
||||||
|
const d = new Date(data[i].x);
|
||||||
|
if (d < refDate) {
|
||||||
|
beforeRef = data[i].y;
|
||||||
|
beforeDate = d;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chercher la mesure juste après la date de référence
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
const d = new Date(data[i].x);
|
||||||
|
if (d > refDate) {
|
||||||
|
afterRef = data[i].y;
|
||||||
|
afterDate = d;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si on a les deux mesures, faire une interpolation linéaire
|
||||||
|
if (beforeRef !== null && afterRef !== null && beforeDate !== null && afterDate !== null) {
|
||||||
|
const timeDiff = afterDate.getTime() - beforeDate.getTime();
|
||||||
|
const refTimeDiff = refDate.getTime() - beforeDate.getTime();
|
||||||
|
const ratio = refTimeDiff / timeDiff;
|
||||||
|
const interpolatedRef = beforeRef + (afterRef - beforeRef) * ratio;
|
||||||
|
return last - interpolatedRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si on n'a qu'une mesure avant, l'utiliser
|
||||||
|
if (beforeRef !== null) {
|
||||||
|
return last - beforeRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si on n'a qu'une mesure après, l'utiliser
|
||||||
|
if (afterRef !== null) {
|
||||||
|
return last - afterRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si aucune mesure n'est disponible, retourner null
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDelta(val) {
|
function formatDelta(val) {
|
||||||
if (val === null) return 'Pas de données';
|
if (val === null) return 'Pas de données';
|
||||||
if (val === 0) return '0';
|
if (val === 0) return '0';
|
||||||
return (val > 0 ? '+' : '') + val;
|
return (val > 0 ? '+' : '') + val;
|
||||||
}
|
}
|
||||||
|
|
||||||
const delta7dCount = getDelta(countData, 7);
|
const delta7dCount = getDelta(countData, 7);
|
||||||
|
const delta7dCompletion = getDelta(completionData, 7);
|
||||||
|
|
||||||
const infoDiv = document.createElement('div');
|
const infoDiv = document.createElement('div');
|
||||||
infoDiv.className = 'mt-3 alert ' + (delta7dCount === null ? 'alert-secondary' : 'alert-info');
|
infoDiv.className = 'mt-3 alert ' + (delta7dCount === null ? 'alert-secondary' : 'alert-info');
|
||||||
infoDiv.innerHTML = `<strong>Progression sur 7 jours :</strong> ${delta7dCount === null ? '<span title="Données insuffisantes pour calculer la progression">Aucune donnée</span>' : (delta7dCount > 0 ? '+' + delta7dCount : delta7dCount === 0 ? '0' : delta7dCount)}`;
|
|
||||||
|
let progressionText = '';
|
||||||
|
if (delta7dCount === null) {
|
||||||
|
progressionText = '<span title="Données insuffisantes pour calculer la progression">Aucune donnée</span>';
|
||||||
|
} else {
|
||||||
|
const countText = delta7dCount > 0 ? '+' + delta7dCount : delta7dCount === 0 ? '0' : delta7dCount;
|
||||||
|
const completionText = delta7dCompletion !== null ?
|
||||||
|
(delta7dCompletion > 0 ? '+' + delta7dCompletion.toFixed(1) : delta7dCompletion === 0 ? '0' : delta7dCompletion.toFixed(1)) + '%' :
|
||||||
|
'N/A';
|
||||||
|
progressionText = `${countText} objets, ${completionText} complétion`;
|
||||||
|
}
|
||||||
|
|
||||||
|
infoDiv.innerHTML = `<strong>Progression sur 7 jours :</strong> ${progressionText}`;
|
||||||
canvas.parentNode.appendChild(infoDiv);
|
canvas.parentNode.appendChild(infoDiv);
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
@ -12,6 +12,9 @@
|
||||||
<a href="{{ path('app_admin_labourer', {'insee_code': stats.zone, 'deleteMissing': 1}) }}" class="btn btn-primary">
|
<a href="{{ path('app_admin_labourer', {'insee_code': stats.zone, 'deleteMissing': 1}) }}" class="btn btn-primary">
|
||||||
<i class="bi bi-shovel"></i> Labourer la zone
|
<i class="bi bi-shovel"></i> Labourer la zone
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{{ path('app_admin_labourer', {'insee_code': stats.zone, 'deleteMissing': 1, 'disableFollowUpCleanup': 1}) }}" class="btn btn-warning" title="Labourer sans nettoyer les suivis OSM">
|
||||||
|
<i class="bi bi-shield-check"></i> Labourer (sans nettoyage)
|
||||||
|
</a>
|
||||||
<a href="{{ path('app_admin_stats', {'insee_code': stats.zone}) }}" class="btn btn-info">
|
<a href="{{ path('app_admin_stats', {'insee_code': stats.zone}) }}" class="btn btn-info">
|
||||||
<i class="bi bi-bar-chart"></i> Voir les stats
|
<i class="bi bi-bar-chart"></i> Voir les stats
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
<div class="example-wrapper">
|
<div class="example-wrapper">
|
||||||
<h1>Labourage fait sur la zone "{{ stats.zone }} {{stats.name}}" ✅</h1>
|
<h1>Labourage fait sur la zone "{{ stats.zone }} {{stats.name}}" ✅</h1>
|
||||||
<a href="{{ path('app_admin_labourer', {'insee_code': stats.zone}) }}" class="btn btn-primary" id="labourer">Labourer les mises à jour</a>
|
<a href="{{ path('app_admin_labourer', {'insee_code': stats.zone}) }}" class="btn btn-primary" id="labourer">Labourer les mises à jour</a>
|
||||||
|
<a href="{{ path('app_admin_labourer', {'insee_code': stats.zone, 'deleteMissing': 1, 'disableFollowUpCleanup': 1}) }}" class="btn btn-warning" id="labourer-no-cleanup" title="Labourer sans nettoyer les suivis OSM">Labourer (sans nettoyage)</a>
|
||||||
<a href="{{ path('app_admin_stats', {'insee_code': stats.zone}) }}" class="btn btn-primary" id="labourer">Voir les résultats</a>
|
<a href="{{ path('app_admin_stats', {'insee_code': stats.zone}) }}" class="btn btn-primary" id="labourer">Voir les résultats</a>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<a href="{{ path('app_admin_labourer', {'insee_code': stats.zone, 'deleteMissing': 1}) }}" class="btn btn-primary" id="labourer">Labourer les mises à jour</a>
|
<a href="{{ path('app_admin_labourer', {'insee_code': stats.zone, 'deleteMissing': 1}) }}" class="btn btn-primary" id="labourer">Labourer les mises à jour</a>
|
||||||
|
<a href="{{ path('app_admin_labourer', {'insee_code': stats.zone, 'deleteMissing': 1, 'disableFollowUpCleanup': 1}) }}" class="btn btn-warning ms-2" id="labourer-no-cleanup" title="Labourer sans nettoyer les suivis OSM">
|
||||||
|
<i class="bi bi-shield-check"></i> Labourer (sans nettoyage)
|
||||||
|
</a>
|
||||||
<a href="{{ path('admin_followup_graph', {'insee_code': stats.zone}) }}" class="btn btn-info ms-2" id="followup-graph-link">
|
<a href="{{ path('admin_followup_graph', {'insee_code': stats.zone}) }}" class="btn btn-info ms-2" id="followup-graph-link">
|
||||||
<i class="bi bi-graph-up"></i> Suivi OSM (graphes)
|
<i class="bi bi-graph-up"></i> Suivi OSM (graphes)
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -169,6 +172,24 @@
|
||||||
<a href="http://127.0.0.1:8111/import?url=https://overpass-api.de/api/interpreter?data={{ overpass_query|url_encode }}" target="_blank" class="fw-bold text-decoration-underline text-dark" title="Charger dans JOSM">{{ followup_labels[type]|default(type|capitalize) }}</a><br>
|
<a href="http://127.0.0.1:8111/import?url=https://overpass-api.de/api/interpreter?data={{ overpass_query|url_encode }}" target="_blank" class="fw-bold text-decoration-underline text-dark" title="Charger dans JOSM">{{ followup_labels[type]|default(type|capitalize) }}</a><br>
|
||||||
<span title="Nombre"> {{ data.count is defined ? data.count.getMeasure() : '?' }}</span><br>
|
<span title="Nombre"> {{ data.count is defined ? data.count.getMeasure() : '?' }}</span><br>
|
||||||
<span title="Complétion"> {{ completion is not null ? completion : '?' }}%</span>
|
<span title="Complétion"> {{ completion is not null ? completion : '?' }}%</span>
|
||||||
|
{% if progression7Days[type] is defined %}
|
||||||
|
{% set countDelta = progression7Days[type].count %}
|
||||||
|
{% set completionDelta = progression7Days[type].completion %}
|
||||||
|
{% if countDelta is not null or completionDelta is not null %}
|
||||||
|
<small class="text-muted">
|
||||||
|
{% if countDelta is not null %}
|
||||||
|
<span title="Progression sur 7 jours - Nombre d'objets">
|
||||||
|
{{ countDelta > 0 ? '+' ~ countDelta : countDelta == 0 ? '0' : countDelta }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if completionDelta is not null %}
|
||||||
|
<span title="Progression sur 7 jours - Complétion">
|
||||||
|
{{ completionDelta > 0 ? '+' ~ completionDelta|round(1) : completionDelta == 0 ? '0' : completionDelta|round(1) }}%
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</small>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,13 @@
|
||||||
>
|
>
|
||||||
<i class="bi bi-recycle"></i>
|
<i class="bi bi-recycle"></i>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{{ path('app_admin_labourer', {'insee_code': stat.zone, 'deleteMissing': 1, 'disableFollowUpCleanup': 1}) }}"
|
||||||
|
class="btn btn-sm btn-warning btn-labourer"
|
||||||
|
data-zip-code="{{ stat.zone }}"
|
||||||
|
title="Labourer cette ville sans nettoyer les suivis OSM"
|
||||||
|
>
|
||||||
|
<i class="bi bi-shield-check"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
<a href="{{ path('app_admin_delete_by_zone', {'insee_code': stat.zone}) }}"
|
<a href="{{ path('app_admin_delete_by_zone', {'insee_code': stat.zone}) }}"
|
||||||
class="btn btn-sm btn-danger"
|
class="btn btn-sm btn-danger"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue