idacy commited on
Commit
e4b1ed6
·
verified ·
1 Parent(s): b8e23e7

Upload live inference API deployment files

Browse files
Files changed (38) hide show
  1. .dockerignore +16 -0
  2. .gitattributes +1 -0
  3. Dockerfile +16 -0
  4. data/model_runs/synthetic_v1_baseline/README.md +87 -0
  5. data/model_runs/synthetic_v1_baseline/calibration_metrics.json +119 -0
  6. data/model_runs/synthetic_v1_baseline/classification_report.json +45 -0
  7. data/model_runs/synthetic_v1_baseline/confusion_matrix.csv +6 -0
  8. data/model_runs/synthetic_v1_baseline/evidence_audit_sample.csv +51 -0
  9. data/model_runs/synthetic_v1_baseline/excluded_columns.json +76 -0
  10. data/model_runs/synthetic_v1_baseline/feature_columns.json +154 -0
  11. data/model_runs/synthetic_v1_baseline/feature_importance.csv +153 -0
  12. data/model_runs/synthetic_v1_baseline/manifest.json +127 -0
  13. data/model_runs/synthetic_v1_baseline/metrics.json +931 -0
  14. data/model_runs/synthetic_v1_baseline/model.joblib +3 -0
  15. data/model_runs/synthetic_v1_baseline/predictions_all.csv +3 -0
  16. data/model_runs/synthetic_v1_baseline/predictions_test.csv +0 -0
  17. data/model_runs/synthetic_v1_baseline/preprocessing.joblib +3 -0
  18. data/model_runs/synthetic_v1_baseline/split_manifest.json +0 -0
  19. data/model_runs/synthetic_v1_baseline/stress_metrics.json +707 -0
  20. data/model_runs/synthetic_v1_baseline/stress_summary.md +50 -0
  21. data/model_runs/synthetic_v1_baseline/validation_summary.md +43 -0
  22. src/datacenter_verification_api/__init__.py +5 -0
  23. src/datacenter_verification_api/app.py +111 -0
  24. src/datacenter_verification_api/feature_completion.py +298 -0
  25. src/datacenter_verification_api/model_service.py +249 -0
  26. src/datacenter_verification_api/requirements.txt +7 -0
  27. src/datacenter_verification_api/schemas.py +80 -0
  28. src/datacenter_verification_api/smoke_test.py +96 -0
  29. src/datacenter_verification_modeling/README.md +111 -0
  30. src/datacenter_verification_modeling/__init__.py +6 -0
  31. src/datacenter_verification_modeling/common.py +611 -0
  32. src/datacenter_verification_modeling/evaluate_model.py +704 -0
  33. src/datacenter_verification_modeling/evaluate_stress_splits.py +366 -0
  34. src/datacenter_verification_modeling/predict.py +30 -0
  35. src/datacenter_verification_modeling/prepare_features.py +49 -0
  36. src/datacenter_verification_modeling/requirements.txt +5 -0
  37. src/datacenter_verification_modeling/rule_baseline.py +192 -0
  38. src/datacenter_verification_modeling/train_model.py +226 -0
