Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b37ef9a
Optimize CDC air quality imports with sharding and scaled compute
abhishekjaisw Aug 31, 2026
4ebc67e
Fix county sharding logic: initialize headers for all shards and prev…
abhishekjaisw Aug 31, 2026
5a7f855
Apply yapf Google code formatting to scripts/us_cdc/environmental_hea…
abhishekjaisw Aug 31, 2026
30e7938
Add automatic test artifact cleanup in parse_air_quality_test tearDown
abhishekjaisw Aug 31, 2026
4e0cd49
Remove redundant config_override block from manifest.json
abhishekjaisw Aug 31, 2026
1363a5f
Update README.md with sharded tMCF files and architecture documentation
abhishekjaisw Aug 31, 2026
2ac44d2
Merge branch 'master' into cdc_air_quality_sharding_and_scaling
abhishekjaisw Sep 1, 2026
c43b1d1
Disable in-memory differ for census tract imports to prevent OOM on s…
abhishekjaisw Sep 2, 2026
46e4e57
Consolidate PM25County TMCFs to single template and streamline valida…
abhishekjaisw Sep 2, 2026
4740ef3
Revert description in validation_config.json to standard form (detail…
abhishekjaisw Sep 2, 2026
416872d
Merge branch 'master' into cdc_air_quality_sharding_and_scaling
abhishekjaisw Sep 2, 2026
681ad34
fix(cdc): remove validation_config_file from census tract imports whe…
abhishekjaisw Sep 3, 2026
b910cff
Merge branch 'master' into cdc_air_quality_sharding_and_scaling
abhishekjaisw Sep 3, 2026
ab963d1
fix(cdc): address code review findings for timeout, date parsing, and…
abhishekjaisw Sep 3, 2026
53d7457
fix(cdc): remove check_max_date_consistent rule from validation_confi…
abhishekjaisw Sep 3, 2026
dd29405
style(cdc): format download_files.py with yapf Google style
abhishekjaisw Sep 3, 2026
8f981e6
Merge branch 'master' into cdc_air_quality_sharding_and_scaling
abhishekjaisw Sep 4, 2026
06808e9
fix(cdc): resolve adversarial review findings for download error hand…
abhishekjaisw Sep 4, 2026
87ac660
fix(cdc): restore check_max_date_consistent rule in validation_config…
abhishekjaisw Sep 4, 2026
288973a
style(cdc): format with yapf --style=google to satisfy CI lint check
abhishekjaisw Sep 4, 2026
44b3c59
feat(cdc): add separate validation configs for Census Tract and Count…
abhishekjaisw Sep 4, 2026
42a34d0
chore(cdc): clean up redundant assignment and remove superseded valid…
abhishekjaisw Sep 4, 2026
aaf3f00
fix(cdc): address code review findings for robustness, memory streami…
abhishekjaisw Sep 4, 2026
1e38700
fix(cdc): resolve P1/P2/P3 review items (exception raise, argv[1], ze…
abhishekjaisw Sep 7, 2026
a82dffe
Merge branch 'master' into cdc_air_quality_sharding_and_scaling
abhishekjaisw Sep 7, 2026
3519271
Merge branch 'master' into cdc_air_quality_sharding_and_scaling
abhishekjaisw Sep 8, 2026
d48c6c2
fix(us_cdc/environmental_health_toxicology): remove check_lint_error_…
abhishekjaisw Sep 8, 2026
125ac4b
Merge branch 'master' into cdc_air_quality_sharding_and_scaling
abhishekjaisw Sep 8, 2026
23177f0
fix(us_cdc/environmental_health_toxicology): address reviewer feedbac…
abhishekjaisw Sep 11, 2026
039e81d
fix(us_cdc/environmental_health_toxicology): address reviewer nits on…
abhishekjaisw Sep 11, 2026
748a7b2
ci(us_cdc): trigger CI retry after Cloud Build infrastructure error
abhishekjaisw Sep 11, 2026
7cee4e7
Merge branch 'master' into cdc_air_quality_sharding_and_scaling
abhishekjaisw Sep 21, 2026
898a1e6
Address PR #2193 review comments in download_files.py and parse_air_q…
abhishekjaisw Sep 21, 2026
f241d79
Merge remote-tracking branch 'origin/cdc_air_quality_sharding_and_sca…
abhishekjaisw Sep 21, 2026
821126a
Merge branch 'master' into cdc_air_quality_sharding_and_scaling
abhishekjaisw Sep 22, 2026
da6bad2
Merge branch 'master' into cdc_air_quality_sharding_and_scaling
abhishekjaisw Sep 22, 2026
148339f
Merge branch 'master' into cdc_air_quality_sharding_and_scaling
abhishekjaisw Sep 22, 2026
f69fcac
Merge branch 'master' into cdc_air_quality_sharding_and_scaling
abhishekjaisw Sep 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ variableMeasured: dcs:Mean_Concentration_AirPollutant_PM2.5
Node: E:PM25CountyPollution->E2
observationAbout: C:PM25CountyPollution->dcid
typeOf: dcs:StatVarObservation
observationDate: C:PPM25CountyPollution->date
observationDate: C:PM25CountyPollution->date
value: C:PM25CountyPollution->pm25_med_pred
observationPeriod: "P24H"
unit: MicrogramsPerCubicMeter
Expand Down
20 changes: 7 additions & 13 deletions scripts/us_cdc/environmental_health_toxicology/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,13 @@ These data were collected as part of the [CDC National Environment Public Health
[`small_Palmer_expected.csv`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/test_data/small_Palmer_expected.csv)

#### tMCFs
[`OzoneCensusTractPollution.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/OzoneCensusTractPollution.tmcf)

[`OzoneCountyPollution.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/OzoneCountyPollution.tmcf)

[`PalmerDroughtSeverityIndex.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/PalmerDroughtSeverityIndex.tmcf)

[`PM25CensusTractPollution.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/PM25CensusTractPollution.tmcf)

[`PM25CountyPollution.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/PM25CountyPollution.tmcf)

[`StandardizedPrecipitationEvapotranspirationIndex.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/StandardizedPrecipitationEvapotranspirationIndex.tmcf)

[`StandardizedPrecipitationIndex.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/StandardizedPrecipitationIndex.tmcf)
* [`OzoneCountyPollution.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/OzoneCountyPollution.tmcf)
* [`PM25CountyPollution.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/PM25CountyPollution.tmcf)
* **Sharded PM2.5 Census Tract tMCFs:** [`PM25CensusTractPollution_part1.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/PM25CensusTractPollution_part1.tmcf), [`part2`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/PM25CensusTractPollution_part2.tmcf), [`part3`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/PM25CensusTractPollution_part3.tmcf), [`part4`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/PM25CensusTractPollution_part4.tmcf)
* **Sharded Ozone Census Tract tMCFs:** [`OzoneCensusTractPollution_part1.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/OzoneCensusTractPollution_part1.tmcf), [`part2`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/OzoneCensusTractPollution_part2.tmcf), [`part3`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/OzoneCensusTractPollution_part3.tmcf), [`part4`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/OzoneCensusTractPollution_part4.tmcf)
* [`PalmerDroughtSeverityIndex.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/PalmerDroughtSeverityIndex.tmcf)
* [`StandardizedPrecipitationEvapotranspirationIndex.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/StandardizedPrecipitationEvapotranspirationIndex.tmcf)
* [`StandardizedPrecipitationIndex.tmcf`](https://github.com/datacommonsorg/data/blob/master/scripts/us_cdc/environmental_health_toxicology/StandardizedPrecipitationIndex.tmcf)

### Import Procedure

Expand Down
110 changes: 74 additions & 36 deletions scripts/us_cdc/environmental_health_toxicology/download_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json, os, requests, sys
import json
import os
from pathlib import Path
from absl import app, logging, flags
import shutil
import sys

from absl import app
from absl import flags
from absl import logging
import requests
from retry import retry

_FLAGS = flags.FLAGS
Expand All @@ -36,60 +43,91 @@ def download_files(importname, configs):
@retry(tries=3, delay=2, backoff=2)
def download_with_retry(url, input_file_name):
logging.info(f"Downloading file from URL: {url}")
response = requests.get(url)
response.raise_for_status()
if response.status_code == 200:
if not response.content:
logging.fatal(
f"No data available for URL: {url}. Aborting download.")
return
filename = os.path.join(_INPUT_FILE_PATH, input_file_name)
with file_util.FileIO(filename, 'wb') as f:
f.write(response.content)
else:
filename = os.path.join(_INPUT_FILE_PATH, input_file_name)
tmp_filename = f"{filename}.tmp"
try:
with requests.get(url, stream=True, timeout=(30, 300)) as response:
response.raise_for_status()
with open(tmp_filename, 'wb') as f:
for chunk in response.iter_content(chunk_size=16 * 1024 *
1024):
if chunk:
f.write(chunk)
if not os.path.exists(tmp_filename) or os.path.getsize(
tmp_filename) <= 0:
raise IOError(
f"Downloaded file {tmp_filename} is empty or missing.")
shutil.move(tmp_filename, filename)
logging.info(f"Successfully saved {filename} from URL {url} "
f"({os.path.getsize(filename)} bytes)")
except Exception as e:
logging.error(
f"Failed to download file from URL: {url}. Status code: {response.status_code}"
)
f"Failed to download {input_file_name} from URL {url}: {e}")
if os.path.exists(tmp_filename):
try:
os.remove(tmp_filename)
except OSError:
pass
raise e
Comment thread
abhishekjaisw marked this conversation as resolved.

@retry(tries=3, delay=2, backoff=2)
def get_record_count_with_retry(count_url):
logging.info(f"Querying record count from URL: {count_url}")
resp = requests.get(count_url, timeout=60)
resp.raise_for_status()
return json.loads(resp.text)[0]['COLUMN_ALIAS_GUARD__count']

url_new = None
import_found = False
try:
for config in configs:
if config["import_name"] == importname:
import_found = True
files = config["files"]
for file_info in files:
url_new = file_info["url"]
logging.info(f"URL from config file {url_new}")
input_file_name = file_info["input_file_name"]
logging.info(f"Input File Name {input_file_name}")

get_record_count = requests.get(
url_new.replace('.csv', record_count_query))
if get_record_count.status_code == 200:
record_count = json.loads(
get_record_count.text
)[0]['COLUMN_ALIAS_GUARD__count']
logging.info(
f"Numbers of records found for the URL {url_new} is {record_count}"
)
url_new = f"{url_new}?$limit={record_count}&$offset=0"
download_with_retry(url_new, input_file_name)
logging.info(
"Successfully downloaded the source data...!!!!")
else:
logging.error(
f"Failed to download files, Status code: {get_record_count.status_code}"
)
count_url = url_new.replace('.csv', record_count_query)
record_count = int(get_record_count_with_retry(count_url))
if record_count <= 0:
raise ValueError(
f"Invalid record count ({record_count}) returned "
f"for URL: {url_new}")
logging.info(
f"Numbers of records found for the URL {url_new} is "
f"{record_count}")
# Socrata API endpoints apply a default row limit (1,000)
# when $limit is omitted. Passing explicit $limit ensures
# the full dataset is downloaded.
# TODO: Evaluate incremental refresh by filtering latest
# years via Socrata $where queries once baseline is split.
url_new = f"{url_new}?$limit={record_count}&$offset=0"
Comment thread
abhishekjaisw marked this conversation as resolved.
download_with_retry(url_new, input_file_name)
logging.info(
f"Successfully downloaded the source data from URL: "
f"{url_new}")
if not import_found:
raise ValueError(
f"Import name '{importname}' not found in configuration")

except Exception as e:
logging.fatal(f"Error downloading URL {url_new} - {e}")
logging.fatal(f"Error downloading URL {url_new or 'unknown'} - {e}")


def main(_):
def main(argv):
"""Main function to download the csv files."""
if len(argv) < 2:
logging.fatal(
"Missing import name argument. Usage: download_files.py <import_name>"
)
return
global _INPUT_FILE_PATH
_INPUT_FILE_PATH = os.path.join(_FLAGS.input_file_path)
_INPUT_FILE_PATH = os.path.join(_MODULE_DIR, _FLAGS.input_file_path)
Path(_INPUT_FILE_PATH).mkdir(parents=True, exist_ok=True)
importname = sys.argv[1]
importname = argv[1]
logging.info(f'Loading config: {_FLAGS.config_file}')
with file_util.FileIO(_FLAGS.config_file, 'r') as f:
config = json.load(f)
Expand Down
70 changes: 48 additions & 22 deletions scripts/us_cdc/environmental_health_toxicology/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@
"parse_air_quality.py CDC_PM25CensusTract"
],
"source_files": [
"input/*.gz"
"input_files/*",
"validation_config_census_tract.json"
],
"resource_limits": {
"cpu": 8,
"memory": 64,
"disk": 100
"cpu": 32,
"memory": 512,
"disk": 2000
},
"validation_config_file": "validation_config_census_tract.json",
"config_override": {
"invoke_differ_tool": false
},
"import_inputs": [
{
Expand Down Expand Up @@ -51,12 +56,17 @@
"parse_air_quality.py CDC_OzoneCensusTract"
],
"source_files": [
"input/*.gz"
"input_files/*",
"validation_config_census_tract.json"
],
"resource_limits": {
"cpu": 8,
"memory": 64,
"disk": 100
"cpu": 32,
"memory": 512,
"disk": 2000
},
"validation_config_file": "validation_config_census_tract.json",
"config_override": {
Comment thread
abhishekjaisw marked this conversation as resolved.
"invoke_differ_tool": false
},
"import_inputs": [
{
Expand Down Expand Up @@ -90,19 +100,33 @@
"parse_air_quality.py CDC_PM25County"
],
"source_files": [
"input_files/*"
"input_files/*",
"validation_config_county.json"
],
"resource_limits": {
"cpu": 32,
"memory": 512,
"disk": 500
},
"validation_config_file": "validation_config_county.json",
"import_inputs": [
{
"template_mcf": "PM25CountyPollution.tmcf",
"cleaned_csv": "output/PM25county.csv"
"cleaned_csv": "output/PM25county_0.csv"
},
{
"template_mcf": "PM25CountyPollution.tmcf",
"cleaned_csv": "output/PM25county_1.csv"
},
{
"template_mcf": "PM25CountyPollution.tmcf",
"cleaned_csv": "output/PM25county_2.csv"
},
{
"template_mcf": "PM25CountyPollution.tmcf",
"cleaned_csv": "output/PM25county_3.csv"
}
],
"resource_limits": {
"cpu": 8,
"memory": 128,
"disk": 200
},
"cron_schedule": "0 1 4 * *"
},
{
Expand All @@ -117,20 +141,22 @@
"parse_air_quality.py CDC_OzoneCounty"
],
"source_files": [
"input_files/*"
"input_files/*",
"validation_config_county.json"
],
"resource_limits": {
"cpu": 32,
"memory": 512,
"disk": 500
},
"validation_config_file": "validation_config_county.json",
"import_inputs": [
{
"template_mcf": "OzoneCountyPollution.tmcf",
"cleaned_csv": "output/OzoneCounty.csv"
}
],
"resource_limits": {
"cpu": 16,
"memory": 512,
"disk": 500
},
"cron_schedule": "0 1 5 * *"
}
]
}
}
Loading
Loading