Files
CM95/tools/cm95_mapping_contract_check_v1.py

191 lines
5.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
r"""
CM95 → NavSea Mapping Contract 静态检查 v1
检查:
1. mapping 的正式 target_source_layer 是否被当前 style 实际消费;
2. chart_icon_image / chart_fill_pattern / chart_line_pattern 常量是否存在于 semantic sprite;
3. cm95_unmapped 不视为正式 style layer;
4. 输出 PASS/FAIL JSON。
示例:
python .\tools\cm95_mapping_contract_check_v1.py `
--mapping .\config\mappings\cm95_to_navsea_v1.yaml `
--style D:\path\style.navsea-delivery-full-semantic-unified-20260808.json `
--sprite-json D:\path\semantic-sprite\sprite.json
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import yaml
SPRITE_FIELDS = {
"chart_icon_image",
"chart_fill_pattern",
"chart_line_pattern",
}
def parse_args():
p = argparse.ArgumentParser()
p.add_argument("--mapping", required=True)
p.add_argument("--style", required=True)
p.add_argument("--sprite-json", required=True)
p.add_argument("--output", default="cm95_mapping_contract_check_v1.json")
return p.parse_args()
def const_value(v):
if isinstance(v, dict) and set(v.keys()) == {"const"}:
return v["const"]
if isinstance(v, str):
return v
return None
def collect_sprite_refs(style_fields, path="style_fields"):
out = []
if not isinstance(style_fields, dict):
return out
for k, v in style_fields.items():
p = f"{path}.{k}"
if k in SPRITE_FIELDS:
c = const_value(v)
if isinstance(c, str) and c:
out.append((p, c))
if isinstance(v, dict):
out.extend(collect_sprite_refs(v, p))
elif isinstance(v, list):
for i, item in enumerate(v):
if isinstance(item, dict):
out.extend(
collect_sprite_refs(item, f"{p}[{i}]")
)
return out
def main():
a = parse_args()
mapping = yaml.safe_load(
Path(a.mapping).read_text(encoding="utf-8")
)
style = json.loads(
Path(a.style).read_text(encoding="utf-8")
)
sprite = json.loads(
Path(a.sprite_json).read_text(encoding="utf-8")
)
style_layers = {
layer.get("source-layer")
for layer in style.get("layers", [])
if layer.get("source") == "navsea_delivery"
and layer.get("source-layer")
}
sprite_keys = set(sprite)
layer_failures = []
sprite_failures = []
for rule in mapping.get("rules", []):
rid = rule.get("rule_id")
target = rule.get("target_source_layer")
if (
target
and target != "cm95_unmapped"
and target not in style_layers
):
layer_failures.append({
"rule_id": rid,
"target_source_layer": target,
})
for p, key in collect_sprite_refs(
rule.get("style_fields", {})
):
if key not in sprite_keys:
sprite_failures.append({
"rule_id": rid,
"path": p,
"sprite_key": key,
})
for i, derived in enumerate(
rule.get("derived_outputs", []) or []
):
layer = derived.get("source_layer")
if (
layer
and layer != "cm95_unmapped"
and layer not in style_layers
):
layer_failures.append({
"rule_id": rid,
"derived_index": i,
"target_source_layer": layer,
})
for p, key in collect_sprite_refs(
derived.get("style_fields", {}),
f"derived_outputs[{i}].style_fields",
):
if key not in sprite_keys:
sprite_failures.append({
"rule_id": rid,
"path": p,
"sprite_key": key,
})
result = {
"mapping": str(Path(a.mapping).resolve()),
"style": str(Path(a.style).resolve()),
"sprite_json": str(Path(a.sprite_json).resolve()),
"style_source_layer_count": len(style_layers),
"sprite_key_count": len(sprite_keys),
"rule_count": len(mapping.get("rules", [])),
"layer_failures": layer_failures,
"sprite_failures": sprite_failures,
"failure_count": (
len(layer_failures) + len(sprite_failures)
),
}
result["overall"] = (
"PASS" if result["failure_count"] == 0 else "FAIL"
)
Path(a.output).write_text(
json.dumps(result, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(
f"Rules : {result['rule_count']}"
)
print(
f"Style source layers : {result['style_source_layer_count']}"
)
print(
f"Sprite keys : {result['sprite_key_count']}"
)
print(
f"Layer failures : {len(layer_failures)}"
)
print(
f"Sprite failures : {len(sprite_failures)}"
)
print(
f"OVERALL : {result['overall']}"
)
if __name__ == "__main__":
main()