.dockerignore ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .git
2
+ .DS_Store
3
+ *__pycache__*
4
+ *.pyc
5
+ xx_private
6
+
7
+ data/synthetic_v0
8
+ data/model_runs/synthetic_v0_baseline
9
+
10
+ data/synthetic_v1/raw_normalized
11
+ data/synthetic_v1/examples
12
+ data/synthetic_v1/schemas
13
+ data/synthetic_v1/validation
14
+ data/synthetic_v1/workbook_rules
15
+ data/synthetic_v1/features/*
16
+ !data/synthetic_v1/features/window_features_all.csv
.gitattributes CHANGED
@@ -34,3 +34,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  data/synthetic_v1/features/window_features_all.csv filter=lfs diff=lfs merge=lfs -text
 
 
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  data/synthetic_v1/features/window_features_all.csv filter=lfs diff=lfs merge=lfs -text
37
+ data/model_runs/synthetic_v1_baseline/predictions_all.csv filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ ENV PYTHONUNBUFFERED=1
4
+ ENV DCV_MODEL_RUN_DIR=data/model_runs/synthetic_v1_baseline
5
+ ENV DCV_FEATURE_TABLE=data/synthetic_v1/features/window_features_all.csv
6
+
7
+ WORKDIR /app
8
+
9
+ COPY src/datacenter_verification_api/requirements.txt /tmp/requirements-api.txt
10
+ RUN pip install --no-cache-dir -r /tmp/requirements-api.txt
11
+
12
+ COPY src ./src
13
+ COPY data/model_runs/synthetic_v1_baseline ./data/model_runs/synthetic_v1_baseline
14
+ COPY data/synthetic_v1/features/window_features_all.csv ./data/synthetic_v1/features/window_features_all.csv
15
+
16
+ CMD ["sh", "-c", "uvicorn src.datacenter_verification_api.app:app --host 0.0.0.0 --port ${PORT:-7860}"]
data/model_runs/synthetic_v1_baseline/README.md ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # synthetic_v1_baseline model run
2
+
3
+ This directory contains a public runnable baseline for the `synthetic_v1` datacenter training-run verification dataset.
4
+
5
+ ## Dataset
6
+
7
+ - Feature table: `data/synthetic_v1/features/window_features_all.csv`
8
+ - Rows: 14712
9
+ - Episode split: grouped by `episode_id`, scenario-stratified, seed `20260510`
10
+ - Split rows: train 8870, validation 2937, test 2905
11
+ - Split episodes: train 458, validation 152, test 150
12
+
13
+ ## Model
14
+
15
+ - Supervised model: calibrated scikit-learn histogram gradient boosting classifier
16
+ - Calibration: validation split only, held-out test evaluated once
17
+ - Rule baseline: deterministic evidence rules in `src/datacenter_verification_modeling/rule_baseline.py`
18
+ - Leakage exclusions: identifiers, labels, site id, episode id, raw manifest hash, scenario metadata, counterfactual metadata, and synthetic-only audit columns
19
+
20
+ ## Headline Test Metrics
21
+
22
+ - Accuracy: 0.9725
23
+ - Macro F1: 0.9781
24
+ - Weighted F1: 0.9723
25
+ - Log loss: 0.1057
26
+ - Label 3/4 precision by predicted label: 0.9935
27
+ - Label 3/4 recall by predicted label: 0.9967
28
+ - `p_large_training >= 0.5` precision: 0.9935
29
+ - `p_large_training >= 0.5` recall: 0.9967
30
+ - Rule baseline macro F1: 0.3636
31
+
32
+ ## Error Scenarios
33
+
34
+ - Largest false-positive scenarios at `p_large_training >= 0.5`: underclocked_energy_capped_training: 4
35
+ - Largest false-negative scenarios at `p_large_training >= 0.5`: fragmented_training_linked: 2
36
+
37
+ ## Calibration
38
+
39
+ - Brier score for `p_large_training`: 0.0018
40
+ - Expected calibration error for `p_large_training`: 0.0105
41
+
42
+ ## Stress Splits
43
+
44
+ Stress outputs are in:
45
+
46
+ - `stress_metrics.json`
47
+ - `stress_summary.md`
48
+
49
+ Key stress checks from the lightweight retraining script:
50
+
51
+ - Episode-grouped random split macro F1: 0.9820; large-training recall: 0.9967
52
+ - Time holdout macro F1: 0.9863; large-training recall: 1.0000
53
+ - Underclocked holdout large-training recall: 1.0000; false negatives: 0
54
+ - Fragmented linked training holdout remains hard: 91 large-training false negatives
55
+ - Source ablation dropping runtime and ML logs changed macro F1 by -0.089 versus the random stress baseline
56
+ - Middle `p_large_training` bins are populated in stress checks: {'0.1_to_0.3': 18, '0.3_to_0.7': 2, '0.7_to_0.9': 0}
57
+
58
+ ## Reproduce
59
+
60
+ ```bash
61
+ python src/datacenter_verification_modeling/train_model.py \
62
+ --features data/synthetic_v1/features/window_features_all.csv \
63
+ --output data/model_runs/synthetic_v1_baseline \
64
+ --seed 20260510
65
+ ```
66
+
67
+ ```bash
68
+ python src/datacenter_verification_modeling/evaluate_model.py \
69
+ --model-run data/model_runs/synthetic_v1_baseline \
70
+ --features data/synthetic_v1/features/window_features_all.csv
71
+ ```
72
+
73
+ ```bash
74
+ python src/datacenter_verification_modeling/predict.py \
75
+ --model-run data/model_runs/synthetic_v1_baseline \
76
+ --features data/synthetic_v1/features/window_features_all.csv \
77
+ --output data/model_runs/synthetic_v1_baseline/predictions_all.csv
78
+ ```
79
+
80
+ ## Limitations
81
+
82
+ - This model is trained on synthetic data only.
83
+ - Performance numbers are not real-world deployment claims.
84
+ - Adjacent windows are correlated, so group splitting by `episode_id` is mandatory.
85
+ - Synthetic labels are generated from rules and latent scenarios, so the model may learn generator assumptions.
86
+ - Real datacenter deployment would require calibration on real telemetry and controlled drills.
87
+ - The model should assist audit triage; it should not be treated as sole proof of a violation.
data/model_runs/synthetic_v1_baseline/calibration_metrics.json ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "brier_large_training": 0.001846207706971921,
3
+ "calibration_by_label": {
4
+ "0": {
5
+ "brier": 0.00841142332402854,
6
+ "mean_predicted_probability": 0.2744490972864224,
7
+ "observed_fraction": 0.2623063683304647,
8
+ "rows": 2905
9
+ },
10
+ "1": {
11
+ "brier": 0.024583974737105475,
12
+ "mean_predicted_probability": 0.21573853144684202,
13
+ "observed_fraction": 0.23752151462994836,
14
+ "rows": 2905
15
+ },
16
+ "2": {
17
+ "brier": 0.018264729634158587,
18
+ "mean_predicted_probability": 0.3017207163585596,
19
+ "observed_fraction": 0.2884681583476764,
20
+ "rows": 2905
21
+ },
22
+ "3": {
23
+ "brier": 0.0017921366976442609,
24
+ "mean_predicted_probability": 0.1570568215171177,
25
+ "observed_fraction": 0.15938037865748708,
26
+ "rows": 2905
27
+ },
28
+ "4": {
29
+ "brier": 7.20109313866924e-05,
30
+ "mean_predicted_probability": 0.05103483339105826,
31
+ "observed_fraction": 0.05232358003442341,
32
+ "rows": 2905
33
+ }
34
+ },
35
+ "expected_calibration_error_large_training": 0.010488907264903001,
36
+ "log_loss": 0.10567154528993643,
37
+ "reliability_bins_large_training": [
38
+ {
39
+ "bin_index": 0,
40
+ "count": 2283,
41
+ "lower": 0.0,
42
+ "mean_predicted_probability": 0.002786695213286837,
43
+ "observed_fraction": 0.0,
44
+ "upper": 0.1
45
+ },
46
+ {
47
+ "bin_index": 1,
48
+ "count": 0,
49
+ "lower": 0.1,
50
+ "mean_predicted_probability": 0.0,
51
+ "observed_fraction": 0.0,
52
+ "upper": 0.2
53
+ },
54
+ {
55
+ "bin_index": 2,
56
+ "count": 2,
57
+ "lower": 0.2,
58
+ "mean_predicted_probability": 0.24783574032227917,
59
+ "observed_fraction": 1.0,
60
+ "upper": 0.30000000000000004
61
+ },
62
+ {
63
+ "bin_index": 3,
64
+ "count": 3,
65
+ "lower": 0.30000000000000004,
66
+ "mean_predicted_probability": 0.30455095537998655,
67
+ "observed_fraction": 0.0,
68
+ "upper": 0.4
69
+ },
70
+ {
71
+ "bin_index": 4,
72
+ "count": 0,
73
+ "lower": 0.4,
74
+ "mean_predicted_probability": 0.0,
75
+ "observed_fraction": 0.0,
76
+ "upper": 0.5
77
+ },
78
+ {
79
+ "bin_index": 5,
80
+ "count": 8,
81
+ "lower": 0.5,
82
+ "mean_predicted_probability": 0.5612793252894144,
83
+ "observed_fraction": 1.0,
84
+ "upper": 0.6000000000000001
85
+ },
86
+ {
87
+ "bin_index": 6,
88
+ "count": 4,
89
+ "lower": 0.6000000000000001,
90
+ "mean_predicted_probability": 0.6781471295808353,
91
+ "observed_fraction": 0.0,
92
+ "upper": 0.7000000000000001
93
+ },
94
+ {
95
+ "bin_index": 7,
96
+ "count": 0,
97
+ "lower": 0.7000000000000001,
98
+ "mean_predicted_probability": 0.0,
99
+ "observed_fraction": 0.0,
100
+ "upper": 0.8
101
+ },
102
+ {
103
+ "bin_index": 8,
104
+ "count": 4,
105
+ "lower": 0.8,
106
+ "mean_predicted_probability": 0.8676498355263033,
107
+ "observed_fraction": 1.0,
108
+ "upper": 0.9
109
+ },
110
+ {
111
+ "bin_index": 9,
112
+ "count": 601,
113
+ "lower": 0.9,
114
+ "mean_predicted_probability": 0.9751439027068034,
115
+ "observed_fraction": 1.0,
116
+ "upper": 1.0
117
+ }
118
+ ]
119
+ }
data/model_runs/synthetic_v1_baseline/classification_report.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "0": {
3
+ "f1-score": 0.9915419648666233,
4
+ "precision": 0.983225806451613,
5
+ "recall": 1.0,
6
+ "support": 762.0
7
+ },
8
+ "1": {
9
+ "f1-score": 0.9450222882615156,
10
+ "precision": 0.9695121951219512,
11
+ "recall": 0.9217391304347826,
12
+ "support": 690.0
13
+ },
14
+ "2": {
15
+ "f1-score": 0.96047197640118,
16
+ "precision": 0.9498249708284714,
17
+ "recall": 0.9713603818615751,
18
+ "support": 838.0
19
+ },
20
+ "3": {
21
+ "f1-score": 0.9935344827586207,
22
+ "precision": 0.9913978494623656,
23
+ "recall": 0.9956803455723542,
24
+ "support": 463.0
25
+ },
26
+ "4": {
27
+ "f1-score": 1.0,
28
+ "precision": 1.0,
29
+ "recall": 1.0,
30
+ "support": 152.0
31
+ },
32
+ "accuracy": 0.9724612736660929,
33
+ "macro avg": {
34
+ "f1-score": 0.9781141424575879,
35
+ "precision": 0.9787921643728803,
36
+ "recall": 0.9777559715737423,
37
+ "support": 2905.0
38
+ },
39
+ "weighted avg": {
40
+ "f1-score": 0.9722899614011162,
41
+ "precision": 0.9725136003461651,
42
+ "recall": 0.9724612736660929,
43
+ "support": 2905.0
44
+ }
45
+ }
data/model_runs/synthetic_v1_baseline/confusion_matrix.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ true_label,pred_0,pred_1,pred_2,pred_3,pred_4
2
+ true_0,762,0,0,0,0
3
+ true_1,13,636,41,0,0
4
+ true_2,0,20,814,4,0
5
+ true_3,0,0,2,461,0
6
+ true_4,0,0,0,0,152
data/model_runs/synthetic_v1_baseline/evidence_audit_sample.csv ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ audit_category,feature_row_id,split,latent_workload_class,true_label,predicted_label,p_large_training,severity_score,top_evidence,critical_missing_layers,integrity_warning,policy_compute_ratio,o2_max_concurrent_normalized_gpus,o2_allocation_duration_hours,o2_gpu_hours_policy_ratio,o4_gpu_util_p95,o4_gpu_util_duty_gt_70,o7_synchronized_fabric_footprint,o7_collective_periodicity_score,o8_rack_power_fraction_p95,o10_runtime_framework_class,o11_checkpoint_periodicity_score,o12_signed_ml_logs_present,o12_declared_parameter_count_b,o14_min_critical_coverage,o14_gap_fraction_critical,o13_confidential_compute_mode_fraction,o2_elastic_resize_count,o2_preemption_restart_count,o2_account_linkage_confidence,o4_hbm_pressure_duration_fraction,o4_power_cap_active_fraction,o7_account_flow_linkage_confidence,o10_runtime_metadata_confidence,o11_artifact_write_pattern_score,o11_dataloader_read_pattern_score,o12_log_delivery_delay_hours,o12_log_completeness_fraction,o4_missing_reason,o7_missing_reason,o12_missing_reason
2
+ correct_label_0,feat_cc640fafc2b8e3abf770c579,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1549,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
3
+ correct_label_0,feat_b9e262daf17134352518a78d,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1549,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
4
+ correct_label_0,feat_f2d694eed3d39809a028e2b2,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1549,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
5
+ correct_label_0,feat_729feb643bec26839d5503c5,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1053,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
6
+ correct_label_0,feat_3d5fc07aac19d9a5f2f3b3c4,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1053,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
7
+ correct_label_0,feat_c7f33b2997f139238815270f,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1115,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
8
+ correct_label_0,feat_313de45b023c747cb085c198,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1115,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
9
+ correct_label_0,feat_ae25d41c049ce4e3cac1242a,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1053,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
10
+ correct_label_0,feat_a4957cf7cc0d15a01c6fb2e3,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1053,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
11
+ correct_label_0,feat_5f0c6f536a6333175af7d1ad,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1549,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
12
+ correct_label_2_hard_false_positive,feat_a13e07bfed9432972ef31ffd,test,large_batch_inference,2,2,0.011030947812113877,1.977255258500455,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,56.3448,0.3667,162.0,0.1229,0.3835,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
13
+ correct_label_2_hard_false_positive,feat_00e8cf11a42724292298701b,test,large_batch_inference,2,2,0.010478314009615335,1.975675486490715,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,43.0872,0.2804,124.0,0.0809,0.406,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
14
+ correct_label_2_hard_false_positive,feat_cd03bedba3159582ed1baa14,test,embedding_generation,2,2,0.010431098895779199,1.9626186151571463,large allocation; high GPU activity,,False,0.3629,713.64,6.25,0.3629,63.8316,0.3986,164.0,0.1905,0.3809,embedding_generation,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3328,0.0,1.0,1.0,0.0,0.3263,0.0,0.0,observed,observed,privacy_redacted
15
+ correct_label_2_hard_false_positive,feat_34bca06c5eb77d0686eb0b00,test,large_batch_inference,2,2,0.010426946064660568,1.975794732799792,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,43.0872,0.2804,124.0,0.094,0.406,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
16
+ correct_label_2_hard_false_positive,feat_a9949f2525b28aadd5101e47,test,large_batch_inference,2,2,0.010426946064660568,1.975794732799792,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,43.0872,0.2804,124.0,0.094,0.406,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
17
+ correct_label_2_hard_false_positive,feat_33b02f78b085c861a75ce235,test,large_batch_inference,2,2,0.0103685521855723,1.9784666216612823,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,82.86,0.5393,226.0,0.1808,0.5639,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
18
+ correct_label_2_hard_false_positive,feat_db7205bdba2eac91416428d7,test,large_batch_inference,2,2,0.0103685521855723,1.9784666216612823,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,82.86,0.5393,226.0,0.1808,0.5639,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
19
+ correct_label_2_hard_false_positive,feat_9aa413faa9c554abf6d2bb64,test,large_batch_inference,2,2,0.010222344032340501,1.9762696989913593,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,56.3448,0.3667,162.0,0.1057,0.3835,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
20
+ correct_label_2_hard_false_positive,feat_d7444c225b8334e750e5a74e,test,large_batch_inference,2,2,0.010177838904019004,1.9763730138561455,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,56.3448,0.3667,162.0,0.1229,0.3835,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
21
+ correct_label_2_hard_false_positive,feat_123d069e43df582c24b12e43,test,large_batch_inference,2,2,0.010177838904019004,1.9763730138561455,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,56.3448,0.3667,162.0,0.1229,0.3835,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
22
+ correct_label_3_4_likely_training,feat_aa9a945cbc067309f88926db,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
23
+ correct_label_3_4_likely_training,feat_45062add05716b1857d2d421,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
24
+ correct_label_3_4_likely_training,feat_daa683c0ae4f6ebda93534f5,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
25
+ correct_label_3_4_likely_training,feat_1a65e998609bc78ecf0dc1cb,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
26
+ correct_label_3_4_likely_training,feat_e60eeafb2f9e0d9386d1c5d0,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
27
+ correct_label_3_4_likely_training,feat_3fa8d05e390014d891252788,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
28
+ correct_label_3_4_likely_training,feat_1f1405339cd56e6df182c550,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
29
+ correct_label_3_4_likely_training,feat_2bf4e8f647710e486ecd057c,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
30
+ correct_label_3_4_likely_training,feat_af8e945fc6fc7f9343da8288,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
31
+ correct_label_3_4_likely_training,feat_2267762b2b19047a21c23936,test,training_without_semantic_logs,3,3,0.9800541368127997,2.9537374894493977,large allocation; long allocation duration; high GPU activity; training runtime metadata; checkpoint cadence,,False,2.5163,461.48,67.0,2.5163,71.44,0.8187,302.0,0.5238,0.38,pytorch_distributed_training,0.7481,False,,0.98,0.01,0.0,0,0,0.8407,0.4875,0.0,0.8407,0.55,0.6235,0.5428,0.0,0.0,observed,observed,privacy_redacted
32
+ false_positive_large_training,feat_49c6f202eb6d2980105018df,test,underclocked_energy_capped_training,2,3,0.6981856840003529,2.6442344946513,large allocation; long allocation duration; high GPU activity; training runtime metadata; signed ML logs; low critical coverage; counter disabled by CC mode,O4:counter_disabled_by_cc_mode:coverage=0.29,True,9.2912,513.05,222.53,9.2912,53.4752,0.5516,267.0,0.4296,0.4283,pytorch_distributed_training,0.2781,True,316.04,0.2921,0.01,0.8046,0,0,1.0,0.3765,0.4654,1.0,0.7611,0.2456,0.7229,0.0,0.342,counter_disabled_by_cc_mode,observed,delayed_log_delivery
33
+ false_positive_large_training,feat_a383ed6dabdf9145d8ee996e,test,underclocked_energy_capped_training,2,3,0.6714676114409962,2.6140027926406604,large allocation; long allocation duration; high GPU activity; training runtime metadata; signed ML logs; low critical coverage; counter disabled by CC mode,O4:counter_disabled_by_cc_mode:coverage=0.29,True,9.2912,513.05,222.53,9.2912,53.4752,0.5516,267.0,0.3695,0.4283,pytorch_distributed_training,0.1981,True,316.04,0.2921,0.01,0.8046,0,0,1.0,0.3765,0.4654,1.0,0.7611,0.2456,0.7229,0.0,0.342,counter_disabled_by_cc_mode,observed,delayed_log_delivery
34
+ false_positive_large_training,feat_ee68609d2f2683b7bef8841b,test,underclocked_energy_capped_training,2,3,0.6714676114409962,2.6140027926406604,large allocation; long allocation duration; high GPU activity; training runtime metadata; signed ML logs; low critical coverage; counter disabled by CC mode,O4:counter_disabled_by_cc_mode:coverage=0.29,True,9.2912,513.05,222.53,9.2912,53.4752,0.5516,267.0,0.4296,0.4283,pytorch_distributed_training,0.1981,True,316.04,0.2921,0.01,0.8046,0,0,1.0,0.3765,0.4654,1.0,0.7611,0.2456,0.7229,0.0,0.342,counter_disabled_by_cc_mode,observed,delayed_log_delivery
35
+ false_positive_large_training,feat_50f4faf6dbd38b6daae6b62f,test,underclocked_energy_capped_training,2,3,0.6714676114409962,2.6140027926406604,large allocation; long allocation duration; high GPU activity; training runtime metadata; signed ML logs; low critical coverage; counter disabled by CC mode,O4:counter_disabled_by_cc_mode:coverage=0.29,True,9.2912,513.05,222.53,9.2912,53.4752,0.5516,267.0,0.4296,0.4283,pytorch_distributed_training,0.1981,True,316.04,0.2921,0.01,0.8046,0,0,1.0,0.3765,0.4654,1.0,0.7611,0.2456,0.7229,0.0,0.342,counter_disabled_by_cc_mode,observed,delayed_log_delivery
36
+ false_negative_large_training,feat_aa8009dde8ce7ceadcee314b,test,fragmented_training_linked,3,2,0.2474303218229664,2.2261162832074306,large allocation; long allocation duration; high GPU activity; training runtime metadata,,False,1.1744,260.88,55.32,1.1744,73.74,0.5307,211.0,0.5568,0.38,pytorch_distributed_training,0.4556,False,,0.98,0.01,0.0,0,0,0.2797,0.3251,0.0,0.2797,0.9268,0.3735,0.3108,0.0,0.0,observed,observed,privacy_redacted
37
+ false_negative_large_training,feat_1d405c4b2a8bf7a3fa052e89,test,fragmented_training_linked,3,2,0.24824115882159198,2.226857273426679,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata,,False,1.1744,260.88,55.32,1.1744,73.74,0.5307,211.0,0.6474,0.38,pytorch_distributed_training,0.4556,False,,0.98,0.01,0.0,0,0,0.2797,0.3251,0.0,0.2797,0.9268,0.3735,0.3108,0.0,0.0,observed,observed,privacy_redacted
38
+ high_uncertainty_or_coverage_edge,feat_3652e586595bc4b0f0a73268,test,training_without_semantic_logs,3,3,0.5362924911368747,2.402141567675965,large allocation; long allocation duration; high GPU activity; power corroboration; training runtime metadata,,False,2.8294,748.77,46.43,2.8294,59.602,0.4835,310.0,0.2928,0.6028,pytorch_distributed_training,0.2225,False,,0.98,0.01,0.0,0,0,1.0,0.533,0.0,1.0,0.7154,0.2446,0.5083,0.0,0.0,observed,observed,privacy_redacted
39
+ high_uncertainty_or_coverage_edge,feat_9aff07df6b963210981b227a,test,training_without_semantic_logs,3,3,0.5362924911368747,2.402141567675965,large allocation; long allocation duration; high GPU activity; power corroboration; training runtime metadata,,False,2.8294,748.77,46.43,2.8294,59.602,0.4835,310.0,0.3405,0.6028,pytorch_distributed_training,0.2225,False,,0.98,0.01,0.0,0,0,1.0,0.533,0.0,1.0,0.7154,0.2446,0.5083,0.0,0.0,observed,observed,privacy_redacted
40
+ high_uncertainty_or_coverage_edge,feat_4877f8beca7baf999dfc875b,test,training_without_semantic_logs,3,3,0.5362924911368747,2.402141567675965,large allocation; long allocation duration; high GPU activity; power corroboration; training runtime metadata,,False,2.8294,748.77,46.43,2.8294,59.602,0.4835,310.0,0.3405,0.6028,pytorch_distributed_training,0.2225,False,,0.98,0.01,0.0,0,0,1.0,0.533,0.0,1.0,0.7154,0.2446,0.5083,0.0,0.0,observed,observed,privacy_redacted
41
+ high_uncertainty_or_coverage_edge,feat_48db5d097443aaf9625944d9,test,training_with_delayed_logs,3,3,0.5742738161131281,2.555844926309712,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,26.8381,1974.23,167.05,26.8381,64.9128,0.5727,1278.0,0.6055,0.3937,pytorch_distributed_training,0.2932,True,254.18,0.98,0.01,0.0,0,0,1.0,0.3817,0.0,1.0,0.8011,0.2015,0.6239,47.3,0.4119,observed,observed,delayed_log_delivery
42
+ high_uncertainty_or_coverage_edge,feat_9089822165b23e10b9ea605c,test,training_with_delayed_logs,3,3,0.575021844861732,2.5565689502386792,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,26.8381,1974.23,167.05,26.8381,64.9128,0.5727,1278.0,0.5207,0.3937,pytorch_distributed_training,0.2932,True,254.18,0.98,0.01,0.0,0,0,1.0,0.3817,0.0,1.0,0.8011,0.2015,0.6239,47.3,0.4119,observed,observed,delayed_log_delivery
43
+ high_uncertainty_or_coverage_edge,feat_d218ca927f7ec4d4efcd5cd2,test,training_with_delayed_logs,3,3,0.575021844861732,2.5565689502386792,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,26.8381,1974.23,167.05,26.8381,64.9128,0.5727,1278.0,0.6055,0.3937,pytorch_distributed_training,0.2932,True,254.18,0.98,0.01,0.0,0,0,1.0,0.3817,0.0,1.0,0.8011,0.2015,0.6239,47.3,0.4119,observed,observed,delayed_log_delivery
44
+ high_uncertainty_or_coverage_edge,feat_38e806129aa6f1882a2e6ec4,test,training_with_delayed_logs,3,3,0.575021844861732,2.5565689502386792,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,26.8381,1974.23,167.05,26.8381,64.9128,0.5727,1278.0,0.6055,0.3937,pytorch_distributed_training,0.2932,True,254.18,0.98,0.01,0.0,0,0,1.0,0.3817,0.0,1.0,0.8011,0.2015,0.6239,47.3,0.4119,observed,observed,delayed_log_delivery
45
+ high_uncertainty_or_coverage_edge,feat_f8462a0c8142dee9ac8578b1,test,training_without_semantic_logs,3,3,0.5820177782063665,2.458458270477721,large allocation; long allocation duration; high GPU activity; power corroboration; training runtime metadata,,False,2.8294,748.77,46.43,2.8294,59.602,0.4835,310.0,0.3405,0.6028,pytorch_distributed_training,0.3025,False,,0.98,0.01,0.0,0,0,1.0,0.533,0.0,1.0,0.7154,0.2446,0.5083,0.0,0.0,observed,observed,privacy_redacted
46
+ high_uncertainty_or_coverage_edge,feat_414b3f8239f8d77cc1b8fe90,test,fragmented_training_linked,2,2,0.3074817930715641,2.284158029977732,large allocation; long allocation duration; high GPU activity; training runtime metadata,,False,2.8713,272.73,129.37,2.8713,73.56,0.8511,179.0,0.5256,0.38,pytorch_distributed_training,0.4202,False,,0.98,0.01,0.0,0,0,0.2112,0.3986,0.0,0.2112,0.8524,0.5538,0.3507,0.0,0.0,observed,observed,privacy_redacted
47
+ high_uncertainty_or_coverage_edge,feat_ba27f8df9db4f33ee67bbb36,test,fragmented_training_linked,2,2,0.30308553653419773,2.280095247643677,large allocation; long allocation duration; high GPU activity; training runtime metadata,,False,2.8713,272.73,129.37,2.8713,73.56,0.8511,179.0,0.5256,0.38,pytorch_distributed_training,0.4202,False,,0.98,0.01,0.0,0,0,0.2112,0.3986,0.0,0.2112,0.8524,0.5538,0.3507,0.0,0.0,observed,observed,privacy_redacted
48
+ high_uncertainty_or_coverage_edge,feat_6de8a0c0b3e26c7a626710a7,test,fragmented_training_linked,2,2,0.30308553653419773,2.280095247643677,large allocation; long allocation duration; high GPU activity; training runtime metadata,,False,2.8713,272.73,129.37,2.8713,73.56,0.8511,179.0,0.5256,0.38,pytorch_distributed_training,0.4202,False,,0.98,0.01,0.0,0,0,0.2112,0.3986,0.0,0.2112,0.8524,0.5538,0.3507,0.0,0.0,observed,observed,privacy_redacted
49
+ high_uncertainty_or_coverage_edge,feat_b40bf45e96cf6cc4b679357e,test,training_with_delayed_logs,4,4,0.8674512750198456,3.7090949558285335,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,14.8646,944.34,193.42,14.8646,56.7324,0.4636,611.0,0.5216,0.306,pytorch_distributed_training,0.2137,True,167.81,0.98,0.01,0.0,0,0,1.0,0.3747,0.0,1.0,0.7358,0.2546,0.4936,58.2,0.9151,observed,observed,delayed_log_delivery
50
+ high_uncertainty_or_coverage_edge,feat_59636d473b9ba2c6c75a8909,test,training_with_delayed_logs,4,4,0.8674512750198456,3.7090949558285335,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,14.8646,944.34,193.42,14.8646,56.7324,0.4636,611.0,0.6066,0.306,pytorch_distributed_training,0.2137,True,167.81,0.98,0.01,0.0,0,0,1.0,0.3747,0.0,1.0,0.7358,0.2546,0.4936,58.2,0.9151,observed,observed,delayed_log_delivery
51
+ high_uncertainty_or_coverage_edge,feat_1fc099dcc832d0323269b33f,test,training_with_delayed_logs,4,4,0.8674512750198456,3.7090949558285335,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,14.8646,944.34,193.42,14.8646,56.7324,0.4636,611.0,0.6066,0.306,pytorch_distributed_training,0.2137,True,167.81,0.98,0.01,0.0,0,0,1.0,0.3747,0.0,1.0,0.7358,0.2546,0.4936,58.2,0.9151,observed,observed,delayed_log_delivery
data/model_runs/synthetic_v1_baseline/excluded_columns.json ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "missing_requested_excluded_columns": [],
3
+ "notes": [
4
+ "Identifier, label, direct leakage, site, and synthetic-only audit columns are excluded.",
5
+ "scope_type and window_length_seconds are retained because they are valid deployment-time context.",
6
+ "Constant version metadata columns are excluded from the baseline."
7
+ ],
8
+ "present_excluded_columns": [
9
+ "feature_row_id",
10
+ "dataset_id",
11
+ "seed",
12
+ "site_id",
13
+ "scope_id_hash",
14
+ "window_start",
15
+ "window_end",
16
+ "episode_id",
17
+ "label_0_to_4",
18
+ "label_confidence",
19
+ "label_reason",
20
+ "label_source",
21
+ "raw_input_manifest_hash",
22
+ "latent_workload_class",
23
+ "scenario_family",
24
+ "scenario_variant",
25
+ "evidence_recipe_id",
26
+ "counterfactual_group_id",
27
+ "synthetic_counterfactual_role",
28
+ "data_quality_regime",
29
+ "privacy_tier",
30
+ "collector_profile",
31
+ "topology_class",
32
+ "temporal_phase",
33
+ "synthetic_hard_case_tags",
34
+ "synthetic_evidence_profile",
35
+ "capacity_evidence_only",
36
+ "integrity_evidence_only",
37
+ "physical_evidence_only",
38
+ "feature_pipeline_version",
39
+ "policy_threshold_version",
40
+ "hardware_normalization_version"
41
+ ],
42
+ "requested_excluded_columns": [
43
+ "feature_row_id",
44
+ "dataset_id",
45
+ "seed",
46
+ "site_id",
47
+ "scope_id_hash",
48
+ "window_start",
49
+ "window_end",
50
+ "episode_id",
51
+ "label_0_to_4",
52
+ "label_confidence",
53
+ "label_reason",
54
+ "label_source",
55
+ "raw_input_manifest_hash",
56
+ "latent_workload_class",
57
+ "scenario_family",
58
+ "scenario_variant",
59
+ "evidence_recipe_id",
60
+ "counterfactual_group_id",
61
+ "synthetic_counterfactual_role",
62
+ "data_quality_regime",
63
+ "privacy_tier",
64
+ "collector_profile",
65
+ "topology_class",
66
+ "temporal_phase",
67
+ "synthetic_hard_case_tags",
68
+ "synthetic_evidence_profile",
69
+ "capacity_evidence_only",
70
+ "integrity_evidence_only",
71
+ "physical_evidence_only",
72
+ "feature_pipeline_version",
73
+ "policy_threshold_version",
74
+ "hardware_normalization_version"
75
+ ]
76
+ }
data/model_runs/synthetic_v1_baseline/feature_columns.json ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ "scope_type",
3
+ "window_length_seconds",
4
+ "capacity_possible",
5
+ "policy_compute_ratio",
6
+ "o1_normalized_h100e_capacity",
7
+ "o1_largest_contiguous_domain_gpus",
8
+ "o1_homogeneous_high_end_fraction",
9
+ "o1_non_partitioned_fraction",
10
+ "o1_inventory_delta_rate",
11
+ "o17_external_capacity_conflict_score",
12
+ "o2_max_concurrent_normalized_gpus",
13
+ "o2_allocation_duration_hours",
14
+ "o2_gpu_hours_policy_ratio",
15
+ "o2_concurrency_fraction_domain",
16
+ "o2_topology_contiguity_score",
17
+ "o2_declared_workload_class",
18
+ "o2_reservation_exclusive_flag",
19
+ "o2_elastic_resize_count",
20
+ "o2_preemption_restart_count",
21
+ "o2_scheduler_queue_delay_hours",
22
+ "o2_account_linkage_confidence",
23
+ "o2_job_array_width",
24
+ "o2_reservation_reuse_count",
25
+ "o3_batch_provisioned_gpus",
26
+ "o3_capacity_reservation_duration_hours",
27
+ "o3_training_sku_fraction",
28
+ "o3_billing_continuity_score",
29
+ "o3_egress_tb",
30
+ "o4_gpu_util_p50",
31
+ "o4_gpu_util_p95",
32
+ "o4_gpu_util_duty_gt_70",
33
+ "o4_sm_tensor_active_p95",
34
+ "o4_hbm_used_fraction_p50",
35
+ "o4_hbm_bandwidth_active_p95",
36
+ "o4_gpu_power_fraction_p95",
37
+ "o4_error_spike_score",
38
+ "o4_gpu_util_cv",
39
+ "o4_gpu_idle_gap_p95_minutes",
40
+ "o4_hbm_pressure_duration_fraction",
41
+ "o4_power_cap_active_fraction",
42
+ "o4_thermal_throttle_fraction",
43
+ "o5_kernel_training_motif_score",
44
+ "o5_tensor_throughput_ratio",
45
+ "o5_profiler_available",
46
+ "o6_nvlink_util_p95",
47
+ "o6_nvlink_periodicity_score",
48
+ "o6_link_error_spike_score",
49
+ "o7_scaleout_port_util_p95",
50
+ "o7_synchronized_fabric_footprint",
51
+ "o7_collective_periodicity_score",
52
+ "o7_burst_duty_cycle",
53
+ "o7_rdma_congestion_score",
54
+ "o7_job_to_port_mapping_coverage",
55
+ "o7_flow_entropy_score",
56
+ "o7_cross_section_sync_score",
57
+ "o7_collective_jitter_score",
58
+ "o7_storage_traffic_fraction",
59
+ "o7_inference_fanout_score",
60
+ "o7_account_flow_linkage_confidence",
61
+ "o8_rack_power_fraction_p95",
62
+ "o8_facility_it_power_mw",
63
+ "o8_baseline_subtracted_energy_kwh",
64
+ "o8_power_continuity_days",
65
+ "o8_power_cv",
66
+ "o8_power_to_gpu_residual",
67
+ "o8_power_baseline_drift_score",
68
+ "o8_power_cap_or_curtailment_active",
69
+ "o8_unattributed_power_fraction",
70
+ "o9_gpu_hbm_temp_score",
71
+ "o9_thermal_delta_t_score",
72
+ "o9_cooling_flow_duty",
73
+ "o9_cooling_maintenance_active",
74
+ "o9_thermal_throttle_support_score",
75
+ "o10_world_size",
76
+ "o10_runtime_framework_class",
77
+ "o10_rank_stability_score",
78
+ "o10_same_image_gpu_count",
79
+ "o10_rendezvous_present",
80
+ "o10_runtime_metadata_confidence",
81
+ "o10_declared_vs_observed_mismatch_score",
82
+ "o11_data_staging_tb",
83
+ "o11_checkpoint_write_tb_per_event",
84
+ "o11_checkpoint_periodicity_score",
85
+ "o11_read_write_training_pattern_score",
86
+ "o11_checkpoint_jitter_score",
87
+ "o11_artifact_write_pattern_score",
88
+ "o11_dataloader_read_pattern_score",
89
+ "o11_backup_or_replication_pattern_score",
90
+ "o11_storage_cotraffic_score",
91
+ "o12_signed_ml_logs_present",
92
+ "o12_declared_parameter_count_b",
93
+ "o12_training_tokens_b",
94
+ "o12_step_count",
95
+ "o12_loss_curve_present",
96
+ "o12_optimizer_state_present",
97
+ "o12_log_delivery_delay_hours",
98
+ "o12_log_completeness_fraction",
99
+ "o12_declaration_consistency_score",
100
+ "o13_attestation_valid_fraction",
101
+ "o13_confidential_compute_mode_fraction",
102
+ "o13_collector_measurement_valid",
103
+ "o14_min_critical_coverage",
104
+ "o14_gap_fraction_critical",
105
+ "o14_clock_drift_max_ms",
106
+ "o14_counter_reset_count",
107
+ "o15_unapproved_physical_change_near_window",
108
+ "o15_firmware_bmc_change_near_window",
109
+ "o16_probe_throughput_ratio_min",
110
+ "o16_probe_latency_inflation_max",
111
+ "o16_vram_residency_conflict_score",
112
+ "o17_external_capacity_assertion",
113
+ "o17_energy_contract_alignment_score",
114
+ "o17_network_provider_utilization_score",
115
+ "o17_procurement_or_maintenance_explanation_score",
116
+ "o1_coverage_fraction",
117
+ "o1_missing_reason",
118
+ "o2_coverage_fraction",
119
+ "o2_missing_reason",
120
+ "o3_coverage_fraction",
121
+ "o3_missing_reason",
122
+ "o4_coverage_fraction",
123
+ "o4_missing_reason",
124
+ "o5_coverage_fraction",
125
+ "o5_missing_reason",
126
+ "o6_coverage_fraction",
127
+ "o6_missing_reason",
128
+ "o7_coverage_fraction",
129
+ "o7_missing_reason",
130
+ "o8_coverage_fraction",
131
+ "o8_missing_reason",
132
+ "o9_coverage_fraction",
133
+ "o9_missing_reason",
134
+ "o10_coverage_fraction",
135
+ "o10_missing_reason",
136
+ "o11_coverage_fraction",
137
+ "o11_missing_reason",
138
+ "o12_coverage_fraction",
139
+ "o12_missing_reason",
140
+ "o13_coverage_fraction",
141
+ "o13_missing_reason",
142
+ "o14_coverage_fraction",
143
+ "o14_missing_reason",
144
+ "o15_coverage_fraction",
145
+ "o15_missing_reason",
146
+ "o16_coverage_fraction",
147
+ "o16_missing_reason",
148
+ "o17_coverage_fraction",
149
+ "o17_missing_reason",
150
+ "scheduler_signature_status",
151
+ "gpu_telemetry_trust_level",
152
+ "fabric_telemetry_trust_level",
153
+ "power_meter_trust_level"
154
+ ]
data/model_runs/synthetic_v1_baseline/feature_importance.csv ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ feature,importance_mean,importance_std,baseline_macro_f1,permuted_macro_f1_mean
2
+ o12_log_completeness_fraction,0.23359158430990315,0.005373690197882257,0.9781141424575879,0.7445225581476848
3
+ o2_allocation_duration_hours,0.14849344450367144,0.0052789099249230705,0.9781141424575879,0.8296206979539164
4
+ o11_checkpoint_periodicity_score,0.09666000121374829,0.0011059737197036232,0.9781141424575879,0.8814541412438396
5
+ o14_min_critical_coverage,0.08425447962391323,0.0028386015672915883,0.9781141424575879,0.8938596628336746
6
+ o7_inference_fanout_score,0.023026962271348744,0.000352376812149185,0.9781141424575879,0.9550871801862391
7
+ o7_synchronized_fabric_footprint,0.0216681347660882,0.0031004413375968374,0.9781141424575879,0.9564460076914997
8
+ policy_compute_ratio,0.01888468408039194,0.002076870274795479,0.9781141424575879,0.959229458377196
9
+ o9_thermal_delta_t_score,0.015522644977829625,0.0017361645292459213,0.9781141424575879,0.9625914974797581
10
+ o8_rack_power_fraction_p95,0.013580628502308012,0.0017886779558558932,0.9781141424575879,0.9645335139552799
11
+ o8_power_cv,0.010049244358529422,0.0007442207607013514,0.9781141424575879,0.9680648980990584
12
+ o2_max_concurrent_normalized_gpus,0.00956190440682921,0.0005897138759531066,0.9781141424575879,0.9685522380507586
13
+ o10_runtime_framework_class,0.009123255221792212,0.0002556343603386503,0.9781141424575879,0.9689908872357957
14
+ o2_topology_contiguity_score,0.008465602515600726,0.000978973878068464,0.9781141424575879,0.9696485399419871
15
+ o4_gpu_idle_gap_p95_minutes,0.008401230772478193,0.0007671011073014647,0.9781141424575879,0.9697129116851096
16
+ o8_power_to_gpu_residual,0.006467490611914021,0.0012204702714004524,0.9781141424575879,0.9716466518456738
17
+ o11_data_staging_tb,0.004774385170709716,0.0011077200740331708,0.9781141424575879,0.9733397572868782
18
+ o4_gpu_util_cv,0.004515834867171553,0.00012814093735156415,0.9781141424575879,0.9735983075904163
19
+ o11_dataloader_read_pattern_score,0.003564301181372668,0.00039712138629140767,0.9781141424575879,0.9745498412762151
20
+ o7_collective_jitter_score,0.003338909258623716,0.00025581495381753373,0.9781141424575879,0.9747752331989642
21
+ o7_collective_periodicity_score,0.0022545386038181534,0.0006384080942919812,0.9781141424575879,0.9758596038537698
22
+ o4_hbm_bandwidth_active_p95,0.0021022174553009254,0.0008926825404043516,0.9781141424575879,0.976011925002287
23
+ o7_burst_duty_cycle,0.001712656669582091,0.0003376920900513128,0.9781141424575879,0.9764014857880058
24
+ o11_read_write_training_pattern_score,0.0014772421580005284,0.0007389848391526284,0.9781141424575879,0.9766369002995873
25
+ o9_gpu_hbm_temp_score,0.001261581096622888,0.0003374158130753862,0.9781141424575879,0.976852561360965
26
+ o7_cross_section_sync_score,0.0012615470192883043,0.0002551176083138864,0.9781141424575879,0.9768525954382996
27
+ o7_coverage_fraction,0.0009957627772852657,0.0002559712480885151,0.9781141424575879,0.9771183796803026
28
+ o4_gpu_power_fraction_p95,0.0009910051757533085,0.0001274863360027242,0.9781141424575879,0.9771231372818345
29
+ o2_concurrency_fraction_domain,0.0009705866111717754,0.0004495525752025081,0.9781141424575879,0.9771435558464162
30
+ o4_coverage_fraction,0.0008706275099251334,0.0002696962800440298,0.9781141424575879,0.9772435149476628
31
+ o7_flow_entropy_score,0.0008107122703610026,0.0,0.9781141424575879,0.9773034301872269
32
+ o11_backup_or_replication_pattern_score,0.0006612636677401454,0.0002701885364253015,0.9781141424575879,0.9774528787898477
33
+ o4_hbm_used_fraction_p50,0.0006554675510399935,0.0004944170097273767,0.9781141424575879,0.9774586749065479
34
+ o5_coverage_fraction,0.0005406101441621051,0.0005840293050570898,0.9781141424575879,0.9775735323134258
35
+ o7_storage_traffic_fraction,0.0002716123882583095,0.00022176553850800082,0.9781141424575879,0.9778425300693296
36
+ o7_account_flow_linkage_confidence,0.00011013595622607249,0.00015575576299984125,0.9781141424575879,0.9780040065013619
37
+ o2_declared_workload_class,6.777792644607761e-05,0.0002861792488526281,0.9781141424575879,0.9780463645311418
38
+ capacity_possible,0.0,0.0,0.9781141424575879,0.9781141424575879
39
+ fabric_telemetry_trust_level,0.0,0.0,0.9781141424575879,0.978114142457588
40
+ gpu_telemetry_trust_level,0.0,0.0,0.9781141424575879,0.978114142457588
41
+ o10_coverage_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
42
+ o10_declared_vs_observed_mismatch_score,0.0,0.0,0.9781141424575879,0.9781141424575879
43
+ o10_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
44
+ o10_rank_stability_score,0.0,0.0,0.9781141424575879,0.978114142457588
45
+ o10_rendezvous_present,0.0,0.0,0.9781141424575879,0.978114142457588
46
+ o10_runtime_metadata_confidence,0.0,0.0,0.9781141424575879,0.978114142457588
47
+ o10_same_image_gpu_count,0.0,0.0,0.9781141424575879,0.978114142457588
48
+ o10_world_size,0.0,0.0,0.9781141424575879,0.978114142457588
49
+ o11_artifact_write_pattern_score,0.0,0.0,0.9781141424575879,0.978114142457588
50
+ o11_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
51
+ o11_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
52
+ o11_storage_cotraffic_score,0.0,0.0,0.9781141424575879,0.978114142457588
53
+ o12_coverage_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
54
+ o12_declaration_consistency_score,0.0,0.0,0.9781141424575879,0.978114142457588
55
+ o12_declared_parameter_count_b,0.0,0.0,0.9781141424575879,0.978114142457588
56
+ o12_log_delivery_delay_hours,0.0,0.0,0.9781141424575879,0.978114142457588
57
+ o12_loss_curve_present,0.0,0.0,0.9781141424575879,0.978114142457588
58
+ o12_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
59
+ o12_optimizer_state_present,0.0,0.0,0.9781141424575879,0.978114142457588
60
+ o12_signed_ml_logs_present,0.0,0.0,0.9781141424575879,0.978114142457588
61
+ o12_step_count,0.0,0.0,0.9781141424575879,0.978114142457588
62
+ o12_training_tokens_b,0.0,0.0,0.9781141424575879,0.978114142457588
63
+ o13_attestation_valid_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
64
+ o13_collector_measurement_valid,0.0,0.0,0.9781141424575879,0.9781141424575879
65
+ o13_confidential_compute_mode_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
66
+ o13_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
67
+ o13_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
68
+ o14_counter_reset_count,0.0,0.0,0.9781141424575879,0.978114142457588
69
+ o14_coverage_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
70
+ o14_gap_fraction_critical,0.0,0.0,0.9781141424575879,0.978114142457588
71
+ o14_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
72
+ o15_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
73
+ o15_firmware_bmc_change_near_window,0.0,0.0,0.9781141424575879,0.978114142457588
74
+ o15_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
75
+ o15_unapproved_physical_change_near_window,0.0,0.0,0.9781141424575879,0.9781141424575879
76
+ o16_coverage_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
77
+ o16_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
78
+ o16_probe_latency_inflation_max,0.0,0.0,0.9781141424575879,0.978114142457588
79
+ o16_probe_throughput_ratio_min,0.0,0.0,0.9781141424575879,0.978114142457588
80
+ o16_vram_residency_conflict_score,0.0,0.0,0.9781141424575879,0.978114142457588
81
+ o17_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
82
+ o17_energy_contract_alignment_score,0.0,0.0,0.9781141424575879,0.9781141424575879
83
+ o17_external_capacity_assertion,0.0,0.0,0.9781141424575879,0.9781141424575879
84
+ o17_external_capacity_conflict_score,0.0,0.0,0.9781141424575879,0.9781141424575879
85
+ o17_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
86
+ o17_network_provider_utilization_score,0.0,0.0,0.9781141424575879,0.9781141424575879
87
+ o17_procurement_or_maintenance_explanation_score,0.0,0.0,0.9781141424575879,0.9781141424575879
88
+ o1_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
89
+ o1_homogeneous_high_end_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
90
+ o1_inventory_delta_rate,0.0,0.0,0.9781141424575879,0.9781141424575879
91
+ o1_largest_contiguous_domain_gpus,0.0,0.0,0.9781141424575879,0.978114142457588
92
+ o1_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
93
+ o1_non_partitioned_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
94
+ o1_normalized_h100e_capacity,0.0,0.0,0.9781141424575879,0.978114142457588
95
+ o2_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
96
+ o2_elastic_resize_count,0.0,0.0,0.9781141424575879,0.978114142457588
97
+ o2_gpu_hours_policy_ratio,0.0,0.0,0.9781141424575879,0.978114142457588
98
+ o2_job_array_width,0.0,0.0,0.9781141424575879,0.978114142457588
99
+ o2_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
100
+ o2_preemption_restart_count,0.0,0.0,0.9781141424575879,0.9781141424575879
101
+ o2_reservation_exclusive_flag,0.0,0.0,0.9781141424575879,0.978114142457588
102
+ o2_reservation_reuse_count,0.0,0.0,0.9781141424575879,0.978114142457588
103
+ o2_scheduler_queue_delay_hours,0.0,0.0,0.9781141424575879,0.9781141424575879
104
+ o3_batch_provisioned_gpus,0.0,0.0,0.9781141424575879,0.978114142457588
105
+ o3_billing_continuity_score,0.0,0.0,0.9781141424575879,0.978114142457588
106
+ o3_capacity_reservation_duration_hours,0.0,0.0,0.9781141424575879,0.978114142457588
107
+ o3_coverage_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
108
+ o3_egress_tb,0.0,0.0,0.9781141424575879,0.978114142457588
109
+ o3_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
110
+ o3_training_sku_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
111
+ o4_gpu_util_duty_gt_70,0.0,0.0,0.9781141424575879,0.978114142457588
112
+ o4_gpu_util_p50,0.0,0.0,0.9781141424575879,0.978114142457588
113
+ o4_gpu_util_p95,0.0,0.0,0.9781141424575879,0.978114142457588
114
+ o4_hbm_pressure_duration_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
115
+ o4_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
116
+ o4_power_cap_active_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
117
+ o4_sm_tensor_active_p95,0.0,0.0,0.9781141424575879,0.978114142457588
118
+ o4_thermal_throttle_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
119
+ o5_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
120
+ o5_profiler_available,0.0,0.0,0.9781141424575879,0.978114142457588
121
+ o5_tensor_throughput_ratio,0.0,0.0,0.9781141424575879,0.978114142457588
122
+ o6_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
123
+ o6_link_error_spike_score,0.0,0.0,0.9781141424575879,0.9781141424575879
124
+ o6_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
125
+ o6_nvlink_util_p95,0.0,0.0,0.9781141424575879,0.978114142457588
126
+ o7_job_to_port_mapping_coverage,0.0,0.0,0.9781141424575879,0.978114142457588
127
+ o7_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
128
+ o7_rdma_congestion_score,0.0,0.0,0.9781141424575879,0.978114142457588
129
+ o7_scaleout_port_util_p95,0.0,0.0,0.9781141424575879,0.978114142457588
130
+ o8_coverage_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
131
+ o8_facility_it_power_mw,0.0,0.0,0.9781141424575879,0.978114142457588
132
+ o8_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
133
+ o8_power_baseline_drift_score,0.0,0.0,0.9781141424575879,0.978114142457588
134
+ o8_power_cap_or_curtailment_active,0.0,0.0,0.9781141424575879,0.978114142457588
135
+ o8_power_continuity_days,0.0,0.0,0.9781141424575879,0.978114142457588
136
+ o8_unattributed_power_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
137
+ o9_cooling_flow_duty,0.0,0.0,0.9781141424575879,0.978114142457588
138
+ o9_cooling_maintenance_active,0.0,0.0,0.9781141424575879,0.978114142457588
139
+ o9_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
140
+ o9_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
141
+ o9_thermal_throttle_support_score,0.0,0.0,0.9781141424575879,0.978114142457588
142
+ power_meter_trust_level,0.0,0.0,0.9781141424575879,0.9781141424575879
143
+ scheduler_signature_status,0.0,0.0,0.9781141424575879,0.9781141424575879
144
+ scope_type,0.0,0.0,0.9781141424575879,0.978114142457588
145
+ window_length_seconds,0.0,0.0,0.9781141424575879,0.978114142457588
146
+ o4_error_spike_score,-0.000112313549517018,0.00015883534496522904,0.9781141424575879,0.9782264560071049
147
+ o5_kernel_training_motif_score,-0.000112313549517018,0.00015883534496522904,0.9781141424575879,0.9782264560071049
148
+ o2_account_linkage_confidence,-0.00022690538180129374,0.00016047059544896947,0.9781141424575879,0.9783410478393891
149
+ o11_checkpoint_jitter_score,-0.00024461273048711113,0.00041613647356600846,0.9781141424575879,0.9783587551880749
150
+ o11_checkpoint_write_tb_per_event,-0.0003526063865893307,0.00026759207342307376,0.9781141424575879,0.9784667488441773
151
+ o6_nvlink_periodicity_score,-0.0006442779638646812,0.0003427827988719719,0.9781141424575879,0.9787584204214527
152
+ o14_clock_drift_max_ms,-0.001169240694518477,0.0009162828683296031,0.9781141424575879,0.9792833831521065
153
+ o8_baseline_subtracted_energy_kwh,-0.0031618376880434562,0.0004309423062690692,0.9781141424575879,0.9812759801456313
data/model_runs/synthetic_v1_baseline/manifest.json ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "artifact_hashes": {
3
+ "README.md": "sha256:e1cca94649d1dd9f08cf2b1a772737fbd7362e3d24f0e5f49567f79fdced1130",
4
+ "calibration_metrics.json": "sha256:a1b673af40faa3ed6b614b7e3cf3753964ede2e3810bcd5354d6657153577897",
5
+ "classification_report.json": "sha256:c74bb9ef28966c94937dd3ddb30296937b8bca224dd8f6fcf781d680621eab0c",
6
+ "confusion_matrix.csv": "sha256:265fa42852a745220b1ea17ee8f502c1b5a24f74063a60f7de9acdd154a830af",
7
+ "evidence_audit_sample.csv": "sha256:a9efe77b1ccc902b0a8f28b2b8b0cb72c9524e39f0d21676b27bb20ec3ce683d",
8
+ "excluded_columns.json": "sha256:0811708d16c4a2b8444eb8324b378ea61cc7ad30931adc14312f2a62587c182b",
9
+ "feature_columns.json": "sha256:f8c411ac97a84df3e13f37163bfa210c2a4019208678b77d5131cb5579f3f976",
10
+ "feature_importance.csv": "sha256:96cb96808c2b5ad9e7a23ba08fc485ddb5e026eddbc54ffa8496e7bb8635c095",
11
+ "metrics.json": "sha256:d7d459e44ed94014a33fb39fbffca89deb69d40ec38658ffa703f009a511a317",
12
+ "model.joblib": "sha256:06c701b0d9c74cdfd0066fe3212013ad2393c3eca34ca625c78792631fa3f338",
13
+ "predictions_all.csv": "sha256:e2e66b8c5f743d13bb4a24b57656cf7f246cb423071efc1049a9f11019e8f549",
14
+ "predictions_test.csv": "sha256:e4fb8e249d438455967b6ff491d51e3db243002c326fe2f66c023f134c5160e1",
15
+ "preprocessing.joblib": "sha256:edf78f71a10335c7703a183a76b97add8ee6ecc8b0ca1068fdcdedab18bcf2de",
16
+ "split_manifest.json": "sha256:4ab606273d10f182b02a5154982a7943c0837b6e5725dee907c97c25e2daff92",
17
+ "validation_summary.md": "sha256:d3da3e62f5e957260990001209805854976b3b2dfe56b921104077a6395918c3"
18
+ },
19
+ "calibration_method": "sigmoid_on_validation_split_frozen_estimator",
20
+ "created_or_updated_at": "2026-05-11T00:23:31Z",
21
+ "features_path": "data/synthetic_v1/features/window_features_all.csv",
22
+ "limitations": [
23
+ "Trained on synthetic data only.",
24
+ "Performance numbers are not real-world deployment claims.",
25
+ "Episode-level group splitting is required because adjacent windows are correlated.",
26
+ "Synthetic labels encode generator assumptions.",
27
+ "Real deployment requires real telemetry calibration and controlled drills."
28
+ ],
29
+ "metrics_summary": {
30
+ "accuracy": 0.9724612736660929,
31
+ "brier_large_training": 0.001846207706971921,
32
+ "ece_large_training": 0.010488907264903001,
33
+ "label_3_4_precision": 0.993517017828201,
34
+ "label_3_4_recall": 0.9967479674796748,
35
+ "macro_f1": 0.9781141424575879,
36
+ "p_large_training_precision_at_0_5": 0.993517017828201,
37
+ "p_large_training_recall_at_0_5": 0.9967479674796748,
38
+ "weighted_f1": 0.9722899614011162
39
+ },
40
+ "model_run_id": "synthetic_v1_baseline",
41
+ "model_type": "CalibratedClassifierCV over HistGradientBoostingClassifier",
42
+ "required_outputs": [
43
+ "README.md",
44
+ "manifest.json",
45
+ "model.joblib",
46
+ "preprocessing.joblib",
47
+ "feature_columns.json",
48
+ "excluded_columns.json",
49
+ "split_manifest.json",
50
+ "metrics.json",
51
+ "calibration_metrics.json",
52
+ "confusion_matrix.csv",
53
+ "classification_report.json",
54
+ "predictions_test.csv",
55
+ "predictions_all.csv",
56
+ "feature_importance.csv",
57
+ "evidence_audit_sample.csv",
58
+ "validation_summary.md"
59
+ ],
60
+ "training_metadata": {
61
+ "calibration_method": "sigmoid_on_validation_split_frozen_estimator",
62
+ "dataset_validation_status": [
63
+ {
64
+ "command": "/Users/ifc24/miniforge3/bin/python /Users/ifc24/Develop/datacenter-verification/src/datacenter_verification_synthetic/validate_synthetic_dataset.py --dataset data/synthetic_v1",
65
+ "name": "synthetic_dataset_validator",
66
+ "returncode": 0,
67
+ "stderr": "",
68
+ "stdout": "PASS\nraw_records: 152457\nfeature_rows: 14712\nlabel_distribution: {0: 3861, 1: 3333, 2: 4203, 3: 2489, 4: 826}"
69
+ },
70
+ {
71
+ "command": "/Users/ifc24/miniforge3/bin/python -m src.datacenter_verification_validators --dataset data/synthetic_v1",
72
+ "name": "public_dataset_validator",
73
+ "returncode": 0,
74
+ "stderr": "",
75
+ "stdout": "Validated dataset: data/synthetic_v1\nErrors: 0\nWarnings: 0\nReport: data/synthetic_v1/validation/validation_report.md"
76
+ }
77
+ ],
78
+ "feature_count": 152,
79
+ "features_path": "data/synthetic_v1/features/window_features_all.csv",
80
+ "model_parameters": {
81
+ "categorical_features": "from_dtype",
82
+ "class_weight": "balanced",
83
+ "early_stopping": true,
84
+ "interaction_cst": null,
85
+ "l2_regularization": 0.03,
86
+ "learning_rate": 0.05,
87
+ "loss": "log_loss",
88
+ "max_bins": 255,
89
+ "max_depth": null,
90
+ "max_features": 1.0,
91
+ "max_iter": 350,
92
+ "max_leaf_nodes": 31,
93
+ "min_samples_leaf": 20,
94
+ "monotonic_cst": null,
95
+ "n_iter_no_change": 10,
96
+ "random_state": 20260510,
97
+ "scoring": "loss",
98
+ "tol": 1e-07,
99
+ "validation_fraction": 0.15,
100
+ "verbose": 0,
101
+ "warm_start": false
102
+ },
103
+ "model_type": "HistGradientBoostingClassifier",
104
+ "seed": 20260510,
105
+ "train_episodes": 458,
106
+ "train_rows": 8870,
107
+ "trained_at": "2026-05-11T00:21:12Z",
108
+ "validation_episodes": 152,
109
+ "validation_rows": 2937
110
+ },
111
+ "validation_status": [
112
+ {
113
+ "command": "/Users/ifc24/miniforge3/bin/python /Users/ifc24/Develop/datacenter-verification/src/datacenter_verification_synthetic/validate_synthetic_dataset.py --dataset data/synthetic_v1",
114
+ "name": "synthetic_dataset_validator",
115
+ "returncode": 0,
116
+ "stderr": "",
117
+ "stdout": "PASS\nraw_records: 152457\nfeature_rows: 14712\nlabel_distribution: {0: 3861, 1: 3333, 2: 4203, 3: 2489, 4: 826}"
118
+ },
119
+ {
120
+ "command": "/Users/ifc24/miniforge3/bin/python -m src.datacenter_verification_validators --dataset data/synthetic_v1",
121
+ "name": "public_dataset_validator",
122
+ "returncode": 0,
123
+ "stderr": "",
124
+ "stdout": "Validated dataset: data/synthetic_v1\nErrors: 0\nWarnings: 0\nReport: data/synthetic_v1/validation/validation_report.md"
125
+ }
126
+ ]
127
+ }
data/model_runs/synthetic_v1_baseline/metrics.json ADDED
@@ -0,0 +1,931 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "calibration": {
3
+ "brier_large_training": 0.001846207706971921,
4
+ "expected_calibration_error_large_training": 0.010488907264903001,
5
+ "log_loss": 0.10567154528993643
6
+ },
7
+ "dataset": {
8
+ "test_episodes": 150,
9
+ "test_label_distribution": {
10
+ "0": 762,
11
+ "1": 690,
12
+ "2": 838,
13
+ "3": 463,
14
+ "4": 152
15
+ },
16
+ "test_rows": 2905
17
+ },
18
+ "governance": {
19
+ "false_negative_scenarios_at_0_5": {
20
+ "fragmented_training_linked": 2
21
+ },
22
+ "false_positive_scenarios_at_0_5": {
23
+ "underclocked_energy_capped_training": 4
24
+ },
25
+ "label_0_missed_under_high_coverage": {
26
+ "missed_rate": 0.0,
27
+ "missed_rows": 0,
28
+ "rows": 762
29
+ },
30
+ "label_3_4_predicted_label": {
31
+ "f1": 0.9951298701298701,
32
+ "precision": 0.993517017828201,
33
+ "recall": 0.9967479674796748
34
+ },
35
+ "p_large_training_threshold_0_5": {
36
+ "f1": 0.9951298701298701,
37
+ "precision": 0.993517017828201,
38
+ "recall": 0.9967479674796748
39
+ }
40
+ },
41
+ "model": {
42
+ "accuracy": 0.9724612736660929,
43
+ "brier_multiclass_mean": 0.010624855064864712,
44
+ "log_loss": 0.10567154528993643,
45
+ "macro_f1": 0.9781141424575879,
46
+ "weighted_f1": 0.9722899614011162
47
+ },
48
+ "rule_baseline": {
49
+ "accuracy": 0.42857142857142855,
50
+ "label_3_4_predicted_label": {
51
+ "f1": 0.8418891170431212,
52
+ "precision": 0.7269503546099291,
53
+ "recall": 1.0
54
+ },
55
+ "macro_f1": 0.36363847151518797,
56
+ "weighted_f1": 0.3650992917466867
57
+ },
58
+ "subgroup_metrics": {
59
+ "capacity_possible": [
60
+ {
61
+ "accuracy": 0.9724612736660929,
62
+ "group": "True",
63
+ "large_training_false_negatives": 2,
64
+ "large_training_false_positives": 4,
65
+ "large_training_precision_at_0_5": 0.993517017828201,
66
+ "large_training_recall_at_0_5": 0.9967479674796748,
67
+ "macro_f1": 0.9781141424575879,
68
+ "rows": 2905
69
+ }
70
+ ],
71
+ "data_quality_regime": [
72
+ {
73
+ "accuracy": 0.9742895805142084,
74
+ "group": "full_observability",
75
+ "large_training_false_negatives": 2,
76
+ "large_training_false_positives": 0,
77
+ "large_training_precision_at_0_5": 1.0,
78
+ "large_training_recall_at_0_5": 0.995249406175772,
79
+ "macro_f1": 0.97854699410172,
80
+ "rows": 2217
81
+ },
82
+ {
83
+ "accuracy": 1.0,
84
+ "group": "routine_privacy_redaction",
85
+ "large_training_false_negatives": 0,
86
+ "large_training_false_positives": 0,
87
+ "large_training_precision_at_0_5": 1.0,
88
+ "large_training_recall_at_0_5": 1.0,
89
+ "macro_f1": 0.4,
90
+ "rows": 126
91
+ },
92
+ {
93
+ "accuracy": 1.0,
94
+ "group": "maintenance_observability_loss",
95
+ "large_training_false_negatives": 0,
96
+ "large_training_false_positives": 0,
97
+ "large_training_precision_at_0_5": 0.0,
98
+ "large_training_recall_at_0_5": 0.0,
99
+ "macro_f1": 0.4,
100
+ "rows": 112
101
+ },
102
+ {
103
+ "accuracy": 1.0,
104
+ "group": "delayed_log_delivery",
105
+ "large_training_false_negatives": 0,
106
+ "large_training_false_positives": 0,
107
+ "large_training_precision_at_0_5": 1.0,
108
+ "large_training_recall_at_0_5": 1.0,
109
+ "macro_f1": 0.6,
110
+ "rows": 111
111
+ },
112
+ {
113
+ "accuracy": 0.8020833333333334,
114
+ "group": "collector_gap_long",
115
+ "large_training_false_negatives": 0,
116
+ "large_training_false_positives": 0,
117
+ "large_training_precision_at_0_5": 0.0,
118
+ "large_training_recall_at_0_5": 0.0,
119
+ "macro_f1": 0.31288358208955225,
120
+ "rows": 96
121
+ },
122
+ {
123
+ "accuracy": 0.9393939393939394,
124
+ "group": "confidential_compute_counters_disabled",
125
+ "large_training_false_negatives": 0,
126
+ "large_training_false_positives": 4,
127
+ "large_training_precision_at_0_5": 0.0,
128
+ "large_training_recall_at_0_5": 0.0,
129
+ "macro_f1": 0.19375,
130
+ "rows": 66
131
+ },
132
+ {
133
+ "accuracy": 1.0,
134
+ "group": "source_not_deployed_for_site",
135
+ "large_training_false_negatives": 0,
136
+ "large_training_false_positives": 0,
137
+ "large_training_precision_at_0_5": 1.0,
138
+ "large_training_recall_at_0_5": 1.0,
139
+ "macro_f1": 0.6,
140
+ "rows": 62
141
+ },
142
+ {
143
+ "accuracy": 1.0,
144
+ "group": "partial_fabric_mapping",
145
+ "large_training_false_negatives": 0,
146
+ "large_training_false_positives": 0,
147
+ "large_training_precision_at_0_5": 0.0,
148
+ "large_training_recall_at_0_5": 0.0,
149
+ "macro_f1": 0.4,
150
+ "rows": 55
151
+ },
152
+ {
153
+ "accuracy": 1.0,
154
+ "group": "collector_gap_short",
155
+ "large_training_false_negatives": 0,
156
+ "large_training_false_positives": 0,
157
+ "large_training_precision_at_0_5": 0.0,
158
+ "large_training_recall_at_0_5": 0.0,
159
+ "macro_f1": 0.4,
160
+ "rows": 44
161
+ },
162
+ {
163
+ "accuracy": 1.0,
164
+ "group": "clock_drift_episode",
165
+ "large_training_false_negatives": 0,
166
+ "large_training_false_positives": 0,
167
+ "large_training_precision_at_0_5": 0.0,
168
+ "large_training_recall_at_0_5": 0.0,
169
+ "macro_f1": 0.2,
170
+ "rows": 16
171
+ }
172
+ ],
173
+ "integrity_warning": [
174
+ {
175
+ "accuracy": 0.9765335529024289,
176
+ "group": "False",
177
+ "large_training_false_negatives": 2,
178
+ "large_training_false_positives": 0,
179
+ "large_training_precision_at_0_5": 1.0,
180
+ "large_training_recall_at_0_5": 0.9967479674796748,
181
+ "macro_f1": 0.978471713929574,
182
+ "rows": 2429
183
+ },
184
+ {
185
+ "accuracy": 0.9516806722689075,
186
+ "group": "True",
187
+ "large_training_false_negatives": 0,
188
+ "large_training_false_positives": 4,
189
+ "large_training_precision_at_0_5": 0.0,
190
+ "large_training_recall_at_0_5": 0.0,
191
+ "macro_f1": 0.5816990094283327,
192
+ "rows": 476
193
+ }
194
+ ],
195
+ "latent_workload_class": [
196
+ {
197
+ "accuracy": 1.0,
198
+ "group": "idle_or_low_activity",
199
+ "large_training_false_negatives": 0,
200
+ "large_training_false_positives": 0,
201
+ "large_training_precision_at_0_5": 0.0,
202
+ "large_training_recall_at_0_5": 0.0,
203
+ "macro_f1": 0.4,
204
+ "rows": 807
205
+ },
206
+ {
207
+ "accuracy": 1.0,
208
+ "group": "hpc_mpi_collective",
209
+ "large_training_false_negatives": 0,
210
+ "large_training_false_positives": 0,
211
+ "large_training_precision_at_0_5": 0.0,
212
+ "large_training_recall_at_0_5": 0.0,
213
+ "macro_f1": 0.2,
214
+ "rows": 171
215
+ },
216
+ {
217
+ "accuracy": 0.9872611464968153,
218
+ "group": "fragmented_training_linked",
219
+ "large_training_false_negatives": 2,
220
+ "large_training_false_positives": 0,
221
+ "large_training_precision_at_0_5": 1.0,
222
+ "large_training_recall_at_0_5": 0.9672131147540983,
223
+ "macro_f1": 0.3946048109965636,
224
+ "rows": 157
225
+ },
226
+ {
227
+ "accuracy": 1.0,
228
+ "group": "training_without_semantic_logs",
229
+ "large_training_false_negatives": 0,
230
+ "large_training_false_positives": 0,
231
+ "large_training_precision_at_0_5": 1.0,
232
+ "large_training_recall_at_0_5": 1.0,
233
+ "macro_f1": 0.4,
234
+ "rows": 144
235
+ },
236
+ {
237
+ "accuracy": 1.0,
238
+ "group": "large_etl_or_data_movement",
239
+ "large_training_false_negatives": 0,
240
+ "large_training_false_positives": 0,
241
+ "large_training_precision_at_0_5": 0.0,
242
+ "large_training_recall_at_0_5": 0.0,
243
+ "macro_f1": 0.2,
244
+ "rows": 136
245
+ },
246
+ {
247
+ "accuracy": 1.0,
248
+ "group": "reserved_but_unused_capacity",
249
+ "large_training_false_negatives": 0,
250
+ "large_training_false_positives": 0,
251
+ "large_training_precision_at_0_5": 0.0,
252
+ "large_training_recall_at_0_5": 0.0,
253
+ "macro_f1": 0.2,
254
+ "rows": 122
255
+ },
256
+ {
257
+ "accuracy": 1.0,
258
+ "group": "model_parallel_inference",
259
+ "large_training_false_negatives": 0,
260
+ "large_training_false_positives": 0,
261
+ "large_training_precision_at_0_5": 0.0,
262
+ "large_training_recall_at_0_5": 0.0,
263
+ "macro_f1": 0.4,
264
+ "rows": 104
265
+ },
266
+ {
267
+ "accuracy": 1.0,
268
+ "group": "training_with_low_fabric_high_checkpoint",
269
+ "large_training_false_negatives": 0,
270
+ "large_training_false_positives": 0,
271
+ "large_training_precision_at_0_5": 1.0,
272
+ "large_training_recall_at_0_5": 1.0,
273
+ "macro_f1": 0.4,
274
+ "rows": 104
275
+ },
276
+ {
277
+ "accuracy": 1.0,
278
+ "group": "hardware_burn_in_or_thermal_soak",
279
+ "large_training_false_negatives": 0,
280
+ "large_training_false_positives": 0,
281
+ "large_training_precision_at_0_5": 0.0,
282
+ "large_training_recall_at_0_5": 0.0,
283
+ "macro_f1": 0.2,
284
+ "rows": 103
285
+ },
286
+ {
287
+ "accuracy": 0.6534653465346535,
288
+ "group": "normal_inference",
289
+ "large_training_false_negatives": 0,
290
+ "large_training_false_positives": 0,
291
+ "large_training_precision_at_0_5": 0.0,
292
+ "large_training_recall_at_0_5": 0.0,
293
+ "macro_f1": 0.277555239695051,
294
+ "rows": 101
295
+ },
296
+ {
297
+ "accuracy": 0.7956989247311828,
298
+ "group": "large_batch_inference",
299
+ "large_training_false_negatives": 0,
300
+ "large_training_false_positives": 0,
301
+ "large_training_precision_at_0_5": 0.0,
302
+ "large_training_recall_at_0_5": 0.0,
303
+ "macro_f1": 0.3176703552708212,
304
+ "rows": 93
305
+ },
306
+ {
307
+ "accuracy": 1.0,
308
+ "group": "training_with_delayed_logs",
309
+ "large_training_false_negatives": 0,
310
+ "large_training_false_positives": 0,
311
+ "large_training_precision_at_0_5": 1.0,
312
+ "large_training_recall_at_0_5": 1.0,
313
+ "macro_f1": 0.6,
314
+ "rows": 91
315
+ },
316
+ {
317
+ "accuracy": 0.9550561797752809,
318
+ "group": "underclocked_energy_capped_training",
319
+ "large_training_false_negatives": 0,
320
+ "large_training_false_positives": 4,
321
+ "large_training_precision_at_0_5": 0.9384615384615385,
322
+ "large_training_recall_at_0_5": 1.0,
323
+ "macro_f1": 0.37826617826617825,
324
+ "rows": 89
325
+ },
326
+ {
327
+ "accuracy": 1.0,
328
+ "group": "capacity_or_integrity_only_warning",
329
+ "large_training_false_negatives": 0,
330
+ "large_training_false_positives": 0,
331
+ "large_training_precision_at_0_5": 0.0,
332
+ "large_training_recall_at_0_5": 0.0,
333
+ "macro_f1": 0.2,
334
+ "rows": 87
335
+ },
336
+ {
337
+ "accuracy": 1.0,
338
+ "group": "cloud_training_redacted_runtime",
339
+ "large_training_false_negatives": 0,
340
+ "large_training_false_positives": 0,
341
+ "large_training_precision_at_0_5": 1.0,
342
+ "large_training_recall_at_0_5": 1.0,
343
+ "macro_f1": 0.4,
344
+ "rows": 86
345
+ },
346
+ {
347
+ "accuracy": 1.0,
348
+ "group": "pretraining_standard",
349
+ "large_training_false_negatives": 0,
350
+ "large_training_false_positives": 0,
351
+ "large_training_precision_at_0_5": 1.0,
352
+ "large_training_recall_at_0_5": 1.0,
353
+ "macro_f1": 0.2,
354
+ "rows": 86
355
+ },
356
+ {
357
+ "accuracy": 1.0,
358
+ "group": "multi_tenant_fragmented_nontraining",
359
+ "large_training_false_negatives": 0,
360
+ "large_training_false_positives": 0,
361
+ "large_training_precision_at_0_5": 0.0,
362
+ "large_training_recall_at_0_5": 0.0,
363
+ "macro_f1": 0.2,
364
+ "rows": 84
365
+ },
366
+ {
367
+ "accuracy": 1.0,
368
+ "group": "storage_rebuild_or_replication",
369
+ "large_training_false_negatives": 0,
370
+ "large_training_false_positives": 0,
371
+ "large_training_precision_at_0_5": 0.0,
372
+ "large_training_recall_at_0_5": 0.0,
373
+ "macro_f1": 0.4,
374
+ "rows": 82
375
+ },
376
+ {
377
+ "accuracy": 1.0,
378
+ "group": "nccl_extended_benchmark",
379
+ "large_training_false_negatives": 0,
380
+ "large_training_false_positives": 0,
381
+ "large_training_precision_at_0_5": 0.0,
382
+ "large_training_recall_at_0_5": 0.0,
383
+ "macro_f1": 0.2,
384
+ "rows": 69
385
+ },
386
+ {
387
+ "accuracy": 0.6296296296296297,
388
+ "group": "embedding_generation",
389
+ "large_training_false_negatives": 0,
390
+ "large_training_false_positives": 0,
391
+ "large_training_precision_at_0_5": 0.0,
392
+ "large_training_recall_at_0_5": 0.0,
393
+ "macro_f1": 0.15454545454545454,
394
+ "rows": 54
395
+ },
396
+ {
397
+ "accuracy": 1.0,
398
+ "group": "large_fine_tune_standard",
399
+ "large_training_false_negatives": 0,
400
+ "large_training_false_positives": 0,
401
+ "large_training_precision_at_0_5": 1.0,
402
+ "large_training_recall_at_0_5": 1.0,
403
+ "macro_f1": 0.4,
404
+ "rows": 42
405
+ },
406
+ {
407
+ "accuracy": 1.0,
408
+ "group": "multi_stage_training_pipeline",
409
+ "large_training_false_negatives": 0,
410
+ "large_training_false_positives": 0,
411
+ "large_training_precision_at_0_5": 1.0,
412
+ "large_training_recall_at_0_5": 1.0,
413
+ "macro_f1": 0.4,
414
+ "rows": 24
415
+ },
416
+ {
417
+ "accuracy": 1.0,
418
+ "group": "counter_suppressed_candidate_window",
419
+ "large_training_false_negatives": 0,
420
+ "large_training_false_positives": 0,
421
+ "large_training_precision_at_0_5": 0.0,
422
+ "large_training_recall_at_0_5": 0.0,
423
+ "macro_f1": 0.2,
424
+ "rows": 22
425
+ },
426
+ {
427
+ "accuracy": 1.0,
428
+ "group": "distributed_database_or_graph_analytics",
429
+ "large_training_false_negatives": 0,
430
+ "large_training_false_positives": 0,
431
+ "large_training_precision_at_0_5": 0.0,
432
+ "large_training_recall_at_0_5": 0.0,
433
+ "macro_f1": 0.2,
434
+ "rows": 18
435
+ },
436
+ {
437
+ "accuracy": 1.0,
438
+ "group": "maintenance_with_collector_gaps",
439
+ "large_training_false_negatives": 0,
440
+ "large_training_false_positives": 0,
441
+ "large_training_precision_at_0_5": 0.0,
442
+ "large_training_recall_at_0_5": 0.0,
443
+ "macro_f1": 0.2,
444
+ "rows": 15
445
+ },
446
+ {
447
+ "accuracy": 1.0,
448
+ "group": "synthetic_data_generation_gpu_heavy",
449
+ "large_training_false_negatives": 0,
450
+ "large_training_false_positives": 0,
451
+ "large_training_precision_at_0_5": 0.0,
452
+ "large_training_recall_at_0_5": 0.0,
453
+ "macro_f1": 0.2,
454
+ "rows": 14
455
+ }
456
+ ],
457
+ "o12_missing_reason": [
458
+ {
459
+ "accuracy": 0.968040370058873,
460
+ "group": "privacy_redacted",
461
+ "large_training_false_negatives": 2,
462
+ "large_training_false_positives": 0,
463
+ "large_training_precision_at_0_5": 1.0,
464
+ "large_training_recall_at_0_5": 0.991701244813278,
465
+ "macro_f1": 0.777350004737448,
466
+ "rows": 2378
467
+ },
468
+ {
469
+ "accuracy": 0.9913978494623656,
470
+ "group": "delayed_log_delivery",
471
+ "large_training_false_negatives": 0,
472
+ "large_training_false_positives": 4,
473
+ "large_training_precision_at_0_5": 0.9889196675900277,
474
+ "large_training_recall_at_0_5": 1.0,
475
+ "macro_f1": 0.5942940479445811,
476
+ "rows": 465
477
+ },
478
+ {
479
+ "accuracy": 1.0,
480
+ "group": "source_not_deployed",
481
+ "large_training_false_negatives": 0,
482
+ "large_training_false_positives": 0,
483
+ "large_training_precision_at_0_5": 1.0,
484
+ "large_training_recall_at_0_5": 1.0,
485
+ "macro_f1": 0.6,
486
+ "rows": 62
487
+ }
488
+ ],
489
+ "o4_missing_reason": [
490
+ {
491
+ "accuracy": 0.9777950915465524,
492
+ "group": "observed",
493
+ "large_training_false_negatives": 2,
494
+ "large_training_false_positives": 0,
495
+ "large_training_precision_at_0_5": 1.0,
496
+ "large_training_recall_at_0_5": 0.9967479674796748,
497
+ "macro_f1": 0.9808162711257303,
498
+ "rows": 2567
499
+ },
500
+ {
501
+ "accuracy": 0.9301470588235294,
502
+ "group": "collector_gap",
503
+ "large_training_false_negatives": 0,
504
+ "large_training_false_positives": 0,
505
+ "large_training_precision_at_0_5": 0.0,
506
+ "large_training_recall_at_0_5": 0.0,
507
+ "macro_f1": 0.3720493787098257,
508
+ "rows": 272
509
+ },
510
+ {
511
+ "accuracy": 0.9393939393939394,
512
+ "group": "counter_disabled_by_cc_mode",
513
+ "large_training_false_negatives": 0,
514
+ "large_training_false_positives": 4,
515
+ "large_training_precision_at_0_5": 0.0,
516
+ "large_training_recall_at_0_5": 0.0,
517
+ "macro_f1": 0.19375,
518
+ "rows": 66
519
+ }
520
+ ],
521
+ "o7_missing_reason": [
522
+ {
523
+ "accuracy": 0.9763382467028704,
524
+ "group": "observed",
525
+ "large_training_false_negatives": 2,
526
+ "large_training_false_positives": 4,
527
+ "large_training_precision_at_0_5": 0.993517017828201,
528
+ "large_training_recall_at_0_5": 0.9967479674796748,
529
+ "macro_f1": 0.9793065248203424,
530
+ "rows": 2578
531
+ },
532
+ {
533
+ "accuracy": 0.9301470588235294,
534
+ "group": "collector_gap",
535
+ "large_training_false_negatives": 0,
536
+ "large_training_false_positives": 0,
537
+ "large_training_precision_at_0_5": 0.0,
538
+ "large_training_recall_at_0_5": 0.0,
539
+ "macro_f1": 0.3720493787098257,
540
+ "rows": 272
541
+ },
542
+ {
543
+ "accuracy": 1.0,
544
+ "group": "privacy_redacted",
545
+ "large_training_false_negatives": 0,
546
+ "large_training_false_positives": 0,
547
+ "large_training_precision_at_0_5": 0.0,
548
+ "large_training_recall_at_0_5": 0.0,
549
+ "macro_f1": 0.4,
550
+ "rows": 55
551
+ }
552
+ ],
553
+ "scenario_family": [
554
+ {
555
+ "accuracy": 1.0,
556
+ "group": "idle_or_low_activity",
557
+ "large_training_false_negatives": 0,
558
+ "large_training_false_positives": 0,
559
+ "large_training_precision_at_0_5": 0.0,
560
+ "large_training_recall_at_0_5": 0.0,
561
+ "macro_f1": 0.4,
562
+ "rows": 807
563
+ },
564
+ {
565
+ "accuracy": 1.0,
566
+ "group": "hpc_mpi_collective",
567
+ "large_training_false_negatives": 0,
568
+ "large_training_false_positives": 0,
569
+ "large_training_precision_at_0_5": 0.0,
570
+ "large_training_recall_at_0_5": 0.0,
571
+ "macro_f1": 0.2,
572
+ "rows": 171
573
+ },
574
+ {
575
+ "accuracy": 0.9872611464968153,
576
+ "group": "fragmented_training_linked",
577
+ "large_training_false_negatives": 2,
578
+ "large_training_false_positives": 0,
579
+ "large_training_precision_at_0_5": 1.0,
580
+ "large_training_recall_at_0_5": 0.9672131147540983,
581
+ "macro_f1": 0.3946048109965636,
582
+ "rows": 157
583
+ },
584
+ {
585
+ "accuracy": 1.0,
586
+ "group": "training_without_semantic_logs",
587
+ "large_training_false_negatives": 0,
588
+ "large_training_false_positives": 0,
589
+ "large_training_precision_at_0_5": 1.0,
590
+ "large_training_recall_at_0_5": 1.0,
591
+ "macro_f1": 0.4,
592
+ "rows": 144
593
+ },
594
+ {
595
+ "accuracy": 1.0,
596
+ "group": "large_etl_or_data_movement",
597
+ "large_training_false_negatives": 0,
598
+ "large_training_false_positives": 0,
599
+ "large_training_precision_at_0_5": 0.0,
600
+ "large_training_recall_at_0_5": 0.0,
601
+ "macro_f1": 0.2,
602
+ "rows": 136
603
+ },
604
+ {
605
+ "accuracy": 1.0,
606
+ "group": "reserved_but_unused_capacity",
607
+ "large_training_false_negatives": 0,
608
+ "large_training_false_positives": 0,
609
+ "large_training_precision_at_0_5": 0.0,
610
+ "large_training_recall_at_0_5": 0.0,
611
+ "macro_f1": 0.2,
612
+ "rows": 122
613
+ },
614
+ {
615
+ "accuracy": 1.0,
616
+ "group": "model_parallel_inference",
617
+ "large_training_false_negatives": 0,
618
+ "large_training_false_positives": 0,
619
+ "large_training_precision_at_0_5": 0.0,
620
+ "large_training_recall_at_0_5": 0.0,
621
+ "macro_f1": 0.4,
622
+ "rows": 104
623
+ },
624
+ {
625
+ "accuracy": 1.0,
626
+ "group": "training_with_low_fabric_high_checkpoint",
627
+ "large_training_false_negatives": 0,
628
+ "large_training_false_positives": 0,
629
+ "large_training_precision_at_0_5": 1.0,
630
+ "large_training_recall_at_0_5": 1.0,
631
+ "macro_f1": 0.4,
632
+ "rows": 104
633
+ },
634
+ {
635
+ "accuracy": 1.0,
636
+ "group": "hardware_burn_in_or_thermal_soak",
637
+ "large_training_false_negatives": 0,
638
+ "large_training_false_positives": 0,
639
+ "large_training_precision_at_0_5": 0.0,
640
+ "large_training_recall_at_0_5": 0.0,
641
+ "macro_f1": 0.2,
642
+ "rows": 103
643
+ },
644
+ {
645
+ "accuracy": 0.6534653465346535,
646
+ "group": "normal_inference",
647
+ "large_training_false_negatives": 0,
648
+ "large_training_false_positives": 0,
649
+ "large_training_precision_at_0_5": 0.0,
650
+ "large_training_recall_at_0_5": 0.0,
651
+ "macro_f1": 0.277555239695051,
652
+ "rows": 101
653
+ },
654
+ {
655
+ "accuracy": 0.7956989247311828,
656
+ "group": "large_batch_inference",
657
+ "large_training_false_negatives": 0,
658
+ "large_training_false_positives": 0,
659
+ "large_training_precision_at_0_5": 0.0,
660
+ "large_training_recall_at_0_5": 0.0,
661
+ "macro_f1": 0.3176703552708212,
662
+ "rows": 93
663
+ },
664
+ {
665
+ "accuracy": 1.0,
666
+ "group": "training_with_delayed_logs",
667
+ "large_training_false_negatives": 0,
668
+ "large_training_false_positives": 0,
669
+ "large_training_precision_at_0_5": 1.0,
670
+ "large_training_recall_at_0_5": 1.0,
671
+ "macro_f1": 0.6,
672
+ "rows": 91
673
+ },
674
+ {
675
+ "accuracy": 0.9550561797752809,
676
+ "group": "underclocked_energy_capped_training",
677
+ "large_training_false_negatives": 0,
678
+ "large_training_false_positives": 4,
679
+ "large_training_precision_at_0_5": 0.9384615384615385,
680
+ "large_training_recall_at_0_5": 1.0,
681
+ "macro_f1": 0.37826617826617825,
682
+ "rows": 89
683
+ },
684
+ {
685
+ "accuracy": 1.0,
686
+ "group": "capacity_or_integrity_only_warning",
687
+ "large_training_false_negatives": 0,
688
+ "large_training_false_positives": 0,
689
+ "large_training_precision_at_0_5": 0.0,
690
+ "large_training_recall_at_0_5": 0.0,
691
+ "macro_f1": 0.2,
692
+ "rows": 87
693
+ },
694
+ {
695
+ "accuracy": 1.0,
696
+ "group": "cloud_training_redacted_runtime",
697
+ "large_training_false_negatives": 0,
698
+ "large_training_false_positives": 0,
699
+ "large_training_precision_at_0_5": 1.0,
700
+ "large_training_recall_at_0_5": 1.0,
701
+ "macro_f1": 0.4,
702
+ "rows": 86
703
+ },
704
+ {
705
+ "accuracy": 1.0,
706
+ "group": "pretraining_standard",
707
+ "large_training_false_negatives": 0,
708
+ "large_training_false_positives": 0,
709
+ "large_training_precision_at_0_5": 1.0,
710
+ "large_training_recall_at_0_5": 1.0,
711
+ "macro_f1": 0.2,
712
+ "rows": 86
713
+ },
714
+ {
715
+ "accuracy": 1.0,
716
+ "group": "multi_tenant_fragmented_nontraining",
717
+ "large_training_false_negatives": 0,
718
+ "large_training_false_positives": 0,
719
+ "large_training_precision_at_0_5": 0.0,
720
+ "large_training_recall_at_0_5": 0.0,
721
+ "macro_f1": 0.2,
722
+ "rows": 84
723
+ },
724
+ {
725
+ "accuracy": 1.0,
726
+ "group": "storage_rebuild_or_replication",
727
+ "large_training_false_negatives": 0,
728
+ "large_training_false_positives": 0,
729
+ "large_training_precision_at_0_5": 0.0,
730
+ "large_training_recall_at_0_5": 0.0,
731
+ "macro_f1": 0.4,
732
+ "rows": 82
733
+ },
734
+ {
735
+ "accuracy": 1.0,
736
+ "group": "nccl_extended_benchmark",
737
+ "large_training_false_negatives": 0,
738
+ "large_training_false_positives": 0,
739
+ "large_training_precision_at_0_5": 0.0,
740
+ "large_training_recall_at_0_5": 0.0,
741
+ "macro_f1": 0.2,
742
+ "rows": 69
743
+ },
744
+ {
745
+ "accuracy": 0.6296296296296297,
746
+ "group": "embedding_generation",
747
+ "large_training_false_negatives": 0,
748
+ "large_training_false_positives": 0,
749
+ "large_training_precision_at_0_5": 0.0,
750
+ "large_training_recall_at_0_5": 0.0,
751
+ "macro_f1": 0.15454545454545454,
752
+ "rows": 54
753
+ },
754
+ {
755
+ "accuracy": 1.0,
756
+ "group": "large_fine_tune_standard",
757
+ "large_training_false_negatives": 0,
758
+ "large_training_false_positives": 0,
759
+ "large_training_precision_at_0_5": 1.0,
760
+ "large_training_recall_at_0_5": 1.0,
761
+ "macro_f1": 0.4,
762
+ "rows": 42
763
+ },
764
+ {
765
+ "accuracy": 1.0,
766
+ "group": "multi_stage_training_pipeline",
767
+ "large_training_false_negatives": 0,
768
+ "large_training_false_positives": 0,
769
+ "large_training_precision_at_0_5": 1.0,
770
+ "large_training_recall_at_0_5": 1.0,
771
+ "macro_f1": 0.4,
772
+ "rows": 24
773
+ },
774
+ {
775
+ "accuracy": 1.0,
776
+ "group": "counter_suppressed_candidate_window",
777
+ "large_training_false_negatives": 0,
778
+ "large_training_false_positives": 0,
779
+ "large_training_precision_at_0_5": 0.0,
780
+ "large_training_recall_at_0_5": 0.0,
781
+ "macro_f1": 0.2,
782
+ "rows": 22
783
+ },
784
+ {
785
+ "accuracy": 1.0,
786
+ "group": "distributed_database_or_graph_analytics",
787
+ "large_training_false_negatives": 0,
788
+ "large_training_false_positives": 0,
789
+ "large_training_precision_at_0_5": 0.0,
790
+ "large_training_recall_at_0_5": 0.0,
791
+ "macro_f1": 0.2,
792
+ "rows": 18
793
+ },
794
+ {
795
+ "accuracy": 1.0,
796
+ "group": "maintenance_with_collector_gaps",
797
+ "large_training_false_negatives": 0,
798
+ "large_training_false_positives": 0,
799
+ "large_training_precision_at_0_5": 0.0,
800
+ "large_training_recall_at_0_5": 0.0,
801
+ "macro_f1": 0.2,
802
+ "rows": 15
803
+ },
804
+ {
805
+ "accuracy": 1.0,
806
+ "group": "synthetic_data_generation_gpu_heavy",
807
+ "large_training_false_negatives": 0,
808
+ "large_training_false_positives": 0,
809
+ "large_training_precision_at_0_5": 0.0,
810
+ "large_training_recall_at_0_5": 0.0,
811
+ "macro_f1": 0.2,
812
+ "rows": 14
813
+ }
814
+ ],
815
+ "site_id": [
816
+ {
817
+ "accuracy": 0.9966101694915255,
818
+ "group": "site_b",
819
+ "large_training_false_negatives": 2,
820
+ "large_training_false_positives": 0,
821
+ "large_training_precision_at_0_5": 1.0,
822
+ "large_training_recall_at_0_5": 0.9878787878787879,
823
+ "macro_f1": 0.9975238356540229,
824
+ "rows": 590
825
+ },
826
+ {
827
+ "accuracy": 1.0,
828
+ "group": "site_a",
829
+ "large_training_false_negatives": 0,
830
+ "large_training_false_positives": 0,
831
+ "large_training_precision_at_0_5": 1.0,
832
+ "large_training_recall_at_0_5": 1.0,
833
+ "macro_f1": 1.0,
834
+ "rows": 464
835
+ },
836
+ {
837
+ "accuracy": 1.0,
838
+ "group": "site_d",
839
+ "large_training_false_negatives": 0,
840
+ "large_training_false_positives": 0,
841
+ "large_training_precision_at_0_5": 1.0,
842
+ "large_training_recall_at_0_5": 1.0,
843
+ "macro_f1": 0.8,
844
+ "rows": 454
845
+ },
846
+ {
847
+ "accuracy": 0.9016786570743405,
848
+ "group": "site_g",
849
+ "large_training_false_negatives": 0,
850
+ "large_training_false_positives": 0,
851
+ "large_training_precision_at_0_5": 1.0,
852
+ "large_training_recall_at_0_5": 1.0,
853
+ "macro_f1": 0.9040527108006021,
854
+ "rows": 417
855
+ },
856
+ {
857
+ "accuracy": 0.948051948051948,
858
+ "group": "site_e",
859
+ "large_training_false_negatives": 0,
860
+ "large_training_false_positives": 0,
861
+ "large_training_precision_at_0_5": 1.0,
862
+ "large_training_recall_at_0_5": 1.0,
863
+ "macro_f1": 0.7622607921673342,
864
+ "rows": 385
865
+ },
866
+ {
867
+ "accuracy": 0.9872611464968153,
868
+ "group": "site_c",
869
+ "large_training_false_negatives": 0,
870
+ "large_training_false_positives": 4,
871
+ "large_training_precision_at_0_5": 0.8333333333333334,
872
+ "large_training_recall_at_0_5": 1.0,
873
+ "macro_f1": 0.776940133037694,
874
+ "rows": 314
875
+ },
876
+ {
877
+ "accuracy": 0.9537366548042705,
878
+ "group": "site_f",
879
+ "large_training_false_negatives": 0,
880
+ "large_training_false_positives": 0,
881
+ "large_training_precision_at_0_5": 1.0,
882
+ "large_training_recall_at_0_5": 1.0,
883
+ "macro_f1": 0.7762378716744914,
884
+ "rows": 281
885
+ }
886
+ ],
887
+ "temporal_phase": [],
888
+ "window_length_seconds": [
889
+ {
890
+ "accuracy": 0.9710467706013363,
891
+ "group": "900",
892
+ "large_training_false_negatives": 1,
893
+ "large_training_false_positives": 1,
894
+ "large_training_precision_at_0_5": 0.9942528735632183,
895
+ "large_training_recall_at_0_5": 0.9942528735632183,
896
+ "macro_f1": 0.976753637450509,
897
+ "rows": 898
898
+ },
899
+ {
900
+ "accuracy": 0.9720044792833147,
901
+ "group": "3600",
902
+ "large_training_false_negatives": 1,
903
+ "large_training_false_positives": 1,
904
+ "large_training_precision_at_0_5": 0.9942196531791907,
905
+ "large_training_recall_at_0_5": 0.9942196531791907,
906
+ "macro_f1": 0.9774669561641736,
907
+ "rows": 893
908
+ },
909
+ {
910
+ "accuracy": 0.9731903485254692,
911
+ "group": "21600",
912
+ "large_training_false_negatives": 0,
913
+ "large_training_false_positives": 1,
914
+ "large_training_precision_at_0_5": 0.9942528735632183,
915
+ "large_training_recall_at_0_5": 1.0,
916
+ "macro_f1": 0.9793116710307792,
917
+ "rows": 746
918
+ },
919
+ {
920
+ "accuracy": 0.9755434782608695,
921
+ "group": "86400",
922
+ "large_training_false_negatives": 0,
923
+ "large_training_false_positives": 1,
924
+ "large_training_precision_at_0_5": 0.9895833333333334,
925
+ "large_training_recall_at_0_5": 1.0,
926
+ "macro_f1": 0.9804646860359316,
927
+ "rows": 368
928
+ }
929
+ ]
930
+ }
931
+ }
data/model_runs/synthetic_v1_baseline/model.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:06c701b0d9c74cdfd0066fe3212013ad2393c3eca34ca625c78792631fa3f338
3
+ size 2178380
data/model_runs/synthetic_v1_baseline/predictions_all.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e2e66b8c5f743d13bb4a24b57656cf7f246cb423071efc1049a9f11019e8f549
3
+ size 12768446
data/model_runs/synthetic_v1_baseline/predictions_test.csv ADDED
The diff for this file is too large to render. See raw diff
 
data/model_runs/synthetic_v1_baseline/preprocessing.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:edf78f71a10335c7703a183a76b97add8ee6ecc8b0ca1068fdcdedab18bcf2de
3
+ size 23460
data/model_runs/synthetic_v1_baseline/split_manifest.json ADDED
The diff for this file is too large to render. See raw diff
 
data/model_runs/synthetic_v1_baseline/stress_metrics.json ADDED
@@ -0,0 +1,707 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "exclusion_metadata": {
3
+ "missing_requested_excluded_columns": [],
4
+ "notes": [
5
+ "Identifier, label, direct leakage, site, and synthetic-only audit columns are excluded.",
6
+ "scope_type and window_length_seconds are retained because they are valid deployment-time context.",
7
+ "Constant version metadata columns are excluded from the baseline."
8
+ ],
9
+ "present_excluded_columns": [
10
+ "feature_row_id",
11
+ "dataset_id",
12
+ "seed",
13
+ "site_id",
14
+ "scope_id_hash",
15
+ "window_start",
16
+ "window_end",
17
+ "episode_id",
18
+ "label_0_to_4",
19
+ "label_confidence",
20
+ "label_reason",
21
+ "label_source",
22
+ "raw_input_manifest_hash",
23
+ "latent_workload_class",
24
+ "scenario_family",
25
+ "scenario_variant",
26
+ "evidence_recipe_id",
27
+ "counterfactual_group_id",
28
+ "synthetic_counterfactual_role",
29
+ "data_quality_regime",
30
+ "privacy_tier",
31
+ "collector_profile",
32
+ "topology_class",
33
+ "temporal_phase",
34
+ "synthetic_hard_case_tags",
35
+ "synthetic_evidence_profile",
36
+ "capacity_evidence_only",
37
+ "integrity_evidence_only",
38
+ "physical_evidence_only",
39
+ "feature_pipeline_version",
40
+ "policy_threshold_version",
41
+ "hardware_normalization_version"
42
+ ],
43
+ "requested_excluded_columns": [
44
+ "feature_row_id",
45
+ "dataset_id",
46
+ "seed",
47
+ "site_id",
48
+ "scope_id_hash",
49
+ "window_start",
50
+ "window_end",
51
+ "episode_id",
52
+ "label_0_to_4",
53
+ "label_confidence",
54
+ "label_reason",
55
+ "label_source",
56
+ "raw_input_manifest_hash",
57
+ "latent_workload_class",
58
+ "scenario_family",
59
+ "scenario_variant",
60
+ "evidence_recipe_id",
61
+ "counterfactual_group_id",
62
+ "synthetic_counterfactual_role",
63
+ "data_quality_regime",
64
+ "privacy_tier",
65
+ "collector_profile",
66
+ "topology_class",
67
+ "temporal_phase",
68
+ "synthetic_hard_case_tags",
69
+ "synthetic_evidence_profile",
70
+ "capacity_evidence_only",
71
+ "integrity_evidence_only",
72
+ "physical_evidence_only",
73
+ "feature_pipeline_version",
74
+ "policy_threshold_version",
75
+ "hardware_normalization_version"
76
+ ]
77
+ },
78
+ "features_path": "data/synthetic_v1/features/window_features_all.csv",
79
+ "max_iter": 90,
80
+ "model_feature_count": 152,
81
+ "scenario_column": "scenario_family",
82
+ "seed": 20260510,
83
+ "splits": {
84
+ "episode_grouped_random": {
85
+ "accuracy": 0.9776247848537005,
86
+ "false_negative_scenarios": {
87
+ "fragmented_training_linked": 2
88
+ },
89
+ "false_positive_scenarios": {
90
+ "underclocked_energy_capped_training": 4
91
+ },
92
+ "large_training": {
93
+ "f1": 0.9951298701298701,
94
+ "precision": 0.993517017828201,
95
+ "recall": 0.9967479674796748
96
+ },
97
+ "large_training_false_negative_count": 2,
98
+ "large_training_false_positive_count": 4,
99
+ "log_loss": 0.09684510600740838,
100
+ "macro_f1": 0.9820361510992616,
101
+ "middle_probability_bins": {
102
+ "0.1_to_0.3": 18,
103
+ "0.3_to_0.7": 2,
104
+ "0.7_to_0.9": 0
105
+ },
106
+ "test_episodes": 150,
107
+ "test_label_distribution": {
108
+ "0": 762,
109
+ "1": 690,
110
+ "2": 838,
111
+ "3": 463,
112
+ "4": 152
113
+ },
114
+ "test_rows": 2905,
115
+ "train_episodes": 610,
116
+ "train_rows": 11807
117
+ },
118
+ "scenario_family_holdout": {
119
+ "fragmented_training_linked": {
120
+ "accuracy": 0.8805774278215223,
121
+ "false_negative_scenarios": {
122
+ "fragmented_training_linked": 91
123
+ },
124
+ "false_positive_scenarios": {},
125
+ "large_training": {
126
+ "f1": 0.8465430016863407,
127
+ "precision": 1.0,
128
+ "recall": 0.7339181286549707
129
+ },
130
+ "large_training_false_negative_count": 91,
131
+ "large_training_false_positive_count": 0,
132
+ "log_loss": 0.23491922435876197,
133
+ "macro_f1": 0.3501482128238128,
134
+ "middle_probability_bins": {
135
+ "0.1_to_0.3": 6,
136
+ "0.3_to_0.7": 81,
137
+ "0.7_to_0.9": 0
138
+ },
139
+ "test_episodes": 35,
140
+ "test_label_distribution": {
141
+ "2": 420,
142
+ "3": 342
143
+ },
144
+ "test_rows": 762,
145
+ "train_episodes": 725,
146
+ "train_rows": 13950
147
+ },
148
+ "hpc_mpi_collective": {
149
+ "accuracy": 0.9530516431924883,
150
+ "false_negative_scenarios": {},
151
+ "false_positive_scenarios": {},
152
+ "large_training": {
153
+ "f1": 0.0,
154
+ "precision": 0.0,
155
+ "recall": 0.0
156
+ },
157
+ "large_training_false_negative_count": 0,
158
+ "large_training_false_positive_count": 0,
159
+ "log_loss": 0.24695636309738603,
160
+ "macro_f1": 0.19744680851063828,
161
+ "middle_probability_bins": {
162
+ "0.1_to_0.3": 58,
163
+ "0.3_to_0.7": 0,
164
+ "0.7_to_0.9": 0
165
+ },
166
+ "test_episodes": 42,
167
+ "test_label_distribution": {
168
+ "0": 34,
169
+ "2": 818
170
+ },
171
+ "test_rows": 852,
172
+ "train_episodes": 718,
173
+ "train_rows": 13860
174
+ },
175
+ "model_parallel_inference": {
176
+ "accuracy": 0.8814655172413793,
177
+ "false_negative_scenarios": {},
178
+ "false_positive_scenarios": {},
179
+ "large_training": {
180
+ "f1": 0.0,
181
+ "precision": 0.0,
182
+ "recall": 0.0
183
+ },
184
+ "large_training_false_negative_count": 0,
185
+ "large_training_false_positive_count": 0,
186
+ "log_loss": 0.32601787797092746,
187
+ "macro_f1": 0.32858292230734254,
188
+ "middle_probability_bins": {
189
+ "0.1_to_0.3": 0,
190
+ "0.3_to_0.7": 0,
191
+ "0.7_to_0.9": 0
192
+ },
193
+ "test_episodes": 23,
194
+ "test_label_distribution": {
195
+ "1": 125,
196
+ "2": 339
197
+ },
198
+ "test_rows": 464,
199
+ "train_episodes": 737,
200
+ "train_rows": 14248
201
+ },
202
+ "multi_tenant_fragmented_nontraining": {
203
+ "accuracy": 1.0,
204
+ "false_negative_scenarios": {},
205
+ "false_positive_scenarios": {},
206
+ "large_training": {
207
+ "f1": 0.0,
208
+ "precision": 0.0,
209
+ "recall": 0.0
210
+ },
211
+ "large_training_false_negative_count": 0,
212
+ "large_training_false_positive_count": 0,
213
+ "log_loss": 0.0001384917381362078,
214
+ "macro_f1": 0.2,
215
+ "middle_probability_bins": {
216
+ "0.1_to_0.3": 0,
217
+ "0.3_to_0.7": 0,
218
+ "0.7_to_0.9": 0
219
+ },
220
+ "test_episodes": 22,
221
+ "test_label_distribution": {
222
+ "2": 461
223
+ },
224
+ "test_rows": 461,
225
+ "train_episodes": 738,
226
+ "train_rows": 14251
227
+ },
228
+ "underclocked_energy_capped_training": {
229
+ "accuracy": 0.979539641943734,
230
+ "false_negative_scenarios": {},
231
+ "false_positive_scenarios": {
232
+ "underclocked_energy_capped_training": 8
233
+ },
234
+ "large_training": {
235
+ "f1": 0.9873015873015873,
236
+ "precision": 0.9749216300940439,
237
+ "recall": 1.0
238
+ },
239
+ "large_training_false_negative_count": 0,
240
+ "large_training_false_positive_count": 8,
241
+ "log_loss": 0.1272874809495267,
242
+ "macro_f1": 0.38693400167084374,
243
+ "middle_probability_bins": {
244
+ "0.1_to_0.3": 0,
245
+ "0.3_to_0.7": 0,
246
+ "0.7_to_0.9": 0
247
+ },
248
+ "test_episodes": 18,
249
+ "test_label_distribution": {
250
+ "2": 80,
251
+ "3": 311
252
+ },
253
+ "test_rows": 391,
254
+ "train_episodes": 742,
255
+ "train_rows": 14321
256
+ }
257
+ },
258
+ "site_holdout": {
259
+ "site_a": {
260
+ "accuracy": 0.9651113913408995,
261
+ "false_negative_scenarios": {
262
+ "fragmented_training_linked": 5
263
+ },
264
+ "false_positive_scenarios": {},
265
+ "large_training": {
266
+ "f1": 0.9958643507030603,
267
+ "precision": 1.0,
268
+ "recall": 0.9917627677100495
269
+ },
270
+ "large_training_false_negative_count": 5,
271
+ "large_training_false_positive_count": 0,
272
+ "log_loss": 0.12471327669067636,
273
+ "macro_f1": 0.969965539074414,
274
+ "middle_probability_bins": {
275
+ "0.1_to_0.3": 3,
276
+ "0.3_to_0.7": 6,
277
+ "0.7_to_0.9": 0
278
+ },
279
+ "test_episodes": 119,
280
+ "test_label_distribution": {
281
+ "0": 523,
282
+ "1": 498,
283
+ "2": 751,
284
+ "3": 428,
285
+ "4": 179
286
+ },
287
+ "test_rows": 2379,
288
+ "train_episodes": 641,
289
+ "train_rows": 12333
290
+ },
291
+ "site_b": {
292
+ "accuracy": 0.9817596566523605,
293
+ "false_negative_scenarios": {},
294
+ "false_positive_scenarios": {},
295
+ "large_training": {
296
+ "f1": 1.0,
297
+ "precision": 1.0,
298
+ "recall": 1.0
299
+ },
300
+ "large_training_false_negative_count": 0,
301
+ "large_training_false_positive_count": 0,
302
+ "log_loss": 0.12083701002956747,
303
+ "macro_f1": 0.9823840732636331,
304
+ "middle_probability_bins": {
305
+ "0.1_to_0.3": 59,
306
+ "0.3_to_0.7": 0,
307
+ "0.7_to_0.9": 0
308
+ },
309
+ "test_episodes": 143,
310
+ "test_label_distribution": {
311
+ "0": 766,
312
+ "1": 442,
313
+ "2": 923,
314
+ "3": 472,
315
+ "4": 193
316
+ },
317
+ "test_rows": 2796,
318
+ "train_episodes": 617,
319
+ "train_rows": 11916
320
+ },
321
+ "site_c": {
322
+ "accuracy": 0.9793233082706767,
323
+ "false_negative_scenarios": {},
324
+ "false_positive_scenarios": {
325
+ "underclocked_energy_capped_training": 4
326
+ },
327
+ "large_training": {
328
+ "f1": 0.993485342019544,
329
+ "precision": 0.9870550161812298,
330
+ "recall": 1.0
331
+ },
332
+ "large_training_false_negative_count": 0,
333
+ "large_training_false_positive_count": 4,
334
+ "log_loss": 0.12938808464320045,
335
+ "macro_f1": 0.9812786466966241,
336
+ "middle_probability_bins": {
337
+ "0.1_to_0.3": 0,
338
+ "0.3_to_0.7": 0,
339
+ "0.7_to_0.9": 0
340
+ },
341
+ "test_episodes": 83,
342
+ "test_label_distribution": {
343
+ "0": 544,
344
+ "1": 314,
345
+ "2": 433,
346
+ "3": 216,
347
+ "4": 89
348
+ },
349
+ "test_rows": 1596,
350
+ "train_episodes": 677,
351
+ "train_rows": 13116
352
+ },
353
+ "site_d": {
354
+ "accuracy": 0.9671179883945842,
355
+ "false_negative_scenarios": {},
356
+ "false_positive_scenarios": {},
357
+ "large_training": {
358
+ "f1": 1.0,
359
+ "precision": 1.0,
360
+ "recall": 1.0
361
+ },
362
+ "large_training_false_negative_count": 0,
363
+ "large_training_false_positive_count": 0,
364
+ "log_loss": 0.1630732533805434,
365
+ "macro_f1": 0.9761029329988823,
366
+ "middle_probability_bins": {
367
+ "0.1_to_0.3": 20,
368
+ "0.3_to_0.7": 0,
369
+ "0.7_to_0.9": 0
370
+ },
371
+ "test_episodes": 134,
372
+ "test_label_distribution": {
373
+ "0": 657,
374
+ "1": 691,
375
+ "2": 751,
376
+ "3": 415,
377
+ "4": 71
378
+ },
379
+ "test_rows": 2585,
380
+ "train_episodes": 626,
381
+ "train_rows": 12127
382
+ },
383
+ "site_e": {
384
+ "accuracy": 0.9577039274924471,
385
+ "false_negative_scenarios": {
386
+ "training_without_semantic_logs": 4
387
+ },
388
+ "false_positive_scenarios": {},
389
+ "large_training": {
390
+ "f1": 0.9952718676122931,
391
+ "precision": 1.0,
392
+ "recall": 0.9905882352941177
393
+ },
394
+ "large_training_false_negative_count": 4,
395
+ "large_training_false_positive_count": 0,
396
+ "log_loss": 0.10221061129077724,
397
+ "macro_f1": 0.9672000210392337,
398
+ "middle_probability_bins": {
399
+ "0.1_to_0.3": 0,
400
+ "0.3_to_0.7": 0,
401
+ "0.7_to_0.9": 0
402
+ },
403
+ "test_episodes": 105,
404
+ "test_label_distribution": {
405
+ "0": 501,
406
+ "1": 505,
407
+ "2": 555,
408
+ "3": 323,
409
+ "4": 102
410
+ },
411
+ "test_rows": 1986,
412
+ "train_episodes": 655,
413
+ "train_rows": 12726
414
+ },
415
+ "site_f": {
416
+ "accuracy": 0.974571586511885,
417
+ "false_negative_scenarios": {
418
+ "fragmented_training_linked": 4
419
+ },
420
+ "false_positive_scenarios": {},
421
+ "large_training": {
422
+ "f1": 0.9951338199513382,
423
+ "precision": 1.0,
424
+ "recall": 0.9903147699757869
425
+ },
426
+ "large_training_false_negative_count": 4,
427
+ "large_training_false_positive_count": 0,
428
+ "log_loss": 0.12939604463893833,
429
+ "macro_f1": 0.979930256900869,
430
+ "middle_probability_bins": {
431
+ "0.1_to_0.3": 2,
432
+ "0.3_to_0.7": 4,
433
+ "0.7_to_0.9": 0
434
+ },
435
+ "test_episodes": 94,
436
+ "test_label_distribution": {
437
+ "0": 512,
438
+ "1": 449,
439
+ "2": 435,
440
+ "3": 330,
441
+ "4": 83
442
+ },
443
+ "test_rows": 1809,
444
+ "train_episodes": 666,
445
+ "train_rows": 12903
446
+ },
447
+ "site_g": {
448
+ "accuracy": 0.9647661755285074,
449
+ "false_negative_scenarios": {
450
+ "training_without_semantic_logs": 3
451
+ },
452
+ "false_positive_scenarios": {},
453
+ "large_training": {
454
+ "f1": 0.9963636363636363,
455
+ "precision": 1.0,
456
+ "recall": 0.9927536231884058
457
+ },
458
+ "large_training_false_negative_count": 3,
459
+ "large_training_false_positive_count": 0,
460
+ "log_loss": 0.05853045642772131,
461
+ "macro_f1": 0.9717372783794571,
462
+ "middle_probability_bins": {
463
+ "0.1_to_0.3": 0,
464
+ "0.3_to_0.7": 7,
465
+ "0.7_to_0.9": 5
466
+ },
467
+ "test_episodes": 82,
468
+ "test_label_distribution": {
469
+ "0": 358,
470
+ "1": 434,
471
+ "2": 355,
472
+ "3": 305,
473
+ "4": 109
474
+ },
475
+ "test_rows": 1561,
476
+ "train_episodes": 678,
477
+ "train_rows": 13151
478
+ }
479
+ },
480
+ "source_ablations": {
481
+ "source_ablation_drop_fabric": {
482
+ "accuracy": 0.9865748709122203,
483
+ "dropped_prefixes": [
484
+ "o6_",
485
+ "o7_",
486
+ "fabric_telemetry_trust_level"
487
+ ],
488
+ "false_negative_scenarios": {},
489
+ "false_positive_scenarios": {
490
+ "underclocked_energy_capped_training": 4
491
+ },
492
+ "feature_count": 132,
493
+ "large_training": {
494
+ "f1": 0.9967585089141004,
495
+ "precision": 0.9935379644588045,
496
+ "recall": 1.0
497
+ },
498
+ "large_training_false_negative_count": 0,
499
+ "large_training_false_positive_count": 4,
500
+ "log_loss": 0.08731589431092471,
501
+ "macro_f1": 0.9891740593379061,
502
+ "middle_probability_bins": {
503
+ "0.1_to_0.3": 0,
504
+ "0.3_to_0.7": 0,
505
+ "0.7_to_0.9": 0
506
+ },
507
+ "test_episodes": 150,
508
+ "test_label_distribution": {
509
+ "0": 762,
510
+ "1": 690,
511
+ "2": 838,
512
+ "3": 463,
513
+ "4": 152
514
+ },
515
+ "test_rows": 2905,
516
+ "train_episodes": 610,
517
+ "train_rows": 11807
518
+ },
519
+ "source_ablation_drop_gpu_telemetry": {
520
+ "accuracy": 0.9645438898450946,
521
+ "dropped_prefixes": [
522
+ "o4_",
523
+ "o5_",
524
+ "gpu_telemetry_trust_level"
525
+ ],
526
+ "false_negative_scenarios": {
527
+ "fragmented_training_linked": 2
528
+ },
529
+ "false_positive_scenarios": {
530
+ "underclocked_energy_capped_training": 4
531
+ },
532
+ "feature_count": 131,
533
+ "large_training": {
534
+ "f1": 0.9951298701298701,
535
+ "precision": 0.993517017828201,
536
+ "recall": 0.9967479674796748
537
+ },
538
+ "large_training_false_negative_count": 2,
539
+ "large_training_false_positive_count": 4,
540
+ "log_loss": 0.09299517347739551,
541
+ "macro_f1": 0.9719602384218333,
542
+ "middle_probability_bins": {
543
+ "0.1_to_0.3": 0,
544
+ "0.3_to_0.7": 0,
545
+ "0.7_to_0.9": 0
546
+ },
547
+ "test_episodes": 150,
548
+ "test_label_distribution": {
549
+ "0": 762,
550
+ "1": 690,
551
+ "2": 838,
552
+ "3": 463,
553
+ "4": 152
554
+ },
555
+ "test_rows": 2905,
556
+ "train_episodes": 610,
557
+ "train_rows": 11807
558
+ },
559
+ "source_ablation_drop_power": {
560
+ "accuracy": 0.9648881239242685,
561
+ "dropped_prefixes": [
562
+ "o8_",
563
+ "o9_",
564
+ "power_meter_trust_level"
565
+ ],
566
+ "false_negative_scenarios": {
567
+ "training_with_delayed_logs": 4
568
+ },
569
+ "false_positive_scenarios": {
570
+ "underclocked_energy_capped_training": 4
571
+ },
572
+ "feature_count": 133,
573
+ "large_training": {
574
+ "f1": 0.9934959349593496,
575
+ "precision": 0.9934959349593496,
576
+ "recall": 0.9934959349593496
577
+ },
578
+ "large_training_false_negative_count": 4,
579
+ "large_training_false_positive_count": 4,
580
+ "log_loss": 0.14389758581141518,
581
+ "macro_f1": 0.9719275856055797,
582
+ "middle_probability_bins": {
583
+ "0.1_to_0.3": 4,
584
+ "0.3_to_0.7": 0,
585
+ "0.7_to_0.9": 0
586
+ },
587
+ "test_episodes": 150,
588
+ "test_label_distribution": {
589
+ "0": 762,
590
+ "1": 690,
591
+ "2": 838,
592
+ "3": 463,
593
+ "4": 152
594
+ },
595
+ "test_rows": 2905,
596
+ "train_episodes": 610,
597
+ "train_rows": 11807
598
+ },
599
+ "source_ablation_drop_runtime_and_ml_logs": {
600
+ "accuracy": 0.9380378657487092,
601
+ "dropped_prefixes": [
602
+ "o10_",
603
+ "o12_"
604
+ ],
605
+ "false_negative_scenarios": {},
606
+ "false_positive_scenarios": {
607
+ "training_with_delayed_logs": 4,
608
+ "underclocked_energy_capped_training": 4
609
+ },
610
+ "feature_count": 132,
611
+ "large_training": {
612
+ "f1": 0.9935379644588045,
613
+ "precision": 0.9871589085072231,
614
+ "recall": 1.0
615
+ },
616
+ "large_training_false_negative_count": 0,
617
+ "large_training_false_positive_count": 8,
618
+ "log_loss": 0.35327183914707877,
619
+ "macro_f1": 0.8925854563387956,
620
+ "middle_probability_bins": {
621
+ "0.1_to_0.3": 4,
622
+ "0.3_to_0.7": 4,
623
+ "0.7_to_0.9": 44
624
+ },
625
+ "test_episodes": 150,
626
+ "test_label_distribution": {
627
+ "0": 762,
628
+ "1": 690,
629
+ "2": 838,
630
+ "3": 463,
631
+ "4": 152
632
+ },
633
+ "test_rows": 2905,
634
+ "train_episodes": 610,
635
+ "train_rows": 11807
636
+ },
637
+ "source_ablation_drop_storage": {
638
+ "accuracy": 0.9655765920826161,
639
+ "dropped_prefixes": [
640
+ "o11_"
641
+ ],
642
+ "false_negative_scenarios": {
643
+ "fragmented_training_linked": 21
644
+ },
645
+ "false_positive_scenarios": {
646
+ "fragmented_training_linked": 7
647
+ },
648
+ "feature_count": 141,
649
+ "large_training": {
650
+ "f1": 0.9769736842105263,
651
+ "precision": 0.9883527454242929,
652
+ "recall": 0.9658536585365853
653
+ },
654
+ "large_training_false_negative_count": 21,
655
+ "large_training_false_positive_count": 7,
656
+ "log_loss": 0.12576959836354257,
657
+ "macro_f1": 0.9712103257601914,
658
+ "middle_probability_bins": {
659
+ "0.1_to_0.3": 13,
660
+ "0.3_to_0.7": 15,
661
+ "0.7_to_0.9": 8
662
+ },
663
+ "test_episodes": 150,
664
+ "test_label_distribution": {
665
+ "0": 762,
666
+ "1": 690,
667
+ "2": 838,
668
+ "3": 463,
669
+ "4": 152
670
+ },
671
+ "test_rows": 2905,
672
+ "train_episodes": 610,
673
+ "train_rows": 11807
674
+ }
675
+ },
676
+ "time_holdout": {
677
+ "accuracy": 0.980971797485559,
678
+ "false_negative_scenarios": {},
679
+ "false_positive_scenarios": {},
680
+ "large_training": {
681
+ "f1": 1.0,
682
+ "precision": 1.0,
683
+ "recall": 1.0
684
+ },
685
+ "large_training_false_negative_count": 0,
686
+ "large_training_false_positive_count": 0,
687
+ "log_loss": 0.12262942519580222,
688
+ "macro_f1": 0.9862673370214863,
689
+ "middle_probability_bins": {
690
+ "0.1_to_0.3": 4,
691
+ "0.3_to_0.7": 0,
692
+ "0.7_to_0.9": 0
693
+ },
694
+ "test_episodes": 179,
695
+ "test_label_distribution": {
696
+ "0": 715,
697
+ "1": 857,
698
+ "2": 776,
699
+ "3": 486,
700
+ "4": 109
701
+ },
702
+ "test_rows": 2943,
703
+ "train_episodes": 622,
704
+ "train_rows": 11769
705
+ }
706
+ }
707
+ }
data/model_runs/synthetic_v1_baseline/stress_summary.md ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Synthetic v1 Stress Summary
2
+
3
+ Feature table: `data/synthetic_v1/features/window_features_all.csv`
4
+ Seed: `20260510`
5
+ Lightweight stress model max_iter: `90`
6
+
7
+ ## Core Splits
8
+
9
+ - episode_grouped_random: rows=2905, macro F1=0.982, large precision=0.994, large recall=0.997, FP=4, FN=2, middle p bins={'0.1_to_0.3': 18, '0.3_to_0.7': 2, '0.7_to_0.9': 0}
10
+ - time_holdout: rows=2943, macro F1=0.986, large precision=1.000, large recall=1.000, FP=0, FN=0, middle p bins={'0.1_to_0.3': 4, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
11
+
12
+ ## Site Holdout
13
+
14
+ - site_a: rows=2379, macro F1=0.970, large precision=1.000, large recall=0.992, FP=0, FN=5, middle p bins={'0.1_to_0.3': 3, '0.3_to_0.7': 6, '0.7_to_0.9': 0}
15
+ - site_b: rows=2796, macro F1=0.982, large precision=1.000, large recall=1.000, FP=0, FN=0, middle p bins={'0.1_to_0.3': 59, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
16
+ - site_c: rows=1596, macro F1=0.981, large precision=0.987, large recall=1.000, FP=4, FN=0, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
17
+ - site_d: rows=2585, macro F1=0.976, large precision=1.000, large recall=1.000, FP=0, FN=0, middle p bins={'0.1_to_0.3': 20, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
18
+ - site_e: rows=1986, macro F1=0.967, large precision=1.000, large recall=0.991, FP=0, FN=4, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
19
+ - site_f: rows=1809, macro F1=0.980, large precision=1.000, large recall=0.990, FP=0, FN=4, middle p bins={'0.1_to_0.3': 2, '0.3_to_0.7': 4, '0.7_to_0.9': 0}
20
+ - site_g: rows=1561, macro F1=0.972, large precision=1.000, large recall=0.993, FP=0, FN=3, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 7, '0.7_to_0.9': 5}
21
+
22
+ ## Scenario Family Holdout
23
+
24
+ - underclocked_energy_capped_training: rows=391, macro F1=0.387, large precision=0.975, large recall=1.000, FP=8, FN=0, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
25
+ false positives: {'underclocked_energy_capped_training': 8}
26
+ - fragmented_training_linked: rows=762, macro F1=0.350, large precision=1.000, large recall=0.734, FP=0, FN=91, middle p bins={'0.1_to_0.3': 6, '0.3_to_0.7': 81, '0.7_to_0.9': 0}
27
+ false negatives: {'fragmented_training_linked': 91}
28
+ - multi_tenant_fragmented_nontraining: rows=461, macro F1=0.200, large precision=0.000, large recall=0.000, FP=0, FN=0, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
29
+ - model_parallel_inference: rows=464, macro F1=0.329, large precision=0.000, large recall=0.000, FP=0, FN=0, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
30
+ - hpc_mpi_collective: rows=852, macro F1=0.197, large precision=0.000, large recall=0.000, FP=0, FN=0, middle p bins={'0.1_to_0.3': 58, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
31
+
32
+ ## Source Ablations
33
+
34
+ - source_ablation_drop_fabric: rows=2905, macro F1=0.989, large precision=0.994, large recall=1.000, FP=4, FN=0, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
35
+ macro F1 delta vs random baseline: +0.007
36
+ - source_ablation_drop_runtime_and_ml_logs: rows=2905, macro F1=0.893, large precision=0.987, large recall=1.000, FP=8, FN=0, middle p bins={'0.1_to_0.3': 4, '0.3_to_0.7': 4, '0.7_to_0.9': 44}
37
+ macro F1 delta vs random baseline: -0.089
38
+ - source_ablation_drop_gpu_telemetry: rows=2905, macro F1=0.972, large precision=0.994, large recall=0.997, FP=4, FN=2, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
39
+ macro F1 delta vs random baseline: -0.010
40
+ - source_ablation_drop_power: rows=2905, macro F1=0.972, large precision=0.993, large recall=0.993, FP=4, FN=4, middle p bins={'0.1_to_0.3': 4, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
41
+ macro F1 delta vs random baseline: -0.010
42
+ - source_ablation_drop_storage: rows=2905, macro F1=0.971, large precision=0.988, large recall=0.966, FP=7, FN=21, middle p bins={'0.1_to_0.3': 13, '0.3_to_0.7': 15, '0.7_to_0.9': 8}
43
+ macro F1 delta vs random baseline: -0.011
44
+
45
+ ## Interpretation
46
+
47
+ - These are stress diagnostics, not tuned headline metrics.
48
+ - Family holdouts identify hard families that still fail under scenario generalization.
49
+ - Source ablations should degrade performance when the dropped layer carries independent evidence.
50
+ - Middle probability bins indicate whether v1 has less all-or-nothing large-training probability mass than v0.
data/model_runs/synthetic_v1_baseline/validation_summary.md ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Validation Summary
2
+
3
+ Feature table: `data/synthetic_v1/features/window_features_all.csv`
4
+
5
+ ## Dataset Validation Before Training
6
+
7
+ - `synthetic_dataset_validator`: return code 0; PASS
8
+ - `public_dataset_validator`: return code 0; PASS
9
+
10
+ ## Required Artifacts
11
+
12
+ - `README.md`: present
13
+ - `manifest.json`: present
14
+ - `model.joblib`: present
15
+ - `preprocessing.joblib`: present
16
+ - `feature_columns.json`: present
17
+ - `excluded_columns.json`: present
18
+ - `split_manifest.json`: present
19
+ - `metrics.json`: present
20
+ - `calibration_metrics.json`: present
21
+ - `confusion_matrix.csv`: present
22
+ - `classification_report.json`: present
23
+ - `predictions_test.csv`: present
24
+ - `predictions_all.csv`: present
25
+ - `feature_importance.csv`: present
26
+ - `evidence_audit_sample.csv`: present
27
+ - `validation_summary.md`: present
28
+
29
+ ## Test Metrics
30
+
31
+ - Accuracy: 0.9725
32
+ - Macro F1: 0.9781
33
+ - Label 3/4 precision: 0.9935
34
+ - Label 3/4 recall: 0.9967
35
+ - `p_large_training >= 0.5` precision: 0.9935
36
+ - `p_large_training >= 0.5` recall: 0.9967
37
+
38
+ ## Governance Checks
39
+
40
+ - Capacity gate applied to post-processed probabilities.
41
+ - Negative certification confidence is `p_label_0 * min_critical_coverage`.
42
+ - Integrity warnings are reported separately from positive training evidence.
43
+ - Raw model probabilities are retained as `raw_p_label_0` through `raw_p_label_4`.
src/datacenter_verification_api/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """Live inference API for the datacenter verification demo."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.1.0"
src/datacenter_verification_api/app.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """FastAPI app for live datacenter verification model inference."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from contextlib import asynccontextmanager
7
+ from typing import AsyncIterator
8
+
9
+ from fastapi import FastAPI, HTTPException
10
+ from fastapi.middleware.cors import CORSMiddleware
11
+
12
+ from src.datacenter_verification_api import __version__
13
+ from src.datacenter_verification_api.model_service import ModelService
14
+ from src.datacenter_verification_api.schemas import (
15
+ BatchPredictRequest,
16
+ BatchPredictResponse,
17
+ HealthResponse,
18
+ MetadataResponse,
19
+ PredictRequest,
20
+ PredictResponse,
21
+ )
22
+
23
+
24
+ DEFAULT_ALLOWED_ORIGINS = [
25
+ "https://idacy.github.io",
26
+ "http://localhost:8000",
27
+ "http://localhost:5173",
28
+ "http://localhost:7860",
29
+ "http://127.0.0.1:8000",
30
+ "http://127.0.0.1:5173",
31
+ "http://127.0.0.1:7860",
32
+ ]
33
+
34
+ service: ModelService | None = None
35
+ load_error: str | None = None
36
+
37
+
38
+ def parse_allowed_origins() -> list[str]:
39
+ raw = os.getenv("DCV_ALLOWED_ORIGINS", "")
40
+ if not raw.strip():
41
+ return DEFAULT_ALLOWED_ORIGINS
42
+ return [item.strip() for item in raw.split(",") if item.strip()]
43
+
44
+
45
+ @asynccontextmanager
46
+ async def lifespan(_: FastAPI) -> AsyncIterator[None]:
47
+ global service, load_error
48
+ try:
49
+ service = ModelService.from_env()
50
+ load_error = None
51
+ except Exception as exc: # pragma: no cover - exercised by deployment misconfiguration
52
+ service = None
53
+ load_error = str(exc)
54
+ yield
55
+
56
+
57
+ app = FastAPI(
58
+ title="Datacenter Verification Live Inference API",
59
+ version=__version__,
60
+ lifespan=lifespan,
61
+ )
62
+
63
+ allowed_origins = parse_allowed_origins()
64
+ app.add_middleware(
65
+ CORSMiddleware,
66
+ allow_origins=["*"] if "*" in allowed_origins else allowed_origins,
67
+ allow_credentials=False,
68
+ allow_methods=["GET", "POST", "OPTIONS"],
69
+ allow_headers=["*"],
70
+ )
71
+
72
+
73
+ def require_service() -> ModelService:
74
+ if service is None:
75
+ detail = "model service is not loaded"
76
+ if load_error:
77
+ detail = f"{detail}: {load_error}"
78
+ raise HTTPException(status_code=503, detail=detail)
79
+ return service
80
+
81
+
82
+ @app.get("/health", response_model=HealthResponse)
83
+ def health() -> HealthResponse:
84
+ loaded = service is not None
85
+ return HealthResponse(
86
+ status="ok" if loaded else "error",
87
+ model_loaded=loaded,
88
+ api_version=__version__,
89
+ model_run_id=service.model_run_id if service else None,
90
+ dataset_id=service.dataset_id if service else None,
91
+ feature_count=len(service.feature_columns) if service else 0,
92
+ base_row_lookup_enabled=bool(service.feature_lookup) if service else False,
93
+ error=load_error,
94
+ )
95
+
96
+
97
+ @app.get("/metadata", response_model=MetadataResponse)
98
+ def metadata() -> MetadataResponse:
99
+ return require_service().metadata()
100
+
101
+
102
+ @app.post("/predict", response_model=PredictResponse)
103
+ def predict(request: PredictRequest) -> PredictResponse:
104
+ return require_service().predict(request)
105
+
106
+
107
+ @app.post("/predict/batch", response_model=BatchPredictResponse)
108
+ def predict_batch(request: BatchPredictRequest) -> BatchPredictResponse:
109
+ loaded = require_service()
110
+ predictions = [loaded.predict(item) for item in request.requests]
111
+ return BatchPredictResponse(model_run_id=loaded.model_run_id, predictions=predictions)
src/datacenter_verification_api/feature_completion.py ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Feature normalization and dependent-feature completion for live inference."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+ import math
9
+
10
+ import numpy as np
11
+ import pandas as pd
12
+
13
+
14
+ NULL_STRINGS = {"", "nan", "none", "null", "undefined", "na", "n/a", "<na>"}
15
+
16
+ FRACTION_COLUMNS = {
17
+ "o1_homogeneous_high_end_fraction",
18
+ "o1_non_partitioned_fraction",
19
+ "o2_concurrency_fraction_domain",
20
+ "o3_training_sku_fraction",
21
+ "o3_billing_continuity_score",
22
+ "o4_gpu_util_duty_gt_70",
23
+ "o4_sm_tensor_active_p95",
24
+ "o4_hbm_used_fraction_p50",
25
+ "o4_hbm_bandwidth_active_p95",
26
+ "o4_gpu_power_fraction_p95",
27
+ "o4_hbm_pressure_duration_fraction",
28
+ "o4_power_cap_active_fraction",
29
+ "o4_thermal_throttle_fraction",
30
+ "o5_kernel_training_motif_score",
31
+ "o5_tensor_throughput_ratio",
32
+ "o6_nvlink_util_p95",
33
+ "o6_nvlink_periodicity_score",
34
+ "o7_scaleout_port_util_p95",
35
+ "o7_collective_periodicity_score",
36
+ "o7_burst_duty_cycle",
37
+ "o7_job_to_port_mapping_coverage",
38
+ "o7_flow_entropy_score",
39
+ "o7_cross_section_sync_score",
40
+ "o7_collective_jitter_score",
41
+ "o7_storage_traffic_fraction",
42
+ "o7_inference_fanout_score",
43
+ "o7_account_flow_linkage_confidence",
44
+ "o8_rack_power_fraction_p95",
45
+ "o8_power_cv",
46
+ "o8_power_cap_or_curtailment_active",
47
+ "o8_unattributed_power_fraction",
48
+ "o9_cooling_flow_duty",
49
+ "o10_rank_stability_score",
50
+ "o10_runtime_metadata_confidence",
51
+ "o11_checkpoint_periodicity_score",
52
+ "o11_read_write_training_pattern_score",
53
+ "o11_checkpoint_jitter_score",
54
+ "o11_artifact_write_pattern_score",
55
+ "o11_dataloader_read_pattern_score",
56
+ "o11_backup_or_replication_pattern_score",
57
+ "o11_storage_cotraffic_score",
58
+ "o12_log_completeness_fraction",
59
+ "o12_declaration_consistency_score",
60
+ "o13_attestation_valid_fraction",
61
+ "o13_confidential_compute_mode_fraction",
62
+ "o13_collector_measurement_valid",
63
+ "o14_min_critical_coverage",
64
+ "o14_gap_fraction_critical",
65
+ "o16_probe_throughput_ratio_min",
66
+ "o17_energy_contract_alignment_score",
67
+ "o17_network_provider_utilization_score",
68
+ "o17_procurement_or_maintenance_explanation_score",
69
+ }
70
+
71
+ for index in range(1, 18):
72
+ FRACTION_COLUMNS.add(f"o{index}_coverage_fraction")
73
+
74
+
75
+ @dataclass
76
+ class FeatureSchema:
77
+ """Column type hints inferred from the training feature table."""
78
+
79
+ feature_columns: list[str]
80
+ numeric_columns: set[str] = field(default_factory=set)
81
+ boolean_columns: set[str] = field(default_factory=set)
82
+
83
+ @classmethod
84
+ def from_frame(cls, feature_columns: list[str], frame: pd.DataFrame | None) -> "FeatureSchema":
85
+ numeric_columns: set[str] = set()
86
+ boolean_columns: set[str] = set()
87
+ if frame is not None:
88
+ for column in feature_columns:
89
+ if column not in frame.columns:
90
+ continue
91
+ dtype = frame[column].dtype
92
+ if pd.api.types.is_bool_dtype(dtype):
93
+ boolean_columns.add(column)
94
+ numeric_columns.add(column)
95
+ elif pd.api.types.is_numeric_dtype(dtype):
96
+ numeric_columns.add(column)
97
+ return cls(
98
+ feature_columns=list(feature_columns),
99
+ numeric_columns=numeric_columns,
100
+ boolean_columns=boolean_columns,
101
+ )
102
+
103
+
104
+ def is_missing(value: Any) -> bool:
105
+ if value is None or value is pd.NA:
106
+ return True
107
+ if isinstance(value, str):
108
+ return value.strip().lower() in NULL_STRINGS
109
+ if isinstance(value, (float, np.floating)):
110
+ return math.isnan(float(value))
111
+ try:
112
+ return bool(pd.isna(value))
113
+ except (TypeError, ValueError):
114
+ return False
115
+
116
+
117
+ def jsonable(value: Any) -> Any:
118
+ if is_missing(value):
119
+ return None
120
+ if isinstance(value, (np.integer,)):
121
+ return int(value)
122
+ if isinstance(value, (np.floating,)):
123
+ return float(value)
124
+ if isinstance(value, (np.bool_,)):
125
+ return bool(value)
126
+ if isinstance(value, pd.Timestamp):
127
+ return value.isoformat()
128
+ return value
129
+
130
+
131
+ def normalize_value(value: Any, column: str | None = None, schema: FeatureSchema | None = None) -> Any:
132
+ if is_missing(value):
133
+ return None
134
+ if isinstance(value, str):
135
+ stripped = value.strip()
136
+ lowered = stripped.lower()
137
+ if lowered in {"true", "t", "yes", "y"}:
138
+ return True
139
+ if lowered in {"false", "f", "no", "n"}:
140
+ return False
141
+ if lowered in {"1", "0"} and schema and column in schema.boolean_columns:
142
+ return lowered == "1"
143
+ if schema and column in schema.numeric_columns:
144
+ try:
145
+ number = float(stripped)
146
+ except ValueError:
147
+ return stripped
148
+ return int(number) if number.is_integer() else number
149
+ return stripped
150
+ if schema and column in schema.boolean_columns:
151
+ if isinstance(value, (bool, np.bool_)):
152
+ return bool(value)
153
+ if isinstance(value, (int, float, np.integer, np.floating)) and not is_missing(value):
154
+ return bool(value)
155
+ if schema and column in schema.numeric_columns:
156
+ try:
157
+ number = float(value)
158
+ except (TypeError, ValueError):
159
+ return value
160
+ if math.isnan(number):
161
+ return None
162
+ return int(number) if number.is_integer() else number
163
+ return jsonable(value)
164
+
165
+
166
+ def normalize_mapping(values: dict[str, Any] | None, schema: FeatureSchema) -> dict[str, Any]:
167
+ if not values:
168
+ return {}
169
+ return {str(key): normalize_value(value, str(key), schema) for key, value in values.items()}
170
+
171
+
172
+ def number_or_none(value: Any) -> float | None:
173
+ if is_missing(value):
174
+ return None
175
+ try:
176
+ number = float(value)
177
+ except (TypeError, ValueError):
178
+ return None
179
+ if math.isnan(number):
180
+ return None
181
+ return number
182
+
183
+
184
+ def bool_or_false(value: Any) -> bool:
185
+ if is_missing(value):
186
+ return False
187
+ if isinstance(value, (bool, np.bool_)):
188
+ return bool(value)
189
+ text = str(value).strip().lower()
190
+ if text in {"true", "t", "1", "yes", "y"}:
191
+ return True
192
+ if text in {"false", "f", "0", "no", "n"}:
193
+ return False
194
+ return False
195
+
196
+
197
+ def set_if_changed(row: dict[str, Any], key: str, value: Any, warnings: list[str], derived: list[str]) -> None:
198
+ old = row.get(key)
199
+ normalized = jsonable(value)
200
+ old_normalized = jsonable(old)
201
+ if old_normalized != normalized:
202
+ row[key] = normalized
203
+ derived.append(key)
204
+
205
+
206
+ def clamp_fraction(value: Any) -> float | None:
207
+ number = number_or_none(value)
208
+ if number is None:
209
+ return None
210
+ return min(1.0, max(0.0, number))
211
+
212
+
213
+ def complete_features(
214
+ row: dict[str, Any],
215
+ changed_keys: set[str],
216
+ *,
217
+ has_base_row: bool,
218
+ derive: bool,
219
+ ) -> tuple[dict[str, Any], list[str]]:
220
+ """Return a completed row and warnings for coherent live inference.
221
+
222
+ Base-row predictions must remain byte-for-byte close to the exported model
223
+ replay, so dependent features are recomputed for base rows only when an
224
+ edited input can affect them. Rows without a base use every available input
225
+ to derive minimal context.
226
+ """
227
+
228
+ out = dict(row)
229
+ warnings: list[str] = []
230
+ derived: list[str] = []
231
+
232
+ if not has_base_row:
233
+ if is_missing(out.get("scope_type")):
234
+ out["scope_type"] = "site"
235
+ if is_missing(out.get("window_length_seconds")):
236
+ out["window_length_seconds"] = 3600
237
+ if is_missing(out.get("capacity_possible")):
238
+ out["capacity_possible"] = False
239
+
240
+ if not derive:
241
+ return out, warnings
242
+
243
+ should_derive_all = not has_base_row
244
+
245
+ allocation = number_or_none(out.get("o2_max_concurrent_normalized_gpus"))
246
+ duration = number_or_none(out.get("o2_allocation_duration_hours"))
247
+ capacity = number_or_none(out.get("o1_normalized_h100e_capacity"))
248
+ contiguous = number_or_none(out.get("o1_largest_contiguous_domain_gpus"))
249
+
250
+ ratio_inputs_changed = bool(
251
+ {"o2_max_concurrent_normalized_gpus", "o2_allocation_duration_hours"} & changed_keys
252
+ )
253
+ if (should_derive_all or ratio_inputs_changed) and allocation is not None and duration is not None:
254
+ ratio = (allocation * duration) / (512 * 24)
255
+ set_if_changed(out, "o2_gpu_hours_policy_ratio", ratio, warnings, derived)
256
+ set_if_changed(out, "policy_compute_ratio", ratio, warnings, derived)
257
+
258
+ concurrency_inputs_changed = bool(
259
+ {"o2_max_concurrent_normalized_gpus", "o1_normalized_h100e_capacity"} & changed_keys
260
+ )
261
+ if (should_derive_all or concurrency_inputs_changed) and allocation is not None and capacity is not None:
262
+ concurrency = clamp_fraction(allocation / capacity) if capacity > 0 else 0.0
263
+ set_if_changed(out, "o2_concurrency_fraction_domain", concurrency, warnings, derived)
264
+
265
+ if (should_derive_all or "o2_max_concurrent_normalized_gpus" in changed_keys) and allocation is not None:
266
+ rounded_allocation = int(round(allocation))
267
+ set_if_changed(out, "o10_world_size", rounded_allocation, warnings, derived)
268
+ set_if_changed(out, "o10_same_image_gpu_count", rounded_allocation, warnings, derived)
269
+
270
+ capacity_inputs_changed = bool(
271
+ {"o1_normalized_h100e_capacity", "o1_largest_contiguous_domain_gpus"} & changed_keys
272
+ )
273
+ if should_derive_all or capacity_inputs_changed:
274
+ if capacity is not None and contiguous is not None:
275
+ set_if_changed(out, "capacity_possible", capacity >= 512 and contiguous >= 512, warnings, derived)
276
+ elif not has_base_row:
277
+ set_if_changed(out, "capacity_possible", False, warnings, derived)
278
+
279
+ for key in sorted(FRACTION_COLUMNS & set(out)):
280
+ if should_derive_all or key in changed_keys:
281
+ clamped = clamp_fraction(out.get(key))
282
+ if clamped is not None:
283
+ set_if_changed(out, key, clamped, warnings, derived)
284
+
285
+ if derived:
286
+ warnings.append(f"derived fields updated from edited inputs: {', '.join(sorted(set(derived)))}")
287
+
288
+ allocation = number_or_none(out.get("o2_max_concurrent_normalized_gpus"))
289
+ capacity = number_or_none(out.get("o1_normalized_h100e_capacity"))
290
+ fabric = number_or_none(out.get("o7_synchronized_fabric_footprint"))
291
+ if capacity is not None and allocation is not None and allocation > capacity + 1:
292
+ warnings.append("allocated GPUs exceed monitored H100e capacity")
293
+ if capacity is not None and fabric is not None and fabric > capacity + 1:
294
+ warnings.append("fabric footprint exceeds monitored H100e capacity")
295
+ if allocation is not None and allocation > 0 and fabric is not None and fabric > allocation + 1:
296
+ warnings.append("fabric footprint exceeds allocated GPUs")
297
+
298
+ return out, warnings
src/datacenter_verification_api/model_service.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Sklearn model loading and one-row inference for the live API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import joblib
10
+ import numpy as np
11
+ import pandas as pd
12
+
13
+ from src.datacenter_verification_api import __version__
14
+ from src.datacenter_verification_api.feature_completion import (
15
+ FeatureSchema,
16
+ complete_features,
17
+ jsonable,
18
+ normalize_mapping,
19
+ )
20
+ from src.datacenter_verification_api.schemas import MetadataResponse, PredictRequest, PredictResponse
21
+ from src.datacenter_verification_modeling.common import (
22
+ LABELS,
23
+ PROB_COLUMNS,
24
+ add_governance_outputs,
25
+ model_input_frame,
26
+ probability_frame,
27
+ read_json,
28
+ )
29
+
30
+
31
+ REPO_ROOT = Path(__file__).resolve().parents[2]
32
+ DEFAULT_MODEL_RUN_DIR = Path("data/model_runs/synthetic_v1_baseline")
33
+ DEFAULT_FEATURE_TABLE = Path("data/synthetic_v1/features/window_features_all.csv")
34
+
35
+
36
+ def resolve_repo_path(value: str | Path | None, default: Path) -> Path:
37
+ path = Path(value) if value else default
38
+ if path.is_absolute():
39
+ return path
40
+ return (REPO_ROOT / path).resolve()
41
+
42
+
43
+ def repo_relative(path: Path | None) -> str | None:
44
+ if path is None:
45
+ return None
46
+ try:
47
+ return path.resolve().relative_to(REPO_ROOT).as_posix()
48
+ except ValueError:
49
+ return path.name
50
+
51
+
52
+ def read_json_if_exists(path: Path) -> Any:
53
+ return read_json(path) if path.exists() else {}
54
+
55
+
56
+ def split_semicolon(value: Any) -> list[str]:
57
+ if value is None:
58
+ return []
59
+ try:
60
+ if pd.isna(value):
61
+ return []
62
+ except (TypeError, ValueError):
63
+ pass
64
+ return [item.strip() for item in str(value).split(";") if item.strip()]
65
+
66
+
67
+ class ModelService:
68
+ """Loaded model artifacts plus base-row lookup for prediction requests."""
69
+
70
+ def __init__(self, model_run_dir: Path, feature_table_path: Path | None = None) -> None:
71
+ self.model_run_dir = model_run_dir
72
+ self.feature_table_path = feature_table_path
73
+ self.manifest = read_json_if_exists(model_run_dir / "manifest.json")
74
+ self.metrics = read_json_if_exists(model_run_dir / "metrics.json")
75
+ self.feature_columns: list[str] = read_json(model_run_dir / "feature_columns.json")
76
+ self.preprocessor = joblib.load(model_run_dir / "preprocessing.joblib")
77
+ self.model = joblib.load(model_run_dir / "model.joblib")
78
+ self.feature_table = self._load_feature_table(feature_table_path)
79
+ self.feature_schema = FeatureSchema.from_frame(self.feature_columns, self.feature_table)
80
+ self.feature_lookup = self._build_feature_lookup(self.feature_table)
81
+ self.model_run_id = str(self.manifest.get("model_run_id") or model_run_dir.name)
82
+
83
+ @classmethod
84
+ def from_env(cls) -> "ModelService":
85
+ model_run_dir = resolve_repo_path(os.getenv("DCV_MODEL_RUN_DIR"), DEFAULT_MODEL_RUN_DIR)
86
+ feature_table = resolve_repo_path(os.getenv("DCV_FEATURE_TABLE"), DEFAULT_FEATURE_TABLE)
87
+ return cls(model_run_dir=model_run_dir, feature_table_path=feature_table)
88
+
89
+ def _load_feature_table(self, path: Path | None) -> pd.DataFrame | None:
90
+ if path is None or not path.exists():
91
+ return None
92
+ frame = pd.read_csv(path)
93
+ if "feature_row_id" not in frame.columns:
94
+ raise ValueError(f"feature table lacks feature_row_id column: {path}")
95
+ return frame
96
+
97
+ def _build_feature_lookup(self, frame: pd.DataFrame | None) -> dict[str, dict[str, Any]]:
98
+ if frame is None:
99
+ return {}
100
+ rows: dict[str, dict[str, Any]] = {}
101
+ for record in frame.to_dict(orient="records"):
102
+ row_id = record.get("feature_row_id")
103
+ if row_id is not None:
104
+ rows[str(row_id)] = dict(record)
105
+ return rows
106
+
107
+ @property
108
+ def dataset_id(self) -> str | None:
109
+ training_metadata = self.manifest.get("training_metadata", {})
110
+ if isinstance(training_metadata, dict):
111
+ path = training_metadata.get("features_path") or self.manifest.get("features_path")
112
+ else:
113
+ path = self.manifest.get("features_path")
114
+ if isinstance(path, str) and "synthetic_v1" in path:
115
+ return "synthetic_v1"
116
+ if self.feature_table is not None and "dataset_id" in self.feature_table.columns:
117
+ values = self.feature_table["dataset_id"].dropna().unique()
118
+ if len(values):
119
+ return str(values[0])
120
+ return None
121
+
122
+ @property
123
+ def dataset_scale(self) -> str | None:
124
+ if self.feature_table is not None and "dataset_id" in self.feature_table.columns:
125
+ values = self.feature_table["dataset_id"].dropna().unique()
126
+ if len(values):
127
+ return str(values[0])
128
+ return None
129
+
130
+ @property
131
+ def model_type(self) -> str | None:
132
+ value = self.manifest.get("model_type")
133
+ if isinstance(value, str):
134
+ return value
135
+ training_metadata = self.manifest.get("training_metadata", {})
136
+ if isinstance(training_metadata, dict):
137
+ inner = training_metadata.get("model_type")
138
+ if isinstance(inner, str):
139
+ return inner
140
+ return type(self.model).__name__
141
+
142
+ def metadata(self) -> MetadataResponse:
143
+ metrics_summary = self.manifest.get("metrics_summary")
144
+ if not isinstance(metrics_summary, dict):
145
+ metrics_summary = {
146
+ "model": self.metrics.get("model", {}),
147
+ "governance": self.metrics.get("governance", {}),
148
+ "calibration": self.metrics.get("calibration", {}),
149
+ }
150
+ return MetadataResponse(
151
+ api_version=__version__,
152
+ model_run_id=self.model_run_id,
153
+ model_run_dir=repo_relative(self.model_run_dir) or self.model_run_dir.name,
154
+ feature_table=repo_relative(self.feature_table_path),
155
+ dataset_id=self.dataset_id,
156
+ dataset_scale=self.dataset_scale,
157
+ model_type=self.model_type,
158
+ metrics_summary=metrics_summary,
159
+ feature_count=len(self.feature_columns),
160
+ feature_columns=self.feature_columns,
161
+ supported_labels=LABELS,
162
+ base_row_lookup_enabled=bool(self.feature_lookup),
163
+ )
164
+
165
+ def build_feature_row(self, request: PredictRequest) -> tuple[dict[str, Any], list[str]]:
166
+ warnings: list[str] = []
167
+ base_row = None
168
+ if request.feature_row_id:
169
+ base_row = self.feature_lookup.get(request.feature_row_id)
170
+ if base_row is None:
171
+ warnings.append(f"feature_row_id not found in base lookup: {request.feature_row_id}")
172
+
173
+ row: dict[str, Any]
174
+ has_base_row = base_row is not None
175
+ if base_row is not None:
176
+ row = dict(base_row)
177
+ else:
178
+ row = {column: None for column in self.feature_columns}
179
+ if request.feature_row_id:
180
+ row["feature_row_id"] = request.feature_row_id
181
+
182
+ context = normalize_mapping(request.context, self.feature_schema)
183
+ features = normalize_mapping(request.features, self.feature_schema)
184
+ changed_keys: set[str] = set()
185
+
186
+ for source_name, values in [("context", context), ("features", features)]:
187
+ for key, value in values.items():
188
+ row[key] = value
189
+ changed_keys.add(key)
190
+ if key not in self.feature_columns and key not in {"feature_row_id"}:
191
+ warnings.append(f"{source_name} field is not a model feature and was kept only as metadata: {key}")
192
+
193
+ for column in self.feature_columns:
194
+ row.setdefault(column, None)
195
+
196
+ row, completion_warnings = complete_features(
197
+ row,
198
+ changed_keys,
199
+ has_base_row=has_base_row,
200
+ derive=request.derive,
201
+ )
202
+ warnings.extend(completion_warnings)
203
+
204
+ if not has_base_row:
205
+ null_count = sum(1 for column in self.feature_columns if row.get(column) is None)
206
+ if null_count > len(self.feature_columns) // 3:
207
+ warnings.append(
208
+ f"no base row supplied; {null_count} of {len(self.feature_columns)} model features are null"
209
+ )
210
+
211
+ return row, warnings
212
+
213
+ def predict(self, request: PredictRequest) -> PredictResponse:
214
+ row, warnings = self.build_feature_row(request)
215
+ record = {
216
+ column: (np.nan if row.get(column) is None else row.get(column))
217
+ for column in sorted(set(row) | set(self.feature_columns))
218
+ }
219
+ frame = pd.DataFrame([record])
220
+ model_frame = model_input_frame(frame, self.feature_columns)
221
+ transformed = self.preprocessor.transform(model_frame)
222
+ raw_probabilities = probability_frame(self.model, transformed)
223
+ governance = add_governance_outputs(frame, raw_probabilities)
224
+ raw = raw_probabilities.iloc[0]
225
+ post = governance.iloc[0]
226
+ probabilities = [float(post[f"p_label_{label}"]) for label in LABELS]
227
+ completed_features = {
228
+ column: jsonable(row.get(column))
229
+ for column in self.feature_columns
230
+ if request.return_completed_features
231
+ }
232
+ return PredictResponse(
233
+ model_run_id=self.model_run_id,
234
+ feature_row_id=request.feature_row_id or jsonable(row.get("feature_row_id")),
235
+ predicted_label=int(post["predicted_label"]),
236
+ p_large_training=float(post["p_large_training"]),
237
+ severity_score=float(post["severity_score"]),
238
+ negative_certification_confidence=float(post["negative_certification_confidence"]),
239
+ integrity_warning=bool(post["integrity_warning"]),
240
+ capacity_possible=bool(post["capacity_possible"]),
241
+ min_critical_coverage=float(post["min_critical_coverage"]),
242
+ probabilities=probabilities,
243
+ probability_by_label={str(label): float(post[f"p_label_{label}"]) for label in LABELS},
244
+ raw_probability_by_label={str(label): float(raw[f"p_label_{label}"]) for label in LABELS},
245
+ top_evidence=split_semicolon(post["top_evidence"]),
246
+ critical_missing_layers=split_semicolon(post["critical_missing_layers"]),
247
+ input_warnings=warnings,
248
+ completed_features=completed_features,
249
+ )
src/datacenter_verification_api/requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Python >= 3.10
2
+ fastapi
3
+ uvicorn[standard]
4
+ pandas
5
+ numpy
6
+ scikit-learn
7
+ joblib
src/datacenter_verification_api/schemas.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Pydantic schemas for the live datacenter verification inference API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+
10
+ class PredictRequest(BaseModel):
11
+ """One-row prediction request.
12
+
13
+ The feature dictionary intentionally accepts arbitrary keys because the
14
+ public demo exposes only a subset of the model columns and future UI
15
+ controls may add new fields before the API schema changes.
16
+ """
17
+
18
+ model_config = ConfigDict(extra="allow")
19
+
20
+ feature_row_id: str | None = None
21
+ features: dict[str, Any] = Field(default_factory=dict)
22
+ context: dict[str, Any] = Field(default_factory=dict)
23
+ derive: bool = True
24
+ return_completed_features: bool = False
25
+
26
+
27
+ class BatchPredictRequest(BaseModel):
28
+ requests: list[PredictRequest]
29
+
30
+
31
+ class HealthResponse(BaseModel):
32
+ status: str
33
+ model_loaded: bool
34
+ api_version: str
35
+ model_run_id: str | None = None
36
+ dataset_id: str | None = None
37
+ feature_count: int = 0
38
+ base_row_lookup_enabled: bool = False
39
+ error: str | None = None
40
+
41
+
42
+ class MetadataResponse(BaseModel):
43
+ api_version: str
44
+ model_run_id: str
45
+ model_run_dir: str
46
+ feature_table: str | None = None
47
+ dataset_id: str | None = None
48
+ dataset_scale: str | None = None
49
+ model_type: str | None = None
50
+ metrics_summary: dict[str, Any] = Field(default_factory=dict)
51
+ feature_count: int
52
+ feature_columns: list[str]
53
+ supported_labels: list[int]
54
+ base_row_lookup_enabled: bool
55
+
56
+
57
+ class PredictResponse(BaseModel):
58
+ mode: str = "live_model_inference"
59
+ model_run_id: str
60
+ feature_row_id: str | None = None
61
+ predicted_label: int
62
+ p_large_training: float
63
+ severity_score: float
64
+ negative_certification_confidence: float
65
+ integrity_warning: bool
66
+ capacity_possible: bool
67
+ min_critical_coverage: float
68
+ probabilities: list[float]
69
+ probability_by_label: dict[str, float]
70
+ raw_probability_by_label: dict[str, float] = Field(default_factory=dict)
71
+ top_evidence: list[str]
72
+ critical_missing_layers: list[str]
73
+ input_warnings: list[str] = Field(default_factory=list)
74
+ completed_features: dict[str, Any] = Field(default_factory=dict)
75
+
76
+
77
+ class BatchPredictResponse(BaseModel):
78
+ mode: str = "live_model_inference_batch"
79
+ model_run_id: str
80
+ predictions: list[PredictResponse]
src/datacenter_verification_api/smoke_test.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Smoke checks for the live sklearn inference service.
2
+
3
+ Run from the repository root:
4
+
5
+ python -m src.datacenter_verification_api.smoke_test
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ from pathlib import Path
12
+
13
+ import pandas as pd
14
+
15
+ from src.datacenter_verification_api.model_service import DEFAULT_MODEL_RUN_DIR, REPO_ROOT, ModelService, resolve_repo_path
16
+ from src.datacenter_verification_api.schemas import PredictRequest
17
+ from src.datacenter_verification_modeling.common import LABELS
18
+
19
+
20
+ def parse_args() -> argparse.Namespace:
21
+ parser = argparse.ArgumentParser(description=__doc__)
22
+ parser.add_argument("--model-run", type=Path, default=DEFAULT_MODEL_RUN_DIR)
23
+ parser.add_argument("--tolerance", type=float, default=1e-8)
24
+ return parser.parse_args()
25
+
26
+
27
+ def selected_prediction_rows(predictions: pd.DataFrame) -> pd.DataFrame:
28
+ rows = []
29
+ for label in LABELS:
30
+ part = predictions[predictions["predicted_label"].astype(int) == label]
31
+ if len(part):
32
+ rows.append(part.iloc[0])
33
+ if len(rows) < len(LABELS):
34
+ for _, row in predictions.head(len(LABELS) - len(rows)).iterrows():
35
+ rows.append(row)
36
+ return pd.DataFrame(rows).drop_duplicates("feature_row_id")
37
+
38
+
39
+ def main() -> int:
40
+ args = parse_args()
41
+ model_run_dir = resolve_repo_path(args.model_run, DEFAULT_MODEL_RUN_DIR)
42
+ service = ModelService(model_run_dir=model_run_dir, feature_table_path=REPO_ROOT / "data/synthetic_v1/features/window_features_all.csv")
43
+ predictions_path = model_run_dir / "predictions_all.csv"
44
+ predictions = pd.read_csv(predictions_path)
45
+ sample = selected_prediction_rows(predictions)
46
+
47
+ checked = 0
48
+ for _, expected in sample.iterrows():
49
+ row_id = str(expected["feature_row_id"])
50
+ actual = service.predict(PredictRequest(feature_row_id=row_id))
51
+ if actual.predicted_label != int(expected["predicted_label"]):
52
+ raise AssertionError(
53
+ f"{row_id}: predicted label mismatch {actual.predicted_label} != {int(expected['predicted_label'])}"
54
+ )
55
+ for label in LABELS:
56
+ expected_probability = float(expected[f"p_label_{label}"])
57
+ actual_probability = actual.probability_by_label[str(label)]
58
+ delta = abs(actual_probability - expected_probability)
59
+ if delta > args.tolerance:
60
+ raise AssertionError(
61
+ f"{row_id}: p_label_{label} mismatch {actual_probability} != {expected_probability} "
62
+ f"(delta={delta})"
63
+ )
64
+ checked += 1
65
+
66
+ edit_row_id = str(sample.iloc[-1]["feature_row_id"])
67
+ edited = service.predict(
68
+ PredictRequest(
69
+ feature_row_id=edit_row_id,
70
+ features={
71
+ "o2_max_concurrent_normalized_gpus": 1024,
72
+ "o2_allocation_duration_hours": 48,
73
+ "o4_gpu_util_p95": 82,
74
+ },
75
+ context={"scope_type": "topology_domain", "window_length_seconds": 3600},
76
+ derive=True,
77
+ return_completed_features=True,
78
+ )
79
+ )
80
+ if len(edited.probabilities) != len(LABELS):
81
+ raise AssertionError("edited prediction did not return five label probabilities")
82
+ if abs(sum(edited.probabilities) - 1.0) > 1e-8:
83
+ raise AssertionError("edited prediction probabilities do not sum to 1")
84
+ if not edited.completed_features:
85
+ raise AssertionError("edited prediction did not return completed_features")
86
+
87
+ print(f"PASS live inference smoke test: matched {checked} exported rows from {predictions_path}")
88
+ print(
89
+ "PASS edited prediction schema: "
90
+ f"row={edit_row_id} label={edited.predicted_label} p_large={edited.p_large_training:.6f}"
91
+ )
92
+ return 0
93
+
94
+
95
+ if __name__ == "__main__":
96
+ raise SystemExit(main())
src/datacenter_verification_modeling/README.md ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # datacenter verification modeling
2
+
3
+ this package trains and evaluates the first public baseline model for the synthetic v0 datacenter training-run verification dataset.
4
+
5
+ the training unit is one row from:
6
+
7
+ ```text
8
+ data/synthetic_v0/features/window_features_all.csv
9
+ ```
10
+
11
+ the model does not read raw telemetry samples, events, or snapshots directly
12
+
13
+ ## outputs
14
+
15
+ the default model run is written to:
16
+
17
+ ```text
18
+ data/model_runs/synthetic_v0_baseline/
19
+ ```
20
+
21
+ it contains the fitted calibrated model, preprocessing pipeline, split manifest, feature metadata, predictions, metrics, calibration diagnostics, feature importance, and an evidence audit sample
22
+
23
+ ## leakage controls
24
+
25
+ rows are split by `episode_id`, not randomly. This is required because the same latent episode appears in multiple adjacent windows. The default split is scenario-stratified at the episode level with seed `20260510`:
26
+
27
+ ```text
28
+ train: 60%
29
+ validation/calibration: 20%
30
+ test: 20%
31
+ ```
32
+
33
+ the supervised model excludes identifiers, direct labels, site id, episode id, raw manifest hashes, and synthetic-only audit columns such as
34
+ `latent_workload_class` and `synthetic_evidence_profile`
35
+
36
+ ## model
37
+
38
+ the supervised baseline is:
39
+
40
+ ```text
41
+ SimpleImputer + OneHotEncoder preprocessing
42
+ HistGradientBoostingClassifier
43
+ CalibratedClassifierCV on the validation split
44
+ ```
45
+
46
+ the package also includes `rule_baseline.py`, a deterministic evidence-rule baseline that encodes broad study logic:
47
+
48
+ - capacity below threshold with strong coverage is negative evidence
49
+ - capacity alone does not prove training
50
+ - missing data is not zero activity
51
+ - integrity anomalies are warnings, not positive proof
52
+ - labels 3 and 4 require coherent multi-layer evidence
53
+
54
+ ## commands
55
+
56
+ train, evaluate, and generate all default artifacts:
57
+
58
+ ```bash
59
+ python src/datacenter_verification_modeling/train_model.py \
60
+ --features data/synthetic_v0/features/window_features_all.csv \
61
+ --output data/model_runs/synthetic_v0_baseline \
62
+ --seed 20260510
63
+ ```
64
+
65
+ Evaluate an existing run:
66
+
67
+ ```bash
68
+ python src/datacenter_verification_modeling/evaluate_model.py \
69
+ --model-run data/model_runs/synthetic_v0_baseline \
70
+ --features data/synthetic_v0/features/window_features_all.csv
71
+ ```
72
+
73
+ Generate predictions from an existing run:
74
+
75
+ ```bash
76
+ python src/datacenter_verification_modeling/predict.py \
77
+ --model-run data/model_runs/synthetic_v0_baseline \
78
+ --features data/synthetic_v0/features/window_features_all.csv \
79
+ --output data/model_runs/synthetic_v0_baseline/predictions_all.csv
80
+ ```
81
+
82
+ Prepare split and feature metadata without training:
83
+
84
+ ```bash
85
+ python src/datacenter_verification_modeling/prepare_features.py \
86
+ --features data/synthetic_v0/features/window_features_all.csv \
87
+ --output data/model_runs/synthetic_v0_baseline \
88
+ --seed 20260510
89
+ ```
90
+
91
+ ## governance outputs
92
+
93
+ prediction files include:
94
+
95
+ - `p_label_0` through `p_label_4`
96
+ - `raw_p_label_0` through `raw_p_label_4`
97
+ - `p_large_training`
98
+ - `severity_score`
99
+ - `capacity_possible`
100
+ - `negative_certification_confidence`
101
+ - `integrity_warning`
102
+ - `critical_missing_layers`
103
+ - `top_evidence`
104
+
105
+ the `p_label_*` columns are post-processed with the capacity gate; the `raw_p_label_*` columns preserve the calibrated model probabilities before that
106
+ gate
107
+
108
+ ## limitations
109
+
110
+ This is a synthetic v0 prototype ! Metrics are useful for testing the pipeline, not for deployment claims. Real datacenter use would require real telemetry, controlled drills, operational calibration, and independent review
111
+
src/datacenter_verification_modeling/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ """Modeling pipeline for datacenter training-run verification prototypes."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.1.0"
6
+
src/datacenter_verification_modeling/common.py ADDED
@@ -0,0 +1,611 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared utilities for synthetic datacenter verification modeling baselines."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import math
8
+ from datetime import datetime, timezone
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import numpy as np
13
+ import pandas as pd
14
+ from sklearn.compose import ColumnTransformer
15
+ from sklearn.impute import SimpleImputer
16
+ from sklearn.pipeline import Pipeline
17
+ from sklearn.preprocessing import OneHotEncoder
18
+
19
+
20
+ DEFAULT_SEED = 20260510
21
+ LABELS = [0, 1, 2, 3, 4]
22
+ SPLITS = ["train", "validation", "test"]
23
+ PROB_COLUMNS = [f"p_label_{label}" for label in LABELS]
24
+ RAW_PROB_COLUMNS = [f"raw_p_label_{label}" for label in LABELS]
25
+
26
+ BASE_EXCLUDED_COLUMNS = [
27
+ "feature_row_id",
28
+ "dataset_id",
29
+ "seed",
30
+ "site_id",
31
+ "scope_id_hash",
32
+ "window_start",
33
+ "window_end",
34
+ "episode_id",
35
+ "label_0_to_4",
36
+ "label_confidence",
37
+ "label_reason",
38
+ "label_source",
39
+ "raw_input_manifest_hash",
40
+ "latent_workload_class",
41
+ "scenario_family",
42
+ "scenario_variant",
43
+ "evidence_recipe_id",
44
+ "counterfactual_group_id",
45
+ "synthetic_counterfactual_role",
46
+ "data_quality_regime",
47
+ "privacy_tier",
48
+ "collector_profile",
49
+ "topology_class",
50
+ "temporal_phase",
51
+ "synthetic_hard_case_tags",
52
+ "synthetic_evidence_profile",
53
+ "capacity_evidence_only",
54
+ "integrity_evidence_only",
55
+ "physical_evidence_only",
56
+ ]
57
+
58
+ VERSION_COLUMNS_EXCLUDED = [
59
+ "feature_pipeline_version",
60
+ "policy_threshold_version",
61
+ "hardware_normalization_version",
62
+ ]
63
+
64
+ CRITICAL_COVERAGE_COLUMNS = [
65
+ "o1_coverage_fraction",
66
+ "o2_coverage_fraction",
67
+ "o4_coverage_fraction",
68
+ "o7_coverage_fraction",
69
+ "o8_coverage_fraction",
70
+ "o14_coverage_fraction",
71
+ ]
72
+
73
+ CRITICAL_MISSING_REASON_COLUMNS = {
74
+ "o1_coverage_fraction": "o1_missing_reason",
75
+ "o2_coverage_fraction": "o2_missing_reason",
76
+ "o4_coverage_fraction": "o4_missing_reason",
77
+ "o7_coverage_fraction": "o7_missing_reason",
78
+ "o8_coverage_fraction": "o8_missing_reason",
79
+ "o14_coverage_fraction": "o14_missing_reason",
80
+ }
81
+
82
+ SELECTED_AUDIT_FEATURES = [
83
+ "policy_compute_ratio",
84
+ "o2_max_concurrent_normalized_gpus",
85
+ "o2_allocation_duration_hours",
86
+ "o2_gpu_hours_policy_ratio",
87
+ "o4_gpu_util_p95",
88
+ "o4_gpu_util_duty_gt_70",
89
+ "o7_synchronized_fabric_footprint",
90
+ "o7_collective_periodicity_score",
91
+ "o8_rack_power_fraction_p95",
92
+ "o10_runtime_framework_class",
93
+ "o11_checkpoint_periodicity_score",
94
+ "o12_signed_ml_logs_present",
95
+ "o12_declared_parameter_count_b",
96
+ "o14_min_critical_coverage",
97
+ "o14_gap_fraction_critical",
98
+ "o13_confidential_compute_mode_fraction",
99
+ "o2_elastic_resize_count",
100
+ "o2_preemption_restart_count",
101
+ "o2_account_linkage_confidence",
102
+ "o4_hbm_pressure_duration_fraction",
103
+ "o4_power_cap_active_fraction",
104
+ "o7_account_flow_linkage_confidence",
105
+ "o10_runtime_metadata_confidence",
106
+ "o11_artifact_write_pattern_score",
107
+ "o11_dataloader_read_pattern_score",
108
+ "o12_log_delivery_delay_hours",
109
+ "o12_log_completeness_fraction",
110
+ "o4_missing_reason",
111
+ "o7_missing_reason",
112
+ "o12_missing_reason",
113
+ ]
114
+
115
+
116
+ def utc_now_iso() -> str:
117
+ return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
118
+
119
+
120
+ def ensure_dir(path: Path) -> Path:
121
+ path.mkdir(parents=True, exist_ok=True)
122
+ return path
123
+
124
+
125
+ def to_jsonable(value: Any) -> Any:
126
+ if isinstance(value, (np.integer,)):
127
+ return int(value)
128
+ if isinstance(value, (np.floating,)):
129
+ if math.isnan(float(value)):
130
+ return None
131
+ return float(value)
132
+ if isinstance(value, (np.bool_,)):
133
+ return bool(value)
134
+ if isinstance(value, np.ndarray):
135
+ return [to_jsonable(item) for item in value.tolist()]
136
+ if isinstance(value, pd.Series):
137
+ return [to_jsonable(item) for item in value.tolist()]
138
+ if isinstance(value, pd.Timestamp):
139
+ return value.isoformat()
140
+ if value is pd.NA:
141
+ return None
142
+ return value
143
+
144
+
145
+ def write_json(path: Path, payload: Any) -> None:
146
+ path.write_text(json.dumps(payload, indent=2, sort_keys=True, default=to_jsonable) + "\n", encoding="utf-8")
147
+
148
+
149
+ def read_json(path: Path) -> Any:
150
+ return json.loads(path.read_text(encoding="utf-8"))
151
+
152
+
153
+ def sha256_file(path: Path) -> str:
154
+ digest = hashlib.sha256()
155
+ with path.open("rb") as handle:
156
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
157
+ digest.update(chunk)
158
+ return f"sha256:{digest.hexdigest()}"
159
+
160
+
161
+ def as_bool(value: Any, default: bool = False) -> bool:
162
+ if value is None:
163
+ return default
164
+ try:
165
+ if pd.isna(value):
166
+ return default
167
+ except TypeError:
168
+ pass
169
+ if isinstance(value, (bool, np.bool_)):
170
+ return bool(value)
171
+ text = str(value).strip().lower()
172
+ if text in {"true", "t", "1", "yes", "y"}:
173
+ return True
174
+ if text in {"false", "f", "0", "no", "n"}:
175
+ return False
176
+ return default
177
+
178
+
179
+ def as_float(value: Any, default: float = 0.0) -> float:
180
+ if value is None:
181
+ return default
182
+ try:
183
+ if pd.isna(value):
184
+ return default
185
+ except TypeError:
186
+ pass
187
+ try:
188
+ return float(value)
189
+ except (TypeError, ValueError):
190
+ return default
191
+
192
+
193
+ def row_float(row: pd.Series, column: str, default: float = 0.0) -> float:
194
+ return as_float(row[column], default) if column in row else default
195
+
196
+
197
+ def row_bool(row: pd.Series, column: str, default: bool = False) -> bool:
198
+ return as_bool(row[column], default) if column in row else default
199
+
200
+
201
+ def load_feature_table(path: Path) -> pd.DataFrame:
202
+ df = pd.read_csv(path)
203
+ required = {
204
+ "feature_row_id",
205
+ "episode_id",
206
+ "latent_workload_class",
207
+ "label_0_to_4",
208
+ "capacity_possible",
209
+ }
210
+ missing = sorted(required - set(df.columns))
211
+ if missing:
212
+ raise ValueError(f"feature table missing required columns: {missing}")
213
+ df["label_0_to_4"] = df["label_0_to_4"].astype(int)
214
+ return df
215
+
216
+
217
+ def derive_dataset_dir(features_path: Path) -> Path:
218
+ path = features_path.resolve()
219
+ if path.parent.name == "features":
220
+ return path.parent.parent
221
+ return path.parent
222
+
223
+
224
+ def split_summary(df: pd.DataFrame) -> dict[str, Any]:
225
+ summary: dict[str, Any] = {}
226
+ for split in SPLITS:
227
+ part = df[df["split"] == split]
228
+ summary[split] = {
229
+ "rows": int(len(part)),
230
+ "episodes": int(part["episode_id"].nunique()),
231
+ "label_distribution": {
232
+ str(label): int(count)
233
+ for label, count in part["label_0_to_4"].value_counts().sort_index().items()
234
+ },
235
+ "scenario_distribution": {
236
+ str(name): int(count)
237
+ for name, count in part["latent_workload_class"].value_counts().sort_index().items()
238
+ },
239
+ }
240
+ return summary
241
+
242
+
243
+ def make_episode_split(df: pd.DataFrame, seed: int = DEFAULT_SEED) -> tuple[pd.DataFrame, dict[str, Any]]:
244
+ """Create a deterministic scenario-stratified grouped split by episode_id."""
245
+ episode_df = (
246
+ df.groupby("episode_id", as_index=False)
247
+ .agg(
248
+ latent_workload_class=("latent_workload_class", "first"),
249
+ label_mode=("label_0_to_4", lambda values: int(values.mode().iloc[0])),
250
+ label_min=("label_0_to_4", "min"),
251
+ label_max=("label_0_to_4", "max"),
252
+ row_count=("feature_row_id", "count"),
253
+ site_count=("site_id", "nunique") if "site_id" in df.columns else ("episode_id", "count"),
254
+ )
255
+ .sort_values(["latent_workload_class", "episode_id"])
256
+ .reset_index(drop=True)
257
+ )
258
+
259
+ rng = np.random.default_rng(seed)
260
+ assignments: dict[str, str] = {}
261
+ scenario_allocations: dict[str, dict[str, int]] = {}
262
+ for scenario, scenario_rows in episode_df.groupby("latent_workload_class", sort=True):
263
+ episode_ids = scenario_rows["episode_id"].to_numpy().copy()
264
+ rng.shuffle(episode_ids)
265
+ count = len(episode_ids)
266
+ if count >= 3:
267
+ validation_count = max(1, round(0.20 * count))
268
+ test_count = max(1, round(0.20 * count))
269
+ train_count = count - validation_count - test_count
270
+ if train_count < 1:
271
+ train_count = 1
272
+ if validation_count > test_count:
273
+ validation_count -= 1
274
+ else:
275
+ test_count -= 1
276
+ elif count == 2:
277
+ train_count, validation_count, test_count = 1, 1, 0
278
+ else:
279
+ train_count, validation_count, test_count = 1, 0, 0
280
+
281
+ for episode_id in episode_ids[:train_count]:
282
+ assignments[str(episode_id)] = "train"
283
+ for episode_id in episode_ids[train_count : train_count + validation_count]:
284
+ assignments[str(episode_id)] = "validation"
285
+ for episode_id in episode_ids[train_count + validation_count :]:
286
+ assignments[str(episode_id)] = "test"
287
+ scenario_allocations[str(scenario)] = {
288
+ "episodes": int(count),
289
+ "train": int(train_count),
290
+ "validation": int(validation_count),
291
+ "test": int(test_count),
292
+ }
293
+
294
+ split_df = df.copy()
295
+ split_df["split"] = split_df["episode_id"].map(assignments)
296
+ if split_df["split"].isna().any():
297
+ raise ValueError("episode split assignment failed for some rows")
298
+
299
+ episode_assignments = []
300
+ split_lookup = dict(zip(episode_df["episode_id"], episode_df.index, strict=False))
301
+ for episode_id, split in sorted(assignments.items()):
302
+ row = episode_df.iloc[split_lookup[episode_id]]
303
+ episode_assignments.append(
304
+ {
305
+ "episode_id": episode_id,
306
+ "split": split,
307
+ "latent_workload_class": row["latent_workload_class"],
308
+ "label_mode": int(row["label_mode"]),
309
+ "label_min": int(row["label_min"]),
310
+ "label_max": int(row["label_max"]),
311
+ "row_count": int(row["row_count"]),
312
+ }
313
+ )
314
+
315
+ manifest = {
316
+ "seed": int(seed),
317
+ "method": "scenario_stratified_grouped_by_episode_id",
318
+ "split_fractions_requested": {"train": 0.60, "validation": 0.20, "test": 0.20},
319
+ "leakage_prevention": "All rows from the same episode_id are assigned to exactly one split.",
320
+ "scenario_allocations": scenario_allocations,
321
+ "summary": split_summary(split_df),
322
+ "episode_assignments": episode_assignments,
323
+ }
324
+ return split_df, manifest
325
+
326
+
327
+ def apply_split_manifest(df: pd.DataFrame, split_manifest: dict[str, Any]) -> pd.DataFrame:
328
+ assignments = {
329
+ item["episode_id"]: item["split"]
330
+ for item in split_manifest.get("episode_assignments", [])
331
+ if "episode_id" in item and "split" in item
332
+ }
333
+ out = df.copy()
334
+ out["split"] = out["episode_id"].map(assignments).fillna("unassigned")
335
+ return out
336
+
337
+
338
+ def determine_feature_columns(df: pd.DataFrame) -> tuple[list[str], dict[str, Any]]:
339
+ requested_exclusions = BASE_EXCLUDED_COLUMNS + VERSION_COLUMNS_EXCLUDED
340
+ present_exclusions = [column for column in requested_exclusions if column in df.columns]
341
+ missing_requested = [column for column in requested_exclusions if column not in df.columns]
342
+ feature_columns = [column for column in df.columns if column not in set(present_exclusions + ["split"])]
343
+ feature_columns = [column for column in feature_columns if not column.startswith("raw_")]
344
+ metadata = {
345
+ "requested_excluded_columns": requested_exclusions,
346
+ "present_excluded_columns": present_exclusions,
347
+ "missing_requested_excluded_columns": missing_requested,
348
+ "notes": [
349
+ "Identifier, label, direct leakage, site, and synthetic-only audit columns are excluded.",
350
+ "scope_type and window_length_seconds are retained because they are valid deployment-time context.",
351
+ "Constant version metadata columns are excluded from the baseline.",
352
+ ],
353
+ }
354
+ return feature_columns, metadata
355
+
356
+
357
+ def categorize_feature_columns(df: pd.DataFrame, feature_columns: list[str]) -> dict[str, list[str]]:
358
+ numeric_columns: list[str] = []
359
+ categorical_columns: list[str] = []
360
+ for column in feature_columns:
361
+ dtype = df[column].dtype
362
+ if pd.api.types.is_numeric_dtype(dtype) or pd.api.types.is_bool_dtype(dtype):
363
+ numeric_columns.append(column)
364
+ else:
365
+ categorical_columns.append(column)
366
+ return {"numeric": numeric_columns, "categorical": categorical_columns}
367
+
368
+
369
+ def make_preprocessor(df: pd.DataFrame, feature_columns: list[str]) -> ColumnTransformer:
370
+ types = categorize_feature_columns(df, feature_columns)
371
+ transformers: list[tuple[str, Pipeline, list[str]]] = []
372
+ if types["numeric"]:
373
+ transformers.append(
374
+ (
375
+ "numeric",
376
+ Pipeline(
377
+ steps=[
378
+ ("imputer", SimpleImputer(strategy="median", add_indicator=True)),
379
+ ]
380
+ ),
381
+ types["numeric"],
382
+ )
383
+ )
384
+ if types["categorical"]:
385
+ try:
386
+ encoder = OneHotEncoder(handle_unknown="ignore", sparse_output=False, dtype=np.float64)
387
+ except TypeError: # pragma: no cover - compatibility for older scikit-learn
388
+ encoder = OneHotEncoder(handle_unknown="ignore", sparse=False, dtype=np.float64)
389
+ transformers.append(
390
+ (
391
+ "categorical",
392
+ Pipeline(
393
+ steps=[
394
+ ("imputer", SimpleImputer(strategy="constant", fill_value="__missing__")),
395
+ ("onehot", encoder),
396
+ ]
397
+ ),
398
+ types["categorical"],
399
+ )
400
+ )
401
+ return ColumnTransformer(transformers=transformers, remainder="drop", verbose_feature_names_out=True)
402
+
403
+
404
+ def model_input_frame(df: pd.DataFrame, feature_columns: list[str]) -> pd.DataFrame:
405
+ missing = [column for column in feature_columns if column not in df.columns]
406
+ if missing:
407
+ raise ValueError(f"feature table missing model feature columns: {missing}")
408
+ return df.loc[:, feature_columns].copy()
409
+
410
+
411
+ def probability_frame(model: Any, transformed_features: Any, prefix: str = "p_label") -> pd.DataFrame:
412
+ raw = model.predict_proba(transformed_features)
413
+ classes = [int(label) for label in model.classes_]
414
+ data = np.zeros((raw.shape[0], len(LABELS)), dtype=float)
415
+ for source_index, label in enumerate(classes):
416
+ if label in LABELS:
417
+ data[:, LABELS.index(label)] = raw[:, source_index]
418
+ data = normalize_probability_array(data)
419
+ return pd.DataFrame(data, columns=[f"{prefix}_{label}" for label in LABELS])
420
+
421
+
422
+ def normalize_probability_array(probs: np.ndarray) -> np.ndarray:
423
+ probs = np.clip(np.asarray(probs, dtype=float), 0.0, 1.0)
424
+ row_sums = probs.sum(axis=1)
425
+ empty = row_sums <= 0
426
+ if np.any(empty):
427
+ probs[empty, :] = 1.0 / len(LABELS)
428
+ row_sums = probs.sum(axis=1)
429
+ return probs / row_sums[:, None]
430
+
431
+
432
+ def minimum_critical_coverage(df: pd.DataFrame) -> pd.Series:
433
+ coverage_values = []
434
+ for column in CRITICAL_COVERAGE_COLUMNS:
435
+ if column in df.columns:
436
+ coverage_values.append(pd.to_numeric(df[column], errors="coerce").fillna(0.0).clip(0.0, 1.0))
437
+ else:
438
+ coverage_values.append(pd.Series(np.zeros(len(df)), index=df.index))
439
+ return pd.concat(coverage_values, axis=1).min(axis=1)
440
+
441
+
442
+ def integrity_warning_series(df: pd.DataFrame) -> pd.Series:
443
+ warnings = pd.Series(False, index=df.index)
444
+ checks = {
445
+ "o14_gap_fraction_critical": (0.05, "gt"),
446
+ "o14_min_critical_coverage": (0.80, "lt"),
447
+ "o13_attestation_valid_fraction": (0.90, "lt"),
448
+ "o13_confidential_compute_mode_fraction": (0.50, "gt"),
449
+ "o14_counter_reset_count": (0.0, "gt"),
450
+ }
451
+ for column, (threshold, direction) in checks.items():
452
+ if column not in df.columns:
453
+ continue
454
+ values = pd.to_numeric(df[column], errors="coerce").fillna(0.0)
455
+ if direction == "gt":
456
+ warnings |= values > threshold
457
+ else:
458
+ warnings |= values < threshold
459
+ if "o15_unapproved_physical_change_near_window" in df.columns:
460
+ warnings |= df["o15_unapproved_physical_change_near_window"].map(as_bool)
461
+ return warnings.astype(bool)
462
+
463
+
464
+ def apply_capacity_gate(df: pd.DataFrame, probabilities: pd.DataFrame, high_label_cap: float = 0.02) -> pd.DataFrame:
465
+ probs = probabilities.loc[:, PROB_COLUMNS].to_numpy(copy=True)
466
+ if "capacity_possible" not in df.columns:
467
+ return pd.DataFrame(normalize_probability_array(probs), columns=PROB_COLUMNS, index=probabilities.index)
468
+ capacity_possible = df["capacity_possible"].map(as_bool).to_numpy()
469
+ if "o17_external_capacity_conflict_score" in df.columns:
470
+ external_conflict = pd.to_numeric(df["o17_external_capacity_conflict_score"], errors="coerce").fillna(0.0).to_numpy()
471
+ else:
472
+ external_conflict = np.zeros(len(df))
473
+ gate_mask = (~capacity_possible) & (external_conflict < 0.5)
474
+ for row_index in np.where(gate_mask)[0]:
475
+ high = probs[row_index, 2:5].sum()
476
+ capped_high = min(float(high), high_label_cap)
477
+ if high > 0:
478
+ probs[row_index, 2:5] *= capped_high / high
479
+ low_target = 1.0 - capped_high
480
+ low = probs[row_index, 0:2].sum()
481
+ if low > 0:
482
+ probs[row_index, 0:2] *= low_target / low
483
+ else:
484
+ probs[row_index, 0] = low_target
485
+ probs[row_index, 1] = 0.0
486
+ return pd.DataFrame(normalize_probability_array(probs), columns=PROB_COLUMNS, index=probabilities.index)
487
+
488
+
489
+ def critical_missing_layers_for_row(row: pd.Series) -> str:
490
+ layers: list[str] = []
491
+ for coverage_column, missing_column in CRITICAL_MISSING_REASON_COLUMNS.items():
492
+ coverage = row_float(row, coverage_column, 0.0)
493
+ reason = str(row[missing_column]) if missing_column in row and not pd.isna(row[missing_column]) else "unknown"
494
+ if coverage < 0.80 or reason not in {"observed", ""}:
495
+ observable = coverage_column.split("_", 1)[0].upper()
496
+ layers.append(f"{observable}:{reason}:coverage={coverage:.2f}")
497
+ return "; ".join(layers)
498
+
499
+
500
+ def top_evidence_for_row(row: pd.Series) -> str:
501
+ evidence: list[str] = []
502
+ capacity = row_bool(row, "capacity_possible")
503
+ external_conflict = row_float(row, "o17_external_capacity_conflict_score", 0.0)
504
+ allocation_gpus = row_float(row, "o2_max_concurrent_normalized_gpus")
505
+ allocation_hours = row_float(row, "o2_allocation_duration_hours")
506
+ gpu_hours_ratio = row_float(row, "o2_gpu_hours_policy_ratio")
507
+ gpu_util = row_float(row, "o4_gpu_util_p95")
508
+ tensor = row_float(row, "o4_sm_tensor_active_p95")
509
+ fabric_footprint = row_float(row, "o7_synchronized_fabric_footprint")
510
+ fabric_periodicity = row_float(row, "o7_collective_periodicity_score")
511
+ rack_power = row_float(row, "o8_rack_power_fraction_p95")
512
+ checkpoint = row_float(row, "o11_checkpoint_periodicity_score")
513
+ signed_logs = row_bool(row, "o12_signed_ml_logs_present")
514
+ min_coverage = row_float(row, "o14_min_critical_coverage", 1.0)
515
+ gap_fraction = row_float(row, "o14_gap_fraction_critical")
516
+ cc_fraction = row_float(row, "o13_confidential_compute_mode_fraction")
517
+ runtime = str(row["o10_runtime_framework_class"]) if "o10_runtime_framework_class" in row else ""
518
+
519
+ if not capacity and external_conflict < 0.5:
520
+ evidence.append("capacity below policy threshold")
521
+ if external_conflict >= 0.5:
522
+ evidence.append("external capacity conflict")
523
+ if allocation_gpus >= 512 or gpu_hours_ratio >= 1.0:
524
+ evidence.append("large allocation")
525
+ elif allocation_gpus >= 128:
526
+ evidence.append("moderate allocation")
527
+ if allocation_hours >= 24:
528
+ evidence.append("long allocation duration")
529
+ if gpu_util >= 70 or tensor >= 0.60:
530
+ evidence.append("high GPU activity")
531
+ if fabric_footprint >= 512 or fabric_periodicity >= 0.60:
532
+ evidence.append("synchronized scale-out fabric")
533
+ if rack_power >= 0.60:
534
+ evidence.append("power corroboration")
535
+ if "training" in runtime or "fine_tune" in runtime:
536
+ evidence.append("training runtime metadata")
537
+ if checkpoint >= 0.55:
538
+ evidence.append("checkpoint cadence")
539
+ if signed_logs:
540
+ evidence.append("signed ML logs")
541
+ if min_coverage < 0.80 or gap_fraction > 0.05:
542
+ evidence.append("low critical coverage")
543
+ if cc_fraction > 0.50 or str(row.get("o4_missing_reason", "")) == "counter_disabled_by_cc_mode":
544
+ evidence.append("counter disabled by CC mode")
545
+ if not evidence:
546
+ evidence.append("no strong positive evidence")
547
+ return "; ".join(evidence[:8])
548
+
549
+
550
+ def add_governance_outputs(df: pd.DataFrame, raw_probabilities: pd.DataFrame) -> pd.DataFrame:
551
+ post_probs = apply_capacity_gate(df, raw_probabilities.loc[:, PROB_COLUMNS])
552
+ out = post_probs.copy()
553
+ prob_values = out.loc[:, PROB_COLUMNS].to_numpy()
554
+ predicted = np.asarray(LABELS)[np.argmax(prob_values, axis=1)]
555
+ out["predicted_label"] = predicted.astype(int)
556
+ out["p_large_training"] = out["p_label_3"] + out["p_label_4"]
557
+ out["severity_score"] = sum(label * out[f"p_label_{label}"] for label in LABELS)
558
+ min_coverage = minimum_critical_coverage(df)
559
+ out["min_critical_coverage"] = min_coverage
560
+ out["negative_certification_confidence"] = out["p_label_0"] * min_coverage
561
+ out["capacity_possible"] = df["capacity_possible"].map(as_bool) if "capacity_possible" in df.columns else False
562
+ out["integrity_warning"] = integrity_warning_series(df)
563
+ out["critical_missing_layers"] = df.apply(critical_missing_layers_for_row, axis=1)
564
+ out["top_evidence"] = df.apply(top_evidence_for_row, axis=1)
565
+ return out
566
+
567
+
568
+ def build_prediction_frame(
569
+ df: pd.DataFrame,
570
+ raw_probabilities: pd.DataFrame,
571
+ governance_probabilities: pd.DataFrame,
572
+ ) -> pd.DataFrame:
573
+ base_columns = [
574
+ "split",
575
+ "episode_id",
576
+ "feature_row_id",
577
+ "site_id",
578
+ "scope_type",
579
+ "scope_id_hash",
580
+ "window_start",
581
+ "window_end",
582
+ "window_length_seconds",
583
+ "latent_workload_class",
584
+ "scenario_family",
585
+ "scenario_variant",
586
+ "data_quality_regime",
587
+ "counterfactual_group_id",
588
+ "synthetic_hard_case_tags",
589
+ "label_0_to_4",
590
+ ]
591
+ present_base_columns = [column for column in base_columns if column in df.columns]
592
+ out = df.loc[:, present_base_columns].copy()
593
+ raw = raw_probabilities.loc[:, PROB_COLUMNS].copy()
594
+ raw.columns = RAW_PROB_COLUMNS
595
+ out = pd.concat([out.reset_index(drop=True), raw.reset_index(drop=True)], axis=1)
596
+ governance_columns = PROB_COLUMNS + [
597
+ "predicted_label",
598
+ "p_large_training",
599
+ "severity_score",
600
+ "capacity_possible",
601
+ "negative_certification_confidence",
602
+ "integrity_warning",
603
+ "critical_missing_layers",
604
+ "top_evidence",
605
+ "min_critical_coverage",
606
+ ]
607
+ out = pd.concat([out, governance_probabilities.loc[:, governance_columns].reset_index(drop=True)], axis=1)
608
+ for column in SELECTED_AUDIT_FEATURES:
609
+ if column in df.columns and column not in out.columns:
610
+ out[column] = df[column].to_numpy()
611
+ return out
src/datacenter_verification_modeling/evaluate_model.py ADDED
@@ -0,0 +1,704 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Evaluate a trained synthetic datacenter verification modeling run."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import joblib
10
+ import numpy as np
11
+ import pandas as pd
12
+ from sklearn.metrics import (
13
+ accuracy_score,
14
+ brier_score_loss,
15
+ classification_report,
16
+ confusion_matrix,
17
+ f1_score,
18
+ log_loss,
19
+ precision_recall_fscore_support,
20
+ )
21
+
22
+ try:
23
+ from .common import (
24
+ DEFAULT_SEED,
25
+ LABELS,
26
+ PROB_COLUMNS,
27
+ RAW_PROB_COLUMNS,
28
+ SELECTED_AUDIT_FEATURES,
29
+ add_governance_outputs,
30
+ apply_split_manifest,
31
+ build_prediction_frame,
32
+ load_feature_table,
33
+ minimum_critical_coverage,
34
+ model_input_frame,
35
+ probability_frame,
36
+ read_json,
37
+ sha256_file,
38
+ utc_now_iso,
39
+ write_json,
40
+ )
41
+ from .rule_baseline import predict_rule_labels
42
+ except ImportError: # pragma: no cover - direct script execution
43
+ from common import (
44
+ DEFAULT_SEED,
45
+ LABELS,
46
+ PROB_COLUMNS,
47
+ RAW_PROB_COLUMNS,
48
+ SELECTED_AUDIT_FEATURES,
49
+ add_governance_outputs,
50
+ apply_split_manifest,
51
+ build_prediction_frame,
52
+ load_feature_table,
53
+ minimum_critical_coverage,
54
+ model_input_frame,
55
+ probability_frame,
56
+ read_json,
57
+ sha256_file,
58
+ utc_now_iso,
59
+ write_json,
60
+ )
61
+ from rule_baseline import predict_rule_labels
62
+
63
+
64
+ HARD_FALSE_POSITIVE_SCENARIOS = {
65
+ "large_batch_inference",
66
+ "synthetic_data_generation",
67
+ "hpc_mpi_simulation",
68
+ "nccl_benchmark",
69
+ "hardware_burn_in",
70
+ "storage_rebuild",
71
+ "large_etl_data_movement",
72
+ "reserved_but_unused_capacity",
73
+ "maintenance_window",
74
+ "large_batch_inference",
75
+ "model_parallel_inference",
76
+ "embedding_generation",
77
+ "synthetic_data_generation_gpu_heavy",
78
+ "hpc_mpi_collective",
79
+ "nccl_extended_benchmark",
80
+ "hardware_burn_in_or_thermal_soak",
81
+ "storage_rebuild_or_replication",
82
+ "large_etl_or_data_movement",
83
+ "distributed_database_or_graph_analytics",
84
+ "reserved_but_unused_capacity",
85
+ "maintenance_with_collector_gaps",
86
+ "multi_tenant_fragmented_nontraining",
87
+ "capacity_or_integrity_only_warning",
88
+ }
89
+
90
+ TRAINING_SCENARIOS = {
91
+ "large_fine_tune",
92
+ "pretraining",
93
+ "cloud_reservation_used_for_training",
94
+ "adversarial_fragmented_training",
95
+ "underclocked_long_duration_training",
96
+ "pretraining_standard",
97
+ "large_fine_tune_standard",
98
+ "cloud_training_redacted_runtime",
99
+ "training_without_semantic_logs",
100
+ "underclocked_energy_capped_training",
101
+ "elastic_preempted_training",
102
+ "fragmented_training_linked",
103
+ "sparse_or_moe_bursty_training",
104
+ "training_with_low_fabric_high_checkpoint",
105
+ "training_with_delayed_logs",
106
+ "multi_stage_training_pipeline",
107
+ }
108
+
109
+ REQUIRED_OUTPUTS = [
110
+ "README.md",
111
+ "manifest.json",
112
+ "model.joblib",
113
+ "preprocessing.joblib",
114
+ "feature_columns.json",
115
+ "excluded_columns.json",
116
+ "split_manifest.json",
117
+ "metrics.json",
118
+ "calibration_metrics.json",
119
+ "confusion_matrix.csv",
120
+ "classification_report.json",
121
+ "predictions_test.csv",
122
+ "predictions_all.csv",
123
+ "feature_importance.csv",
124
+ "evidence_audit_sample.csv",
125
+ "validation_summary.md",
126
+ ]
127
+
128
+
129
+ def predict_for_model_run(model_run_dir: Path, features_path: Path) -> tuple[pd.DataFrame, pd.DataFrame]:
130
+ df = load_feature_table(features_path)
131
+ split_manifest_path = model_run_dir / "split_manifest.json"
132
+ if split_manifest_path.exists():
133
+ df = apply_split_manifest(df, read_json(split_manifest_path))
134
+ else:
135
+ df = df.copy()
136
+ df["split"] = "unassigned"
137
+
138
+ feature_columns = read_json(model_run_dir / "feature_columns.json")
139
+ preprocessor = joblib.load(model_run_dir / "preprocessing.joblib")
140
+ model = joblib.load(model_run_dir / "model.joblib")
141
+
142
+ model_frame = model_input_frame(df, feature_columns)
143
+ transformed = preprocessor.transform(model_frame)
144
+ raw_probabilities = probability_frame(model, transformed)
145
+ governance = add_governance_outputs(df, raw_probabilities)
146
+ predictions = build_prediction_frame(df, raw_probabilities, governance)
147
+ return df, predictions
148
+
149
+
150
+ def binary_prf(true_binary: pd.Series | np.ndarray, predicted_binary: pd.Series | np.ndarray) -> dict[str, float]:
151
+ precision, recall, f1, _ = precision_recall_fscore_support(
152
+ true_binary.astype(bool),
153
+ predicted_binary.astype(bool),
154
+ average="binary",
155
+ zero_division=0,
156
+ )
157
+ return {"precision": float(precision), "recall": float(recall), "f1": float(f1)}
158
+
159
+
160
+ def scenario_counts(frame: pd.DataFrame) -> dict[str, int]:
161
+ if len(frame) == 0:
162
+ return {}
163
+ return {str(key): int(value) for key, value in frame["latent_workload_class"].value_counts().sort_values(ascending=False).items()}
164
+
165
+
166
+ def calibration_by_label(test_predictions: pd.DataFrame) -> dict[str, Any]:
167
+ y_true = test_predictions["label_0_to_4"].astype(int).to_numpy()
168
+ out: dict[str, Any] = {}
169
+ for label in LABELS:
170
+ observed = (y_true == label).astype(int)
171
+ predicted = test_predictions[f"p_label_{label}"].to_numpy()
172
+ out[str(label)] = {
173
+ "rows": int(len(test_predictions)),
174
+ "observed_fraction": float(observed.mean()) if len(observed) else 0.0,
175
+ "mean_predicted_probability": float(predicted.mean()) if len(predicted) else 0.0,
176
+ "brier": float(brier_score_loss(observed, predicted)) if len(np.unique(observed)) > 1 else float(np.mean((predicted - observed) ** 2)),
177
+ }
178
+ return out
179
+
180
+
181
+ def reliability_bins(probabilities: np.ndarray, observed: np.ndarray, bin_count: int = 10) -> tuple[list[dict[str, Any]], float]:
182
+ bins: list[dict[str, Any]] = []
183
+ expected_calibration_error = 0.0
184
+ total = len(probabilities)
185
+ edges = np.linspace(0.0, 1.0, bin_count + 1)
186
+ for index in range(bin_count):
187
+ lower = edges[index]
188
+ upper = edges[index + 1]
189
+ if index == bin_count - 1:
190
+ mask = (probabilities >= lower) & (probabilities <= upper)
191
+ else:
192
+ mask = (probabilities >= lower) & (probabilities < upper)
193
+ count = int(mask.sum())
194
+ if count:
195
+ mean_predicted = float(probabilities[mask].mean())
196
+ observed_fraction = float(observed[mask].mean())
197
+ expected_calibration_error += (count / total) * abs(mean_predicted - observed_fraction)
198
+ else:
199
+ mean_predicted = 0.0
200
+ observed_fraction = 0.0
201
+ bins.append(
202
+ {
203
+ "bin_index": index,
204
+ "lower": float(lower),
205
+ "upper": float(upper),
206
+ "count": count,
207
+ "mean_predicted_probability": mean_predicted,
208
+ "observed_fraction": observed_fraction,
209
+ }
210
+ )
211
+ return bins, float(expected_calibration_error)
212
+
213
+
214
+ def compute_calibration_metrics(test_predictions: pd.DataFrame) -> dict[str, Any]:
215
+ y_true = test_predictions["label_0_to_4"].astype(int).to_numpy()
216
+ probabilities = test_predictions[PROB_COLUMNS].to_numpy()
217
+ large_observed = (y_true >= 3).astype(int)
218
+ large_probability = test_predictions["p_large_training"].to_numpy()
219
+ bins, ece = reliability_bins(large_probability, large_observed)
220
+ return {
221
+ "log_loss": float(log_loss(y_true, probabilities, labels=LABELS)),
222
+ "brier_large_training": float(brier_score_loss(large_observed, large_probability)),
223
+ "expected_calibration_error_large_training": ece,
224
+ "reliability_bins_large_training": bins,
225
+ "calibration_by_label": calibration_by_label(test_predictions),
226
+ }
227
+
228
+
229
+ def subgroup_metrics(test_predictions: pd.DataFrame, group_column: str) -> list[dict[str, Any]]:
230
+ if group_column not in test_predictions.columns:
231
+ return []
232
+ rows: list[dict[str, Any]] = []
233
+ for value, part in test_predictions.groupby(group_column, dropna=False):
234
+ y_true = part["label_0_to_4"].astype(int)
235
+ y_pred = part["predicted_label"].astype(int)
236
+ large_true = y_true >= 3
237
+ large_pred = part["p_large_training"] >= 0.5
238
+ prf = binary_prf(large_true.to_numpy(), large_pred.to_numpy())
239
+ rows.append(
240
+ {
241
+ "group": str(value),
242
+ "rows": int(len(part)),
243
+ "accuracy": float(accuracy_score(y_true, y_pred)),
244
+ "macro_f1": float(f1_score(y_true, y_pred, labels=LABELS, average="macro", zero_division=0)),
245
+ "large_training_precision_at_0_5": prf["precision"],
246
+ "large_training_recall_at_0_5": prf["recall"],
247
+ "large_training_false_positives": int(((~large_true) & large_pred).sum()),
248
+ "large_training_false_negatives": int((large_true & (~large_pred)).sum()),
249
+ }
250
+ )
251
+ return sorted(rows, key=lambda item: (-item["rows"], item["group"]))
252
+
253
+
254
+ def compute_metrics(test_predictions: pd.DataFrame, test_features: pd.DataFrame) -> tuple[dict[str, Any], dict[str, Any]]:
255
+ y_true = test_predictions["label_0_to_4"].astype(int)
256
+ y_pred = test_predictions["predicted_label"].astype(int)
257
+ probabilities = test_predictions[PROB_COLUMNS].to_numpy()
258
+ large_true = y_true >= 3
259
+ large_pred_by_label = y_pred >= 3
260
+ large_pred_by_threshold = test_predictions["p_large_training"] >= 0.5
261
+ large_label_prf = binary_prf(large_true.to_numpy(), large_pred_by_label.to_numpy())
262
+ large_threshold_prf = binary_prf(large_true.to_numpy(), large_pred_by_threshold.to_numpy())
263
+
264
+ y_one_hot = np.zeros_like(probabilities)
265
+ y_one_hot[np.arange(len(y_true)), y_true.to_numpy()] = 1.0
266
+
267
+ rule_labels = predict_rule_labels(test_features)
268
+ rule_large = rule_labels >= 3
269
+ rule_prf = binary_prf(large_true.to_numpy(), rule_large.to_numpy())
270
+
271
+ false_positives = test_predictions[(y_true < 3) & large_pred_by_threshold]
272
+ false_negatives = test_predictions[(y_true >= 3) & (~large_pred_by_threshold)]
273
+ high_coverage_label0 = test_predictions[
274
+ (y_true == 0) & (pd.to_numeric(test_predictions["min_critical_coverage"], errors="coerce").fillna(0.0) >= 0.95)
275
+ ]
276
+ label0_missed = high_coverage_label0[high_coverage_label0["predicted_label"] != 0]
277
+
278
+ metrics: dict[str, Any] = {
279
+ "dataset": {
280
+ "test_rows": int(len(test_predictions)),
281
+ "test_episodes": int(test_predictions["episode_id"].nunique()),
282
+ "test_label_distribution": {
283
+ str(label): int(count) for label, count in y_true.value_counts().sort_index().items()
284
+ },
285
+ },
286
+ "model": {
287
+ "accuracy": float(accuracy_score(y_true, y_pred)),
288
+ "macro_f1": float(f1_score(y_true, y_pred, labels=LABELS, average="macro", zero_division=0)),
289
+ "weighted_f1": float(f1_score(y_true, y_pred, labels=LABELS, average="weighted", zero_division=0)),
290
+ "log_loss": float(log_loss(y_true, probabilities, labels=LABELS)),
291
+ "brier_multiclass_mean": float(np.mean((probabilities - y_one_hot) ** 2)),
292
+ },
293
+ "governance": {
294
+ "label_3_4_predicted_label": large_label_prf,
295
+ "p_large_training_threshold_0_5": large_threshold_prf,
296
+ "false_positive_scenarios_at_0_5": scenario_counts(false_positives),
297
+ "false_negative_scenarios_at_0_5": scenario_counts(false_negatives),
298
+ "label_0_missed_under_high_coverage": {
299
+ "rows": int(len(high_coverage_label0)),
300
+ "missed_rows": int(len(label0_missed)),
301
+ "missed_rate": float(len(label0_missed) / len(high_coverage_label0)) if len(high_coverage_label0) else 0.0,
302
+ },
303
+ },
304
+ "rule_baseline": {
305
+ "accuracy": float(accuracy_score(y_true, rule_labels)),
306
+ "macro_f1": float(f1_score(y_true, rule_labels, labels=LABELS, average="macro", zero_division=0)),
307
+ "weighted_f1": float(f1_score(y_true, rule_labels, labels=LABELS, average="weighted", zero_division=0)),
308
+ "label_3_4_predicted_label": rule_prf,
309
+ },
310
+ "subgroup_metrics": {
311
+ column: subgroup_metrics(test_predictions, column)
312
+ for column in [
313
+ "latent_workload_class",
314
+ "scenario_family",
315
+ "data_quality_regime",
316
+ "temporal_phase",
317
+ "site_id",
318
+ "window_length_seconds",
319
+ "o4_missing_reason",
320
+ "o7_missing_reason",
321
+ "o12_missing_reason",
322
+ "capacity_possible",
323
+ "integrity_warning",
324
+ ]
325
+ },
326
+ }
327
+ calibration = compute_calibration_metrics(test_predictions)
328
+ metrics["calibration"] = {
329
+ "log_loss": calibration["log_loss"],
330
+ "brier_large_training": calibration["brier_large_training"],
331
+ "expected_calibration_error_large_training": calibration["expected_calibration_error_large_training"],
332
+ }
333
+ return metrics, calibration
334
+
335
+
336
+ def write_confusion_matrix(path: Path, test_predictions: pd.DataFrame) -> None:
337
+ y_true = test_predictions["label_0_to_4"].astype(int)
338
+ y_pred = test_predictions["predicted_label"].astype(int)
339
+ matrix = confusion_matrix(y_true, y_pred, labels=LABELS)
340
+ frame = pd.DataFrame(matrix, index=[f"true_{label}" for label in LABELS], columns=[f"pred_{label}" for label in LABELS])
341
+ frame.to_csv(path, index_label="true_label")
342
+
343
+
344
+ def write_classification_report(path: Path, test_predictions: pd.DataFrame) -> None:
345
+ report = classification_report(
346
+ test_predictions["label_0_to_4"].astype(int),
347
+ test_predictions["predicted_label"].astype(int),
348
+ labels=LABELS,
349
+ output_dict=True,
350
+ zero_division=0,
351
+ )
352
+ write_json(path, report)
353
+
354
+
355
+ def compute_feature_importance(
356
+ model_run_dir: Path,
357
+ test_features: pd.DataFrame,
358
+ test_predictions: pd.DataFrame,
359
+ seed: int = DEFAULT_SEED,
360
+ repeats: int = 3,
361
+ ) -> pd.DataFrame:
362
+ feature_columns = read_json(model_run_dir / "feature_columns.json")
363
+ preprocessor = joblib.load(model_run_dir / "preprocessing.joblib")
364
+ model = joblib.load(model_run_dir / "model.joblib")
365
+ x_test = model_input_frame(test_features, feature_columns)
366
+ y_true = test_predictions["label_0_to_4"].astype(int)
367
+ baseline_pred = np.asarray(LABELS)[
368
+ np.argmax(probability_frame(model, preprocessor.transform(x_test))[PROB_COLUMNS].to_numpy(), axis=1)
369
+ ]
370
+ baseline_score = f1_score(y_true, baseline_pred, labels=LABELS, average="macro", zero_division=0)
371
+ rng = np.random.default_rng(seed)
372
+ rows: list[dict[str, Any]] = []
373
+ for column in feature_columns:
374
+ scores: list[float] = []
375
+ if x_test[column].nunique(dropna=False) <= 1:
376
+ importances = [0.0] * repeats
377
+ else:
378
+ importances = []
379
+ for _ in range(repeats):
380
+ permuted = x_test.copy()
381
+ values = permuted[column].to_numpy(copy=True)
382
+ rng.shuffle(values)
383
+ permuted[column] = values
384
+ permuted_pred = np.asarray(LABELS)[
385
+ np.argmax(
386
+ probability_frame(model, preprocessor.transform(permuted))[PROB_COLUMNS].to_numpy(),
387
+ axis=1,
388
+ )
389
+ ]
390
+ score = f1_score(y_true, permuted_pred, labels=LABELS, average="macro", zero_division=0)
391
+ scores.append(float(score))
392
+ importances.append(float(baseline_score - score))
393
+ rows.append(
394
+ {
395
+ "feature": column,
396
+ "importance_mean": float(np.mean(importances)),
397
+ "importance_std": float(np.std(importances)),
398
+ "baseline_macro_f1": float(baseline_score),
399
+ "permuted_macro_f1_mean": float(np.mean(scores)) if scores else float(baseline_score),
400
+ }
401
+ )
402
+ return pd.DataFrame(rows).sort_values(["importance_mean", "feature"], ascending=[False, True])
403
+
404
+
405
+ def evidence_audit_sample(test_predictions: pd.DataFrame, min_rows: int = 50) -> pd.DataFrame:
406
+ selected_indices: set[int] = set()
407
+ samples: list[pd.DataFrame] = []
408
+
409
+ def add_sample(name: str, mask: pd.Series, count: int, sort_column: str | None = None, ascending: bool = False) -> None:
410
+ candidates = test_predictions[mask & (~test_predictions.index.isin(selected_indices))].copy()
411
+ if sort_column and sort_column in candidates.columns:
412
+ candidates = candidates.sort_values(sort_column, ascending=ascending)
413
+ candidates = candidates.head(count)
414
+ if len(candidates):
415
+ selected_indices.update(int(index) for index in candidates.index)
416
+ candidates["audit_category"] = name
417
+ samples.append(candidates)
418
+
419
+ true_label = test_predictions["label_0_to_4"].astype(int)
420
+ predicted_label = test_predictions["predicted_label"].astype(int)
421
+ large_pred = test_predictions["p_large_training"] >= 0.5
422
+ add_sample("correct_label_0", (true_label == 0) & (predicted_label == 0), 10, "negative_certification_confidence", False)
423
+ add_sample(
424
+ "correct_label_2_hard_false_positive",
425
+ (true_label == 2)
426
+ & (predicted_label == 2)
427
+ & test_predictions["latent_workload_class"].isin(HARD_FALSE_POSITIVE_SCENARIOS),
428
+ 10,
429
+ "p_large_training",
430
+ False,
431
+ )
432
+ add_sample("correct_label_3_4_likely_training", (true_label >= 3) & (predicted_label >= 3), 10, "p_large_training", False)
433
+ add_sample("false_positive_large_training", (true_label < 3) & large_pred, 10, "p_large_training", False)
434
+ add_sample("false_negative_large_training", (true_label >= 3) & (~large_pred), 10, "p_large_training", True)
435
+
436
+ if samples:
437
+ sample = pd.concat(samples, axis=0)
438
+ else:
439
+ sample = test_predictions.iloc[0:0].copy()
440
+ if len(sample) < min_rows:
441
+ filler = test_predictions[~test_predictions.index.isin(selected_indices)].copy()
442
+ filler["uncertainty_distance"] = (filler["p_large_training"] - 0.5).abs()
443
+ filler = filler.sort_values(["uncertainty_distance", "severity_score"], ascending=[True, False]).head(min_rows - len(sample))
444
+ filler["audit_category"] = "high_uncertainty_or_coverage_edge"
445
+ sample = pd.concat([sample, filler], axis=0)
446
+
447
+ columns = [
448
+ "audit_category",
449
+ "feature_row_id",
450
+ "split",
451
+ "latent_workload_class",
452
+ "label_0_to_4",
453
+ "predicted_label",
454
+ "p_large_training",
455
+ "severity_score",
456
+ "top_evidence",
457
+ "critical_missing_layers",
458
+ "integrity_warning",
459
+ ]
460
+ columns += [column for column in SELECTED_AUDIT_FEATURES if column in sample.columns]
461
+ return sample.loc[:, [column for column in columns if column in sample.columns]].rename(columns={"label_0_to_4": "true_label"})
462
+
463
+
464
+ def top_scenarios_text(scenarios: dict[str, int], limit: int = 5) -> str:
465
+ if not scenarios:
466
+ return "none"
467
+ return ", ".join(f"{name}: {count}" for name, count in list(scenarios.items())[:limit])
468
+
469
+
470
+ def write_run_readme(model_run_dir: Path, features_path: Path, metrics: dict[str, Any], calibration: dict[str, Any]) -> None:
471
+ model_metrics = metrics["model"]
472
+ governance = metrics["governance"]
473
+ split_manifest = read_json(model_run_dir / "split_manifest.json")
474
+ dataset_dir = features_path.parent.parent if features_path.parent.name == "features" else features_path.parent
475
+ dataset_name = dataset_dir.name
476
+ model_run_name = model_run_dir.name
477
+ readme = f"""# {model_run_name} model run
478
+
479
+ This directory contains a public runnable baseline for the `{dataset_name}` datacenter training-run verification dataset.
480
+
481
+ ## Dataset
482
+
483
+ - Feature table: `{features_path}`
484
+ - Rows: {sum(split['rows'] for split in split_manifest['summary'].values())}
485
+ - Episode split: grouped by `episode_id`, scenario-stratified, seed `{split_manifest['seed']}`
486
+ - Split rows: train {split_manifest['summary']['train']['rows']}, validation {split_manifest['summary']['validation']['rows']}, test {split_manifest['summary']['test']['rows']}
487
+ - Split episodes: train {split_manifest['summary']['train']['episodes']}, validation {split_manifest['summary']['validation']['episodes']}, test {split_manifest['summary']['test']['episodes']}
488
+
489
+ ## Model
490
+
491
+ - Supervised model: calibrated scikit-learn histogram gradient boosting classifier
492
+ - Calibration: validation split only, held-out test evaluated once
493
+ - Rule baseline: deterministic evidence rules in `src/datacenter_verification_modeling/rule_baseline.py`
494
+ - Leakage exclusions: identifiers, labels, site id, episode id, raw manifest hash, scenario metadata, counterfactual metadata, and synthetic-only audit columns
495
+
496
+ ## Headline Test Metrics
497
+
498
+ - Accuracy: {model_metrics['accuracy']:.4f}
499
+ - Macro F1: {model_metrics['macro_f1']:.4f}
500
+ - Weighted F1: {model_metrics['weighted_f1']:.4f}
501
+ - Log loss: {model_metrics['log_loss']:.4f}
502
+ - Label 3/4 precision by predicted label: {governance['label_3_4_predicted_label']['precision']:.4f}
503
+ - Label 3/4 recall by predicted label: {governance['label_3_4_predicted_label']['recall']:.4f}
504
+ - `p_large_training >= 0.5` precision: {governance['p_large_training_threshold_0_5']['precision']:.4f}
505
+ - `p_large_training >= 0.5` recall: {governance['p_large_training_threshold_0_5']['recall']:.4f}
506
+ - Rule baseline macro F1: {metrics['rule_baseline']['macro_f1']:.4f}
507
+
508
+ ## Error Scenarios
509
+
510
+ - Largest false-positive scenarios at `p_large_training >= 0.5`: {top_scenarios_text(governance['false_positive_scenarios_at_0_5'])}
511
+ - Largest false-negative scenarios at `p_large_training >= 0.5`: {top_scenarios_text(governance['false_negative_scenarios_at_0_5'])}
512
+
513
+ ## Calibration
514
+
515
+ - Brier score for `p_large_training`: {calibration['brier_large_training']:.4f}
516
+ - Expected calibration error for `p_large_training`: {calibration['expected_calibration_error_large_training']:.4f}
517
+
518
+ ## Reproduce
519
+
520
+ ```bash
521
+ python src/datacenter_verification_modeling/train_model.py \\
522
+ --features {features_path} \\
523
+ --output {model_run_dir} \\
524
+ --seed {split_manifest['seed']}
525
+ ```
526
+
527
+ ```bash
528
+ python src/datacenter_verification_modeling/evaluate_model.py \\
529
+ --model-run {model_run_dir} \\
530
+ --features {features_path}
531
+ ```
532
+
533
+ ```bash
534
+ python src/datacenter_verification_modeling/predict.py \\
535
+ --model-run {model_run_dir} \\
536
+ --features {features_path} \\
537
+ --output {model_run_dir / 'predictions_all.csv'}
538
+ ```
539
+
540
+ ## Limitations
541
+
542
+ - This model is trained on synthetic data only.
543
+ - Performance numbers are not real-world deployment claims.
544
+ - Adjacent windows are correlated, so group splitting by `episode_id` is mandatory.
545
+ - Synthetic labels are generated from rules and latent scenarios, so the model may learn generator assumptions.
546
+ - Real datacenter deployment would require calibration on real telemetry and controlled drills.
547
+ - The model should assist audit triage; it should not be treated as sole proof of a violation.
548
+ """
549
+ (model_run_dir / "README.md").write_text(readme, encoding="utf-8")
550
+
551
+
552
+ def write_validation_summary(
553
+ model_run_dir: Path,
554
+ features_path: Path,
555
+ metrics: dict[str, Any],
556
+ validation_status: list[dict[str, Any]] | None,
557
+ ) -> None:
558
+ if validation_status is None and (model_run_dir / "manifest.json").exists():
559
+ existing_manifest = read_json(model_run_dir / "manifest.json")
560
+ validation_status = existing_manifest.get("validation_status") or None
561
+ required_rows = []
562
+ for filename in REQUIRED_OUTPUTS:
563
+ path = model_run_dir / filename
564
+ exists = path.exists() or filename == "validation_summary.md"
565
+ required_rows.append(f"- `{filename}`: {'present' if exists else 'missing'}")
566
+ validation_lines = []
567
+ if validation_status:
568
+ for item in validation_status:
569
+ validation_lines.append(
570
+ f"- `{item['name']}`: return code {item['returncode']}; "
571
+ f"{'PASS' if item['returncode'] == 0 else 'FAIL'}"
572
+ )
573
+ else:
574
+ validation_lines.append("- Dataset validation was not rerun by this evaluation command; see `manifest.json` for any recorded training validation status.")
575
+
576
+ model_metrics = metrics["model"]
577
+ governance = metrics["governance"]
578
+ text = f"""# Validation Summary
579
+
580
+ Feature table: `{features_path}`
581
+
582
+ ## Dataset Validation Before Training
583
+
584
+ {chr(10).join(validation_lines)}
585
+
586
+ ## Required Artifacts
587
+
588
+ {chr(10).join(required_rows)}
589
+
590
+ ## Test Metrics
591
+
592
+ - Accuracy: {model_metrics['accuracy']:.4f}
593
+ - Macro F1: {model_metrics['macro_f1']:.4f}
594
+ - Label 3/4 precision: {governance['label_3_4_predicted_label']['precision']:.4f}
595
+ - Label 3/4 recall: {governance['label_3_4_predicted_label']['recall']:.4f}
596
+ - `p_large_training >= 0.5` precision: {governance['p_large_training_threshold_0_5']['precision']:.4f}
597
+ - `p_large_training >= 0.5` recall: {governance['p_large_training_threshold_0_5']['recall']:.4f}
598
+
599
+ ## Governance Checks
600
+
601
+ - Capacity gate applied to post-processed probabilities.
602
+ - Negative certification confidence is `p_label_0 * min_critical_coverage`.
603
+ - Integrity warnings are reported separately from positive training evidence.
604
+ - Raw model probabilities are retained as `raw_p_label_0` through `raw_p_label_4`.
605
+ """
606
+ (model_run_dir / "validation_summary.md").write_text(text, encoding="utf-8")
607
+
608
+
609
+ def write_manifest(
610
+ model_run_dir: Path,
611
+ features_path: Path,
612
+ metrics: dict[str, Any],
613
+ calibration: dict[str, Any],
614
+ validation_status: list[dict[str, Any]] | None,
615
+ training_metadata: dict[str, Any] | None,
616
+ ) -> None:
617
+ existing: dict[str, Any] = {}
618
+ manifest_path = model_run_dir / "manifest.json"
619
+ if manifest_path.exists():
620
+ existing = read_json(manifest_path)
621
+ artifact_hashes = {
622
+ filename: sha256_file(model_run_dir / filename)
623
+ for filename in REQUIRED_OUTPUTS
624
+ if (model_run_dir / filename).exists() and filename != "manifest.json"
625
+ }
626
+ manifest = {
627
+ **existing,
628
+ "created_or_updated_at": utc_now_iso(),
629
+ "model_run_id": model_run_dir.name,
630
+ "features_path": str(features_path),
631
+ "model_type": "CalibratedClassifierCV over HistGradientBoostingClassifier",
632
+ "calibration_method": existing.get("calibration_method", "sigmoid_on_validation_split"),
633
+ "metrics_summary": {
634
+ "accuracy": metrics["model"]["accuracy"],
635
+ "macro_f1": metrics["model"]["macro_f1"],
636
+ "weighted_f1": metrics["model"]["weighted_f1"],
637
+ "label_3_4_precision": metrics["governance"]["label_3_4_predicted_label"]["precision"],
638
+ "label_3_4_recall": metrics["governance"]["label_3_4_predicted_label"]["recall"],
639
+ "p_large_training_precision_at_0_5": metrics["governance"]["p_large_training_threshold_0_5"]["precision"],
640
+ "p_large_training_recall_at_0_5": metrics["governance"]["p_large_training_threshold_0_5"]["recall"],
641
+ "brier_large_training": calibration["brier_large_training"],
642
+ "ece_large_training": calibration["expected_calibration_error_large_training"],
643
+ },
644
+ "validation_status": validation_status or existing.get("validation_status", []),
645
+ "training_metadata": training_metadata or existing.get("training_metadata", {}),
646
+ "required_outputs": REQUIRED_OUTPUTS,
647
+ "artifact_hashes": artifact_hashes,
648
+ "limitations": [
649
+ "Trained on synthetic data only.",
650
+ "Performance numbers are not real-world deployment claims.",
651
+ "Episode-level group splitting is required because adjacent windows are correlated.",
652
+ "Synthetic labels encode generator assumptions.",
653
+ "Real deployment requires real telemetry calibration and controlled drills.",
654
+ ],
655
+ }
656
+ write_json(manifest_path, manifest)
657
+
658
+
659
+ def evaluate_model_run(
660
+ model_run_dir: Path,
661
+ features_path: Path,
662
+ validation_status: list[dict[str, Any]] | None = None,
663
+ training_metadata: dict[str, Any] | None = None,
664
+ ) -> dict[str, Any]:
665
+ model_run_dir.mkdir(parents=True, exist_ok=True)
666
+ feature_df, predictions = predict_for_model_run(model_run_dir, features_path)
667
+ predictions.to_csv(model_run_dir / "predictions_all.csv", index=False)
668
+ test_predictions = predictions[predictions["split"] == "test"].copy()
669
+ test_features = feature_df[feature_df["split"] == "test"].copy()
670
+ if test_predictions.empty:
671
+ raise ValueError("no test split rows available for evaluation")
672
+ test_predictions.to_csv(model_run_dir / "predictions_test.csv", index=False)
673
+
674
+ metrics, calibration = compute_metrics(test_predictions, test_features)
675
+ write_json(model_run_dir / "metrics.json", metrics)
676
+ write_json(model_run_dir / "calibration_metrics.json", calibration)
677
+ write_confusion_matrix(model_run_dir / "confusion_matrix.csv", test_predictions)
678
+ write_classification_report(model_run_dir / "classification_report.json", test_predictions)
679
+ importance = compute_feature_importance(model_run_dir, test_features, test_predictions)
680
+ importance.to_csv(model_run_dir / "feature_importance.csv", index=False)
681
+ audit = evidence_audit_sample(test_predictions)
682
+ audit.to_csv(model_run_dir / "evidence_audit_sample.csv", index=False)
683
+ write_run_readme(model_run_dir, features_path, metrics, calibration)
684
+ write_manifest(model_run_dir, features_path, metrics, calibration, validation_status, training_metadata)
685
+ write_validation_summary(model_run_dir, features_path, metrics, validation_status)
686
+ write_manifest(model_run_dir, features_path, metrics, calibration, validation_status, training_metadata)
687
+ return metrics
688
+
689
+
690
+ def main(argv: list[str] | None = None) -> int:
691
+ parser = argparse.ArgumentParser(description=__doc__)
692
+ parser.add_argument("--model-run", type=Path, required=True)
693
+ parser.add_argument("--features", type=Path, required=True)
694
+ args = parser.parse_args(argv)
695
+ metrics = evaluate_model_run(args.model_run, args.features)
696
+ print(f"accuracy: {metrics['model']['accuracy']:.4f}")
697
+ print(f"macro_f1: {metrics['model']['macro_f1']:.4f}")
698
+ print(f"label_3_4_precision: {metrics['governance']['label_3_4_predicted_label']['precision']:.4f}")
699
+ print(f"label_3_4_recall: {metrics['governance']['label_3_4_predicted_label']['recall']:.4f}")
700
+ return 0
701
+
702
+
703
+ if __name__ == "__main__":
704
+ raise SystemExit(main())
src/datacenter_verification_modeling/evaluate_stress_splits.py ADDED
@@ -0,0 +1,366 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Evaluate stress splits for synthetic datacenter verification datasets."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from collections import Counter
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ import numpy as np
11
+ import pandas as pd
12
+ from sklearn.ensemble import HistGradientBoostingClassifier
13
+ from sklearn.metrics import accuracy_score, f1_score, log_loss, precision_recall_fscore_support
14
+
15
+ try:
16
+ from .common import (
17
+ DEFAULT_SEED,
18
+ LABELS,
19
+ PROB_COLUMNS,
20
+ determine_feature_columns,
21
+ ensure_dir,
22
+ load_feature_table,
23
+ make_episode_split,
24
+ make_preprocessor,
25
+ model_input_frame,
26
+ probability_frame,
27
+ write_json,
28
+ )
29
+ except ImportError: # pragma: no cover - direct script execution
30
+ from common import (
31
+ DEFAULT_SEED,
32
+ LABELS,
33
+ PROB_COLUMNS,
34
+ determine_feature_columns,
35
+ ensure_dir,
36
+ load_feature_table,
37
+ make_episode_split,
38
+ make_preprocessor,
39
+ model_input_frame,
40
+ probability_frame,
41
+ write_json,
42
+ )
43
+
44
+
45
+ FAMILY_HOLDOUTS = [
46
+ "underclocked_energy_capped_training",
47
+ "fragmented_training_linked",
48
+ "multi_tenant_fragmented_nontraining",
49
+ "model_parallel_inference",
50
+ "hpc_mpi_collective",
51
+ ]
52
+
53
+ SOURCE_ABLATIONS = {
54
+ "source_ablation_drop_fabric": ["o6_", "o7_", "fabric_telemetry_trust_level"],
55
+ "source_ablation_drop_runtime_and_ml_logs": ["o10_", "o12_"],
56
+ "source_ablation_drop_gpu_telemetry": ["o4_", "o5_", "gpu_telemetry_trust_level"],
57
+ "source_ablation_drop_power": ["o8_", "o9_", "power_meter_trust_level"],
58
+ "source_ablation_drop_storage": ["o11_"],
59
+ }
60
+
61
+
62
+ def _base_classifier(seed: int, max_iter: int) -> HistGradientBoostingClassifier:
63
+ kwargs: dict[str, Any] = {
64
+ "learning_rate": 0.06,
65
+ "max_iter": max_iter,
66
+ "max_leaf_nodes": 31,
67
+ "l2_regularization": 0.04,
68
+ "early_stopping": True,
69
+ "validation_fraction": 0.15,
70
+ "random_state": seed,
71
+ }
72
+ try:
73
+ return HistGradientBoostingClassifier(class_weight="balanced", **kwargs)
74
+ except TypeError: # pragma: no cover
75
+ return HistGradientBoostingClassifier(**kwargs)
76
+
77
+
78
+ def _binary_prf(y_true: np.ndarray, p_large: np.ndarray) -> dict[str, float]:
79
+ precision, recall, f1, _ = precision_recall_fscore_support(
80
+ y_true >= 3,
81
+ p_large >= 0.5,
82
+ average="binary",
83
+ zero_division=0,
84
+ )
85
+ return {"precision": float(precision), "recall": float(recall), "f1": float(f1)}
86
+
87
+
88
+ def _scenario_counts(df: pd.DataFrame, mask: pd.Series, scenario_column: str) -> dict[str, int]:
89
+ if not mask.any():
90
+ return {}
91
+ return {str(key): int(value) for key, value in df.loc[mask, scenario_column].value_counts().sort_values(ascending=False).items()}
92
+
93
+
94
+ def _middle_probability_bins(p_large: np.ndarray) -> dict[str, int]:
95
+ return {
96
+ "0.1_to_0.3": int(((p_large >= 0.1) & (p_large < 0.3)).sum()),
97
+ "0.3_to_0.7": int(((p_large >= 0.3) & (p_large < 0.7)).sum()),
98
+ "0.7_to_0.9": int(((p_large >= 0.7) & (p_large < 0.9)).sum()),
99
+ }
100
+
101
+
102
+ def _fit_predict_split(
103
+ df: pd.DataFrame,
104
+ train_mask: pd.Series,
105
+ test_mask: pd.Series,
106
+ feature_columns: list[str],
107
+ seed: int,
108
+ max_iter: int,
109
+ ) -> tuple[pd.DataFrame, dict[str, Any]]:
110
+ train_df = df[train_mask].copy()
111
+ test_df = df[test_mask].copy()
112
+ if train_df.empty or test_df.empty:
113
+ raise ValueError("stress split has empty train or test set")
114
+ if train_df["label_0_to_4"].nunique() < 2:
115
+ raise ValueError("stress split train set has fewer than two labels")
116
+
117
+ preprocessor = make_preprocessor(train_df, feature_columns)
118
+ x_train = preprocessor.fit_transform(model_input_frame(train_df, feature_columns))
119
+ y_train = train_df["label_0_to_4"].astype(int).to_numpy()
120
+ x_test = preprocessor.transform(model_input_frame(test_df, feature_columns))
121
+ y_test = test_df["label_0_to_4"].astype(int).to_numpy()
122
+
123
+ model = _base_classifier(seed, max_iter)
124
+ model.fit(x_train, y_train)
125
+ probabilities = probability_frame(model, x_test)
126
+ p_values = probabilities[PROB_COLUMNS].to_numpy()
127
+ y_pred = np.asarray(LABELS)[np.argmax(p_values, axis=1)]
128
+ p_large = probabilities["p_label_3"].to_numpy() + probabilities["p_label_4"].to_numpy()
129
+ out = test_df[["feature_row_id", "episode_id", "label_0_to_4"]].copy()
130
+ for column in ["scenario_family", "latent_workload_class", "site_id", "window_start"]:
131
+ if column in test_df.columns:
132
+ out[column] = test_df[column].to_numpy()
133
+ out["predicted_label"] = y_pred
134
+ out["p_large_training"] = p_large
135
+ for column in PROB_COLUMNS:
136
+ out[column] = probabilities[column].to_numpy()
137
+
138
+ large_true = y_test >= 3
139
+ large_pred = p_large >= 0.5
140
+ scenario_column = "scenario_family" if "scenario_family" in test_df.columns else "latent_workload_class"
141
+ metrics = {
142
+ "train_rows": int(len(train_df)),
143
+ "test_rows": int(len(test_df)),
144
+ "train_episodes": int(train_df["episode_id"].nunique()),
145
+ "test_episodes": int(test_df["episode_id"].nunique()),
146
+ "test_label_distribution": {str(k): int(v) for k, v in Counter(y_test).items()},
147
+ "accuracy": float(accuracy_score(y_test, y_pred)),
148
+ "macro_f1": float(f1_score(y_test, y_pred, labels=LABELS, average="macro", zero_division=0)),
149
+ "large_training": _binary_prf(y_test, p_large),
150
+ "large_training_false_positive_count": int(((~large_true) & large_pred).sum()),
151
+ "large_training_false_negative_count": int((large_true & (~large_pred)).sum()),
152
+ "false_positive_scenarios": _scenario_counts(test_df, pd.Series((~large_true) & large_pred, index=test_df.index), scenario_column),
153
+ "false_negative_scenarios": _scenario_counts(test_df, pd.Series(large_true & (~large_pred), index=test_df.index), scenario_column),
154
+ "middle_probability_bins": _middle_probability_bins(p_large),
155
+ }
156
+ try:
157
+ metrics["log_loss"] = float(log_loss(y_test, p_values, labels=LABELS))
158
+ except ValueError:
159
+ metrics["log_loss"] = None
160
+ return out, metrics
161
+
162
+
163
+ def _drop_features(feature_columns: list[str], prefixes: list[str]) -> list[str]:
164
+ dropped = []
165
+ for column in feature_columns:
166
+ if any(column == prefix or column.startswith(prefix) for prefix in prefixes):
167
+ continue
168
+ dropped.append(column)
169
+ return dropped
170
+
171
+
172
+ def evaluate_stress_splits(features_path: Path, output_dir: Path, seed: int = DEFAULT_SEED, max_iter: int = 120) -> dict[str, Any]:
173
+ ensure_dir(output_dir)
174
+ df = load_feature_table(features_path)
175
+ scenario_column = "scenario_family" if "scenario_family" in df.columns else "latent_workload_class"
176
+ feature_columns, exclusion_meta = determine_feature_columns(df.copy())
177
+ metrics: dict[str, Any] = {
178
+ "features_path": str(features_path),
179
+ "seed": int(seed),
180
+ "max_iter": int(max_iter),
181
+ "scenario_column": scenario_column,
182
+ "model_feature_count": int(len(feature_columns)),
183
+ "exclusion_metadata": exclusion_meta,
184
+ "splits": {},
185
+ }
186
+
187
+ split_df, split_manifest = make_episode_split(df, seed=seed)
188
+ random_train = split_df["split"].isin(["train", "validation"])
189
+ random_test = split_df["split"] == "test"
190
+ _, metrics["splits"]["episode_grouped_random"] = _fit_predict_split(
191
+ split_df, random_train, random_test, feature_columns, seed, max_iter
192
+ )
193
+
194
+ sorted_times = pd.to_datetime(df["window_start"], utc=True)
195
+ cutoff = sorted_times.quantile(0.80)
196
+ _, metrics["splits"]["time_holdout"] = _fit_predict_split(
197
+ df, sorted_times < cutoff, sorted_times >= cutoff, feature_columns, seed + 1, max_iter
198
+ )
199
+
200
+ site_results = {}
201
+ for idx, site_id in enumerate(sorted(df["site_id"].dropna().unique())):
202
+ test_mask = df["site_id"] == site_id
203
+ train_mask = ~test_mask
204
+ try:
205
+ _, site_results[str(site_id)] = _fit_predict_split(df, train_mask, test_mask, feature_columns, seed + 10 + idx, max_iter)
206
+ except ValueError as exc:
207
+ site_results[str(site_id)] = {"error": str(exc)}
208
+ metrics["splits"]["site_holdout"] = site_results
209
+
210
+ family_results = {}
211
+ for idx, family in enumerate(FAMILY_HOLDOUTS):
212
+ if family not in set(df[scenario_column].astype(str)):
213
+ family_results[family] = {"error": "family not present"}
214
+ continue
215
+ test_mask = df[scenario_column].astype(str) == family
216
+ train_mask = ~test_mask
217
+ try:
218
+ _, family_results[family] = _fit_predict_split(df, train_mask, test_mask, feature_columns, seed + 30 + idx, max_iter)
219
+ except ValueError as exc:
220
+ family_results[family] = {"error": str(exc)}
221
+ metrics["splits"]["scenario_family_holdout"] = family_results
222
+
223
+ ablation_results = {}
224
+ for idx, (name, prefixes) in enumerate(SOURCE_ABLATIONS.items()):
225
+ ablated_features = _drop_features(feature_columns, prefixes)
226
+ try:
227
+ _, ablation_results[name] = _fit_predict_split(
228
+ split_df, random_train, random_test, ablated_features, seed + 50 + idx, max_iter
229
+ )
230
+ ablation_results[name]["feature_count"] = int(len(ablated_features))
231
+ ablation_results[name]["dropped_prefixes"] = prefixes
232
+ except ValueError as exc:
233
+ ablation_results[name] = {"error": str(exc)}
234
+ metrics["splits"]["source_ablations"] = ablation_results
235
+
236
+ write_json(output_dir / "stress_metrics.json", metrics)
237
+ write_stress_summary(output_dir / "stress_summary.md", metrics)
238
+ update_model_run_readme_with_stress(output_dir, metrics)
239
+ return metrics
240
+
241
+
242
+ def _split_line(name: str, item: dict[str, Any]) -> str:
243
+ if "error" in item:
244
+ return f"- {name}: ERROR: {item['error']}"
245
+ return (
246
+ f"- {name}: rows={item['test_rows']}, macro F1={item['macro_f1']:.3f}, "
247
+ f"large precision={item['large_training']['precision']:.3f}, "
248
+ f"large recall={item['large_training']['recall']:.3f}, "
249
+ f"FP={item['large_training_false_positive_count']}, FN={item['large_training_false_negative_count']}, "
250
+ f"middle p bins={item['middle_probability_bins']}"
251
+ )
252
+
253
+
254
+ def write_stress_summary(path: Path, metrics: dict[str, Any]) -> None:
255
+ splits = metrics["splits"]
256
+ lines = [
257
+ "# Synthetic v1 Stress Summary",
258
+ "",
259
+ f"Feature table: `{metrics['features_path']}`",
260
+ f"Seed: `{metrics['seed']}`",
261
+ f"Lightweight stress model max_iter: `{metrics['max_iter']}`",
262
+ "",
263
+ "## Core Splits",
264
+ "",
265
+ _split_line("episode_grouped_random", splits["episode_grouped_random"]),
266
+ _split_line("time_holdout", splits["time_holdout"]),
267
+ "",
268
+ "## Site Holdout",
269
+ "",
270
+ ]
271
+ for site_id, item in splits["site_holdout"].items():
272
+ lines.append(_split_line(site_id, item))
273
+ lines.extend(["", "## Scenario Family Holdout", ""])
274
+ for family, item in splits["scenario_family_holdout"].items():
275
+ lines.append(_split_line(family, item))
276
+ if "false_negative_scenarios" in item and item["false_negative_scenarios"]:
277
+ lines.append(f" false negatives: {item['false_negative_scenarios']}")
278
+ if "false_positive_scenarios" in item and item["false_positive_scenarios"]:
279
+ lines.append(f" false positives: {item['false_positive_scenarios']}")
280
+ lines.extend(["", "## Source Ablations", ""])
281
+ baseline = splits["episode_grouped_random"]
282
+ baseline_f1 = baseline.get("macro_f1") if "error" not in baseline else None
283
+ for name, item in splits["source_ablations"].items():
284
+ lines.append(_split_line(name, item))
285
+ if baseline_f1 is not None and "macro_f1" in item:
286
+ lines.append(f" macro F1 delta vs random baseline: {item['macro_f1'] - baseline_f1:+.3f}")
287
+ lines.extend(
288
+ [
289
+ "",
290
+ "## Interpretation",
291
+ "",
292
+ "- These are stress diagnostics, not tuned headline metrics.",
293
+ "- Family holdouts identify hard families that still fail under scenario generalization.",
294
+ "- Source ablations should degrade performance when the dropped layer carries independent evidence.",
295
+ "- Middle probability bins indicate whether v1 has less all-or-nothing large-training probability mass than v0.",
296
+ ]
297
+ )
298
+ path.write_text("\n".join(lines) + "\n", encoding="utf-8")
299
+
300
+
301
+ def update_model_run_readme_with_stress(model_run_dir: Path, metrics: dict[str, Any]) -> None:
302
+ readme_path = model_run_dir / "README.md"
303
+ if not readme_path.exists():
304
+ return
305
+ splits = metrics["splits"]
306
+ baseline = splits["episode_grouped_random"]
307
+ fragmented = splits["scenario_family_holdout"].get("fragmented_training_linked", {})
308
+ underclocked = splits["scenario_family_holdout"].get("underclocked_energy_capped_training", {})
309
+ runtime_drop = splits["source_ablations"].get("source_ablation_drop_runtime_and_ml_logs", {})
310
+ baseline_f1 = baseline.get("macro_f1", 0.0)
311
+ runtime_delta = runtime_drop.get("macro_f1", baseline_f1) - baseline_f1 if "macro_f1" in runtime_drop else 0.0
312
+ section = f"""## Stress Splits
313
+
314
+ Stress outputs are in:
315
+
316
+ - `stress_metrics.json`
317
+ - `stress_summary.md`
318
+
319
+ Key stress checks from the lightweight retraining script:
320
+
321
+ - Episode-grouped random split macro F1: {baseline.get('macro_f1', 0.0):.4f}; large-training recall: {baseline.get('large_training', {}).get('recall', 0.0):.4f}
322
+ - Time holdout macro F1: {splits['time_holdout'].get('macro_f1', 0.0):.4f}; large-training recall: {splits['time_holdout'].get('large_training', {}).get('recall', 0.0):.4f}
323
+ - Underclocked holdout large-training recall: {underclocked.get('large_training', {}).get('recall', 0.0):.4f}; false negatives: {underclocked.get('large_training_false_negative_count', 0)}
324
+ - Fragmented linked training holdout remains hard: {fragmented.get('large_training_false_negative_count', 0)} large-training false negatives
325
+ - Source ablation dropping runtime and ML logs changed macro F1 by {runtime_delta:+.3f} versus the random stress baseline
326
+ - Middle `p_large_training` bins are populated in stress checks: {baseline.get('middle_probability_bins', {})}
327
+
328
+ """
329
+ readme = readme_path.read_text(encoding="utf-8")
330
+ start = readme.find("## Stress Splits\n")
331
+ if start != -1:
332
+ end = readme.find("## Reproduce\n", start)
333
+ if end == -1:
334
+ end = readme.find("## Limitations\n", start)
335
+ if end == -1:
336
+ readme = readme[:start].rstrip() + "\n\n" + section
337
+ else:
338
+ readme = readme[:start].rstrip() + "\n\n" + section + readme[end:]
339
+ else:
340
+ marker = "## Reproduce\n"
341
+ idx = readme.find(marker)
342
+ if idx == -1:
343
+ readme = readme.rstrip() + "\n\n" + section
344
+ else:
345
+ readme = readme[:idx].rstrip() + "\n\n" + section + readme[idx:]
346
+ readme_path.write_text(readme, encoding="utf-8")
347
+
348
+
349
+ def main(argv: list[str] | None = None) -> int:
350
+ parser = argparse.ArgumentParser(description=__doc__)
351
+ parser.add_argument("--features", type=Path, required=True)
352
+ parser.add_argument("--model-run", type=Path, required=True, help="Directory to write stress outputs into.")
353
+ parser.add_argument("--seed", type=int, default=DEFAULT_SEED)
354
+ parser.add_argument("--max-iter", type=int, default=120)
355
+ args = parser.parse_args(argv)
356
+ metrics = evaluate_stress_splits(args.features, args.model_run, seed=args.seed, max_iter=args.max_iter)
357
+ random_metrics = metrics["splits"]["episode_grouped_random"]
358
+ print(f"episode_grouped_random_macro_f1: {random_metrics['macro_f1']:.4f}")
359
+ print(f"episode_grouped_random_large_recall: {random_metrics['large_training']['recall']:.4f}")
360
+ print(f"stress_metrics: {args.model_run / 'stress_metrics.json'}")
361
+ print(f"stress_summary: {args.model_run / 'stress_summary.md'}")
362
+ return 0
363
+
364
+
365
+ if __name__ == "__main__":
366
+ raise SystemExit(main())
src/datacenter_verification_modeling/predict.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generate calibrated and governance post-processed predictions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+ try:
9
+ from .evaluate_model import predict_for_model_run
10
+ except ImportError: # pragma: no cover - direct script execution
11
+ from evaluate_model import predict_for_model_run
12
+
13
+
14
+ def main(argv: list[str] | None = None) -> int:
15
+ parser = argparse.ArgumentParser(description=__doc__)
16
+ parser.add_argument("--model-run", type=Path, required=True)
17
+ parser.add_argument("--features", type=Path, required=True)
18
+ parser.add_argument("--output", type=Path, required=True)
19
+ args = parser.parse_args(argv)
20
+ _, predictions = predict_for_model_run(args.model_run, args.features)
21
+ args.output.parent.mkdir(parents=True, exist_ok=True)
22
+ predictions.to_csv(args.output, index=False)
23
+ print(f"wrote_predictions: {args.output}")
24
+ print(f"rows: {len(predictions)}")
25
+ return 0
26
+
27
+
28
+ if __name__ == "__main__":
29
+ raise SystemExit(main())
30
+
src/datacenter_verification_modeling/prepare_features.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Prepare leakage-safe feature metadata for the synthetic v0 modeling run."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+ try:
9
+ from .common import DEFAULT_SEED, determine_feature_columns, ensure_dir, load_feature_table, make_episode_split, write_json
10
+ except ImportError: # pragma: no cover - direct script execution
11
+ from common import DEFAULT_SEED, determine_feature_columns, ensure_dir, load_feature_table, make_episode_split, write_json
12
+
13
+
14
+ def prepare_features(features_path: Path, output_dir: Path, seed: int = DEFAULT_SEED) -> dict[str, object]:
15
+ df = load_feature_table(features_path)
16
+ split_df, split_manifest = make_episode_split(df, seed=seed)
17
+ feature_columns, excluded_columns = determine_feature_columns(split_df)
18
+
19
+ ensure_dir(output_dir)
20
+ write_json(output_dir / "split_manifest.json", split_manifest)
21
+ write_json(output_dir / "feature_columns.json", feature_columns)
22
+ write_json(output_dir / "excluded_columns.json", excluded_columns)
23
+
24
+ return {
25
+ "rows": int(len(split_df)),
26
+ "episodes": int(split_df["episode_id"].nunique()),
27
+ "feature_count": int(len(feature_columns)),
28
+ "split_summary": split_manifest["summary"],
29
+ }
30
+
31
+
32
+ def main(argv: list[str] | None = None) -> int:
33
+ parser = argparse.ArgumentParser(description=__doc__)
34
+ parser.add_argument("--features", type=Path, required=True)
35
+ parser.add_argument("--output", type=Path, required=True)
36
+ parser.add_argument("--seed", type=int, default=DEFAULT_SEED)
37
+ args = parser.parse_args(argv)
38
+ summary = prepare_features(args.features, args.output, seed=args.seed)
39
+ print(f"rows: {summary['rows']}")
40
+ print(f"episodes: {summary['episodes']}")
41
+ print(f"model_features: {summary['feature_count']}")
42
+ for split, values in summary["split_summary"].items():
43
+ print(f"{split}: rows={values['rows']} episodes={values['episodes']}")
44
+ return 0
45
+
46
+
47
+ if __name__ == "__main__":
48
+ raise SystemExit(main())
49
+
src/datacenter_verification_modeling/requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # python >= 3.10
2
+ pandas
3
+ numpy
4
+ scikit-learn
5
+ joblib
src/datacenter_verification_modeling/rule_baseline.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Deterministic evidence-rule baseline for synthetic v0.
2
+
3
+ The rule baseline: not intended to replace the supervised model; it provides a leakage-free point of comparison and encodes the study's governance constraints around capacity, missingness, and multi-layer evidence
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import numpy as np
13
+ import pandas as pd
14
+
15
+ try:
16
+ from .common import LABELS, PROB_COLUMNS, as_bool, load_feature_table, normalize_probability_array, row_bool, row_float
17
+ except ImportError: # pragma: no cover - direct script execution
18
+ from common import LABELS, PROB_COLUMNS, as_bool, load_feature_table, normalize_probability_array, row_bool, row_float
19
+
20
+
21
+ FALSE_POSITIVE_RUNTIME_MARKERS = [
22
+ "inference",
23
+ "hpc",
24
+ "mpi",
25
+ "nccl",
26
+ "benchmark",
27
+ "burn_in",
28
+ "storage",
29
+ "etl",
30
+ "synthetic_data",
31
+ ]
32
+
33
+ FALSE_POSITIVE_DECLARED_CLASSES = {
34
+ "inference",
35
+ "hpc",
36
+ "benchmark",
37
+ "burn_in",
38
+ "data",
39
+ "reserved",
40
+ "none",
41
+ }
42
+
43
+
44
+ def evidence_flags(row: pd.Series) -> dict[str, bool]:
45
+ capacity = row_bool(row, "capacity_possible")
46
+ external_conflict = row_float(row, "o17_external_capacity_conflict_score")
47
+ allocation_gpus = row_float(row, "o2_max_concurrent_normalized_gpus")
48
+ allocation_hours = row_float(row, "o2_allocation_duration_hours")
49
+ gpu_hours_ratio = row_float(row, "o2_gpu_hours_policy_ratio")
50
+ policy_ratio = row_float(row, "policy_compute_ratio")
51
+ gpu_util = row_float(row, "o4_gpu_util_p95")
52
+ gpu_duty = row_float(row, "o4_gpu_util_duty_gt_70")
53
+ tensor = row_float(row, "o4_sm_tensor_active_p95")
54
+ fabric_footprint = row_float(row, "o7_synchronized_fabric_footprint")
55
+ fabric_periodicity = row_float(row, "o7_collective_periodicity_score")
56
+ fabric_util = row_float(row, "o7_scaleout_port_util_p95")
57
+ rack_power = row_float(row, "o8_rack_power_fraction_p95")
58
+ checkpoint = row_float(row, "o11_checkpoint_periodicity_score")
59
+ checkpoint_tb = row_float(row, "o11_checkpoint_write_tb_per_event")
60
+ storage_pattern = row_float(row, "o11_read_write_training_pattern_score")
61
+ signed_logs = row_bool(row, "o12_signed_ml_logs_present")
62
+ declared_params = row_float(row, "o12_declared_parameter_count_b")
63
+ training_tokens = row_float(row, "o12_training_tokens_b")
64
+ optimizer_state = row_bool(row, "o12_optimizer_state_present")
65
+ min_coverage = row_float(row, "o14_min_critical_coverage", 1.0)
66
+ gap_fraction = row_float(row, "o14_gap_fraction_critical")
67
+ cc_fraction = row_float(row, "o13_confidential_compute_mode_fraction")
68
+ counter_resets = row_float(row, "o14_counter_reset_count")
69
+ runtime = str(row["o10_runtime_framework_class"]).lower() if "o10_runtime_framework_class" in row else ""
70
+ declared_class = str(row["o2_declared_workload_class"]).lower() if "o2_declared_workload_class" in row else ""
71
+
72
+ allocation = allocation_gpus >= 512 or gpu_hours_ratio >= 1.0 or policy_ratio >= 1.0 or (
73
+ allocation_gpus >= 256 and allocation_hours >= 12
74
+ )
75
+ gpu_activity = gpu_util >= 70 or gpu_duty >= 0.45 or tensor >= 0.60
76
+ fabric_sync = fabric_footprint >= 256 or fabric_periodicity >= 0.55 or fabric_util >= 0.65
77
+ physical_support = rack_power >= 0.55
78
+ storage_semantic = checkpoint >= 0.55 or checkpoint_tb >= 0.25 or storage_pattern >= 0.60
79
+ runtime_semantic = "training" in runtime or "fine_tune" in runtime or "pytorch_distributed" in runtime
80
+ ml_semantic = signed_logs or declared_params >= 50 or training_tokens >= 100 or optimizer_state
81
+ integrity = (
82
+ gap_fraction > 0.05
83
+ or min_coverage < 0.80
84
+ or cc_fraction > 0.50
85
+ or counter_resets > 0
86
+ or row_bool(row, "o15_unapproved_physical_change_near_window")
87
+ or str(row.get("o4_missing_reason", "")) == "counter_disabled_by_cc_mode"
88
+ )
89
+ false_positive_pattern = any(marker in runtime for marker in FALSE_POSITIVE_RUNTIME_MARKERS) or declared_class in (
90
+ FALSE_POSITIVE_DECLARED_CLASSES
91
+ )
92
+ reserved_without_activity = (
93
+ allocation_gpus >= 512
94
+ and gpu_util < 30
95
+ and fabric_footprint < 64
96
+ and fabric_periodicity < 0.20
97
+ and not storage_semantic
98
+ and not ml_semantic
99
+ )
100
+ return {
101
+ "capacity": capacity,
102
+ "external_conflict": external_conflict >= 0.5,
103
+ "allocation": allocation,
104
+ "gpu_activity": gpu_activity,
105
+ "fabric_sync": fabric_sync,
106
+ "physical_support": physical_support,
107
+ "storage_semantic": storage_semantic,
108
+ "runtime_semantic": runtime_semantic,
109
+ "ml_semantic": ml_semantic,
110
+ "signed_logs": signed_logs,
111
+ "integrity": integrity,
112
+ "false_positive_pattern": false_positive_pattern,
113
+ "reserved_without_activity": reserved_without_activity,
114
+ "strong_coverage": min_coverage >= 0.90 and gap_fraction <= 0.05,
115
+ "policy_scale": policy_ratio >= 1.0 or gpu_hours_ratio >= 1.0 or allocation_gpus >= 512,
116
+ }
117
+
118
+
119
+ def predict_rule_label(row: pd.Series) -> int:
120
+ flags = evidence_flags(row)
121
+ primary_count = int(flags["allocation"]) + int(flags["gpu_activity"]) + int(flags["fabric_sync"])
122
+ semantic_count = int(flags["runtime_semantic"]) + int(flags["storage_semantic"]) + int(flags["ml_semantic"])
123
+
124
+ if not flags["capacity"] and not flags["external_conflict"]:
125
+ return 0 if flags["strong_coverage"] else 1
126
+
127
+ if flags["signed_logs"] and flags["policy_scale"] and primary_count >= 2:
128
+ return 4
129
+
130
+ if (
131
+ flags["allocation"]
132
+ and flags["gpu_activity"]
133
+ and flags["fabric_sync"]
134
+ and flags["physical_support"]
135
+ and semantic_count >= 2
136
+ and flags["policy_scale"]
137
+ ):
138
+ return 4
139
+
140
+ if flags["reserved_without_activity"]:
141
+ return 1
142
+
143
+ if flags["false_positive_pattern"] and (flags["gpu_activity"] or flags["fabric_sync"] or flags["physical_support"]):
144
+ return 2
145
+
146
+ if flags["integrity"] and not flags["ml_semantic"] and (primary_count >= 1 or flags["physical_support"]):
147
+ return 2
148
+
149
+ if primary_count >= 2 and (flags["physical_support"] or semantic_count >= 1):
150
+ return 3
151
+
152
+ if primary_count >= 1 or flags["physical_support"] or semantic_count >= 1:
153
+ return 2
154
+
155
+ return 1 if flags["capacity"] else 0
156
+
157
+
158
+ def predict_rule_labels(df: pd.DataFrame) -> pd.Series:
159
+ return df.apply(predict_rule_label, axis=1).astype(int)
160
+
161
+
162
+ def predict_rule_probabilities(df: pd.DataFrame, confidence: float = 0.82) -> pd.DataFrame:
163
+ labels = predict_rule_labels(df)
164
+ off_probability = (1.0 - confidence) / (len(LABELS) - 1)
165
+ probabilities = np.full((len(df), len(LABELS)), off_probability, dtype=float)
166
+ for row_index, label in enumerate(labels):
167
+ probabilities[row_index, LABELS.index(int(label))] = confidence
168
+ probabilities = normalize_probability_array(probabilities)
169
+ return pd.DataFrame(probabilities, columns=PROB_COLUMNS, index=df.index)
170
+
171
+
172
+ def main(argv: list[str] | None = None) -> int:
173
+ parser = argparse.ArgumentParser(description=__doc__)
174
+ parser.add_argument("--features", type=Path, required=True)
175
+ parser.add_argument("--output", type=Path)
176
+ args = parser.parse_args(argv)
177
+
178
+ df = load_feature_table(args.features)
179
+ labels = predict_rule_labels(df)
180
+ out = df[["feature_row_id", "episode_id", "latent_workload_class", "label_0_to_4"]].copy()
181
+ out["rule_predicted_label"] = labels
182
+ if args.output:
183
+ args.output.parent.mkdir(parents=True, exist_ok=True)
184
+ out.to_csv(args.output, index=False)
185
+ else:
186
+ print(out.head(20).to_string(index=False))
187
+ return 0
188
+
189
+
190
+ if __name__ == "__main__":
191
+ raise SystemExit(main())
192
+
src/datacenter_verification_modeling/train_model.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Train the calibrated supervised baseline for synthetic datacenter verification data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ import joblib
12
+ import numpy as np
13
+ from sklearn.calibration import CalibratedClassifierCV
14
+ from sklearn.ensemble import HistGradientBoostingClassifier
15
+
16
+ try:
17
+ from .common import (
18
+ DEFAULT_SEED,
19
+ determine_feature_columns,
20
+ derive_dataset_dir,
21
+ ensure_dir,
22
+ load_feature_table,
23
+ make_episode_split,
24
+ make_preprocessor,
25
+ model_input_frame,
26
+ utc_now_iso,
27
+ write_json,
28
+ )
29
+ from .evaluate_model import evaluate_model_run
30
+ except ImportError: # pragma: no cover - direct script execution
31
+ from common import (
32
+ DEFAULT_SEED,
33
+ determine_feature_columns,
34
+ derive_dataset_dir,
35
+ ensure_dir,
36
+ load_feature_table,
37
+ make_episode_split,
38
+ make_preprocessor,
39
+ model_input_frame,
40
+ utc_now_iso,
41
+ write_json,
42
+ )
43
+ from evaluate_model import evaluate_model_run
44
+
45
+
46
+ def repo_root() -> Path:
47
+ return Path(__file__).resolve().parents[2]
48
+
49
+
50
+ def run_dataset_validations(features_path: Path) -> list[dict[str, Any]]:
51
+ root = repo_root()
52
+ dataset_dir = derive_dataset_dir(features_path)
53
+ try:
54
+ dataset_arg = dataset_dir.relative_to(root)
55
+ except ValueError:
56
+ dataset_arg = dataset_dir
57
+ commands = [
58
+ {
59
+ "name": "synthetic_dataset_validator",
60
+ "command": [
61
+ sys.executable,
62
+ str(root / "src/datacenter_verification_synthetic/validate_synthetic_dataset.py"),
63
+ "--dataset",
64
+ str(dataset_arg),
65
+ ],
66
+ },
67
+ {
68
+ "name": "public_dataset_validator",
69
+ "command": [
70
+ sys.executable,
71
+ "-m",
72
+ "src.datacenter_verification_validators",
73
+ "--dataset",
74
+ str(dataset_arg),
75
+ ],
76
+ },
77
+ ]
78
+ results: list[dict[str, Any]] = []
79
+ for item in commands:
80
+ completed = subprocess.run(
81
+ item["command"],
82
+ cwd=root,
83
+ check=False,
84
+ text=True,
85
+ capture_output=True,
86
+ )
87
+ result = {
88
+ "name": item["name"],
89
+ "command": " ".join(item["command"]),
90
+ "returncode": int(completed.returncode),
91
+ "stdout": completed.stdout.strip(),
92
+ "stderr": completed.stderr.strip(),
93
+ }
94
+ results.append(result)
95
+ if completed.returncode != 0:
96
+ raise RuntimeError(
97
+ f"dataset validation failed for {item['name']} with return code {completed.returncode}\n"
98
+ f"stdout:\n{completed.stdout}\n\nstderr:\n{completed.stderr}"
99
+ )
100
+ return results
101
+
102
+
103
+ def make_base_classifier(seed: int) -> HistGradientBoostingClassifier:
104
+ kwargs: dict[str, Any] = {
105
+ "learning_rate": 0.05,
106
+ "max_iter": 350,
107
+ "max_leaf_nodes": 31,
108
+ "l2_regularization": 0.03,
109
+ "early_stopping": True,
110
+ "validation_fraction": 0.15,
111
+ "random_state": seed,
112
+ }
113
+ try:
114
+ return HistGradientBoostingClassifier(class_weight="balanced", **kwargs)
115
+ except TypeError: # pragma: no cover - older scikit-learn fallback
116
+ return HistGradientBoostingClassifier(**kwargs)
117
+
118
+
119
+ def calibrate_prefit_model(base_model: Any, x_validation: np.ndarray, y_validation: np.ndarray) -> tuple[Any, str]:
120
+ try:
121
+ from sklearn.frozen import FrozenEstimator
122
+
123
+ calibrator = CalibratedClassifierCV(FrozenEstimator(base_model), method="sigmoid")
124
+ method = "sigmoid_on_validation_split_frozen_estimator"
125
+ except Exception: # pragma: no cover - older scikit-learn fallback
126
+ calibrator = CalibratedClassifierCV(base_model, method="sigmoid", cv="prefit")
127
+ method = "sigmoid_on_validation_split_prefit"
128
+ calibrator.fit(x_validation, y_validation)
129
+ return calibrator, method
130
+
131
+
132
+ def train_model(features_path: Path, output_dir: Path, seed: int = DEFAULT_SEED, skip_dataset_validation: bool = False) -> dict[str, Any]:
133
+ ensure_dir(output_dir)
134
+ validation_status = [] if skip_dataset_validation else run_dataset_validations(features_path)
135
+
136
+ df = load_feature_table(features_path)
137
+ split_df, split_manifest = make_episode_split(df, seed=seed)
138
+ feature_columns, excluded_columns = determine_feature_columns(split_df)
139
+ write_json(output_dir / "split_manifest.json", split_manifest)
140
+ write_json(output_dir / "feature_columns.json", feature_columns)
141
+ write_json(output_dir / "excluded_columns.json", excluded_columns)
142
+
143
+ train_df = split_df[split_df["split"] == "train"].copy()
144
+ validation_df = split_df[split_df["split"] == "validation"].copy()
145
+ if train_df.empty or validation_df.empty:
146
+ raise ValueError("train and validation splits must both be non-empty")
147
+
148
+ preprocessor = make_preprocessor(train_df, feature_columns)
149
+ x_train = preprocessor.fit_transform(model_input_frame(train_df, feature_columns))
150
+ y_train = train_df["label_0_to_4"].astype(int).to_numpy()
151
+ x_validation = preprocessor.transform(model_input_frame(validation_df, feature_columns))
152
+ y_validation = validation_df["label_0_to_4"].astype(int).to_numpy()
153
+
154
+ base_model = make_base_classifier(seed)
155
+ base_model.fit(x_train, y_train)
156
+ calibrated_model, calibration_method = calibrate_prefit_model(base_model, x_validation, y_validation)
157
+
158
+ joblib.dump(calibrated_model, output_dir / "model.joblib")
159
+ joblib.dump(preprocessor, output_dir / "preprocessing.joblib")
160
+
161
+ training_metadata = {
162
+ "trained_at": utc_now_iso(),
163
+ "seed": int(seed),
164
+ "features_path": str(features_path),
165
+ "model_type": "HistGradientBoostingClassifier",
166
+ "model_parameters": base_model.get_params(),
167
+ "calibration_method": calibration_method,
168
+ "train_rows": int(len(train_df)),
169
+ "validation_rows": int(len(validation_df)),
170
+ "train_episodes": int(train_df["episode_id"].nunique()),
171
+ "validation_episodes": int(validation_df["episode_id"].nunique()),
172
+ "feature_count": int(len(feature_columns)),
173
+ "dataset_validation_status": validation_status,
174
+ }
175
+ write_json(
176
+ output_dir / "manifest.json",
177
+ {
178
+ "model_run_id": output_dir.name,
179
+ "created_or_updated_at": utc_now_iso(),
180
+ "features_path": str(features_path),
181
+ "model_type": "CalibratedClassifierCV over HistGradientBoostingClassifier",
182
+ "calibration_method": calibration_method,
183
+ "training_metadata": training_metadata,
184
+ "validation_status": validation_status,
185
+ },
186
+ )
187
+
188
+ metrics = evaluate_model_run(
189
+ output_dir,
190
+ features_path,
191
+ validation_status=validation_status,
192
+ training_metadata=training_metadata,
193
+ )
194
+ return {
195
+ "metrics": metrics,
196
+ "split_summary": split_manifest["summary"],
197
+ "validation_status": validation_status,
198
+ "training_metadata": training_metadata,
199
+ }
200
+
201
+
202
+ def main(argv: list[str] | None = None) -> int:
203
+ parser = argparse.ArgumentParser(description=__doc__)
204
+ parser.add_argument("--features", type=Path, required=True)
205
+ parser.add_argument("--output", type=Path, required=True)
206
+ parser.add_argument("--seed", type=int, default=DEFAULT_SEED)
207
+ parser.add_argument(
208
+ "--skip-dataset-validation",
209
+ action="store_true",
210
+ help="Skip pre-training dataset validation. Intended only for development.",
211
+ )
212
+ args = parser.parse_args(argv)
213
+ result = train_model(args.features, args.output, seed=args.seed, skip_dataset_validation=args.skip_dataset_validation)
214
+ metrics = result["metrics"]
215
+ print(f"train_rows: {result['split_summary']['train']['rows']}")
216
+ print(f"validation_rows: {result['split_summary']['validation']['rows']}")
217
+ print(f"test_rows: {result['split_summary']['test']['rows']}")
218
+ print(f"accuracy: {metrics['model']['accuracy']:.4f}")
219
+ print(f"macro_f1: {metrics['model']['macro_f1']:.4f}")
220
+ print(f"label_3_4_precision: {metrics['governance']['label_3_4_predicted_label']['precision']:.4f}")
221
+ print(f"label_3_4_recall: {metrics['governance']['label_3_4_predicted_label']['recall']:.4f}")
222
+ return 0
223
+
224
+
225
+ if __name__ == "__main__":
226
+ raise SystemExit(main())