Files
FreeCADWasmDev/tools/generate_full_parity_audit.py
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

190 lines
8.2 KiB
Python
Raw 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
"""Generate the bounded source-backed Full Parity audit baseline."""
from __future__ import annotations
import json
import subprocess
from collections import Counter
from datetime import date
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DOCS = ROOT / "docs"
def revision() -> str:
try:
return subprocess.run(
["git", "-C", str(ROOT / "freecad-source"), "rev-parse", "HEAD"],
check=True,
capture_output=True,
text=True,
).stdout.strip()
except (OSError, subprocess.CalledProcessError):
return "unknown"
workbenches = json.loads((DOCS / "workbench-inventory.json").read_text())
objects = json.loads((DOCS / "object-inventory.json").read_text())
format_api = json.loads((DOCS / "format-api-inventory.json").read_text())
matrix = json.loads((DOCS / "web-parity-matrix.json").read_text())
modules = workbenches["modules"]
object_rows = objects["objects"]
format_rows = format_api["formats"]
api_rows = format_api["python_api"]
extensions = sorted({str(item["extension"]) for item in format_rows if item.get("extension")})
module_status = {row["module"]: row.get("web_status", "pending") for row in matrix["rows"]}
web_counts = {
"htmlEntrypoints": len(list(ROOT.joinpath("web").glob("*.html"))),
"workers": len(list((ROOT / "web" / "src").glob("*.worker.ts"))),
"facades": len(list((ROOT / "web" / "src").glob("*facade.ts"))),
"nativeProbeSources": len(list((ROOT / "freecad-source" / "src" / "WebAssembly" / "FreeCADCoreProbe").glob("*Probe.cpp"))),
}
baseline = {
"sourceModules": len(modules),
"appModules": sum(bool(item.get("has_app")) for item in modules),
"guiModules": sum(bool(item.get("has_gui")) for item in modules),
"guiOnlyModules": sum(bool(item.get("has_gui")) and not bool(item.get("has_app")) for item in modules),
"noAppNoGuiModules": sum(not bool(item.get("has_gui")) and not bool(item.get("has_app")) for item in modules),
"objectTypes": len(object_rows),
"objectModules": len({str(item["module"]) for item in object_rows}),
"formatRecords": len(format_rows),
"uniqueFormatExtensions": len(extensions),
"pythonApiRecords": len(api_rows),
"pythonApiModules": len({str(item["module"]) for item in api_rows}),
"web": web_counts,
"p6ModuleGatesCompleted": sum(status == "completed" for status in module_status.values()),
}
gaps = [
{
"id": "FPA-CORE",
"area": "core-shell",
"priority": "P0",
"status": "planned",
"evidence": ["web/src/main.ts", "docs/current/FREECAD_WEB_MIGRATION_ANALYSIS.md"],
"scope": "统一 React 工作区、Document actor、命令/错误/取消/进度、树/属性/选择/视图状态。",
"exit": "FW-P6-FPA-B + FW-P6-FPA-C",
},
{
"id": "FPA-DOC",
"area": "document-persistence",
"priority": "P0",
"status": "planned",
"evidence": ["docs/p6/sketcher-action-04-kernel.json", "docs/p6/techdraw-action-05-persistence-native.json"],
"scope": "多对象图、跨模块链接、属性、重算、FCStd、Undo/Redo、事务和恢复。",
"exit": "FW-P6-FPA-C",
},
{
"id": "FPA-OBJ",
"area": "object-coverage",
"priority": "P0",
"status": "planned",
"count": len(object_rows),
"evidence": ["docs/object-inventory.json", "docs/web-parity-matrix.json"],
"scope": "逐对象 native create/recompute/inspect/property/view/persistence 适用性。",
"exit": "FW-P6-FPA-D",
},
{
"id": "FPA-FMT",
"area": "format-coverage",
"priority": "P0",
"status": "planned",
"count": len(format_rows),
"evidence": ["docs/format-api-inventory.json"],
"scope": "逐记录 handler、参数、错误、恢复、互操作和浏览器回放。",
"exit": "FW-P6-FPA-E",
},
{
"id": "FPA-PY",
"area": "python-api",
"priority": "P0",
"status": "planned",
"count": len(api_rows),
"evidence": ["docs/format-api-inventory.json", "docs/current/FREECAD_WEB_MIGRATION_ANALYSIS.md"],
"scope": "完整 API 分类、binding、权限、参数/返回值、错误和受限执行。",
"exit": "FW-P6-FPA-F",
},
{
"id": "FPA-GUI",
"area": "workbench-gui",
"priority": "P0",
"status": "planned",
"count": sum(bool(item.get("has_gui")) for item in modules),
"evidence": ["docs/workbench-inventory.json", "freecad-source/src/Gui"],
"scope": "32 模块、22 GUI 模块的命令、任务面板、资源、选择和视图行为。",
"exit": "FW-P6-FPA-G",
},
{
"id": "FPA-UX",
"area": "rendering-interaction",
"priority": "P1",
"status": "planned",
"evidence": ["web/src/main.ts", "web/package.json"],
"scope": "Three.js 渲染、选择/预选、相机、标准视图、属性编辑、快捷键、响应式和可访问性。",
"exit": "FW-P6-FPA-H",
},
{
"id": "FPA-QUAL",
"area": "quality-release",
"priority": "P1",
"status": "planned",
"evidence": ["docs/current/FREECAD_WEB_MIGRATION_ANALYSIS.md", "tools/run_p6_chrome_harness.mjs"],
"scope": "性能、内存、CSP/权限、无障碍、兼容性和发布 gate。",
"exit": "FW-P6-FPA-I",
},
]
report = {
"schema": "freecad-web-full-parity-audit/v1",
"generatedAt": date.today().isoformat(),
"sourceRevision": revision(),
"truth": "P6 module completed means bounded slice gate; Full Parity remains planned until every audit record has a conclusion.",
"baseline": baseline,
"moduleStatus": module_status,
"formatExtensions": extensions,
"gaps": gaps,
"roadmap": [
{"task": "FW-P6-FPA-A", "area": "audit", "status": "in_progress"},
{"task": "FW-P6-FPA-B", "area": "core-shell", "status": "planned"},
{"task": "FW-P6-FPA-C", "area": "document-persistence", "status": "planned"},
{"task": "FW-P6-FPA-D", "area": "object-coverage", "status": "planned"},
{"task": "FW-P6-FPA-E", "area": "format-coverage", "status": "planned"},
{"task": "FW-P6-FPA-F", "area": "python-api", "status": "planned"},
{"task": "FW-P6-FPA-G", "area": "workbench-gui", "status": "planned"},
{"task": "FW-P6-FPA-H", "area": "rendering-interaction", "status": "planned"},
{"task": "FW-P6-FPA-I", "area": "quality-release", "status": "planned"},
{"task": "FW-P6-FPA-Y", "area": "full-parity-gate", "status": "planned"},
],
}
(DOCS / "p6" / "full-parity-audit.json").write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n")
lines = [
"# FreeCAD Web 完整对标审计",
"",
f"- 源码 revision:`{report['sourceRevision']}`",
"- 当前结论:`planned`;P6 模块 gate 不是 Full Parity 结论。",
f"- 模块:{baseline['sourceModules']}(App {baseline['appModules']},GUI {baseline['guiModules']})。",
f"- 对象:{baseline['objectTypes']} 个,覆盖 {baseline['objectModules']} 个模块。",
f"- 格式:{baseline['formatRecords']} 条记录,{baseline['uniqueFormatExtensions']} 个扩展名。",
f"- Python:{baseline['pythonApiRecords']} 条 API 记录,覆盖 {baseline['pythonApiModules']} 个模块。",
f"- Web 资产:{web_counts['htmlEntrypoints']} 个入口页、{web_counts['workers']} 个 Worker、{web_counts['facades']} 个 Facade、{web_counts['nativeProbeSources']} 个 native probe 源文件。",
"",
"## 关键差距",
"",
]
for gap in gaps:
lines.append(f"- `{gap['id']}` `{gap['priority']}` `{gap['area']}`:{gap['scope']} 出口 `{gap['exit']}`。")
lines.extend([
"",
"## 连续队列",
"",
"`FW-P6-FPA-A → B → C → (D/E/F) → G → H → I → Y`。每张卡独立上下文、独立证据和独立恢复卡;任意未实现记录都会阻止 Y gate。",
])
(DOCS / "FREECAD_WEB_FULL_PARITY_AUDIT.md").write_text("\n".join(lines) + "\n")
print(json.dumps({"schema": report["schema"], "sourceRevision": report["sourceRevision"], "gaps": len(gaps), "objectTypes": baseline["objectTypes"], "formatRecords": baseline["formatRecords"], "pythonApiRecords": baseline["pythonApiRecords"]}, ensure_ascii=False))