返回 TeachFlow 项目总览 · 交互图:作业 · 生命周期 / 作业 · 反馈 · 时序
把一段文字和附件发给学生,只完成了“通知”。真正的作业系统还要回答:发布时有哪些学生?后来转入的学生是否应该收到旧作业?返修会不会覆盖第一次提交?教师确认完成是否等于评分正确?哪些结果才有资格进入学情?
TeachFlow 把这些问题放进一条追加式状态链,而不是一张可随意修改的作业表。
发布冻结的是范围,不是一个链接
独立作业在同一 PostgreSQL 事务中写入作业、目标学生、知识关联与附件。全班发布也会冻结当时的成员范围,后来转入的学生不会静默获得历史任务。稳定 ID 与内容指纹使相同请求可以幂等重放,不同内容复用同一 ID 则显式冲突。
教师发布前看到用途、截止时间、附件、题目与目标学生;这层 UI 复核不替代服务端鉴权和数据库合同。

返修不是覆盖
学生每次提交、附件与教师审核都追加保存。教师可以确认完成,也可以说明原因后发回修改;学生再次提交时,上一轮正文、附件和反馈仍可读取。当前状态由最新提交与最新审核派生,历史 occurrence 不被覆盖。
这一区分还保护了附件语义:第二轮没有重新上传文件,不代表第一轮附件变成本轮附件。页面按提交版本展示来源,而不是把现有文件拼成一个从未发生过的“最新组合”。
完成、正确与掌握是三件事
自由文本或附件作业的“教师确认完成”只是流程状态,不自动写 LearningEvent、Current 或 Neo4j。只有冻结答案键的在线题、教师结构化成绩或受控练习结果,才可能经过 Scored Evidence Gate 进入学情。
这种保守设计避免了一个诱人的错误:为了让流程看起来闭环,让模型给主观作业猜分。TeachFlow 宁愿保留“已交但未形成正式评分证据”,也不把工作流完成冒充学习结论。
一次返修如何被准确回放
可以用一个最小场景解释数据合同:学生第一次提交正文 A 和附件 A1,教师以“步骤缺失”发回;学生第二次只提交正文 B,没有重新上传文件;教师随后确认完成。系统不能把 B 与 A1 拼成“第二次提交的完整证据”,也不能删除 A 和反馈。正确结果是保留两次 submission、一次退回决定和一次完成决定,当前工作流状态由最新记录派生,查看历史时仍能知道每一轮究竟包含什么。
同样,教师在发布后修改班级成员或题目内容,也不能静默改变已经发出的作业。目标学生和题目版本属于发布时快照;需要变化时应形成新的任务或明确的新版本。否则,同一个作业 ID 在不同时间会代表不同事实,幂等重试、审计和学生申诉都会失去依据。
设计取舍:为什么不用一张 status 字段解决
单个 status 可以回答“现在显示什么”,却回答不了“为什么来到这里”。追加式提交与审核记录承担历史证据,派生状态承担高效读取,两者职责不同。代价是查询和迁移更复杂,所以系统需要稳定 ID、内容指纹、最新记录索引和明确的非法状态迁移拒绝。
理解这条链路时,应把“流程完成”和“掌握度更新”分开:前者属于作业域,后者只有经过 Scored Evidence Gate 才发生。若没有本轮数据库回归回执,就只能说源码确认了这条边界,不能说整个闭环刚刚端到端通过。
当前边界
源码与历史专项记录支持冻结范围、追加提交、返修、附件授权和确定性评分分流;现有图表解释了状态与调用关系。本轮没有重新启动隔离 PostgreSQL 执行完整作业回归,因此包级状态为 partial。学校通知、Rubric、批量审核、长期附件增长和生产对象存储仍需单独验证。
结论
作业闭环的价值不在于页面数量,而在于每一步都能回答“谁在什么时候提交了什么、谁做了什么决定、哪些结果可以进入学情”。当提交可回放、返修不覆写、评分有准入门,作业才从通知变成可信教学事实。

