audit: document P0/P1/P2 findings in AUDIT_REPORT.md
Browse files- docs/AUDIT_REPORT.md +225 -0
docs/AUDIT_REPORT.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AETERNA AI — Audit Report
|
| 2 |
+
**Branch**: refactor/jsc-data-integrity-v1
|
| 3 |
+
**Audited by**: AETERNA AI MLOps Auditor
|
| 4 |
+
**Date**: 2026-08-28
|
| 5 |
+
**Purpose**: Scientific defensibility assessment for Jakarta Smart City / DLH DKI Jakarta R&D presentation
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Severity Legend
|
| 10 |
+
- **P0** — Scientifically misleading or incorrect. Must be fixed before any government presentation.
|
| 11 |
+
- **P1** — Important engineering issue. Must be resolved before production deployment.
|
| 12 |
+
- **P2** — Improvement. Recommended for quality and transparency.
|
| 13 |
+
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
## P0 — Critical Scientific Integrity Issues
|
| 17 |
+
|
| 18 |
+
### P0-1 · Synthetic Dataset Misrepresented as Real DLH/SIPSN Observations
|
| 19 |
+
|
| 20 |
+
**Affected files**:
|
| 21 |
+
- `scripts/generate_real_kecamatan_dataset.py`
|
| 22 |
+
- `data/dataset_real_kecamatan_2024_2025.csv`
|
| 23 |
+
- `app.py` (startup log, line ~393)
|
| 24 |
+
|
| 25 |
+
**Finding**:
|
| 26 |
+
`generate_real_kecamatan_dataset.py` creates `Volume_Sampah_Ton` entirely from:
|
| 27 |
+
- Manually defined `base_ton` constants per kecamatan (not measured)
|
| 28 |
+
- `np.random.exponential` rainfall simulation
|
| 29 |
+
- Hardcoded day-of-week, zone, mudik, and event multipliers
|
| 30 |
+
- Gaussian noise: `np.random.normal(0, base_vol * 0.075)`
|
| 31 |
+
|
| 32 |
+
The output CSV is named `dataset_real_kecamatan_2024_2025.csv`, suggesting it contains real observational data. The app startup log calls it `"Real DLH Jakarta baseline dataset"`.
|
| 33 |
+
|
| 34 |
+
All model evaluation metrics (R², MAE, RMSE, MAPE) therefore measure how well the ML model learns the **synthetic generation function**, not real-world forecasting performance.
|
| 35 |
+
|
| 36 |
+
**Classification**: SYNTHETIC SIMULATION DATA
|
| 37 |
+
**Impact**: All published model performance metrics are synthetic benchmarks only.
|
| 38 |
+
**Required fix**: Rename dataset, update documentation, label all metrics as synthetic benchmarks.
|
| 39 |
+
|
| 40 |
+
---
|
| 41 |
+
|
| 42 |
+
### P0-2 · R² Converted to Operational "Efficiency" and "Accuracy"
|
| 43 |
+
|
| 44 |
+
**Affected files**:
|
| 45 |
+
- `frontend/index.html` lines ~148, ~181
|
| 46 |
+
- `app.py` `/llms.txt`, `/llms-full.txt` endpoints
|
| 47 |
+
- `README.md`
|
| 48 |
+
|
| 49 |
+
**Finding**:
|
| 50 |
+
- `index.html`: *"Meningkatkan efisiensi tata kelola sampah DKI Jakarta hingga 98.28%"*
|
| 51 |
+
- `index.html`: *"Akurasi Validitas Tinggi (98.28%)"*
|
| 52 |
+
- `app.py` llms.txt: *"R²=98.28%, MAPE=1.72%"*
|
| 53 |
+
- `app.py` llms-full.txt: *"GBR trained with R² = 98.28% accuracy"*
|
| 54 |
+
|
| 55 |
+
Additionally, the figures are inconsistent: README states GBR R²=88.45%, while llms.txt states 98.28%. The current deployed model is a StackingRegressor, not GBR.
|
| 56 |
+
|
| 57 |
+
**Classification**: Misleading metric representation
|
| 58 |
+
**Required fix**: Remove all R²-as-efficiency/accuracy conversions. Remove inconsistent metric values. Label remaining metrics as synthetic benchmark only.
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
### P0-3 · News System Fabricates Articles with Real Publisher Branding
|
| 63 |
+
|
| 64 |
+
**Affected files**:
|
| 65 |
+
- `app.py` lines ~587–659 (`generate_dynamic_news_fallback`)
|
| 66 |
+
- `app.py` lines ~661–737 (`/api/v1/news`)
|
| 67 |
+
|
| 68 |
+
**Finding**:
|
| 69 |
+
The LLM prompt instructs the model to *"generate mock but highly realistic and valid-looking news articles"* under real publisher names (Kompas.com, Detik.com, Antara News). The fallback function reuses real article URLs from Detik.com/Antara with randomly modified content (random truck counts, random dates). A user clicking the URL receives a real article whose content does not match the displayed headline.
|
| 70 |
+
|
| 71 |
+
**Classification**: Fabricated journalism under trusted publisher branding
|
| 72 |
+
**Required fix**: Remove LLM fabrication. Remove dynamic fallback generator. Serve only curated static articles with verified URLs.
|
| 73 |
+
|
| 74 |
+
---
|
| 75 |
+
|
| 76 |
+
### P0-4 · Normal_Avg_Ton Creates Target Leakage
|
| 77 |
+
|
| 78 |
+
**Affected files**:
|
| 79 |
+
- `scripts/generate_real_kecamatan_dataset.py` line 217
|
| 80 |
+
- `scripts/train.py` line 69
|
| 81 |
+
|
| 82 |
+
**Finding**:
|
| 83 |
+
`Normal_Avg_Ton` in the dataset is exactly `base_ton`, the constant used to generate `Volume_Sampah_Ton`. The model is trained with this constant as a feature. Since `Volume_Sampah_Ton = f(base_ton, noise)`, the model trivially learns to predict using `Normal_Avg_Ton`, which explains the high R² achieved on synthetic data. This is not evidence of meaningful learning.
|
| 84 |
+
|
| 85 |
+
**Classification**: Target leakage risk (within synthetic context)
|
| 86 |
+
**Note**: This does not affect real-world deployment if real data is substituted (real `Normal_Avg_Ton` from DLH could be genuinely informative). The leakage is only critical within the synthetic training/evaluation loop.
|
| 87 |
+
|
| 88 |
+
---
|
| 89 |
+
|
| 90 |
+
### P0-5 · Truck Capacity Inconsistency (8T docs vs 15T code)
|
| 91 |
+
|
| 92 |
+
**Affected files**:
|
| 93 |
+
- `README.md` line 51: *"Armada Truk Compactor (8-Ton Divisor)"*
|
| 94 |
+
- `app.py` `/llms.txt` line ~532: *"Truck Divisor: 8-Ton"*
|
| 95 |
+
- `app.py` `/llms-full.txt` line ~561: *"8-Ton Compactor trucks (vol / 8)"*
|
| 96 |
+
- `services/logistics_engine.py` line 25: `truck_capacity_ton: 15.0` (active code)
|
| 97 |
+
|
| 98 |
+
**Finding**:
|
| 99 |
+
All public-facing documentation describes 8T trucks. The active logistics engine uses 15T. Fleet calculations differ by 87.5% depending on which figure is referenced.
|
| 100 |
+
|
| 101 |
+
**Required fix**: Standardize to 15T throughout (matches active code). Label as prototype assumption, not validated DLH specification.
|
| 102 |
+
|
| 103 |
+
---
|
| 104 |
+
|
| 105 |
+
## P1 — Engineering Issues
|
| 106 |
+
|
| 107 |
+
### P1-1 · Architecture Description Outdated (GBR/Chronos vs StackingRegressor)
|
| 108 |
+
- `README.md`, `docs/BACKEND_DOC.md`, `/status` API endpoint still describe GBR + Chronos as the spatial ML engine.
|
| 109 |
+
- Active trained model (`models/model_sampah_advanced.pkl`) is a StackingRegressor (DT + RF + GBR → Ridge).
|
| 110 |
+
- **Fix**: Update all documentation to describe actual StackingRegressor architecture.
|
| 111 |
+
|
| 112 |
+
### P1-2 · Model Metadata Lacks Provenance Fields
|
| 113 |
+
- Current `model_metadata.pkl` stores only `feature_cols`, `zone_map`, `metrics`, `best_params`.
|
| 114 |
+
- Missing: `model_name`, `model_version`, `trained_at`, `training_dataset`, `dataset_type`, `evaluation_type`, `evaluation_note`, `git_commit`.
|
| 115 |
+
- **Fix**: Extend metadata schema in `scripts/train.py`.
|
| 116 |
+
|
| 117 |
+
### P1-3 · CORS Misconfiguration
|
| 118 |
+
- `app.py`: `allow_origins=["*"]` combined with `allow_credentials=True` is invalid per browser CORS spec.
|
| 119 |
+
- **Fix**: Remove `allow_credentials=True`.
|
| 120 |
+
|
| 121 |
+
### P1-4 · Startup Log Misidentifies Data Source
|
| 122 |
+
- `app.py` line ~393: `"Real DLH Jakarta baseline dataset loaded"`
|
| 123 |
+
- **Fix**: Update to `"Synthetic spatial training dataset loaded"`.
|
| 124 |
+
|
| 125 |
+
### P1-5 · No Baseline Model Comparison
|
| 126 |
+
- The ML model is not compared against simple baselines (last-value, rolling mean, historical mean).
|
| 127 |
+
- **Fix**: Add baseline comparison in `scripts/train.py`.
|
| 128 |
+
|
| 129 |
+
### P1-6 · Logistics Config Labeled as "DLH Standards" Without Citation
|
| 130 |
+
- `services/logistics_engine.py` line 21: *"Standards derived from Dinas Lingkungan Hidup (DLH) DKI Jakarta"*
|
| 131 |
+
- No official DLH document cited.
|
| 132 |
+
- **Fix**: Change to `"Prototype Operational Assumptions"` with a note that DLH validation is pending.
|
| 133 |
+
|
| 134 |
+
### P1-7 · Forecast Reliability Score Uses MAPE as Accuracy Complement
|
| 135 |
+
- `services/logistics_engine.py` line ~361: *"A MAPE of 6.12% represents ~93.88% accuracy precision"*
|
| 136 |
+
- MAPE is not a complement of accuracy.
|
| 137 |
+
- **Fix**: Remove incorrect framing.
|
| 138 |
+
|
| 139 |
+
### P1-8 · Hardcoded Fallback MAPE in Reliability Score
|
| 140 |
+
- `calculate_forecast_reliability_score` defaults to `test_mape=6.12` — a hardcoded value from a previous evaluation.
|
| 141 |
+
- **Fix**: Load MAPE from model metadata, document the hardcoded fallback explicitly.
|
| 142 |
+
|
| 143 |
+
---
|
| 144 |
+
|
| 145 |
+
## P2 — Improvements
|
| 146 |
+
|
| 147 |
+
### P2-1 · No `data_status` Field in API Responses
|
| 148 |
+
- API responses lack fields: `data_status`, `disclaimer`, `model_version`, `training_data_type`, `generated_at`.
|
| 149 |
+
|
| 150 |
+
### P2-2 · Uncertainty Intervals Are Hardcoded ±15%
|
| 151 |
+
- `app.py` line ~316: `"lower": round(vol*0.85, 2), "upper": round(vol*1.15, 2)` — not statistically derived.
|
| 152 |
+
- **Fix**: Label as `"indicative_range"` and document as non-statistical.
|
| 153 |
+
|
| 154 |
+
### P2-3 · No Data Provenance Documentation
|
| 155 |
+
- No `docs/DATA_PROVENANCE.md`.
|
| 156 |
+
|
| 157 |
+
### P2-4 · No Real Data Connector Architecture
|
| 158 |
+
- No `data_sources/` package.
|
| 159 |
+
|
| 160 |
+
### P2-5 · No Methodology Page in Frontend
|
| 161 |
+
|
| 162 |
+
### P2-6 · No Frontend Data Status Badge
|
| 163 |
+
|
| 164 |
+
---
|
| 165 |
+
|
| 166 |
+
## Pipeline Trace
|
| 167 |
+
|
| 168 |
+
```
|
| 169 |
+
SOURCE
|
| 170 |
+
└─ scripts/generate_real_kecamatan_dataset.py
|
| 171 |
+
Type: SYNTHETIC SIMULATION
|
| 172 |
+
Inputs: hardcoded constants, np.random
|
| 173 |
+
Output: data/dataset_real_kecamatan_2024_2025.csv
|
| 174 |
+
|
| 175 |
+
PREPROCESSING
|
| 176 |
+
└─ scripts/train.py
|
| 177 |
+
- Chronological sort (prevents temporal ordering issue)
|
| 178 |
+
- Zone_Type categorical encoding
|
| 179 |
+
- Train cutoff: 2025-07-01 (chronological split — correct approach)
|
| 180 |
+
|
| 181 |
+
FEATURES
|
| 182 |
+
└─ Population_Jiwa: SYNTHETIC (from generator constants)
|
| 183 |
+
└─ Normal_Avg_Ton: SYNTHETIC (= base_ton, source of target leakage)
|
| 184 |
+
└─ Zone_Type_Code: DERIVED (ordinal encoding)
|
| 185 |
+
└─ Rainfall_mm: SYNTHETIC (in training) / EXTERNAL_REALTIME (in inference)
|
| 186 |
+
└─ Rain_Lag_1: SYNTHETIC/DERIVED
|
| 187 |
+
└─ Is_Weekend, Hari_Dalam_Minggu, Bulan: DERIVED (calendar)
|
| 188 |
+
└─ Is_Mudik: DERIVED (hardcoded window)
|
| 189 |
+
└─ Ada_Event, Event_Crowd_Headcount: SYNTHETIC (hardcoded event calendar)
|
| 190 |
+
|
| 191 |
+
TRAINING
|
| 192 |
+
└─ StackingRegressor (DT + RF + GBR → Ridge)
|
| 193 |
+
└─ Chronological split: Train < 2025-07-01, Test >= 2025-07-01
|
| 194 |
+
└─ Evaluation: MODE A SYNTHETIC BENCHMARK ONLY
|
| 195 |
+
|
| 196 |
+
INFERENCE
|
| 197 |
+
└─ Rainfall: EXTERNAL_REALTIME via Open-Meteo API
|
| 198 |
+
└─ Population: UNVERIFIED (manually entered, needs BPS validation)
|
| 199 |
+
└─ Events: MANUALLY CURATED calendar
|
| 200 |
+
|
| 201 |
+
FRONTEND
|
| 202 |
+
└─ Volume forecast labeled as FORECAST (correct)
|
| 203 |
+
└─ Compositions labeled as derived proportions (correct)
|
| 204 |
+
└─ Logistics plan: DETERMINISTIC OPERATIONAL SIMULATION (not AI)
|
| 205 |
+
|
| 206 |
+
LOGISTICS
|
| 207 |
+
└─ services/logistics_engine.py
|
| 208 |
+
└─ Fleet: ceil(volume / effective_capacity) — DETERMINISTIC MATH
|
| 209 |
+
└─ Manpower: trucks × 3 crew — PROTOTYPE ASSUMPTION
|
| 210 |
+
└─ Collection time: volume / (trucks × rate) — DETERMINISTIC MATH
|
| 211 |
+
└─ Efficiency score: weighted formula — PROTOTYPE SIMULATION
|
| 212 |
+
```
|
| 213 |
+
|
| 214 |
+
---
|
| 215 |
+
|
| 216 |
+
## Summary
|
| 217 |
+
|
| 218 |
+
| Category | Count |
|
| 219 |
+
|----------|-------|
|
| 220 |
+
| P0 Critical | 5 |
|
| 221 |
+
| P1 Engineering | 8 |
|
| 222 |
+
| P2 Improvement | 6 |
|
| 223 |
+
| **Total** | **19** |
|
| 224 |
+
|
| 225 |
+
All P0 issues are addressed in this refactor branch. P1 and P2 issues are addressed where feasible without breaking existing functionality.
|