isolation_forest_fire 6b9b59a411c9
Unsupervised anomaly detector for daily fire activity over the TerraSentinel study
regions. Trained with IsolationForest on 27 strictly causal
features β every rolling window ends at 1 preceding, so no feature can see the day it
scores.
Provenance
| Dataset commit | 5b4cd7cd6acb5026c75ff7b2b32fc317355d2c5b |
| Gold table | gold.gold_fire_anomalies |
| Rows | 1448 |
| Regions | greece_fire, iberia_fire |
| Date range | 2024-09-28 β 2026-09-21 |
| Reference rule | median/MAD z-score in gold.gold_fire_anomalies |
| Trained at | 2026-09-21T18:47:49.567139+00:00 |
| n_estimators | 300 |
| Contamination | 0.025 |
The dataset commit is the point of this table: it is what lets you answer "which data produced this model".
Evaluation
No labels exist, so accuracy and F1 are not reported β they would be fabricated. What was measured:
| Metric | Value |
|---|---|
| Flagged days (contamination budget) | 37.0000 |
| Mean detections, flagged slice | 2523.7568 |
| Mean detections, rest | 122.3806 |
| Ratio (separation) | 20.6222x |
| Agreement with the median/MAD rule | 1.0000 |
| Jaccard with the rule | 1.0000 |
| Score mass in the top slice | 0.0462 |
| Known-event regression | 1.0000 (6/6: four documented events caught, one negative control correctly unflagged, one caught by rank) |
Measured limitation β read this before using the score. At the serving threshold the model's flagged slice is identical to the statistical median/MAD rule in gold_fire_anomalies (37 of 37 days, Jaccard 1.0). The highest days are ~20x the rest, so they are trivially separable and every method finds the same ones. This model therefore adds no information over the rule on this dataset; it is a working baseline and a registry/scoring vehicle, not an improvement. Its value would appear where the rule is blind β fusing independent signals, or a lower flag rate β which needs the Sentinel deforestation series that is not yet backfilled.
Documented events used as a regression test
ml/validation/known_events.py holds real events with measured signatures. Two were
caught only by rank (below the serving threshold), which is reported rather than hidden:
- Iberia 2025-08-15, peak 13,329 detections (caught by flag)
- Greece 2025-08-12, peak 2,005 (caught by flag)
- Iberia 2026-02-24..27, peak 1,184 β out-of-season winter fires (caught by rank)
- Iberia 2026-07-03, peak 2,238 (caught by flag)
- Greece 2024-09-30, peak 808 (caught by rank)
- NEGATIVE CONTROL Iberia 2025-07-26, peak 172, z=-0.5 β correctly not flagged
Limitations
- Two regions and two years. ~1,450 training rows. This is a baseline, not a production dataset.
- No labels. Every number above is distributional or agreement-based. Nothing here is a precision or recall against truth.
- The top slice is trivially separable (see the measured limitation above).
- Instrument FRP is not comparable across sensors: MODIS mean FRP is ~100 MW where
VIIRS is ~15 MW for the same fires. The model consumes
frp_sumandfrp_per_detectionpooled across instruments, so intensity features carry an instrument-mix confound. - The percentile score is relative to the training distribution. A genuinely new regime (a year far outside the training range) will saturate the percentile.
Usage
from ml.bundle import load_bundle, score_to_percentile
bundle = load_bundle("path/or/hub/snapshot")
raw = -bundle.model.score_samples(X[bundle.feature_columns])
percentiles, comparable = score_to_percentile(bundle, raw)
Anomaly is percentile >= 0.9750.
Attribution
Fire detections: NASA FIRMS (MODIS and VIIRS active fire products). Sea-surface temperature precursor: NOAA OISST v2.1. Imagery (not used by this model, but by the pipeline): Copernicus Sentinel.