You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

48 line
5.6 KiB

  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import assert from 'node:assert/strict';
  4. import {login,call} from './role-permissions-live.mjs';
  5. import {normalizeIntroduction,introductionErrors} from '../../unreal_tran_web/src/features/training/introduction.js';
  6. const dir='reports/bridge-introduction-20260918'; fs.mkdirSync(dir,{recursive:true});
  7. const source=path.resolve('fixtures/bridge-script-source/脚本Markdown');
  8. const titles=['液压泵应急切换','液压泵应急替代','前泵换件修复','平衡阀拆拼互换'];
  9. const covers=['bridge-pump-switch-v2.jpg','bridge-pump-substitute-v4.jpg','bridge-pump-replace-v4.jpg','bridge-balance-valve-swap-v4.jpg'];
  10. const seeds=titles.map((title,i)=>{
  11. const code=`TASK-VIRTUAL-00${i+5}`,md=fs.readFileSync(path.join(source,code+'-'+title+'.md'),'utf8');
  12. const sections=md.split(/^## /m).slice(1).map(s=>({title:s.split('\n')[0].trim(),text:s.slice(s.indexOf('\n')+1)}));
  13. const body=text=>text.split(/\n\s*\n/).filter(p=>p.trim()&&!/^(来源:|>)/.test(p.trim())).join('\n\n').trim();
  14. const intro=normalizeIntroduction({background:{enabled:true,title:'项目背景 · '+title,format:'image-text',body:body(sections.find(s=>s.title.startsWith('项目背景原文')).text),imageUrl:'/models/bridge-training/'+covers[i],source:code+' 图片原文'},lectures:sections.filter(s=>/^讲解\d/.test(s.title)).map((s,index)=>({id:code+'-L'+(index+1),title:s.title.replace(/^讲解\d+:/,'').replace(/(.*$/,''),format:'image-text',body:body(s.text.includes('### 解说词原文')?s.text.split('### 解说词原文')[1]:s.text),imageUrl:'/models/bridge-training/'+covers[i],source:code+' 图片解说词'}))});
  15. // Do not read editorial notes or uncertain model codes to the learner.
  16. for(const lecture of intro.lectures)lecture.body=lecture.body.replace('GQZ009型重型支援桥','重型支援桥').replaceAll('构成完成的液压回路','构成完整的液压回路').replaceAll('是而是','而是');
  17. if(i===2)intro.background.body=intro.background.body.replace('而后桥梁分队分队长指挥操作手试车检查,驾驶员在接通取力器后,操作手按照规定程序试车,此时托架和架设架所有执行机构动作,随即向分队长报告。','');
  18. if(i===3){intro.background.body='本项目训练重型支援桥平衡阀的拆拼互换。\n\n按脚本依次完成四组操作:\n1. 拆卸小腔油口连接平衡阀。\n2. 拆卸大腔油口连接平衡阀。\n3. 将拆下的平衡阀互换安装到大腔油口。\n4. 将另一平衡阀安装到小腔油口。\n\n操作阶段包含清洗、拆卸、油口封堵和安装等步骤。';intro.background.source='由9.jpg、10.jpg四组操作归纳的项目概览;原稿未提供故障背景';}
  19. assert.deepEqual(introductionErrors(intro),[]);return {code,intro};
  20. });
  21. fs.writeFileSync(dir+'/introduction-seeds.json',JSON.stringify(seeds,null,2));
  22. const signed=await login('root');
  23. const api=async(endpoint,body,method)=>{const r=await call('tran/v1/'+endpoint,signed.accessToken,body,method);assert.equal(r.status,200,endpoint+': '+r.message);return r.data;};
  24. const backup=(name,value)=>{const p=dir+'/'+name+'.json';if(!fs.existsSync(p))fs.writeFileSync(p,JSON.stringify(value,null,2));};
  25. const records=[];
  26. for(const [id,index] of [[86,0],[87,1],[88,2],[89,3],[116,0]]) {
  27. let task=await api('teaching/assignments/'+id),detail=await api('content/projects/'+task.contentProjectId);
  28. assert.equal(task.code,id===116?'BRIDGE-005-AUTHORING-PILOT':seeds[index].code);
  29. backup('before-task-'+id,task);backup('before-project-'+task.contentProjectId,detail);
  30. const content=structuredClone(detail.currentVersion.content),intro=seeds[index].intro;
  31. const beforeDefinition=structuredClone(task.definitionSnapshot);
  32. if(JSON.stringify(content.introduction)!==JSON.stringify(intro)){
  33. content.introduction=intro;
  34. detail=await api('content/projects/'+detail.project.id,{...detail.project,content,assets:detail.currentVersion.assets,dependencies:detail.currentVersion.dependencies,version:detail.project.version,changeSummary:'接入图片原文背景与前置讲解;保留原操作步骤、模型和评分'},'PUT');
  35. }
  36. if(detail.project.publishedVersionId!==detail.currentVersion.id)detail=await api('content/projects/'+detail.project.id+'/publish',{version:detail.project.version});
  37. if(task.contentVersionId!==detail.project.publishedVersionId){
  38. task=task.status==='PUBLISHED'
  39. ? await api('teaching/assignments/'+id+'/introduction-version',{contentVersionId:detail.project.publishedVersionId,version:task.version,reason:'接入可在训练编排配置的背景与前置讲解,不修改现有操作与成绩'},'PUT')
  40. : await api('teaching/assignments/'+id,{...task,contentVersionId:detail.project.publishedVersionId,version:task.version},'PUT');
  41. }
  42. const after=structuredClone(task.definitionSnapshot);delete beforeDefinition.introduction;delete after.introduction;assert.deepEqual(after,beforeDefinition,'only introduction changes');
  43. const original=JSON.parse(fs.readFileSync(dir+'/before-task-'+id+'.json','utf8'));
  44. assert.deepEqual(task.members,original.members);assert.deepEqual(task.runs,original.runs);
  45. records.push({id,code:task.code,projectId:task.contentProjectId,contentVersionId:task.contentVersionId,background:true,lectures:intro.lectures.length,steps:task.definitionSnapshot.steps.length});console.log(records.at(-1));
  46. }
  47. fs.writeFileSync(dir+'/migration.json',JSON.stringify({pass:true,records,notes:['005省略原稿不清晰型号;修正两处明显病句。','007背景略去原文有无不字冲突的一句,不扩写故障判断。','008项目概览由四组操作归纳,不补造故障背景。','007、008未添加原稿缺失的独立讲解。']},null,2));