Causal reasoning is an active research direction at SpeckTech rather than a finished feature. The goal: build structural causal models (Judea Pearl's SCM framework, with do-calculus for counterfactuals) that explain why a pattern emerged, not just that it did. The page below describes the current prototype — what nodes the graph contains, which edges the rule base encodes, and how the intervention engine works. Output quality is bounded by the breadth of sources we ingest; expanding the source set is the main lever we're pulling, and the rule base will grow with it.
Two kinds of events feed the DAG. Exogenous roots are observable external conditions ingested directly from authoritative public feeds — these have no causes within the model. Endogenous events are patterns detected from live position data. Each event carries a confidence score, the entities involved, the H3 cell where it occurred, and supporting metrics.
| Event Type | Domain | Source / Detection Logic |
|---|---|---|
| Weather Alert | Weather | Exogenous — NWS active alerts (Severe / Extreme), one node per alert polygon |
| TFR Active | Airspace | Exogenous — FAA Temporary Flight Restrictions, one node per active TFR |
| Dark Vessel Gap | Maritime | > 15 minutes of silence in track history |
| Density Anomaly | Maritime / Aviation | Z-score ≥ 2.0 vs. historical baseline |
| Ground Stop Indicator | Aviation | > 70% of aircraft on ground (min 5) |
Detected events become nodes in a directed acyclic graph. Edges are discovered by matching event pairs against a domain knowledge rule base that encodes known causal relationships — for example, "severe weather drives vessels to anchor or reduce speed."
The rule base currently encodes ~15 cause→effect relationships. Active live examples (San Francisco + Chicago, sample observed):
| Cause (root) | Effect | Mechanism |
|---|---|---|
| weather_alert | ground_stop_indicator | Severe weather triggers airport ground stops |
| weather_alert | density_anomaly_high | Weather-driven delays back up vessels and aircraft into dense clusters |
| tfr_active | density_anomaly_low | Active TFR voids the affected airspace of normal traffic |
| tfr_active | ground_stop_indicator | Active TFR forces aircraft to delay, hold, or reroute |
| tfr_active | density_anomaly_high | Aircraft rerouting around an active TFR concentrates traffic in adjacent corridors |
strength = base_strength × temporal_decay × confidence_factor
Temporal decay penalizes events that are far apart in time (max gap: 6 hours). Confidence factor is the geometric mean of both events' confidence scores. Edges below strength 0.1 are discarded.
The graph is verified for acyclicity after each edge addition using NetworkX. This ensures the causal structure is always a valid DAG suitable for do-calculus operations.
Pearl's do-operator enables "what if" reasoning. Given a causal graph, the intervention engine can answer questions like: "If the active TFR were not in place, would the density anomaly in the adjacent corridor still have emerged?"
1. Mutilate the graph — remove all incoming edges to the intervention target
2. Force the target node to a counterfactual value (typically zero)
3. Propagate effects through all descendants along causal paths
4. Report which downstream events would be affected and by how much
The causal graph is serialized into a compact, token-efficient format and passed to the on-device Foundation Model. The FM generates a natural language narrative explaining the causal chain — turning graph structure into human-readable intelligence.
| Endpoint | Returns |
|---|---|
/api/causal/layer?region={name} | Geographic DAG — nodes carry lat/lng for map rendering, suitable for client overlays. Region optional; omit for all active regions. |
/api/causal/{h3_cell} | Per-cell causal graph with nodes, edges, and counterfactual interventions |
/api/causal/{h3_cell}?compact=true | Token-optimized compact format for FM consumption |
The DAG cannot change faster than its slowest input. Weather refreshes
server-side every 5 min, TFRs every 15 min, ADS-B every 45s, AIS streams
continuously. Polling /api/causal/layer faster than
2 minutes returns identical data. Skip the poll
entirely when the layer is hidden.