Fix several percentage calculations | Begin adding vaccination data | Add reddit rdaily eport generator for r/Atlanta.
This commit is contained in:
@@ -2,11 +2,13 @@ import fs from 'fs/promises'
|
||||
import fg from 'fast-glob'
|
||||
import path from 'path'
|
||||
import StreamZip from 'node-stream-zip'
|
||||
import XLSX from 'xlsx'
|
||||
import Counties from './parser/counties.js'
|
||||
import OverallTesting from './parser/overall/testing.js'
|
||||
import OverallCases from './parser/overall/cases.js'
|
||||
import OverallHospitalizations from './parser/overall/hospitalizations.js'
|
||||
import OverallDeaths from './parser/overall/deaths.js'
|
||||
import OverallVaccinations from './parser/overall/vaccinations.js'
|
||||
|
||||
import RiskAge from './parser/risk/age.js'
|
||||
import RiskHealthConditions from './parser/risk/health-conditions.js'
|
||||
@@ -14,7 +16,7 @@ import RiskHealthConditions from './parser/risk/health-conditions.js'
|
||||
import Summary from './parser/summary.js'
|
||||
|
||||
async function main() {
|
||||
const sources = await fg(['./data/raw/*.zip'])
|
||||
const sources = await fg(['./data/raw/dph-daily-report/*.zip'])
|
||||
sources.sort()
|
||||
|
||||
const zips = sources.map(source => ({
|
||||
@@ -22,12 +24,19 @@ async function main() {
|
||||
zip: new StreamZip.async({ file: source })
|
||||
}))
|
||||
|
||||
const vaccinations = await fg(['./data/raw/vaccinations/*.xlsx'])
|
||||
const vaccinationSheets = vaccinations.map(source => ({
|
||||
date: path.basename(source, path.extname(source)),
|
||||
xlsx: XLSX.readFile(source)
|
||||
}))
|
||||
|
||||
const counties = await Counties(zips)
|
||||
|
||||
await OverallTesting(zips)
|
||||
await OverallCases(zips, counties)
|
||||
await OverallHospitalizations(zips, counties)
|
||||
await OverallDeaths(zips, counties)
|
||||
await OverallVaccinations(vaccinationSheets, counties)
|
||||
|
||||
await RiskAge(zips)
|
||||
await RiskHealthConditions(zips)
|
||||
|
||||
95
data/parser/overall/vaccinations.js
Normal file
95
data/parser/overall/vaccinations.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import mkdirp from 'mkdirp'
|
||||
import path from 'path'
|
||||
import fsp from 'fs/promises'
|
||||
import XLSX from 'xlsx'
|
||||
import { getCounty } from '../../util.js'
|
||||
|
||||
async function processByCounty({ date, xlsx }, countyInfo, output) {
|
||||
output = {
|
||||
directory: path.join(output.directory, 'by-county'),
|
||||
file: county => `${county}.json`
|
||||
}
|
||||
|
||||
const rows = XLSX.utils.sheet_to_json(xlsx.Sheets.VAX_ADMIN_BY_DAY_COUNTY)
|
||||
|
||||
const counties = rows.reduce((counties, row) => {
|
||||
const county = getCounty(row.COUNTY_NAME)
|
||||
const info = countyInfo[county]
|
||||
if (!county) return counties
|
||||
if (!counties[county]) counties[county] = []
|
||||
counties[county].push({
|
||||
report_date: row.ADMIN_DATE,
|
||||
population: info.population,
|
||||
vaccinations: row.VAXADMIN,
|
||||
total_vaccinations: row.CUMVAXADMIN,
|
||||
})
|
||||
return counties
|
||||
}, {})
|
||||
|
||||
for (const county in counties) {
|
||||
const data = {
|
||||
segment: { county },
|
||||
headers: Object.keys(counties[county][0]),
|
||||
rows: counties[county].map(row => Object.values(row))
|
||||
}
|
||||
|
||||
await mkdirp(output.directory)
|
||||
await fsp.writeFile(path.join(output.directory, output.file(county)), JSON.stringify(data))
|
||||
}
|
||||
}
|
||||
|
||||
async function processCombined({ date, xlsx }, countyInfo, output) {
|
||||
output = {
|
||||
directory: output.directory,
|
||||
file: `combined.json`
|
||||
}
|
||||
|
||||
const rows = XLSX.utils.sheet_to_json(xlsx.Sheets.VAX_ADMIN_BY_DAY_COUNTY)
|
||||
|
||||
const report_dates = new Set()
|
||||
|
||||
const results = rows.map(row => {
|
||||
const county = getCounty(row.COUNTY_NAME)
|
||||
if (!county) return null
|
||||
if (['GEORGIA'].includes(county)) return null
|
||||
|
||||
const info = countyInfo[county]
|
||||
report_dates.add(row['report_date'])
|
||||
return {
|
||||
county,
|
||||
report_date: row.ADMIN_DATE,
|
||||
population: info.population,
|
||||
vaccinations: row.VAXADMIN,
|
||||
total_vaccinations: row.CUMVAXADMIN,
|
||||
}
|
||||
}).filter(row => !!row)
|
||||
|
||||
const data = {
|
||||
segment: { report_date: Array.from(report_dates) },
|
||||
headers: Object.keys(results[0]),
|
||||
rows: results.map(row => Object.values(row))
|
||||
}
|
||||
|
||||
await mkdirp(output.directory)
|
||||
await fsp.writeFile(path.join(output.directory, output.file), JSON.stringify(data))
|
||||
}
|
||||
|
||||
|
||||
async function processSingleWorkbook (xlsx, countyInfo) {
|
||||
const output = {
|
||||
directory: `./public/data/overall/vaccinations/`,
|
||||
}
|
||||
|
||||
try {
|
||||
await fsp.rm(output.directory, { recursive: true })
|
||||
} catch (e) {}
|
||||
|
||||
await processByCounty(xlsx, countyInfo, output)
|
||||
await processCombined(xlsx, countyInfo, output)
|
||||
}
|
||||
|
||||
function process (vaccinationSources, countyInfo) {
|
||||
return processSingleWorkbook(vaccinationSources.at(-1), countyInfo)
|
||||
}
|
||||
|
||||
export default process
|
||||
129
data/parser/reddit/index.js
Normal file
129
data/parser/reddit/index.js
Normal file
@@ -0,0 +1,129 @@
|
||||
|
||||
import mkdirp from 'mkdirp'
|
||||
import path from 'path'
|
||||
import fsp from 'fs/promises'
|
||||
|
||||
function normalize(data) {
|
||||
return data.rows.map(row => {
|
||||
const obj = {}
|
||||
row.forEach((col, i) => {
|
||||
obj[data.headers[i]] = col
|
||||
})
|
||||
|
||||
return obj
|
||||
})
|
||||
}
|
||||
|
||||
function percentChange (current, prev) {
|
||||
const change = current - prev
|
||||
let percent = (change / Math.abs(prev)) * 100
|
||||
|
||||
if (isNaN(percent)) return 0
|
||||
|
||||
const sign = percent > 0 ? '+' : '-'
|
||||
if (Math.abs(percent) === Infinity) percent = 100
|
||||
|
||||
return `${sign}${Math.abs(percent.toFixed(2)).toLocaleString()}`
|
||||
}
|
||||
|
||||
function replaceVariables (template, variables) {
|
||||
Object.keys(variables).forEach(key => {
|
||||
let value = variables[key]
|
||||
if (typeof value === 'number') value = value.toLocaleString()
|
||||
|
||||
template = template.split(`{${key}}`).join(value)
|
||||
})
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
|
||||
async function readJSON(file) {
|
||||
return JSON.parse(await fsp.readFile(file, 'utf-8'))
|
||||
}
|
||||
|
||||
async function process () {
|
||||
const template = await fsp.readFile('./data/parser/reddit/template.md', 'utf-8')
|
||||
|
||||
const data = {
|
||||
tests: normalize(await readJSON('./public/data/overall/testing/by-county/-- All --.json')).reverse(),
|
||||
cases: normalize(await readJSON('./public/data/overall/cases/by-county/-- All --.json')).reverse(),
|
||||
hospitalizations: normalize(await readJSON('./public/data/overall/hospitalizations/by-county/-- All --.json')).reverse(),
|
||||
deaths: normalize(await readJSON('./public/data/overall/deaths/by-county/-- All --.json')).reverse(),
|
||||
vaccinations: normalize(await readJSON('./public/data/overall/vaccinations/by-county/-- All --.json')).reverse(),
|
||||
}
|
||||
|
||||
function formatRow(data, index) {
|
||||
const prevData = {
|
||||
tests: data.tests[index + 1],
|
||||
cases: data.cases[index + 1],
|
||||
hospitalizations: data.hospitalizations[index + 1],
|
||||
deaths: data.deaths[index + 1],
|
||||
vaccinations: data.vaccinations[index + 1],
|
||||
}
|
||||
|
||||
const currentData = {
|
||||
tests: data.tests[index],
|
||||
cases: data.cases[index],
|
||||
hospitalizations: data.hospitalizations[index],
|
||||
deaths: data.deaths[index],
|
||||
vaccinations: data.vaccinations[index],
|
||||
}
|
||||
|
||||
currentData.hospitalizations.total_hospitalizations = data.hospitalizations.reduce((total, r) => {
|
||||
return total + r.hospitalizations
|
||||
}, 0)
|
||||
|
||||
const date = new Date(`${currentData.tests.report_date} 23:59:59`)
|
||||
|
||||
const date_short = `${`${(date.getMonth() + 1)}`.padStart(2, '0')}/${`${date.getDate()}`.padStart(2, '0')}`
|
||||
|
||||
const computedData = {
|
||||
date,
|
||||
date_short,
|
||||
date_long: date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }),
|
||||
current_percent_positive: Math.round(currentData.tests.seven_day_percent_positive),
|
||||
current_tests: currentData.tests.combined_performed_running_total.toLocaleString(),
|
||||
current_cases: currentData.cases.total_cases.toLocaleString(),
|
||||
current_hospitalizations: currentData.hospitalizations.total_hospitalizations.toLocaleString(),
|
||||
current_deaths: currentData.deaths.total_deaths.toLocaleString(),
|
||||
current_vaccinations: currentData.vaccinations.total_vaccinations.toLocaleString(),
|
||||
current_tests_increase: (currentData.tests.combined_performed - prevData.tests.combined_performed).toLocaleString(),
|
||||
current_cases_increase: currentData.cases.cases.toLocaleString(),
|
||||
current_hospitalizations_increase: currentData.hospitalizations.hospitalizations.toLocaleString(),
|
||||
current_deaths_increase: currentData.deaths.deaths.toLocaleString(),
|
||||
current_vaccinations_increase: currentData.vaccinations.vaccinations.toLocaleString(),
|
||||
current_tests_change_percent: percentChange(currentData.tests.combined_performed, prevData.tests.combined_performed),
|
||||
current_cases_change_percent: percentChange(currentData.cases.cases, prevData.cases.cases),
|
||||
current_hospitalizations_change_percent: percentChange(currentData.hospitalizations.hospitalizations, prevData.hospitalizations.hospitalizations),
|
||||
current_deaths_change_percent: percentChange(currentData.deaths.deaths, prevData.deaths.deaths),
|
||||
current_vaccinations_change_percent: percentChange(currentData.vaccinations.vaccinations, prevData.vaccinations.vaccinations),
|
||||
}
|
||||
|
||||
return computedData
|
||||
}
|
||||
|
||||
const results = []
|
||||
|
||||
for (let i = 0; i <= 30; i++) {
|
||||
results.push(formatRow(data, i))
|
||||
}
|
||||
|
||||
const variables = {
|
||||
...results.at(0),
|
||||
table_rows: results.map(row => {
|
||||
return `| ${[
|
||||
row.date_short,
|
||||
`${row.current_percent_positive}%`,
|
||||
`${row.current_cases_increase} [${row.current_cases_change_percent}%]`,
|
||||
`${row.current_hospitalizations_increase} [${row.current_hospitalizations_change_percent}%]`,
|
||||
`${row.current_deaths_increase} [${row.current_deaths_change_percent}%]`,
|
||||
`${row.current_vaccinations_increase} [${row.current_vaccinations_change_percent}%]`,
|
||||
].join(' | ')} |`
|
||||
}).join('\n')
|
||||
}
|
||||
|
||||
console.log(replaceVariables(template, variables))
|
||||
}
|
||||
|
||||
process()
|
||||
54
data/parser/reddit/template.md
Normal file
54
data/parser/reddit/template.md
Normal file
@@ -0,0 +1,54 @@
|
||||
**{date_long} Case + Vaccine Update**
|
||||
|
||||
This post is an attempt to accurately represent a few helpful statistics provided by the Georgia Department of Public Health (DPH) at the time of posting.
|
||||
|
||||
---------------------
|
||||
|
||||
Total Tests (PCR + Antigen): {current_tests}
|
||||
|
||||
Tests Reported Today: {current_tests_increase} ({current_tests_change_percent}% day-over-day)
|
||||
|
||||
7-Day Percent Positive: {current_percent_positive}%
|
||||
|
||||
-------------------
|
||||
|
||||
Total Confirmed Cases (PCR + Antigen): {current_cases}
|
||||
|
||||
Cases Reported Today: {current_cases_increase} ({current_cases_change_percent}% day-over-day)
|
||||
|
||||
---------------------
|
||||
|
||||
Total Hospitalizations: {current_hospitalizations}
|
||||
|
||||
Hospitalized Today: {current_hospitalizations_increase} ({current_hospitalizations_change_percent}% day-over-day)
|
||||
|
||||
---------------------
|
||||
|
||||
Total Deaths: {current_deaths}
|
||||
|
||||
Deaths Reported Today: {current_deaths_increase} ({current_deaths_change_percent}% day-over-day)
|
||||
|
||||
---------------------
|
||||
|
||||
Total Vaccine Doses Administered: {current_vaccinations}
|
||||
|
||||
Vaccine Doses Reported Today: {current_vaccinations_increase} ({current_vaccinations_change_percent}% day-over-day)
|
||||
|
||||
---------------------
|
||||
|
||||
**Each column below contains the following space-separated values:**
|
||||
|
||||
Contents of New Cases, New Hospitalizations, New Deaths, and New Vaccine Doses columns:
|
||||
- The day-over-day increase [The day-over-day change as a percent of the previous day]
|
||||
|
||||
| Date | Test % Positive | New Cases | New Hospitalizations | New Deaths | New Vaccine Doses |
|
||||
| ---- | --------------- | --------- | -------------------- | ---------- | ----------------- |
|
||||
{table_rows}
|
||||
|
||||
Final data for December 2021, with links to posts starting in March 2020 courtesy of /u/diemunkiesdie can be found here: https://www.reddit.com/r/Atlanta/comments/rpkf94/comment/hqq24ct/
|
||||
|
||||
---------------------
|
||||
|
||||
**Sourced from today's Georgia DPH COVID-19 Report and Vaccination Data:**
|
||||
- https://dph.georgia.gov/covid-19-daily-status-report
|
||||
- https://experience.arcgis.com/experience/3d8eea39f5c1443db1743a4cb8948a9c
|
||||
BIN
data/raw/vaccinations/2022-01-04.xlsx
Normal file
BIN
data/raw/vaccinations/2022-01-04.xlsx
Normal file
Binary file not shown.
@@ -1,5 +1,6 @@
|
||||
export function getCounty (county) {
|
||||
if (county === 'Georgia') return '-- All --'
|
||||
if (!county) return county
|
||||
if (county.toLowerCase() === 'georgia') return '-- All --'
|
||||
if (county === 'Non-GA Resident/Unknown State') return '-- Unknown --'
|
||||
return county
|
||||
return county.split('County').join('').split('county').join('').trim()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user