from copy import deepcopy from pathlib import Path from docx import Document from docx.enum.text import WD_BREAK ROOT = Path('/Users/smart/Documents/TRAE培训/TRAE课件/exams') SOURCE = ROOT / 'TRAE编程培训考试评估卷-实操与项目实战.docx' TARGET = ROOT / 'TRAE编程培训考试评估卷-实操与项目实战(修订版).docx' def replace_paragraph(paragraph, text, bold=None, size=None): first_run = paragraph.runs[0] if paragraph.runs else None old_bold = first_run.bold if first_run else None old_size = first_run.font.size if first_run else None paragraph.clear() run = paragraph.add_run(text) run.bold = old_bold if bold is None else bold if size is not None: run.font.size = size elif old_size is not None: run.font.size = old_size return paragraph def replace_cell(cell, text, bold=None): for paragraph in cell.paragraphs[1:]: paragraph._element.getparent().remove(paragraph._element) paragraph = cell.paragraphs[0] first_run = paragraph.runs[0] if paragraph.runs else None old_bold = first_run.bold if first_run else None old_size = first_run.font.size if first_run else None paragraph.clear() run = paragraph.add_run(text) run.bold = old_bold if bold is None else bold if old_size is not None: run.font.size = old_size def find_paragraph(document, text): for paragraph in document.paragraphs: if text in paragraph.text: return paragraph raise ValueError(f'Paragraph not found: {text}') def set_table_rows(table, rows): while len(table.rows) < len(rows): table.add_row() if len(table.rows) != len(rows): raise ValueError('Unexpected table row count') for row, values in zip(table.rows, rows): if len(row.cells) != len(values): raise ValueError('Unexpected table column count') for cell, value in zip(row.cells, values): replace_cell(cell, value) def clone_paragraph(document, source_paragraph, text): paragraph = document.add_paragraph() placeholder = paragraph._p element = deepcopy(source_paragraph._element) placeholder.getparent().replace(placeholder, element) paragraph._p = paragraph._element = element replace_paragraph(paragraph, text) return paragraph def clone_table(document, source_table): table = document.add_table(rows=1, cols=1) placeholder = table._tbl element = deepcopy(source_table._element) placeholder.getparent().replace(placeholder, element) table._tbl = table._element = element return table def insert_after(anchor, element): anchor._element.addnext(element._element) doc = Document(SOURCE) replace_paragraph(find_paragraph(doc, '实操 + 公务用车项目实战综合卷'), '实操 + 系统用户状态管理综合卷') # 1. Fixed time: 20 minutes for A, 60 for B, 10 for submission check. set_table_rows(doc.tables[0], [ ['项目', '说明'], ['适用对象', '参加 TRAE 编程培训并完成 JeecgBoot 实战的技术学员。'], ['考试形式', '允许使用 TRAE,上机完成;提交 Markdown 产物包与 AI 使用记录。'], ['建议时长', '90 分钟:A 卷 20 分钟,B 卷 60 分钟,提交检查 10 分钟。'], ['总分', '100 分:小实操 30 分,项目实战 70 分。'], ['评估目标', '考察学员能否把真实研发问题拆成 TRAE 可执行、团队可验收、后续可复用的工程任务。'], ]) set_table_rows(doc.tables[2], [ ['部分', '时间', '分值', '形式', '核心能力'], ['A. 小实操', '20 分钟', '30 分', '个人完成', 'Prompt 改写、证据判断'], ['B. 项目实战', '60 分钟', '70 分', '个人完成', '项目理解、变更边界、测试安全网、Review 与交付'], ['提交检查', '10 分钟', '-', '个人完成', '核对目录、文件名与执行状态'], ['合计', '90 分钟', '100 分', '上机 + 文档交付', '个人基本功 + 项目落地能力'], ]) # 2. Deliverable templates and missing-file treatment. replace_paragraph(find_paragraph(doc, '建议要求学员提交一个 Markdown 目录包'), '使用 exam-kit/exam-output/ 中的模板提交,目录和文件名不得改动:') set_table_rows(doc.tables[3], [ ['文件', '主要内容'], ['01-project-understanding.md', '结论摘要、证据表、影响与保护边界、待确认项'], ['02-change-and-test.md', '验收标准、校验位置、不改动项、测试矩阵、执行状态'], ['03-pr-and-assets.md', 'Review、PR 说明、可复用 Prompt、Rule/Checklist/Skill 设计卡'], ['trae-usage-record.md', 'Prompt、采纳/否决、人工判断点、证据或验证状态;缺失则任务 3 的如实记录项计 0 分'], ]) # Insert the fixed source / tools contract after the deliverable table. input_heading = clone_paragraph(doc, find_paragraph(doc, '二、最终提交物'), '考试输入与作答约束') input_intro = clone_paragraph(doc, find_paragraph(doc, '使用 exam-kit/exam-output/'), 'B 卷仅以 exam-kit/ 中的固定版本、指定 diff 和模板为准。') input_table = clone_table(doc, doc.tables[3]) set_table_rows(input_table, [ ['项目', '统一要求'], ['固定源码', 'GitHub jeecgboot/JeecgBoot;标签 v3.9.2:7df07a823fd558be857d0208ccae96342539fbc1。'], ['指定模块', 'jeecg-module-system;以 SysUserController.frozenBatch 和 SysUserServiceImpl.updateStatus 为事实入口。'], ['指定 diff', 'exam-kit/review-diff.patch;仅以该两文件变更为 Review 输入。'], ['允许材料', 'TRAE、GitHub 固定版本与 exam-kit;版本不一致时记录环境问题并联系监考人员。'], ]) anchor = doc.tables[3] insert_after(anchor, input_heading) insert_after(input_heading, input_intro) insert_after(input_intro, input_table) replace_paragraph(find_paragraph(doc, '本部分由学员个人完成,重点考察 TRAE 基础使用'), '本部分由学员个人完成,重点考察 TRAE 基础使用与 AI 协作基本功。限时 20 分钟。') replace_cell(doc.tables[5].cell(0, 0), '原始 Prompt\n帮我看一下系统用户批量冻结/解冻功能有没有问题。') replace_cell(doc.tables[7].cell(0, 0), 'AI 输出片段\n页面已限制状态值,因此 Service 不需要校验;直接调用接口也不会有问题。') replace_paragraph(find_paragraph(doc, 'B 卷:公务用车项目实战'), 'B 卷:系统用户状态管理项目实战(70 分)') replace_paragraph(find_paragraph(doc, '本部分使用 JeecgBoot 现有公务用车模块'), '本部分使用 exam-kit 指定版本的 JeecgBoot 系统用户模块,由学员个人完成。限时 60 分钟。') replace_cell(doc.tables[9].cell(0, 0), '实战任务背景\n案例使用 GitHub 公开的 JeecgBoot v3.9.2 中系统用户模块。训练改造要求:批量冻结/解冻用户时,后端必须拒绝空白或 1、2 之外的状态值;页面限制不能替代 Service 入口的业务保护。本次考试不要求完整开发所有代码,重点考察如何使用 TRAE 完成项目理解、变更边界分析、测试设计、Review 和资产沉淀。') # 3. Observable requirements for B tasks. replace_paragraph(find_paragraph(doc, '使用 TRAE 输出公务用车模块结构说明'), '使用 TRAE 完成 01-project-understanding.md:说明系统用户模块结构、批量冻结/解冻接口、Service 入口、权限、管理员保护、缓存和测试入口。证据表至少含 5 条有效依据,每条必须写文件路径、符号或方法,以及它证明的结论;另说明非法状态值与接口、用户状态写入、缓存失效的影响关系,并区分事实、推断和待确认项。') replace_paragraph(find_paragraph(doc, '输出最小改造方案和测试安全网'), '完成 02-change-and-test.md:说明应在哪个方法、哪次用户状态写入之前校验非法状态值;列出不改动的接口权限、管理员保护和缓存失效逻辑及依据;为空白或非法状态、正常冻结、正常解冻、无权限调用、管理员保护与缓存/用户状态回归逐项写明预期结果、验证入口、执行状态。已执行项必须给出命令和结果,未执行项必须说明原因和计划验证方式。') replace_paragraph(find_paragraph(doc, '基于 `main...training/require-cancel-reason`'), '基于 exam-kit/review-diff.patch 的实际两文件 diff,完成 03-pr-and-assets.md:生成 Review 和 PR 说明,再输出一条可复用 Prompt 及一条 Rule、Checklist 或 Skill 设计卡。Review 可以得出“未发现阻断问题”的结论,但必须说明检查范围、证据和残余风险;所有已执行与未执行的验证必须如实标注。') # 4. Replace vague scoring with observable, non-overlapping criteria. set_table_rows(doc.tables[6], [ ['评分项', '分值'], ['背景与目标清楚', '3'], ['范围可控,限定目录、文件、接口或页面', '2'], ['约束清楚,含项目规则、安全、权限、测试要求及信息不足时先提问', '4'], ['输出格式明确,如表格、清单、报告或 PR 说明', '3'], ['验收标准和证据要求明确', '3'], ]) set_table_rows(doc.tables[10], [ ['评分项', '分值'], ['系统用户模块与批量冻结/解冻入口明确', '4'], ['frozenBatch 接口、Controller 与 Service 调用链明确', '5'], ['权限、管理员保护、缓存和测试入口明确', '4'], ['至少 5 条有效证据,每条含路径、符号和证明内容', '7'], ['影响与保护边界完整,并区分事实、推断和待确认项', '5'], ]) set_table_rows(doc.tables[11], [ ['评分项', '分值'], ['状态校验改造目标和验收标准清楚', '4'], ['校验方法、位置和副作用前后顺序正确', '5'], ['不改动项及其依据清楚', '4'], ['非法状态、冻结、解冻、权限、管理员保护/缓存五类场景完整', '8'], ['验证命令、执行结果或未执行原因明确', '4'], ]) set_table_rows(doc.tables[12], [ ['评分项', '分值'], ['Review 结论、证据、风险和验收方式完整', '5'], ['PR 说明包含背景、实现、测试和风险', '5'], ['可复用 Prompt 的输入、约束、输出和验收清楚', '5'], ['Rule、Checklist 或 Skill 设计卡的边界和验收清楚', '3'], ['AI 使用记录完整;不编造测试结果,能说明人工取舍', '2'], ]) replace_paragraph(find_paragraph(doc, '三、综合评分维度'), '三、阅卷通用原则(不重复计分)') set_table_rows(doc.tables[13], [ ['阅卷原则', '优秀表现', '使用方式'], ['证据意识', '结论能引用文件、函数、接口、日志或数据来源', '仅用于判断对应任务中的证据项,不单独追加扣分'], ['上下文选择', '知道先全局找路,再文件聚焦,再代码细查', '仅用于判断对应任务中的任务表达与证据项'], ['任务表达', '背景、目标、范围、约束、输出、验收完整', '仅用于判断 A 卷 Prompt 改写项'], ['风险意识', '能识别权限、状态、审计、异常态、测试缺口', '仅用于判断 B 卷影响、测试和 Review 项'], ['人机协作', '知道哪些让 AI 做,哪些必须人工确认', '仅用于判断 AI 使用记录与待确认项'], ['资产意识', '能沉淀可复用 Prompt、Rule、Checklist、Skill', '仅用于判断任务 3 的资产项'], ]) set_table_rows(doc.tables[15], [ ['环节', '要求'], ['发卷前检查', '发放 exam-kit;核对 GitHub 仓库、v3.9.2 完整提交和 review-diff.patch 均可读取。'], ['考试输入', '考试前完成 GitHub 固定版本下载;版本不一致时由监考人员处理,不得由学员自行替换版本。'], ['过程记录', '必须提交 trae-usage-record.md;缺失时任务 3 的 AI 使用记录项计 0 分。'], ['阅卷方式', '先核对四个交付文件,再按对应评分表打分;通用原则不重复扣分。'], ['独立作答', '抽查关键路径、证据来源和人工取舍,确认交付物与作答者理解一致。'], ]) doc.core_properties.title = 'TRAE 编程培训考试评估卷(修订版)' doc.core_properties.subject = '实操与系统用户状态管理综合卷' doc.save(TARGET) print(TARGET)