# SANGAM (संगम): open sensor-fusion SDK and common operating picture

SANGAM fuses radar, thermal and low-light cameras, acoustic nodes, AIS-like transponders and
fence sensors from any vendor into one set of tracks and alerts. Sensor makers write one adapter
to five open JSON records; command systems read tracks and alerts back. It runs on-premise or
air-gapped. Alerts are recommendations for a trained operator, who verifies and decides.

Everything SANGAM shows today is simulated and labelled SIMULATED: a seeded night on the
Ichamati between Taki and Hasnabad, on real OpenStreetMap geometry and real Open-Meteo weather.

- Console: `/dashboard/sangam/` (COP replay), `metrics.html`, `sdk.html`, `methods.html`
- Product page: `/products/sangam.html`
- Schemas: `docs/sangam/schema/` (also bundled in `dashboard/data/sangam/schemas.json`)
- Pipeline and SDK: `pipeline/trinetra/sangam/`, exports in `dashboard/data/sangam/`
- Tests: `tests/test_sangam.py` (offline, fixtures in `tests/fixtures/sangam/`)

## 1. Users and the decisions they make

| User | Decision SANGAM supports |
|---|---|
| System integrators | How to connect a new sensor once, instead of once per command system |
| Sensor makers | What their sensor adds to a fused picture, scored the same way as everyone else's |
| Technical wings and control rooms | Which objects on a river stretch need attention now, and which alerts are safety warnings for crews |
| Command and decision support systems | How to receive tracks and alerts: pull, push or file, including across an air gap |
| Evaluators and researchers | Whether fusion beats each sensor alone, by how much, and where it fails |

The research behind the product: the Smart Border Project announced on 22 May 2026 (drones,
radars and smart cameras along about 6,000 km, pilots at 7 to 8 sites) builds on CIBMS, which the
Ministry of Home Affairs defines as the integration of manpower, sensors, networks, intelligence
and command and control. Pilots that buy sensors from several makers need fusion that works across
all of them. See `docs/research/2026-10-frontier-brief.md` and
`docs/research/2026-10-market-and-procurement.md`.

## 2. What it does, page by page

- **COP replay.** A Leaflet map of the reach with play, pause, scrub, speed and keyboard control;
  layers for raw detections by sensor type, fused and tentative tracks, 2-sigma uncertainty,
  60 s trails, ground truth, the river, the boundary and setback line, zones and the fence line,
  sensor sites and coverage; a track list with class, confidence and the
  sensors behind each track; a selected-track panel; an alert log that jumps the replay to each
  alert; the night's conditions; the sensor list; and the simulation script. SIMULATED badge on
  the map and the page.
- **Fusion metrics.** Six configurations on identical sensor output (all sensors fused, radar,
  thermal, low-light, transponders, and all sensors with equal classifier trust): OSPA over time
  and split into localisation and cardinality, track counts against objects present, MOTA,
  identity switches, false tracks, time to confirm, continuity, class accuracy, classified
  coverage, a confusion matrix, alert recall, precision and delay by rule, realised detection
  probability per sensor and class, and every fused failure listed.
- **SDK.** The five schemas field by field, conventions, a Python quickstart, adapter patterns,
  engine settings, northbound integration patterns, validation, real records from the engine,
  and downloads.
- **Methods.** Sources, geography, conditions, ground truth, sensor models, the filter, gating,
  lifecycle, class fusion, timing, alert rules and policies, metrics, configurations, units,
  doctrine, limits, reproduction and references. Its tables read the current export.

## 3. Open records

JSON Schema 2020-12, version 1.0. Every record carries `type`, `schema_version` and, when
synthetic, `"simulated": true`. Minor versions only add optional fields.

| Record | Holds |
|---|---|
| `Detection` | One report from one sensor at one time: `position` (lat, lon, covariance in m²) or `polar` (range and bearing with sigmas, from a surveyed origin; bearing-only allowed), optional velocity, `classification {label, confidence}`, attributes |
| `Track` | A fused object: status (`tentative`, `confirmed`, `coasting`, `deleted`), position and velocity with covariance, class and class probabilities, contributing sensors, transponder id if any, speed and course |
| `SensorStatus` | Health (`ok`, `degraded`, `offline`), site, coverage, rate and latency of one sensor |
| `Alert` | Rule, level (`normal`, `watch`, `warning`, `danger`), track, position, message, advisory, safety flag, line or zone |
| `Zone` | A polygon or line with its purpose and alert policy (classes, minimum confidence, registered vessels excluded, level by class, direction) |

