Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 

54 рядки
5.4 KiB

  1. import fs from 'node:fs';
  2. import assert from 'node:assert/strict';
  3. import { login, call } from './role-permissions-live.mjs';
  4. // Read-only final audit. Never publishes or changes an existing assignment.
  5. const dir = 'reports/bridge-005-authoring-20260916';
  6. const read = name => JSON.parse(fs.readFileSync(`${dir}/${name}.json`, 'utf8'));
  7. const fixture = read('pilot');
  8. const before = read('original-assignments');
  9. const session = await login('root');
  10. async function api(endpoint) {
  11. const result = await call(`tran/v1/${endpoint}`, session.accessToken);
  12. assert.equal(result.status, 200, endpoint);
  13. return result.data;
  14. }
  15. const originals = [];
  16. for (const id of ['86', '87', '88', '89']) {
  17. const current = await api(`teaching/assignments/${id}`);
  18. delete current.serverNow;
  19. delete before[id].serverNow;
  20. assert.deepEqual(current, before[id], `Original assignment ${id} changed`);
  21. const project = await api(`content/projects/${current.contentProjectId}`);
  22. assert.equal(project.project.publishedVersionId, current.contentVersionId);
  23. originals.push({ id, code: current.code, versionId: current.contentVersionId, unchanged: true });
  24. }
  25. const task = await api(`teaching/assignments/${fixture.taskId}`);
  26. assert.equal(task.status, 'DRAFT');
  27. assert.equal(task.contentVersionId, fixture.trainingVersionId);
  28. const training = await api(`content/projects/${fixture.trainingId}`);
  29. const scene = await api(`content/projects/${fixture.sceneId}`);
  30. assert.equal(scene.project.publishedVersionId, fixture.sceneVersionId);
  31. const content = training.currentVersion.content;
  32. assert.equal(content.runtime?.scriptId, undefined);
  33. assert.notEqual(content.runtime?.definitionSource, 'SCRIPT');
  34. assert.equal(content.steps.length, 5);
  35. assert.equal(content.steps.flatMap(s => s.action.mechanism.steps).length, 6);
  36. assert.equal(content.steps[2].action.mechanism.steps[0].deltaDegrees, 90);
  37. assert.equal(scene.currentVersion.content.objects.flatMap(o => o.modelPartTargets || []).length, 7);
  38. assert.ok(training.currentVersion.dependencies.some(d => d.relationType === 'TRAINING_SCENE' && d.targetVersionId === fixture.sceneVersionId));
  39. const media = content.steps.map((s, index) => {
  40. const codes = [s.audio.narrationVideoAssetCode, s.audio.narrationAssetCode];
  41. for (const code of codes) assert.ok(training.currentVersion.assets.some(a => a.code === code && a.status === 'READY'), code);
  42. return { step: index + 1, video: codes[0], audio: codes[1] };
  43. });
  44. const edited = await api(`content/projects/${fixture.trainingId}/versions/543`);
  45. assert.equal(edited.content.steps[2].action.mechanism.steps[0].deltaDegrees, 75);
  46. const evidence = ['editor-result', 'editor-result-90', 'runtime-edited-75', 'runtime-final-90-lan', 'original-tasks-regression', 'original-005-full-regression'];
  47. for (const name of evidence) assert.equal(read(name).pass, true, name);
  48. const audit = { pass: true, checkedAt: new Date().toISOString(), fixture, originals, steps: 5, operations: 6, partTargets: 7, editedAngle: 75, restoredAngle: 90, media, evidence };
  49. fs.writeFileSync(`${dir}/audit.json`, JSON.stringify(audit, null, 2));
  50. const links = evidence.map(name => `<li><a href="${name}.json">${name}</a>:通过</li>`).join('');
  51. fs.writeFileSync(`${dir}/index.html`, `<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>005 场景与编排验收</title><style>body{font:16px/1.7 system-ui;margin:32px auto;padding:0 24px;max-width:1100px;color:#173d38;background:#f4f8f7}a{color:#087f70}section{background:white;padding:22px;border:1px solid #d5e7e3;border-radius:12px;margin:20px 0}img{width:100%;border-radius:8px}h1{font-size:28px}li{margin:8px 0}</style><h1>005 液压泵应急切换:场景制作 → 训练编排 → 教学预览</h1><p>验收时间:${audit.checkedAt}。独立验证工程,原四个已发布任务数据和版本完整对比一致。</p><section><h2>入口</h2><ul><li><a href="http://127.0.0.1:6180/content/scenes/${fixture.sceneId}/edit">场景制作:整车、地形、七个真实部件目标</a></li><li><a href="http://127.0.0.1:6180/content/training-projects/${fixture.trainingId}/legacy">训练编排:五步、六个机构动作及逐步数字人讲解</a></li><li><a href="http://127.0.0.1:6180${fixture.previewUrl}">独立教学预览</a>(草稿任务,不替换现有学员入口)</li></ul></section><section><h2>验证结果</h2><p>实际编辑场景并发布后,训练重新绑定已发布场景。球阀从 90° 改为 75°,保存、刷新、发布后,完成全部六次鼠标操作及服务端预览校验;随后恢复 90° 并在局域网完整复测。五段视频和五段语音已作为训练资产绑定。</p><p>原 005 完整流程回归、原四任务初始场景回归均通过。前端构建与合同测试、后端单元测试和打包通过。当前验证仅覆盖教员预览,不代表新增学员成绩验收。</p><ul>${links}<li><a href="audit.json">任务、版本、讲解资产与原数据核对结果</a></li></ul></section><section><h2>场景制作</h2><img src="scene-editor.png" alt="005 场景制作"></section><section><h2>训练编排</h2><img src="training-editor.png" alt="005 训练编排"></section><section><h2>球阀操作与互救开关</h2><img src="task-116-step-3.png" alt="球阀操作"><img src="task-116-step-5.png" alt="互救开关操作"></section></html>`);
  52. console.log('AUDIT PASS: original tasks unchanged; 005 authoring and runtime verified');