# SignalReach Propagation Integration Plan

## Goal
Integrate the older "realism stack" ideas from the system pack into the current
SignalReach solver while also upgrading the terrain/RF backbone from the current
FSPL + diffraction + LR-lite approximation to a full LR-ITM path.

This document is intentionally practical. It maps the packed design artifacts
onto the code that exists today and recommends an implementation order that
reduces rewrite risk.

## Current Snapshot
Already landed:
- propagation service seam
- vendored LR-ITM backend via `itmlogic`
- coverage/heatmap routing through the propagation service
- fast-display heatmap backend policy
- first dry clutter runtime scaffolding with manual/preset vegetation and
  structure penalties
- dormant moisture schema/hooks
- sampler-facing module stubs for clutter, vegetation, structures, and
  environment

## What Was In The Pack
The external pack is mostly design material, not production code. The useful
ideas are:

### 1. Layered solver decomposition
From:
- `SignalReach_Solver_Architecture.md`
- `SignalReach_Solver_Phases.md`

Key idea:
- keep terrain, clutter, environment, synthesis, and decision logic as distinct
  layers with explicit contracts

### 2. Dry-world clutter realism
From:
- `SignalReach_Propagation_Math_Framework.docx`
- `SignalReach_Successor_Brief.md`

Key idea:
- total path loss should be split into:
  - terrain loss
  - vegetation loss
  - structure loss
  - local terminal clutter loss
  - optional environmental loss

### 3. Moisture as a modifier, not a replacement model
From:
- `SignalReach_Moisture_Technical_Brief.md`
- `SignalReach_Moisture_Schema.yaml`
- `SignalReach_Moisture_Presets.yaml`

Key idea:
- moisture should stay deferred at first
- when activated, it should modify vegetation/local/atmospheric terms, not act
  as a blunt global multiplier

### 4. Full solver request/result contract
From:
- `SignalReach_Solver_IO_Schema.yaml`

Key idea:
- the future propagation core wants a canonical "solve one path" contract that
  returns both physics outputs and confidence inputs

## What Exists Today

### Current terrain/RF stack
Current code:
- [los.py](/C:/dev/rfplanner/signalreach/los.py)
- [rf.py](/C:/dev/rfplanner/signalreach/rf.py)

Today the model is roughly:
- terrain path sampling from DEM
- geometric LOS / Fresnel checks
- Bullington/knife-edge-ish diffraction proxy
- FSPL path loss
- optional LR-lite excess-loss term based on path roughness and reliability
- confidence derived from fade margin

This is much lighter than a full Longley-Rice / ITM implementation.

### Current solver integration points
Current consumers:
- [cli.py](/C:/dev/rfplanner/signalreach/cli.py)
- [asset_assign.py](/C:/dev/rfplanner/signalreach/asset_assign.py)
- [tiered_plan.py](/C:/dev/rfplanner/signalreach/tiered_plan.py)
- [backhaul.py](/C:/dev/rfplanner/signalreach/backhaul.py)
- [heatmap.py](/C:/dev/rfplanner/signalreach/heatmap.py)

Important reality:
- the same geometry/RF primitives are reused in multiple places
- if we jam LR-ITM or clutter logic into only one path, the solver will become
  internally inconsistent

### Current schema state
Current config is centered on:
- `los.*`
- `rf.*`
- `rf_backhaul.*`
- assets / anchors / backhaul options in [mission.py](/C:/dev/rfplanner/signalreach/mission.py)

There is currently:
- no vegetation grid schema
- no structure grid schema
- no clutter query contract
- no moisture runtime contract
- no canonical terrain-backbone result object

## Recommended Strategic Direction

### Do not do this
Do not start by:
- dropping LR-ITM logic directly into [rf.py](/C:/dev/rfplanner/signalreach/rf.py)
- adding a few ad-hoc `veg_loss_db` fields to random site dicts
- implementing moisture before the dry clutter stack

That would create a tangled model with no clean boundary between:
- terrain propagation
- clutter penalties
- confidence/uncertainty
- planner-facing outputs

### Do this instead
Build a new propagation contract and make the current solver call through it.

The pack is right about one key thing:
- the terrain backbone must be swappable

That means we should make the current lite model and the future LR-ITM model
conform to the same path-solve interface.

## Target Architecture

### New conceptual layers

1. Mission context resolver
2. Terrain backbone
3. Clutter sampler
4. Vegetation solver
5. Structure solver
6. Environment solver
7. Synthesis solver
8. Confidence / PSI solver
9. Advisory solver

### New code boundary we should introduce
Add a small propagation package, for example:
- `signalreach/propagation/contracts.py`
- `signalreach/propagation/context.py`
- `signalreach/propagation/terrain_lite.py`
- `signalreach/propagation/terrain_itm.py`
- `signalreach/propagation/clutter_sampler.py`
- `signalreach/propagation/vegetation.py`
- `signalreach/propagation/structures.py`
- `signalreach/propagation/environment.py`
- `signalreach/propagation/synthesis.py`
- `signalreach/propagation/confidence.py`

