190 lines
8.2 KiB
Python
190 lines
8.2 KiB
Python
#!/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))
|