thundercode commited on
Commit
bf2779e
·
verified ·
1 Parent(s): 93a048a

release: add tools/verify_readme_metrics.py

Browse files
Files changed (1) hide show
  1. tools/verify_readme_metrics.py +149 -0
tools/verify_readme_metrics.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Verify every metric quoted in the release README against its source artifact.
2
+
3
+ Read-only. Each claim is compared at the precision at which the README states it.
4
+ Writes nothing; prints a report suitable for pasting into FINAL_RELEASE_VERIFICATION.md.
5
+
6
+ Key names were discovered by walking the artifacts, NOT assumed: several live under nested
7
+ paths (e.g. change metrics are `metrics.pooled.iou`, grounding is
8
+ `results.head_threshold.mean_best_iou`, VLM is `why_usable_verified.adapted_test.*`).
9
+
10
+ WHERE THE ARTIFACTS COME FROM
11
+ -----------------------------
12
+ This tool reads the project's `artifacts/` directory, which is NOT part of the public release
13
+ (the release ships the six trained weights on the Hugging Face Hub, not the full artifacts tree).
14
+ Point it at a checkout that has the artifacts:
15
+
16
+ SATQUERY_ARTIFACTS_ROOT=/path/to/satquery-ai python verify_readme_metrics.py
17
+
18
+ Without that variable it defaults to `../..` relative to this file, which is the layout the tool
19
+ was authored in.
20
+ """
21
+ import json
22
+ import os
23
+ import sys
24
+
25
+ ROOT = os.environ.get(
26
+ "SATQUERY_ARTIFACTS_ROOT",
27
+ os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..")),
28
+ )
29
+
30
+ # (label, relative path, dotted key, value as printed in the README)
31
+ CLAIMS = [
32
+ ("change pooled IoU", "artifacts/change/eval_test/eval_result.json",
33
+ "metrics.pooled.iou", "0.8122"),
34
+ ("change macro IoU", "artifacts/change/eval_test/eval_result.json",
35
+ "metrics.macro.miou", "0.8457"),
36
+ ("change pooled F1", "artifacts/change/eval_test/eval_result.json",
37
+ "metrics.pooled.f1", "0.8964"),
38
+ ("grounding canonical head_threshold mean_best_IoU",
39
+ "artifacts/grounding/remoteclip_grounding_v001/eval_result_canonical.json",
40
+ "results.head_threshold.mean_best_iou", "0.2838"),
41
+ ("grounding canonical head_threshold recall@0.5",
42
+ "artifacts/grounding/remoteclip_grounding_v001/eval_result_canonical.json",
43
+ "results.head_threshold.recall.0.50", "0.2198"),
44
+ ("grounding matched6 head_threshold mean_best_IoU",
45
+ "artifacts/grounding/remoteclip_grounding_v001/eval_result_matched6.json",
46
+ "results.head_threshold.mean_best_iou", "0.2566"),
47
+ ("grounding matched6 head_threshold recall@0.5",
48
+ "artifacts/grounding/remoteclip_grounding_v001/eval_result_matched6.json",
49
+ "results.head_threshold.recall.0.50", "0.1938"),
50
+ ("grounding head_argmax mean_best_IoU (canonical)",
51
+ "artifacts/grounding/remoteclip_grounding_v001/eval_result_canonical.json",
52
+ "results.head_argmax.mean_best_iou", "0.1215"),
53
+ ("grounding zero-shot baseline IoU (canonical)",
54
+ "artifacts/grounding/remoteclip_grounding_v001/eval_result_canonical.json",
55
+ "results.zero_shot_matched.mean_best_iou", "0.0972"),
56
+ ("optical-SAR fusion accuracy",
57
+ "artifacts/optical_sar/fusion_head_production_v001/pre_registered_115_metric.json",
58
+ "accuracy", "0.931"),
59
+ ("optical-SAR fusion macro_F1",
60
+ "artifacts/optical_sar/fusion_head_production_v001/pre_registered_115_metric.json",
61
+ "macro_f1", "0.434161"),
62
+ ("change_vqa test accuracy", "artifacts/change_vqa/run/PROMOTION.json",
63
+ "verification.test_accuracy", "0.697626"),
64
+ ("change_vqa test macro_F1", "artifacts/change_vqa/run/PROMOTION.json",
65
+ "verification.test_macro_f1", "0.378373"),
66
+ ("change_vqa test2 accuracy", "artifacts/change_vqa/run/PROMOTION.json",
67
+ "verification.test2_accuracy", "0.651469"),
68
+ ("change_vqa test2 macro_F1", "artifacts/change_vqa/run/PROMOTION.json",
69
+ "verification.test2_macro_f1", "0.372309"),
70
+ ("router overall ungated accuracy", "artifacts/router/threshold_sweep_val.json",
71
+ "overall_ungated_accuracy", "0.965116"),
72
+ ("calibration ECE before scaling", "artifacts/calibration_v001.json",
73
+ "metrics.ece_before", "0.013755"),
74
+ ("calibration ECE after scaling", "artifacts/calibration_v001.json",
75
+ "metrics.ece_after", "0.014929"),
76
+ ("VLM adapter exact_match", "artifacts/vlm/phase6_closure.json",
77
+ "why_usable_verified.adapted_test.exact_match", "0.963"),
78
+ ("VLM adapter F1", "artifacts/vlm/phase6_closure.json",
79
+ "why_usable_verified.adapted_test.f1", "0.96432"),
80
+ ]
81
+
82
+
83
+ def get(obj, dotted):
84
+ """Resolve a dotted path, preferring the LONGEST matching key at each step.
85
+
86
+ Needed because some artifact keys themselves contain dots (e.g. the recall
87
+ dict is keyed "0.10"/"0.25"/"0.50"), so a naive split(".") walk would break
88
+ `results.head_threshold.recall.0.50` into ...recall -> 0 -> 50 and fail.
89
+ """
90
+ return _get(obj, dotted.split("."))
91
+
92
+
93
+ def _get(cur, parts):
94
+ if not parts:
95
+ return cur, True
96
+ if isinstance(cur, dict):
97
+ for i in range(len(parts), 0, -1): # longest key first
98
+ key = ".".join(parts[:i])
99
+ if key in cur:
100
+ return _get(cur[key], parts[i:])
101
+ return None, False
102
+ if isinstance(cur, list):
103
+ return _get(cur[int(parts[0])], parts[1:])
104
+ return None, False
105
+
106
+
107
+ def decimals(s):
108
+ return len(s.split(".")[1]) if "." in s else 0
109
+
110
+
111
+ fails = 0
112
+ print(f"{'STATUS':8} {'claim':48} {'artifact':>14} {'readme':>12} source")
113
+ print("-" * 118)
114
+ for label, rel, key, claimed in CLAIMS:
115
+ path = os.path.join(ROOT, rel)
116
+ if not os.path.exists(path):
117
+ print(f"{'NOFILE':8} {label:48} {'':>14} {claimed:>12} {rel}")
118
+ fails += 1
119
+ continue
120
+ data = json.load(open(path, encoding="utf-8"))
121
+ val, found = get(data, key)
122
+ if not found:
123
+ print(f"{'NOKEY':8} {label:48} {'':>14} {claimed:>12} {rel}#{key}")
124
+ fails += 1
125
+ continue
126
+ ok = round(float(val), decimals(claimed)) == float(claimed)
127
+ if not ok:
128
+ fails += 1
129
+ print(f"{'MATCH' if ok else 'DIFFER':8} {label:48} {val:>14} {claimed:>12} {rel}#{key}")
130
+
131
+ # --- artifact-declared statuses that the README also asserts ---
132
+ print()
133
+ print("=== status assertions ===")
134
+ d = json.load(open(os.path.join(ROOT, "artifacts/vlm/phase6_closure.json"), encoding="utf-8"))
135
+ hl = d.get("headline", "")
136
+ print(f" VLM headline contains ACCEPTANCE-REJECTED : {'ACCEPTANCE-REJECTED' in hl}")
137
+ print(f" VLM status : {d.get('status')}")
138
+ r = json.load(open(os.path.join(ROOT, "artifacts/router/threshold_sweep_val.json"), encoding="utf-8"))
139
+ print(f" router corpus_limited : {r.get('corpus_limited')}")
140
+ print(f" router n_val : {r.get('n_val')}")
141
+ c = json.load(open(os.path.join(ROOT, "artifacts/calibration_v001.json"), encoding="utf-8"))
142
+ print(f" calibration temperature (temperature_scaling.temperature) : "
143
+ f"{c.get('temperature_scaling', {}).get('temperature')}")
144
+ print(f" calibration ece_improvement : "
145
+ f"{c.get('metrics', {}).get('ece_improvement')} (negative => calibration did NOT help)")
146
+
147
+ print()
148
+ print(f"RESULT: {'ALL CLAIMS VERIFIED' if fails == 0 else str(fails) + ' CLAIM(S) FAILED'}")
149
+ sys.exit(0 if fails == 0 else 1)