This does not require deleting the current code immediately. The first goal is
to wrap the current logic behind a better interface.

## Canonical Path-Solve Contract

### Proposed request object
Each path solve should receive:
- tx context
  - lat/lon
  - antenna height AGL
  - TX power / gains / losses
  - node class
- rx context
  - lat/lon
  - antenna height AGL
  - receiver threshold / gains / losses
  - node class
- band/context
  - frequency
  - polarization
  - reliability mode
  - propagation model selection
- model options
  - terrain model
  - vegetation enabled
  - structure enabled
  - moisture enabled
  - confidence enabled
- source references
  - DEM source
  - vegetation grid source
  - structure grid source
  - moisture source or preset

### Proposed result object
Each path solve should return:
- terrain solution
- vegetation contribution
- structure contribution
- environmental contribution
- total loss
- received power
- fade margin
- uncertainty
- PSI / severity
- link confidence
- reason flags / advisories
- debug payload

This mirrors the strongest part of `SignalReach_Solver_IO_Schema.yaml`.

## How LR-ITM Fits

### What LR-ITM should replace
LR-ITM should replace the current "terrain propagation" portion of the stack,
not the entire solver.

In other words:
- LR-ITM becomes the terrain backbone
- vegetation/structure/moisture remain additional layers on top of that

That gives us a clean equation:

`L_total = L_terrain_backbone + L_veg + L_struct + L_local + L_env`

### Why this is the right split
LR-ITM already attempts to model terrain-mediated propagation effects.
It should not be polluted with:
- ad-hoc vegetation penalties
- ad-hoc structure penalties
- local terminal clutter embedment
- coarse moisture presets

Those should remain separable so they can be:
- calibrated independently
- turned on/off independently
- explained to the user independently

## Where To Integrate LR-ITM In The Current Code

### Current call sites that need to stop owning physics directly
Current physics is hardwired into:
- [los.py](/C:/dev/rfplanner/signalreach/los.py)
- [rf.py](/C:/dev/rfplanner/signalreach/rf.py)
- [backhaul.py](/C:/dev/rfplanner/signalreach/backhaul.py)
- [heatmap.py](/C:/dev/rfplanner/signalreach/heatmap.py)
- [asset_assign.py](/C:/dev/rfplanner/signalreach/asset_assign.py)
- [tiered_plan.py](/C:/dev/rfplanner/signalreach/tiered_plan.py)

### First integration step
Do not replace all those call sites one by one.

Instead:
1. create a new propagation service function, something like:
   - `solve_path(...)`
2. make the current lite model implement it first
3. migrate call sites to the service
4. add LR-ITM as a second implementation

This yields:
- no behavior change at first
- one seam for future model selection
- one place to layer clutter and moisture later

## How Vegetation And Structure Should Fit

### New data model we need
Before physics, we need data products.

The solver pack assumes:
- vegetation path samples
- structure path samples
- local terminal summaries

That means we need a clutter query layer capable of answering:
- along a TX→RX path, what vegetation is present?
- along a TX→RX path, what structures are present?
- near TX and RX, what local clutter surrounds the terminal?

### Minimal dry-world clutter model to build first
For the first real implementation, keep it simple and calibratable:

Vegetation:
- path fraction through vegetation
- mean or integrated penetration depth
- morph/density class
- terminal canopy embedment

Structures:
- path fraction through built areas
- integrated building penetration
- terminal structure embedment
- hard-block fraction for major blockers

### Recommended first formulas
Vegetation:
- use a saturating attenuation model similar to the pack
- keep coefficients band-dependent and morphology-dependent
- cap runaway loss so long paths through dense vegetation do not explode

Structures:
- separate:
  - soft obstruction / penetration loss
  - hard-block fraction
- hard-block fraction should feed uncertainty and advisories, not just loss

Local clutter:
- keep separate from along-path clutter
- make it depend on terminal embedment and node height

## Moisture Plan

### Recommendation
Do not implement moisture in the first integration wave.

What we should do now:
- create schema hooks
- create preset-only placeholder mode
- keep runtime disabled by default

### Why
The pack itself says moisture should be deferred.

That is the right call because moisture is only useful after:
- dry vegetation model exists
- local clutter model exists
- confidence plumbing exists

Otherwise we will be multiplying noise by more noise.

### First useful moisture activation
The first activation worth doing is:
- preset mode only
- canopy wetness modifier only

That means:
- no weather API dependency yet
- no soil model yet
- no atmospheric moisture yet

This gets a controllable scenario knob without pretending we have a validated
weather-driven field model on day one.

## PSI And Confidence

### Current state
Today confidence is mostly:
- fade margin -> logistic confidence
- plus some terrain roughness via LR-lite excess loss

### Future state
Confidence should be split into:
- signal confidence
- data confidence
- severity burden

The pack’s PSI concept is useful, but it should not be the first thing we ship.

Recommended sequence:
1. keep current fade-margin confidence
2. add data-confidence penalties from clutter source quality
3. add structure hard-block penalty into uncertainty/confidence
4. add PSI only after there are at least 3-4 physically meaningful burden terms

