{"record":{"id":"22cb2c186c0f16fe","repo":"ruvnet/RuView","slug":"path-each-node-needs-id-and-position-m-x-y","errorCode":null,"errorMessage":"{path}: each node needs 'id' and 'position_m' [x,y,z]","messagePattern":"(.+?): each node needs 'id' and 'position_m' \\[x,y,z\\]","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scripts/calibrate-camera-room.py","lineNumber":172,"sourceCode":"        except ValueError:\n            print(\"  positions must be numeric\", file=sys.stderr)\n            continue\n        nodes.append(node)\n    if not nodes:\n        print(\"WARNING: no transceiver nodes entered; bundle will carry empty geometry.\",\n              file=sys.stderr)\n    return {\"nodes\": nodes, \"units\": \"meters\", \"source\": \"tape-measure-prompt\"}\n\n\ndef load_geometry_file(path: Path) -> dict:\n    with open(path, \"r\", encoding=\"utf-8\") as f:\n        data = json.load(f)\n    nodes = data.get(\"nodes\", data if isinstance(data, list) else None)\n    if nodes is None:\n        raise ValueError(f\"{path}: expected {{'nodes': [...]}} or a top-level list\")\n    for node in nodes:\n        if \"id\" not in node or \"position_m\" not in node:\n            raise ValueError(f\"{path}: each node needs 'id' and 'position_m' [x,y,z]\")\n    return {\"nodes\": nodes, \"units\": \"meters\", \"source\": \"file\"}\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        description=\"Two-checkerboard camera-room calibration (ADR-152 S2.1.3 / ADR-079).\"\n    )\n    parser.add_argument(\"--wall-image\", required=True,\n                        help=\"Photo of the checkerboard on the origin wall\")\n    parser.add_argument(\"--floor-image\", required=True,\n                        help=\"Photo of the checkerboard on the floor (camera NOT moved)\")\n    parser.add_argument(\"--wall-origin\", type=parse_vec3, default=\"0.5,0.0,1.6\",\n                        help=\"Room xyz (m) of the wall board's first inner corner \"\n                             \"(default: 0.5,0.0,1.6)\")\n    parser.add_argument(\"--floor-origin\", type=parse_vec3, default=\"1.0,1.0,0.0\",\n                        help=\"Room xyz (m) of the floor board's first inner corner \"\n                             \"(default: 1.0,1.0,0.0)\")\n    parser.add_argument(\"--wall-axes\", default=\"+x,-z\",","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/ruvnet/RuView/blob/4685618388a5e49fad5b3005806f3bdd6a7c25c3/scripts/calibrate-camera-room.py#L154-L190","documentation":"After load_geometry_file finds the nodes array, every element must be a JSON object containing at least 'id' and 'position_m' (a three-component [x, y, z] array in meters). The first node missing either key raises ValueError naming the offending file.","triggerScenarios":"A node entry like {\"node_id\": \"n1\", \"xyz\": [0,0,1]} (renamed keys), or positions expressed as separate x/y/z fields instead of one 'position_m' array.","commonSituations":"Schema drift between the tool that generated the geometry file and this script; manual editing that renames or restructures keys.","solutions":["Rename keys to exactly 'id' and 'position_m' with a [x, y, z] array of meters","Check every element — the loop raises on the first offender, so fix all","If the file comes from another tool, write a small converter to the expected schema"],"exampleFix":"// before\n{\"nodes\": [{\"node_id\": \"n1\", \"xyz\": [0, 0, 1]}]}\n\n// after\n{\"nodes\": [{\"id\": \"n1\", \"position_m\": [0.0, 0.0, 1.0]}]}","handlingStrategy":"validation","validationCode":"import json\n\ndef nodes_valid(path: str) -> bool:\n    with open(path, encoding=\"utf-8\") as f:\n        data = json.load(f)\n    nodes = data.get(\"nodes\", []) if isinstance(data, dict) else []\n    return bool(nodes) and all(\n        \"id\" in n and \"position_m\" in n\n        and isinstance(n[\"position_m\"], list) and len(n[\"position_m\"]) == 3\n        for n in nodes\n    )","typeGuard":null,"tryCatchPattern":"try:\n    geom = load_geometry_file(path)\nexcept ValueError as e:\n    raise SystemExit(f\"fix node entries (need 'id' + 'position_m' [x,y,z]): {e}\") from e","preventionTips":["Use the same schema template for every node entry","Write a linter/converter for files produced by other tools","Include a sample node in the geometry file header or docs"],"tags":["python","json","schema","validation","calibration"],"backgroundTag":null,"analyzedSha":"4685618388a5e49fad5b3005806f3bdd6a7c25c3","analyzedAt":"2026-08-16T06:09:40.886Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}