Conventions: WGS84 decimal degrees; covariance `[[ee, en], [ne, nn]]` in m² in an east-north
frame at the reported point; bearings in degrees clockwise from true north; velocity over ground
in m/s; times ISO 8601 UTC with `Z`, at measurement; classes `small_boat`, `large_vessel`,
`person`, `animal`, `debris`, plus `unknown` for sensor reports. No record type holds personal
identity.

## 4. SDK

`pipeline/trinetra/sangam/`, Python 3.11 with numpy and scipy, no network needed.

| Module | Role |
|---|---|
| `schema.py` | The schemas and a dependency-free validator (agrees with the `jsonschema` reference implementation in tests) |
| `sdk.py` | Record dataclasses, semantic checks, in-memory `Bus` that validates on publish, `JsonlSink`, time helpers |
| `geo.py` | WGS84 local ENU frame and 2-D geometry |
| `kalman.py`, `assoc.py`, `tracker.py`, `classify.py`, `alerts.py`, `fusion.py` | The engine |
| `examples/quickstart.py` | Two boats, a radar, a transponder and a virtual line, end to end |
| `examples/vendor_adapter.py` | NMEA 0183 TTM radar targets to Detection records, with checksum and unit handling |

Northbound patterns (offered as contracts; no compatibility with any deployed system is claimed):
pull (`GET /v1/tracks?since=<cursor>`, JSONL plus next cursor), push (webhook or MQTT topics
`sangam/alerts`, `sangam/tracks`, `sangam/sensor_status`, at least once, de-duplicate by `id`),
file (hourly JSONL with SHA-256 in a manifest, for air gaps), and display (this console).
Operator actions go to the SAKSHI audit chain, never back into the engine.

## 5. Data

| Source | Use | Licence | Notes |
|---|---|---|---|
| OpenStreetMap via Overpass API | Boundary ways (admin_level 2), Ichamati centreline, river water areas in 22.535 to 22.635 N, 88.905 to 88.985 E | ODbL 1.0 | Indicative only; raw response cached under `pipeline/cache/sangam/` |
| Open-Meteo Forecast API | Hourly temperature, humidity, dew point, precipitation, cloud, visibility and wind for the scenario hour | CC BY 4.0 | Interpolated to the middle of the scenario; visibility is a model estimate |
| Computed | Sun and moon altitude and the moon's lit fraction | Not applicable | Low-precision almanac series |
| SANGAM simulator | Objects, sensor sites, zones, detections, tracks, alerts | Seeded (20261020) | Labelled SIMULATED in the UI and `"simulated": true` in JSON |

A failed fetch falls back to the cached response and marks the source stale in the manifest; if
neither is available, geography comes from the previous export and weather from a typical clear
post-monsoon night, flagged `fallback`.

## 6. Methods

The console's methods page has every formula with its parameters; this is the summary.

### 6.1 Frame and filter

Local east-north plane at 22.59 N, 88.94 E, converted exactly through WGS84 ECEF (Bowring's
inverse with two Newton steps). State `[e, n, v_e, v_n]`; nearly constant velocity with
white-noise acceleration (Bar-Shalom, Li and Kirubarajan 2001, section 6.2.2):

    F(dt) = [[I, dt I], [0, I]]
    Q(dt) = q [[dt^3/3 I, dt^2/2 I], [dt^2/2 I, dt I]]

q is class-aware (m²/s³): small boat 0.30, large vessel 0.15, person 0.03, animal 0.04,
debris 0.02, mixed by the track's class probabilities, floor 0.02, multiplied by 12 while a
boat-like track manoeuvres. Measurement models: position (transponder, fence), range and bearing
(radar, cameras, extended Kalman filter), bearing only (acoustic; updates but never starts a
track). Joseph-form updates; angle residuals wrapped to (-pi, pi]. Tracks start from one report
with zero velocity and 4 m/s sigma, within 900 m of the river centreline.

### 6.2 Gating and assignment

Squared Mahalanobis distance of the innovation against the chi-square 99 % quantile: 9.21 for
2-D reports, 6.63 for bearings. Global nearest neighbour per sensor scan, solved exactly with
`scipy.optimize.linear_sum_assignment`, cost `d² + ln det S`. Confirmed and coasting tracks
claim reports before tentative ones. A report whose runner-up cost is within 4 of its best
updates kinematics only. Transponder reports get -8 against a track with the same identifier and
+8 against a different one; identity is the vote of the last three unambiguous reports.

