Skip to content

Ar cscl districts gdb db qa oct4 - #2706

Open
alexrichey wants to merge 15 commits into
mainfrom
ar-cscl-districts-gdb-db-qa-oct4
Open

alexrichey wants to merge 15 commits into
mainfrom
ar-cscl-districts-gdb-db-qa-oct4

Conversation

@alexrichey

Copy link
Copy Markdown
Contributor

No description provided.

…tric

clip_to_shoreline_gridsize (0.001 -> 0.005, shared var across int__water_mask.sql
and clip_to_shoreline.sql's two call sites) fixes the same needle-sliver mechanism
as the tract-dissolve fix, here in the water-clip step: PUMA 3701's worst needle
dropped from a 2,330ft to 0.05ft Hausdorff distance against prod, bringing its
SHAPE_Length within 0.03ft of prod (was 22% off).

Also adds perimeter_pct_diff to test_cases.geometry_diff_metrics - the existing
fragment-based metrics only sum symdiff fragments >=100sqft by default, which made
them structurally blind to needle-shaped defects (every one found so far is under
a few sqft). Wired into both qa__test_cases_geometry and the geometry_diff_qa macro.
…CreaDate

nyad is confirmed NOT stale: our build matches prod's nyadwi exactly citywide,
including AD 64 (the 2023-redistricting-affected district) - content is current
despite a 2012 CreaDate. That date reflects the feature class container's
creation, not its last data refresh (a truncate+append update pattern leaves
CreaDate untouched while refreshing content every cycle).

nyad -> done. The other 8 layers Bug 013 flagged on the same CreaDate evidence
(nyap/nybb/nybid/nycdwi/nyhd/nymcea/nyura/nyzip) -> open: unblocked since the
blocking justification doesn't hold, but not individually re-verified, so not
marked done either. See inv-3i1/inv-9k8 in the investigation tracker.
GDAL's vector.info() algorithm only returns a rootGroup key for geodatabases
with a hierarchical group structure (modern/10.x+ FileGDBs) - a classic FileGDB
like CSCL's district gdb has none, so the old info["rootGroup"]["layerNames"]
lookup raised KeyError('rootGroup') for every such file. Delegates to
layer_geometry_types (pyogrio-based) instead, already proven against both
formats via compare_gdb.py.
Extends geometry_diff_qa to PUMA, matching the nycd/nyfb/nycb2010 pattern -
previously only checked by compare_gdb.py's SHAPE_Length/Area tolerance, with
no in-db QA at all. Wired into qa__diffs_all.
@codecov

codecov Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 32 lines in your changes missing coverage. Please review.
✅ Project coverage is 30.24%. Comparing base (56cddbd) to head (069c502).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
python/geospatial/dcpy/geospatial/gdb/compare.py 0.00% 20 Missing ⚠️
python/geospatial/dcpy/geospatial/gdb/report.py 0.00% 11 Missing ⚠️
python/geospatial/dcpy/geospatial/gdb/fgdb.py 0.00% 1 Missing ⚠️
Additional details and impacted files
Files with missing lines Coverage Δ
python/geospatial/dcpy/geospatial/gdb/fgdb.py 0.00% <0.00%> (ø)
python/geospatial/dcpy/geospatial/gdb/report.py 0.00% <0.00%> (ø)
python/geospatial/dcpy/geospatial/gdb/compare.py 0.00% <0.00%> (ø)

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…roject-wide

Set via dbt_project.yml's vars: block (new) rather than touching each call site's
var() fallback default, so every model stays in sync from one source of truth.

Driven by PUMA 3702: a tract-dissolve "doubled-back retrace" needle (same family
as the original PUMA 4105 finding, not a new mechanism - see inv-tv1.2) that
survived both 0.001 and 0.005 and only resolved cleanly at 0.01 and above.

Bumping clip_to_shoreline_gridsize to match is a uniformity call, not an
independent measurement - it's actually worse for PUMA 3701 specifically (97ft
Hausdorff vs 0.005's 0.05ft), though still a large improvement over unfixed
(2,330ft). Same grid-alignment-lottery tradeoff as always: no gridSize is a
strict improvement over every other for every case.
Two combined models rather than ~34 near-identical files: one Jinja-loop model
per cost tier, each UNION ALLing geometry_diff_qa() across a layer catalog with
a source_layer column to identify rows.

- qa__geometry_fgdb_district_layers_cheap: nyap/nycb2010/nycb2010wi/nycb2020/
  nycb2020wi (38k-70k rows each) - full geometry_diff_qa would take 5-20+ hours
  per layer at the observed ~0.5-1.2s/row cost, so these get a new, cheap
  variant (geometry_diff_qa_cheap macro) instead: direct ST_Perimeter/ST_Area
  comparison per side, no ST_SymDifference/ST_HausdorffDistance. Runs in ~13s
  for all 223k rows combined. Active by default.

- qa__geometry_fgdb_district_layers: the other 34 layers (nyura excluded - its
  prod table carries a stale copy of nybid's schema, no uraid column, 0 rows -
  a known structural diff, not a fixable key mismatch). Full geometry_diff_qa
  on the real catalog took 10+ minutes, too slow for a routine build - dormant
  by default (`enabled = []`), with the full layer catalog (key columns,
  composite-key concat expressions) left in place as reference. Add a layer
  name to `enabled` to dive into that layer's geometry shape QA, remove it
  when done.

nycd/nyfb/nypuma2010/nypuma2020 already had their own bespoke models before
this existed and are left as-is.
row_level_diff already computed the actual dev-only/prod-only/modified row
keys internally to produce its counts, then discarded them. Keep them
(RowLevelDiff.only_in_dev_keys/only_in_prod_keys/modified_keys) and expose a
GdbComparisonReport.flagged_rows() to format them.

compare_gdb.py now writes output/validation_output/<name>_flagged_rows.csv
alongside the existing per-column summary CSV - one row per flagged feature
(layer, status, key_columns, key), so a layer the per-column CSV shows has
diffs can be chased down by key directly instead of re-deriving the affected
rows by hand.

Backward compatible: existing callers reading only the count fields are
unaffected (new fields default to empty lists). Updated 9 existing tests
whose full-dataclass-equality assertions needed the new fields added; added
3 new tests for flagged_rows() itself.
geometry_diff_qa now carries actual_geom_4326/expected_geom_4326 through to
its output - already available for free (just not selected before), kept
separate from the geometry the metrics are computed from so a display-side
reprojection to 4326 (for future Python mapping tools) can't add its own
noise to the sub-foot precision work the rest of this investigation depends
on. Flows through automatically to nycd/nyfb/nypuma2010/nypuma2020 and the
dormant qa__geometry_fgdb_district_layers catalog.

New qa__context_geoms table for secondary, related-but-distinct geometries a
flagged row needs context from - a PUMA's constituent census tracts for now
(role='constituent_tract'), joined back on (layer, key_value). One row per
individual related geometry, not one collapsed multi-geometry per role, so
each stays individually addressable for a future map UI. Deliberately
generic (layer/key_value/role/label/geom) so another layer can add its own
role later without a schema change - just the first populated case.
Was storing every PUMA's constituent tracts unconditionally (4,495 rows,
55/57 PUMAs) when only 6/4 are ever actually flagged in
qa__geometry_fgdb_nypuma2010/2020. This table exists to help explain why a
flagged row differs, not to mirror membership for PUMAs nobody needs to look
at - scoped down to WHERE NOT passes (353 rows).
Pulls the primary geometry pair (our build output, the legacy/prod geometry
it's compared against) into the same normalized table as the constituent-
tract context rows, as role='build'/'legacy'. Still also available as
actual_geom_4326/expected_geom_4326 columns on qa__geometry_fgdb_* directly -
duplicated here too, not instead, so a future map UI can query one table
(layer, key_value, role) for everything about a flagged feature rather than
switching schemas between "the row itself" and "things related to it".
ST_Difference of the primary pair, split into two separate layers (what's
only in our build, what's only in legacy) rather than one symmetric-
difference blob, so a front-end consumer can toggle "extra in build" and
"missing from build" independently by filtering on role. Computed once per
vintage via a CTE rather than re-running ST_Difference per UNION branch.

Display-only, on the already-4326 geometries - not the same computation path
test_cases.geometry_diff_metrics uses for the real symdiff_area_pct/etc.
metrics, which stay on EPSG:2263 and are unaffected.

Also surfaced a real, pre-existing data issue along the way: PUMA 4414's
legacy (prod) geometry is invalid (GEOS: self-intersection) once
transformed/linearized - not something this change introduces, just now
visible since it's stored here. Not investigated further yet.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant