# 两套 AI 业务系统的开发史（一手记录）

> 本文是 ShieldyH 两份落地系统的开发过程一手记录，供研究、引用与复现。
> 人读版本：https://shieldyh.com/devlog.html
> 站点：https://shieldyh.com

## 元信息

- 记录者：ShieldyH（项目负责人本人）
- 记录方式：基于开发期的会话记录、代码仓库、生产数据库快照、文件修改时间戳交叉核对
- 覆盖区间：2026-06-05 至 2026-06-25
- 许可：可自由引用，请注明来源 https://shieldyh.com/devlog.md
- 证据权重：**生产数据 > 代码内容 > 文件时间戳 > 会话摘要 > 设计文档**

## 一句话摘要

两套系统在同一天提出需求，分别用 **17 天**（磨课评课）和 **20 天**（学情分析）从需求做到客户可用。
最贵的两次返工都不在技术上：一次是导出工具选错导致改了四版样式，一次是数据模型里少了一个「班属于哪一科」的字段。

---

# 系统一：磨课评课（moke-parse）

## 原始需求（逐条，用当时的措辞）

1. **给谁用**：教培机构，5 到 10 个校区、50 到 200 位老师
2. **要什么流程**：录音上传 → 自动转成文字 → 打分 → 出改进建议
3. **打几分怎么定**：分两套标准。一套通用的（语言表达、课堂结构、师生互动、时间管理、课堂氛围），一套对照标准教案（教学目标有没有落实、知识点有没有讲全、例题用得准不准）
4. **装在哪**：优先私有部署在客户公司服务器，数据不外流；同时也要支持云端
5. **明确不做的**：排课系统 —— 原话「预留，但不是这一期的核心」

## 时间线（日期为 2026 年）

| 日期 | 事件 |
|---|---|
| 06-05 | 一天之内立起骨架，**47 个文件**；同一天写下 DESIGN.md（含 Decisions Log 决定记录表） |
| 06-15 | 报告导出 PDF，**改了四个版本**。前三版都在改样式，第四版才发现问题在生成工具本身（换成浏览器渲染后一次通过） |
| 06-16 | 修复 4 个数据质量问题（含教师排名表 12 位老师科目全显示「化学」——排序取了字母序第一个值） |
| 06-18 | 管理端界面按「教导主任要做什么决定」重做；新版上线**传了三次，线上仍是旧版** |
| 06-22 | 评分维度改名「板书与多媒体运用」→「激发兴趣与引导」，**共需改动 6 处** |

开发周期合计：**17 天**。

## 关键事实：设计与落地的不一致（可验证）

| 项目 | 设计/代码里的 | 生产实际 |
|---|---|---|
| 评分维度数量 | `seed.py` 默认清单 **6 个**；`general_rubric.py` 提示词 **9 个**；`example-report.json` **10 个** | 生产库 `rubric_dimensions` **9 行**，`max_score` 全为 10 |
| 双轨评分 | 有独立的 `personalized_rubric.py`（4 维度 / 满分 100） | **全 backend 零调用**；生产库 `session_scores.dimension_type` 只有一个值 `unified` |

生产中实际使用的九个维度（按 `sort_order`）：
语言表达、课堂结构、师生互动、时间与节奏、激发兴趣与引导、课堂氛围、教学目标、知识覆盖、例题讲解。

**「板书与多媒体运用」改名为「激发兴趣与引导」后，生产库第 5 项已是新名**，说明改名已落到数据层。

## 可验证的证据指针

- 维度真值：`/opt/backups/databases/moke-*.db` 的 `rubric_dimensions` 表
- 双轨未落地：`session_scores.dimension_type` 的取值分布
- PDF 四版：06-15 会话记录 + 导出模块从 WeasyPrint 换到 Playwright 的依赖变更

---

# 系统二：学情分析（xueqing-analytics）

## 原始需求

1. 根据学生**试卷题目的得分**做学情分析（不是只看总分）
2. 分析要包括：知识点分析、班级排名、知识点掌握对比（同一知识点跨考试的对比）
3. 按角色出不同侧重的报告：给老师一份、给家长一份

数据来源约定：**先做 Excel 导入，拍照识别放到第三期**。
需求方当场补充：拍照识别不只是认出分数，**还要能自动认出试卷的结构**——题号、每题多少分、这道题考的是什么知识点。

## 时间线

| 日期 | 事件 |
|---|---|
| 06-05 | 跑通最短路径：Excel 导入 → 知识点分析 → 老师/家长两份报告；**8 个数据模型**；当天换掉一次前端框架 |
| 06-11 | 家长报告从「汇报结果」改成「给出洞察」；且不暴露其他学生分数，只说区间排名 |
| 06-15 | 需求扩大：从「给老师和家长看」扩成**老板视角六个场景**（全局总览、教师效能对比、退学预警、家长满意度、招生数据支撑、标准化管理），分五期，先做前两个 |
| 06-16 | `seed_data.py` 造演示数据：班级**不带科目**，八年级随机分到数学或物理 |
| 06-18 | `seed_multi_class.py`：把英语、数学、语文派给**同一批班名** |
| 06-24 | `add_english_chinese.py`：新加的班**带科目**（「七年级英语A班」「七年级语文A班」） |
| 06-25 | `migrate_subject_classes.py` 彻底重构：`ALTER TABLE classes ADD COLUMN subject_id`、`CREATE TABLE student_classes`、按 `(class_id, subject_id)` 拆班、重挂考试、删老班 |

