| @@ -0,0 +1,27 @@ | |||||
| # 桥梁数字教员讲解修正验收 | |||||
| 2026-09-15,开发、本机局域网及测试环境均通过。 | |||||
| - 四个任务默认视频讲解;场景加载完成且首帧显示后才播放。通过延迟真实模型/解码资源响应,确认 loading 可见时没有讲解播放调用。浏览器拒绝自动播放时保留视频模式,点击播放后正常继续。 | |||||
| - 右上角场景信息和整车工具使用纵向布局;1920、1600、1366 三种宽度无重叠。“三维实时交互”角标已移除。数字人框保持 3:4。 | |||||
| - 审核 39 段讲解及全部安全提示,重新生成 006 第 7 步、007 第 8/9/11 步、008 第 11/15 步。移除原稿、未展开、未给出参数等制作说明;008 保留记录依据并提交教员核对的要求。现用文件为 39 段 MP4、41 个 WAV,旧版本文件保留。 | |||||
| - 浮窗按钮与展开面板无阴影,头像绿点已移除。原因是公开接口优先用了旧上传视频,测试环境文件不存在返回 404;已统一使用随项目发布的内置头像。验收检查了实际教员 PNG 地址和图片加载完成,避免把临时品牌图标当作头像通过。 | |||||
| - 测试环境实际任务 86–89、固定内容版本 533–536 的全部 39 个步骤与新视频/讲解文字准确绑定;动作定义不变。 | |||||
| ## 验证记录 | |||||
| - 前端两项目构建通过;桥梁脚本、媒体绑定及正式入口 14 项契约测试通过。 | |||||
| - Java 17 `mvn test` 通过,Surefire 汇总 562 项,失败/错误为 0。 | |||||
| - 公开头像接口回归 2 项通过,包含旧上传文件缺失的重现案例。 | |||||
| - 四任务前两步的视频播放、语音切换、视频失败备用、切步停止、刷新和关闭通过,见 `playback/live-result.json`。 | |||||
| - 本机、局域网、测试环境的 39 个 MP4 与 41 个 WAV HTTP SHA-256 均与发布文件一致;MP4 Range 返回 206。 | |||||
| - 三个数字人在测试环境 `/human/viewer/{id}` 完成真实语音驱动,收到开始/结束事件并有持续嘴部运动,无浏览器错误。见 `ai_person/ape2e/reports/instructors-v2-test-20260915/stable-viewer-validation.json`。 | |||||
| ## 测试发布 | |||||
| - 数字车间:http://1.14.103.234:6180 | |||||
| - 数字人平台:http://1.14.103.234:7180 | |||||
| - 镜像:`digital-human-service:lecture-v2-20260915`,运行摘要 `6d66dd620303ea162047d7a9713b30f0f36575750f1a9a03c4f44aaf58f7659d`,容器 healthy。 | |||||
| - 五个源码仓库均已发布,提交及备份位置见 `deployment.json`。数据库无待执行的新变更;已有四任务及有效训练记录保留。 | |||||
| - 测试环境原有 v2 素材修改与新提交逐文件核对一致后,保存在名为 `lecture-v2-20260915-pre-deploy` 的 Git stash 中,再快进更新源码。 | |||||
| - 常规构建提示:前端部分包超过 500 kB;FastAPI 测试客户端有上游弃用提示。检查均成功。 | |||||
| @@ -0,0 +1,9 @@ | |||||
| { | |||||
| "origin": "http://192.168.31.168:6180", | |||||
| "videos": 39, | |||||
| "audio": 41, | |||||
| "allTextsClean": true, | |||||
| "exactFiles": true, | |||||
| "buildCopiesMatch": true, | |||||
| "rangeRequests": true | |||||
| } | |||||
| @@ -0,0 +1,33 @@ | |||||
| { | |||||
| "origin": "http://1.14.103.234:6180", | |||||
| "result": [ | |||||
| { | |||||
| "task": "TASK-VIRTUAL-005", | |||||
| "id": "86", | |||||
| "version": "533", | |||||
| "exactVideoAndTextBindings": 5, | |||||
| "operationsUnchanged": true | |||||
| }, | |||||
| { | |||||
| "task": "TASK-VIRTUAL-006", | |||||
| "id": "87", | |||||
| "version": "534", | |||||
| "exactVideoAndTextBindings": 7, | |||||
| "operationsUnchanged": true | |||||
| }, | |||||
| { | |||||
| "task": "TASK-VIRTUAL-007", | |||||
| "id": "88", | |||||
| "version": "535", | |||||
| "exactVideoAndTextBindings": 11, | |||||
| "operationsUnchanged": true | |||||
| }, | |||||
| { | |||||
| "task": "TASK-VIRTUAL-008", | |||||
| "id": "89", | |||||
| "version": "536", | |||||
| "exactVideoAndTextBindings": 16, | |||||
| "operationsUnchanged": true | |||||
| } | |||||
| ] | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| { | |||||
| "origin": "http://1.14.103.234:6180", | |||||
| "videos": 39, | |||||
| "audio": 41, | |||||
| "allTextsClean": true, | |||||
| "exactFiles": true, | |||||
| "buildCopiesMatch": true, | |||||
| "rangeRequests": true | |||||
| } | |||||
| @@ -0,0 +1,82 @@ | |||||
| { | |||||
| "origin": "http://1.14.103.234:6180", | |||||
| "delayed": 8, | |||||
| "checks": [ | |||||
| { | |||||
| "task": "TASK-VIRTUAL-005", | |||||
| "videoDefault": true, | |||||
| "noPlaybackWhileLoading": true, | |||||
| "autoplayRestrictionRetainsVideo": true, | |||||
| "noHudOverlap": [ | |||||
| 1920, | |||||
| 1600, | |||||
| 1366 | |||||
| ], | |||||
| "plays": [ | |||||
| { | |||||
| "loading": false, | |||||
| "src": "/legacy/virtual-training/videos/narration/bridge-pump-switch/v2/instructor-1-v2/step-01-bd17dc16621458c4.mp4" | |||||
| } | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "task": "TASK-VIRTUAL-006", | |||||
| "videoDefault": true, | |||||
| "noPlaybackWhileLoading": true, | |||||
| "autoplayRestrictionRetainsVideo": true, | |||||
| "noHudOverlap": [ | |||||
| 1920, | |||||
| 1600, | |||||
| 1366 | |||||
| ], | |||||
| "plays": [ | |||||
| { | |||||
| "loading": false, | |||||
| "src": "/legacy/virtual-training/videos/narration/bridge-pump-substitute/v3/instructor-1-v2/step-01-053bd89d013ca8c2.mp4" | |||||
| } | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "task": "TASK-VIRTUAL-007", | |||||
| "videoDefault": true, | |||||
| "noPlaybackWhileLoading": true, | |||||
| "autoplayRestrictionRetainsVideo": true, | |||||
| "noHudOverlap": [ | |||||
| 1920, | |||||
| 1600, | |||||
| 1366 | |||||
| ], | |||||
| "plays": [ | |||||
| { | |||||
| "loading": false, | |||||
| "src": "/legacy/virtual-training/videos/narration/bridge-pump-replace/v3/instructor-1-v2/step-01-071874b4ec4b9e19.mp4" | |||||
| } | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "task": "TASK-VIRTUAL-008", | |||||
| "videoDefault": true, | |||||
| "noPlaybackWhileLoading": true, | |||||
| "autoplayRestrictionRetainsVideo": true, | |||||
| "noHudOverlap": [ | |||||
| 1920, | |||||
| 1600, | |||||
| 1366 | |||||
| ], | |||||
| "plays": [ | |||||
| { | |||||
| "loading": false, | |||||
| "src": "/legacy/virtual-training/videos/narration/bridge-balance-valve-swap/v3/instructor-1-v2/step-01-8540f64d275cd500.mp4" | |||||
| } | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "widget": "http://1.14.103.234:7180", | |||||
| "avatarLoaded": true, | |||||
| "avatar": "/avatars/builtin/equipment-instructor-1.png", | |||||
| "greenDotRemoved": true, | |||||
| "launcherAndPanelShadows": "none" | |||||
| } | |||||
| ], | |||||
| "errors": [] | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| import fs from 'node:fs'; | |||||
| import assert from 'node:assert/strict'; | |||||
| import {credentials} from './role-permissions-live.mjs'; | |||||
| import videos from '../../unreal_tran_web/src/features/virtual-training-scripts/narration-video-manifest.js'; | |||||
| import {scriptTrainingDefinition} from '../../unreal_tran_web/src/features/virtual-training-scripts/registry.js'; | |||||
| const origin=process.env.BASE_URL || 'http://127.0.0.1:6180'; | |||||
| const dir=process.env.REPORT_DIR || 'reports/bridge-lecture-polish-20260915/local'; | |||||
| const login=await fetch(`${origin}/api/auth/v1/auth/login`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({...credentials('root'),roleCode:'admin',rememberMe:false})}).then(r=>r.json()); | |||||
| assert.ok(login.data?.accessToken); | |||||
| const tasks=await fetch(`${origin}/api/tran/v1/teaching/assignments?channel=VIRTUAL&assignmentKind=TRAINING&size=100`,{headers:{Authorization:`Bearer ${login.data.accessToken}`}}).then(r=>r.json()); | |||||
| const result=[]; | |||||
| for(const script of videos.scripts){ | |||||
| const task=tasks.data.records.find(t=>t.code===script.code);assert.ok(task); | |||||
| const definition=scriptTrainingDefinition(task); | |||||
| assert.equal(definition.steps.length,script.steps.length); | |||||
| for(const [i,clip] of script.steps.entries()){ | |||||
| assert.equal(definition.steps[i].audio.narrationUrl,clip.url,script.code+' '+clip.number); | |||||
| assert.equal(definition.steps[i].audio.narrationText,clip.text); | |||||
| assert.deepEqual(definition.steps[i].action,task.definitionSnapshot.steps[i].action); | |||||
| } | |||||
| result.push({task:script.code,id:task.id,version:task.contentVersionId,exactVideoAndTextBindings:script.steps.length,operationsUnchanged:true}); | |||||
| } | |||||
| fs.mkdirSync(dir,{recursive:true});fs.writeFileSync(dir+'/bindings.json',JSON.stringify({origin,result},null,2));console.log(JSON.stringify(result)); | |||||