### 6.3 Track lifecycle

| Transition | Condition |
|---|---|
| tentative to confirmed | 3 hits in the last 5 opportunities |
| tentative to deleted | cannot reach 3 of 5, or silent for max(6 s, 2 revisits) |
| confirmed to coasting | no position update for max(3 s, 1.5 revisits) |
| coasting to confirmed | 2 position updates within 3 s |
| coasting to deleted | silent for max(20 s, 3 revisits), or 1-sigma position error over 150 m |
| any to deleted | merged: within 8 m and 1 m/s of another track for 4 cycles |

Misses count only for a healthy radar or camera that has seen the track and covers its predicted
position. Transponders, acoustic nodes and the fence never count misses.

### 6.4 Classification fusion

Per sensor type, a confusion matrix C (true class by reported label). A report r with
confidence w gives likelihood `L(c) = w C[c, r] + (1 - w) / 5`; the posterior is updated by
Bayes' rule with a 0.005 floor. Speed gives a soft upper limit per class (15, 10, 1.6, 2.0 and
1.2 m/s), applied at read time only. The engine uses matrices estimated from a simulated labelled
trial (150 reports per class, Laplace smoothing), never the simulator's true matrices. Policies
treat confidence below 0.5 as unclassified.

### 6.5 Timing

A 3 s fixed-lag reorder buffer: reports are processed in measurement-time order once they are
3 s old; later arrivals are dropped and counted (7 of 24,094 on the published night). The engine
steps at 2 Hz. Alert rules run on the state at the buffer's watermark, so every alert is at
least 3 s after the moment it describes.

### 6.6 Alert rules

| Rule | Fires when | Level |
|---|---|---|
| Setback line | A confirmed track changes side of a line 137.16 m (150 yards) inside the boundary, counting a side only beyond max(8 m, 1.5 sigma) | Danger for boats, warning for people, watch otherwise |
| Zone entry | Inside for 2 cycles after 2 outside; re-armed after 30 s outside | Warning; watch for animals and unclassified |
| Loitering | 120 s with 90 % of positions within 25 m, net movement under 25 m, within 300 m of the boundary | Watch |
| Boundary proximity (safety) | A registered vessel within 60 m of the boundary or across it; re-armed beyond 150 m | Watch; warning across |
| Person in water (safety) | A person-class track (confidence 0.5 or more) on water for 60 s | Watch |

Only confirmed tracks with at least 6 position updates fire. Registered vessels are excluded from
intrusion rules. Person-in-water and loitering repeats within 600 s and 150 m are recorded as
duplicates. Suppressed firings are kept with their reason. The 150-yard distance is the one
named at the 57th BGB-BSF chiefs' talks for a halt to unauthorised construction near the border
(The Financial Express, 16 Jun 2026); here it is only a notional alerting line.

### 6.7 Scenario and sensor models

Fourteen objects on a 0.25 s grid between 21:30 and 21:48 IST on 20 October 2026: three
registered fishing boats (one drifts toward the boundary while its transponder is silent for
four minutes), a ferry, an unregistered small boat that waits beyond the boundary, crosses to a
landing point and returns, two swimmers who land and walk inland across the fence, three cattle
swum across with a herder, and three pieces of debris; plus a bird flock and clutter. Swimmers,
animals and debris drift on an assumed 0.32 m/s ebb current.

Ten notional sensors: an X-band radar (Swerling I, `Pd = Pfa^(1/(1+SINR))`, sea clutter
`sigma0(dB) = -48 + 2.5 U10`, plots merged within one resolution cell), two thermal and two
low-light cameras on 18 m towers (logistic detection around a class range r50 limited by thermal
contrast, moonlight and Koschmieder visibility; range from the depression angle, so its error
grows as range squared over height), three acoustic nodes (engines only, bearings only), a
transponder receiver (10 s reports, 8 m noise, 3 % loss, 0.5 to 2.6 s delay) and a fence sensor
(25 m cells, Pd 0.95, 6 nuisance alarms an hour). Each sensor has its own random stream, so
changing one never changes another's draws. The south thermal camera goes offline for 60 s.

### 6.8 Metrics

OSPA with cut-off 100 m and order 2 (Schuhmacher, Vo and Vo 2008), split into localisation
and cardinality; CLEAR MOT with a 50 m threshold (Bernardin and Stiefelhagen 2008): MOTA,
MOTP, identity switches, fragmentations; false tracks (matched under half their life), time to
confirm (from first detection by any sensor in the configuration), continuity, class accuracy
and classified coverage (object time both tracked and correctly labelled). Alerts are scored
against expected events from ground truth under the same rules: true within 75 m, at most 30 s
early and 120 s late (no late limit for conditions that persist).

