Conversation
…ElevationValidator
Core inversion: trusted AI-verified seeds define which contour levels exist;
raw OCR values are candidates validated against that model, not the
governing distribution.
Motivation: IQR on contaminated input (Edgewater: 86% noise, 14% signal)
produces a poisoned fence [-5422, 7605 ft], keeping everything.
Seed-driven validation on the same input: keeps only the 3 real elevations.
New module: app/pipeline/layer2/elevation_validator.py
- ElevationSource enum: ai_verified_seed | ocr_raw | propagated | rejected_outlier
- SeedModel.from_seeds(): detects contour interval from sorted seeds,
extrapolates valid level set (seed ± n*interval), computes site bounds
- ElevationValidator.validate(): accepts candidates near a valid level
(distance ≤ interval*0.30 tolerance); rejects with reason + distance
- Fallback: model=None → all_low_confidence=True, no values filtered
Interval detection (_detect_interval):
- Computes modal diff from consecutive seed pairs
- Requires ≥60% consensus to mark interval_consistent=True
- Tolerance is proportional to interval (no floor clamp — a 0.5ft minimum
on a 1ft interval would cover the entire number line)
Rejection provenance: each rejected value carries reason (outside_site_bounds
or off_interval), source (rejected_outlier), and distance_ft to nearest level.
Fallback when no model: marks all_low_confidence=True, returns all values
unfiltered. Conservative by design — fabricating a model from insufficient
seeds is worse than staying uncertain.
32 tests covering interval detection, SeedModel construction, accepts(),
ElevationValidator with and without model, and the full Edgewater adversarial
scenario (18 noise / 3 signal — seed validator passes all 3, rejects all 18).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cular-certainty lock
Addresses four follow-up concerns after initial seed validator review:
1. Interval diagnostics (IntervalDiagnostics class)
- Exposes raw_diffs, n_diffs, modal_interval, modal_count,
consensus_ratio, consistent, method for every SeedModel
- to_dict() emits full audit trail; included in ValidationResult.to_dict()
- method tag: interval_consistent | interval_inconsistent | no_interval
2. Inconsistency guard (fixes 819/836/847 -> 11ft nonsense)
- Previously: inconsistent interval was still used for extrapolation/tolerance
- Now: inconsistent interval → no extrapolation (valid_levels = seed values only)
+ tolerance falls back to _DEFAULT_MATCH_TOLERANCE_FT
- Also: single diff (2 seeds) is never marked consistent — requires ≥2 matching
diffs; 1 diff cannot distinguish coherent contour system from two seeds in
different spatial zones
3. potentially_mixed_zones flag
- Set True when n_seeds ≥ 3 and interval_consistent is False
- Flags that seeds may come from spatially disjoint contour neighborhoods
- Zone-aware per-zone SeedModel is deferred; this flag is the diagnostic signal
4. Circular certainty guard (from_ai_verified_only)
- Hard API boundary: raises ValueError if any non-AI_VERIFIED_SEED source
is passed (propagated, ocr_raw, etc.)
- Prevents: seeds → propagation → propagated values redefine seed model
→ model expands itself with its own output
44 tests — all pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds KT Civil user-verified seeds (proposed contours 736-756, 1ft
interval) and two diagnostic overlay scripts for the elevation
validator. These complete the visual validation work for Ch36.
seeds_ktcivil.json
- 21 proposed contour elevations verified by user against PDF
- zone center at confirmed 738 detection (x=2824, y=1003)
- excluded_ocr_values documents legend/garbage reads
visualize_elevation_text.py
- marks OCR text bounding boxes at actual pixel positions (not
curve centroids) — answers spatial plausibility directly
- green box = seed model accepted, red = rejected, orange = no model
- optional --link-lines draws line from each label to nearest curve
- confirmed: Edgewater 3 accepted, KT Civil 4 accepted (736/738/742/748),
all junk correctly rejected
visualize_elevation_validator.py
- first-pass curve-centroid overlay; retained for reference
RGB rendering experiment: tried csRGB for better Tesseract contrast
(improved KT Civil 2→4 accepted) but caused line-count shifts on all
3 golden drawings (KT Civil −50, Easton −118, Simpson +86). Reverted.
runner.py stays on csGRAY. RGB rendering tracked as future work.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ElevationSourceenum: explicit provenance taxonomy —ai_verified_seed | ocr_raw | propagated | rejected_outlier— eliminates the flat elevation pool that let the filter get poisonedSeedModel: built from trusted anchors; detects contour interval via modal diff, extrapolates valid level set (seed ± n×interval), computes site bounds. Tolerance proportional to interval (no floor clamp — a 0.5ft minimum on a 1ft interval would cover the entire number line)ElevationValidator: accepts/rejects withreason+distance_ftprovenance. No model →all_low_confidence=True, no filtering (conservative fallback for sheets without enough trusted seeds)Test plan
test_ch36_seed_elevation_validator.py— all pass locallyruff check .clean🤖 Generated with Claude Code