diff --git a/src/chartConfig/index.js b/src/chartConfig/index.js index 0e1711b..19177f8 100644 --- a/src/chartConfig/index.js +++ b/src/chartConfig/index.js @@ -30,6 +30,14 @@ export const ontarioStatusCharts = [ icu, ]; +export const phuStatusCharts = [ + activeCases, + totalCases, + newCases, + totalDeaths, + newDeaths, +]; + export const vaccineCharts = [ dailyDoses, totalDoses, diff --git a/src/chartConfig/ontario/hospitalized.js b/src/chartConfig/ontario/hospitalized.js index 7cd0f06..64fbe93 100644 --- a/src/chartConfig/ontario/hospitalized.js +++ b/src/chartConfig/ontario/hospitalized.js @@ -2,7 +2,7 @@ const hospitalized = { title: 'Patients hospitalized', dataKeyX: 'date_string', bars: [{ - dataKey: 'Number of patients hospitalized with COVID-19', + dataKey: 'total_hospital', name: 'Patients hospitalized', fill: '#ef8c8c', }], diff --git a/src/chartConfig/ontario/icu.js b/src/chartConfig/ontario/icu.js index 635b99c..6910def 100644 --- a/src/chartConfig/ontario/icu.js +++ b/src/chartConfig/ontario/icu.js @@ -2,7 +2,7 @@ const icu = { title: 'Patients in ICU', dataKeyX: 'date_string', bars: [{ - dataKey: 'Number of patients in ICU on a ventilator due to COVID-19', + dataKey: 'total_vent', fill: '#509ee3', name: 'ICU (with ventilator)', stackId: 'a' diff --git a/src/chartConfig/ontario/newCases.js b/src/chartConfig/ontario/newCases.js index 32d95b3..b545625 100644 --- a/src/chartConfig/ontario/newCases.js +++ b/src/chartConfig/ontario/newCases.js @@ -7,7 +7,7 @@ const newCases = { fill: '#f9d45c', }], lines: [{ - dataKey: 'new_cases_rolling_average', + dataKey: 'avg_new_cases', name: '7 day rolling average', stroke: 'black', strokeWidth: 2, diff --git a/src/chartConfig/ontario/newDeaths.js b/src/chartConfig/ontario/newDeaths.js index ce64046..ce98c09 100644 --- a/src/chartConfig/ontario/newDeaths.js +++ b/src/chartConfig/ontario/newDeaths.js @@ -7,7 +7,7 @@ const newDeaths = { fill: '#ef8c8c', }], lines: [{ - dataKey: 'new_deaths_rolling_average', + dataKey: 'avg_new_deaths', name: '7 day rolling average', stroke: 'black', strokeWidth: 2, diff --git a/src/chartConfig/ontario/positiveRate.js b/src/chartConfig/ontario/positiveRate.js index 9ed39d7..e97c024 100644 --- a/src/chartConfig/ontario/positiveRate.js +++ b/src/chartConfig/ontario/positiveRate.js @@ -2,12 +2,12 @@ const positiveRate = { title: 'Percent positive', dataKeyX: 'date_string', bars: [{ - dataKey: 'percent_positive', + dataKey: 'new_percent_pos', name: 'Percent positive', fill: '#509ee3', }], lines: [ { - dataKey: 'tests_positive_rolling_average', + dataKey: 'avg_percent_pos', name: '7 day rolling average', stroke: 'black', strokeWidth: 2, diff --git a/src/chartConfig/ontario/tests.js b/src/chartConfig/ontario/tests.js index b9e4c19..fca7b90 100644 --- a/src/chartConfig/ontario/tests.js +++ b/src/chartConfig/ontario/tests.js @@ -2,18 +2,18 @@ const tests = { title: 'New tests', dataKeyX: 'date_string', bars: [{ - dataKey: 'Total tests completed in the last day', + dataKey: 'new_tests', name: 'New tests', fill: '#509ee3', }], lines: [{ - dataKey: 'Under Investigation', + dataKey: 'under_investigation', name: 'Pending tests', dot: false, strokeWidth: 2, stroke: 'teal', }, { - dataKey: 'tests_rolling_average', + dataKey: 'avg_new_tests', name: '7 day rolling average', stroke: 'black', strokeWidth: 2, diff --git a/src/chartConfig/ontario/totalCases.js b/src/chartConfig/ontario/totalCases.js index 69dfa55..139e648 100644 --- a/src/chartConfig/ontario/totalCases.js +++ b/src/chartConfig/ontario/totalCases.js @@ -2,13 +2,13 @@ const totalCases = { title: 'Total cases', dataKeyX: 'date_string', areas: [{ - dataKey: 'vocsTotal', + dataKey: 'total_voc', fill: '#ef8c8c', stroke: '#ef8c8c', name: 'Variants of concern', stackId: 'a', }, { - dataKey: 'totalNonVOC', + dataKey: 'total_non_voc', fill: '#f9d45c', stroke: '#f9d45c', name: 'Vanilla cases', diff --git a/src/chartConfig/ontario/totalDeaths.js b/src/chartConfig/ontario/totalDeaths.js index 2c126d7..6f51002 100644 --- a/src/chartConfig/ontario/totalDeaths.js +++ b/src/chartConfig/ontario/totalDeaths.js @@ -2,7 +2,7 @@ const totalDeaths = { title: 'Total deaths', dataKeyX: 'date_string', areas: [{ - dataKey: 'Deaths', + dataKey: 'total_deaths', name: 'Total deaths', fill: '#ef8c8c', }], diff --git a/src/components/OntarioStatusTable.js b/src/components/OntarioStatusTable.js index 53b518b..0313956 100644 --- a/src/components/OntarioStatusTable.js +++ b/src/components/OntarioStatusTable.js @@ -1,6 +1,3 @@ -const hospitalField = 'Number of patients hospitalized with COVID-19'; -const icuField = 'Number of patients in ICU due to COVID-19'; - import DataTable from './DataTable'; const formatNumber = val => { @@ -29,7 +26,7 @@ const columns = [{ highlight: 'negative', }, { label: 'Total cases', - key: 'Total Cases', + key: 'total_cases', formatValue: formatNumber, align: 'right', headerColSpan: 2, @@ -40,7 +37,7 @@ const columns = [{ highlight: 'negative', }, { label: 'Deaths', - key: 'Deaths', + key: 'total_deaths', formatValue: formatNumber, align: 'right', headerColSpan: 2, @@ -51,7 +48,7 @@ const columns = [{ highlight: 'negative', }, { label: 'Patients hospitalized', - key: hospitalField, + key: 'total_hospital', formatValue: formatNumber, align: 'right', headerColSpan: 2, @@ -62,7 +59,7 @@ const columns = [{ highlight: true, }, { label: 'Patients in ICU', - key: icuField, + key: 'total_icu', formatValue: formatNumber, align: 'right', headerColSpan: 2, @@ -73,7 +70,7 @@ const columns = [{ highlight: 'negative', }, { label: 'Variants of concern cases', - key: 'vocsTotal', + key: 'total_voc', formatValue: formatNumber, }]; diff --git a/src/components/PHUStatusTable.js b/src/components/PHUStatusTable.js index 1128ee1..6b0f79d 100644 --- a/src/components/PHUStatusTable.js +++ b/src/components/PHUStatusTable.js @@ -12,21 +12,37 @@ const PHUStatusTable = ({ dataSource = [], phuName }) => { key: 'date_string', }, { label: 'Active cases', - key: 'ACTIVE_CASES', + key: 'active_cases', formatValue: formatNumber, + align: 'right', + headerColSpan: 2, }, { - label: 'New Cases', - key: 'new_cases', + label: '', + key: 'new_active_cases', formatValue: formatDelta, highlight: 'negative', }, { label: 'Total cases', key: 'total_cases', formatValue: formatNumber, + align: 'right', + headerColSpan: 2, + }, { + label: '', + key: 'new_cases', + formatValue: formatDelta, + highlight: 'negative', }, { label: 'Deaths', - key: 'DEATHS', + key: 'total_deaths', formatValue: formatNumber, + align: 'right', + headerColSpan: 2, + }, { + label: '', + key: 'new_deaths', + formatValue: formatDelta, + highlight: 'negative', }]; return ( diff --git a/src/data/getOntarioStatuses.js b/src/data/getOntarioStatuses.js index ee2e702..d16dcfa 100644 --- a/src/data/getOntarioStatuses.js +++ b/src/data/getOntarioStatuses.js @@ -1,76 +1,12 @@ import jsonpFetch from './jsonpFetch'; +import { processOntarioRecords } from './utils'; const dataUrl = 'https://data.ontario.ca/api/3/action/datastore_search?resource_id=ed270bb8-340b-41f9-a7c6-e8ef587e6d11&limit=1000'; -const hospitalField = 'Number of patients hospitalized with COVID-19'; -const icuField = 'Number of patients in ICU due to COVID-19'; const getOntarioStatuses = () => { return new Promise((resolve) => { - jsonpFetch(dataUrl, function(data) { - const records = data.result.records; - records.sort((a,b) => new Date(a['Reported Date']) - new Date(b['Reported Date'])); - - let cases_last7days = [0, 0, 0, 0, 0, 0, 0]; - let deaths_last7days = [0, 0, 0, 0, 0, 0, 0]; - let tests_last7days = [0, 0, 0, 0, 0, 0, 0]; - let percentTestsPositive_last7days = [0, 0, 0, 0, 0, 0, 0]; - let yesterdayCases = 0; - let yesterdayDeaths = 0; - let yesterdayActiveCases = 0; - let yesterdayResolvedCases = 0; - let yesterdayHospital = 0; - let yesterdayIcu = 0; - - records.map(record => { - record.new_cases = record['Total Cases'] - yesterdayCases; - record.new_deaths = Math.max(record['Deaths'] - yesterdayDeaths, 0); - record.icu_no_ventilator = record[icuField] - record['Number of patients in ICU on a ventilator due to COVID-19']; - record.date_string = new Date(record['Reported Date']).toLocaleString('en-us', { - month: 'short', - day: 'numeric', - }); - record.active_cases = record['Total Cases'] - record['Resolved'] - record['Deaths']; - record.new_active_cases = record.active_cases - yesterdayActiveCases; - record.new_resolved = record['Resolved'] - yesterdayResolvedCases; - record.new_hospital = record[hospitalField] - yesterdayHospital; - record.new_icu = record[icuField] - yesterdayIcu; - record.percent_positive = ((record['new_cases'] / record['Total tests completed in the last day']) * 100).toFixed(2); - if (record.percent_positive > 100) { - record.percent_positive = 0; - } else if (isNaN(record.percent_positive)) { - record.percent_positive = 0; - } - - percentTestsPositive_last7days.shift(); - percentTestsPositive_last7days.push(Number(record.percent_positive || 0)); - record.tests_positive_rolling_average = (percentTestsPositive_last7days.reduce((total, cases) => cases + total, 0) / 7.0).toFixed(2); - - tests_last7days.shift(); - tests_last7days.push(record['Total tests completed in the last day'] || 0); - record.tests_rolling_average = Math.round(tests_last7days.reduce((total, cases) => cases + total, 0) / 7.0); - - cases_last7days.shift(); - cases_last7days.push(record.new_cases); - const total_cases_last7days = cases_last7days.reduce((total, cases) => cases + total, 0); - record.new_cases_rolling_average = Math.round(total_cases_last7days / 7); - - deaths_last7days.shift(); - deaths_last7days.push(record.new_deaths); - const total_deaths_last7days = deaths_last7days.reduce((total, cases) => cases + total, 0); - record.new_deaths_rolling_average = Math.round(total_deaths_last7days / 7); - - yesterdayIcu = record[icuField]; - yesterdayHospital = record[hospitalField]; - yesterdayCases = record['Total Cases']; - yesterdayDeaths = record['Deaths']; - yesterdayResolvedCases = record['Resolved']; - yesterdayActiveCases = record['active_cases']; - - record.vocsTotal = record['Total_Lineage_B.1.1.7'] + record['Total_Lineage_B.1.351'] + record['Total_Lineage_P.1']; - record.totalNonVOC = record['Total Cases'] - record.vocsTotal; - return record; - }); - resolve(records); + jsonpFetch(dataUrl, ({ result }) => { + resolve(processOntarioRecords(result.records)); }); }); }; diff --git a/src/data/getPHUData.js b/src/data/getPHUData.js index 023fe53..599072c 100644 --- a/src/data/getPHUData.js +++ b/src/data/getPHUData.js @@ -1,4 +1,5 @@ import jsonpFetch from './jsonpFetch'; +import { processPHURecords } from './utils'; const dataUrl = 'https://data.ontario.ca/en/api/3/action/datastore_search?resource_id=d1bfe1ad-6575-4352-8302-09ca81f7ddfc&limit=10000'; @@ -9,32 +10,11 @@ const getPHUdata = (phuName = '')=> { return new Promise((resolve) => { jsonpFetch(url, ({ result }) => { - const rawRecords = result.records; - rawRecords.sort((a, b) => new Date(a.FILE_DATE) - new Date(b.FILE_DATE)); - const deDupedRecords = rawRecords.filter((record, index, array) => ( - index === array.findIndex(other => record.FILE_DATE === other.FILE_DATE) - )); - - let yesterdayTotalCases = 0; - let cases_last7days = [0, 0, 0, 0, 0, 0, 0]; - const records = deDupedRecords.map(record => { - record.total_cases = record.ACTIVE_CASES + record.DEATHS + record.RESOLVED_CASES; - record.new_cases = Math.max(0, record.total_cases - yesterdayTotalCases); - record.date_string = new Date(record.FILE_DATE).toLocaleString('en-us', { - month: 'short', - day: 'numeric', - }); - - cases_last7days.shift(); - cases_last7days.push(record.new_cases); - const total_cases_last7days = cases_last7days.reduce((total, cases) => cases + total, 0); - record.new_cases_rolling_average = Math.round(total_cases_last7days / 7); - - yesterdayTotalCases = record['total_cases']; - return record; - }); - - resolve(records); + resolve(processPHURecords(result.records.filter( + (record, index, array) => ( + index === array.findIndex(other => record.FILE_DATE === other.FILE_DATE) + ) + ))); }); }); }; diff --git a/src/data/utils.js b/src/data/utils.js new file mode 100644 index 0000000..6416744 --- /dev/null +++ b/src/data/utils.js @@ -0,0 +1,146 @@ +const ZERO_RECORD = { + active_cases: 0, + resolved_cases: 0, + total_cases: 0, + total_deaths: 0, + new_tests: 0, + under_investigation: 0, + new_percent_pos: 0, + total_hospital: 0, + total_icu: 0, + pos_icu: 0, + neg_icu: 0, + total_vent: 0, + pos_vent: 0, + neg_vent: 0, + total_ltc: 0, + total_ltc_hcw: 0, + total_ltc_deaths: 0, + total_ltc_hcw_deaths: 0, + total_b117: 0, + total_b1351: 0, + total_p1: 0 +}; + +const normalizePhuRecord = (record) => { + const active_cases = record.ACTIVE_CASES; + const resolved_cases = record.RESOLVED_CASES; + const total_deaths = record.DEATHS; + + return { + ...ZERO_RECORD, + date: record.FILE_DATE, + active_cases, + resolved_cases, + total_cases: active_cases + resolved_cases + total_deaths, + total_deaths + }; +}; + +const normalizeOntarioRecord = (record) => { + const resolved_cases = record['Resolved']; + const total_cases = record['Total Cases']; + const total_deaths = record['Deaths']; + const new_tests = record['Total tests completed in the last day']; + const under_investigation = record['Under Investigation']; + const new_percent_pos = record['Percent positive tests in last day']; + const total_hospital = record['Number of patients hospitalized with COVID-19']; + const total_icu = record['Number of patients in ICU due to COVID-19']; + const pos_icu = record['Number of patients in ICU,testing positive for COVID-19']; + const neg_icu = record['Number of patients in ICU,testing negative for COVID-19']; + const total_vent = record['Number of patients in ICU on a ventilator due to COVID-19']; + const pos_vent = record['Num. of patients in ICU on a ventilator testing positive']; + const neg_vent = ['Num. of patients in ICU on a ventilator testing negative']; + const total_ltc = record['Total Positive LTC Resident Cases']; + const total_ltc_hcw = record['Total Positive LTC HCW Cases']; + const total_ltc_deaths = record['Total LTC Resident Deaths']; + const total_ltc_hcw_deaths = record['Total LTC HCW Deaths']; + const total_b117 = record['Total_Lineage_B.1.1.7']; + const total_b1351 = record['Total_Lineage_B.1.351']; + const total_p1 = record['Total_Lineage_P.1']; + + return { + date: record['Reported Date'], + active_cases: total_cases - resolved_cases - total_deaths, + resolved_cases, + total_cases, + total_deaths, + new_tests, + under_investigation, + new_percent_pos, + total_hospital, + total_icu, + pos_icu, + neg_icu, + total_vent, + pos_vent, + neg_vent, + total_ltc, + total_ltc_hcw, + total_ltc_deaths, + total_ltc_hcw_deaths, + total_b117, + total_b1351, + total_p1 + }; +}; + +const addCalculatedFields = record => ({ + ...record, + date_string: new Date(record.date).toLocaleString('en-us', { month: 'short', day: 'numeric' }), + icu_no_ventilator: record.total_icu - record.total_vent, + total_voc: record.total_b117 + record.total_b1351 + record.total_p1, + total_non_voc: record.total_cases - (record.total_b117 + record.total_b1351 + record.total_p1), +}); + +const sortRecords = (a, b) => new Date(a.date) - new Date(b.date); + +const calculateDeltas = (record, index, array) => { + const yesterday = index ? array[index - 1] : ZERO_RECORD; + + return { + ...record, + new_cases: Math.max(0, record.total_cases - yesterday.total_cases), + new_deaths: Math.max(0, record.total_deaths - yesterday.total_deaths), + new_active_cases: Math.max(0, record.active_cases - yesterday.active_cases), + new_resolved: Math.max(0, record.resolved_cases - yesterday.resolved_cases), + new_hospital: Math.max(0, record.total_hospital - yesterday.total_hospital), + new_icu: Math.max(0, record.total_icu - yesterday.total_icu), + }; +}; + +const calculateAverages = (record, index, array) => { + const last7days = array.slice(Math.max(0, index - 6), Math.min(index + 1, array.length)); + + return { + ...record, + avg_new_cases: Math.round(last7days.reduce((total, record) => total + record.new_cases, 0) / last7days.length), + avg_new_deaths: Math.round(last7days.reduce((total, record) => total + record.new_deaths, 0) / last7days.length), + avg_new_tests: Math.round(last7days.reduce((total, record) => total + record.new_tests, 0) / last7days.length), + avg_percent_pos: last7days.reduce((total, record) => total + record.new_percent_pos, 0) / last7days.length, + }; +}; + +export const processOntarioRecords = (records) => ( + records + .map(rec => + addCalculatedFields( + normalizeOntarioRecord(rec) + ) + ) + .sort(sortRecords) + .map(calculateDeltas) + .map(calculateAverages) +); + +export const processPHURecords = (records) => ( + records + .map(rec => + addCalculatedFields( + normalizePhuRecord(rec) + ) + ) + .sort(sortRecords) + .map(calculateDeltas) + .map(calculateAverages) +); \ No newline at end of file diff --git a/src/pages/phus.js b/src/pages/phus.js index 22cb5d9..c22e477 100644 --- a/src/pages/phus.js +++ b/src/pages/phus.js @@ -9,6 +9,8 @@ import ChartContainer from '../components/ChartContainer'; import PHUStatusTable from '../components/PHUStatusTable'; import LayoutSimple from '../components/LayoutSimple'; +import { phuStatusCharts } from '../chartConfig'; + const PHUContainer = () => { const [name, setName] = useState('TORONTO'); @@ -58,61 +60,14 @@ const PHUContainer = () => { ) : ( - - - ( + - + ))} ) )}