120 lines
4.4 KiB
Python
120 lines
4.4 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_expected(value: str) -> list[int]:
|
|
values: list[int] = []
|
|
for token in value.split(','):
|
|
token = token.strip()
|
|
if not token:
|
|
continue
|
|
if '-' in token:
|
|
a, b = token.split('-', 1)
|
|
start, end = int(a), int(b)
|
|
if end < start:
|
|
raise ValueError(f'invalid expected zoom range: {token}')
|
|
values.extend(range(start, end + 1))
|
|
else:
|
|
values.append(int(token))
|
|
return sorted(set(values))
|
|
|
|
|
|
def parse_args():
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument('--plan', default='build/production/cm95_production_plan_v1.json')
|
|
p.add_argument('--output', default='build/production/cm95_production_plan_audit_v1.json')
|
|
p.add_argument('--expected-zooms', default='0-12')
|
|
return p.parse_args()
|
|
|
|
|
|
def main():
|
|
a = parse_args()
|
|
plan = json.loads(Path(a.plan).read_text(encoding='utf-8'))
|
|
jobs = plan.get('jobs') or []
|
|
keys = [(int(j['z']), int(j['x']), int(j['y'])) for j in jobs]
|
|
unique = set(keys)
|
|
by_zoom = Counter(z for z, _, _ in keys)
|
|
invalid = []
|
|
for z, x, y in keys:
|
|
n = 1 << z
|
|
if z < 0 or x < 0 or y < 0 or x >= n or y >= n:
|
|
invalid.append({'z': z, 'x': x, 'y': y})
|
|
|
|
source_stats = plan.get('source_stats') or {}
|
|
stats_mismatch = []
|
|
for z, count in sorted(by_zoom.items()):
|
|
declared = int((source_stats.get(str(z)) or {}).get('tile_count') or 0)
|
|
if declared != count:
|
|
stats_mismatch.append({'z': z, 'declared': declared, 'actual': count})
|
|
|
|
expected = parse_expected(a.expected_zooms)
|
|
planned_zooms = sorted(int(z) for z in (plan.get('zooms') or []))
|
|
missing_plan_zooms = sorted(set(expected) - set(planned_zooms))
|
|
unexpected_plan_zooms = sorted(set(planned_zooms) - set(expected))
|
|
missing_job_zooms = sorted(z for z in expected if by_zoom.get(z, 0) == 0)
|
|
|
|
failures = []
|
|
if len(unique) != len(keys):
|
|
failures.append('duplicate_jobs')
|
|
if invalid:
|
|
failures.append('invalid_xyz')
|
|
if int(plan.get('job_count') or 0) != len(keys):
|
|
failures.append('job_count_mismatch')
|
|
if stats_mismatch:
|
|
failures.append('source_stats_mismatch')
|
|
if missing_plan_zooms:
|
|
failures.append('missing_plan_zooms')
|
|
if unexpected_plan_zooms:
|
|
failures.append('unexpected_plan_zooms')
|
|
if missing_job_zooms:
|
|
failures.append('missing_job_zooms')
|
|
|
|
result = {
|
|
'schema': 'navsea.cm95.production-plan-audit/v1.1',
|
|
'plan_id': plan.get('plan_id'),
|
|
'release_id': plan.get('release_id'),
|
|
'dataset_id': plan.get('dataset_id'),
|
|
'builder_contract_version': plan.get('builder_contract_version'),
|
|
'expected_zooms': expected,
|
|
'planned_zooms': planned_zooms,
|
|
'job_count': len(keys),
|
|
'unique_job_count': len(unique),
|
|
'tile_count_by_zoom': {str(k): v for k, v in sorted(by_zoom.items())},
|
|
'missing_plan_zooms': missing_plan_zooms,
|
|
'unexpected_plan_zooms': unexpected_plan_zooms,
|
|
'missing_job_zooms': missing_job_zooms,
|
|
'invalid_xyz': invalid,
|
|
'source_stats_mismatch': stats_mismatch,
|
|
'failures': failures,
|
|
'overall': 'PASS' if not failures else 'FAIL',
|
|
}
|
|
out = Path(a.output)
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
out.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding='utf-8')
|
|
|
|
print(f"Plan ID : {result['plan_id']}")
|
|
print(f"Release ID : {result['release_id']}")
|
|
if result.get('builder_contract_version'):
|
|
print(f"Builder : {result['builder_contract_version']}")
|
|
print(f"Expected zooms : {a.expected_zooms}")
|
|
print(f"Jobs : {result['job_count']}")
|
|
for z in expected:
|
|
stat = source_stats.get(str(z)) or {}
|
|
enum_scales = stat.get('enumeration_scales') or []
|
|
ladder = stat.get('scale_ladder') or []
|
|
print(f"z{z} tiles : {by_zoom.get(z, 0)} enum={enum_scales} fallback={ladder}")
|
|
print(f"Duplicate jobs : {len(keys)-len(unique)}")
|
|
print(f"Invalid XYZ : {len(invalid)}")
|
|
print(f"Missing zooms : {len(missing_plan_zooms) + len(missing_job_zooms)}")
|
|
print(f"OVERALL : {result['overall']}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|