## 7. Results on the simulated night

Seed 20261020, as built on 21 Oct 2026. One night, so these compare configurations; they do not
predict field performance.

| Configuration | Mean OSPA | MOTA | Objects tracked | Tracked and labelled right | Alert recall | Alert precision |
|---|---|---|---|---|---|---|
| All sensors fused | 36.9 m | 0.86 | 14 of 14 | 84 % | 94 % (15 of 16) | 100 % |
| Radar only | 57.4 m | 0.68 | 12 | 64 % | 69 % | 52 % |
| Thermal cameras only | 58.8 m | 0.60 | 12 | 59 % | 63 % | 50 % |
| Low-light cameras only | 62.0 m | 0.60 | 11 | 60 % | 38 % | 40 % |
| Transponders only | 83.8 m | 0.29 | 4 | 29 % | 6 % | 100 % |
| All sensors, equal trust | 36.9 m | 0.86 | 14 | 84 % | 94 % | 94 % |

Fused median time to confirm is 4.4 s (thermal alone 5.2 s, radar alone 14.0 s). Fusion's costs:
11 identity switches, mostly inside the cattle group, and 2 short false tracks (6.7 an hour).
The one missed alert is the unregistered boat's outbound crossing of the setback line on its way back. Per-sensor class trust changes little on this
night except alert precision: with equal trust one alert is false.

## 8. Safeguards (doctrine)

- Events and objects, not people: a track is an object and its motion; no record holds a name,
  a face, a phone number or any personal identifier. Transponder identifiers name a device and
  are synthetic here (`SIM-TX-0001`).
- No identity targeting: rules use place, motion and object class only.
- Safety first: registered fishing boats near the boundary get a crew-safety advisory, and a
  person in the water raises a rescue-readiness alert, alongside the intrusion rules.
- Human in the loop: alerts recommend; an operator verifies on camera and decides; operator
  actions go to the SAKSHI audit chain. Nothing triggers an action by itself.
- Defensive by design: detection, tracking, classification and alerting; no targeting or
  effector guidance.
- Sovereign: runs on-premise or air-gapped with no foreign cloud dependency.
- Simulated data is labelled SIMULATED in the UI and `"simulated": true` in every record.

## 9. Limits and failure modes

1. Close groups break identities: four swimmers 8 to 20 m apart are closer than camera range
   error, and global nearest neighbour with constant velocity cannot hold them apart. JPDA or
   multiple-hypothesis tracking would help; neither is implemented.
2. Camera range by monoplotting degrades as range squared; waves, tide, mast sway and a wrong
   water level make it worse. The simulator assumes flat water at a known level.
3. No occlusion, multipath or radar shadowing.
4. Classifier matrices, detection ranges, clutter rates and the current are assumptions, not
   measurements of any product or of this river.
5. One clear night and one seed: fog and rain terms barely act, and small differences between
   configurations can come from chance.
6. Alerts are late by design (3 s buffer plus confirmation); reports later than the buffer are
   dropped.
7. Transponder spoofing, swapped devices and jamming are not modelled.
8. The OpenStreetMap boundary and banks are approximate, and the river moves (see NADI). The
   setback line has no legal standing.

## 10. Validation

`python3 -m pytest tests/test_sangam.py -q` runs offline. The tests cover: schema examples and
violations, agreement with the `jsonschema` reference validator, record round trips and semantic
checks, the bus and JSONL sink; ECEF and local-frame round trips (centimetres over 8 km) and
curvature radii; CV matrices, the polar Jacobian against finite differences, bearing wrap,
filter consistency (NEES near the state dimension) and polar-to-Cartesian covariance against
Monte Carlo; chi-square gates, GNN against greedy assignment, gate respect; calibrated matrices,
reliability weighting, posterior recovery, kinematic limits; M-of-N confirmation, coasting,
deletion, clutter death, resumption, miss rules, transponder identity votes, crossings without
identity loss, merging, bearing-only rules; every alert rule and its hysteresis, re-arming and
suppression; the reorder buffer and arrival-order independence; Overpass and Open-Meteo fixtures,
moon phases and sunset; OSPA reference values and optimal assignment, CLEAR MOT switches and
fragmentation; deterministic ground truth and sensors; replay round trips; the data contract of
the committed exports; and both SDK examples.

## 11. Exports

