I built this to answer a question I kept running into: what happens when someone feeds garbage into a predictive maintenance model? Not accidentally - deliberately. Most RUL (Remaining Useful Life) systems assume clean sensor data, spit out a number, and call it done. In aviation maintenance, that assumption can either ground a healthy aircraft or clear a failing one for flight.
PulseNet is a Remaining Useful Life forecasting system for turbofan engines (NASA C-MAPSS dataset) that treats input integrity as a first-class requirement. Before any prediction runs, incoming sensor vectors are checked against learned operational envelopes. Readings that look physically impossible or statistically adversarial get rejected and logged. The prediction pipeline itself is wrapped in RBAC, audit trails, and encrypted model storage - because a compromised model is as dangerous as bad sensor data.
This is an experimental reference architecture, not production-ready. I wrote it to work through the engineering problems of combining ML inference with real security controls: how much latency does input validation add, what does RBAC look like for a prediction API, how do you make audit logs tamper-evident. The models here are adequate but unremarkable. The interesting part is everything around them.
Predictive maintenance research focuses almost entirely on model accuracy. That is necessary but insufficient. In production, you also need to know: who ran this prediction, what data went in, can I trust that nobody modified the model since it was trained, and can an attacker manipulate sensor readings to produce a wrong prediction.
This repository demonstrates those patterns concretely:
- Input validation against learned physical envelopes before inference
- STRIDE threat modeling applied to an ML prediction service
- RBAC, JWT auth, and audit logging that don't make development impossible
- Anomaly detection, RUL forecasting, and operational monitoring in one deployable system
- Benchmarking for both prediction quality and operational resilience (packet loss, latency)
┌─────────────────────────────────────────────────────────────────────────────┐
│ PulseNet System │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌───────────────────┐ ┌──────────────────────┐ │
│ │ Sensor Input │───>│ Adversarial Gate │───>│ RUL Prediction Model │ │
│ │ 21 channels │ │ (envelope check) │ │ (IF/LSTM/Transformer)│ │
│ │ + 3 settings │ │ │ │ │ │
│ └──────────────┘ └────────┬───────────┘ └──────────┬───────────┘ │
│ │ reject/flag │ prediction │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌─────────────────┐ │
│ │ Audit Log + │ │ Evaluation │ │
│ │ Blockchain Ledger│ │ (RMSE + NASA) │ │
│ └──────────────────┘ └─────────────────┘ │
│ │
├──────────────────────────────────────────────────────────────────────────────┤
│ INFRASTRUCTURE LAYER │
│ ┌────────────┐ ┌───────────┐ ┌────────────┐ ┌───────────────────────┐ │
│ │ FastAPI │ │ RBAC + │ │ Prometheus │ │ MLflow Experiment │ │
│ │ (serve) │ │ JWT Auth │ │ Metrics │ │ Tracking + Drift Mon │ │
│ └────────────┘ └───────────┘ └────────────┘ └───────────────────────┘ │
│ │
│ ┌────────────┐ ┌───────────┐ ┌────────────┐ ┌───────────────────────┐ │
│ │ Streamlit │ │ Fernet │ │ OpenTele- │ │ Helm / K8s / Terraform│ │
│ │ Dashboard │ │ Encryption│ │ metry │ │ (deploy) │ │
│ └────────────┘ └───────────┘ └────────────┘ └───────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
Component responsibilities:
| Module | Purpose |
|---|---|
src/pulsenet/core |
Data loading, preprocessing, sliding window creation for C-MAPSS data |
src/pulsenet/models |
Isolation Forest, LSTM, Transformer model definitions and training |
src/pulsenet/security |
Adversarial telemetry validation, audit logging, blockchain ledger, encryption, Vault integration, token revocation |
src/pulsenet/api |
FastAPI endpoints for prediction, health checks, and model management |
src/pulsenet/pipeline |
End-to-end orchestration: data ingestion through prediction |
src/pulsenet/monitoring |
Prometheus metrics export, hardware telemetry (GPU via pynvml) |
src/pulsenet/mlops |
MLflow tracking, drift detection (KL divergence), auto-retrain triggers |
src/pulsenet/streaming |
Async queue-based sensor stream ingestion with backpressure |
src/pulsenet/evaluation |
RMSE, NASA scoring function, model comparison |
src/pulsenet/benchmarks |
Inference latency, DDP multi-GPU benchmarks, network resilience |
src/pulsenet/dashboard |
Streamlit real-time monitoring UI |
deploy/ |
Helm charts, Kubernetes manifests, Terraform IaC, Prometheus/Grafana configs |
Here is how data moves through the system from raw sensor readings to a maintenance decision:
-
Ingestion: Raw C-MAPSS time-series files (21 sensor channels, 3 operational settings per engine per cycle) are loaded from
data/official/. -
Preprocessing: Sensor channels are normalized per operational condition. Constant/uninformative sensors are dropped. A rolling window smooths noise. Degradation trajectories are segmented into fixed-length sliding windows (default: 30 cycles).
-
Envelope Learning: During training, per-channel min/max operational envelopes are learned from healthy-cycle data (first N cycles of each engine). These become the adversarial validation bounds.
-
Adversarial Validation: At inference time, each incoming sensor vector is checked against learned envelopes. Readings outside bounds are flagged. If the violation exceeds configurable thresholds, the input is rejected and an audit event is emitted.
-
Prediction: Valid inputs pass to the active model (Isolation Forest for anomaly scoring, LSTM or Transformer for RUL regression). The model outputs predicted cycles remaining.
-
Evaluation: Predictions are scored using RMSE and the NASA asymmetric scoring function (which penalizes late predictions more heavily than early ones, because missing a failure is worse than scheduling premature maintenance).
-
Audit and Logging: Every prediction, rejection, and model access is logged. The blockchain ledger provides tamper-evident sequencing. JWT-authenticated RBAC controls who can train, predict, or access audit records.
-
Monitoring: Prometheus scrapes prediction latency, rejection rates, and drift metrics. The Streamlit dashboard visualizes system health in real time.
Why Isolation Forest as the default model? It is fast, unsupervised, and works well for anomaly-based degradation detection without labeled failure points. The LSTM and Transformer options give better RUL regression accuracy but require more compute and tuning. The config lets you switch between them.
Why a blockchain-style audit ledger? In regulated industries (aviation, energy), you need tamper-evident logs that prove no one silently altered prediction records after the fact. A hash-chain is the simplest structure that provides this property. It is not a distributed blockchain; it is a local append-only ledger with Merkle tree verification.
Why Fernet encryption for model artifacts? Models at rest should be encrypted so that a compromised file system does not leak proprietary degradation patterns. Fernet (AES-256-CBC with HMAC) is a single-key symmetric scheme that is simple to rotate and has no configuration footguns. Vault integration handles key management in production.
Why FastAPI over Flask? Async support for streaming sensor data, automatic OpenAPI documentation, and native Pydantic validation. The overhead compared to Flask is negligible, and the type safety catches integration bugs at development time rather than in production.
Why both Helm and raw K8s manifests? Helm for production clusters with templated values. Raw manifests for quick local testing with minikube/kind where you do not want to install Helm. The Terraform module provisions the underlying cloud infrastructure.
Trade-off: Security overhead vs. iteration speed
Every prediction request goes through JWT validation, RBAC checks, and input envelope validation. This adds latency (~5-15ms per request). The benchmark target is 50ms total inference, which is achievable, but you feel the overhead during development. The --validate-inputs flag lets you disable the gate for experimentation.
| Layer | Technology |
|---|---|
| Language | Python 3.12 |
| ML | PyTorch, scikit-learn, MLflow |
| API | FastAPI, Uvicorn, Gunicorn |
| Security | python-jose (JWT), bcrypt, Fernet, Vault |
| Monitoring | Prometheus client, OpenTelemetry, pynvml |
| Dashboard | Streamlit, Plotly |
| Infrastructure | Docker, Kubernetes, Helm, Terraform |
| CI/CD | GitHub Actions (ci.yml, codeql.yml, archive-integrity.yml) |
| Code Quality | Ruff (lint + format), Pyright (types), Pyre, pre-commit |
| Dependency Management | pip-compile (requirements.in -> requirements.lock), uv |
Prerequisites: Python 3.12+, pip or uv
# Clone the repository
git clone https://github.com/poojakira/PulseNet-RUL-Forecasting.git
cd PulseNet-RUL-Forecasting
# Install dependencies
pip install -r requirements.txt
# Or using uv (faster)
uv sync
# Copy and configure environment
cp .env.example .env
cp config.example.yaml config.yaml
# Edit config.yaml to set data paths and security keys# Download NASA C-MAPSS data
python scripts/download_data.py
# Run the full training pipeline (FD001 by default)
make train
# Or directly:
python main_pipeline.py --mode full
# Start the prediction API
make serve
# Available at http://localhost:8000/docs (OpenAPI)
# Launch the monitoring dashboard
make dashboard
# Available at http://localhost:8501
# Run with adversarial input validation
python main.py --dataset FD001 --validate-inputsSingle prediction via API:
curl -X POST http://localhost:8000/predict \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"engine_id": 1, "sensors": [518.67, 642.44, 1585.29, ...], "settings": [0.0, 0.0, 100.0]}'Run benchmarks (latency + network resilience):
make benchmark
# Or:
python main_pipeline.py --mode benchmarkMulti-GPU training:
NUM_GPUS=4 make train-ddpDocker deployment:
make docker # Build image
make docker-up # Start full stack (API + monitoring + dashboard)This system handles predictions that drive physical maintenance decisions. The threat model treats the ML pipeline as an attack surface.
| STRIDE Category | Threat | Mitigation |
|---|---|---|
| Spoofing | Attacker impersonates an authorized user to submit fake sensor data or extract model weights | JWT authentication with configurable expiry (60min default), bcrypt password hashing, token revocation list |
| Tampering | Modification of sensor inputs to produce incorrect RUL predictions; alteration of audit logs to hide manipulation | Adversarial input envelope validation rejects out-of-bounds readings; blockchain-style hash-chain audit ledger detects log tampering; Merkle tree verification |
| Repudiation | Operator denies issuing a prediction that led to a maintenance decision | Every API call logged with user identity, timestamp, input hash, and prediction output. Append-only ledger with cryptographic chaining |
| Information Disclosure | Model weights or training data leaked, revealing proprietary degradation patterns | AES-256-Fernet encryption of model artifacts at rest; Vault-managed key rotation (30-day cycle); RBAC restricts who can access models |
| Denial of Service | Flood of prediction requests overwhelms the service; streaming backpressure exploited | Rate limiting (100 req/min default), streaming queue with configurable backpressure threshold (80%), Kubernetes horizontal pod autoscaling |
| Elevation of Privilege | Operator-role user accesses admin functions (train, audit, verify) | Role-based access control with three tiers: admin, engineer, operator. Each role has explicit permission sets. JWT claims encode role. API endpoints enforce checks before execution |
Additional CI/CD security:
- CodeQL static analysis on every push
pip-auditfor known-vulnerable dependencies- Dependabot automated dependency updates
- SARIF-format security findings integrated into GitHub Security tab
- Archive integrity workflow validates repository state
Metrics used:
- RMSE (Root Mean Square Error): Standard regression accuracy
- NASA Scoring Function: Asymmetric metric that penalizes late predictions (predicting 10 cycles remaining when only 5 are left) more heavily than early predictions (penalizes under-prediction exponentially)
Dataset characteristics:
| Subset | Train Engines | Test Engines | Fault Modes | Operating Conditions |
|---|---|---|---|---|
| FD001 | 100 | 100 | 1 (HPC degradation) | 1 |
| FD002 | 260 | 259 | 1 (HPC degradation) | 6 |
| FD003 | 100 | 100 | 2 | 1 |
| FD004 | 249 | 248 | 2 | 6 |
Benchmark targets:
- Inference latency: < 50ms per prediction (including validation)
- Network resilience tested at 10%, 20%, 30% simulated packet loss
Known limitations:
- Single-dataset validation: Results are on C-MAPSS only. Real turbofan sensor data has different noise characteristics, sampling rates, and failure modes.
- Static envelopes: The adversarial validation bounds are learned once during training. They do not adapt to gradual operational drift (though MLflow drift monitoring flags when retraining is needed).
- No real adversarial evaluation: The envelope check catches naive perturbations and sensor faults, but has not been tested against sophisticated adaptive adversaries who craft inputs within bounds.
- Experimental status: The repository is archived. The security and MLOps layers demonstrate patterns but have not been hardened through production incident cycles.
| Criterion | Status | Notes |
|---|---|---|
| CI/CD pipeline | Implemented | GitHub Actions: lint, test, CodeQL, archive integrity |
| Container packaging | Implemented | Dockerfile + docker-compose |
| Kubernetes deployment | Implemented | Helm charts + raw manifests + Terraform |
| RBAC and authentication | Implemented | JWT + role-based permissions (3 tiers) |
| Encryption at rest | Implemented | Fernet (AES-256) with key rotation |
| Monitoring | Implemented | Prometheus + OpenTelemetry + Streamlit dashboard |
| Audit logging | Implemented | Append-only blockchain ledger |
| Dependency scanning | Implemented | pip-audit + Dependabot |
| Type checking | Implemented | Pyright + Pyre, py.typed marker |
| Load testing | Partial | Benchmark suite exists; no sustained load/soak testing |
| Chaos/fault injection | Partial | Packet loss simulation; no pod-kill or network partition tests |
| Multi-region deployment | Not implemented | Single-cluster Terraform only |
| Formal security audit | Not done | STRIDE model documented; no external pen test |
Verdict: The architecture and patterns are production-grade. The implementation is experimental and not battle-tested. Use as a reference architecture, not a drop-in deployment.
Honest accounting of where security modules stand relative to production use.
Fully integrated and exercised at runtime:
| Module | Status | How it's wired in |
|---|---|---|
JWT Authentication (api/auth.py) |
✅ Integrated | Every API endpoint requires a valid JWT. Role-based access control enforced on predict, train, audit routes. |
Encryption (security/encryption.py) |
✅ Integrated | Model artifacts encrypted at rest via Fernet. Key rotation supported. |
Blockchain audit ledger (security/blockchain.py) |
✅ Integrated | Prediction events written to hash-chained append-only ledger. |
Audit logging (security/audit.py) |
✅ Integrated | Access events logged per-tenant with SHA-256 integrity hashes. |
Adversarial telemetry guard (security/adversarial_telemetry.py) |
✅ Integrated | OOD z-score check and perturbation sensitivity run at inference time when --validate-inputs is set. |
Implemented but standalone (not wired into runtime code paths):
| Module | Status | What's missing |
|---|---|---|
Token revocation (security/token_revocation.py) |
The InMemoryBlocklist and RedisBlocklist work correctly (tested), but verify_token() in api/auth.py does not call revocation_list.is_revoked(). Issued tokens cannot be revoked until they expire naturally. Wiring this in is a ~5 line change but requires deciding on the JTI claim strategy. |
|
Vault integration (security/vault_integration.py) |
Full HashiCorp Vault client for JWT key rotation, artifact signing, and dynamic DB credentials. Not called from any runtime path - secrets are loaded from environment variables. Integrating requires a running Vault instance and changes to the config loader. | |
Log shipping (security/log_shipping.py) |
CloudWatch, S3 WORM, and stdout shippers are implemented but never called from the audit logger. Local hash-chain is the only audit destination currently. |
Not implemented:
- mTLS between services (relies on K8s network policy)
- External penetration testing
- Sustained load/soak testing with security controls enabled
- Multi-region secret replication
CI security caveats:
pip-auditandpyrightsteps use|| true- they report findings but never block merges. This was a deliberate choice to avoid blocking on false positives during development but means real vulnerabilities can slip through.
- Adaptive envelopes: Online learning to update sensor bounds as operational conditions shift, reducing false rejections on legitimate data from new operating regimes
- Adversarial robustness testing: Integration with ART (Adversarial Robustness Toolbox) to evaluate model behavior under crafted perturbations within valid envelopes
- Multi-engine fleet inference: Batch prediction endpoint with fleet-level health scoring and maintenance prioritization
- Federated training: Train across multiple sites without centralizing sensitive degradation data
- ONNX export: Convert PyTorch models for edge deployment on embedded maintenance hardware
- Sustained load testing: k6 or Locust soak tests to validate resource limits under realistic traffic patterns
- mTLS for inter-service communication: Currently relies on network-level isolation in Kubernetes
-
NASA C-MAPSS Dataset: Saxena, A., Goebel, K., Simon, D., & Eklund, N. (2008). "Damage propagation modeling for aircraft engine run-to-failure simulation." International Conference on Prognostics and Health Management. NASA Prognostics Data Repository
-
NIST AI Risk Management Framework (AI RMF 1.0): National Institute of Standards and Technology (2023). Provides the governance structure for AI system trustworthiness that informs this project's security controls. NIST AI 100-1
-
MITRE ATLAS (Adversarial Threat Landscape for AI Systems): Framework for understanding adversarial ML threats. The STRIDE mapping in this project draws on ATLAS tactics for ML-specific attack vectors. MITRE ATLAS
-
STRIDE Threat Model: Microsoft's threat classification (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege). Applied here to an ML prediction service rather than a traditional web application.
License: Apache License 2.0. See LICENSE for full text.
Author: poojakira
Project page: poojakira.github.io/PulseNet-RUL-Forecasting
The most useful thing this project demonstrates is not the model architecture (there are better RUL predictors in the literature). It is the pattern of treating ML inference as a security boundary. Every input crosses a trust boundary. Every prediction is an auditable event. Every model artifact is a sensitive asset.
Most ML systems in production fail not because the model is inaccurate, but because the operational envelope around the model (who can modify it, what inputs it accepts, whether you can trace a bad prediction back to its cause) was never built. The hard part is not the neural network. The hard part is everything around it.