Ajouter les statistiques par période
This commit is contained in:
parent
9ff26ad9be
commit
86642095cd
14 changed files with 315 additions and 125 deletions
|
|
@ -4,10 +4,14 @@ declare(strict_types=1);
|
|||
|
||||
final class StatisticsService
|
||||
{
|
||||
public static function costs(?int $year, ?array $ids = null): array
|
||||
public static function costs(?int $year, ?array $ids = null, ?string $start = null, ?string $end = null): array
|
||||
{
|
||||
$db = DB::pdo();
|
||||
$scopeIds = self::costScopeIds($year, $ids);
|
||||
if ($start === null && $year !== null) {
|
||||
$start = sprintf('%04d-01-01', $year);
|
||||
$end = sprintf('%04d-12-31', $year);
|
||||
}
|
||||
$scopeIds = self::costScopeIds($year, $ids, $start, $end);
|
||||
$empty = [
|
||||
'total_cents' => 0,
|
||||
'animals' => 0,
|
||||
|
|
@ -23,9 +27,9 @@ final class StatisticsService
|
|||
}
|
||||
$where = ['a.deleted_at IS NULL', 'ae.animal_id IN(' . implode(',', array_map('intval', $scopeIds)) . ')'];
|
||||
$params = [];
|
||||
if ($year !== null) {
|
||||
if ($start !== null && $end !== null) {
|
||||
$where[] = 'date(ae.occurred_on) BETWEEN :start AND :end';
|
||||
$params = [':start' => sprintf('%04d-01-01', $year), ':end' => sprintf('%04d-12-31', $year)];
|
||||
$params = [':start' => $start, ':end' => $end];
|
||||
}
|
||||
$base =
|
||||
' FROM animal_expenses ae JOIN animals a ON a.id=ae.animal_id LEFT JOIN directory_contacts dc ON dc.id=ae.clinic_contact_id LEFT JOIN clinic_tariffs ct ON ct.id=ae.tariff_id WHERE ' .
|
||||
|
|
@ -63,10 +67,14 @@ final class StatisticsService
|
|||
];
|
||||
}
|
||||
|
||||
public static function costsByMunicipality(?int $year): array
|
||||
public static function costsByMunicipality(?int $year, ?string $start = null, ?string $end = null): array
|
||||
{
|
||||
$db = DB::pdo();
|
||||
$scopeIds = self::costScopeIds($year, null);
|
||||
if ($start === null && $year !== null) {
|
||||
$start = sprintf('%04d-01-01', $year);
|
||||
$end = sprintf('%04d-12-31', $year);
|
||||
}
|
||||
$scopeIds = self::costScopeIds($year, null, $start, $end);
|
||||
if (!$scopeIds) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -92,9 +100,9 @@ final class StatisticsService
|
|||
}
|
||||
$params = [];
|
||||
$dateSql = '';
|
||||
if ($year !== null) {
|
||||
if ($start !== null && $end !== null) {
|
||||
$dateSql = ' AND date(occurred_on) BETWEEN :start AND :end';
|
||||
$params = [':start' => sprintf('%04d-01-01', $year), ':end' => sprintf('%04d-12-31', $year)];
|
||||
$params = [':start' => $start, ':end' => $end];
|
||||
}
|
||||
$s = $db->prepare(
|
||||
'SELECT animal_id,SUM(total_cents) total_cents FROM animal_expenses WHERE animal_id IN(' .
|
||||
|
|
@ -133,22 +141,24 @@ final class StatisticsService
|
|||
return $out;
|
||||
}
|
||||
|
||||
private static function costScopeIds(?int $year, ?array $ids): array
|
||||
private static function costScopeIds(?int $year, ?array $ids, ?string $start = null, ?string $end = null): array
|
||||
{
|
||||
$db = DB::pdo();
|
||||
$limit = $ids === null ? '' : self::idSql($ids, 'a.id');
|
||||
if ($year === null) {
|
||||
$sql = 'SELECT a.id FROM animals a WHERE a.deleted_at IS NULL' . $limit;
|
||||
} else {
|
||||
if ($start === null && $year !== null) {
|
||||
$start = sprintf('%04d-01-01', $year);
|
||||
$end = sprintf('%04d-12-31', $year);
|
||||
}
|
||||
if ($start === null || $end === null) {
|
||||
$sql = 'SELECT a.id FROM animals a WHERE a.deleted_at IS NULL' . $limit;
|
||||
} else {
|
||||
$sql =
|
||||
'SELECT a.id FROM animals a WHERE a.deleted_at IS NULL' .
|
||||
$limit .
|
||||
" AND (date(a.intake_date) BETWEEN :start AND :end OR EXISTS(SELECT 1 FROM animal_movements m WHERE m.animal_id=a.id AND m.kind='entry' AND date(m.created_at) BETWEEN :start AND :end) OR EXISTS(SELECT 1 FROM animal_expenses ae WHERE ae.animal_id=a.id AND date(ae.occurred_on) BETWEEN :start AND :end))";
|
||||
}
|
||||
$s = $db->prepare($sql);
|
||||
$s->execute($year === null ? [] : [':start' => $start, ':end' => $end]);
|
||||
$s->execute($start === null || $end === null ? [] : [':start' => $start, ':end' => $end]);
|
||||
return array_map('intval', $s->fetchAll(PDO::FETCH_COLUMN));
|
||||
}
|
||||
|
||||
|
|
@ -224,64 +234,49 @@ final class StatisticsService
|
|||
|
||||
public static function annual(int $year, ?array $ids = null): array
|
||||
{
|
||||
if ($ids === null) {
|
||||
$stats = GrantService::statistics($year);
|
||||
} else {
|
||||
$db = DB::pdo();
|
||||
$p = [':start' => sprintf('%04d-01-01', $year), ':end' => sprintf('%04d-12-31', $year)];
|
||||
$in = self::idSql($ids);
|
||||
$scalar = static function (string $sql) use ($db, $p): float {
|
||||
$s = $db->prepare($sql);
|
||||
$s->execute($p);
|
||||
return (float) ($s->fetchColumn() ?: 0);
|
||||
};
|
||||
$stats = [];
|
||||
$stats['entries'] = $scalar(
|
||||
"SELECT COUNT(*) FROM (SELECT m.animal_id FROM animal_movements m JOIN animals a ON a.id=m.animal_id WHERE a.deleted_at IS NULL AND m.kind='entry' AND date(m.created_at) BETWEEN :start AND :end" .
|
||||
self::idSql($ids, 'm.animal_id') .
|
||||
' UNION SELECT a.id FROM animals a WHERE a.deleted_at IS NULL AND date(a.intake_date) BETWEEN :start AND :end' .
|
||||
self::idSql($ids, 'a.id') .
|
||||
')',
|
||||
);
|
||||
$stats['exits'] = $scalar(
|
||||
'SELECT (SELECT COUNT(*) FROM adoptions ad JOIN animals a ON a.id=ad.animal_id WHERE a.deleted_at IS NULL AND date(ad.adoption_date) BETWEEN :start AND :end' .
|
||||
self::idSql($ids, 'ad.animal_id') .
|
||||
")+(SELECT COUNT(*) FROM animal_deaths d JOIN animals a ON a.id=d.animal_id WHERE a.deleted_at IS NULL AND date(COALESCE(d.deceased_date,printf('%04d-01-01',d.death_year))) BETWEEN :start AND :end AND NOT EXISTS(SELECT 1 FROM adoptions ad WHERE ad.animal_id=d.animal_id)" .
|
||||
self::idSql($ids, 'd.animal_id') .
|
||||
')',
|
||||
);
|
||||
$stats['adoptions'] = $scalar(
|
||||
'SELECT COUNT(*) FROM adoptions WHERE date(adoption_date) BETWEEN :start AND :end' . $in,
|
||||
);
|
||||
$stats['deaths'] = $scalar(
|
||||
"SELECT COUNT(*) FROM animal_deaths WHERE date(COALESCE(deceased_date,printf('%04d-01-01',death_year))) BETWEEN :start AND :end" .
|
||||
$in,
|
||||
);
|
||||
$stats['births'] = $scalar(
|
||||
"SELECT COUNT(*) FROM litter_kittens lk JOIN litters l ON l.id=lk.litter_id JOIN animals a ON a.id=lk.animal_id WHERE a.deleted_at IS NULL AND date(COALESCE(NULLIF(l.birth_date,''),NULLIF(a.birth_date,''))) BETWEEN :start AND :end" .
|
||||
self::idSql($ids, 'lk.animal_id'),
|
||||
);
|
||||
$stats['vaccinations'] = $scalar(
|
||||
'SELECT COUNT(*) FROM vaccinations WHERE date(done_date) BETWEEN :start AND :end' . $in,
|
||||
);
|
||||
$stats['treatments'] = $scalar(
|
||||
'SELECT COUNT(*) FROM treatments WHERE date(start_date) BETWEEN :start AND :end' . $in,
|
||||
);
|
||||
$stats['avg'] = $scalar(
|
||||
"SELECT AVG(julianday(adoption_date)-julianday(COALESCE(NULLIF(a.intake_date,''),date(a.created_at)))) FROM adoptions ad JOIN animals a ON a.id=ad.animal_id WHERE date(adoption_date) BETWEEN :start AND :end AND date(adoption_date)>=date(COALESCE(NULLIF(a.intake_date,''),date(a.created_at)))" .
|
||||
self::idSql($ids, 'a.id'),
|
||||
);
|
||||
$stats['current'] =
|
||||
(float) ($db
|
||||
->query(
|
||||
"SELECT COUNT(*) FROM animals WHERE deleted_at IS NULL AND archived_at IS NULL AND status NOT IN ('adopte','decede')" .
|
||||
self::idSql($ids, 'id'),
|
||||
)
|
||||
->fetchColumn() ?:
|
||||
0);
|
||||
}
|
||||
return self::period(sprintf('%04d-01-01', $year), sprintf('%04d-12-31', $year), $ids);
|
||||
}
|
||||
|
||||
public static function period(string $start, string $end, ?array $ids = null): array
|
||||
{
|
||||
$db = DB::pdo();
|
||||
$range = [':start' => sprintf('%04d-01-01', $year), ':end' => sprintf('%04d-12-31', $year)];
|
||||
$range = [':start' => $start, ':end' => $end];
|
||||
$scalar = static function (string $sql) use ($db, $range): float {
|
||||
$s = $db->prepare($sql);
|
||||
$s->execute($range);
|
||||
return (float) ($s->fetchColumn() ?: 0);
|
||||
};
|
||||
$in = self::idSql($ids);
|
||||
$stats = [];
|
||||
$stats['entries'] = $scalar(
|
||||
"SELECT COUNT(*) FROM (SELECT m.animal_id FROM animal_movements m JOIN animals a ON a.id=m.animal_id WHERE a.deleted_at IS NULL AND m.kind='entry' AND date(m.created_at) BETWEEN :start AND :end" .
|
||||
self::idSql($ids, 'm.animal_id') .
|
||||
' UNION SELECT a.id FROM animals a WHERE a.deleted_at IS NULL AND date(a.intake_date) BETWEEN :start AND :end' .
|
||||
self::idSql($ids, 'a.id') .
|
||||
')',
|
||||
);
|
||||
$stats['exits'] = $scalar(
|
||||
'SELECT (SELECT COUNT(*) FROM adoptions ad JOIN animals a ON a.id=ad.animal_id WHERE a.deleted_at IS NULL AND date(ad.adoption_date) BETWEEN :start AND :end' .
|
||||
self::idSql($ids, 'ad.animal_id') .
|
||||
")+(SELECT COUNT(*) FROM animal_deaths d JOIN animals a ON a.id=d.animal_id WHERE a.deleted_at IS NULL AND date(COALESCE(d.deceased_date,printf('%04d-01-01',d.death_year))) BETWEEN :start AND :end AND NOT EXISTS(SELECT 1 FROM adoptions ad WHERE ad.animal_id=d.animal_id)" .
|
||||
self::idSql($ids, 'd.animal_id') .
|
||||
')',
|
||||
);
|
||||
$stats['adoptions'] = $scalar('SELECT COUNT(*) FROM adoptions WHERE date(adoption_date) BETWEEN :start AND :end' . $in);
|
||||
$stats['deaths'] = $scalar(
|
||||
"SELECT COUNT(*) FROM animal_deaths WHERE date(COALESCE(deceased_date,printf('%04d-01-01',death_year))) BETWEEN :start AND :end" . $in,
|
||||
);
|
||||
$stats['births'] = $scalar(
|
||||
"SELECT COUNT(*) FROM litter_kittens lk JOIN litters l ON l.id=lk.litter_id JOIN animals a ON a.id=lk.animal_id WHERE a.deleted_at IS NULL AND date(COALESCE(NULLIF(l.birth_date,''),NULLIF(a.birth_date,''))) BETWEEN :start AND :end" . self::idSql($ids, 'lk.animal_id'),
|
||||
);
|
||||
$stats['vaccinations'] = $scalar('SELECT COUNT(*) FROM vaccinations WHERE date(done_date) BETWEEN :start AND :end' . $in);
|
||||
$stats['treatments'] = $scalar('SELECT COUNT(*) FROM treatments WHERE date(start_date) BETWEEN :start AND :end' . $in);
|
||||
$stats['avg'] = $scalar(
|
||||
"SELECT AVG(julianday(adoption_date)-julianday(COALESCE(NULLIF(a.intake_date,''),date(a.created_at)))) FROM adoptions ad JOIN animals a ON a.id=ad.animal_id WHERE date(adoption_date) BETWEEN :start AND :end AND date(adoption_date)>=date(COALESCE(NULLIF(a.intake_date,''),date(a.created_at)))" . self::idSql($ids, 'a.id'),
|
||||
);
|
||||
$stats['current'] = (float) ($db->query(
|
||||
"SELECT COUNT(*) FROM animals WHERE deleted_at IS NULL AND archived_at IS NULL AND status NOT IN ('adopte','decede')" . self::idSql($ids, 'id'),
|
||||
)->fetchColumn() ?: 0);
|
||||
$filter = self::idSql($ids, 'a.id');
|
||||
$s = $db->prepare(
|
||||
"SELECT COUNT(*) FROM (SELECT m.animal_id FROM animal_movements m JOIN animals a ON a.id=m.animal_id WHERE a.deleted_at IS NULL AND m.kind='entry' AND date(m.created_at) BETWEEN :start AND :end" .
|
||||
|
|
@ -303,6 +298,24 @@ final class StatisticsService
|
|||
return $stats;
|
||||
}
|
||||
|
||||
public static function comparisonPeriod(string $start, string $end, ?array $ids = null): array
|
||||
{
|
||||
$from = new DateTimeImmutable($start);
|
||||
$to = new DateTimeImmutable($end);
|
||||
$days = (int) $from->diff($to)->format('%a') + 1;
|
||||
$previousEnd = $from->modify('-1 day');
|
||||
$previousStart = $previousEnd->modify('-' . ($days - 1) . ' days');
|
||||
$current = self::period($start, $end, $ids);
|
||||
$previous = self::period($previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $ids);
|
||||
$out = [];
|
||||
foreach (['handled','entries','exits','adoptions','deaths','births','vaccinations','treatments','medical_notes','avg'] as $key) {
|
||||
$a = (float) $current[$key];
|
||||
$b = (float) $previous[$key];
|
||||
$out[$key] = ['current' => $a,'previous' => $b,'difference' => $a - $b,'percent' => $b != 0 ? (($a - $b) / $b) * 100 : null];
|
||||
}
|
||||
return ['rows' => $out,'previous_start' => $previousStart->format('Y-m-d'),'previous_end' => $previousEnd->format('Y-m-d')];
|
||||
}
|
||||
|
||||
public static function comparison(int $year, ?array $ids = null): array
|
||||
{
|
||||
$current = self::annual($year, $ids);
|
||||
|
|
@ -532,10 +545,10 @@ final class StatisticsService
|
|||
];
|
||||
}
|
||||
|
||||
private static function details(int $year, array $ids): array
|
||||
private static function detailsRange(string $start, string $end, ?array $ids): array
|
||||
{
|
||||
$db = DB::pdo();
|
||||
$p = [':start' => sprintf('%04d-01-01', $year), ':end' => sprintf('%04d-12-31', $year)];
|
||||
$p = [':start' => $start, ':end' => $end];
|
||||
$base =
|
||||
"WITH entry_sources AS (SELECT m.animal_id,date(m.created_at) entry_date FROM animal_movements m JOIN animals source_animal ON source_animal.id=m.animal_id WHERE source_animal.deleted_at IS NULL AND m.kind='entry' AND date(m.created_at) BETWEEN :start AND :end" .
|
||||
self::idSql($ids, 'm.animal_id') .
|
||||
|
|
@ -561,7 +574,7 @@ final class StatisticsService
|
|||
);
|
||||
$deathCauses = $map(
|
||||
"SELECT cause_code label,COUNT(*) total FROM animal_deaths WHERE date(COALESCE(deceased_date,printf('%04d-01-01',death_year))) BETWEEN :start AND :end" .
|
||||
$in .
|
||||
self::idSql($ids) .
|
||||
' GROUP BY cause_code ORDER BY total DESC',
|
||||
);
|
||||
$stays = $map(
|
||||
|
|
@ -572,10 +585,33 @@ final class StatisticsService
|
|||
return compact('sex', 'ages', 'deathCauses', 'stays');
|
||||
}
|
||||
|
||||
public static function distributions(?int $year, ?array $ids = null, ?string $municipality = null): array
|
||||
private static function originsForPeriod(string $start, string $end, ?array $ids): array
|
||||
{
|
||||
$db = DB::pdo();
|
||||
$p = $year === null ? [] : [':start' => sprintf('%04d-01-01', $year), ':end' => sprintf('%04d-12-31', $year)];
|
||||
$sql =
|
||||
"SELECT DISTINCT a.id,a.rescue_location_name,a.rescue_address FROM animals a WHERE a.deleted_at IS NULL" .
|
||||
self::idSql($ids, 'a.id') .
|
||||
" AND (date(a.intake_date) BETWEEN :start AND :end OR EXISTS(SELECT 1 FROM animal_movements m WHERE m.animal_id=a.id AND m.kind='entry' AND date(m.created_at) BETWEEN :start AND :end))";
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute([':start' => $start, ':end' => $end]);
|
||||
$origins = [];
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$city = GrantService::municipality((string) ($row['rescue_location_name'] ?: $row['rescue_address']));
|
||||
$origins[$city] = ($origins[$city] ?? 0) + 1;
|
||||
}
|
||||
arsort($origins, SORT_NUMERIC);
|
||||
return $origins;
|
||||
}
|
||||
|
||||
public static function distributions(?int $year, ?array $ids = null, ?string $municipality = null, ?string $start = null, ?string $end = null): array
|
||||
{
|
||||
$db = DB::pdo();
|
||||
if ($start === null && $year !== null) {
|
||||
$start = sprintf('%04d-01-01', $year);
|
||||
$end = sprintf('%04d-12-31', $year);
|
||||
}
|
||||
$bounded = $start !== null && $end !== null;
|
||||
$p = $bounded ? [':start' => $start, ':end' => $end] : [];
|
||||
$rows = static function (string $sql, array $params = []) use ($db): array {
|
||||
$s = $db->prepare($sql);
|
||||
$s->execute($params);
|
||||
|
|
@ -585,9 +621,9 @@ final class StatisticsService
|
|||
}
|
||||
return $out;
|
||||
};
|
||||
$movementDate = $year === null ? '' : ' AND date(m.created_at) BETWEEN :start AND :end';
|
||||
$animalDate = $year === null ? '' : ' AND date(intake_date) BETWEEN :start AND :end';
|
||||
$datedAnimal = $year === null ? '' : " AND NULLIF(TRIM(intake_date),'') IS NOT NULL";
|
||||
$movementDate = $bounded ? ' AND date(m.created_at) BETWEEN :start AND :end' : '';
|
||||
$animalDate = $bounded ? ' AND date(intake_date) BETWEEN :start AND :end' : '';
|
||||
$datedAnimal = $bounded ? " AND NULLIF(TRIM(intake_date),'') IS NOT NULL" : '';
|
||||
$entered =
|
||||
"WITH entry_sources AS (SELECT m.animal_id,date(m.created_at) entry_date FROM animal_movements m JOIN animals source_animal ON source_animal.id=m.animal_id WHERE source_animal.deleted_at IS NULL AND m.kind='entry'" .
|
||||
$movementDate .
|
||||
|
|
@ -627,7 +663,7 @@ final class StatisticsService
|
|||
"SELECT CASE WHEN a.intake_owner_care_home=1 THEN 'owner_care_home' ELSE COALESCE(NULLIF(a.intake_reason,''),'unknown') END label,COUNT(*) total FROM entered e JOIN animals a ON a.id=e.animal_id GROUP BY label ORDER BY total DESC",
|
||||
$p,
|
||||
);
|
||||
if ($year === null) {
|
||||
if (!$bounded) {
|
||||
$animalFilter = self::idSql($ids, 'a.id');
|
||||
$details = [];
|
||||
$details['sex'] = $rows(
|
||||
|
|
@ -651,12 +687,11 @@ final class StatisticsService
|
|||
' GROUP BY label ORDER BY total DESC',
|
||||
);
|
||||
$origins = $municipality !== null ? [$municipality => (int) array_sum($species)] : self::municipalities();
|
||||
} elseif ($ids !== null) {
|
||||
$details = self::details($year, $ids);
|
||||
$origins = $municipality !== null ? [$municipality => (int) array_sum($species)] : [];
|
||||
} else {
|
||||
$details = GrantService::detailedStatistics($year);
|
||||
$origins = $details['origins'];
|
||||
$details = self::detailsRange($start, $end, $ids);
|
||||
$origins = $municipality !== null
|
||||
? [$municipality => (int) array_sum($species)]
|
||||
: self::originsForPeriod($start, $end, $ids);
|
||||
}
|
||||
foreach ($details as &$items) {
|
||||
arsort($items, SORT_NUMERIC);
|
||||
|
|
@ -674,7 +709,7 @@ final class StatisticsService
|
|||
' GROUP BY label ORDER BY total DESC,label',
|
||||
);
|
||||
$releaseSql =
|
||||
$year === null
|
||||
!$bounded
|
||||
? "SELECT substr(free_cat_released_at,1,4) label,COUNT(*) total FROM animals WHERE deleted_at IS NULL AND free_cat_released_at IS NOT NULL AND trim(free_cat_released_at)<>''" .
|
||||
$filter .
|
||||
' GROUP BY label ORDER BY label DESC'
|
||||
|
|
|
|||
Loading…
Reference in a new issue