| File | Contents |
|---|---|
| `manifest.json` | Provenance: sources with retrieval times and stale flags, seed, scenario, file sizes, checks |
| `scenario.json` | Geography, setback line, fence, zones, sensors, sensor status changes, objects, script, conditions |
| `replay.json` | 2 Hz frames: ground truth, fused tracks (status, class, confidence, sigma, sources, transponder, filter velocity in 0.2 m/s steps) and raw detections by sensor; positions as delta-encoded 1e-5 degree integers (about 1.1 MB) |
| `alerts.json` | Fused alerts, suppressed firings with reasons, and alert scoring |
| `metrics.json` | Series and summaries for all configurations, per object, confusion, failures, sensor detection probability, reliability matrices |
| `schemas.json` | The schema documents in one bundle |
| `sample_stream.jsonl` | Ten seconds of every record type around the unregistered boat's crossing |

Every file is under 1.5 MB; the pipeline refuses to write one that is not.

## 12. Reproduce

    cd pipeline
    python3 -m trinetra.sangam run              # fetch, simulate, fuse, score, export
    python3 -m trinetra.sangam run --offline    # same exports from cached responses
    python3 -m trinetra.sangam run --seed 7     # another night with the same script
    python3 -m trinetra.sangam validate FILE.jsonl
    python3 -m trinetra.sangam examples
    cd .. && python3 -m pytest tests/test_sangam.py -q

With the same seed and cached inputs the exports are byte-identical; only the manifest's build
time changes.

## 13. Next

- Many seeds and weathers (fog, rain, wind) with confidence intervals on every score.
- JPDA or MHT association for close groups; an interacting multiple model filter for boats.
- Occlusion from banks and vegetation; tide and a tidal current from a hydrodynamic model.
- Adapters for real sensor protocols (ASTERIX radar plots and tracks, ONVIF analytics metadata for
  cameras, AIS AIVDM sentences) and a replay of recorded field logs.
- A SAKSHI-chained operator acknowledgement loop in the COP.

## References

- Y. Bar-Shalom, X. R. Li, T. Kirubarajan. Estimation with Applications to Tracking and
  Navigation. Wiley, 2001. https://doi.org/10.1002/0471221279
- S. Blackman, R. Popoli. Design and Analysis of Modern Tracking Systems. Artech House, 1999.
- D. F. Crouse. On implementing 2D rectangular assignment algorithms. IEEE TAES 52(4), 2016.
  https://doi.org/10.1109/TAES.2016.140952
- D. Schuhmacher, B.-T. Vo, B.-N. Vo. A consistent metric for performance evaluation of
  multi-object filters. IEEE TSP 56(8), 2008. https://doi.org/10.1109/TSP.2008.920469
- K. Bernardin, R. Stiefelhagen. Evaluating multiple object tracking performance: the CLEAR MOT
  metrics. EURASIP JIVP, 2008. https://link.springer.com/article/10.1155/2008/246309
- P. Swerling. Probability of detection for fluctuating targets. IRE Trans. IT 6(2), 1960.
  https://doi.org/10.1109/TIT.1960.1057560
- F. E. Nathanson. Radar Design Principles, 2nd ed. McGraw-Hill, 1991.
- ITU-R P.838-3. Specific attenuation model for rain, 2005.
  https://www.itu.int/rec/R-REC-P.838-3-200503-I/en
- B. R. Bowring. Transformation from spatial to geographical coordinates. Survey Review 23(181),
  1976. https://doi.org/10.1179/sre.1976.23.181.323
- US Naval Observatory. Approximate solar coordinates. https://aa.usno.navy.mil/faq/sun_approx
- Open-Meteo Forecast API. https://open-meteo.com/en/docs
- OpenStreetMap copyright and licence. https://www.openstreetmap.org/copyright
- JSON Schema 2020-12. https://json-schema.org/draft/2020-12
- ETV Bharat, 22 May 2026, Smart Border Project.
  https://www.etvbharat.com/en/bharat/india-to-launch-smart-border-project-along-pak-bdesh-borders-to-curb-infiltration-shah-enn26052202509
- The Tribune, 6 Jun 2026, Smart Border pilots.
  https://www.tribuneindia.com/news/india/pilot-smart-border-project-to-be-launched-by-year-end-amit-shah/
- The Financial Express (Bangladesh), 16 Jun 2026, BGB-BSF talks and the 150-yard distance.
  https://thefinancialexpress.com.bd/national/bgb-bsf-talks-focus-on-border-killings-push-ins-security-cooperation
