Fix for epa-airpollutantemission-level1 - #2148
shourya116 wants to merge 24 commits into
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request increases the resource limits (CPU, memory, and disk) in the manifest configuration and refactors pandas DataFrame operations in process.py to avoid using .loc for simple column assignments and to replace deprecated inplace=True usage. Feedback is provided to remove a redundant .replace('', np.nan) call on the observation column, as empty strings are already converted to NaN earlier in the processing pipeline.
abhishekjaisw
left a comment
There was a problem hiding this comment.
Review scope
- Target: PR #2148 (
4fc4609467faf6425589b232ce6e674c6677ec2d) - Reviewed:
scripts/us_epa/national_emissions_inventory/manifest.jsonscripts/us_epa/national_emissions_inventory/process.pyscripts/us_epa/national_emissions_inventory/validation_config.json
- Skipped: None
Summary of Findings
- [P1] CI Test Suite Failure (
data-pull-request-py) & Missing Regression Tests for 2017/2020 Nonpoint & Tribal Fixes (process.py:144,process_test.py)- Running
./run_tests.sh -p scripts/us_epa/national_emissions_inventory(executed by GitHub CIdata-pull-request-py) fails withImportError: attempted relative import with no known parent packageatprocess_test.py:20(from .process import *) becausescripts/us_epa/national_emissions_inventory/lacks an__init__.py. - Furthermore, PR #2148 modifies
_regularize_columnsto fix the silent drop of 767,314 observations across 2017/2020nonpoint,point_, and 2014tribesfiles, but adds zero unit tests or test fixtures for these branches.
- Running
- [P2] Missing Date Freshness Validation Rule in
validation_config.json(Outdated Branch / Already Fixed DuckDB Differ Issue) (validation_config.json:9)validation_config.jsonomits date freshness validation becauseSQL_VALIDATORpreviously failed on emptydiffer_df(Need a DataFrame with at least one column). However, this framework bug was already fixed onmasterin commits3315fc4b(Aug 14, 2026) and4d856505(Aug 31, 2026) intools/import_validation/validator.py:62-63andrunner.py:194. Rebasing PR #2148 ontomasterenables re-adding theSQL_VALIDATORdate freshness check (MAX(MaxDate) >= 2020andMIN(MaxDate) >= 2014).
- [P2] Exception Tracebacks Discarded in
logging.fatalCatch Blocks (process.py:239)- Passing only
{e}tologging.fatal(...)discards the Python stack trace (exc_info), obscuring line numbers and call stacks during Cloud Batch failures.
- Passing only
- [P3] Operational Configs in
source_files& Google CLA Check (manifest.json:25)- Consider adding
"validation_config.json"to"source_files"inmanifest.jsonso validation configs are archived in GCS. Additionally, please resolve the failingcla/googlecheck on GitHub before merge.
- Consider adding
Positive findings
scripts/us_epa/national_emissions_inventory/process.py:139-155- Precise filename/path disambiguation and direct column assignment ✓- Finding: Good - Replaced broad
"point" in file_pathchecks with"point_" in os.path.basename(file_path) or "facility_process" in file_pathand explicit"nonpoint"branches, avoiding substring collisions withnonpointfiles, and replaced.loc[:, col] = ""with direct column assignment (df["emissions type code"] = "") to prevent Pandas 2.xfloat64dtype errors.
- Finding: Good - Replaced broad
scripts/us_epa/national_emissions_inventory/process.py:314- Eager evaluation ofThreadPoolExecutor.map✓- Finding: Good - Wrapped
executor.map(...)inlist(...)so worker thread exceptions andSystemExitfromlogging.fatalpropagate immediately to the main thread rather than silently dropping failed files.
- Finding: Good - Wrapped
scripts/us_epa/national_emissions_inventory/manifest.json:21-29- Scaled Cloud Batch compute resources and registerednode_mcf✓- Finding: Good - Increased memory to 512 GiB and CPU to 32 to eliminate OOM kills (
exit code 50002) during multi-year DataFrame concatenation, and explicitly wirednode_mcfandvalidation_config_file.
- Finding: Good - Increased memory to 512 GiB and CPU to 32 to eliminate OOM kills (
Coverage
| File | Status | Result |
|---|---|---|
scripts/us_epa/national_emissions_inventory/manifest.json |
Reviewed | One P3 finding |
scripts/us_epa/national_emissions_inventory/process.py |
Reviewed | One P1 finding, one P2 finding |
scripts/us_epa/national_emissions_inventory/validation_config.json |
Reviewed | One P2 finding |
Verification and limitations
- Checks run:
./run_tests.sh -p scripts/us_epa/national_emissions_inventory(Reproduced CI failure:ImportError: attempted relative import with no known parent packageinprocess_test.py)PYTHONPATH=. .env/bin/python3 -m unittest scripts/us_epa/national_emissions_inventory/process_test.py(Passed: 1 test in 0.575s)- Inspected GCS prod (
2025_12_31T16_03_29_040514_08_00) and dev (2026_09_06T07_23_45_163580_07_00)summary_report.csvandvalidation_output.csv.
- Checks not run: None
- Limitations: None
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…ove tracebacks, and restore date freshness validation
4fc4609 to
15b2958
Compare
shourya116
left a comment
There was a problem hiding this comment.
Changed Implemented as per feedback
Code Review Summary for PR #2148 (
|
…t manifest - In process.py, remove unreachable raise and sys.exit(1) calls after logging.fatal(), avoid calling logging.fatal() inside worker threads (_national_emissions, _process_file) so exceptions propagate cleanly via list(executor.map(...)), and remove commented-out imports. - In validation_config.json, update check_deleted_records_percent description to explicitly state the 0.1% threshold and deletion rationale. - In manifest.json, remove extra trailing blank lines at EOF.
There was a problem hiding this comment.
All the latest review comments have been resolved and verified. Both the CRA review document and Postmortem report have also been updated and synced live.
Summary of Actions Taken
-
[P2]
process.pyException Handling & Cleanup:- Worker Thread Exception Propagation: Avoided calling
logging.fatal()insideThreadPoolExecutorworker threads (_national_emissionsand_process_file). Errors are logged usinglogging.exception()and re-raised so exceptions propagate cleanly tolist(executor.map(...))on the main thread. - Unreachable Code Removal: Removed unreachable
raiseandsys.exit(1)statements after terminatinglogging.fatal()calls across 6 exception blocks (intermediate file reading loop, empty DataFrame check, input file discovery, and main execution handler). - Import Cleanup: Removed commented-out imports (
# import shutil,# import tempfile).
- Worker Thread Exception Propagation: Avoided calling
-
[P2]
validation_config.jsonDescription Update:- Updated
check_deleted_records_percentdescription to explicitly state the 0.1% threshold and deletion rationale:{ "rule_id": "check_deleted_records_percent", "description": "Verifies that the percentage of deleted records does not exceed the 0.1% threshold, ensuring unintended observation drops (such as missing nonpoint sources which drop >20% of records) are caught while accommodating minor upstream EPA revisions.", "validator": "DELETED_RECORDS_PERCENT", "params": { "threshold": 0.1 } } - Corrected the typo in Postmortem Section 5.3 from
(10%)to(0.1%).
- Updated
-
[P3]
manifest.jsonFormatting:- Removed the 3 extra trailing blank lines at EOF.
-
[P2] CRA Paste & Postmortem Sync:
- Head Commit Sync: Updated both documents to reference head commit
242f22800dcf60581dc309111b5388ddb77f974e. - Unit Tests: Updated unit test counts in both documents to reflect 5 passing tests (
0.635s, OK). - Date Freshness Validation: Confirmed that
check_date_freshness(SQL_VALIDATOR) is active and passing following the clean rebase with master's DuckDB empty-differ fix. - Updated Paste Links:
- CRA Paste: https://paste.googleplex.com/6620650563829760
- Postmortem Report: https://paste.googleplex.com/5957668808818688
- Head Commit Sync: Updated both documents to reference head commit
-
Cloud Batch Verification Run (SUCCEEDED):
- Ran end-to-end verification job
epa-airpollutantemission-level1-shouryasingh-20260908-184507onus-west4usingn2-highmem-64(32 CPUs / 512 GiB RAM / 300 GB disk). - Status:
SUCCEEDED(execution duration: 7,932s / ~2.2 hrs). - All 3,041,170 rows were successfully processed (all 767,314 missing nonpoint observations recovered).
- All 5 validations passed:
check_deleted_records_percent: PASSED (0 deleted records, 0.0% vs 0.1% threshold)check_empty_import: PASSED (3,041,634 nodes, 3,041,170 rows)check_missing_refs_count: PASSED (0 missing references)check_lint_error_count: PASSED (0 lint errors)check_date_freshness: PASSED (MAX(MaxDate) >= 2020 AND MIN(MaxDate) >= 2008)
- Cloud Batch Job: https://console.cloud.google.com/batch/jobsDetail/regions/us-west4/jobs/epa-airpollutantemission-level1-shouryasingh-20260908-184507/details?project=datcom-infosys-dev
- GCS Output:
gs://datcom-import-test/scripts/us_epa/national_emissions_inventory/EPA_AirPollutantEmission_Level1/2026_09_08T11_47_48_764425_07_00 - GCS Console: https://console.cloud.google.com/storage/browser/datcom-import-test/scripts/us_epa/national_emissions_inventory/EPA_AirPollutantEmission_Level1/2026_09_08T11_47_48_764425_07_00?project=datcom-infosys-dev
- Ran end-to-end verification job
-
Sanitized PR Description
|
LGTM |
|
/gcbrun |
- Fix replacement_20 column mappings to retain raw columns matching df_columns - Apply point replacement unconditionally for 2017 (Regions 1-5) and 2020 (Regions 1-10) - Remove duplicate node_mcf from manifest import_inputs - Purge intermediate output directory on start of execution to prevent rerun accumulation - Fix county FIPS code formatting using numeric coercion and 5-digit padding - Strip trailing .0 from SCC strings and remove duplicate key 4 from replace_source_metadata - Bound MAX_WORKERS to 8 and restore resource_limits in manifest to standard operational values - Remove static validation_config.json from source_files in manifest - Update date freshness validation to verify temporal start with MIN(MinDate) <= 2008 - Make 2014 survey year matching explicit with ValueError fallback and structure test input under 2014 - Use ignore_index in concat and dropna on numeric observation - Use uuid for unique intermediate filenames - Add comprehensive unit tests covering all fixes
- Pre-aggregate emission observations by geo_Id, year, Measurement_Method, and SV within each file before saving intermediate files - Use pickle format for intermediate files to preserve exact 64-bit IEEE float precision - Implement chunked consolidation in batches of 10 files with explicit garbage collection to prevent memory surges - Eliminate OOM failure (Batch exit code 50002) during consolidation phase
…t_17 In config.py, replacement_point_17 inadvertently mapped 'total emissions': 'observation'. Since 2017 point_12345.csv already contained the standard 'total emissions' column, it was mapped to 'observation' inside _regularize_columns before columns were subsetted to df_columns. Because 'observation' is not in df_columns, it was dropped and 'total emissions' was set to NaN, resulting in all 108,057 observations for EPA regions 1-5 in 2017 being dropped and failing the deleted records validation check.
- Re-raise exceptions in process_files and _process on corrupt intermediate files and unexpected errors - Optimize .groupby() aggregation by explicitly summing numeric observation column before assigning unit - Remove unused drop_tribes configuration - Expand process_test to cover 2014 data processing and exception handling
- Quote date literals in validation_config.json check_date_freshness to avoid DuckDB Binder Error - Add manifest.json and validation_config.json to manifest.json source_files - Implement two-tier logging contract in process.py and eliminate unreachable fatal/raise combinations - Remove redundant intermediate directory purge in USAirEmissionTrends._process - Synchronize cleaned output paths in README.md to gcs_output/output_files/
|
/gcbrun |
SandeepTuniki
left a comment
There was a problem hiding this comment.
Thanks for the fixes! I've requested one change below.
|
/gcbrun |
SandeepTuniki
left a comment
There was a problem hiding this comment.
Thanks for the changes! Please fix the CI failures before merging.
Summary
Fixes and stabilizes the
EPA_AirPollutantEmission_Level1import pipeline across all historical observation periods (2008–2020) and source categories (point, nonpoint, onroad, nonroad, and tribal sources).This update resolves:
.locstring assignment on float64 columns and broad exception swallowing.replacement_point_17): An errant'total emissions': 'observation'mapping dropped all 108,057 point observations for EPA regions 1–5 in 2017 during regularized column subsetting, triggering validation failure.manifest.jsonnode MCF declarations, restored standard VM resource limits, quoted DuckDB date literals in date freshness validation, and retained operational configuration artifacts.Root Cause & Incident Breakdown
emissions type codecolumns loaded asfloat64.'point' in file_pathevaluated toTruefor..._nonpoint/...paths, executingdf.loc[:, 'emissions type code'] = ''. In Pandas 2.x, assigning a string to a float64 Series via.locraisesTypeError: Invalid value '' for dtype 'float64'._national_emissions()caught this exception and returned an empty DataFrame, silently omitting 767,314 nonpoint records.config.py:replacement_point_17mapped'total emissions': 'observation'. Becausepoint_12345.csvalready used standard'total emissions', re-mapping to'observation'caused the column to be dropped when filtered againstdf_columns, converting all observations toNaNand failingcheck_deleted_records_percent(108,057 deleted records).Key Changes
1. Data Ingestion & Transformation (
process.py,config.py)'point_' in os.path.basename(file_path)and use direct column assignment (df['emissions type code'] = '').'total emissions': 'observation'mapping fromreplacement_point_17inconfig.py.drop_tribesfromconfig.py.replacement_20column mappings to retain raw columns matchingdf_columns.ValueErrorfallback for unsupported years.(geo_Id, year, Measurement_Method, SV)within each worker task before writing intermediate files..pkl) files rather than CSVs to preserve exact 64-bit IEEE float precision and eliminate text serialization overhead.gc.collect()to guarantee flat memory usage (< 2 GB peak).zfill(5)), ensuring valid Data CommonsgeoId/XXXXXresolution (e.g.,geoId/01001)..0from float-parsed SCC strings and deduplicated key4inreplace_source_metadata.logging.exception()and re-raise, whilemain(argv)handles top-level termination cleanly vialogging.fatal(..., exc_info=True)without unreachableraisestatements.2. Import Manifest & Validation Config (
manifest.json,validation_config.json)manifest.json:cpu: 8, memory: 128, disk: 300) and boundedMAX_WORKERSto 8.node_mcfunderimport_inputsto prevent duplicate ingestion of pre-existing canonical StatVars.manifest.json,validation_config.json) insource_filesfor provenance tracking.validation_config.json:check_deleted_records_percentwith a 0.1% threshold.check_date_freshness(max_date >= '2020' AND min_date <= '2008'), completely preventing DuckDBVARCHARvsINTEGER_LITERALBinder Error/CONFIG_ERROR.3. Unit Tests (
process_test.py)float64NaN handling.total emissionsfor 2017 Regions 1–5 (point_12345.csv) and 2020 Regions 1–10.Verification & Artifacts
23a89dcf11302c1828312a60bda27c9d52163627(23a89dcf)a1ca509663d77be5da24ae5806c7fe098679a8e2(a1ca5096)epa-airpollutantemission-level1-shouryasingh-20260922-192749epa-airpollutantem-1b3ef5bf-e17f-41130datcom-infosys-dev(Location:us-west4)n2-highmem-64(32 vCPUs, 512 GiB RAM, 300 GB disk)SUCCEEDED(Exit Code: 0, Total Duration: 5,738.49s / ~1h 35m)gs://datcom-import-test/scripts/us_epa/national_emissions_inventory/EPA_AirPollutantEmission_Level1/2026_09_22T12_30_45_790197_07_00ImportStatus.STAGINGDiffer & Validation Summary
3,041,1700098,5600check_deleted_records_percentthreshold: 0.1%)check_empty_importcheck_missing_refs_countthreshold: 0)check_lint_error_countthreshold: 0)check_date_freshnessmin_date <= '2008' AND max_date >= '2020'Unit Tests:
python3 -m unittest scripts/us_epa/national_emissions_inventory/process_test.pyReferences: