From 6dde669510a54c2c66aadd6428a8944888496fef Mon Sep 17 00:00:00 2001 From: DanieCuevas <43822444+DanielCuevas1208@users.noreply.github.com> Date: Tue, 28 Jul 2026 17:34:22 -0700 Subject: [PATCH] feat: extend tiny anomaly lab --- .github/workflows/ci.yml | 7 + README.md | 76 ++++- examples/sample_run/flagged_rows.csv | 10 + examples/sample_run/model_card.md | 7 +- examples/sample_run/report.html | 2 +- examples/sample_run/scores.csv | 313 ++++++++++++++++++ .../sample_run_mahalanobis/flagged_rows.csv | 7 + examples/sample_run_mahalanobis/model_card.md | 71 ++++ examples/sample_run_mahalanobis/report.html | 7 + examples/sample_run_mahalanobis/scores.csv | 313 ++++++++++++++++++ examples/sample_run_zscore/flagged_rows.csv | 5 + examples/sample_run_zscore/model_card.md | 7 +- examples/sample_run_zscore/report.html | 2 +- examples/sample_run_zscore/scores.csv | 313 ++++++++++++++++++ tests/test_experiment.py | 19 ++ tests/test_export.py | 111 +++++++ tests/test_model_card.py | 8 + tests/test_models.py | 46 ++- tests/test_report.py | 29 ++ tiny_anomaly_lab/__main__.py | 6 +- tiny_anomaly_lab/experiment.py | 33 ++ tiny_anomaly_lab/export.py | 99 ++++++ tiny_anomaly_lab/model_card.py | 24 ++ tiny_anomaly_lab/models.py | 64 +++- tiny_anomaly_lab/report.py | 78 ++++- 25 files changed, 1617 insertions(+), 40 deletions(-) create mode 100644 examples/sample_run/flagged_rows.csv create mode 100644 examples/sample_run/scores.csv create mode 100644 examples/sample_run_mahalanobis/flagged_rows.csv create mode 100644 examples/sample_run_mahalanobis/model_card.md create mode 100644 examples/sample_run_mahalanobis/report.html create mode 100644 examples/sample_run_mahalanobis/scores.csv create mode 100644 examples/sample_run_zscore/flagged_rows.csv create mode 100644 examples/sample_run_zscore/scores.csv create mode 100644 tests/test_export.py create mode 100644 tiny_anomaly_lab/export.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4fc01dc..dac0982 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,13 @@ jobs: python -m tiny_anomaly_lab --output ci_output test -f ci_output/report.html test -f ci_output/model_card.md + test -f ci_output/scores.csv + test -f ci_output/flagged_rows.csv + - name: Run the Mahalanobis detector demo + run: | + python -m tiny_anomaly_lab --detector mahalanobis --output ci_output_mah + test -f ci_output_mah/report.html + test -f ci_output_mah/flagged_rows.csv - name: Upload artifacts if: always() diff --git a/README.md b/README.md index 9be9af4..8fd0383 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ An explainable anomaly-detection workbench for small time-series datasets. It runs one leakage-safe experiment on bundled data and writes a reproducible interactive report. The lab shows feature pipelines, evaluation without leakage, -model cards and interactive visualization. +model cards, per-feature contributions and interactive visualization. ## Scope @@ -28,8 +28,9 @@ secret or network call is needed for the demo or for the tests. | `data.py` | Loads the bundled CSV and validates the schema. | | `features.py` | Builds causal rolling and lag features. | | `split.py` | Splits the feature frame by time only. | -| `models.py` | Holds the detector registry and the scorers. | +| `models.py` | Holds the detector registry, scorers and contributions. | | `evaluate.py` | Computes ranking and threshold metrics. | +| `export.py` | Writes scores and flagged rows to CSV. | | `model_card.py` | Renders a Markdown model card. | | `report.py` | Writes a self-contained Plotly HTML report. | | `experiment.py` | Runs one experiment from start to finish. | @@ -37,8 +38,8 @@ secret or network call is needed for the demo or for the tests. The experiment pulls these modules into one pipeline. The data step loads the CSV. The feature step adds causal rows. The split step cuts by time. The model -step fits the detector. The evaluate step scores and flags rows. The report step -writes the artifacts. +step fits the detector and the contribution method. The evaluate step scores +and flags rows. The report and export steps write the artifacts. ## Leakage-safety model @@ -87,22 +88,47 @@ Or call the package as a module. python -m tiny_anomaly_lab --output output ``` -The command writes `output/report.html` and `output/model_card.md`. Open the -report in a web browser. No server and no network are needed. +The command writes `output/report.html`, `output/model_card.md`, +`output/scores.csv` and `output/flagged_rows.csv`. Open the report in a web +browser. No server and no network are needed. ## Detectors -Two detectors ship with the lab. +Three detectors ship with the lab. - `isolation_forest` wraps the scikit-learn Isolation Forest. - `zscore_rule` scores the largest standardised residual. +- `mahalanobis` scores the correlation-aware Mahalanobis distance. Pick the detector with a flag. ```bash -tiny-anomaly-lab --detector zscore_rule --output output +tiny-anomaly-lab --detector mahalanobis --output output ``` +## Local feature contribution + +Each detector can attribute its row score to the input features. The lab uses +this in two places: a contribution panel in the report and a `top_feature` +column in the flagged-rows CSV. + +- `zscore_rule`: contribution is the absolute standardised residual. The + largest value marks the feature that drove the score. +- `mahalanobis`: contribution is a signed exact split of the squared distance. + Row contributions sum to the squared score. A negative value shows a feature + that offsets the distance through correlation. +- `isolation_forest`: no additive decomposition ships with the lab. The report + omits the contribution panel for this detector. + +## CSV export + +Two CSV files sit next to the report. + +- `scores.csv` holds every feature-frame row with its score, flag and a + `contrib_` column per feature. +- `flagged_rows.csv` holds the flagged evaluation rows with the raw value + columns, the score, the threshold and the dominant contributor. + ## Configuration | Flag | Default | Meaning | @@ -139,6 +165,8 @@ ROC AUC: 0.7082 Average precision: 0.3970 Flagged 9 rows in evaluation (TP=3, FP=6). Report: output\report.html Model card: output\model_card.md +Scores: output\scores.csv +Flagged rows: output\flagged_rows.csv ``` This output comes from the z-score rule run. @@ -149,11 +177,22 @@ ROC AUC: 0.7151 Average precision: 0.5175 Flagged 4 rows in evaluation (TP=3, FP=1). ``` +This output comes from the Mahalanobis run. + +```text +Detector: mahalanobis +ROC AUC: 0.6938 Average precision: 0.4756 +Flagged 6 rows in evaluation (TP=3, FP=3). +``` + A committed sample lives under `examples/sample_run/`. Open -`examples/sample_run/report.html` to view the interactive figure. +`examples/sample_run/report.html` to view the interactive figure. The +`examples/sample_run_mahalanobis/` folder holds the same artifacts for the +Mahalanobis detector, including a contribution panel and signed contributions +in `flagged_rows.csv`. The model card records the detector, the data windows, the leakage controls, the -metrics, the intended use and the limitations. +metrics, the per-feature contribution method, the intended use and the limitations. ## Tests @@ -170,14 +209,18 @@ ruff check . ``` The suite covers data loading, causal features, the split, the metrics, the -detectors, the experiment, the model card and the report. +detectors, the per-feature contributions, the CSV export, the experiment, the +model card and the report. ## Limitations The bundled dataset is synthetic and small. Results are illustrative. Isolation Forest scores shift when the random state or the scikit-learn version changes. Causal rolling features lag true change points by the window length. The -detector assumes the calibration window is close to normal operation. +detector assumes the calibration window is close to normal operation. The +Mahalanobis covariance uses a shrinkage estimate that trades bias for stability +on small windows, and its signed contributions can hide a feature behind a +correlated partner. ## Project layout @@ -194,10 +237,15 @@ examples/ Committed sample artifacts. Later releases stay independent of this one. +Done in this release: + - Add a multivariate detector beside the two current scorers. -- Add a stream evaluation that replays rows one at a time. -- Add local feature contribution for the z-score rule. +- Add local feature contribution for the z-score rule and the new detector. - Add CSV export of the flagged rows and the scores. + +Still open: + +- Add a stream evaluation that replays rows one at a time. - Add a container image for a fully reproducible run. ## License diff --git a/examples/sample_run/flagged_rows.csv b/examples/sample_run/flagged_rows.csv new file mode 100644 index 0000000..7a9d148 --- /dev/null +++ b/examples/sample_run/flagged_rows.csv @@ -0,0 +1,10 @@ +timestamp,temperature,pressure,is_anomaly,score,threshold,top_feature,top_contribution +2024-05-08T23:00:00.000000,20.6558,100.7938,0,0.009341502065532858,-1.973247953923618e-17,temperature_resid,0.0 +2024-05-11T22:00:00.000000,17.4403,102.5557,1,0.05595914490488485,-1.973247953923618e-17,temperature_resid,0.0 +2024-05-11T23:00:00.000000,18.0048,98.5259,1,0.06060271102137793,-1.973247953923618e-17,temperature_resid,0.0 +2024-05-12T00:00:00.000000,19.062,102.5169,1,0.014676161592953041,-1.973247953923618e-17,temperature_resid,0.0 +2024-05-12T21:00:00.000000,15.7843,100.8118,0,0.03643460189011305,-1.973247953923618e-17,temperature_resid,0.0 +2024-05-12T22:00:00.000000,17.0782,100.4592,0,0.025249986865079732,-1.973247953923618e-17,temperature_resid,0.0 +2024-05-12T23:00:00.000000,17.6438,100.8774,0,0.005199350558301674,-1.973247953923618e-17,temperature_resid,0.0 +2024-05-13T17:00:00.000000,14.0389,99.96,0,0.03554997145410932,-1.973247953923618e-17,temperature_resid,0.0 +2024-05-13T18:00:00.000000,14.735,100.764,0,0.013833939210193269,-1.973247953923618e-17,temperature_resid,0.0 diff --git a/examples/sample_run/model_card.md b/examples/sample_run/model_card.md index b87f375..6a8c113 100644 --- a/examples/sample_run/model_card.md +++ b/examples/sample_run/model_card.md @@ -1,6 +1,6 @@ # Model card: isolation_forest -Generated: 2026-07-28 UTC. +Generated: 2026-07-29 UTC. ## Overview @@ -54,6 +54,10 @@ Derived features (8): temperature_resid, temperature_z, temperature_lag_1, tempe | true_positives | 3 | | false_positives | 6 | +## Local feature contribution + +No native additive decomposition ships with the lab. The contribution panel is omitted for this detector. + ## Intended use Use this card to reproduce a leakage-safe anomaly experiment on small time-series datasets. Treat the output as a teaching and review artifact, not as a production control. @@ -64,3 +68,4 @@ Use this card to reproduce a leakage-safe anomaly experiment on small time-serie - Isolation Forest scores shift when the random state or scikit-learn version changes. - Causal rolling features lag true change points by the window length. - The detector assumes the calibration window is close to normal operation. +- Mahalanobis contributions are signed; correlated features can offset each other. diff --git a/examples/sample_run/report.html b/examples/sample_run/report.html index 9b56c1d..4898044 100644 --- a/examples/sample_run/report.html +++ b/examples/sample_run/report.html @@ -2,6 +2,6 @@