Files
nrml_dashboard/dashboard/app/Services/DashboardService.php
2026-03-19 09:50:21 +07:00

755 lines
24 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Services;
use App\Models\Surveillance;
use App\Models\SurveillanceCase;
use App\Models\CaseLabResult;
class DashboardService
{
private function totalTested($surveillanceId, $startYear, $startWeek, $endYear, $endWeek)
{
return SurveillanceCase::where('surveillance_id', $surveillanceId)
->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) {
$q->whereRaw("(year_data > ? OR (year_data = ? AND week_data >= ?))", [$startYear, $startYear, $startWeek])
->whereRaw("(year_data < ? OR (year_data = ? AND week_data <= ?))", [$endYear, $endYear, $endWeek]);
})
->distinct('lab_code')
->count('lab_code');
}
/*
|--------------------------------------------------------------------------
| Overview Summary Cards
|--------------------------------------------------------------------------
*/
public function summaryCards($dateFrom, $dateTo)
{
$programs = Surveillance::orderBy('id')->get();
$results = [];
foreach ($programs as $program) {
$current = SurveillanceCase::where('surveillance_id', $program->id)
->whereBetween('case_date', [$dateFrom, $dateTo])
->count();
$previous = SurveillanceCase::where('surveillance_id', $program->id)
->whereBetween('case_date', [
date('Y-m-d', strtotime($dateFrom . ' -7 days')),
date('Y-m-d', strtotime($dateFrom . ' -1 day'))
])
->count();
$percentChange = $previous > 0
? round((($current - $previous) / $previous) * 100, 1)
: 0;
$results[] = [
'surveillance_id' => $program->id,
'code' => $program->code,
'current_total' => $current,
'previous_total' => $previous,
'percent_change' => $percentChange
];
}
return $results;
}
/*
|--------------------------------------------------------------------------
| Fast SARI Summary (single query)
|--------------------------------------------------------------------------
*/
public function programSummaryFast($surveillanceId, $year, $week)
{
$row = SurveillanceCase::leftJoin(
'case_lab_results',
'surveillance_cases.lab_code',
'=',
'case_lab_results.lab_code'
)
->where('surveillance_cases.surveillance_id', $surveillanceId)
->where('surveillance_cases.year_data', $year)
->where('surveillance_cases.week_data', $week)
->selectRaw("
COUNT(DISTINCT surveillance_cases.lab_code) as total_cases,
COUNT(DISTINCT CASE
WHEN case_lab_results.is_positive = 1
THEN surveillance_cases.lab_code
END) as overall_positive,
COUNT(DISTINCT CASE
WHEN case_lab_results.is_positive = 1
AND (
LOWER(case_lab_results.pathogen_name) LIKE '%influenza%'
OR LOWER(case_lab_results.pathogen_name) LIKE '%influzena%'
)
THEN surveillance_cases.lab_code
END) as influenza_positive,
COUNT(DISTINCT CASE
WHEN case_lab_results.is_positive = 1
AND (
LOWER(case_lab_results.pathogen_name) LIKE '%covid%'
OR LOWER(case_lab_results.pathogen_name) LIKE '%sars%'
)
THEN surveillance_cases.lab_code
END) as covid_positive
")
->first();
if (!$row || $row->total_cases == 0) {
return [
'cases' => 0,
'overall_rate' => 0,
'influenza_rate' => 0,
'covid_rate' => 0
];
}
return [
'cases' => $row->total_cases,
'overall_rate' => round(($row->overall_positive / $row->total_cases) * 100, 1),
'influenza_rate' => round(($row->influenza_positive / $row->total_cases) * 100, 1),
'covid_rate' => round(($row->covid_positive / $row->total_cases) * 100, 1),
];
}
/*
|--------------------------------------------------------------------------
| Program Summary
|--------------------------------------------------------------------------
*/
public function programSummary($surveillanceId, $startYear, $startWeek, $endYear, $endWeek)
{
$prevWeek = $endWeek - 1;
$prevYear = $endYear;
if ($prevWeek <= 0) {
$prevWeek = 52;
$prevYear--;
}
$latest = SurveillanceCase::where('surveillance_id', $surveillanceId)
->selectRaw("year_data, week_data")
->orderByDesc('year_data')
->orderByDesc('week_data')
->first();
$year = $latest->year_data;
$week = $latest->week_data;
$current = $this->programSummaryFast($surveillanceId, $year, $week);
$previous = $this->programSummaryFast($surveillanceId, $prevYear, $prevWeek);
$prevWeek = $week - 1;
$prevYear = $year;
if ($prevWeek <= 0) {
$prevWeek = 52;
$prevYear--;
}
$previous = $this->programSummaryFast($surveillanceId, $prevYear, $prevWeek);
return [
'cases' => [
'current' => $current['cases'],
'previous' => $previous['cases']
],
'hospital_rate' => [
'current' => 0,
'previous' => 0
],
'icu_rate' => [
'current' => 0,
'previous' => 0
],
'positivity_rate' => [
'current' => $current['overall_rate'],
'previous' => $previous['overall_rate']
],
'influenza_rate' => [
'current' => $current['influenza_rate'],
'previous' => $previous['influenza_rate']
],
'covid_rate' => [
'current' => $current['covid_rate'],
'previous' => $previous['covid_rate']
],
];
}
/*
|--------------------------------------------------------------------------
| Overview Trend
|--------------------------------------------------------------------------
*/
public function aggregateAllPrograms($startYear, $startWeek, $endYear, $endWeek)
{
$data = SurveillanceCase::selectRaw("
surveillance_id,
year_data as year,
week_data as period,
COUNT(*) as total
")
->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) {
$q->whereRaw(
"(year_data > ? OR (year_data = ? AND week_data >= ?))",
[$startYear, $startYear, $startWeek]
)
->whereRaw(
"(year_data < ? OR (year_data = ? AND week_data <= ?))",
[$endYear, $endYear, $endWeek]
);
})
->groupBy('surveillance_id', 'year_data', 'week_data')
->orderBy('year_data')
->orderBy('week_data')
->get();
$programs = Surveillance::pluck('code', 'id');
$results = [];
foreach ($data as $row) {
$code = $programs[$row->surveillance_id];
$results[$code][] = $row;
}
return $results;
}
/*
|--------------------------------------------------------------------------
| Program Dashboard
|--------------------------------------------------------------------------
*/
public function programDashboardData($surveillanceId, $startYear, $startWeek, $endYear, $endWeek)
{
return [
'summary' => $this->programSummary(
$surveillanceId,
$startYear,
$startWeek,
$endYear,
$endWeek
),
'trend' => $this->trendSingleProgram(
$surveillanceId,
$startYear,
$startWeek,
$endYear,
$endWeek
),
'pathogen_distribution' => $this->pathogenDistribution(
$surveillanceId,
$startYear,
$startWeek,
$endYear,
$endWeek
),
'age_distribution' => $this->ageDistribution(
$surveillanceId,
$startYear,
$startWeek,
$endYear,
$endWeek
),
'sex_distribution' => $this->sexDistribution(
$surveillanceId,
$startYear,
$startWeek,
$endYear,
$endWeek
),
'province_distribution' => $this->provinceProgram(
$surveillanceId,
$startYear,
$startWeek,
$endYear,
$endWeek
),
'virus_trend' => $this->virusTrend(
$surveillanceId,
$startYear,
$startWeek,
$endYear,
$endWeek
),
'subtype_distribution' => $this->subtypeDistribution(
$surveillanceId,
$startYear,
$startWeek,
$endYear,
$endWeek
),
'sentinel_sites' => $this->sentinelSites(
$surveillanceId,
$startYear,
$startWeek,
$endYear,
$endWeek
),
];
}
/*
|--------------------------------------------------------------------------
| Trend Single Program
|--------------------------------------------------------------------------
*/
public function trendSingleProgram($surveillanceId, $startYear, $startWeek, $endYear, $endWeek)
{
$rows = SurveillanceCase::leftJoin(
'case_lab_results',
'surveillance_cases.lab_code',
'=',
'case_lab_results.lab_code'
)
->where('surveillance_cases.surveillance_id', $surveillanceId)
->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) {
$q->whereRaw(
"(surveillance_cases.year_data > ? OR (surveillance_cases.year_data = ? AND surveillance_cases.week_data >= ?))",
[$startYear, $startYear, $startWeek]
)
->whereRaw(
"(surveillance_cases.year_data < ? OR (surveillance_cases.year_data = ? AND surveillance_cases.week_data <= ?))",
[$endYear, $endYear, $endWeek]
);
})
->selectRaw("
surveillance_cases.year_data as year,
surveillance_cases.week_data as period,
COUNT(DISTINCT surveillance_cases.lab_code) as total_samples,
-- Overall positivity rate
ROUND(
COUNT(DISTINCT CASE
WHEN case_lab_results.is_positive = 1
THEN surveillance_cases.lab_code
END)
/ NULLIF(COUNT(DISTINCT surveillance_cases.lab_code), 0) * 100
,1) as positivity_rate,
-- Influenza positivity rate
ROUND(
COUNT(DISTINCT CASE
WHEN case_lab_results.is_positive = 1
AND (
LOWER(case_lab_results.pathogen_name) LIKE '%influenza%'
OR LOWER(case_lab_results.pathogen_name) LIKE '%influzena%'
)
THEN surveillance_cases.lab_code
END)
/ NULLIF(COUNT(DISTINCT surveillance_cases.lab_code), 0) * 100
,1) as influenza_rate,
-- COVID positivity rate
ROUND(
COUNT(DISTINCT CASE
WHEN case_lab_results.is_positive = 1
AND (
case_lab_results.pathogen_name = 'Positive'
OR case_lab_results.pathogen_name = 'SARS-CoV-2'
)
AND case_lab_results.indicator LIKE '%Covid%'
THEN surveillance_cases.lab_code
END)
/ NULLIF(COUNT(DISTINCT surveillance_cases.lab_code), 0) * 100
,1) as covid_rate
")
->groupBy(
'surveillance_cases.year_data',
'surveillance_cases.week_data'
)
->get()
->keyBy(fn($r) => $r->year . '-' . $r->period);
$results = [];
$year = $startYear;
$week = $startWeek;
while (true) {
$key = $year . '-' . $week;
if (isset($rows[$key])) {
$results[] = $rows[$key];
} else {
$results[] = [
'year' => $year,
'period' => $week,
'total_samples' => 0,
'positivity_rate' => 0
];
}
if ($year == $endYear && $week == $endWeek)
break;
$week++;
if ($week > 52) {
$week = 1;
$year++;
}
}
return $results;
}
public function virusTrend($surveillanceId, $startYear, $startWeek, $endYear, $endWeek)
{
return CaseLabResult::join(
'surveillance_cases',
'case_lab_results.lab_code',
'=',
'surveillance_cases.lab_code'
)
->where('surveillance_cases.surveillance_id', $surveillanceId)
->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) {
$q->whereRaw("(year_data > ? OR (year_data = ? AND week_data >= ?))", [$startYear, $startYear, $startWeek])
->whereRaw("(year_data < ? OR (year_data = ? AND week_data <= ?))", [$endYear, $endYear, $endWeek]);
})
->selectRaw("
surveillance_cases.week_data as period,
COUNT(DISTINCT CASE
WHEN case_lab_results.is_positive = 1
AND (
LOWER(case_lab_results.pathogen_name) LIKE '%influenza%'
OR LOWER(case_lab_results.pathogen_name) LIKE '%influzena%'
)
THEN surveillance_cases.lab_code
END) as influenza,
COUNT(DISTINCT CASE
WHEN case_lab_results.is_positive = 1
AND (
case_lab_results.pathogen_name = 'Positive'
OR case_lab_results.pathogen_name = 'SARS-CoV-2'
)
AND case_lab_results.indicator LIKE '%Covid%'
THEN surveillance_cases.lab_code
END) as covid
")
->groupBy('surveillance_cases.week_data')
->orderBy('surveillance_cases.week_data')
->get();
}
/*
|--------------------------------------------------------------------------
| Province Distribution (Program)
|--------------------------------------------------------------------------
*/
public function provinceCircles($startYear, $startWeek, $endYear, $endWeek)
{
return SurveillanceCase::selectRaw(" surveillance_cases.site_province_name, surveillance_cases.surveillance_id, COUNT(*) as total ")->join('case_lab_results', 'surveillance_cases.lab_code', '=', 'case_lab_results.lab_code')->where('case_lab_results.is_positive', 1)->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) {
$q->whereRaw("(surveillance_cases.year_data > ? OR (surveillance_cases.year_data = ? AND surveillance_cases.week_data >= ?))", [$startYear, $startYear, $startWeek])->whereRaw("(surveillance_cases.year_data < ? OR (surveillance_cases.year_data = ? AND surveillance_cases.week_data <= ?))", [$endYear, $endYear, $endWeek]);
})->groupBy('surveillance_cases.site_province_name', 'surveillance_cases.surveillance_id')->get();
}
public function provinceProgram($surveillanceId, $startYear, $startWeek, $endYear, $endWeek)
{
return SurveillanceCase::selectRaw("
surveillance_cases.site_province_name,
COUNT(DISTINCT surveillance_cases.lab_code) as total,
COUNT(DISTINCT CASE
WHEN case_lab_results.is_positive = 1
THEN surveillance_cases.lab_code
END) as positive
")
->join(
'case_lab_results',
'surveillance_cases.lab_code',
'=',
'case_lab_results.lab_code'
)
->where('surveillance_cases.surveillance_id', $surveillanceId)
->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) {
$q->whereRaw(
"(surveillance_cases.year_data > ? OR (surveillance_cases.year_data = ? AND surveillance_cases.week_data >= ?))",
[$startYear, $startYear, $startWeek]
)
->whereRaw(
"(surveillance_cases.year_data < ? OR (surveillance_cases.year_data = ? AND surveillance_cases.week_data <= ?))",
[$endYear, $endYear, $endWeek]
);
})
->groupBy('surveillance_cases.site_province_name')
->get();
}
/*
|--------------------------------------------------------------------------
| sentinel sites
|--------------------------------------------------------------------------
*/
public function sentinelSites($surveillanceId, $startYear, $startWeek, $endYear, $endWeek)
{
return SurveillanceCase::selectRaw("
sentinel_site_name as name,
COUNT(DISTINCT lab_code) as total
")
->where('surveillance_id', $surveillanceId)
->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) {
$q->whereRaw("(year_data > ? OR (year_data = ? AND week_data >= ?))", [$startYear, $startYear, $startWeek])
->whereRaw("(year_data < ? OR (year_data = ? AND week_data <= ?))", [$endYear, $endYear, $endWeek]);
})
->groupBy('sentinel_site_name')
->orderByDesc('total') // nice for chart
->get();
}
/*
|--------------------------------------------------------------------------
| Pathogen Distribution
|--------------------------------------------------------------------------
*/
public function pathogenDistribution($surveillanceId, $startYear, $startWeek, $endYear, $endWeek)
{
$total = $this->totalTested($surveillanceId, $startYear, $startWeek, $endYear, $endWeek);
$rows = CaseLabResult::join(
'surveillance_cases',
'case_lab_results.lab_code',
'=',
'surveillance_cases.lab_code'
)
->where('surveillance_cases.surveillance_id', $surveillanceId)
->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) {
$q->whereRaw("(year_data > ? OR (year_data = ? AND week_data >= ?))", [$startYear, $startYear, $startWeek])
->whereRaw("(year_data < ? OR (year_data = ? AND week_data <= ?))", [$endYear, $endYear, $endWeek]);
})
->where('case_lab_results.is_positive', 1)
->selectRaw("
CASE
WHEN LOWER(case_lab_results.pathogen_name) LIKE '%influenza%'
OR LOWER(case_lab_results.pathogen_name) LIKE '%influzena%'
THEN 'Influenza'
WHEN case_lab_results.pathogen_name = 'Positive'
AND case_lab_results.indicator LIKE '%Covid%'
THEN 'SARS-CoV-2'
ELSE case_lab_results.pathogen_name
END as pathogen,
COUNT(DISTINCT surveillance_cases.lab_code) as total
")
->groupBy('pathogen')
->havingRaw("pathogen IS NOT NULL AND pathogen != ''")
->orderByDesc('total')
->get();
return $rows->map(function ($r) use ($total) {
$r->rate = $total > 0 ? round(($r->total / $total) * 100, 1) : 0;
return $r;
});
}
public function subtypeDistribution($surveillanceId, $startYear, $startWeek, $endYear, $endWeek)
{
$total = $this->totalTested($surveillanceId, $startYear, $startWeek, $endYear, $endWeek);
$rows = CaseLabResult::join(
'surveillance_cases',
'case_lab_results.lab_code',
'=',
'surveillance_cases.lab_code'
)
->where('surveillance_cases.surveillance_id', $surveillanceId)
->where('case_lab_results.is_positive', 1)
->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) {
$q->whereRaw("(year_data > ? OR (year_data = ? AND week_data >= ?))", [$startYear, $startYear, $startWeek])
->whereRaw("(year_data < ? OR (year_data = ? AND week_data <= ?))", [$endYear, $endYear, $endWeek]);
})
->selectRaw("
subtype,
COUNT(DISTINCT surveillance_cases.lab_code) as total
")
->groupBy('subtype')
->havingRaw("subtype IS NOT NULL AND subtype != 'Positive' AND subtype != ''")
->orderByDesc('total')
->get();
return $rows->map(function ($r) use ($total) {
$r->rate = $total > 0 ? round(($r->total / $total) * 100, 1) : 0;
return $r;
});
}
/*
|--------------------------------------------------------------------------
| Age Distribution
|--------------------------------------------------------------------------
*/
public function ageDistribution($surveillanceId, $startYear, $startWeek, $endYear, $endWeek)
{
return SurveillanceCase::selectRaw("
CASE
WHEN patient_age_inday < 365 THEN '<1 year'
WHEN patient_age_inday < 1825 THEN '14 years'
WHEN patient_age_inday < 6570 THEN '517 years'
WHEN patient_age_inday < 16425 THEN '1844 years'
WHEN patient_age_inday < 23725 THEN '4564 years'
ELSE '65+ years'
END as age_group,
COUNT(*) as total
")
->where('surveillance_id', $surveillanceId)
->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) {
$q->whereRaw(
"(year_data > ? OR (year_data = ? AND week_data >= ?))",
[$startYear, $startYear, $startWeek]
)
->whereRaw(
"(year_data < ? OR (year_data = ? AND week_data <= ?))",
[$endYear, $endYear, $endWeek]
);
})
->groupBy('age_group')
->get();
}
/*
|--------------------------------------------------------------------------
| Sex Distribution
|--------------------------------------------------------------------------
*/
public function sexDistribution($surveillanceId, $startYear, $startWeek, $endYear, $endWeek)
{
return SurveillanceCase::selectRaw("
patient_sex,
COUNT(*) as total
")
->where('surveillance_id', $surveillanceId)
->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) {
$q->whereRaw(
"(year_data > ? OR (year_data = ? AND week_data >= ?))",
[$startYear, $startYear, $startWeek]
)
->whereRaw(
"(year_data < ? OR (year_data = ? AND week_data <= ?))",
[$endYear, $endYear, $endWeek]
);
})
->groupBy('patient_sex')
->get();
}
}