Skip to content

Configurable scoring, FRC5 residential mapping, slip road penalty - #22

Merged
bnaul merged 13 commits into
mainfrom
frc_configurable
Feb 9, 2026
Merged

Configurable scoring, FRC5 residential mapping, slip road penalty#22
bnaul merged 13 commits into
mainfrom
frc_configurable

Conversation

@bnaul

@bnaul bnaul commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Expose scoring weights in Python: distance_weight, bearing_weight, frc_weight, fow_weight are now configurable via DecoderConfig, and Python defaults now match Rust defaults (max_bearing_diff=90, frc_tolerance=2)
  • Offset fractions: Replace OpenLR binary offset fractions with projection-based offsets that project LRP coordinates onto first/last decoded edges, returning both meter offsets and edge fractions
  • Deprioritize residential/access roads: Remap residential, living_street, service, track from FRC4→FRC5 so they're deprioritized in candidate scoring but still matchable within ±1 FRC tolerance of HERE FRC4 LRPs
  • Slip road penalty: Add configurable A* cost penalty (default 20m) for slip road edges, making the decoder prefer staying on main roads over taking ramps when paths are similar in length
  • Relaxed LFRCNP for residential: When LFRCNP=FRC4, force a relaxed pass to reconsider FRC5 (residential) edges that would otherwise be filtered. Scoped to the FRC4→FRC5 boundary only to avoid undermining LFRCNP at other levels

Test plan

  • All 53 unit tests pass including new tests for slip road penalty and relaxed LFRCNP
  • Run batch decode on Vermont/Kansas parquet to verify match rates aren't regressed
  • Spot-check residential road decodes in visualization app

🤖 Generated with Claude Code

bnaul and others added 13 commits February 2, 2026 10:35
Add distance_weight, bearing_weight, frc_weight, and fow_weight
parameters to DecoderConfig, allowing users to tune candidate
scoring behavior. This enables adjusting how much FRC/FOW matches
matter relative to spatial proximity.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Residential roads now map to FRC5 instead of FRC4. This deprioritizes
them during candidate scoring while still allowing them to match
HERE-encoded LRPs with FRC4 (within ±1 tolerance).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move residential, living_street, service, and track from FRC4 to FRC5.
This helps the decoder prefer through-roads (tertiary, unclassified) over
access roads when both would otherwise have the same FRC score.

FRC4 now contains: tertiary, secondary_link, tertiary_link, unclassified
FRC5 now contains: residential, living_street, service, track

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Since we added an exception to LFRCNP to allow slip roads (ramps/links)
to be used in paths regardless of FRC, we still want to gently prefer staying
on the same input FRC when possible.
- Fix Python default max_bearing_diff (30→90) and frc_tolerance (3→2)
  to match Rust defaults for cross-provider decoding
- Narrow needs_relaxed_pass to only trigger at the FRC4→FRC5 boundary,
  preventing LFRCNP violations at other FRC levels
- Compare relaxed same-edge solutions against best_score instead of
  returning unconditionally
- Update A* doc comment to accurately describe slip road penalty
  behavior (trades real-cost optimality for main road preference)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of mapping residential/service/track to FRC5 (which caused
LFRCNP filtering issues requiring a complex relaxed-pass workaround),
keep them at FRC4 and add an access_road_cost_penalty (default 10m)
in A* search. This gives a soft preference for tertiary over
residential without hard LFRCNP violations.

Changes:
- Revert residential/living_street/service/track from FRC5 back to FRC4
- Add is_access_road flag to Edge, set during loading
- Add access_road_cost_penalty to DecoderConfig (default 10m)
- Apply penalty in bounded_astar alongside existing slip_road_cost_penalty
- Remove needs_relaxed_pass logic (no longer needed)
- Expose access_road_cost_penalty in Python bindings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract AStarConstraints struct for bounded_astar search parameters
- Remove Edge::from_precomputed, use direct struct construction instead

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OpenLR Section 12.10 says offsets come from binary-encoded fractions,
but HERE data always encodes them as 0. We use projection-based offsets
instead, giving edge-relative values meaningful for OSM consumers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bnaul
bnaul merged commit 88e4eff into main Feb 9, 2026
2 checks passed
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