全链路死角审查与验收清单.md

全链路死角审查与验收清单

范围:/home/zyw/Downloads/ai-lesson-prep(Next.js 14)。已实证跑通:教案生成 → plan_confirmed → PPT 生成 → ppt_confirmed → 导出 plan_docx / ppt。
本审查只覆盖剩余步骤:材料生成/确认/completed、其余导出类型、教研样例发布、学习通五步、三类课时(无主案例讲授课 / 案例课 / 实验课)。
证据均为代码行号;判定口径:可行 = 按 UI 正常点能走通;有风险 = 能走通但会空文件 / 与 UI 不符 / 需绕路;死路 = 按 UI 操作会 400/按钮永灰,该步骤不可达。

状态机(单 RANK,不是真并行):

not_started → plan_draft → plan_confirmed → ppt_draft → ppt_confirmed → materials_draft → completed

src/lib/prepare/state.ts:3-4, 22-30, 50-81。generate_ppt / generate_materials 用 maxStatus 只升不降;completed 除「重生成教案」外锁死。


0. 总表(先看结论)

#步骤无主案例讲授课案例课实验课判定
1材料生成 → 确认 → completed死路(卡在生成教案),绕开 G2 后材料段可行可行(有主案例)死路(同 G2),绕开后材料段可行/有风险见 §1
2a导出 question_bank有材料才行;无习题则空表同左同左;实验更易空见 §2
2b导出 question_bank_xlsx同上同上同上见 §2
2c导出 materials_teacher / materials_student有材料草稿即可(未确认会警告)同左同左见 §2
2d导出 activity_list可行(只要求已确认教案)同左同左见 §2、§4
2e导出 toolkit同题库,须有材料同左同左见 §2
3publish-sample须先 completed;脱敏有 422 边角同左同左见 §3
4学习通五步 ↔ activity_list无硬依赖同左同左见 §4
5实验课教案/PPT/材料/题库G1 已不再因无目标卡死;G2/课型硬编码仍卡—见左见 §5

1. 材料生成 → 确认(step=materials)→ completed

1.1 主路径可达性

字段内容
判定可行(前提:已 plan_confirmed,且本课能生成出非空 exercises 或 cases)
证据状态机 confirm_materials:src/lib/prepare/state.ts:73-75(当前 RANK ≥ materials_draft 才进 completed)。后端确认:src/app/api/lesson-plans/[id]/confirm/route.ts:97-115(无 draft.materials → 400 need_materials;RANK < materials_draft → 400 need_materials_draft;G1 失败 → 400 g1_no_objectives,同文件 67-74)。生成材料门槛:src/lib/ai/generate.ts:536-544 + src/lib/prepare/state.ts:42-44(须 plan_confirmed 及以上)。落库升态:src/lib/prepare/persist.ts:71-81(generate_materials → maxStatus(..., materials_draft))。废弃一键完成:confirm/route.ts:116-124(step=complete 恒 400 complete_removed)。
UI 确认按钮src/app/prepare/[classId]/lesson-dev-panel.tsx:954-977。disabled 条件:busy 或 !bundle.materials 或 status !== "materials_draft" 或 g1 不通过。按钮文案「确认材料(完成备课)」,只在「材料」页签出现。
修复建议不改主路径。把 status !== "materials_draft" 的 title 写全(G1 / 空材料)。completed 后若允许补确认材料,按钮应在 completed && draft.materials 新于 confirmed 时重新点亮,或提供「更新确认稿」。
验证方式plan_confirmed 后 POST /api/lesson-plans/generate {step:"materials",run:true} → 任务 done → GET 教案 status=materials_draft → PUT .../confirm {step:"materials"} → status=completed。UI:材料页按钮从灰→可点→完成后变灰。

1.2 UI 与后端对「空材料」不一致

