# SignalReach Architecture Guide

## Purpose
This guide explains how the project is put together at a systems level.

It is meant to answer:
- What are the major runtime pieces?
- How does a mission move from UI input to solver output?
- Where do artifacts come from?
- How do Build mode and Results mode differ in the browser?
- Which files are the best entry points for a new engineer?

## High-Level Shape
SignalReach has three main layers:

1. Mission authoring and result inspection in the browser UI
2. HTTP server glue that stages inputs and serves run artifacts
3. The Python solver pipeline that performs terrain, LOS, RF, optimization, and export work

In practice, the UI does not perform solver math. It builds a mission document, sends it to the backend, and renders the resulting artifacts.

## Core Runtime Components

### 1. Browser UI
Main files:
- [app.js](/C:/dev/rfplanner/ui/server/static/app.js)
- [index.html](/C:/dev/rfplanner/ui/server/static/index.html)
- [styles.css](/C:/dev/rfplanner/ui/server/static/styles.css)

Responsibilities:
- Collect Build-side inputs
- Serialize Mission JSON
- Manage map state, pick modes, AOI drawing, and editable tables
- List and render result artifacts
- Restore past runs into the current build session
- Keep Build and Results workspaces distinct

Important concept:
- The browser has two different concerns:
  - authoring a future solve
  - inspecting a completed solve
- The current UI represents this as `Build` and `Results` workspaces.

### 2. UI Server
Main file:
- [app.py](/C:/dev/rfplanner/ui/server/app.py)

Responsibilities:
- Serve the static UI
- Accept run requests
- Stage AOI and mission inputs into a run folder
- Launch the solver
- Capture and save `solver.log`
- Serve current and prior run artifacts
- Extract KML from KMZ when older artifacts need browser rendering

Important concept:
- The run directory is the unit of truth for a result.

### 3. Solver
Primary entrypoint:
- [cli.py](/C:/dev/rfplanner/signalreach/cli.py)

Supporting modules:
- [mission.py](/C:/dev/rfplanner/signalreach/mission.py)
- [dem.py](/C:/dev/rfplanner/signalreach/dem.py)
- [terrarium.py](/C:/dev/rfplanner/signalreach/terrarium.py)
- [propagation/](/C:/dev/rfplanner/signalreach/propagation)
- [candidate_engine.py](/C:/dev/rfplanner/signalreach/candidate_engine.py)
- [los.py](/C:/dev/rfplanner/signalreach/los.py)
- [rf.py](/C:/dev/rfplanner/signalreach/rf.py)
- [connected_opt.py](/C:/dev/rfplanner/signalreach/connected_opt.py)
- [tiered_plan.py](/C:/dev/rfplanner/signalreach/tiered_plan.py)
- [asset_assign.py](/C:/dev/rfplanner/signalreach/asset_assign.py)
- [backhaul.py](/C:/dev/rfplanner/signalreach/backhaul.py)
- [heatmap.py](/C:/dev/rfplanner/signalreach/heatmap.py)
- [export_kml.py](/C:/dev/rfplanner/signalreach/export_kml.py)

Responsibilities:
- Validate and adapt mission input
- Build demand points from the AOI
- Read terrain
- Route path solves through the propagation service seam
- Score candidate sites
- Optimize coverage and connectivity
- Apply inventory and tiered deployment logic
- Generate KML, PNG, and audit artifacts

## End-to-End Solver Flow

This is the normal path for a fresh run.

### Step 1. Mission ingestion
Handled in:
- [cli.py](/C:/dev/rfplanner/signalreach/cli.py)
- [mission.py](/C:/dev/rfplanner/signalreach/mission.py)

Flow:
- Read mission JSON from disk
- Parse optional raw compatibility settings
- Validate against the Pydantic mission model
- Apply protocol defaults
- Attach normalized runtime-only frontier config

Why it matters:
- This is where older JSON variants are adapted into the current runtime model without forcing the UI to know every legacy schema detail.

### Step 2. AOI and demand generation
Handled in:
- [kml.py](/C:/dev/rfplanner/signalreach/kml.py)
- [grid.py](/C:/dev/rfplanner/signalreach/grid.py)
- [must_cover.py](/C:/dev/rfplanner/signalreach/must_cover.py)

Flow:
- Load AOI polygon from KML
- Compute AOI bounds/statistics for logging
- Generate regularly spaced demand points inside the polygon
- Expand must-cover zones and points into explicit sampled targets

Important concept:
- The demand grid is the solver’s evaluation surface.
- Must-cover constraints are represented as a distinguished subset of those points.

### Step 3. Terrain access
Handled in:
- [dem.py](/C:/dev/rfplanner/signalreach/dem.py)
- [terrarium.py](/C:/dev/rfplanner/signalreach/terrarium.py)

Flow:
- Create a DEM reader from mission config
- Load elevation values from cloud or local Terrarium tiles
- Use one `elevation_m(lat, lon)` interface everywhere

