Files
FreeCADWasmDev/tools/prepare_format_business_batch.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

120 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""Prepare module-level import/export workflows without API-sized tasks."""
from __future__ import annotations
import hashlib
import json
from collections import defaultdict
from datetime import datetime, timezone
from pathlib import Path
from implementation_first import all_implementation_ready_ids
from scheduler_progress import cooling_ids, retry_details
ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / "docs/p6/format-coverage-kernel.json"
STATE = ROOT / "docs/current/task-state.json"
OUTPUT = ROOT / "docs/current/module-batch-manifest.json"
MODULE_ORDER = {
"Part": 0, "Mesh": 1, "Import": 2, "TechDraw": 3, "Spreadsheet": 4,
"Points": 5, "BIM": 6, "Draft": 7, "OpenSCAD": 8, "Fem": 9, "CAM": 10,
}
def business_id(module: str, direction: str, extension: str) -> str:
return f"business-format:{module}:{direction}:{extension}"
def main() -> int:
source = json.loads(SOURCE.read_text(encoding="utf-8"))
ready = all_implementation_ready_ids()
cooling = cooling_ids(candidates={business_id(str(row.get("module")), str(row.get("direction")), str(row.get("extension"))) for row in source.get("records", []) if isinstance(row, dict)})
deferred_ids = set()
grouped: dict[tuple[str, str, str], list[dict]] = defaultdict(list)
for row in source.get("records", []):
if not isinstance(row, dict) or row.get("direction") not in {"import", "export"}:
continue
key = (str(row.get("module")), str(row.get("direction")), str(row.get("extension")))
grouped[key].append(row)
pending: dict[str, list[dict]] = defaultdict(list)
for (module, direction, extension), rows in grouped.items():
record_id = business_id(module, direction, extension)
if record_id in ready or any(row.get("status") == "passed" for row in rows):
continue
if record_id in cooling:
deferred_ids.add(record_id)
continue
source_row = min(rows, key=lambda row: (str(row.get("path", "")), int(row.get("line", 0) or 0)))
pending[module].append({
"id": record_id,
"name": f"{direction} {extension}",
"direction": direction,
"extension": extension,
"source": source_row.get("path"),
"sourceLine": source_row.get("line"),
"sourceHandlerCount": len(rows),
})
if not pending:
result = {
"schema": "freecad-web-module-function-batch/v1",
"status": "no_ready_batch",
"generatedAt": datetime.now(timezone.utc).isoformat(),
"strategy": "module-function-direct-deferred-validation",
"reason": "all source-declared import/export workflows have module implementation receipts",
"records": [],
"queue": [],
**retry_details(deferred_ids),
}
OUTPUT.write_text(json.dumps(result, ensure_ascii=False, separators=(",", ":")) + "\n", encoding="utf-8")
print(json.dumps({"status": result["status"], "formatWorkflows": len(grouped), "records": 0}, ensure_ascii=False))
return 0
module = min(pending, key=lambda value: (MODULE_ORDER.get(value, 50), value))
records = sorted(pending[module], key=lambda row: (row["extension"], row["direction"]))
identity = "|".join([module, "format-workflow", "import-export", *(str(row["id"]) for row in records)])
batch_id = hashlib.sha256(identity.encode("utf-8")).hexdigest()[:24]
state = json.loads(STATE.read_text(encoding="utf-8"))
queue = [
{"module": name, "tier": "format-workflow", "family": "import-export", "unresolved": len(rows), "nextSize": len(rows)}
for name, rows in sorted(pending.items(), key=lambda item: (MODULE_ORDER.get(item[0], 50), item[0]))
]
result = {
"schema": "freecad-web-module-function-batch/v1",
"status": "ready",
"generatedAt": datetime.now(timezone.utc).isoformat(),
"taskId": state.get("active_task"),
"batchId": batch_id,
"strategy": "module-function-direct-deferred-validation",
"businessBundle": {
"module": module,
"implementationUnit": "module-import-export-dispatcher",
"selectedRecordsAreUserWorkflows": True,
"sourceHandlerCount": sum(int(row["sourceHandlerCount"]) for row in records),
},
"minimumRecords": 1,
"singletonFallback": len(records) == 1,
"selected": {
"module": module,
"tier": "format-workflow",
"family": "import-export",
"recordCount": len(records),
"dispositionCounts": {"not_implemented": len(records)},
},
"fields": ["id", "name", "disposition", "source", "sourceLine"],
"records": [[row["id"], row["name"], "not_implemented", row["source"], row["sourceLine"]] for row in records],
"queue": queue,
"boundaries": [
"One record is one user-visible module import/export workflow, not a Python API.",
"All same-module extensions share the existing native dispatcher, Worker and WASM artifact.",
"Source mentions with unknown direction are not user format handlers and are excluded.",
"Implementation performs build/sync/receipt only; validation is disabled.",
],
}
OUTPUT.write_text(json.dumps(result, ensure_ascii=False, separators=(",", ":")) + "\n", encoding="utf-8")
print(json.dumps({"status": "ready", "batchId": batch_id, "module": module, "family": "import-export", "records": len(records)}, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())