字段内容
判定有风险(UI 更严;直打 API 可把空材料标成 completed)
证据UI 点击处理 lesson-dev-panel.tsx:415-425:exercises.length===0 && cases.length===0 则前端报 need_materials,不发请求。后端 confirm/route.ts:99-104 只判断 !draft.materials(空对象 {} 或 {exercises:[]} 也算有)。Schema src/lib/ai/schema.ts:429-432:exercises 必须是数组,允许空数组。材料页列表 lesson-dev-panel.tsx:982-1003:无习题时显示「尚未生成材料」,但只要 materials 对象在且 status=materials_draft,确认按钮仍可点(空习题+空案例时点下去才报错)。
修复建议后端与 UI 对齐:确认时要求 exercises.length+cases.length≥1(或实验课允许 worksheets 等字段)。Schema 对空 exercises 标 error,避免 flagged 仍落库升态。
验证方式手工把 draft.materials 写成 {exercises:[],cases:[]},status=materials_draft:UI 点确认应失败;curl PUT confirm step=materials 今日会 201/200 且 status=completed(这就是不一致)。

1.3 确认按钮对状态名过严(重生成教案后的假死)

字段内容
判定有风险(材料还在草稿里,按钮永久灰,直到再点一次「生成材料草稿」)
证据`nextStatus("completed"
修复建议确认材料的准入改为「draft.materials 非空 且 RANK ≥ plan_confirmed」,升态用 maxStatus(cur, completed);或重生成教案时若已有材料则 nextStatus 回到 materials_draft 而非 plan_draft 一条路走到底。completed 后更新材料:允许 confirm_materials 覆盖 confirmed.materials。
验证方式completed → 再生成教案 → plan_draft → 确认教案 → plan_confirmed → 材料页确认按钮应为可点(今日不可点)→ 再点「生成材料草稿」后应变为可点。

1.4 无主案例讲授课 / 实验课:卡在「生成教案」,材料段根本进不去

字段内容
判定死路(UI 全链路;与「材料」步骤的关系是前置被切)
证据UI 生成一律 lessonType: "案例讨论":lesson-dev-panel.tsx:224-243(plan/materials/ppt 三次调用同一函数)。生成 API 的 G2 默认值也是 "案例讨论":src/app/api/lesson-plans/generate/route.ts:66-78。G2:src/lib/prepare/g2.ts:114-126——只要请求课型是案例型,无主案例就 400 g2_no_main_case,不管模板偏好是不是「讲授/实验」。UI 前置拦截只用 lesson.requiresMainCase(wizard-data.ts:128 ← needsMainCase(preferred)),讲授课/实验课 requiresMainCase=false,按钮可点,请求照样被 G2 打回。课型下拉默认同样是「案例讨论」:lesson-dev-panel.tsx:1216-1217。
三类课时案例课:向导会要主案例(lesson-dev-panel.tsx:226-228, 608),选了就能过 G2,材料段可达。无主案例讲授课 / 实验课:UI 点「生成教案」→ 400,到不了 plan_confirmed,后续材料/完成/发布全断。直打 API 并显式传 lessonType:"讲授" 或 "实验" 可绕开(g2.ts:116-118:requested 非案例型则 mustBeCase=false)。
修复建议runGenerate 传 lesson.preferredLessonTypes[0](或用户在表单选的 plan.lesson_type),禁止写死「案例讨论」。generate/route.ts:71 去掉 ?? "案例讨论",无课型时走模板偏好(G2 的 uniqueTypes.every(isCaseLessonType) 分支)。
验证方式理工班/未选主案例的讲授课、实验1:UI 点生成教案,今日必 g2_no_main_case。改 body lessonType:"讲授" 后应 201 并落到 plan_draft。

1.5 并行 PPT 后仍能确认材料(不是死路)

字段内容
判定可行
证据plan_confirmed → 先生成并确认 PPT → ppt_confirmed(RANK 4)→ 再生成材料 → maxStatus(ppt_confirmed, materials_draft)=materials_draft(state.ts:70-72, 36-39)。此时确认按钮条件满足。先材料后 PPT:generate_ppt 从 materials_draft 不降级(state.ts:64-66);confirm_ppt 的 maxStatus(materials_draft, ppt_confirmed) 仍是 materials_draft(state.ts:67-69),与 UI 提示「PPT 确认不回退」一致(lesson-dev-panel.tsx:389-391, 979-980)。可以不确认 PPT 就完成备课:completed 不要求 ppt_confirmed。
修复建议无需改可达性。若产品要求「完成=教案+材料+PPT 都确认」,应在 confirm_materials 增加 generation_meta.ppt_confirmed===true 闸门(今日没有)。
验证方式两条序:A. 教案确认→材料→确认材料→completed(无 PPT)。B. 教案确认→PPT 确认→材料→确认材料→completed。均应成功。

2. 导出类型前置校验(src/lib/exporters/run.ts)

公共入口:runLessonExport。弱教案闸:run.ts:136-142——confirmed 里既没有 lesson_title 也没有 objectives 数组 → 400 need_plan_confirmed。空数组 objectives:[] 也算「有数组」,会放行。 PPT 导出读 仅 confirmed(run.ts:144, 281-286);材料读 confirmed 否则 draft(run.ts:145-149)。

needMaterials():run.ts:95-103(materials_teacher|materials_student|question_bank|question_bank_xlsx|toolkit)。activity_list 不在其中。requireMaterials():run.ts:271-278(无材料 → 400 need_materials,文案写的是「须先生成材料再导出题库」,材料 docx 另有文案 run.ts:350-354)。

导出按钮 UI 只看 busy 01 !planId(lesson-dev-panel.tsx:904-927),不看状态、不看是否已确认,点下去才 400。与文案「导出基于已确认内容」(900-901 行)不符。

2.1 ppt(已实证,对照用)

字段内容
判定可行(须 PPT 已写入 confirmed_content.ppt.slides)
证据run.ts:281-295:无 slides → 400 need_ppt_confirmed;再跑 validateGenerationPayload("ppt"),失败 → 400 ppt_hard_constraint(页级:要点≤5 / 单条≤20 字 / 讨论课争议页+记录页,schema.ts:274-396,上限 PPT_MAX_SLIDES=16 见 src/lib/ai/types.ts:53)。不接受 draft PPT。 不要求 status===ppt_confirmed(材料先行导致状态停在 materials_draft/completed 时,只要确认过 PPT 仍能导出——与面板 979-980 行说明一致)。
缺了会怎样未确认 PPT:400 need_ppt_confirmed。硬约束失败:400,带 issues。
修复建议导出按钮在 !confirmed.ppt.slides 时禁用,避免空点。
验证方式ppt_confirmed 或 materials_draft 且 meta ppt_confirmed=true 时导出 201;仅 draft PPT 应 400。

2.2 plan_docx(已实证)

字段内容
判定可行
证据只走弱教案闸 run.ts:140-142, 322-348。用 confirmed plan。不要求材料/PPT。
缺了会怎样从未确认教案:400 need_plan_confirmed。
修复建议弱闸改为「checkG1(plan) 通过」或 statusRank>=plan_confirmed,避免空 objectives:[] 漏网。
验证方式plan_confirmed 后导出 201(已实证)。

2.3 materials_teacher / materials_student

字段内容
判定有风险(不强制「已确认材料」;未确认按草稿导出并警告)
证据needMaterials 含二者:run.ts:95-103。无任何材料:run.ts:349-354 400 need_materials。有 draft 无 confirmed:run.ts:147-148 warnings.push("材料尚未确认,本次按草稿导出"),仍写 docx。教师版含答案、学生版隐藏:materials-docx.ts:59-70。不要求 completed。
缺了会怎样无材料:400。未确认教案:若 confirmed 无 plan,先被 need_plan_confirmed 拦住。未确认材料:成功 + warning。
三类课时讲授课/实验课只要材料生成成功即可导出,不必先完成备课。案例课同。
修复建议若产品要求「确认后才导出」,在 needMaterials 分支改为「无 confirmed 材料则 400」,draft 仅警告不够。UI 按钮按 bundle.materials 禁用。
验证方式materials_draft 导出教师版 → 201 且 warnings 含「尚未确认」。completed 后再导出 → 无该警告。无材料 → 400 need_materials。

2.4 question_bank

字段内容
判定有风险(有材料就成功;无习题也成功,得到空表)
证据run.ts:372-398:requireMaterials() 后写 4 个文件:CSV(P7 列)、xlsx、导入说明、顺带一份 activity_list。行数据只来自 materials.exercises:question-bank.ts:18-37, 75-107。cases 不进题库。不要求材料已确认(draft 回落 + 警告,同 147-148)。不要求 PPT。
缺了会怎样无材料对象:400 need_materials。有材料无习题:201,CSV 仅表头、xlsx「题库」表只有表头。未确认教案:可能先 400 need_plan_confirmed。
修复建议exercises.length===0 时 400 empty_question_bank 或强制 warning。UI 提示「题库来自材料习题,案例讨论题不入库」。
验证方式有习题:xlsx 行数 = 1+N。无习题有案例:201 但行数=1(仅表头)。

2.5 question_bank_xlsx

字段内容
判定有风险(同 2.4)
证据run.ts:399-419:xlsx + 学习通 CSV + 导入说明。不附带 activity_list(与 question_bank 不同)。同样 requireMaterials(),同样不查习题是否为空。
缺了会怎样同 2.4。
修复建议同 2.4。
验证方式与 2.4 对照文件集合:本类型无 activity_list_txt。

2.6 activity_list

字段内容
判定可行(只要求已确认教案;与五步勾选无关)
证据不在 needMaterials():run.ts:95-103, 420-432。写活动清单 + 课件包清单。清单正文 question-bank.ts:253-320:目标来自 confirmed plan;讨论/作业/测验可来自 lesson_events 与 可选 materials。无材料时各类可出现「本课教案未单独列出,可按资料上传或跳过」(310-311)。不读 chaoxing_five_steps。
缺了会怎样未确认教案:400 need_plan_confirmed。未生成材料:仍成功,活动建议偏空。未勾五步:仍成功。
修复建议保持「照做清单不依赖勾选」。UI 标明「不要求材料/五步」。若希望清单饱满,在无材料时 warning。
验证方式plan_confirmed 且无材料:201,正文含章节目录与占位活动。completed 且有习题:测验/作业条数随 exercises 增加。

2.7 toolkit(一键交换物包)

字段内容
判定有风险(须材料;内含空题库仍打包成功;含五步记录快照)
证据run.ts:433-466:requireMaterials() 后逐个 save xlsx/csv/学习通 csv/guide/activity_list/课件包清单/chaoxing_checklist,再 zip。checklist 来自 parseChaoxingSteps(gen.chaoxing_five_steps)(run.ts:222-233, 441),全未勾也能导出(记录里写「未勾选」)。不要求 PPT、不要求 completed、不要求材料已确认。
缺了会怎样无材料:400 need_materials。未勾五步:zip 内仍有「-五步勾选记录.txt」。无习题:题库文件空表但仍进包。
修复建议与题库同样处理空习题。产品若要求「完成备课才出交换物」,加 status===completed。
验证方式materials_draft:201 + warning「尚未确认」。无材料:400。zip 内应有 7 个成员文件 + toolkit 自身记录。

2.8 前置校验对照表

导出 type须已确认教案须已有材料须已确认材料须已确认 PPT缺前置
ppt弱闸否否是(confirmed slides + schema)need_plan_confirmed / need_ppt_confirmed / ppt_hard_constraint
plan_docx弱闸否否否need_plan_confirmed
materials_teacher弱闸是(draft 可)否(警告)否need_materials
materials_student弱闸是(draft 可)否(警告)否need_materials
question_bank弱闸是否(警告)否need_materials;空习题仍 201
question_bank_xlsx弱闸是否(警告)否同上
activity_list弱闸否否否仅 need_plan_confirmed
toolkit弱闸是否(警告)否need_materials

3. 教研样例 publish-sample / research_samples / 脱敏

3.1 发布闸门与落库

字段内容
判定可行(须 completed);若干 边角失败 见下
证据API:src/app/api/lesson-plans/[id]/publish-sample/route.ts:38-48。核心:src/lib/samples/publish.ts:47-163。非 completed → 400「仅已完成(completed)教案可发布为教研样例」(73-75)。非本人/非 admin → 403(70-72)。samplePublishOptOut → 403(76-78)。个人偏好关闭且非 admin → 403(79-82)。已 revoked → 409,教师不能自行再发布(83-85)。快照:confirmed 优先、plan/ppt/materials 可回落 draft(31-44)。source_lesson_plan_id 唯一(prisma/schema.prisma ResearchSample),再发布走 update 不是 create(134-144)。UI:sample-publish-card.tsx:32, 106-116 按钮 disabled={!completed || optOut || !allowPublishSample}。
修复建议409 文案在 UI 展示「联系管理员恢复」。completed 提示旁加「须先确认材料」。撤销后若允许教师覆盖,去掉 83-85 或改为 admin-only restore 入口(已有 reviewResearchSample restore,publish.ts:199-215)。
验证方式ppt_confirmed/materials_draft 点发布:按钮灰,API 400。completed 且允许发布:201,research_samples 一行,sanitizedContent 无班级名。再 POST:200 created=false。admin revoke 后再 POST:409。

3.2 脱敏 sanitize 边角

字段内容
判定有风险(命中则 422,发布失败;内容含教师短名/用户名/残留 URL 时)
证据sanitizeValue + assertNoPersonalInfo:publish.ts:95-104,实现 src/lib/samples/sanitize.ts:294-354。班级名/教师名/学号/画像桶/URL 会替换;替换后若 findPersonalInfo 仍命中则抛错 → 422。风险点:① buildTeacherAliases 把 username 长度≥4 且非 admin 加入别名(sanitize.ts:107-108),教案正文若出现该登录名(如 test、demo)会 422。② 教师 displayName 两字且与知识点常用词撞车。③ strip_personal_links:false 时 citations 的 https:// 会被 findPersonalInfo 当成「个人链接」(336-341);UI 固定传 true(sample-publish-card.tsx:63),直打 API 传 false 会踩。④ 质量分只写入字段,不是闸门(publish.ts:110, 124-125 + quality.ts)。
修复建议username 别名加最小长度/中文名限制;422 响应把 hits 带回 UI。发布闸不要用「全文 includes(username)」。
验证方式已实证脚本:scripts/test-s3c.ts / demo-s3c.ts。补测:把教师 username 写进 class_profile_summary 再发布,预期今日 422。

4. 「去学习通五步」与导出 activity_list

字段内容
判定可行(两者解耦,不是死路);有风险(教师会以为勾选/导出互为前置)
证据勾选持久化:generation_meta.chaoxing_five_steps,src/lib/prepare/chaoxing-steps.ts:1-4, 38-61;API src/app/api/lesson-plans/[id]/chaoxing-steps/route.ts:35-72。确认/再生成用 parseGenMeta 整包写回(persist.ts:53-79, bumpMetaOnConfirm 111-124),不会抹掉五步。activity_list 渲染不读勾选(question-bank.ts:253-320),只在文末打印五步标题常量(317 行,来自 status.ts:73-114)。toolkit 才把勾选快照写成 chaoxing_checklist(run.ts:441, 449)。UI 卡片出现条件:① 任意页签顶部,仅 status==="completed"(lesson-dev-panel.tsx:655-661);② 导出页、材料页,仅 status==="materials_draft"(929-935, 1004-1010)。ppt_confirmed 且尚未生成材料时,界面上没有五步卡片——此时仍可导出 activity_list。勾选不是导出前置,导出也不是勾选前置。
修复建议卡片改为 statusRank>=plan_confirmed 即显示(与「对照活动清单去勾」一致)。导出页用一句话写清:「活动清单=照做文本;五步=教师核对;互不阻断。」
验证方式plan_confirmed 导出 activity_list 应 201。completed 勾选 ①② → GET chaoxing-steps 为 done;再导出 toolkit,checklist 对应行「已勾选」。全不勾导出 toolkit 仍 201。

5. 实验课时(现已有 objectives)与题库是否为空

5.1 教案 / PPT / 材料导出是否顺畅

字段内容
判定G1:可行(课时目标已录入)。生成教案:死路(UI 硬编码案例课型,见 1.4)。绕开 G2 后教案确认/PPT/材料:可行,PPT 有讨论课硬约束 有风险。
证据G1 只看 objectives.length≥1:src/lib/prepare/g1.ts:18-32,确认教案/材料都走(confirm/route.ts:67-74)。整学期内容已声明实验课 3–5 条目标(整学期备课-验收示范说明.md)。实验课 isCaseLessonType("实验")===false(g2.ts:14-27),本不应要主案例。材料生成提示词仍写「习题与案例讨论」(generate.ts:523-524),实验课也会被要求出 exercises。PPT:若模型把 lesson_type 写成含「讨论/案例」(UI 又在请求里写死「案例讨论」),schema.ts:274-277, 384-395 强制争议页+记录页,实验课 PPT 可能 ppt_hard_constraint 不得确认。导出教案/材料不依赖课型。学时 2,PPT 上限 16 页(types.ts:53)一般够。
修复建议同 1.4 去掉课型硬编码。材料 userExtra 对 lesson_type 含「实验」改为「实验步骤/记录表/思考题,exercises 至少 2 道(含 1 道 recall 以便题库)」。PPT isDiscussionLesson 不要被请求参数污染。
验证方式实验1:lessonType:"实验" 生成教案 → 确认(G1 通过)→ 生成 PPT/材料 → 导出 plan_docx/ppt/materials_student。用 UI 默认「案例讨论」应 G2 失败(对照)。

5.2 question_bank 必须材料里有习题才不空

字段内容
判定有风险(导出不失败,但是空题库;实验课若只有 cases/操作步骤则必空)
证据question-bank.ts:18-20, 75-76 只 map exercises。确认材料 UI 允许「仅 cases 无 exercises」(lesson-dev-panel.tsx:419-423)——此时可 completed,题库仍空。实验课 authentic_task 无 options 会进简答题列(question-bank.ts:57-61),学习通仍能导入,但 recall 单选可能为 0。
修复建议材料 schema:exercises.length≥1。实验课生成至少 1 道 recall。空题库导出 400 或显著 warning。
验证方式导出后数 xlsx「题库」数据行;实验1 若 N=0 记失败(产品口径),今日代码会当成功。

6. 额外发现(会使某步不可行)

6.1 生成任务 persist 失败仍标 done

字段内容
判定有风险(偶发假成功:UI 说已生成,草稿没有,确认按钮一直灰)
证据src/lib/ai/tasks.ts:106-128:persist catch 只 console.error,随后仍把 task 写成 done。
修复建议persist 失败则 task=failed,不要 done。
验证方式单测 mock persist throw,期望 task.status=failed。

6.2 确认 PPT 没有「半执行」保护(材料确认有)

字段内容
判定有风险
证据材料确认专门拒绝 RANK < materials_draft,避免「写入 confirmed 但状态不前进」(confirm/route.ts:105-114 注释)。PPT 确认 没有 对等闸:status=plan_draft 但 draft 里还有旧 ppt 时,UI 按钮只看 bundle.ppt 与 schema(lesson-dev-panel.tsx:808-813),API 会把 ppt 写入 confirmed,nextStatus 因 RANK < ppt_draft 保持 plan_draft(state.ts:67-68)。
修复建议与材料对称:RANK < ppt_draft 时 400 need_ppt_draft。
验证方式completed→重生成教案=plan_draft→不确认教案直接点「确认 PPT」:今日可能改 confirmed.ppt 而 status 仍 plan_draft。

6.3 flagged 材料/教案不阻断确认

字段内容
判定有风险(与文案「不得直接确认」不符)
证据生成成功文案:lesson-dev-panel.tsx:253-256「已生成但存在标红,不得直接确认」。确认教案只拦 G1(311-316);确认材料只拦 G1+空材料;都不看 flagged / 材料 schema。后端确认材料不调用 validateGenerationPayload("materials")。PPT 确认则拦 schema(confirm/route.ts:85-95,lesson-dev-panel.tsx:372-377)。
修复建议材料确认复用 PPT 模式:schema invalid → 400。教案确认至少把 flagged 当强警告(产品已用质量六条作非硬阻断,需在文案上统一)。
验证方式生成材料缺 objective_ids(schema 429-454 行会标红)→ 任务 flagged → 今日仍可确认到 completed。

6.4 导出按钮与校验不同步

字段内容
判定有风险(不是死路,是 UI 撒谎)
证据lesson-dev-panel.tsx:904-927 vs run.ts 各 need_*。
修复建议按 §2.8 表禁用并 title 写 code。
验证方式plan_draft 点「教师版材料」「题库」「toolkit」应灰;今日可点,返回 400。

6.5 无「跳过材料完成备课」通道

字段内容
判定有风险(讲授课若认为不需要材料,则永远不能 completed,也就不能发布样例)
证据complete_removed:confirm/route.ts:116-124。publish.ts:73-75 只认 completed。
修复建议若讲授课允许无材料完成:confirm 增加 step=complete 且课型非案例,或发布闸改为 plan_confirmed && (materials 已确认 || 非案例型)。若产品坚持「完成=有材料」,保持现状并在 UI 写死「三类课时都必须生成材料」。
验证方式讲授课只确认教案+PPT:发布按钮保持灰(今日如此)。

6.6 占位章 G3(当前学期内容已灌满则不是死路)

字段内容
判定当前 29 课时内容库:可行。若有人重跑旧 seed/course.ts 占位章:死路
证据G3:g3.ts:27-56 + teachable.ts:9-35(description &lt; 40 字拒生成)。generate/route.ts:81-95 对 plan/ppt/materials 都查。旧占位:seed/course.ts:173-218 知识点 description 空。当前验收说明:29 课时 G1/G3 全绿。
修复建议禁止对占位章点生成;知识点页面对短 description 给同一句 G3 文案。
验证方式抽一课时 POST generate,期望不再 g3_short_description。

《逐课时验收脚本》

环境:演示班「2024级演示班(通识)」或「2024级法学1班」;Cookie 登录 admin。课时 id 不要写死(seed/current-structure.json 的 id 会随导入变化)。先解析:

# 0) 登录拿 Cookie(按项目实际 login API)
# 1) GET /api/classes → 记 CLASS_ID
# 2) GET /api/lesson-plans?classId=$CLASS_ID  (已有 plan 时带 status)
# 3) 备课页 SSR 的 wizard.lessons 或:GET /api/chapters?courseId=… 再 GET /api/lessons?chapterId=…
# 按 title 匹配下表代表课时,记下 LESSON_ID;若尚无 plan,生成后用返回/列表拿 PLAN_ID

代表课时(8 章各 1 + 实验 1;第 8 章第 1 课时 plan_docx/ppt 已实证,本脚本从材料段接上):

代号章代表课时 title 含课型口径主案例
L0第0章 计算思维与计算机基础计算思维(0.1–0.3)讲授无
L1第1章 人工智能概述人工智能概述(1/2)案例DeepSeek-R1
L2第2章 Python程序设计基础Python程序设计基础(1/4)讲授无
L5第5章 计算机视觉计算机视觉技术与应用(1/3)案例香港换脸诈骗
L6第6章 智能语音智能语音处理与应用(1/3)案例AI 声音人格权
L7第7章 NLP自然语言处理与应用(1/2)案例流浪狗舆情
L8第8章 生成式大模型生成式大模型简介案例文生图著作权(已实证到 ppt/plan_docx)
E1实验篇实验1 Python程序设计基础实验实验无

课型参数(绕开 1.4 死路): 讲授传 "讲授",案例传 "案例讨论" 且 body 带已选 caseId,实验传 "实验"。对照坑:UI 默认 "案例讨论" 且无 caseId 时 L0/L2/E1 应 400 g2_no_main_case(记为已知死路,不挡案例课验收)。

公共 API 序列(每个代表课时)

设 $C=CLASS_ID $L=LESSON_ID $T=课型 $CASE=主案例id或省略。

# A. 教案(L8 若已 plan_confirmed / ppt_confirmed 则从 C 接)
POST /api/lesson-plans/generate
  {classId:$C, lessonId:$L, step:"plan", lessonType:$T, caseId:$CASE, run:true}
→ 201 task.id ;轮询 GET /api/tasks/:id 至 done|failed(≤90×2s,与面板 pollTask 一致)
期望:done 且 result.payload.objectives.length≥1;failed+g2_no_main_case 仅允许出现在「未传正确课型/无主案例」对照。
GET  /api/lesson-plans?classId=$C → 该课 status=plan_draft,记下 planId=$P

PUT  /api/lesson-plans/$P/confirm  {step:"plan"}
期望:200,plan.status=plan_confirmed;400 g1_no_objectives 视为该课内容库失败(当前学期不应出现)。

# B. PPT(可与 C 调换顺序)
POST /api/lesson-plans/generate {classId:$C, lessonId:$L, step:"ppt", run:true}
→ 轮询 done
PUT  /api/lesson-plans/$P/confirm {step:"ppt"}
期望:schema.valid 时 200;status 为 ppt_confirmed,或已是 materials_draft/completed 则不降级。
      schema invalid → 400 ppt_hard_constraint,不得进入「已确认 PPT」。
POST /api/lesson-plans/$P/export {type:"ppt"}     期望 201(已实证)
POST /api/lesson-plans/$P/export {type:"plan_docx"} 期望 201(已实证)

# C. 材料 → completed(本审查重点)
POST /api/lesson-plans/generate {classId:$C, lessonId:$L, step:"materials", run:true}
→ 轮询 done
期望:status=materials_draft(若已 completed 则仍 completed,见 1.3);
      draft.materials.exercises 为数组。E1 额外断言 exercises.length≥1(否则题库空,记有风险)。
PUT  /api/lesson-plans/$P/confirm {step:"materials"}
期望:200,status=completed。
      对照:status=ppt_confirmed 且从未 generate materials → 400 need_materials_draft。
      对照:{step:"complete"} → 400 complete_removed。

# D. 其余导出(均 POST /api/lesson-plans/$P/export {type})
type=materials_teacher     期望 201;若确认前导出,warnings 含「材料尚未确认」
type=materials_student     期望 201
type=question_bank         期望 201;下载 csv/xlsx,数据行 = exercises.length(E1 行数=0 则记缺陷)
type=question_bank_xlsx    期望 201;文件集合无 activity_list(与 question_bank 区分)
type=activity_list         期望 201(即使未勾五步);正文含本章课时目录与「照做顺序」
type=toolkit               期望 201;含 chaoxing_checklist;无材料时应 400 need_materials(本步已有材料)

# E. 学习通五步(不阻断 D)
PUT  /api/lesson-plans/$P/chaoxing-steps {step:"align_chapter", done:true}
… 五步均可
GET  同上,期望 done/at
再 export toolkit,checklist 对应行「已勾选」
对照:五步全 false 时 activity_list 仍 201

# F. 发布样例
POST /api/lesson-plans/$P/publish-sample {strip_personal_links:true}
期望:201/200,sample.status=published;sanitizedContent 无真实班级名/教师名。
对照:在 materials_draft 时调用 → 400。
对照:opt-out 后 → 403。

分课时期望状态

课时应用课型 $T生成教案确认教案PPT 确认材料确认导出题库发布样例
L0 讲授讲授plan_draftplan_confirmed可completed行数≥1 为佳201
L1 案例案例讨论+case同上同上讨论课须有争议/记录页同上习题+案例都有201
L2 讲授讲授同上同上可无争议页同上同 L0201
L5 案例案例讨论+case同上同上同 L1同上同上201
L6 案例案例讨论+case同上同上同 L1同上同上201
L7 案例案例讨论+case同上同上同 L1同上同上201
L8 案例已实证到 ppt/plan_docx跳到 C已 plan_confirmed已 ppt_confirmed本步必须新跑新跑新跑
E1 实验实验绕开 G2 后 plan_draftG1 应过勿被「案例讨论」schema 误杀completed断言习题非空201

UI 对照(每课至少抽 1 次,建议 L0 + L1 + E1):

  1. 材料页「确认材料(完成备课)」仅在 materials_draft 且有 bundle.materials 时可点。
  2. completed 后页顶出现五步卡片;ppt_confirmed 无材料时不应依赖五步才能导出活动清单。
  3. 发布按钮仅 completed 可点。
  4. 讲授课/实验课未选主案例时,用 UI 默认生成 应失败(死路 1.4);用正确 $T 的 API 应成功。

修复优先级(建议)

  1. P0 死路:去掉 lessonType: "案例讨论" 硬编码(面板 240 行 + generate route 71 行),否则无主案例讲授/实验永远到不了材料段。
  2. P0 完成态:确认材料的状态准入不要死盯 === materials_draft(重生成教案后假死)。
  3. P1:空 exercises 不得静默导出题库;材料确认前后端空材料规则对齐。
  4. P1:persist 失败不要标 done;PPT 确认补半执行闸。
  5. P2:导出按钮按 need_* 禁用;五步卡片在 plan_confirmed 即显示;发布 422/409 把原因打到面板。

材料确认主路径代码是通的,真正会卡死「备课→上课」的是 UI/G2 把所有课当成案例课、以及确认按钮死盯 materials_draft。 题库/toolkit 缺材料会 400,缺习题却会空文件成功;activity_list 与五步勾选互不阻断。 实验课已有 objectives,G1 不再是死路,但必须显式传课型 实验 并保证材料里有 exercises,否则完成备课后题库仍空、样例也发不出去。

下载此文件