From 4534f502e2c58f48b225a01fb650b1cc34593a53 Mon Sep 17 00:00:00 2001 From: pcalengratha Date: Wed, 8 Apr 2026 14:48:42 +0700 Subject: [PATCH] modify dashboard and services fetch source data --- .../Controllers/Api/DashboardController.php | 81 +++ dashboard/app/Services/DashboardService.php | 179 ++++- .../app/Services/DataRetrievalService.php | 370 +++++++--- dashboard/config/app.php | 12 +- dashboard/public/js/overview.js | 641 ++++++++++++++++-- dashboard/public/js/program.js | 1 - .../views/dashboard/overview.blade.php | 176 +++-- .../resources/views/layouts/app.blade.php | 99 ++- dashboard/routes/api.php | 4 + 9 files changed, 1322 insertions(+), 241 deletions(-) diff --git a/dashboard/app/Http/Controllers/Api/DashboardController.php b/dashboard/app/Http/Controllers/Api/DashboardController.php index 3a2d0c1..c963d4d 100644 --- a/dashboard/app/Http/Controllers/Api/DashboardController.php +++ b/dashboard/app/Http/Controllers/Api/DashboardController.php @@ -139,6 +139,87 @@ class DashboardController extends Controller return response()->json($data); } + /* + |-------------------------------------------------------------------------- + | Influenza subtype distribution (Overview) + |-------------------------------------------------------------------------- + */ + + public function influenzaSubtypeDetected(Request $request) + { + $range = $this->getEpiRange($request); + + if (!$range) { + return response()->json(['error' => 'Missing epiweek range'], 400); + } + + $data = $this->service->influenzaSubtypeDetected( + $range['startYear'], + $range['startWeek'], + $range['endYear'], + $range['endWeek'] + ); + + return response()->json($data); + } + + public function covidDistributedByAgeGroup(Request $request) + { + $range = $this->getEpiRange($request); + + if (!$range) { + return response()->json(['error' => 'Missing epiweek range'], 400); + } + + $data = $this->service->covidDistributedByAgeGroup( + $range['startYear'], + $range['startWeek'], + $range['endYear'], + $range['endWeek'] + ); + + return response()->json($data); + } + + public function covidLineageRelativeOverTime(Request $request) + { + $range = $this->getEpiRange($request); + + if (!$range) { + return response()->json(['error' => 'Missing epiweek range'], 400); + } + + $data = $this->service->covidLineageRelativeOverTime( + $range['startYear'], + $range['startWeek'], + $range['endYear'], + $range['endWeek'] + ); + + return response()->json($data); + } + + public function influenzaRelativeOverTime(Request $request) + { + $range = $this->getEpiRange($request); + + if (!$range) { + return response()->json(['error' => 'Missing epiweek range'], 400); + } + + $data = $this->service->influenzaRelativeOverTime( + $range['startYear'], + $range['startWeek'], + $range['endYear'], + $range['endWeek'] + ); + + return response()->json($data); + } + + + + /* |-------------------------------------------------------------------------- diff --git a/dashboard/app/Services/DashboardService.php b/dashboard/app/Services/DashboardService.php index 9f694e7..9e8c013 100644 --- a/dashboard/app/Services/DashboardService.php +++ b/dashboard/app/Services/DashboardService.php @@ -575,6 +575,33 @@ class DashboardService */ public function provinceCircles($startYear, $startWeek, $endYear, $endWeek) { + + return SurveillanceCase::join('case_lab_results', function ($join) { + $join->on('surveillance_cases.lab_code', '=', 'case_lab_results.lab_code') + ->on('surveillance_cases.surveillance_id', '=', 'case_lab_results.surveillance_id'); + }) + ->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) { + $q->whereRaw( + "(surveillance_cases.year_data * 100 + surveillance_cases.week_data) BETWEEN ? AND ?", + [ + $startYear * 100 + $startWeek, + $endYear * 100 + $endWeek + ] + ); + }) + ->whereRaw('case_lab_results.is_positive = 1 and surveillance_cases.surveillance_id not in(6) and case_lab_results.indicator="Influenza"') + ->selectRaw(" + surveillance_cases.patient_province, + case_lab_results.subtype as pathogen_name, + COUNT(DISTINCT surveillance_cases.lab_code) as total + ") + ->groupBy( + 'surveillance_cases.patient_province', + 'case_lab_results.subtype' + ) + ->get(); + + return SurveillanceCase::leftJoin( 'case_lab_results', 'surveillance_cases.lab_code', @@ -590,28 +617,156 @@ class DashboardService ->whereRaw( "(surveillance_cases.year_data < ? OR (surveillance_cases.year_data = ? AND surveillance_cases.week_data <= ?))", [$endYear, $endYear, $endWeek] - ); + ) + ->whereRaw('case_lab_results.is_positive = 1 and surveillance_cases.surveillance_id not in(6)'); }) ->selectRaw(" surveillance_cases.patient_province, - surveillance_cases.surveillance_id, + case_lab_results.pathogen_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 + COUNT(DISTINCT surveillance_cases.lab_code) as total ") ->groupBy( 'surveillance_cases.patient_province', - 'surveillance_cases.surveillance_id' + 'case_lab_results.pathogen_name' ) ->get(); } + + /* Start Overview Section */ + + public function influenzaSubtypeDetected($startYear, $startWeek, $endYear, $endWeek) + { + return SurveillanceCase::join('case_lab_results', function ($join) { + $join->on('surveillance_cases.lab_code', '=', 'case_lab_results.lab_code') + ->on('surveillance_cases.surveillance_id', '=', 'case_lab_results.surveillance_id'); + }) + ->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) { + $q->whereRaw( + "(surveillance_cases.year_data * 100 + surveillance_cases.week_data) BETWEEN ? AND ?", + [ + $startYear * 100 + $startWeek, + $endYear * 100 + $endWeek + ] + ); + }) + ->whereRaw('case_lab_results.is_positive = 1 and surveillance_cases.surveillance_id not in(6) and case_lab_results.indicator="Influenza"') + ->selectRaw(" + case_lab_results.subtype, + COUNT(DISTINCT surveillance_cases.lab_code) as total + ") + ->groupBy( + 'case_lab_results.subtype' + ) + ->get(); + } + + public function covidDistributedByAgeGroup($startYear, $startWeek, $endYear, $endWeek) + { + return SurveillanceCase::join('case_lab_results', function ($join) { + $join->on('surveillance_cases.lab_code', '=', 'case_lab_results.lab_code') + ->on('surveillance_cases.surveillance_id', '=', 'case_lab_results.surveillance_id'); + }) + ->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) { + $q->whereRaw( + "(surveillance_cases.year_data * 100 + surveillance_cases.week_data) BETWEEN ? AND ?", + [ + $startYear * 100 + $startWeek, + $endYear * 100 + $endWeek + ] + ); + }) + ->whereRaw('case_lab_results.is_positive = 1 and surveillance_cases.surveillance_id not in(6) and case_lab_results.indicator="Covid-19"') + ->selectRaw(" + CASE + WHEN patient_age_inday <= 28 THEN '0–28 days' + WHEN patient_age_inday <= 364 THEN '29 days–11 months' + WHEN patient_age_inday <= 1460 THEN '1–4 years' + WHEN patient_age_inday <= 5110 THEN '5–14 years' + WHEN patient_age_inday <= 8765 THEN '15–24 years' + WHEN patient_age_inday <= 18250 THEN '25–49 years' + WHEN patient_age_inday <= 23725 THEN '50–64 years' + ELSE '65+ years' + END as age_group, + CASE + WHEN patient_age_inday <= 28 THEN 1 + WHEN patient_age_inday <= 364 THEN 2 + WHEN patient_age_inday <= 1460 THEN 3 + WHEN patient_age_inday <= 5110 THEN 4 + WHEN patient_age_inday <= 8765 THEN 5 + WHEN patient_age_inday <= 18250 THEN 6 + WHEN patient_age_inday <= 23725 THEN 7 + ELSE 8 + END as age_order, + COUNT(*) as total + ") + ->groupBy('age_group', 'age_order') + ->orderBy('age_order') + ->get(); + + } + + public function covidLineageRelativeOverTime($startYear, $startWeek, $endYear, $endWeek) + { + return SurveillanceCase::join('case_lab_results', function ($join) { + $join->on('surveillance_cases.lab_code', '=', 'case_lab_results.lab_code') + ->on('surveillance_cases.surveillance_id', '=', 'case_lab_results.surveillance_id'); + }) + ->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) { + $q->whereRaw( + "(surveillance_cases.year_data * 100 + surveillance_cases.week_data) BETWEEN ? AND ?", + [ + $startYear * 100 + $startWeek, + $endYear * 100 + $endWeek + ] + ); + }) + ->whereRaw('case_lab_results.is_positive = 1 and surveillance_cases.surveillance_id=6 and case_lab_results.indicator="Covid-19" and case_lab_results.pathogen_name not in("","unable to align","N/A","NA")') + ->selectRaw(" + case_lab_results.pathogen_name as lineage, + concat(surveillance_cases.year_data,'-',surveillance_cases.week_data) as week, + COUNT(DISTINCT surveillance_cases.lab_code) as total + ") + ->groupBy( + 'case_lab_results.pathogen_name', + 'week' + ) + ->get(); + } + + public function influenzaRelativeOverTime($startYear, $startWeek, $endYear, $endWeek) + { + return SurveillanceCase::join('case_lab_results', function ($join) { + $join->on('surveillance_cases.lab_code', '=', 'case_lab_results.lab_code') + ->on('surveillance_cases.surveillance_id', '=', 'case_lab_results.surveillance_id'); + }) + ->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) { + $q->whereRaw( + "(surveillance_cases.year_data * 100 + surveillance_cases.week_data) BETWEEN ? AND ?", + [ + $startYear * 100 + $startWeek, + $endYear * 100 + $endWeek + ] + ); + }) + ->whereRaw('case_lab_results.is_positive = 1 and surveillance_cases.surveillance_id not in(6) and case_lab_results.indicator="Influenza"') + ->selectRaw(" + case_lab_results.subtype as lineage, + concat(surveillance_cases.year_data,'-',surveillance_cases.week_data) as week, + COUNT(DISTINCT surveillance_cases.lab_code) as total + ") + ->groupBy( + 'case_lab_results.subtype', + 'week' + ) + ->get(); + } + + /* End Overview Section */ + public function provinceProgram($surveillanceId, $startYear, $startWeek, $endYear, $endWeek) { return SurveillanceCase::selectRaw(" @@ -699,7 +854,7 @@ class DashboardService ->selectRaw(" CASE - WHEN LOWER(case_lab_results.pathogen_name) LIKE '%influenza%' + WHEN LOWER(case_lab_results.pathogen_name) LIKE '%influenza%' THEN 'Influenza' ELSE case_lab_results.pathogen_name END as pathogen, @@ -740,7 +895,7 @@ class DashboardService ->where('case_lab_results.subtype', '!=', 'Positive') - ->groupBy('subtype') + ->groupBy('subtype') ->orderByDesc('total') ->get(); @@ -874,4 +1029,4 @@ class DashboardService ->orderBy('period') ->get(); } -} \ No newline at end of file +} diff --git a/dashboard/app/Services/DataRetrievalService.php b/dashboard/app/Services/DataRetrievalService.php index 4ac6af9..b696d37 100644 --- a/dashboard/app/Services/DataRetrievalService.php +++ b/dashboard/app/Services/DataRetrievalService.php @@ -20,7 +20,7 @@ class DataRetrievalService $this->getILICases(now()->subDays(config('app.lookback_days.ILI'))->toDateString(), $toDate); // done $this->getLBMCases(now()->subDays(config('app.lookback_days.LBM'))->toDateString(), $toDate); // done $this->getAFICases(now()->subDays(config('app.lookback_days.AFI'))->toDateString(), $toDate); // done - //$this->getNSDCases(now()->subDays(config('app.lookback_days.NDS'))->toDateString(), $toDate); + $this->getNSDCases(now()->subDays(config('app.lookback_days.NDS'))->toDateString(), $toDate); $this->getSEQCases(now()->subDays(config('app.lookback_days.SEQ'))->toDateString(), $toDate); // done Log::channel('jobs')->info($toDate->toDateString(). ' Service Reload Data Successfully Ran'); return true; @@ -42,24 +42,35 @@ class DataRetrievalService ->select(" SELECT patient.labcode, - patient.patdate, + min(if(year(tps.collected)=0,patient.patdate, tps.collected)) as patdate, patient.isnewcase, l.labname_en, l.labaddress_en, 1 as surveillance_id, - niphc0_nphl.get_epi_period(patient.patdate, 'Y') as year_data, - niphc0_nphl.get_epi_period(patient.patdate, 'W') as week_data, + min(niphc0_nphl.get_epi_period(if(year(tps.collected)=0,patient.patdate, tps.collected), 'Y')) as year_data, + min(niphc0_nphl.get_epi_period(if(year(tps.collected)=0,patient.patdate, tps.collected), 'W')) as week_data, patient.patage, patient.patsex, NULL as is_alive, p.proname_en FROM niphc0_nphl.`labopatients` patient inner join niphc0_nphl.sari_patients sr_p on sr_p.patid = patient.patid + inner join niphc0_nphl.test_patsample tps on tps.patientid = patient.patid and tps.status = 1 inner join niphc0_nphl.laboratory l on l.labid = patient.patsource left join niphc0_nphl.province p on p.proid = patient.frompro WHERE patient.patgroup =2 and patient.status = 1 - ".$cond); + ".$cond . " + group by + patient.labcode, + patient.isnewcase, + l.labname_en, + l.labaddress_en, + patient.patage, + patient.patsex, + p.proname_en;" + + ); $this->insert_surveillance_cases($sari_cases); @@ -71,7 +82,7 @@ class DataRetrievalService if(trs.rtitle='Negatives', 0, 1) as is_positive, if(trs.rtitle !='Negatives', concat('Influenza ',LEFT(UPPER(trs.rtitle),1)),'') pathogen_name, if(trs.rtitle !='Negatives', trs.rtitle, '') as subtype, - 'SARI Influenza Test' as indicator + 'Influenza' as indicator FROM `labopatients` patient inner join sari_patients sr_p on sr_p.patid = patient.patid inner join test_patsample tps on tps.patientid = patient.patid @@ -93,7 +104,7 @@ class DataRetrievalService if(trs.rtitle='Negative', 0, 1) as is_positive, if(trs.rtitle !='Negative', 'SARS-CoV-2', '') as pathogen_name, '' as subtype, - 'SARI Covid Test' as indicator + 'Covid-19' as indicator FROM `labopatients` patient inner join sari_patients sr_p on sr_p.patid = patient.patid @@ -126,24 +137,33 @@ class DataRetrievalService ->select(" SELECT patient.labcode, - patient.patdate, + min(if(year(tps.collected)=0,patient.patdate, tps.collected)) as patdate, patient.isnewcase, l.labname_en, l.labaddress_en, 2 as surveillance_id, - niphc0_nphl.get_epi_period(patient.patdate, 'Y') as year_data, - niphc0_nphl.get_epi_period(patient.patdate, 'W') as week_data, + min(niphc0_nphl.get_epi_period(if(year(tps.collected)=0,patient.patdate, tps.collected), 'Y')) as year_data, + min(niphc0_nphl.get_epi_period(if(year(tps.collected)=0,patient.patdate, tps.collected), 'W')) as week_data, patient.patage, patient.patsex, NULL as is_alive, p.proname_en FROM niphc0_nphl.`labopatients` patient inner join niphc0_nphl.ili_patients sr_p on sr_p.patid = patient.patid + inner join niphc0_nphl.test_patsample tps on tps.patientid = patient.patid and tps.status = 1 inner join niphc0_nphl.laboratory l on l.labid = patient.patsource left join niphc0_nphl.province p on p.proid = patient.frompro WHERE patient.patgroup =3 and patient.status = 1 - ".$cond + ".$cond . " + group by + patient.labcode, + patient.isnewcase, + l.labname_en, + l.labaddress_en, + patient.patage, + patient.patsex, + p.proname_en;" ); @@ -158,7 +178,7 @@ class DataRetrievalService if(trs.rtitle='Negatives', 0, 1) as is_positive, if(trs.rtitle !='Negatives', concat('Influenza ',LEFT(UPPER(trs.rtitle),1)),'') pathogen_name, if(trs.rtitle !='Negatives', trs.rtitle, '') as subtype, - 'ILI Influenza Test' as indicator + 'Influenza' as indicator FROM niphc0_nphl.`labopatients` patient inner join niphc0_nphl.ili_patients sr_p on sr_p.patid = patient.patid inner join niphc0_nphl.test_patsample tps on tps.patientid = patient.patid @@ -182,7 +202,7 @@ class DataRetrievalService if(trs.rtitle='Negative', 0, 1) as is_positive, if(trs.rtitle !='Negative', 'SARS-CoV-2', '') as pathogen_name, '' as subtype, - 'ILI Covid Test' as indicator + 'Covid-19' as indicator FROM niphc0_nphl.`labopatients` patient inner join niphc0_nphl.ili_patients sr_p on sr_p.patid = patient.patid inner join niphc0_nphl.test_patsample tps on tps.patientid = patient.patid @@ -211,24 +231,34 @@ class DataRetrievalService ->select(" SELECT patient.labcode, - patient.patdate, + min(if(year(tps.collected)=0,patient.patdate, tps.collected)) as patdate, patient.isnewcase, l.labname_en, l.labaddress_en, 3 as surveillance_id, - niphc0_nphl.get_epi_period(patient.patdate, 'Y') year_data, - niphc0_nphl.get_epi_period(patient.patdate, 'W') week_data, + min(niphc0_nphl.get_epi_period(if(year(tps.collected)=0,patient.patdate, tps.collected), 'Y')) as year_data, + min(niphc0_nphl.get_epi_period(if(year(tps.collected)=0,patient.patdate, tps.collected), 'W')) as week_data, patient.patage, patient.patsex, NULL as is_alive, p.proname_en FROM niphc0_nphl.`labopatients` patient inner join niphc0_nphl.lbms_patients on lbms_patients.patid = patient.patid + inner join niphc0_nphl.test_patsample tps on tps.patientid = patient.patid and tps.status = 1 left join niphc0_nphl.province p on p.proid = patient.frompro inner join niphc0_nphl.laboratory l on l.labid = patient.patsource WHERE patient.patgroup = 17 and patient.status = 1 - ".$cond); + ".$cond . " + group by + patient.labcode, + patient.isnewcase, + l.labname_en, + l.labaddress_en, + patient.patage, + patient.patsex, + p.proname_en;" + ); $this->insert_surveillance_cases($lbm_cases); @@ -241,7 +271,7 @@ class DataRetrievalService if(trs.rtitle='Negatives', 0, 1) as is_positive, if(trs.rtitle !='Negatives', concat('Influenza ',LEFT(UPPER(trs.rtitle),1)),'') pathogen_name, if(trs.rtitle !='Negatives', trs.rtitle, '') as subtype, - 'LBM Influenza Test' as indicator + 'Influenza' as indicator FROM niphc0_nphl.`labopatients` patient inner join niphc0_nphl.lbms_patients sr_p on sr_p.patid = patient.patid inner join niphc0_nphl.test_patsample tps on tps.patientid = patient.patid @@ -264,7 +294,7 @@ class DataRetrievalService if(trs.rtitle='Negative', 0, 1) as is_positive, if(trs.rtitle !='Negative', 'SARS-CoV-2', '') as pathogen_name, '' as subtype, - 'LBM Covid Test' as indicator + 'Covid-19' as indicator FROM niphc0_nphl.`labopatients` patient inner join niphc0_nphl.lbms_patients sr_p on sr_p.patid = patient.patid inner join niphc0_nphl.test_patsample tps on tps.patientid = patient.patid @@ -287,19 +317,19 @@ class DataRetrievalService public function getAFICases($dateFrom, $dateTo = NULL) { $cond = ""; - $cond .= " and afi_lab_result.results_date >= '".date('Y-m-d', strtotime($dateFrom))."' "; - $cond .= (!empty($dateTo)) ? " and afi_lab_result.results_date <='".date('Y-m-d', strtotime($dateTo))."' ":""; + $cond .= " and afi_case.date_of_interview >= '".date('Y-m-d', strtotime($dateFrom))."' "; + $cond .= (!empty($dateTo)) ? " and afi_case.date_of_interview <='".date('Y-m-d', strtotime($dateTo))."' ":""; $afi_cases = DB::connection('mysql_afi') ->select(" SELECT afi_lab_result.sample_code as labcode, - afi_lab_result.results_date as patdate, + afi_case.date_of_interview as patdate, 1 as isnewcase, afi_case.sentinel_site as labname_en, '' as labaddress_en, 4 as surveillance_id, - niphc0_nphl.get_epi_period(afi_lab_result.results_date, 'Y') as year_data, - niphc0_nphl.get_epi_period(afi_lab_result.results_date, 'W') as week_data, + niphc0_nphl.get_epi_period(afi_case.date_of_interview, 'Y') as year_data, + niphc0_nphl.get_epi_period(afi_case.date_of_interview, 'W') as week_data, ifnull(DATEDIFF(afi_case.date_of_interview, date_of_birth),180) as patage, if(afi_case.sex='Male','M','F') as patsex, NULL as is_alive, @@ -309,7 +339,8 @@ class DataRetrievalService left join niphc0_nphl.province p on p.proid = afi_case.residence_province where afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null " .$cond); + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null " .$cond); $this->insert_surveillance_cases($afi_cases); @@ -321,13 +352,14 @@ class DataRetrievalService if(afi_lab_result.influenza_result='Positive',1,0) as is_positive, if(afi_lab_result.influenza_result='Positive', concat('Influenza ',LEFT(UPPER(JSON_UNQUOTE(JSON_EXTRACT(afi_lab_result.influenza_subtypes, '$[0].id'))),1)),'') pathogen_name, if(afi_lab_result.influenza_result='Positive', REPLACE(UPPER(JSON_UNQUOTE(JSON_EXTRACT(afi_lab_result.influenza_subtypes, '$[0].id'))),'_','/'),'') as subtype, - 'AFI Influenza Test' as indicator + 'Influenza' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.influenza_result in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond." union @@ -337,13 +369,15 @@ class DataRetrievalService if(afi_lab_result.sarscov2_result='Positive',1,0) as is_positive, if(afi_lab_result.sarscov2_result='Positive', 'SARS-CoV-2','') pathogen_name, '' as subtype, - 'AFI Covid Test' as indicator + 'Covid-19' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.sarscov2_result in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null + ".$cond." union @@ -351,15 +385,16 @@ class DataRetrievalService afi_lab_result.sample_code as labcode, 4 as surveillance_id, if(afi_lab_result.dengue_pcr='Positive',1,0) as is_positive, - if(afi_lab_result.dengue_pcr='Positive', 'Dengue','') pathogen_name, + if(afi_lab_result.dengue_pcr='Positive', 'Dengue Virus','') pathogen_name, '' as subtype, - 'AFI Dengue PCR Test' as indicator + 'Dengue Virus|PCR' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.dengue_pcr in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond." union @@ -367,15 +402,16 @@ class DataRetrievalService afi_lab_result.sample_code as labcode, 4 as surveillance_id, if(afi_lab_result.chikungunya_pcr='Positive',1,0) as is_positive, - if(afi_lab_result.chikungunya_pcr='Positive', 'Chikungunya','') pathogen_name, + if(afi_lab_result.chikungunya_pcr='Positive', 'Chikungunya Virus','') pathogen_name, '' as subtype, - 'AFI Chikungunya PCR Test' as indicator + 'Chikungunya Virus|PCR' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.chikungunya_pcr in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond." union @@ -383,15 +419,16 @@ class DataRetrievalService afi_lab_result.sample_code as labcode, 4 as surveillance_id, if(afi_lab_result.leptospira_pcr='Positive',1,0) as is_positive, - if(afi_lab_result.leptospira_pcr='Positive', 'Leptospira','') pathogen_name, + if(afi_lab_result.leptospira_pcr='Positive', 'Leptospira spp.','') pathogen_name, '' as subtype, - 'AFI Leptospira PCR Test' as indicator + 'Leptospira spp.|PCR' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.leptospira_pcr in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond." union @@ -399,15 +436,16 @@ class DataRetrievalService afi_lab_result.sample_code as labcode, 4 as surveillance_id, if(afi_lab_result.plasmodium_pcr='Positive',1,0) as is_positive, - if(afi_lab_result.plasmodium_pcr='Positive', 'Plasmodium','') pathogen_name, + if(afi_lab_result.plasmodium_pcr='Positive', 'Plasmodium spp.','') pathogen_name, '' as subtype, - 'AFI Plasmodium PCR Test' as indicator + 'Plasmodium spp.|PCR' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.plasmodium_pcr in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond." UNION @@ -415,15 +453,16 @@ class DataRetrievalService afi_lab_result.sample_code as labcode, 4 as surveillance_id, if(afi_lab_result.rickettsia_pcr='Positive',1,0) as is_positive, - if(afi_lab_result.rickettsia_pcr='Positive', 'Rickettsia','') pathogen_name, + if(afi_lab_result.rickettsia_pcr='Positive', 'Rickettsia spp.','') pathogen_name, '' as subtype, - 'AFI Rickettsia PCR Test' as indicator + 'Rickettsia spp.|PCR' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.rickettsia_pcr in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond." UNION @@ -431,15 +470,16 @@ class DataRetrievalService afi_lab_result.sample_code as labcode, 4 as surveillance_id, if(afi_lab_result.salmonella_pcr='Positive',1,0) as is_positive, - if(afi_lab_result.salmonella_pcr='Positive', 'Salmonella','') pathogen_name, + if(afi_lab_result.salmonella_pcr='Positive', 'Salmonella spp.','') pathogen_name, '' as subtype, - 'AFI Salmonella PCR Test' as indicator + 'Salmonella spp.|PCR' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.salmonella_pcr in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond." UNION @@ -447,15 +487,16 @@ class DataRetrievalService afi_lab_result.sample_code as labcode, 4 as surveillance_id, if(afi_lab_result.westnile_pcr='Positive',1,0) as is_positive, - if(afi_lab_result.westnile_pcr='Positive', 'Westnile Nile','') pathogen_name, + if(afi_lab_result.westnile_pcr='Positive', 'West Nile Virus','') pathogen_name, '' as subtype, - 'AFI Westnile PCR Test' as indicator + 'West Nile Virus|PCR' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.westnile_pcr in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond." UNION @@ -463,15 +504,16 @@ class DataRetrievalService afi_lab_result.sample_code as labcode, 4 as surveillance_id, if(afi_lab_result.zika_pcr='Positive',1,0) as is_positive, - if(afi_lab_result.zika_pcr='Positive', 'ZIKA','') pathogen_name, + if(afi_lab_result.zika_pcr='Positive', 'ZIKA Virus','') pathogen_name, '' as subtype, - 'AFI ZIKA PCR Test' as indicator + 'ZIKA Virus|PCR' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.zika_pcr in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond." UNION @@ -481,15 +523,16 @@ class DataRetrievalService afi_lab_result.sample_code as labcode, 4 as surveillance_id, if(afi_lab_result.chikungunya_serum='Positive',1,0) as is_positive, - if(afi_lab_result.chikungunya_serum='Positive', 'Chikungunya','') pathogen_name, + if(afi_lab_result.chikungunya_serum='Positive', 'Chikungunya Virus','') pathogen_name, '' as subtype, - 'AFI Chikungunya Serum Test' as indicator + 'Chikungunya Virus|Serum' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.chikungunya_serum in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond." UNION @@ -497,15 +540,16 @@ class DataRetrievalService afi_lab_result.sample_code as labcode, 4 as surveillance_id, if(afi_lab_result.dengue_serum='Positive',1,0) as is_positive, - if(afi_lab_result.dengue_serum='Positive', 'Dengue','') pathogen_name, + if(afi_lab_result.dengue_serum='Positive', 'Dengue Virus','') pathogen_name, '' as subtype, - 'AFI Dengue Serum Test' as indicator + 'Dengue Virus|Serum' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.dengue_serum in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond." UNION @@ -513,15 +557,16 @@ class DataRetrievalService afi_lab_result.sample_code as labcode, 4 as surveillance_id, if(afi_lab_result.westnile_serum='Positive',1,0) as is_positive, - if(afi_lab_result.westnile_serum='Positive', 'Westnile','') pathogen_name, + if(afi_lab_result.westnile_serum='Positive', 'West Nile Virus','') pathogen_name, '' as subtype, - 'AFI Westnile Serum Test' as indicator + 'West Nile Virus|Serum' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.westnile_serum in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond." UNION @@ -529,15 +574,16 @@ class DataRetrievalService afi_lab_result.sample_code as labcode, 4 as surveillance_id, if(afi_lab_result.zika_serum='Positive',1,0) as is_positive, - if(afi_lab_result.zika_serum='Positive', 'ZIKA','') pathogen_name, + if(afi_lab_result.zika_serum='Positive', 'ZIKA Virus','') pathogen_name, '' as subtype, - 'AFI ZIKA Serum Test' as indicator + 'ZIKA Virus|Serum' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.zika_serum in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond." + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond." UNION @@ -545,15 +591,16 @@ class DataRetrievalService afi_lab_result.sample_code as labcode, 4 as surveillance_id, if(afi_lab_result.je_virus='Positive',1,0) as is_positive, - if(afi_lab_result.je_virus='Positive', 'Japanese Encephalitis','') pathogen_name, + if(afi_lab_result.je_virus='Positive', 'Japanese Encephalitis Virus','') pathogen_name, '' as subtype, - 'AFI Japanese Encephalitis Test' as indicator + 'Japanese Encephalitis Virus|Serum' as indicator FROM afi_db.`afi_tbl_afiform` as afi_case INNER join afi_db.afi_tbl_result as afi_lab_result on afi_lab_result.unique_afi_id = afi_case.unique_afi_id where afi_lab_result.sample_code is not null and afi_lab_result.je_virus in('Negative', 'Positive') and afi_case.afi_event is NOT null - and afi_lab_result.results_date is not null ".$cond + and afi_case.date_of_interview is not null + and afi_lab_result.sample_code is not null ".$cond ); $this->insert_surveillance_case_lab_results($afi_lab_results); @@ -561,62 +608,179 @@ class DataRetrievalService public function getNSDCases($dateFrom, $dateTo = NULL) { - return []; + $cond = ""; + $cond .= " and sc.sc_date >= '".date('Y-m-d', strtotime($dateFrom))."' "; + $cond .= (!empty($dateTo)) ? " and sc.sc_date <='".date('Y-m-d', strtotime($dateTo))."' ":""; + $nds_cases = DB::connection('mysql_nds') + ->select(" + SELECT + DISTINCT + sc.sc_sample_code as labcode, + sc.sc_date as patdate, + 1 as isnewcase, + site_ad.sn_name as labname_en, + '' as labaddress_en, + 5 as surveillance_id, + niphc0_nphl.get_epi_period(sc.sc_date, 'Y') as year_data, + niphc0_nphl.get_epi_period(sc.sc_date, 'W') as week_data, + ifnull(DATEDIFF(sc.sc_date, patient.pat_dob),180) as patage, + if(patient.pat_gender='Male','M','F') as patsex, + NULL as is_alive, + pro.pro_eng as proname_en + from nds_db.tbl_nds_site_invest case_inv + inner join nds_db.tbl_nds_siteinvet_patient inv_patient + on inv_patient.pksi_id = case_inv.pksi_id + inner join nds_db.tbl_nds_site_address site_ad + on site_ad.pksn_id = case_inv.fksn_id + inner join nds_db.tbl_nds_patient patient + on patient.pkpat_id = inv_patient.fkpat_id + inner join nds_db.tbl_nds_basic_sample bs + on bs.fkpat_id = inv_patient.fkpat_id + inner join nds_db.tbl_nds_sample_collection sc + on sc.fkbs_id = bs.pkbs_id + left join nds_db.tblprovince pro + on pro.pkpro_code = JSON_UNQUOTE(JSON_EXTRACT(patient.pat_address, '$[0].province')) + where + sc.sc_sample_code is not null + and length(sc_sample_code)>0 " .$cond); + + $this->insert_surveillance_cases($nds_cases); + + $nds_lab_results = DB::connection('mysql_nds') + ->select(" + SELECT + DISTINCT + sc.sc_sample_code as labcode, + 5 as surveillance_id, + rt.rt_positive as is_positive, + trt.trt_name pathogen_name, + '' as subtype, + trt.trt_name as indicator + from nds_db.tbl_nds_site_invest case_inv + inner join nds_db.tbl_nds_siteinvet_patient inv_patient + on inv_patient.pksi_id = case_inv.pksi_id + inner join nds_db.tbl_nds_patient patient + on patient.pkpat_id = inv_patient.fkpat_id + inner join nds_db.tbl_nds_basic_sample bs + on bs.fkpat_id = inv_patient.fkpat_id + inner join nds_db.tbl_nds_sample_collection sc + on sc.fkbs_id = bs.pkbs_id + inner join nds_db.tbl_nds_result_test rt + on rt.fkpat_id = bs.fkpat_id + inner join nds_db.tbl_nds_test_result_type trt + on trt.pktrt_id = rt.fktrt_id + + where + sc.sc_sample_code is not null + and length(sc_sample_code)>0 " .$cond); + + $this->insert_surveillance_case_lab_results($nds_lab_results); + } public function getSEQCases($dateFrom, $dateTo = NULL) { $cond = ""; - $cond .= " and patient.patdate >= '".date('Y-m-d', strtotime($dateFrom))."' "; - $cond .= (!empty($dateTo)) ? " and patient.patdate <='".date('Y-m-d', strtotime($dateTo))."' ":""; + $cond .= " and c.patdate >= '".date('Y-m-d', strtotime($dateFrom))."' "; + $cond .= (!empty($dateTo)) ? " and c.patdate <='".date('Y-m-d', strtotime($dateTo))."' ":""; $seq_cases = DB::connection('mysql_nphl') ->select(" SELECT DISTINCT - patient.labcode, - patient.patdate, - patient.isnewcase, + c.labcode, + min(if(year(tps.collected)=0,c.patdate, tps.collected)) as patdate, + c.isnewcase, l.labname_en, l.labaddress_en, 6 as surveillance_id, - niphc0_nphl.get_epi_period(patient.patdate, 'Y') as year_data, - niphc0_nphl.get_epi_period(patient.patdate, 'W') as week_data, - patient.patage, - patient.patsex, + min(niphc0_nphl.get_epi_period(if(year(tps.collected)=0,c.patdate, tps.collected), 'Y')) as year_data, + min(niphc0_nphl.get_epi_period(if(year(tps.collected)=0,c.patdate, tps.collected), 'W')) as week_data, + c.patage, + c.patsex, NULL as is_alive, pro.proname_en from niphc0_nphl.sq_batch sqb inner join niphc0_nphl.sq_sub_batch sqsb on sqsb.bt_id = sqb.bt_id - inner join niphc0_nphl.labopatients patient on patient.patid = sqsb.patid - inner join niphc0_nphl.laboratory l on l.labid = patient.patsource - inner join niphc0_nphl.test_patsample tps on tps.patientid = patient.patid + inner join niphc0_nphl.labopatients c on c.patid = sqsb.patid + inner join niphc0_nphl.laboratory l on l.labid = p.patsource + inner join niphc0_nphl.test_patsample tps on tps.patientid = c.patid and tps.status = 1 inner join niphc0_nphl.test_patsamtest tpst on tpst.patsmid = tps.sampleid and tpst.patid = tps.patientid - left join niphc0_nphl.province pro on pro.proid = patient.frompro - where patient.status = 1 - and tpst.labtestid IN ( 659, 662, 666, 667, 668 ) - " . $cond); + left join niphc0_nphl.province pro on pro.proid = c.frompro + where c.status = 1 + and tpst.labtestid IN ( 659, 662, 666, 667, 668 ) + " . $cond . " + group by + c.labcode, + c.isnewcase, + l.labname_en, + l.labaddress_en, + c.patage, + c.patsex, + pro.proname_en;" + ); $this->insert_surveillance_cases($seq_cases); $seq_lab_results = DB::connection('mysql_nphl') ->select(" - SELECT DISTINCT - patient.labcode, - 6 as surveillance_id, - 1 is_positive, - ifnull(sqsb.seqlineage,'N/A') as pathogen_name, - trs.rtitle as subtype, - concat('Sequencing Test ', case when patgroup=2 then 'SARI' when patgroup=3 then 'ILI' when patgroup=19 then 'COVID-19' else '' end) as indicator - from niphc0_nphl.sq_batch sqb - inner join niphc0_nphl.sq_sub_batch sqsb on sqsb.bt_id = sqb.bt_id - inner join niphc0_nphl.labopatients patient on patient.patid = sqsb.patid - inner join niphc0_nphl.test_patsample tps on tps.patientid = patient.patid - inner join niphc0_nphl.test_patsamtest tpst on tpst.patsmid = tps.sampleid and tpst.patid = tps.patientid - inner join niphc0_nphl.test_pattestresult tptr on tptr.pstid = tpst.pstid - inner join niphc0_nphl.test_resultselect trs on trs.rid = tptr.rsltid - where patient.status = 1 - and tpst.labtestid IN ( 659, 662, 666, 667, 668 ) - and trs.rtitle not in ('Inconclusive', 'In Progress') - " . $cond); + SELECT + labcode, + 6 as surveillance_id, + 1 is_positive, + ifnull(seqlineage,'N/A') as pathogen_name, + GROUP_CONCAT(seq_result SEPARATOR '') as subtype, + (case + when (Group_concat(influ_result SEPARATOR '')='Negatives' or Group_concat(influ_result SEPARATOR '')='') and Group_concat(covid_result SEPARATOR '')='Positive' then 'Covid-19' + when (Group_concat(influ_result SEPARATOR '') !='Negatives' and Group_concat(influ_result SEPARATOR '')!='') and Group_concat(covid_result SEPARATOR '')='Negative' then 'Influenza' + when (Group_concat(influ_result SEPARATOR '') !='Negatives' and Group_concat(influ_result SEPARATOR '')!='') and Group_concat(covid_result SEPARATOR '')='Positive' then 'Co-infection' + else 'N/A' + end) as indicator + FROM (SELECT + c.labcode, + ( CASE + WHEN e1.labtestid IN ( 238, 340, 381 ) THEN e3.rtitle + ELSE '' + end ) influ_result, + ( CASE + WHEN e1.labtestid IN ( 650, 651 ) THEN e3.rtitle + ELSE '' + end ) covid_result, + ( CASE + WHEN e1.labtestid IN ( 668, 659, 666, 667, 662 ) THEN + e3.rtitle + ELSE '' + end ) seq_result, + b.seqlineage + FROM niphc0_nphl.sq_batch a + LEFT JOIN niphc0_nphl.sq_sub_batch b + ON a.bt_id = b.bt_id + LEFT JOIN niphc0_nphl.labopatients c + ON b.patid = c.patid + LEFT JOIN niphc0_nphl.laboratory d + ON c.patsource = d.labid + LEFT JOIN niphc0_nphl.test_patsamtest e1 + ON c.patid = e1.patid + LEFT JOIN niphc0_nphl.test_pattestresult e2 + ON e1.pstid = e2.pstid + LEFT JOIN niphc0_nphl.test_resultselect e3 + ON e2.rsltid = e3.rid + LEFT JOIN niphc0_nphl.ncov_patients e4 + ON c.patid = e4.patid + LEFT JOIN niphc0_nphl.test_patsample e5 + ON c.patid = e5.patientid + AND e5.sampleid = e1.patsmid + LEFT JOIN niphc0_nphl.test_patsmpbyunit f + ON e5.sampleid = f.patsamid + AND c.patid = f.patid + WHERE e1.labtestid IN ( 238, 340, 381, 650, + 651, 668, 659, 666, + 667, 662 ) + " . $cond . " + ) tbl_a + GROUP BY labcode, + seqlineage + HAVING + GROUP_CONCAT(seq_result SEPARATOR '') not in ('','Inconclusive', 'In Progress') + "); $this->insert_surveillance_case_lab_results($seq_lab_results); diff --git a/dashboard/config/app.php b/dashboard/config/app.php index 9aa6d51..ad5e6bf 100644 --- a/dashboard/config/app.php +++ b/dashboard/config/app.php @@ -16,12 +16,12 @@ return [ 'name' => env('APP_NAME', 'Laravel'), 'lookback_days' => [ - 'SARI' => 777, // - 'ILI' => 777, - 'LBM' => 777, - 'AFI' => 777, - 'NDS' => 777, - 'SEQ' => 200 + 'SARI' => 7, // + 'ILI' => 7, + 'LBM' => 7, + 'AFI' => 7, + 'NDS' => 7, + 'SEQ' => 7 ], /* diff --git a/dashboard/public/js/overview.js b/dashboard/public/js/overview.js index 984559e..88f88a7 100644 --- a/dashboard/public/js/overview.js +++ b/dashboard/public/js/overview.js @@ -1,4 +1,8 @@ let trendChart; +let influenzaSubtypeChart; +let covidDistributedByAgeChart; +let covidLineageFrequencyChart; +let influenzaSubtypeFrequencyChart; let map; /* @@ -30,7 +34,7 @@ function loadSummary() {
-
${item.code}
+
${item.code} Cases