Important concept:
- Terrain is not pre-expanded into a giant raster in memory.
- Most solver stages query elevation lazily by lat/lon.

### Step 4. Candidate generation
Handled in:
- [candidate_engine.py](/C:/dev/rfplanner/signalreach/candidate_engine.py)
- [topo.py](/C:/dev/rfplanner/signalreach/topo.py)
- [frontier_v2.py](/C:/dev/rfplanner/signalreach/frontier_v2.py)

Flow:
- Score terrain points by elevation, slope, ridge-ness, and combined topo score
- Apply minimum separation filtering
- Crest-snap local maxima
- Inject saddle-like candidates
- Add early backhaul plausibility scoring
- Optionally apply frontier guidance:
  - reorder existing candidates
  - inject new candidates near uncovered pockets
  - hybrid reorder-then-inject

Important concept:
- This stage produces a shortlist, not the final plan.

### Step 5. Geometry and RF evaluation
Handled in:
- [los.py](/C:/dev/rfplanner/signalreach/los.py)
- [rf.py](/C:/dev/rfplanner/signalreach/rf.py)
- [parallel_los.py](/C:/dev/rfplanner/signalreach/parallel_los.py)

Flow:
- Sample a manageable subset of demand points for LOS work
- For each candidate, compute:
  - terrain clearance
  - Fresnel margin
  - optional diffraction proxies
- If RF is enabled, convert those geometry results into:
  - RX power
  - fade margin
  - confidence

Important concept:
- LOS answers “is the path geometrically viable?”
- RF answers “how strong and robust is the link?”

### Step 6. Site selection
Handled in:
- [optimizer.py](/C:/dev/rfplanner/signalreach/optimizer.py)
- [connected_opt.py](/C:/dev/rfplanner/signalreach/connected_opt.py)
- [cli.py](/C:/dev/rfplanner/signalreach/cli.py)

Flow:
- Select a subset of candidate sites
- Optionally enforce connected topology or gateway reachability
- Produce the base deployment plan

Important concept:
- This stage chooses locations.
- It still does not assign specific inventory units unless later stages are enabled.

### Step 7. Tiered planning and asset assignment
Handled in:
- [tiered_plan.py](/C:/dev/rfplanner/signalreach/tiered_plan.py)
- [asset_assign.py](/C:/dev/rfplanner/signalreach/asset_assign.py)
- [anchors.py](/C:/dev/rfplanner/signalreach/anchors.py)

Flow:
- Inject fixed anchors
- Treat some selected sites as backbone candidates
- Assign available assets to those sites
- Add extenders when useful
- Decorate result sites with real deployment metadata:
  - asset type
  - heights
  - access chain
  - optional backhaul overrides

Important concept:
- Anchors are fixed input locations.
- Backbone/extender sites are selected output locations.
- Assets describe what hardware can populate those sites.

### Step 8. Output generation
Handled in:
- [export_kml.py](/C:/dev/rfplanner/signalreach/export_kml.py)
- [heatmap.py](/C:/dev/rfplanner/signalreach/heatmap.py)
- [backhaul.py](/C:/dev/rfplanner/signalreach/backhaul.py)
- [cli.py](/C:/dev/rfplanner/signalreach/cli.py)

Flow:
- Write recommendation KML
- Write heatmap PNG/KML artifacts
- Write backhaul link KML
- Write `audit.json`
- Save `solver.log`

Important concept:
- Exports are intentionally richer than the browser map rendering.
- KML artifacts carry metadata for audit and external tools, while the UI may suppress some of that noise in popups.

## Artifact Flow

### Run directory
Every solve produces a dedicated run folder under:
- `out/runs/<run_id>/`

Typical contents:
- `mission.json`
- `AOI.kml`
- `recommendation.kml`
- `backhaul_links.kml`
- `coverage.png`
- `coverage_overlay.kml`
- `coverage_mission.png`
- `coverage_mission_overlay.kml`
- `margin_mission.png`
- `legend.png`
- `legend_margin.png`
- `audit.json`
- `solver.log`

Not every run will have every file. Heatmaps and backhaul are conditional on settings and solver progress.

### Why the run directory matters
The run folder is the contract boundary between:
- the solver
- the UI
- external tools like Google Earth or TAK-style workflows

The UI should treat the run folder as an immutable result bundle.

### Audit file
Handled in:
- [cli.py](/C:/dev/rfplanner/signalreach/cli.py)

`audit.json` contains:
- mission/aoi references
- effective runtime settings
- selected sites
- metrics
- audit file references

Use it when you need machine-readable result structure.

### Solver log
Handled in:
- [ui/server/app.py](/C:/dev/rfplanner/ui/server/app.py)
- [cli.py](/C:/dev/rfplanner/signalreach/cli.py)

`solver.log` contains:
- phase-by-phase console output
- candidate summaries
- coverage/backhaul summary text
- warnings/errors
- tracebacks when runs fail

Use it when you need the narrative of the run rather than the structured summary.

