        nodes=output.get('nodes',[]);edges=output.get('edges',[]);source={r['id']:r for r in original['positions']};by_id={r.get('id'):r for r in nodes};expected_edges={(r['manager'],r['id']) for r in source.values() if r['manager']};got_edges={(r.get('from'),r.get('to')) for r in edges}
        check('岗位ID不合并，同名员工各有身份',set(by_id)==set(source) and all(by_id[k].get('employee_id')==v['employee_id'] and by_id[k].get('employee')==v['employee'] for k,v in source.items()));check('空岗未补造员工',by_id.get('P4',{}).get('employee') is None and by_id.get('P4',{}).get('employee_id') is None);check('经理到岗位关系完整且无新边',got_edges==expected_edges)
        lines=['digraph Organization {','graph [rankdir=TB, bgcolor="#f8f4ed"];','node [shape=box, style=rounded, fontname="sans-serif"];']
        for key in sorted(by_id):
            r=by_id[key];person=r.get('employee') or '待聘';lab=f"{key} · {r.get('name','')}\n{person} / {r.get('employee_id') or 'VACANT'}";lines.append(json.dumps(key)+' [label='+json.dumps(lab,ensure_ascii=False)+'];')
        for a,z in sorted(got_edges):lines.append(json.dumps(a)+' -> '+json.dumps(z)+';')
        lines.append('}');artifact('organization.dot').write_text('\n'.join(lines))
        script='''import fs from 'node:fs';import path from 'node:path';import {pathToFileURL} from 'node:url';\nconst modules=process.env.BATCH100O_NODE_MODULES || path.resolve('web-deps/node_modules');\nconst {Graphviz}=await import(pathToFileURL(path.join(modules,'@hpcc-js/wasm-graphviz/dist/index.js')).href);\nconst engine=await Graphviz.load();const dot=fs.readFileSync(process.argv[2],'utf8');fs.writeFileSync(process.argv[3],engine.layout(dot,'svg','dot'));\n''';artifact('render-graph.mjs').write_text(script);node=shutil.which('node')
        if not node:raise RuntimeError('Node.js is required for actual Graphviz WASM layout')
        subprocess.run([node,str((folder/'render-graph.mjs').resolve()),str((folder/'organization.dot').resolve()),str((folder/'organization.svg').resolve())],check=True,capture_output=True,text=True,timeout=40);artifacts.append('organization.svg');tree=ET.parse(folder/'organization.svg');ns={'s':'http://www.w3.org/2000/svg'};graph_nodes=tree.findall('.//s:g[@class="node"]',ns);check('官方Graphviz引擎真实布局5个岗位',len(graph_nodes)==5);result={'nodes':nodes,'edges':edges,'renderEngine':'@hpcc-js/wasm-graphviz 1.30.0; real Graphviz WASM','partTimeEmployee':'E1 appears in P1 and P3'};dump(artifact('organization-map.json'),result)