${item.current_total}

Last 7 days
@@ -102,12 +106,15 @@ function loadTrend(periodType, startYear, startWeek, endYear, endWeek) { const colors = { SARI: '#2563eb', ILI: '#10b981', - LBM: '#9333ea' + LBM: '#9333ea', + AFI: '#fc5741', + NDS: '#d59d01', + SEQ: '#9ca3af' }; const datasets = []; - const allowedPrograms = ['SARI', 'ILI', 'LBM']; + const allowedPrograms = ['SARI', 'ILI', 'LBM', 'AFI', 'NDS', 'SEQ']; Object.keys(data).forEach(code => { @@ -145,7 +152,10 @@ function loadTrend(periodType, startYear, startWeek, endYear, endWeek) { options: { responsive: true, plugins: { - legend: { position: 'bottom' } + legend: { position: 'bottom' }, + datalabels: { + display: false + } }, interaction: { mode: 'index', @@ -154,15 +164,499 @@ function loadTrend(periodType, startYear, startWeek, endYear, endWeek) { scales: { y: { beginAtZero: true, - ticks: { stepSize: 1 } + ticks: { + stepSize: 1 + }, + title: { + display: true, + text: 'Number of Cases' + }, }, - x: { grid: { display: false } } + x: { + grid: { + display: false + }, + title: { + display: true, + text: 'Surveillance' + }, + } } } }); }); } + +function loadInfluenzaSubtypeDistribution(periodType, startYear, startWeek, endYear, endWeek) { + + fetch(`/api/dashboard/influenza-subtype-distribution?period_type=${periodType}&start_year=${startYear}&start_week=${startWeek}&end_year=${endYear}&end_week=${endWeek}`) + .then(res => res.json()) + .then(data => { + + let displayLabels = data.map(item => item.subtype); + let dataset = data.map(item => item.total); + + const colors = [ + 'rgba(177,111,243,0.94)' + ]; + + + if (influenzaSubtypeChart) influenzaSubtypeChart.destroy(); + influenzaSubtypeChart = new Chart(document.getElementById('influenzaSubtypeDistribution'), { + type: 'bar', // you can change to 'pie', 'doughnut', etc. + data: { + labels: displayLabels, + datasets: [{ + data: dataset, + backgroundColor: colors, + }] + }, + options: { + indexAxis: 'y', + responsive: true, + plugins: { + legend: { + display: false, + position:'right' + }, + datalabels: { + color: '#ffffff', + backgroundColor: 'rgba(68,76,68,0.31)', + borderRadius: 6, + z: 1000, + padding: { + top: 6, + bottom: 6, + left: 10, + right: 10 + }, + font: { + weight: 'bold', + size: 12 + }, + formatter: (value) => value, + anchor: 'end', + align: 'end', + offset: 10, + clamp: false + } + }, + scales: { + x: { + stacked: true, + beginAtZero: true, + title: { + display: true, + text: 'Number of Positive Influenza Subtypes' + } + }, + y: { + stacked: true, + beginAtZero: true, + title: { + display: true, + text: 'Influenza Subtypes' + }, + grid:{ + display: false + } + } + } + }, + plugins: [ChartDataLabels] + }); + + }); +} + +function loadCovidDistributedByAgeGroup(periodType, startYear, startWeek, endYear, endWeek) { + + fetch(`/api/dashboard/covid-distributed-by-age-group?period_type=${periodType}&start_year=${startYear}&start_week=${startWeek}&end_year=${endYear}&end_week=${endWeek}`) + .then(res => res.json()) + .then(data => { + + let displayLabels = data.map(item => item.age_group); + let dataset = data.map(item => item.total); + + const colors = [ + '#84cc16' + ]; + + if (covidDistributedByAgeChart) covidDistributedByAgeChart.destroy(); + covidDistributedByAgeChart = new Chart(document.getElementById('covidDistributedByAgeGroup'), { + type: 'bar', // you can change to 'pie', 'doughnut', etc. + data: { + labels: displayLabels, + datasets: [{ + label: 'Total Covid-19 Detected', + data: dataset, + backgroundColor: colors, + }] + }, + options: { + responsive: true, + plugins: { + legend: { + display: false, + position:'bottom' + }, + datalabels: { + color: '#fff', + backgroundColor: 'rgba(68,76,68,0.7)', + borderRadius: 6, + z: 1000, + padding: { + top: 6, + bottom: 6, + left: 10, + right: 10 + }, + font: { + weight: 'bold', + size: 12 + }, + formatter: (value) => value, + anchor: 'end', + align: 'end', + offset: 10, + clamp: false + } + }, + scales: { + x: { + stacked: true, + beginAtZero: true, + title: { + display: true, + text: 'Patient Age Group' + }, + grid:{ + display: false + } + }, + y: { + stacked: true, + beginAtZero: true, + title: { + display: true, + text: 'Number of Positive SARS-CoV-2' + } + } + } + } + }); + + }); +} + +function loadCovidLineageFrequency(periodType, startYear, startWeek, endYear, endWeek) { + + fetch(`/api/dashboard/covid-lineage-frequency?period_type=${periodType}&start_year=${startYear}&start_week=${startWeek}&end_year=${endYear}&end_week=${endWeek}`) + .then(res => res.json()) + .then(data => { + + // Extract unique weeks (X-axis) + const weeks = [...new Set(data.map(item => item.week))].sort(); + + // Extract unique lineages + const lineages = [...new Set(data.map(item => item.lineage))]; + + // Color palette + const colors = [ + '#84cc16','#22c55e','#06b6d4','#3b82f6', + '#6366f1','#a855f7','#ec4899','#ef4444', + '#f97316','#eab308' + ]; + + // Build datasets + const datasets = lineages.map((lineage, index) => { + const lineageData = weeks.map(week => { + const found = data.find( + item => item.week === week && item.lineage === lineage + ); + return found ? found.total : 0; + }); + + return { + label: lineage, + data: lineageData, + fill: true, // area fill + tension: 0.4, // smooth curve + borderColor: 'transparent', // hide the line + borderWidth: 0, + pointRadius: 0, // hide points + backgroundColor: hexToRGBA(colors[index % colors.length], 0.3), + stack: 'total' + }; + }); + + // Destroy previous chart if exists + if (covidLineageFrequencyChart) covidLineageFrequencyChart.destroy(); + + const ctx = document.getElementById('covidLineageFrequency').getContext('2d'); + + covidLineageFrequencyChart = new Chart(ctx, { + type: 'line', + data: { + labels: weeks, + datasets: datasets + }, + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { + mode: 'index', + intersect: false + }, + plugins: { + legend: { + display: false // hide default legend + }, + tooltip: { + mode: 'index', + intersect: false + }, + datalabels: { + display: false // hide labels + } + }, + scales: { + x: { + stacked: true, + title: { + display: true, + text: 'Week' + }, + grid:{ + display: false + } + }, + y: { + stacked: true, + beginAtZero: true, + title: { + display: true, + text: 'Relative Frequency' + }, + } + } + } + }); + + // ------------------------- + // Custom right-side scrollable legend + // ------------------------- + const legendContainer = document.getElementById('legendContainer'); + legendContainer.innerHTML = ''; // clear old legend + + datasets.forEach((dataset, index) => { + const item = document.createElement('div'); + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.marginBottom = '4px'; + item.style.fontSize = '11px'; + item.style.cursor = 'pointer'; + item.innerHTML = ` + + ${dataset.label} + `; + + item.addEventListener('click', () => { + const meta = covidLineageFrequencyChart.getDatasetMeta(index); + + // If the clicked dataset is already the only visible one, show all + const allHidden = datasets.every((d, i) => covidLineageFrequencyChart.getDatasetMeta(i).hidden || i === index); + if (!allHidden) { + // Hide all datasets + datasets.forEach((d, i) => { + covidLineageFrequencyChart.getDatasetMeta(i).hidden = true; + }); + // Show only clicked + meta.hidden = false; + } else { + // Show all datasets + datasets.forEach((d, i) => { + covidLineageFrequencyChart.getDatasetMeta(i).hidden = false; + }); + } + + covidLineageFrequencyChart.update(); + + // Update legend opacity + Array.from(legendContainer.children).forEach((child, i) => { + const metaItem = covidLineageFrequencyChart.getDatasetMeta(i); + child.style.opacity = metaItem.hidden ? 0.5 : 1; + }); + }); + + legendContainer.appendChild(item); + }); + + // Scrollable CSS (in case legend is long) + legendContainer.style.maxHeight = '375px'; + legendContainer.style.overflowY = 'auto'; + legendContainer.style.padding = '8px'; + legendContainer.style.borderRadius = '0px'; + }); +} + +function loadInfluenzaSubtypeFrequency(periodType, startYear, startWeek, endYear, endWeek) { + + fetch(`/api/dashboard/influenza-relative-frequency?period_type=${periodType}&start_year=${startYear}&start_week=${startWeek}&end_year=${endYear}&end_week=${endWeek}`) + .then(res => res.json()) + .then(data => { + + // Extract unique weeks (X-axis) + const weeks = [...new Set(data.map(item => item.week))].sort(); + + const colors = [ + '#84cc16','#22c55e','#06b6d4','#3b82f6', + '#6366f1','#a855f7','#ec4899','#ef4444', + '#f97316','#eab308' + ]; + + // Extract unique lineages + const lineages = [...new Set(data.map(item => item.lineage))]; + // Build datasets + const datasets = lineages.map((lineage, index) => { + const lineageData = weeks.map(week => { + const found = data.find( + item => item.week === week && item.lineage === lineage + ); + return found ? found.total : 0; + }); + + return { + label: lineage, + data: lineageData, + fill: true, // area fill + tension: 0.4, // smooth curve + borderColor: 'transparent', // hide the line + borderWidth: 0, + pointRadius: 0, // hide points + backgroundColor: hexToRGBA(colors[index % colors.length], 0.3), + stack: 'total' + }; + }); + + // Destroy previous chart if exists + if (influenzaSubtypeFrequencyChart) influenzaSubtypeFrequencyChart.destroy(); + + const ctx = document.getElementById('influenzaSubtypeFrequency').getContext('2d'); + + influenzaSubtypeFrequencyChart = new Chart(ctx, { + type: 'line', + data: { + labels: weeks, + datasets: datasets + }, + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { + mode: 'index', + intersect: false + }, + plugins: { + legend: { + display: false // hide default legend + }, + tooltip: { + mode: 'index', + intersect: false + }, + datalabels: { + display: false // hide labels + } + }, + scales: { + x: { + stacked: true, + title: { + display: true, + text: 'Week' + }, + grid:{ + display: false + } + }, + y: { + stacked: true, + beginAtZero: true, + title: { + display: true, + text: 'Relative Frequency' + }, + } + } + } + }); + + const legendContainer = document.getElementById('legendContainerInfluenzaSubtypeFrequency'); + legendContainer.innerHTML = ''; // clear old legend + + datasets.forEach((dataset, index) => { + const item = document.createElement('div'); + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.marginBottom = '4px'; + item.style.fontSize = '11px'; + item.style.cursor = 'pointer'; + item.innerHTML = ` + + ${dataset.label} + `; + + item.addEventListener('click', () => { + const meta = influenzaSubtypeFrequencyChart.getDatasetMeta(index); + + // If the clicked dataset is already the only visible one, show all + const allHidden = datasets.every((d, i) => influenzaSubtypeFrequencyChart.getDatasetMeta(i).hidden || i === index); + if (!allHidden) { + // Hide all datasets + datasets.forEach((d, i) => { + influenzaSubtypeFrequencyChart.getDatasetMeta(i).hidden = true; + }); + // Show only clicked + meta.hidden = false; + } else { + // Show all datasets + datasets.forEach((d, i) => { + influenzaSubtypeFrequencyChart.getDatasetMeta(i).hidden = false; + }); + } + + influenzaSubtypeFrequencyChart.update(); + + // Update legend opacity + Array.from(legendContainer.children).forEach((child, i) => { + const metaItem = influenzaSubtypeFrequencyChart.getDatasetMeta(i); + child.style.opacity = metaItem.hidden ? 0.5 : 1; + }); + }); + + legendContainer.appendChild(item); + }); + + // Scrollable CSS (in case legend is long) + legendContainer.style.maxHeight = '375px'; + legendContainer.style.overflowY = 'auto'; + legendContainer.style.padding = '8px'; + legendContainer.style.borderRadius = '0px'; + }); +} + +// ------------------------- +// Helper to convert hex to rgba +// ------------------------- +function hexToRGBA(hex, alpha) { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + return `rgba(${r}, ${g}, ${b}, ${alpha})`; +} + function updateAlerts() { if (!window._summaryData || !window._provinceData) return; @@ -380,10 +874,14 @@ function renderAlerts(alerts) { |-------------------------------------------------------------------------- */ function getPositivityColor(p) { - if (p > 20) return "#b91c1c"; - if (p > 10) return "#ef4444"; - if (p > 5) return "#f59e0b"; - if (p > 0) return "#84cc16"; + if (p == 'A/H1N1pdm') return "#2563eb"; + if (p == 'A/H3N2') return "#11de9d"; + if (p == 'B/Yam') return "#9333ea"; + if (p == 'B/Vic') return "#1021b9"; + if (p == 'A/H9N2') return "#b91081"; + if (p == 'A/H5N1') return "#ad850d"; + if (p == 'A/Unsubtypable') return "#047393"; + if (p == 'B/Unsubtypable') return "#890512"; return "#9ca3af"; } function normalizeProvince(name, validSet) { @@ -432,13 +930,15 @@ function addPositivityLegend() { div.innerHTML = `
-
Positivity
- -
> 20%
-
10–20%
-
5–10%
-
0–5%
-
0%
+
Influenza Subtypes
+
A/H1N1pdm
+
A/H3N2
+
A/H9N2
+
A/H5N1
+
A/Unsubtypable
+
B/Yam
+
B/Vic
+
B/Unsubtypable
`; @@ -490,52 +990,42 @@ function loadProvinceMap(startYear, startWeek, endYear, endWeek) { const center = layer.getBounds().getCenter(); const rows = data.filter(d => { - if (![1, 2, 3].includes(d.surveillance_id)) return false; - const name = normalizeProvince(d.patient_province, validProvinces); return name === province; }); - const offsets = { 1: -0.15, 2: 0, 3: 0.15 }; + if (!rows.length) return; - const colors = { - 1: '#2563eb', - 2: '#10b981', - 3: '#9333ea' - }; + // 👉 group by pathogen_name + const pathogens = [...new Set(rows.map(r => r.pathogen_name))]; + + const spacing = 1; //0.12; // adjust spacing between circles rows.forEach(row => { - const percent = row.total - ? ((row.positive / row.total) * 100).toFixed(1) - : 0; - - const offset = offsets[row.surveillance_id] ?? 0; + // 👉 dynamic offset based on pathogen index + const index = pathogens.indexOf(row.pathogen_name); + const offset = (index - (pathogens.length - 1) / 2) * spacing; const lat = center.lat; const lng = center.lng + offset; - const programName = - row.surveillance_id === 1 ? 'SARI' : - row.surveillance_id === 2 ? 'ILI' : 'LBM'; L.circleMarker([lat, lng], { radius: getRadius(row.total), - fillColor: colors[row.surveillance_id], - color: getPositivityColor(percent), + fillColor: getColorByPathogen(row.pathogen_name), // 👈 new function + color: getPositivityColor(row.pathogen_nam), weight: 2, fillOpacity: 0.9 }) .bindTooltip(` - ${province}
- ${programName}
- Cases: ${row.total}
- Positivity: ${percent}% - `) + ${province}
+ ${row.pathogen_name}
+ Total Detected: ${row.total}
+ `) .addTo(map); }); - } }).addTo(map); @@ -544,6 +1034,21 @@ function loadProvinceMap(startYear, startWeek, endYear, endWeek) { } +function getColorByPathogen(name) { + const colors = { + "A/H1N1pdm": "#2563eb", + "A/H3N2": "#11de9d", + "B/Yam": "#9333ea", + "B/Vic" : "#1021b9", + "A/H9N2": "#b91081", + "A/H5N1": "#ad850d", + "A/Unsubtypable": "#047393", + "B/Unsubtypable": "#890512" + }; + + return colors[name] || "#000000"; // fallback color +} + /* |-------------------------------------------------------------------------- @@ -558,8 +1063,58 @@ document.addEventListener("DOMContentLoaded", () => { new DashboardFilter((startYear, startWeek, endYear, endWeek) => { loadTrend('week', startYear, startWeek, endYear, endWeek); + loadInfluenzaSubtypeDistribution('week', startYear, startWeek, endYear, endWeek); + loadCovidDistributedByAgeGroup('week', startYear, startWeek, endYear, endWeek); + loadInfluenzaSubtypeFrequency('week', startYear, startWeek, endYear, endWeek); + loadCovidLineageFrequency('week', startYear, startWeek, endYear, endWeek); loadProvinceMap(startYear, startWeek, endYear, endWeek); - + const elements = document.querySelectorAll(".report-period"); + elements.forEach(el => { + el.textContent = 'Week ' + startWeek + ' of '+startYear+' to ' + 'Week ' + endWeek + ' of ' + endYear + }); }); -}); \ No newline at end of file +}); + + +document.addEventListener("DOMContentLoaded", function () { + + let currentSlide = 0; + const slides = document.querySelectorAll('.slide'); + const nextBtn = document.querySelector('.next-btn'); + const prevBtn = document.querySelector('.prev-btn'); + + function showSlide(index) { + slides.forEach((slide, i) => { + slide.classList.remove('active', 'prev'); + + if (i === index) { + slide.classList.add('active'); + } else if (i === index - 1) { + slide.classList.add('prev'); + } + }); + } + + function nextSlide() { + currentSlide = (currentSlide + 1) % slides.length; + showSlide(currentSlide); + } + + function prevSlide() { + currentSlide = (currentSlide - 1 + slides.length) % slides.length; + showSlide(currentSlide); + } + + // Button events + nextBtn.addEventListener('click', nextSlide); + prevBtn.addEventListener('click', prevSlide); + + // Auto slide every 1 minute + let slideInterval = 2 * (60 * 1000); + setInterval(nextSlide, slideInterval); + + // Init + showSlide(currentSlide); + +}); diff --git a/dashboard/public/js/program.js b/dashboard/public/js/program.js index 90c97f1..8f63903 100644 --- a/dashboard/public/js/program.js +++ b/dashboard/public/js/program.js @@ -140,7 +140,6 @@ function renderProvinceHeatmap(rows) { const total = totals[province]?.total || 0; const positive = totals[province]?.positive || 0; - // ✅ positivity kept const percent = total ? ((positive / total) * 100).toFixed(1) : 0; diff --git a/dashboard/resources/views/dashboard/overview.blade.php b/dashboard/resources/views/dashboard/overview.blade.php index 38536d2..037d1f5 100644 --- a/dashboard/resources/views/dashboard/overview.blade.php +++ b/dashboard/resources/views/dashboard/overview.blade.php @@ -20,7 +20,6 @@
- -
+
+ +
+
-
+
- -
+ +
+
+
- -
-
+
+
-
-
Epidemic Trend
-

- (based on selected epiweek range) -

+
Epidemic Trend
+

+ (based on selected epiweek range) +

+ +
+
+ +
- - -
-
- -
-
+ +
+
+
+
+
+
Influenza Subtypes Distribution
+

+ (based on selected epiweek range) +

-
Recent Alerts & Notifications
+ -
    +
    +
    + +
    +
    -
    + +
    +
    +
    +
    +
    +
    SARS-CoV-2 Detected Distribute by Age Group
    +

    + (based on selected epiweek range) +

    + +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +
    SARS-CoV-2 Lineage Relative Frequencies Over Time
    +

    + (based on selected epiweek range) +

    + +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +
    Influenza Subtypes Relative Frequencies Over Time
    +

    + (based on selected epiweek range) +

    + +
    +
    +
    + +
    +
    +
    + + + + + +
    +
    +
    +
    - -
    +
    +
    -
    -
    +
    Influenza Subtypes Detected by Province
    +

    (based on selected epiweek range)

    -
    Total Cases by Provinces
    -

    (based on selected epiweek range)

    - -
    - -
    - - - - SARI - - - - - ILI - - - - - LBM - +
    +
    -
    @@ -117,4 +183,4 @@ -@endsection \ No newline at end of file +@endsection diff --git a/dashboard/resources/views/layouts/app.blade.php b/dashboard/resources/views/layouts/app.blade.php index 3587178..3d28a6c 100644 --- a/dashboard/resources/views/layouts/app.blade.php +++ b/dashboard/resources/views/layouts/app.blade.php @@ -92,7 +92,8 @@ .content-area { padding: 20px; background: #f8f9fa; - min-height: calc(100vh - 60px); + /*min-height: calc(100vh - 60px);*/ + min-height: calc(100vh - 110px); } .brand-logo { @@ -107,9 +108,65 @@ border: 1px solid #E5E7EB; } + .form-select{ + border-radius: 0px !important; + } + + .shadow-sm{ + box-shadow: none !important; + } + .card h3 { color: #0B8F3C; } + + .slide-wrapper { + position: relative; + overflow: hidden; + height: 100%; + min-height: 400px; + } + + .slide { + position: absolute; + width: 100%; + top: 0; + left: 100%; + opacity: 0; + transition: all 0.5s ease-in-out; + } + + .slide.active { + left: 0; + opacity: 1; + z-index: 2; + } + + .slide.prev { + left: -100%; + opacity: 0; + } + + /* Buttons */ + .slide-btn { + position: absolute; + top: 10%; + transform: translateY(-50%); + background: rgba(0, 128, 0, 0.43); + color: white; + border: none; + padding: 8px 15px; + cursor: pointer; + z-index: 10; + } + + .prev-btn { right: 75px; } + .next-btn { right: 25px; } + + .slide-btn:hover { + background: rgba(7, 120, 24, 0.8); + } + @@ -135,31 +192,31 @@ Overview - - @foreach($programs->where('code', '!=', 'NDS') as $program) - - @if($program->code === 'SEQ') - - - SEQ - - @else - - {{ $program->code }} - - @endif - @endforeach +{{-- @foreach($programs->where('code', '!=', 'NDS') as $program)--}} + +{{-- @if($program->code === 'SEQ')--}} + +{{-- --}} +{{-- SEQ--}} +{{-- --}} +{{-- @else--}} +{{-- --}} +{{-- {{ $program->code }}--}} +{{-- --}} +{{-- @endif--}} + +{{-- @endforeach--}}
    -
    @@ -190,4 +247,4 @@ - \ No newline at end of file + diff --git a/dashboard/routes/api.php b/dashboard/routes/api.php index 80f0e6a..fafcf38 100644 --- a/dashboard/routes/api.php +++ b/dashboard/routes/api.php @@ -7,6 +7,10 @@ Route::get('/dashboard/summary', [DashboardController::class, 'summary']); Route::get('/dashboard/trend', [DashboardController::class, 'trend']); Route::get('/dashboard/program', [DashboardController::class, 'program']); Route::get('/dashboard/province-circles', [DashboardController::class, 'provinceCircles']); +Route::get('/dashboard/influenza-subtype-distribution', [DashboardController::class, 'influenzaSubtypeDetected']); +Route::get('/dashboard/covid-distributed-by-age-group', [DashboardController::class, 'covidDistributedByAgeGroup']); +Route::get('/dashboard/covid-lineage-frequency', [DashboardController::class, 'covidLineageRelativeOverTime']); +Route::get('/dashboard/influenza-relative-frequency', [DashboardController::class, 'influenzaRelativeOverTime']); Route::get('/dashboard/sentinel-map', [DashboardController::class, 'sentinelMap']); Route::get('/dashboard/reload', [DashboardController::class, 'fetchSourceData']); Route::get('/dashboard/sequencing', [DashboardController::class, 'sequencing']);