fix: finalize S-52 runtime production contract
This commit is contained in:
95
docs/报告/CM95_S52_ProductionPipelineV2与九州运行报告_v2.md
Normal file
95
docs/报告/CM95_S52_ProductionPipelineV2与九州运行报告_v2.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# CM95 S-52 Production Pipeline v2 与九州运行报告 v2
|
||||||
|
|
||||||
|
日期:2026-09-18
|
||||||
|
|
||||||
|
## 1. 本次收尾范围
|
||||||
|
|
||||||
|
本报告记录九州工程 AOI 的 S-52 Production Pipeline v2 正式生产、最终 release gate、runtime contract 修正与 finalize 结果。
|
||||||
|
|
||||||
|
- Release ID:`cm95-d1-62a85bd40d3a-bbaaeafee1b4`
|
||||||
|
- Plan ID:`bbaaeafee1b400dc97bcc774ec36247cbc4cf68860b267dea8befe1bdd890564`
|
||||||
|
- Pipeline:`cm95pbf-production/2.0-s52-aoi`
|
||||||
|
- Builder contract:`cm95pbf-builder/0.4.8-s52-mariner-runtime-boundary`
|
||||||
|
- AOI:`kyushu-main-nearby-v1`
|
||||||
|
- bbox:`128.0,29.5,132.5,34.8`
|
||||||
|
- profile:`delivery`
|
||||||
|
- S-52:enabled,point=`Paper`,boundary=`Plain`,mariner_mode=`runtime`
|
||||||
|
- Lookup SHA256:`f63cbc0d3c9317b1873c0b0c470a5e7371d1b6d8d58393da660f70b837a52eb7`
|
||||||
|
|
||||||
|
该 bbox 是工程测试范围,不是九州行政边界。
|
||||||
|
|
||||||
|
## 2. Production 最终结果
|
||||||
|
|
||||||
|
生产 ledger 最终为:
|
||||||
|
|
||||||
|
- jobs:1109
|
||||||
|
- PASS:1109
|
||||||
|
- FAILED:0
|
||||||
|
- PBF:1109
|
||||||
|
- planned output features:393856
|
||||||
|
- report output features:393856
|
||||||
|
- decoded PBF features:393856
|
||||||
|
|
||||||
|
因此 planned / report / decoded 三者完全一致,PBF 数量与 Plan job 数完全一致。
|
||||||
|
|
||||||
|
各 zoom job 数保持 Plan 冻结值:z5=2、z6=4、z7=12、z8=24、z9=70、z10=197、z11=503、z12=297;z0-z4 为 0。
|
||||||
|
|
||||||
|
## 3. 首次 finalize 发现的问题
|
||||||
|
|
||||||
|
首次执行 `finalize-production --skip-style-contract` 时,PBF 技术数量与 Feature 数均正常,但 S-52 runtime contract 报 134 个失败。
|
||||||
|
|
||||||
|
调查确认这 134 个并不是 PBF 生成错误,而是 release gate 对“源数据合法未知值”要求过严:
|
||||||
|
|
||||||
|
- 133 个 `OBSTRN04`:全部为 `EXPSOU=1` 且源对象没有 `VALSOU`;此时 hazard depth 本来就是未知,运行时应结合 EXPSOU、WATLEV 与周围 DEPARE 摘要判断,不能伪造 `s52_hazard_depth_m`。
|
||||||
|
- 1 个 `DEPARE01`:对象为 `DRGARE`,源记录 `attributes_json={}`;reference CSP 本来就对“疏浚区缺 DRVAL1”有专门缺省呈现规则,不能伪造 `depth_min_m`。
|
||||||
|
|
||||||
|
典型源记录核对:
|
||||||
|
|
||||||
|
- `cm95:1:03300360/D/03630390.D:955:point`:`UWTROC`,属性为 `{"EXPSOU": 1, "WATLEV": 3}`。
|
||||||
|
- `cm95:1:03300360/E/03710393.E:27:area`:`DRGARE`,属性为 `{}`。
|
||||||
|
|
||||||
|
因此收尾修正采用“保留未知事实 + gate 明确记录例外”的方式,没有为了通过 finalize 向 PBF 填充虚构深度,也没有重建 1109 个已经正确生成的 PBF。
|
||||||
|
|
||||||
|
## 4. Runtime contract gate 修正
|
||||||
|
|
||||||
|
`src/cm95pbf/release.py` 的 runtime gate 已改为:
|
||||||
|
|
||||||
|
- 普通 DEPARE/DEPARE02 仍必须有 `depth_min_m`;
|
||||||
|
- 仅 `canonical_object_type=dredged_area` 且源深度未知时允许缺失,并记录例外;
|
||||||
|
- OBSTRN04/WRECKS02 默认仍必须有 `s52_hazard_depth_m`;
|
||||||
|
- 仅 `OBSTRN04 + s52_expsou_code=1` 的未知 hazard depth 允许缺失,并记录例外;
|
||||||
|
- 其他缺失仍然 fail-closed;
|
||||||
|
- dynamic final state 泄漏规则不变。
|
||||||
|
|
||||||
|
Standalone runtime contract checker 同步采用同一语义,避免工具与 finalize gate 不一致。
|
||||||
|
|
||||||
|
新增审计字段:`unknown_fact_exceptions`。
|
||||||
|
|
||||||
|
## 5. 最终 finalize 结果
|
||||||
|
|
||||||
|
再次执行 `finalize-production --skip-style-contract`:
|
||||||
|
|
||||||
|
- release status:**PASS**
|
||||||
|
- S-52 runtime contract:**PASS**
|
||||||
|
- mariner dependent features:237849
|
||||||
|
- DEPARE01:35722
|
||||||
|
- DEPCNT02:37634
|
||||||
|
- OBSTRN04:35661
|
||||||
|
- SOUNDG02:128126
|
||||||
|
- WRECKS02:706
|
||||||
|
- `dynamic_final_leaks = {}`
|
||||||
|
- `failure_count = 0`
|
||||||
|
- `unknown_fact_exceptions = {"DEPARE:dredged_area_missing_depth_min": 1, "OBSTRN04:EXPSOU1_unknown_hazard_depth": 133}`
|
||||||
|
- nav name conflict audit:**PASS**
|
||||||
|
- quantization fallbacks:10
|
||||||
|
- quantization omissions:154
|
||||||
|
- non-renderable omissions:2,均为 `seabed_text_point`
|
||||||
|
- release failures:`[]`
|
||||||
|
|
||||||
|
`--skip-style-contract` 仅跳过旧 NavSea semantic style/sprite contract;本次 PBF 技术检查与 S-52 runtime contract 均实际执行并通过。
|
||||||
|
|
||||||
|
## 6. 尚未宣称完成的范围
|
||||||
|
|
||||||
|
`semantic_completeness` 仍为 `PENDING_REVIEW`,当前仍有 112 个 pending semantic review variants / 52828 rows。该项不是本次九州 Production Pipeline v2 的失败 gate,因此不能据此声称全部 ENC/S-52 对象语义已经完整支持。
|
||||||
|
|
||||||
|
本次收尾没有修改或提交现有 Linux/MySQL migration dirty 文件;只处理 S-52 runtime release gate、对应检查工具和本报告。
|
||||||
@@ -345,6 +345,7 @@ def finalize_release(
|
|||||||
s52_runtime_dependent_count = 0
|
s52_runtime_dependent_count = 0
|
||||||
s52_runtime_leaks = Counter()
|
s52_runtime_leaks = Counter()
|
||||||
s52_runtime_fact_counts = Counter()
|
s52_runtime_fact_counts = Counter()
|
||||||
|
s52_runtime_unknown_fact_exceptions = Counter()
|
||||||
s52_dynamic_final_fields = {
|
s52_dynamic_final_fields = {
|
||||||
"s52_is_safety_contour",
|
"s52_is_safety_contour",
|
||||||
"s52_sounding_prefix",
|
"s52_sounding_prefix",
|
||||||
@@ -379,7 +380,14 @@ def finalize_release(
|
|||||||
s52_runtime_leaks[key] += 1
|
s52_runtime_leaks[key] += 1
|
||||||
if csp in {"DEPARE01", "DEPARE02"}:
|
if csp in {"DEPARE01", "DEPARE02"}:
|
||||||
if "depth_min_m" not in props:
|
if "depth_min_m" not in props:
|
||||||
s52_runtime_failures.append(f"{pbf_rel}:{layer}:{fid} {csp} missing depth_min_m")
|
if props.get("canonical_object_type") == "dredged_area":
|
||||||
|
s52_runtime_unknown_fact_exceptions[
|
||||||
|
"DEPARE:dredged_area_missing_depth_min"
|
||||||
|
] += 1
|
||||||
|
else:
|
||||||
|
s52_runtime_failures.append(
|
||||||
|
f"{pbf_rel}:{layer}:{fid} {csp} missing depth_min_m"
|
||||||
|
)
|
||||||
if "s52_area_color" in props:
|
if "s52_area_color" in props:
|
||||||
s52_runtime_leaks["DEPARE:s52_area_color"] += 1
|
s52_runtime_leaks["DEPARE:s52_area_color"] += 1
|
||||||
elif csp == "DEPCNT02" and "contour_depth_m" not in props:
|
elif csp == "DEPCNT02" and "contour_depth_m" not in props:
|
||||||
@@ -387,7 +395,14 @@ def finalize_release(
|
|||||||
elif csp == "SOUNDG02" and "s52_sounding_depth_m" not in props:
|
elif csp == "SOUNDG02" and "s52_sounding_depth_m" not in props:
|
||||||
s52_runtime_failures.append(f"{pbf_rel}:{layer}:{fid} SOUNDG02 missing s52_sounding_depth_m")
|
s52_runtime_failures.append(f"{pbf_rel}:{layer}:{fid} SOUNDG02 missing s52_sounding_depth_m")
|
||||||
elif csp in {"OBSTRN04", "WRECKS02"} and "s52_hazard_depth_m" not in props:
|
elif csp in {"OBSTRN04", "WRECKS02"} and "s52_hazard_depth_m" not in props:
|
||||||
s52_runtime_failures.append(f"{pbf_rel}:{layer}:{fid} {csp} missing s52_hazard_depth_m")
|
if csp == "OBSTRN04" and props.get("s52_expsou_code") == 1:
|
||||||
|
s52_runtime_unknown_fact_exceptions[
|
||||||
|
"OBSTRN04:EXPSOU1_unknown_hazard_depth"
|
||||||
|
] += 1
|
||||||
|
else:
|
||||||
|
s52_runtime_failures.append(
|
||||||
|
f"{pbf_rel}:{layer}:{fid} {csp} missing s52_hazard_depth_m"
|
||||||
|
)
|
||||||
elif "s52_runtime_csp" in props:
|
elif "s52_runtime_csp" in props:
|
||||||
s52_runtime_failures.append(f"{pbf_rel}:{layer}:{fid} runtime CSP without mariner-dependent marker")
|
s52_runtime_failures.append(f"{pbf_rel}:{layer}:{fid} runtime CSP without mariner-dependent marker")
|
||||||
for key in (
|
for key in (
|
||||||
@@ -460,6 +475,7 @@ def finalize_release(
|
|||||||
"runtime_csp_counts": dict(sorted(s52_runtime_csp_counts.items())),
|
"runtime_csp_counts": dict(sorted(s52_runtime_csp_counts.items())),
|
||||||
"runtime_layer_counts": dict(sorted(s52_runtime_layer_counts.items())),
|
"runtime_layer_counts": dict(sorted(s52_runtime_layer_counts.items())),
|
||||||
"fact_counts": dict(sorted(s52_runtime_fact_counts.items())),
|
"fact_counts": dict(sorted(s52_runtime_fact_counts.items())),
|
||||||
|
"unknown_fact_exceptions": dict(sorted(s52_runtime_unknown_fact_exceptions.items())),
|
||||||
"dynamic_final_leaks": dict(sorted(s52_runtime_leaks.items())),
|
"dynamic_final_leaks": dict(sorted(s52_runtime_leaks.items())),
|
||||||
"failure_count": len(s52_runtime_failures),
|
"failure_count": len(s52_runtime_failures),
|
||||||
"failure_examples": s52_runtime_failures[:100],
|
"failure_examples": s52_runtime_failures[:100],
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ def audit_runtime(features: list[tuple[str, dict[str, Any]]]) -> dict[str, Any]:
|
|||||||
|
|
||||||
csp_counts = Counter(str(props.get("s52_runtime_csp") or "") for _, props in runtime)
|
csp_counts = Counter(str(props.get("s52_runtime_csp") or "") for _, props in runtime)
|
||||||
leaked = Counter()
|
leaked = Counter()
|
||||||
|
unknown_fact_exceptions = Counter()
|
||||||
for _, props in runtime:
|
for _, props in runtime:
|
||||||
csp = str(props.get("s52_runtime_csp") or "")
|
csp = str(props.get("s52_runtime_csp") or "")
|
||||||
if not csp:
|
if not csp:
|
||||||
@@ -51,6 +52,9 @@ def audit_runtime(features: list[tuple[str, dict[str, Any]]]) -> dict[str, Any]:
|
|||||||
leaked[key] += 1
|
leaked[key] += 1
|
||||||
if csp in {"DEPARE01", "DEPARE02"}:
|
if csp in {"DEPARE01", "DEPARE02"}:
|
||||||
if "depth_min_m" not in props:
|
if "depth_min_m" not in props:
|
||||||
|
if props.get("canonical_object_type") == "dredged_area":
|
||||||
|
unknown_fact_exceptions["DEPARE:dredged_area_missing_depth_min"] += 1
|
||||||
|
else:
|
||||||
failures.append(f"{csp} 缺少 depth_min_m")
|
failures.append(f"{csp} 缺少 depth_min_m")
|
||||||
if "s52_area_color" in props:
|
if "s52_area_color" in props:
|
||||||
leaked["DEPARE:s52_area_color"] += 1
|
leaked["DEPARE:s52_area_color"] += 1
|
||||||
@@ -58,6 +62,11 @@ def audit_runtime(features: list[tuple[str, dict[str, Any]]]) -> dict[str, Any]:
|
|||||||
failures.append("DEPCNT02 缺少 contour_depth_m")
|
failures.append("DEPCNT02 缺少 contour_depth_m")
|
||||||
elif csp == "SOUNDG02" and "s52_sounding_depth_m" not in props:
|
elif csp == "SOUNDG02" and "s52_sounding_depth_m" not in props:
|
||||||
failures.append("SOUNDG02 缺少 s52_sounding_depth_m")
|
failures.append("SOUNDG02 缺少 s52_sounding_depth_m")
|
||||||
|
elif csp in {"OBSTRN04", "WRECKS02"} and "s52_hazard_depth_m" not in props:
|
||||||
|
if csp == "OBSTRN04" and props.get("s52_expsou_code") == 1:
|
||||||
|
unknown_fact_exceptions["OBSTRN04:EXPSOU1_unknown_hazard_depth"] += 1
|
||||||
|
else:
|
||||||
|
failures.append(f"{csp} 缺少 s52_hazard_depth_m")
|
||||||
|
|
||||||
if leaked:
|
if leaked:
|
||||||
failures.append(f"发现 runtime 最终状态泄漏: {dict(leaked)}")
|
failures.append(f"发现 runtime 最终状态泄漏: {dict(leaked)}")
|
||||||
@@ -79,6 +88,7 @@ def audit_runtime(features: list[tuple[str, dict[str, Any]]]) -> dict[str, Any]:
|
|||||||
"s52_surrounding_min_drval2_m",
|
"s52_surrounding_min_drval2_m",
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
"unknown_fact_exceptions": dict(unknown_fact_exceptions),
|
||||||
"dynamic_final_leaks": dict(leaked),
|
"dynamic_final_leaks": dict(leaked),
|
||||||
"failures": failures,
|
"failures": failures,
|
||||||
"status": "PASS" if not failures else "FAIL",
|
"status": "PASS" if not failures else "FAIL",
|
||||||
|
|||||||
Reference in New Issue
Block a user