ALAMDIENG commited on
Commit
938a0db
·
1 Parent(s): 0ee1043

audit: document P0/P1/P2 findings in AUDIT_REPORT.md

Browse files
Files changed (1) hide show
  1. 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.