Otherwise PSI becomes a decorative scalar instead of a trustworthy planner signal.

## Concrete Implementation Phases

### Phase 0. Contract and wrapper pass
Deliverables:
- new propagation request/result dataclasses or pydantic models
- new `solve_path()` façade
- current lite model wrapped behind the façade
- no solver behavior change

Files likely touched:
- new `signalreach/propagation/*`
- [cli.py](/C:/dev/rfplanner/signalreach/cli.py)
- [los.py](/C:/dev/rfplanner/signalreach/los.py)
- [rf.py](/C:/dev/rfplanner/signalreach/rf.py)
- [backhaul.py](/C:/dev/rfplanner/signalreach/backhaul.py)
- [heatmap.py](/C:/dev/rfplanner/signalreach/heatmap.py)

### Phase 1. Full LR-ITM terrain backbone
Deliverables:
- selectable terrain model: `lite` vs `itm`
- full LR-ITM implementation behind the terrain contract
- parity tests between old and new interfaces
- configuration added to mission schema

Recommended schema additions:
- `rf.prop_model: "fspl" | "lr_lite" | "itm"`
- `rf_backhaul.prop_model: ...`
- optional LR-ITM environment/ground parameters if needed

Important rule:
- the old model remains available during rollout

### Phase 2. Clutter sampling infrastructure
Deliverables:
- vegetation grid/query interface
- structure grid/query interface
- path-sample to clutter-sample transform
- local terminal clutter summaries

No heavy physics yet.

Goal:
- prove we can reliably answer "what clutter is along this path?"

### Phase 3. Dry vegetation solver
Deliverables:
- coefficient table by frequency band and morphology
- saturating attenuation model
- local terminal canopy penalty
- debug outputs:
  - vegetation loss
  - path fraction
  - mean penetration
  - source confidence

### Phase 4. Dry structure solver
Deliverables:
- structure loss model
- hard-block fraction
- local terminal structure penalty
- debug outputs and advisories

### Phase 5. Synthesis, uncertainty, and advisories
Deliverables:
- total loss synthesis
- explicit uncertainty term
- planner-facing advisories
- terrain vs clutter attribution in outputs

This is where the result becomes user-explainable.

### Phase 6. Moisture hooks, still conservative
Deliverables:
- schema support
- preset mode only
- canopy wetness modifier only
- disabled by default

### Phase 7. Weather-driven moisture
Only after the dry model is validated.

## Mission Schema Changes We Should Add Early

### New top-level concepts
Recommended additions in [mission.py](/C:/dev/rfplanner/signalreach/mission.py):

- `propagation`
  - `terrain_model`
  - `vegetation_enabled`
  - `structure_enabled`
  - `moisture_enabled`
  - `confidence_enabled`
  - `psi_enabled`

- `clutter`
  - `vegetation_grid_id`
  - `structure_grid_id`
  - `source_confidence_mode`
  - `calibration_profile`

- `moisture`
  - `mode`
  - `preset`
  - future source config

### Why not stuff these into `rf.*`
Because they are not purely RF knobs.
They are propagation-stack and data-source knobs.

Keeping them separate will make the model easier to explain in the UI and less
confusing for calibration.

## UI Implications

### Build-side additions
Eventually the UI will need:
- terrain model selector:
  - Lite
  - LR-ITM
- clutter toggles:
  - vegetation
  - structures
- clutter source selection / status
- moisture mode:
  - off
  - preset
  - later weather-driven

### Results-side additions
Results will need attribution, not just one confidence number.

At minimum:
- terrain burden
- vegetation loss
- structure loss
- local clutter loss
- environmental loss
- uncertainty contribution

Otherwise users will not understand why LR-ITM plus clutter changed a result.

## Validation Strategy

### For LR-ITM
Need:
- fixed regression geometries
- known expected behavior for LOS, partial obstruction, and long-path cases
- comparison against trusted ITM outputs

### For vegetation/structure
Need:
- calibration scenarios with controlled geometry
- dry-world validation before moisture
- paired cases:
  - same path
  - different clutter state or morphology

### For moisture
Need:
- paired wet/dry scenarios with same geometry and foliage state

## Recommended First Build Order

If we start tomorrow, this is the order I would use:

1. Add propagation request/result contract
2. Wrap current lite model behind it
3. Add LR-ITM implementation as an alternate terrain backbone
4. Add clutter sampler interfaces with no physics
5. Add dry vegetation loss
6. Add dry structure loss
7. Add synthesis + debug attribution
8. Add confidence/uncertainty extensions
9. Add advisories
10. Add preset-only moisture hooks

## Bottom Line

The pack is directionally right, but it is architecture, not implementation.

The safest way to integrate it with the current solver is:
- first create a propagation contract
- then install LR-ITM as a swappable terrain backbone
- then add dry clutter realism
- only then add moisture as a modifier layer

If we follow that order, we get:
- minimal breakage to existing solver consumers
- consistent use across recommendation, backhaul, and heatmaps
- a realistic path to calibration and validation
- a system that remains explainable to users