## UI State Flow

The browser has two intentionally different modes.

### Build workspace
Purpose:
- Author the next solve setup

Primary UI responsibilities:
- Edit AOI, assets, anchors, must-covers, advanced settings
- Build mission JSON
- Launch solver
- Show a lightweight live solver activity panel

Map behavior:
- Supports pick modes
- Supports editable draft geometry
- Should stay visually clean when not inspecting results

### Results workspace
Purpose:
- Inspect one completed run

Primary UI responsibilities:
- Show past runs
- Load output artifacts
- Toggle recommendation/backhaul/heatmap layers
- Show coverage summaries, legends, and full logs
- Restore a prior run into Build

Map behavior:
- Should be inspection-safe
- Pick modes should not remain active
- Build-only draft overlays should not create confusion

### Why the split exists
Before the split, the same map and panel had to behave like:
- an editor
- a run inspector
- a past-run browser

That made it easy to:
- accidentally add anchors/must-covers while inspecting
- mix stale result overlays with a new build session
- lose track of whether the current panel represented input or output

The `Build | Results` split is the current solution to that ambiguity.

## Promotion Flow

“Promote a result into a new setup” means:
- read the prior run’s `mission.json`
- restore those settings into the current Build session
- restore the AOI into the editable setup
- clear rendered result layers from the live map
- switch to Build mode

It does not mean:
- delete old artifacts
- mutate the prior run directory

This is a copy-forward workflow, not an in-place edit of a historical run.

## Recommended File Entry Points

If you are new to the codebase, start here:

### To understand solver flow
1. [cli.py](/C:/dev/rfplanner/signalreach/cli.py)
2. [mission.py](/C:/dev/rfplanner/signalreach/mission.py)
3. [candidate_engine.py](/C:/dev/rfplanner/signalreach/candidate_engine.py)
4. [los.py](/C:/dev/rfplanner/signalreach/los.py)
5. [rf.py](/C:/dev/rfplanner/signalreach/rf.py)
6. [tiered_plan.py](/C:/dev/rfplanner/signalreach/tiered_plan.py)
7. [backhaul.py](/C:/dev/rfplanner/signalreach/backhaul.py)
8. [heatmap.py](/C:/dev/rfplanner/signalreach/heatmap.py)

### To understand browser behavior
1. [index.html](/C:/dev/rfplanner/ui/server/static/index.html)
2. [app.js](/C:/dev/rfplanner/ui/server/static/app.js)
3. [styles.css](/C:/dev/rfplanner/ui/server/static/styles.css)
4. [app.py](/C:/dev/rfplanner/ui/server/app.py)

### To understand output contracts
1. [export_kml.py](/C:/dev/rfplanner/signalreach/export_kml.py)
2. [heatmap.py](/C:/dev/rfplanner/signalreach/heatmap.py)
3. [backhaul.py](/C:/dev/rfplanner/signalreach/backhaul.py)
4. [docs/CURRENT_STATE.md](/C:/dev/rfplanner/docs/CURRENT_STATE.md)

## Known Design Conventions

### Schema stability over churn
The project prefers adapting legacy mission fields in code rather than forcing every client and preset to update immediately.

### Runtime state may be attached to the mission object
Examples:
- must-cover sampled indices
- frontier runtime config

These are solver conveniences, not canonical schema fields.

### UI rendering is not identical to KML export
The browser may hide:
- raw style keys
- some metadata noise
- auxiliary rings

That is deliberate. Export artifacts are audit-rich; the browser aims to be operationally readable.

### AOI, recommendation, backhaul, heatmaps are separate layers
This separation is intentional to prevent:
- label duplication
- color washout
- accidental visual stacking of unrelated overlays

## If You Are Debugging

### A solve looks wrong
Start with:
- `solver.log`
- `audit.json`
- [cli.py](/C:/dev/rfplanner/signalreach/cli.py)

### A site popup looks wrong
Check:
- result KML `ExtendedData`
- [export_kml.py](/C:/dev/rfplanner/signalreach/export_kml.py)
- browser popup filtering in [app.js](/C:/dev/rfplanner/ui/server/static/app.js)

### The map rendering looks wrong
Check:
- layer classification and popup/render logic in [app.js](/C:/dev/rfplanner/ui/server/static/app.js)
- CSS container behavior in [styles.css](/C:/dev/rfplanner/ui/server/static/styles.css)

### A run restores badly into Build
Check:
- run detail loading in [app.py](/C:/dev/rfplanner/ui/server/app.py)
- mission application and AOI restore logic in [app.js](/C:/dev/rfplanner/ui/server/static/app.js)

## Suggested Future Docs
Useful follow-on docs after this guide:
- a dedicated `SOLVER_PIPELINE.md` with even deeper phase-by-phase logic
- a `UI_STATE_MODEL.md` for map layers, pick state, and tab behavior
- a `RUN_ARTIFACTS.md` that enumerates every output file and its expected semantics
