#!/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()