提交当前项目改动
This commit is contained in:
282
web-rtcp-5axis-xyzbc-trt-sim-plan/tools/collect-native-xyzbc-trt-evidence.py
Executable file
282
web-rtcp-5axis-xyzbc-trt-sim-plan/tools/collect-native-xyzbc-trt-evidence.py
Executable file
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
AXES = ["X", "Y", "Z", "A", "B", "C", "U", "V", "W"]
|
||||
DEFAULT_INI = "/home/mes123456/linuxcnc-master/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini"
|
||||
DEFAULT_PROGRAM = "/home/mes123456/linuxcnc-master/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc"
|
||||
DEFAULT_OUTPUT = "/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/evidence/native-xyzbc-trt-evidence.json"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Collect native LinuxCNC xyzbc-trt evidence as JSON.")
|
||||
parser.add_argument("--ini", default=DEFAULT_INI)
|
||||
parser.add_argument("--program", default=DEFAULT_PROGRAM)
|
||||
parser.add_argument("--output", default=DEFAULT_OUTPUT)
|
||||
parser.add_argument("--run", action="store_true", help="Optionally execute the program through linuxcnc.command().")
|
||||
parser.add_argument("--timeout", type=float, default=60.0)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
import linuxcnc
|
||||
except Exception as exc:
|
||||
write_json(args.output, {
|
||||
"apiName": "xyzbc-trt-native-linuxcnc-evidence",
|
||||
"status": "blocked",
|
||||
"blocker": "python_linuxcnc_import_failed",
|
||||
"error": f"{type(exc).__name__}: {exc}",
|
||||
"hint": "Run through /home/mes123456/linuxcnc-master/scripts/rip-environment python3",
|
||||
})
|
||||
return 2
|
||||
|
||||
stat = linuxcnc.stat()
|
||||
command = linuxcnc.command()
|
||||
error_channel = linuxcnc.error_channel()
|
||||
|
||||
before = poll_stat(stat)
|
||||
command_result = None
|
||||
if args.run:
|
||||
command_result = run_program(linuxcnc, stat, command, pathlib.Path(args.program), args.timeout)
|
||||
|
||||
after = poll_stat(stat)
|
||||
hal = collect_hal_snapshot()
|
||||
errors = drain_errors(error_channel)
|
||||
|
||||
evidence = {
|
||||
"apiName": "xyzbc-trt-native-linuxcnc-evidence",
|
||||
"status": "ok",
|
||||
"collectedAt": iso_now(),
|
||||
"executionMode": "auto-run" if args.run else "snapshot-only",
|
||||
"iniPath": args.ini,
|
||||
"programPath": args.program,
|
||||
"programExists": pathlib.Path(args.program).exists(),
|
||||
"programLineCount": count_program_lines(args.program),
|
||||
"linuxcncRuntime": {
|
||||
"pythonApi": True,
|
||||
"processes": list_processes(),
|
||||
},
|
||||
"before": before,
|
||||
"after": after,
|
||||
"commandResult": command_result,
|
||||
"hal": hal,
|
||||
"errors": errors,
|
||||
"coverage": {
|
||||
"axisProfile": before.get("axisMask") == 55 or after.get("axisMask") == 55,
|
||||
"xyzbcProgramOpen": str(after.get("file") or before.get("file") or "").endswith("xyzbc_switchkins.ngc"),
|
||||
"switchkinsPinReadable": "motion.switchkins-type" in hal.get("pins", {}),
|
||||
"jointFeedbackReadable": all(f"joint.{i}.pos-fb" in hal.get("pins", {}) for i in range(5)),
|
||||
"taskStateReadable": after.get("taskState") is not None,
|
||||
"positionReadable": bool(after.get("position")),
|
||||
},
|
||||
"semanticBoundary": "native_linuxcnc_axis_vismach_xyzbc_trt_runtime",
|
||||
}
|
||||
write_json(args.output, evidence)
|
||||
print(f"native_xyzbc_trt_evidence={args.output}")
|
||||
return 0
|
||||
|
||||
|
||||
def run_program(linuxcnc, stat, command, program_path, timeout):
|
||||
result = {
|
||||
"requested": True,
|
||||
"program": str(program_path),
|
||||
"events": [],
|
||||
"status": "unknown",
|
||||
}
|
||||
try:
|
||||
command.state(linuxcnc.STATE_ESTOP_RESET)
|
||||
command.wait_complete()
|
||||
command.state(linuxcnc.STATE_ON)
|
||||
command.wait_complete()
|
||||
command.mode(linuxcnc.MODE_AUTO)
|
||||
command.wait_complete()
|
||||
command.program_open(str(program_path))
|
||||
command.wait_complete()
|
||||
command.auto(linuxcnc.AUTO_RUN, 0)
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
snapshot = poll_stat(stat)
|
||||
result["events"].append({
|
||||
"elapsedSeconds": round(time.time() - start, 3),
|
||||
"interpState": snapshot.get("interpState"),
|
||||
"execState": snapshot.get("execState"),
|
||||
"currentLine": snapshot.get("currentLine"),
|
||||
"readLine": snapshot.get("readLine"),
|
||||
"position": snapshot.get("position"),
|
||||
"jointActualPosition": snapshot.get("jointActualPosition"),
|
||||
})
|
||||
if snapshot.get("interpState") == linuxcnc.INTERP_IDLE and len(result["events"]) > 2:
|
||||
result["status"] = "completed"
|
||||
break
|
||||
time.sleep(0.05)
|
||||
else:
|
||||
result["status"] = "timeout"
|
||||
except Exception as exc:
|
||||
result["status"] = "error"
|
||||
result["error"] = f"{type(exc).__name__}: {exc}"
|
||||
return result
|
||||
|
||||
|
||||
def poll_stat(stat):
|
||||
stat.poll()
|
||||
return {
|
||||
"taskState": value(stat, "task_state"),
|
||||
"taskMode": value(stat, "task_mode"),
|
||||
"interpState": value(stat, "interp_state"),
|
||||
"execState": value(stat, "exec_state"),
|
||||
"file": value(stat, "file"),
|
||||
"currentLine": value(stat, "current_line"),
|
||||
"readLine": value(stat, "read_line"),
|
||||
"axisMask": value(stat, "axis_mask"),
|
||||
"homed": tuple_to_list(value(stat, "homed")),
|
||||
"position": axes_tuple(value(stat, "position")),
|
||||
"actualPosition": axes_tuple(value(stat, "actual_position")),
|
||||
"jointActualPosition": joint_tuple(value(stat, "joint_actual_position")),
|
||||
"jointPosition": joint_tuple(value(stat, "joint_position")),
|
||||
"dtg": axes_tuple(value(stat, "dtg")),
|
||||
"velocity": value(stat, "current_vel"),
|
||||
"feedrate": value(stat, "feedrate"),
|
||||
"rapidrate": value(stat, "rapidrate"),
|
||||
"spindle": normalize_json(value(stat, "spindle")),
|
||||
}
|
||||
|
||||
|
||||
def collect_hal_snapshot():
|
||||
pins = {}
|
||||
commands = [
|
||||
["halcmd", "show", "pin", "motion.switchkins-type"],
|
||||
["halcmd", "show", "pin", "motion.analog-out-03"],
|
||||
["halcmd", "show", "pin", "motion.tooloffset.z"],
|
||||
["halcmd", "show", "pin", "joint.0.pos-fb"],
|
||||
["halcmd", "show", "pin", "joint.1.pos-fb"],
|
||||
["halcmd", "show", "pin", "joint.2.pos-fb"],
|
||||
["halcmd", "show", "pin", "joint.3.pos-fb"],
|
||||
["halcmd", "show", "pin", "joint.4.pos-fb"],
|
||||
["halcmd", "show", "pin", "xyzbc-trt-kins.tool-offset"],
|
||||
["halcmd", "show", "pin", "xyzbc-trt-kins.x-offset"],
|
||||
["halcmd", "show", "pin", "xyzbc-trt-kins.z-offset"],
|
||||
]
|
||||
raw = []
|
||||
for command in commands:
|
||||
completed = subprocess.run(command, text=True, capture_output=True)
|
||||
raw.append({
|
||||
"command": command,
|
||||
"returncode": completed.returncode,
|
||||
"stdout": completed.stdout,
|
||||
"stderr": completed.stderr,
|
||||
})
|
||||
parse_halcmd_pins(completed.stdout, pins)
|
||||
return {
|
||||
"pins": pins,
|
||||
"raw": raw,
|
||||
}
|
||||
|
||||
|
||||
def parse_halcmd_pins(text, pins):
|
||||
for line in text.splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) < 5:
|
||||
continue
|
||||
name = parts[4]
|
||||
if "." not in name:
|
||||
continue
|
||||
pins[name] = {
|
||||
"owner": parts[0],
|
||||
"type": parts[1],
|
||||
"direction": parts[2],
|
||||
"value": parse_number(parts[3]),
|
||||
"linked": "==>" in line or "<==" in line,
|
||||
"raw": line,
|
||||
}
|
||||
|
||||
|
||||
def drain_errors(error_channel):
|
||||
errors = []
|
||||
for _ in range(20):
|
||||
error = error_channel.poll()
|
||||
if not error:
|
||||
break
|
||||
errors.append(normalize_json(error))
|
||||
return errors
|
||||
|
||||
|
||||
def list_processes():
|
||||
completed = subprocess.run(
|
||||
["ps", "-ef"],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
processes = []
|
||||
for line in completed.stdout.splitlines():
|
||||
if "xyzbc-trt" in line or "linuxcncsvr" in line or "milltask" in line or "halui -ini" in line:
|
||||
processes.append(line)
|
||||
return processes
|
||||
|
||||
|
||||
def count_program_lines(path):
|
||||
try:
|
||||
return len(pathlib.Path(path).read_text(encoding="utf-8", errors="replace").splitlines())
|
||||
except OSError:
|
||||
return 0
|
||||
|
||||
|
||||
def value(obj, attr):
|
||||
return normalize_json(getattr(obj, attr, None))
|
||||
|
||||
|
||||
def axes_tuple(values):
|
||||
values = tuple_to_list(values)
|
||||
return {axis.lower(): values[index] for index, axis in enumerate(AXES) if index < len(values)}
|
||||
|
||||
|
||||
def joint_tuple(values):
|
||||
values = tuple_to_list(values)
|
||||
return {str(index): values[index] for index in range(min(5, len(values)))}
|
||||
|
||||
|
||||
def tuple_to_list(values):
|
||||
if values is None:
|
||||
return []
|
||||
return [normalize_json(value) for value in values]
|
||||
|
||||
|
||||
def normalize_json(value):
|
||||
if isinstance(value, float):
|
||||
if math.isnan(value) or math.isinf(value):
|
||||
return None
|
||||
return value
|
||||
if isinstance(value, (str, int, bool)) or value is None:
|
||||
return value
|
||||
if isinstance(value, tuple):
|
||||
return [normalize_json(item) for item in value]
|
||||
if isinstance(value, list):
|
||||
return [normalize_json(item) for item in value]
|
||||
if isinstance(value, dict):
|
||||
return {str(key): normalize_json(item) for key, item in value.items()}
|
||||
return str(value)
|
||||
|
||||
|
||||
def parse_number(value):
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
return value
|
||||
|
||||
|
||||
def write_json(path, payload):
|
||||
output = pathlib.Path(path)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def iso_now():
|
||||
return time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,167 @@
|
||||
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { createMemorySessionStorage } from "../app/src/runtime/five-axis-session.js";
|
||||
import { parseLinuxCncIni } from "../app/src/runtime/linuxcnc-ini-runtime.js";
|
||||
import {
|
||||
selectMachineFileProgram,
|
||||
stageProfileMachineFiles,
|
||||
} from "../app/src/runtime/linuxcnc-machine-file-staging.js";
|
||||
import { createSimulationStore } from "../app/src/state/store.js";
|
||||
import { getFiveAxisProfile } from "../app/src/profiles/index.js";
|
||||
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const projectRoot = resolve(repoRoot, "web-rtcp-5axis-xyzbc-trt-sim-plan");
|
||||
const outputPath = process.argv[2]
|
||||
|| resolve(projectRoot, "working/evidence/web-xyzbc-trt-evidence.json");
|
||||
const sourceRel = "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc";
|
||||
|
||||
const profile = getFiveAxisProfile("xyzbc-trt");
|
||||
const wasmArtifacts = await inspectWasmArtifacts();
|
||||
const iniText = await readFile(
|
||||
resolve(repoRoot, "wasm-port/vendor/linuxcnc", profile.iniPath),
|
||||
"utf8",
|
||||
);
|
||||
const ini = parseLinuxCncIni(iniText, {
|
||||
path: profile.iniPath,
|
||||
profileId: profile.id,
|
||||
});
|
||||
|
||||
const storage = createMemorySessionStorage();
|
||||
const staged = await stageProfileMachineFiles(profile, { storage });
|
||||
const selectedPlan = selectMachineFileProgram(staged.plan, staged.save, sourceRel);
|
||||
const store = createSimulationStore();
|
||||
const storeStage = await store.stageMachineFiles({ storage: createMemorySessionStorage() });
|
||||
store.dispatch({ type: "LOAD_LINUXCNC_GCODE_SOURCE", sourceRel });
|
||||
const state = store.getState();
|
||||
|
||||
const evidence = {
|
||||
apiName: "xyzbc-trt-web-opfs-wasm-evidence",
|
||||
status: wasmArtifacts.ready ? "ready-for-wasm-runtime" : "blocked",
|
||||
collectedAt: new Date().toISOString(),
|
||||
profile: {
|
||||
id: profile.id,
|
||||
machineName: profile.machineName,
|
||||
iniPath: profile.iniPath,
|
||||
pyvcpXmlPath: profile.pyvcpXmlPath,
|
||||
toolTablePath: profile.toolTablePath,
|
||||
coordinates: profile.coordinates,
|
||||
kinematics: profile.kinematics,
|
||||
kinematicsModuleId: profile.kinematicsModuleId,
|
||||
defaultProgramFilename: profile.machineFileStaging?.defaultProgramFilename,
|
||||
switchkinsTypes: profile.kinematicsParameters?.switchkinsTypes,
|
||||
halPins: profile.halPins,
|
||||
},
|
||||
ini: {
|
||||
ready: ini.validation.ready,
|
||||
machineName: ini.machineName,
|
||||
coordinates: ini.traj.coordinates,
|
||||
kinematicsName: ini.kinematics.name,
|
||||
kinematicsModuleId: ini.kinematicsModuleId,
|
||||
remaps: ini.rs274ngc.remaps,
|
||||
haluiMdiCommands: ini.halui.mdiCommands,
|
||||
toolTable: ini.emcio.toolTable,
|
||||
parameterFile: ini.rs274ngc.parameterFile,
|
||||
jointConfig: ini.jointConfig,
|
||||
},
|
||||
opfsStaging: {
|
||||
storageMode: staged.save.storageMode,
|
||||
opfsRoot: staged.save.opfsRoot,
|
||||
fileCount: staged.save.fileCount,
|
||||
summary: staged.save.summary,
|
||||
files: staged.save.files.map((file) => ({
|
||||
sourceRel: file.sourceRel,
|
||||
opfsPath: file.opfsPath,
|
||||
wasmPath: file.wasmPath,
|
||||
kind: file.kind,
|
||||
bytes: file.bytes,
|
||||
executable: file.executable,
|
||||
})),
|
||||
gcodeSources: staged.save.gcodeSources,
|
||||
selectedProgram: {
|
||||
sourceRel: selectedPlan.selectedProgramSourceRel,
|
||||
filename: selectedPlan.selectedProgramFilename,
|
||||
wasmProgramPath: selectedPlan.wasmProgramPath,
|
||||
},
|
||||
},
|
||||
store: {
|
||||
machineProfile: state.machineProfile,
|
||||
sessionName: state.sessionName,
|
||||
machineProjectRoot: state.machineProject?.projectRoot || storeStage.save.opfsRoot,
|
||||
activeProgram: state.activeProgram,
|
||||
programSource: state.programSource,
|
||||
programLineCount: state.programLines.length,
|
||||
selectedGcodeSourceRel: state.machineFileStaging.selectedGcodeSourceRel,
|
||||
},
|
||||
wasm: wasmArtifacts,
|
||||
coverage: {
|
||||
profileDefaultXyzbc: profile.id === "xyzbc-trt",
|
||||
iniReady: ini.validation.ready,
|
||||
opfsStaged: staged.save.status === "saved" && staged.save.fileCount > 0,
|
||||
pyvcpXmlStaged: staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc-trt.xml")),
|
||||
remapsStaged: ["428remap.ngc", "429remap.ngc", "430remap.ngc"].every((name) => (
|
||||
staged.save.files.some((file) => file.sourceRel.endsWith(`/remap_subs/${name}`))
|
||||
)),
|
||||
toolTableStaged: staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc-trt.tbl")),
|
||||
parameterFileStaged: staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc.var")),
|
||||
defaultProgramStaged: staged.save.gcodeSources.some((source) => source.filename === "xyzbc_switchkins.ngc"),
|
||||
wasmArtifactsReady: wasmArtifacts.ready,
|
||||
},
|
||||
blockers: [
|
||||
...(wasmArtifacts.ready ? [] : [{
|
||||
id: "missing-wasm-artifacts",
|
||||
detail: "wasm-port/build/wasm does not contain all required kinematics/core/tp/task-hal artifacts.",
|
||||
required: wasmArtifacts.required,
|
||||
missing: wasmArtifacts.missing,
|
||||
}]),
|
||||
...(staged.save.files.some((file) => file.sourceRel.endsWith("xyzbc.var")) ? [] : [{
|
||||
id: "missing-parameter-file-staging",
|
||||
detail: "xyzbc.var is referenced by INI but absent from wasm-port/vendor manifest in this workspace.",
|
||||
}]),
|
||||
],
|
||||
semanticBoundary: "web_opfs_wasm_runtime_readiness_for_linuxcnc_xyzbc_trt",
|
||||
};
|
||||
|
||||
await mkdir(dirname(outputPath), { recursive: true });
|
||||
await writeFile(outputPath, JSON.stringify(evidence, null, 2) + "\n", "utf8");
|
||||
console.log(`web_xyzbc_trt_evidence=${outputPath}`);
|
||||
|
||||
async function inspectWasmArtifacts() {
|
||||
const required = [
|
||||
"wasm-port/build/wasm/kinematics/linuxcnc_xyzbc_trt_kinematics.js",
|
||||
"wasm-port/build/wasm/kinematics/linuxcnc_xyzbc_trt_kinematics.wasm",
|
||||
"wasm-port/build/wasm/core/linuxcnc_interp.js",
|
||||
"wasm-port/build/wasm/core/linuxcnc_interp.wasm",
|
||||
"wasm-port/build/wasm/tp/linuxcnc_tp.js",
|
||||
"wasm-port/build/wasm/tp/linuxcnc_tp.wasm",
|
||||
"wasm-port/build/wasm/task-hal/linuxcnc_task_hal.js",
|
||||
"wasm-port/build/wasm/task-hal/linuxcnc_task_hal.wasm",
|
||||
];
|
||||
const files = [];
|
||||
const missing = [];
|
||||
for (const rel of required) {
|
||||
const abs = resolve(repoRoot, rel);
|
||||
try {
|
||||
await access(abs);
|
||||
files.push(rel);
|
||||
} catch {
|
||||
missing.push(rel);
|
||||
}
|
||||
}
|
||||
return {
|
||||
required,
|
||||
files,
|
||||
missing,
|
||||
ready: missing.length === 0,
|
||||
emscriptenAvailable: Boolean(await commandExists("emcc")),
|
||||
};
|
||||
}
|
||||
|
||||
async function commandExists(command) {
|
||||
const { spawn } = await import("node:child_process");
|
||||
return new Promise((resolveCommand) => {
|
||||
const child = spawn("bash", ["-lc", `command -v ${command}`], { stdio: "ignore" });
|
||||
child.on("exit", (code) => resolveCommand(code === 0));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const projectRoot = resolve(repoRoot, "web-rtcp-5axis-xyzbc-trt-sim-plan");
|
||||
const nativePath = process.argv[2] || resolve(projectRoot, "working/evidence/native-xyzbc-trt-evidence.json");
|
||||
const webPath = process.argv[3] || resolve(projectRoot, "working/evidence/web-xyzbc-trt-evidence.json");
|
||||
const outputPath = process.argv[4] || resolve(projectRoot, "working/evidence/compare-xyzbc-trt-evidence.json");
|
||||
|
||||
const nativeEvidence = JSON.parse(await readFile(nativePath, "utf8"));
|
||||
const webEvidence = JSON.parse(await readFile(webPath, "utf8"));
|
||||
|
||||
const checks = [
|
||||
check("profile", "native axis mask is XYZBC", nativeEvidence.coverage?.axisProfile === true, {
|
||||
nativeAxisMask: nativeEvidence.after?.axisMask ?? nativeEvidence.before?.axisMask,
|
||||
}),
|
||||
check("profile", "web default profile is xyzbc-trt", webEvidence.coverage?.profileDefaultXyzbc === true, {
|
||||
webProfile: webEvidence.profile?.id,
|
||||
}),
|
||||
check("ini", "native loaded xyzbc switchkins program", nativeEvidence.coverage?.xyzbcProgramOpen === true, {
|
||||
nativeFile: nativeEvidence.after?.file ?? nativeEvidence.before?.file,
|
||||
}),
|
||||
check("ini", "web INI parse is ready", webEvidence.coverage?.iniReady === true, {
|
||||
webIniReady: webEvidence.ini?.ready,
|
||||
}),
|
||||
check("switchkins", "native switchkins pin readable", nativeEvidence.coverage?.switchkinsPinReadable === true, {
|
||||
nativePin: nativeEvidence.hal?.pins?.["motion.switchkins-type"],
|
||||
}),
|
||||
check("switchkins", "web remap files staged", webEvidence.coverage?.remapsStaged === true, {
|
||||
remaps: webEvidence.ini?.remaps,
|
||||
}),
|
||||
check("program", "web default xyzbc_switchkins program staged", webEvidence.coverage?.defaultProgramStaged === true, {
|
||||
selected: webEvidence.opfsStaging?.selectedProgram,
|
||||
}),
|
||||
check("tool-table", "web tool table staged", webEvidence.coverage?.toolTableStaged === true, {
|
||||
toolTable: webEvidence.ini?.toolTable,
|
||||
}),
|
||||
check("pyvcp", "web PyVCP XML staged", webEvidence.coverage?.pyvcpXmlStaged === true, {
|
||||
pyvcpXmlPath: webEvidence.profile?.pyvcpXmlPath,
|
||||
}),
|
||||
check("positions", "native joint feedback readable", nativeEvidence.coverage?.jointFeedbackReadable === true, {
|
||||
joints: nativeEvidence.after?.jointActualPosition,
|
||||
}),
|
||||
check("wasm", "required WASM artifacts available", webEvidence.coverage?.wasmArtifactsReady === true, {
|
||||
missing: webEvidence.wasm?.missing,
|
||||
}),
|
||||
check("parameters", "parameter file staged", webEvidence.coverage?.parameterFileStaged === true, {
|
||||
parameterFile: webEvidence.ini?.parameterFile,
|
||||
}),
|
||||
];
|
||||
|
||||
const failed = checks.filter((item) => item.status !== "pass");
|
||||
const blockers = [
|
||||
...(nativeEvidence.status === "blocked" ? nativeEvidence.blocker ? [nativeEvidence.blocker] : ["native-blocked"] : []),
|
||||
...(webEvidence.blockers || []).map((blocker) => blocker.id),
|
||||
];
|
||||
|
||||
const report = {
|
||||
apiName: "xyzbc-trt-native-web-evidence-comparison",
|
||||
status: failed.length === 0 ? "pass" : "fail",
|
||||
comparedAt: new Date().toISOString(),
|
||||
nativePath,
|
||||
webPath,
|
||||
summary: {
|
||||
checkCount: checks.length,
|
||||
passCount: checks.length - failed.length,
|
||||
failCount: failed.length,
|
||||
blockers,
|
||||
nativeStatus: nativeEvidence.status,
|
||||
webStatus: webEvidence.status,
|
||||
},
|
||||
checks,
|
||||
requiredImprovements: failed.map((item) => ({
|
||||
category: item.category,
|
||||
requirement: item.requirement,
|
||||
evidence: item.evidence,
|
||||
})),
|
||||
semanticBoundary: "native_linuxcnc_vs_web_opfs_wasm_xyzbc_trt_evidence_comparison",
|
||||
};
|
||||
|
||||
await mkdir(dirname(outputPath), { recursive: true });
|
||||
await writeFile(outputPath, JSON.stringify(report, null, 2) + "\n", "utf8");
|
||||
console.log(`compare_xyzbc_trt_evidence=${outputPath}`);
|
||||
if (failed.length > 0) {
|
||||
console.log(`compare_xyzbc_trt_status=fail fail_count=${failed.length}`);
|
||||
} else {
|
||||
console.log("compare_xyzbc_trt_status=pass");
|
||||
}
|
||||
|
||||
function check(category, requirement, passed, evidence = {}) {
|
||||
return {
|
||||
category,
|
||||
requirement,
|
||||
status: passed ? "pass" : "fail",
|
||||
evidence,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user