Files
CM95/tools/cm95_pbf_build_reports_audit_v1.py

113 lines
4.5 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations
import argparse
from collections import Counter
import json
from pathlib import Path
def parse_args():
p = argparse.ArgumentParser()
p.add_argument("--report-root", default="build/pbf-reports")
p.add_argument("--profile", choices=["delivery", "engineering"], default="delivery")
p.add_argument("--output", default="cm95_pbf_build_reports_audit_v1.json")
return p.parse_args()
def main():
a = parse_args()
root = Path(a.report_root) / a.profile
files = sorted(
p for p in root.rglob("*.json")
if not p.name.endswith(".trace.json")
)
layer_counts = Counter()
unmapped = Counter()
hard_gate = []
total_input = 0
total_output = 0
relation_groups = 0
standalone_lights = 0
named_from_direct_source = 0
name_conflict_groups = 0
quantization_fallbacks = 0
quantization_omissions = 0
quantization_omission_layers = Counter()
encoder_dropped_features = []
planned_output = 0
for p in files:
obj = json.loads(p.read_text(encoding="utf-8"))
total_input += int(obj.get("input_feature_count") or 0)
total_output += int(obj.get("output_feature_count") or 0)
planned_output += int(obj.get("planned_output_feature_count") or obj.get("output_feature_count") or 0)
layer_counts.update(obj.get("output_layer_counts") or {})
unmapped.update(obj.get("unmapped_class_counts") or {})
nav = obj.get("navigation_composites") or {}
relation_groups += int(nav.get("groups") or 0)
standalone_lights += int(nav.get("standalone_lights") or 0)
named_from_direct_source += int(nav.get("named_from_direct_source") or 0)
name_conflict_groups += int(nav.get("name_conflict_groups") or 0)
quantization_fallbacks += len(obj.get("quantization_fallbacks") or [])
omissions = obj.get("quantization_omissions") or []
quantization_omissions += len(omissions)
for item in omissions:
if isinstance(item, dict):
quantization_omission_layers[str(item.get("layer") or "UNKNOWN")] += 1
for item in obj.get("encoder_dropped_features") or []:
encoder_dropped_features.append({"file": str(p), **item})
for item in obj.get("hard_gate_unmapped") or []:
hard_gate.append({"file": str(p), **item})
result = {
"schema": "navsea.cm95.build-report-audit/v1",
"profile": a.profile,
"report_file_count": len(files),
"input_rows_considered": total_input,
"planned_output_features": planned_output,
"output_features": total_output,
"output_layer_counts": dict(layer_counts),
"unmapped_class_counts": dict(unmapped),
"navigation_relation_groups": relation_groups,
"standalone_lights": standalone_lights,
"nav_named_from_direct_source": named_from_direct_source,
"nav_name_conflict_groups": name_conflict_groups,
"quantization_fallbacks": quantization_fallbacks,
"quantization_omissions": quantization_omissions,
"quantization_omission_layers": dict(sorted(quantization_omission_layers.items())),
"encoder_dropped_features": encoder_dropped_features,
"hard_gate_unmapped": hard_gate,
"failure_count": len(hard_gate) + name_conflict_groups + len(encoder_dropped_features) + (1 if planned_output != total_output else 0),
"overall": (
"PASS"
if not hard_gate and name_conflict_groups == 0 and not encoder_dropped_features and planned_output == total_output
else "FAIL"
),
}
Path(a.output).write_text(
json.dumps(result, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(f"Reports : {len(files)}")
print(f"Input rows : {total_input}")
print(f"Planned output : {planned_output}")
print(f"Output features : {total_output}")
print(f"Navigation composites : {relation_groups}")
print(f"Standalone LIGHTS : {standalone_lights}")
print(f"Direct nav names : {named_from_direct_source}")
print(f"Nav name conflicts : {name_conflict_groups}")
print(f"Hard-gate unmapped : {len(hard_gate)}")
print(f"Quantization fallback : {quantization_fallbacks}")
print(f"Quantization omissions : {quantization_omissions}")
print(f"Encoder dropped : {len(encoder_dropped_features)}")
print(f"OVERALL : {result['overall']}")
if __name__ == "__main__":
main()