telemetry-anomdet 0.1.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/LICENSE +1 -1
  2. telemetry_anomdet-0.3.0/PKG-INFO +226 -0
  3. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/README.md +61 -9
  4. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/pyproject.toml +46 -24
  5. telemetry_anomdet-0.3.0/src/telemetry_anomdet/evaluation.py +388 -0
  6. telemetry_anomdet-0.3.0/src/telemetry_anomdet/ingest/__init__.py +41 -0
  7. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/ingest/csv_loader.py +13 -5
  8. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/ingest/dataset.py +3 -6
  9. telemetry_anomdet-0.3.0/src/telemetry_anomdet/ingest/esa.py +347 -0
  10. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/ingest/smap.py +29 -3
  11. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/models/__init__.py +3 -2
  12. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/models/base.py +7 -7
  13. telemetry_anomdet-0.3.0/src/telemetry_anomdet/models/deep/__init__.py +18 -0
  14. telemetry_anomdet-0.3.0/src/telemetry_anomdet/models/deep/_ar.py +186 -0
  15. telemetry_anomdet-0.3.0/src/telemetry_anomdet/models/deep/_kan.py +353 -0
  16. telemetry_anomdet-0.3.0/src/telemetry_anomdet/models/deep/_net.py +241 -0
  17. telemetry_anomdet-0.3.0/src/telemetry_anomdet/models/deep/codegen.py +1034 -0
  18. telemetry_anomdet-0.3.0/src/telemetry_anomdet/models/deep/distill.py +484 -0
  19. telemetry_anomdet-0.3.0/src/telemetry_anomdet/models/deep/gdn.py +651 -0
  20. telemetry_anomdet-0.3.0/src/telemetry_anomdet/models/deep/kan_gdn.py +164 -0
  21. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/models/ensemble.py +1 -1
  22. telemetry_anomdet-0.3.0/src/telemetry_anomdet/thresholding.py +392 -0
  23. telemetry_anomdet-0.3.0/src/telemetry_anomdet/version.py +12 -0
  24. telemetry_anomdet-0.3.0/src/telemetry_anomdet/visualization.py +425 -0
  25. telemetry_anomdet-0.3.0/src/telemetry_anomdet.egg-info/PKG-INFO +226 -0
  26. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet.egg-info/SOURCES.txt +27 -1
  27. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet.egg-info/requires.txt +9 -2
  28. telemetry_anomdet-0.3.0/tests/test_csv_loader.py +263 -0
  29. telemetry_anomdet-0.3.0/tests/test_deep_ar.py +429 -0
  30. telemetry_anomdet-0.3.0/tests/test_deep_attribution.py +246 -0
  31. telemetry_anomdet-0.3.0/tests/test_deep_codegen.py +733 -0
  32. telemetry_anomdet-0.3.0/tests/test_deep_distill.py +218 -0
  33. telemetry_anomdet-0.3.0/tests/test_deep_gdn.py +268 -0
  34. telemetry_anomdet-0.3.0/tests/test_deep_kan.py +85 -0
  35. telemetry_anomdet-0.3.0/tests/test_deep_kangdn.py +110 -0
  36. telemetry_anomdet-0.3.0/tests/test_deep_smoothing.py +103 -0
  37. telemetry_anomdet-0.3.0/tests/test_evaluation_prauc.py +87 -0
  38. telemetry_anomdet-0.3.0/tests/test_evaluation_sequences.py +122 -0
  39. telemetry_anomdet-0.3.0/tests/test_ingest_esa.py +349 -0
  40. telemetry_anomdet-0.3.0/tests/test_score_channels.py +167 -0
  41. telemetry_anomdet-0.3.0/tests/test_threshold_budget.py +124 -0
  42. telemetry_anomdet-0.3.0/tests/test_thresholding.py +281 -0
  43. telemetry_anomdet-0.3.0/tests/test_visualization.py +257 -0
  44. telemetry_anomdet-0.1.0/PKG-INFO +0 -364
  45. telemetry_anomdet-0.1.0/src/telemetry_anomdet/evaluation.py +0 -167
  46. telemetry_anomdet-0.1.0/src/telemetry_anomdet/ingest/__init__.py +0 -21
  47. telemetry_anomdet-0.1.0/src/telemetry_anomdet/version.py +0 -15
  48. telemetry_anomdet-0.1.0/src/telemetry_anomdet/visualization.py +0 -13
  49. telemetry_anomdet-0.1.0/src/telemetry_anomdet.egg-info/PKG-INFO +0 -364
  50. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/setup.cfg +0 -0
  51. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/__init__.py +0 -0
  52. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/feature_extraction/features.py +0 -0
  53. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/ingest/ccsds_loader.py +0 -0
  54. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/models/supervised/__init__.py +0 -0
  55. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/models/unsupervised/__init__.py +0 -0
  56. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/models/unsupervised/gaussian_nb.py +0 -0
  57. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/models/unsupervised/isolation_forest.py +0 -0
  58. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/models/unsupervised/kmeans.py +0 -0
  59. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/models/unsupervised/pca.py +0 -0
  60. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/preprocessing/__init__.py +0 -0
  61. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/preprocessing/preprocessing.py +0 -0
  62. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet/py.typed +0 -0
  63. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet.egg-info/dependency_links.txt +0 -0
  64. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/src/telemetry_anomdet.egg-info/top_level.txt +0 -0
  65. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/tests/test_base_detector.py +0 -0
  66. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/tests/test_ensemble.py +0 -0
  67. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/tests/test_evaluation.py +0 -0
  68. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/tests/test_import.py +0 -0
  69. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/tests/test_preprocessing.py +0 -0
  70. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/tests/test_smap.py +0 -0
  71. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/tests/test_unsup_kmeans.py +0 -0
  72. {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.3.0}/tests/test_unsup_pca.py +0 -0
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright 2026 Telemetry Anomdet
189
+ Copyright 2025-2026 Julio Anzaldo
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
@@ -0,0 +1,226 @@
1
+ Metadata-Version: 2.4
2
+ Name: telemetry-anomdet
3
+ Version: 0.3.0
4
+ Summary: Spacecraft telemetry anomaly detection: classical and graph deep detectors, label-free thresholding, and distillation to C for flight hardware.
5
+ Author-email: Julio Anzaldo <anzaldojuliojesus@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/JulioAnzaldo/telemetry-anomdet
8
+ Project-URL: Repository, https://github.com/JulioAnzaldo/telemetry-anomdet
9
+ Keywords: telemetry,anomaly-detection,machine-learning,python
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Natural Language :: English
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Programming Language :: C
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
24
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
25
+ Classifier: Topic :: Software Development :: Libraries
26
+ Classifier: Topic :: Software Development :: Embedded Systems
27
+ Classifier: Topic :: Software Development :: Code Generators
28
+ Classifier: Topic :: System :: Monitoring
29
+ Classifier: Typing :: Typed
30
+ Requires-Python: >=3.11
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Requires-Dist: pandas<3.0.0,>=2.2.2
34
+ Requires-Dist: numpy<3.0,>=2.1
35
+ Requires-Dist: scikit-learn<2.0,>=1.3
36
+ Provides-Extra: ccsds
37
+ Requires-Dist: ccsdspy<3.0,>=2.0.0; extra == "ccsds"
38
+ Provides-Extra: deep
39
+ Requires-Dist: torch>=2.0; extra == "deep"
40
+ Provides-Extra: viz
41
+ Requires-Dist: matplotlib>=3.7; extra == "viz"
42
+ Provides-Extra: track
43
+ Requires-Dist: wandb>=0.17; extra == "track"
44
+ Dynamic: license-file
45
+
46
+ <h2 align="center">telemetry-anomdet</h2>
47
+
48
+ <p align="center">
49
+ <a href="https://julioanzaldo.github.io/telemetry-anomdet/"><img alt="Documentation" src="https://img.shields.io/badge/Docs-Online-blue"></a>
50
+ <a href="https://test.pypi.org/project/telemetry-anomdet/"><img alt="Test PyPI" src="https://img.shields.io/badge/Test-PyPI-yellow"></a>
51
+ <a href="https://pypi.org/project/telemetry-anomdet/"><img alt="PyPI" src="https://img.shields.io/pypi/v/telemetry-anomdet?label=PyPi"></a>
52
+ <a href="https://github.com/JulioAnzaldo/telemetry-anomdet/actions/workflows/ci.yml"><img alt="Testing" src="https://img.shields.io/github/actions/workflow/status/JulioAnzaldo/telemetry-anomdet/ci.yml?branch=main&label=Testing&logo=github&logoColor=white"></a>
53
+ <a href="https://github.com/JulioAnzaldo/telemetry-anomdet/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/License-Apache%202.0-blue"></a>
54
+ </p>
55
+
56
+ **telemetry-anomdet** is an open-source anomaly detection toolkit for spacecraft telemetry. It runs classical and graph-based deep detectors behind one interface, selects alarm thresholds without labels, points each alarm at the channels that raised it, and distills a trained detector down to Power of Ten conformant C that runs on flight hardware. SHAP attribution and LLM-generated diagnostic reports are on the roadmap.
57
+
58
+ Benchmarked on SMAP and MSL (NASA) and ESA-ADB (ESA).
59
+
60
+ ---
61
+
62
+ <table>
63
+ <tr>
64
+ <td align="center"><a href="https://julioanzaldo.github.io/telemetry-anomdet/getting_started.html"><b>Getting Started</b></a><br>Installation and setup</td>
65
+ <td align="center"><a href="https://julioanzaldo.github.io/telemetry-anomdet/api/models/base.html"><b>API Reference</b></a><br>BaseDetector, models, ensemble</td>
66
+ <td align="center"><a href="https://julioanzaldo.github.io/telemetry-anomdet/tutorials/real_time_example.html"><b>Tutorial</b></a><br>End-to-end detection example</td>
67
+ <td align="center"><a href="https://julioanzaldo.github.io/telemetry-anomdet/user_guide/pipeline_overview.html"><b>Pipeline Overview</b></a><br>Ingest, detect, explain</td>
68
+ <td align="center"><a href="https://github.com/JulioAnzaldo/telemetry-anomdet/discussions"><b>Discussions</b></a><br>Questions and feedback</td>
69
+ </tr>
70
+ </table>
71
+
72
+ ---
73
+
74
+ ## Quick Install
75
+
76
+ ```bash
77
+ # recommended
78
+ uv add telemetry-anomdet
79
+
80
+ # or with pip
81
+ pip install telemetry-anomdet
82
+ ```
83
+
84
+ The base install is torch-free. The deep detectors (`GDN`, `KANGDN`) need the
85
+ `deep` extra:
86
+
87
+ ```bash
88
+ uv add "telemetry-anomdet[deep]"
89
+ ```
90
+
91
+ Plotting, including the learned sensor graph, needs the `viz` extra.
92
+
93
+ ## Example Usage
94
+
95
+ ### Fit the Ensemble on Nominal Telemetry
96
+
97
+ All detectors are trained on **nominal data only**, no anomaly labels used during training. Labels are used exclusively for evaluation.
98
+
99
+ ```python
100
+ import numpy as np
101
+ from telemetry_anomdet.models.unsupervised.pca import PCAAnomaly
102
+ from telemetry_anomdet.models.unsupervised.kmeans import KMeansAnomaly
103
+ from telemetry_anomdet.models.ensemble import AnomalyEnsemble
104
+
105
+ # X_train: (n_windows, window_size, n_features), produced by windowify()
106
+ X_train = ...
107
+
108
+ ensemble = AnomalyEnsemble(
109
+ models = {
110
+ "pca": PCAAnomaly(n_components = 10),
111
+ "kmeans": KMeansAnomaly(n_clusters = 5, scale = True),
112
+ },
113
+ combine = "mean",
114
+ normalize = "robust",
115
+ percentile = 95.0,
116
+ )
117
+
118
+ ensemble.fit(X_train)
119
+ ```
120
+
121
+ ### Inference and Anomaly Flags
122
+
123
+ ```python
124
+ scores = ensemble.decision_function(X_test) # higher = more anomalous
125
+ flags = ensemble.is_anomaly(X_test) # boolean mask at training threshold
126
+
127
+ # Runtime sensitivity override, no retraining needed
128
+ flags_strict = ensemble.is_anomaly(X_test, percentile = 98.0)
129
+ flags_custom = ensemble.is_anomaly(X_test, threshold = 0.75)
130
+ ```
131
+
132
+ ### Per-Model Score Decomposition
133
+
134
+ `score_components()` returns raw per-model scores before normalization or combination. This is the SHAP hook: perturb input channels, measure how each sensor drives each model's score independently.
135
+
136
+ ```python
137
+ components = ensemble.score_components(X_test)
138
+ # {"pca": array(100,), "kmeans": array(100,)}
139
+ ```
140
+
141
+ ### Getting the SMAP dataset
142
+
143
+ The benchmarks and examples need a local copy of the NASA SMAP/MSL data, which
144
+ is not redistributed with the toolkit. From the root of the repo:
145
+
146
+ ```bash
147
+ pip install kaggle
148
+
149
+ # Requires a Kaggle API key in ~/.kaggle/kaggle.json
150
+ kaggle datasets download -d patrickfleith/nasa-anomaly-detection-dataset-smap-msl \
151
+ && mv nasa-anomaly-detection-dataset-smap-msl.zip data.zip \
152
+ && unzip -o data.zip \
153
+ && rm data.zip \
154
+ && mv data/data tmp && rm -r data && mv tmp data
155
+ ```
156
+
157
+ That leaves `data/train/`, `data/test/` and `labeled_anomalies.csv`. Point the
158
+ examples at it:
159
+
160
+ ```bash
161
+ export TAD_SMAP_DIR=data # Windows: $env:TAD_SMAP_DIR = "data"
162
+ python examples/smap_demo.py
163
+ python examples/smap_benchmark.py
164
+ ```
165
+
166
+ Datasets are never committed; `data/` is gitignored.
167
+
168
+ ### Coming in Phase 3+
169
+
170
+ ```python
171
+ # Per-channel SHAP attribution (Phase 3)
172
+ # shap_values = explainer.explain(ensemble, X_anomalous)
173
+ # {"sensor_01": 0.42, "sensor_02": 0.18, ...}
174
+
175
+ # LLM diagnostic report (Phase 4)
176
+ # report = llm_engine.explain(shap_values, context, channel_names)
177
+ # DiagnosticReport(
178
+ # anomaly_type = "power subsystem fault",
179
+ # severity = "high",
180
+ # primary_channels = ["sensor_01", "sensor_07"],
181
+ # explanation = "...",
182
+ # recommended_action = "...",
183
+ # confidence = 0.87,
184
+ # )
185
+ ```
186
+
187
+ ## Features
188
+
189
+ **Available now**
190
+
191
+ - **One interface for every detector.** Classical and deep detectors share the same `fit` / `decision_function` / `predict` / `is_anomaly` API, so stacking or swapping models needs no per-model glue.
192
+ - **Graph deviation detectors.** `GDN` and `KANGDN` forecast each channel from its learned top-k neighbours and score the deviation, catching readings that are individually plausible but wrong in relation to each other. `feat_mode` selects how `KANGDN` encodes each channel's history (`linear`, `kan`, `ar_kan`, `ar_kan_residual`).
193
+ - **Which sensor, not just when.** `channel_deviations` and `dominant_channels` attribute each alarm to the channels driving it, and `plot_sensor_graph` draws the graph the detector learned.
194
+ - **Alarms without labels.** `threshold_for_budget` and `dynamic_threshold` pick an operating point from the score distribution alone. Operations can state an alarm rate; they cannot state a recall they have no way to observe.
195
+ - **Honest evaluation.** Event-level scoring alongside the conventional point-adjusted F1, plus a random baseline row in the benchmark. ESA-ADB is scored by its own protocol, with F0.5 as the headline metric.
196
+ - **Runs on flight hardware.** A fitted `KANGDN` distills to a torch-free NumPy evaluator, then to Power of Ten conformant C with golden vectors, for every `feat_mode`. Host and ESP32-S3 targets are in [`targets/`](targets/).
197
+ - **Stacking ensemble** with robust (median + IQR) score normalization, built for anomaly scores that are extreme by definition.
198
+ - **Per-model score decomposition** via `score_components()`, the hook that enables per-channel attribution without retraining.
199
+ - **Spacecraft-native ingestion.** SMAP, MSL, ESA-ADB and CSV load straight into a long-form `TelemetryDataset`, no schema wrangling. The ESA-ADB loader reads the official archive directly, with no preprocessing step; its payloads are pickled, so load only that archive, never untrusted files.
200
+ - **Runtime sensitivity control.** `is_anomaly()` accepts a percentile or threshold override, so operators re-tune without retraining.
201
+
202
+ **On the roadmap**
203
+
204
+ - `TranAD`: transformer-based sequence reconstruction
205
+ - `SHAPExplainer`: per-channel attribution over `score_components()` (Phase 3)
206
+ - LLM diagnostic reports, SHAP chart supplied as an image (Phase 4)
207
+ - Human-in-the-loop threshold feedback (Phase 5)
208
+
209
+ ## AI Transparency
210
+
211
+ AI assistants are used while writing and maintaining this project's documentation.
212
+
213
+ What that does not change:
214
+
215
+ - **Numbers come from runs.** Benchmark figures are produced by [`examples/smap_benchmark.py`](examples/smap_benchmark.py) and [`examples/esa_benchmark.py`](examples/esa_benchmark.py), and the hardware results in the deployment docs are measured on an ESP32-S3 by the conformance harness in [`targets/`](targets/). Nothing quoted in these docs is estimated or inferred.
216
+ - **Behavior claims are backed by tests.** The suite gates CI, and the documented API is covered by it.
217
+
218
+ Everything published here is reviewed before it lands.
219
+
220
+ ## Getting Help
221
+
222
+ ### Discussions
223
+ To suggest improvements, or ask for help, please see [GitHub Discussions](https://github.com/JulioAnzaldo/telemetry-anomdet/discussions)
224
+
225
+ ### Bug reports
226
+ To submit a report on any bugs or issues, [open an issue here](https://github.com/JulioAnzaldo/telemetry-anomdet/issues)
@@ -1,14 +1,16 @@
1
1
  <h2 align="center">telemetry-anomdet</h2>
2
2
 
3
3
  <p align="center">
4
- <a href="https://julioanzaldo.github.io/telemetry-anomdet/"><img alt="Documentation" src="https://img.shields.io/badge/docs-online-blue"></a>
4
+ <a href="https://julioanzaldo.github.io/telemetry-anomdet/"><img alt="Documentation" src="https://img.shields.io/badge/Docs-Online-blue"></a>
5
5
  <a href="https://test.pypi.org/project/telemetry-anomdet/"><img alt="Test PyPI" src="https://img.shields.io/badge/Test-PyPI-yellow"></a>
6
- <a href="https://pypi.org/project/telemetry-anomdet/"><img alt="PyPI" src="https://img.shields.io/pypi/v/telemetry-anomdet"></a>
7
- <a href="https://github.com/JulioAnzaldo/telemetry-anomdet/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/JulioAnzaldo/telemetry-anomdet/actions/workflows/ci.yml/badge.svg?branch=main"></a>
8
- <a href="https://github.com/JulioAnzaldo/telemetry-anomdet/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/license-Apache%202.0-blue"></a>
6
+ <a href="https://pypi.org/project/telemetry-anomdet/"><img alt="PyPI" src="https://img.shields.io/pypi/v/telemetry-anomdet?label=PyPi"></a>
7
+ <a href="https://github.com/JulioAnzaldo/telemetry-anomdet/actions/workflows/ci.yml"><img alt="Testing" src="https://img.shields.io/github/actions/workflow/status/JulioAnzaldo/telemetry-anomdet/ci.yml?branch=main&label=Testing&logo=github&logoColor=white"></a>
8
+ <a href="https://github.com/JulioAnzaldo/telemetry-anomdet/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/License-Apache%202.0-blue"></a>
9
9
  </p>
10
10
 
11
- **telemetry-anomdet** is an open-source anomaly detection toolkit for spacecraft telemetry. It runs a stacking ensemble of classical and deep learning detectors with per-channel SHAP attribution, SymTorch symbolic fault expressions, and LLM-generated diagnostic reports.
11
+ **telemetry-anomdet** is an open-source anomaly detection toolkit for spacecraft telemetry. It runs classical and graph-based deep detectors behind one interface, selects alarm thresholds without labels, points each alarm at the channels that raised it, and distills a trained detector down to Power of Ten conformant C that runs on flight hardware. SHAP attribution and LLM-generated diagnostic reports are on the roadmap.
12
+
13
+ Benchmarked on SMAP and MSL (NASA) and ESA-ADB (ESA).
12
14
 
13
15
  ---
14
16
 
@@ -34,6 +36,15 @@ uv add telemetry-anomdet
34
36
  pip install telemetry-anomdet
35
37
  ```
36
38
 
39
+ The base install is torch-free. The deep detectors (`GDN`, `KANGDN`) need the
40
+ `deep` extra:
41
+
42
+ ```bash
43
+ uv add "telemetry-anomdet[deep]"
44
+ ```
45
+
46
+ Plotting, including the learned sensor graph, needs the `viz` extra.
47
+
37
48
  ## Example Usage
38
49
 
39
50
  ### Fit the Ensemble on Nominal Telemetry
@@ -82,6 +93,33 @@ components = ensemble.score_components(X_test)
82
93
  # {"pca": array(100,), "kmeans": array(100,)}
83
94
  ```
84
95
 
96
+ ### Getting the SMAP dataset
97
+
98
+ The benchmarks and examples need a local copy of the NASA SMAP/MSL data, which
99
+ is not redistributed with the toolkit. From the root of the repo:
100
+
101
+ ```bash
102
+ pip install kaggle
103
+
104
+ # Requires a Kaggle API key in ~/.kaggle/kaggle.json
105
+ kaggle datasets download -d patrickfleith/nasa-anomaly-detection-dataset-smap-msl \
106
+ && mv nasa-anomaly-detection-dataset-smap-msl.zip data.zip \
107
+ && unzip -o data.zip \
108
+ && rm data.zip \
109
+ && mv data/data tmp && rm -r data && mv tmp data
110
+ ```
111
+
112
+ That leaves `data/train/`, `data/test/` and `labeled_anomalies.csv`. Point the
113
+ examples at it:
114
+
115
+ ```bash
116
+ export TAD_SMAP_DIR=data # Windows: $env:TAD_SMAP_DIR = "data"
117
+ python examples/smap_demo.py
118
+ python examples/smap_benchmark.py
119
+ ```
120
+
121
+ Datasets are never committed; `data/` is gitignored.
122
+
85
123
  ### Coming in Phase 3+
86
124
 
87
125
  ```python
@@ -106,19 +144,33 @@ components = ensemble.score_components(X_test)
106
144
  **Available now**
107
145
 
108
146
  - **One interface for every detector.** Classical and deep detectors share the same `fit` / `decision_function` / `predict` / `is_anomaly` API, so stacking or swapping models needs no per-model glue.
147
+ - **Graph deviation detectors.** `GDN` and `KANGDN` forecast each channel from its learned top-k neighbours and score the deviation, catching readings that are individually plausible but wrong in relation to each other. `feat_mode` selects how `KANGDN` encodes each channel's history (`linear`, `kan`, `ar_kan`, `ar_kan_residual`).
148
+ - **Which sensor, not just when.** `channel_deviations` and `dominant_channels` attribute each alarm to the channels driving it, and `plot_sensor_graph` draws the graph the detector learned.
149
+ - **Alarms without labels.** `threshold_for_budget` and `dynamic_threshold` pick an operating point from the score distribution alone. Operations can state an alarm rate; they cannot state a recall they have no way to observe.
150
+ - **Honest evaluation.** Event-level scoring alongside the conventional point-adjusted F1, plus a random baseline row in the benchmark. ESA-ADB is scored by its own protocol, with F0.5 as the headline metric.
151
+ - **Runs on flight hardware.** A fitted `KANGDN` distills to a torch-free NumPy evaluator, then to Power of Ten conformant C with golden vectors, for every `feat_mode`. Host and ESP32-S3 targets are in [`targets/`](targets/).
109
152
  - **Stacking ensemble** with robust (median + IQR) score normalization, built for anomaly scores that are extreme by definition.
110
153
  - **Per-model score decomposition** via `score_components()`, the hook that enables per-channel attribution without retraining.
111
- - **Spacecraft-native ingestion.** SMAP and CSV load straight into a long-form `TelemetryDataset`, no schema wrangling.
154
+ - **Spacecraft-native ingestion.** SMAP, MSL, ESA-ADB and CSV load straight into a long-form `TelemetryDataset`, no schema wrangling. The ESA-ADB loader reads the official archive directly, with no preprocessing step; its payloads are pickled, so load only that archive, never untrusted files.
112
155
  - **Runtime sensitivity control.** `is_anomaly()` accepts a percentile or threshold override, so operators re-tune without retraining.
113
156
 
114
157
  **On the roadmap**
115
158
 
116
- - `GDN` and `TranAD` deep detectors: inter-sensor relational and transformer-reconstruction faults (Phase 2)
159
+ - `TranAD`: transformer-based sequence reconstruction
117
160
  - `SHAPExplainer`: per-channel attribution over `score_components()` (Phase 3)
118
161
  - LLM diagnostic reports, SHAP chart supplied as an image (Phase 4)
119
162
  - Human-in-the-loop threshold feedback (Phase 5)
120
- - OPS-SAT cross-dataset generalization (Phase 6)
121
- - SymTorch symbolic distillation to closed-form fault expressions for edge / microcontroller deployment (stretch)
163
+
164
+ ## AI Transparency
165
+
166
+ AI assistants are used while writing and maintaining this project's documentation.
167
+
168
+ What that does not change:
169
+
170
+ - **Numbers come from runs.** Benchmark figures are produced by [`examples/smap_benchmark.py`](examples/smap_benchmark.py) and [`examples/esa_benchmark.py`](examples/esa_benchmark.py), and the hardware results in the deployment docs are measured on an ESP32-S3 by the conformance harness in [`targets/`](targets/). Nothing quoted in these docs is estimated or inferred.
171
+ - **Behavior claims are backed by tests.** The suite gates CI, and the documented API is covered by it.
172
+
173
+ Everything published here is reviewed before it lands.
122
174
 
123
175
  ## Getting Help
124
176
 
@@ -1,10 +1,11 @@
1
1
  [project]
2
2
  name = "telemetry-anomdet"
3
- version = "0.1.0"
4
- description = "Telemetry anomaly detection toolkit with preprocessing, feature extraction, and unsupervised models."
3
+ version = "0.3.0"
4
+ description = "Spacecraft telemetry anomaly detection: classical and graph deep detectors, label-free thresholding, and distillation to C for flight hardware."
5
5
  readme = "README.md"
6
- license = { file = "LICENSE" }
7
- requires-python = ">=3.10"
6
+ license = "Apache-2.0"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.11"
8
9
  authors = [
9
10
  { name = "Julio Anzaldo", email = "anzaldojuliojesus@gmail.com" }
10
11
  ]
@@ -12,50 +13,71 @@ dependencies = [
12
13
  "pandas>=2.2.2,<3.0.0",
13
14
  "numpy>=2.1,<3.0",
14
15
  "scikit-learn>=1.3,<2.0",
15
- "click>=8.4.2,<9.0",
16
- "typing-extensions>=4.16.0",
17
16
  ]
18
17
  keywords = ["telemetry", "anomaly-detection", "machine-learning", "python"]
18
+ # License is declared as an SPDX expression above, not a classifier: license
19
+ # classifiers are deprecated and setuptools warns on them.
19
20
  classifiers = [
21
+ "Development Status :: 3 - Alpha",
22
+ "Intended Audience :: Science/Research",
23
+ "Intended Audience :: Developers",
24
+ "Natural Language :: English",
25
+ "Operating System :: OS Independent",
20
26
  "Programming Language :: Python :: 3",
21
- "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3 :: Only",
22
28
  "Programming Language :: Python :: 3.11",
23
29
  "Programming Language :: Python :: 3.12",
24
30
  "Programming Language :: Python :: 3.13",
25
- "Operating System :: OS Independent",
26
- "Topic :: Software Development :: Libraries",
31
+ "Programming Language :: Python :: 3.14",
32
+ # The toolkit is pure Python; the artifact it generates for flight targets is C.
33
+ "Programming Language :: C",
27
34
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
28
- "Intended Audience :: Science/Research",
29
- "Intended Audience :: Developers",
30
- "Development Status :: 3 - Alpha",
31
- "License :: OSI Approved :: Apache Software License",
32
- "Typing :: Typed"
35
+ "Topic :: Scientific/Engineering :: Information Analysis",
36
+ "Topic :: Scientific/Engineering :: Astronomy",
37
+ "Topic :: Software Development :: Libraries",
38
+ "Topic :: Software Development :: Embedded Systems",
39
+ "Topic :: Software Development :: Code Generators",
40
+ "Topic :: System :: Monitoring",
41
+ "Typing :: Typed",
33
42
  ]
34
43
 
35
44
  [project.optional-dependencies]
36
45
  ccsds = [
37
46
  "ccsdspy>=2.0.0,<3.0",
38
47
  ]
48
+ # Phase 2 deep detector (GDN). Optional so the base install stays
49
+ # torch-free; torch is imported lazily at fit time.
50
+ deep = [
51
+ "torch>=2.0",
52
+ ]
53
+ # Plotting. Optional so the base install stays lean; matplotlib is imported
54
+ # lazily inside each figure function.
55
+ viz = [
56
+ "matplotlib>=3.7",
57
+ ]
58
+ # Experiment tracking for the benchmark scripts. Nothing in the library imports
59
+ # it; examples/smap_benchmark.py logs to it only when TAD_WANDB_PROJECT is set.
60
+ track = [
61
+ "wandb>=0.17",
62
+ ]
39
63
 
40
64
  [dependency-groups]
41
65
  dev = [
42
66
  "pytest>=9.1.1,<10.0",
43
67
  "pytest-cov>=7.1.0,<8.0",
44
- "pre-commit>=4.6.1,<5.0",
68
+ "pre-commit>=4.6.2,<5.0",
45
69
  "black>=26.5.1,<27.0",
46
- "ruff>=0.16.0,<1.0",
70
+ "ruff>=0.16.2,<1.0",
47
71
  ]
48
72
  docs = [
49
73
  "sphinx>=7.0",
50
- "sphinx-rtd-theme>=2.0",
74
+ "pydata-sphinx-theme>=0.15",
51
75
  "sphinx-autodoc-typehints>=2.0",
52
76
  "numpydoc>=1.7",
77
+ "sphinx-sitemap>=2.6",
78
+ "sphinxext-opengraph>=0.9",
79
+ "sphinx-notfound-page>=1.0",
53
80
  ]
54
- # Phase 2 -- uncomment when GDN/TranAD training starts
55
- # deep = [
56
- # "torch>=2.0",
57
- # "torch-geometric",
58
- # ]
59
81
  # Phase 3
60
82
  # xai = [
61
83
  # "shap",
@@ -72,12 +94,12 @@ Homepage = "https://github.com/JulioAnzaldo/telemetry-anomdet"
72
94
  Repository = "https://github.com/JulioAnzaldo/telemetry-anomdet"
73
95
 
74
96
  [build-system]
75
- requires = ["setuptools>=83.0.0", "wheel"]
97
+ requires = ["setuptools>=84.0.0", "wheel"]
76
98
  build-backend = "setuptools.build_meta"
77
99
 
78
100
  [tool.ruff]
79
101
  line-length = 100
80
- target-version = "py310"
102
+ target-version = "py311"
81
103
  src = ["src", "tests"]
82
104
 
83
105
  [tool.ruff.lint]