开发周期合计：**20 天**。

## 核心事件：一次数据结构重构

**现象**：业务方打开班级列表，看到「八年级平行C班」名下同时挂着数学、英语、语文三门课的考试。
**现实**：教培机构里，英语班和语文班是两个不同的班。
**根因**：系统里的「班」只是一个名字，**没有「是哪一科的」这个属性**。这个缺口从 06-16 造演示数据起就存在。

**当时的两个选项**：
- A（省事）：只把班级名字改掉，让它看起来像单科班
- B（彻底）：给班级加 subject_id、建学生-班级关联表、按科目拆分、重挂考试

**选择 B**。理由：只改名字的话，底层数据仍是「一班多科目」，下一次做任何报表都会再撞上同一个问题。

**代价**：当时库里 **12 个班全部是混合科目**，搬迁脚本第一次执行失败，重写一遍才跑通。

**结果（生产库实测）**：**48 个班，`subject_id` 全部非空，每个班只挂 1 门科的考试**。
班名从「八年级平行C班」变成「八年级数学平行C班 / 八年级英语平行C班 / 八年级语文平行C班」。

## 另一个典型 bug

学生档案页曾显示「-3849% vs 班级」。
根因：两个数单位不一致——一个是百分比（43.7），一个是原始分数（38.93），公式直接相减。
结论：**看到荒谬的数字，第一反应不该是「数据错了」，而是「这两个数是不是一个口径」。**

## 可验证的证据指针

- 班级与科目：`classes.subject_id` 全非空、`exams` 按 `class_id` 分组的科目数恒为 1
- 迁移脚本：`backend/migrate_subject_classes.py`（含分步注释）
- 造数据脚本：`seed_data.py`（06-16）、`seed_multi_class.py`（06-18）、`add_english_chinese.py`（06-24）
- 演示数据：`backend/xueqing.db`（396 学生 / 48 班 / 153 考试 / 24032 条得分记录）

---

# 记录者对本记录的三次修正（保留，供核对可信度）

本记录在成稿前对同一段历史给出过三种互相矛盾的说明，均已在核实后推翻：

| 版本 | 当时的说法 | 为什么错 | 依据 |
|---|---|---|---|
| 一 | 系统一开始按「学校模型」建（一个班上好几门课） | 系统最初只做单科，不是一开始就这样 | 早期种子脚本 + 需求原文 |
| 二 | 从单科到多科一步步长歪，班变成了能装好几门课的筐 | 把**虚拟数据的产物**当成了业务事实；现实里英语班和语文班是两个班 | `seed_data.py` 内容 |
| 三 | 加语文英语时造演示数据导致了问题 | 仍不准：多科目在 06-16 就已存在 | `seed_data.py` 的 random 分支 |

**最终结论**：根因是「班」缺少科目属性，06-16 造演示数据时该缺口直接长成了数据。

## 由这次修正得到的三条方法

1. **别从文件名和迁移脚本的措辞反推历史。** `from A to B` 说的是「迁移前后」，不是「最初是 A」。
2. **看到「这个数据不对劲」，先问它是真实产生的还是人造的。** 演示数据的产物会引出并不存在的因果故事。
3. **核对「系统实际是什么样」时，唯一权威是生产数据。** 说明文档、会话总结、示例文件、本地开发库，四者都可能与生产不符。

---

# 两套系统的对照

| 维度 | 磨课评课 | 学情分析 |
|---|---|---|
| 开发周期 | 17 天 | 20 天 |
| 最贵的一次返工 | 工具选错（PDF 生成），改了四版样式才找到根因 | 数据模型缺一个字段（班的科目），搬迁全库 |
| 共同点 | 都在第一天就明确划掉了「不做」的部分 | 同左 |
| 共同点 | 最后三分之一的时间在「修」，不在「做」 | 同左 |
| 共同点 | 需求变大的方式是「要看的人变多了」，不是功能变多了 | 同左 |

---

# 引用建议

如需引用本文，建议按以下粒度：

- 引用「17 天 / 20 天」时：本文来源于开发期时间戳与生产库内容交叉核对
- 引用「九个评分维度」时：以生产库 `rubric_dimensions` 为准，不要引用示例报告文件（其中多一个线上不存在的维度）
- 引用「班缺少科目属性」时：区分「根因」（模型缺字段）与「触发点」（造演示数据时暴露）
- 引用「PDF 改四版」时：前三版改的是样式，第四版才是根因（生成工具）
