USFEMA_FloodInsuranceClaims: Optimize download and transformation pipeline - #2217
kartik-s21 wants to merge 10 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request re-engineers the FEMA flood insurance claims import pipeline, replacing a slow, sequential, row-by-row legacy processor with a highly optimized, vectorized, multi-process chunked pipeline. It introduces direct bulk CSV downloading with paginated API fallback, updates the manifest and documentation, and adds comprehensive unit tests. Feedback suggests further optimizing the generation of StatVar names in process.py by replacing .apply() with .map() using pre-computed unique flood zone names to avoid Python function call overhead when generating millions of observations.
4d7f6d6 to
56746b1
Compare
58c6d1a to
3848d33
Compare
c6a8334 to
7bb1d11
Compare
…ipeline - Re-engineer download pipeline with direct bulk CSV streaming and API pagination fallback. - Replace sequential row iteration in process.py with multi-process vectorized pipeline. - Use concurrent.futures.ProcessPoolExecutor with spawn context for safe multiprocessing. - Add FIPS zero-padding for tract and county places to prevent data loss for states 01-09. - Enforce atomic publishing for observations, MCF, TMCF, and counters. - Improve error handling with non-zero exit on empty aggregations. - Replace eval() with ast.literal_eval() for secure mapping parsing. - Update manifest.json node_mcf pattern and source_files. - Expand README documentation and add comprehensive unit test suite.
7bb1d11 to
0c411ef
Compare
- Fix swallowed IOError and stage chunks before atomic move in fema_download.py. - Enforce record count verification and raise on incomplete pagination. - Add --output_dir flag and resolve paths relative to script directory. - Format CountOfClaims as clean integer strings and lower chunk size floor to 25k in process.py. - Remove dead --config_file flag and clean pv_map argument in manifest.json and process.py. - Remove unused retry dependency from README prerequisites. - Add unit tests for download fallback paths and IOError handling in fema_download_test.py. - Regenerate test_data/flood_insurance_claims_output.csv with current pipeline output.
- Strip trailing newlines when computing records in chunk to prevent phantom record accumulation. - Normalize chunk writing to avoid double newline injection between chunk boundaries. - Update unit test fixtures to test trailing newlines.
…der-only chunk writes - Resolve relative paths against _SCRIPT_DIR in process.py when invoked from outside the script directory. - Guard against trailing newline injection when pagination chunks return only a CSV header. - Add test_relative_path_resolution_from_external_cwd in process_test.py.
…pagination, and test coverage
…tures to concise sizing
…ng, test sys.path, and lint
…euse test, require total_records for bulk, dynamic TMCF table ID, pagination chunk cleanup, and README CWD
| "golden_files": "../../../../golden_data/golden_observations.csv", | ||
| "input_files": "../../../../output/nfip_output.csv" | ||
| } | ||
| } |
There was a problem hiding this comment.
[P2] Missing explicit date freshness validation rule in validation_config.json
Finding: validation_config.json removes check_goldens_summary_report and Check_goldens_output_csv, leaving only check_deleted_records_percent (threshold: 0.1) without any date freshness validation rule (MAX_DATE_LATEST or SQL_VALIDATOR). Inspection of MaxDate across the 309 StatVars in summary_report.csv shows differing MaxDate values across historical flood zone sub-codes (e.g., FEMAFloodZoneAA / FEMAFloodZoneAS), while primary aggregate and active flood risk-zone series always contain current-year observations (2026-05 / 2026-06).
Impact: Without a scoped MAX_DATE_LATEST or SQL_VALIDATOR freshness rule in validation_config.json, stale upstream snapshots or regressions that drop recent years/months will pass automated validation undetected.
Recommendation: Add a scoped MAX_DATE_LATEST (or SQL_VALIDATOR) rule in validation_config.json targeting the primary aggregate and active risk-zone StatVars:
{
"rule_id": "check_max_date_latest_aggregates",
"description": "Verifies that core aggregate NFIP claim StatVars contain current-year data.",
"validator": "MAX_DATE_LATEST",
"scope": {
"variables": {
"dcids": [
"CountOfClaims_NaturalHazardInsurance_BuildingStructureAndContents_FloodEvent",
"SettlementAmount_NaturalHazardInsurance_BuildingStructureAndContents_FloodEvent",
"SettlementAmount_NaturalHazardInsurance_BuildingStructure_FloodEvent",
"SettlementAmount_NaturalHazardInsurance_BuildingContents_FloodEvent",
"CountOfClaims_NaturalHazardInsurance_FEMAHighRiskFloodZone_BuildingStructureAndContents_FloodEvent",
"SettlementAmount_NaturalHazardInsurance_FEMALowRiskFloodZone_BuildingStructureAndContents_FloodEvent"
]
}
},
"params": {}
}There was a problem hiding this comment.
Added the scoped MAX_DATE_LATEST rule (check_max_date_latest_aggregates) targeting the 6 primary aggregate and active flood risk-zone StatVars (CountOfClaims & SettlementAmount for Building & Contents, High Risk, and Low Risk).
| logging.info( | ||
| "Direct bulk download complete. Saved to: %s", | ||
| final_filepath) | ||
| return |
There was a problem hiding this comment.
[P2] Un-retried get_total_records API call in fema_download.py can discard a valid 3 GB bulk download
Finding: get_total_records(api_url) executes a single requests.get(url, timeout=30) with no retry/backoff logic. In download_data(), get_total_records(api_url) is invoked after downloading the ~2.98 GB bulk CSV (FimaNfipClaims.csv). If that single metadata API call experiences a transient network or HTTP 5xx error, the except block sets total_records = None, rejects the already-downloaded 2.98 GB bulk file, deletes temp_dir in finally, and falls back to the 2,722-page pagination loop (calling get_total_records(api_url) a second time at line 248, which will abort the entire job if the transient error persists).
Impact: A single transient failure on the lightweight $count=true API endpoint either forces an unnecessary ~2.5-hour paginated download after already streaming 2.98 GB of valid bulk data, or causes the entire import job to fail.
Recommendation: Wrap the HTTP request inside get_total_records(api_url) with bounded retries and exponential backoff (for example, reusing _retry_method from download_util_script.py):
from download_util_script import download_file, _retry_method
def get_total_records(api_url):
"""Makes a preliminary API call to get the total number of records."""
url = f"{api_url}?$top=1&$count=true"
try:
response = _retry_method(url, headers=None, tries=5, delay=5, backoff=2)
data = response.json()
...There was a problem hiding this comment.
Updated get_total_records to use _retry_method from download_util_script.py with 5 retries and exponential backoff (delay=5, backoff=2) to protect against transient metadata API hiccups. Also added $top=1 to the query URL (?$top=1&$count=true) so only 1 sample record is streamed in the payload rather than the default 1,000 records. All unit tests updated and passing.
… retry logic in get_total_records
Summary
Optimizes and stabilizes the
USFEMA_FloodInsuranceClaimsdata pipeline to eliminate multi-hour runtime bottlenecks, enhance download resilience against API snapshot discrepancies, ensure atomic file publication, and resolve all review and Code Review Agent (CRA) findings.Script execution time has been reduced from ~61 hours (~219,660s) to ~78.6 seconds, processing 13,765,520 rows with zero missing references and zero lint errors.
Key Changes
1. Download Stage (
fema_download.py)~60s) while retaining automatic fallback to paginated API requests.get_total_records()with_retry_method(tries=5, delay=5, backoff=2) fromutil.download_util_scriptto prevent transient network timeouts or HTTP 5xx errors from rejecting a valid ~3 GB bulk download and triggering an expensive 2.5-hour pagination fallback.$top=1to the count query (?$top=1&$count=true), reducing the returned payload from 1,000 serialized JSON records to 1 sample record for faster latency.tolerance_ratio=0.98) when comparing bulk CSV row counts against the live OpenFEMA API$count=truecounter. This prevents false-negative fallbacks to a 2.5-hour pagination loop caused by periodic snapshot lags.get_total_records()API calls by caching and reusing the live record count between bulk validation and pagination fallback._publish_file_atomicallyusing staging files andos.replacewitherrno.EXDEVfallback (shutil.move) to protect against corrupted or partial downloads.logging.fatal()prior toraisewithlogging.error(...)+raiseacross library functions, reservinglogging.fatal()for top-level CLI termination inmain(argv).2. Processing Stage (
process.py)concurrent.futures.ProcessPoolExecutor(multiprocessing.get_context('spawn')) with dynamic core scaling..tmpstaging and replace for all output.csv,.tmcf,.mcf, and counters.eval()withast.literal_eval()for safe mapping configuration ingestion.logging.error+raise) from CLI process termination (logging.fatalinmain(_)) to resolve dead code and ensure test compatibility.3. Test Infrastructure & CI (
fema_download_test.py,process_test.py,util/download_util_script.py)_SCRIPT_DIRintosys.pathacross test modules to support direct execution from the repository root viapython3 -m unittest._retry_methodHTTP retry/backoff validation. All 29 unit tests pass cleanly.req_lib.headinutil/download_util_script.pyto fix presubmit CI.4. Manifest & Validation (
validation_config.json,manifest.json,README.md)MAX_DATE_LATESTrule (check_max_date_latest_aggregates) targeting the 6 primary aggregate and active flood risk-zone StatVars (CountOfClaims&SettlementAmountfor Building & Contents, High Risk, and Low Risk) to guarantee current-year observations are verified on automated pipeline runs without false alarms on historical deprecated flood zone sub-codes.check_deleted_records_percentwiththreshold: 0.1(0.1%), fully conforming to Data Commons guidelines.node_mcfto"output/*.mcf"and included"manifest.json"insource_files.README.mdwith layout, CLI usage, and test instructions.Test Environment & Latest Job Runs
Performance Benchmarks
Documentation & Reports