2026-06-22-growth-record-rebuild-design.md 9.0 KB

成长档案重建 — 设计规范

日期: 2026-06-22 状态: 待评审 关联计划: .omo/plans/2026-06-22-growth-record-rebuild.md 范围: cfc-backend + cfc-frontend + cfc-web


1. 背景

当前 growth_records 表和 GrowthRecordService 存在的问题:

问题 现状
无档案类型 externalSync 布尔值,无法区分平台购买/自己上传/补充
无去重机制 同一测评可重复建档
无补充机制 无法在已有档案上追加新报告
无报告日期提取 日期全靠手动,无法从报告中自动提取
前端无上传入口 create.vue 只填基本信息,无文件上传

2. 三种建档流程

2.1 平台购买测评 → 自动同步建档

家长购买测评 → 规划师完成测评 → 提交 DanAssessmentResult
    → 系统自动创建 GrowthRecord(source_type=platform_purchase)
    → report_date 从 DanAssessmentResult.assessmentDate 取
    → danLevel/overallScore/analysisReport 等字段从 DanAssessmentResult 直接复制
    → 去重校验(同一 childId + assessmentDate 不重复建档)

触发时机: 规划师调用 POST /api/dan-assessment/result/record 提交测评结果时,自动触发。

2.2 用户自己上传报告建档

用户点击"上传报告" → 选择 PDF/图片文件
    → 后端存储文件(复用 StorageService)
    → 后端解析报告提取结构化数据(ReportParseService)
    → 返回提取结果(含 report_date + 关键字段)给前端预览
    → 用户确认/修改提取结果
    → 后端创建 GrowthRecord(source_type=self_upload)
    → 去重校验(同一 childId + reportDate 不重复建档)

报告日期提取规则:

  • PDF → Apache PDFBox 提取文本 → 正则匹配日期
  • 图片 → 读取 EXIF 日期(如有)
  • 提取失败 → 前端显示日期输入框,用户手动补充

2.3 档案补充

已有档案详情页 → 点击"补充报告"
    → 两条路径(二选一):
      a. 平台购买(同 2.1,额外传 parent_record_id)
      b. 自己上传(同 2.2,额外传 parent_record_id)
    → parent_record_id 关联原档案
    → 补充后原档案仍保留(不覆盖),形成时间线

3. 数据库设计

3.1 growth_records 表新增字段

ALTER TABLE growth_records ADD COLUMN source_type VARCHAR(20) DEFAULT 'manual'
    COMMENT '档案来源: platform_purchase/self_upload/supplement/manual';

ALTER TABLE growth_records ADD COLUMN source_file_url VARCHAR(500)
    COMMENT '上传报告文件URL(self_upload时有值)';

ALTER TABLE growth_records ADD COLUMN order_id BIGINT
    COMMENT '关联测评订单ID(platform_purchase时有值)';

ALTER TABLE growth_records ADD COLUMN report_date DATETIME
    COMMENT '报告测评日期(从报告内容提取)';

ALTER TABLE growth_records ADD COLUMN parent_record_id BIGINT
    COMMENT '补充来源档案ID(supplement时有值)';

ALTER TABLE growth_records ADD COLUMN is_latest TINYINT(1) DEFAULT 1
    COMMENT '是否该孩子最新档案(去重用)';

ALTER TABLE growth_records ADD COLUMN dedup_hash VARCHAR(64)
    COMMENT '去重哈希: SHA256(childId|reportDate|sourceType)';

ALTER TABLE growth_records ADD COLUMN parsed_data TEXT
    COMMENT '原始解析结果JSON(上传报告的完整提取结果)';

ALTER TABLE growth_records ADD UNIQUE INDEX uk_dedup_hash (dedup_hash);

字段映射:

字段 平台购买 自己上传 补充(平台) 补充(上传) 原手工建档
source_type platform_purchase self_upload supplement supplement manual
source_file_url NULL 文件URL NULL 文件URL NULL
order_id 测评订单ID NULL 测评订单ID NULL NULL
report_date assessmentDate 正则提取 assessmentDate 正则提取 NULL
parent_record_id NULL NULL 原档案ID 原档案ID NULL
dedup_hash SHA256 SHA256 SHA256 SHA256 NULL
parsed_data NULL 解析JSON NULL 解析JSON NULL

3.2 去重规则

dedupHash = SHA256(childId + "|" + reportDate(yyyy-MM-dd) + "|" + sourceType)

去重逻辑:

  1. 插入前计算 dedupHash
  2. 查询 WHERE dedup_hash = ? AND is_latest = 1
  3. 存在 → 返回冲突信息(已有档案 ID 和标题)
  4. 用户选择:覆盖(旧 is_latest=0,插入新记录)或取消
  5. 不存在 → 直接插入

supplement 类型 dedupHash 包含 parent_record_id,同一原始档案可有多份不同日期的补充。

4. 报告解析 Service(ReportParseService)

4.1 新增依赖

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.31</version>
</dependency>

4.2 解析流程

  • parseFromResult(DanAssessmentResult) — 结构化数据直接提取(平台购买)
  • parseReport(String localFilePath) — PDFBox 提取文本 + 正则匹配(上传报告)

4.3 正则提取规则

日期:

  • (测评|报告|评估)日期[::]\\s*(\\d{4})年(\\d{1,2})月(\\d{1,2})日
  • (\\d{4})[-/.](\\d{1,2})[-/.](\\d{1,2})
  • (测评|报告|评估)日期[::]\\s*(\\d{4})[-/.](\\d{1,2})[-/.](\\d{1,2})

DAN 等级: (?:DAN|dan)[\\s等级]*[::]\\s*([A-D])

得分: {字段名}[\\s*得]*分*[::]\\s*(\\d+)

段落: 从 startLabel 匹配到 endLabel 之间的文本

4.4 ParsedReport DTO

@Data @Builder
public class ParsedReport {
    private Date reportDate;
    private String danLevel;
    private Integer overallScore;
    private Integer attentionScore;
    private Integer focusScore;
    private Integer memoryScore;
    private Integer logicScore;
    private Integer perceptionScore;
    private Integer spatialScore;
    private Integer processingSpeedScore;
    private Integer emotionScore;
    private Integer resilienceScore;
    private String assessmentSummary;
    private String growthSuggestions;
    private String rawText;

    public static ParsedReport empty() { return ParsedReport.builder().build(); }
}

5. 后端 API 设计

5.1 修改现有接口

接口 修改
POST /api/growth/record/create 增加 source_type/report_date/source_file_url/parsed_data/parent_record_id;增加去重校验
POST /api/growth/record/create-with-order 传 source_type=platform_purchase;增加去重校验
POST /api/growth/external/sync 传 source_type=platform_purchase;自动从 DanAssessmentResult 取 report_date;去重校验
POST /api/growth/record/{id} 返回增加新字段

5.2 新增接口

接口 说明
POST /api/growth/record/upload-and-parse 上传文件 → 存储 → 解析 → 返回 ParsedReport
POST /api/growth/record/supplement 补充建档(parent_record_id + 同 create 参数)
POST /api/growth/record/duplicate-check 去重检查 {childId, reportDate, sourceType}
POST /api/growth/record/supplements/{recordId} 获取某档案的所有补充记录

5.3 自动建档触发

规划师提交测评结果 POST /api/dan-assessment/result/record 时,自动调用 growthRecordService.createFromAssessment()

6. 前端改动

6.1 growth/create.vue 重构

两种入口:平台购买测评 / 上传报告建档。上传流程:选文件 → 上传解析 → 预览提取结果 → 用户确认 → 建档。日期提取失败时显示手动输入框。

6.2 growth/detail.vue 增强

来源标签、报告日期、原始报告链接、"补充报告"按钮、补充记录列表。

6.3 growth/index.vue 增强

来源标签(平台同步/自己上传/补充)、去重提示。

6.4 新增页面

页面 路径 说明
上传报告建档 pages/growth/upload.vue 上传→解析→确认
补充报告 pages/growth/supplement.vue 选方式→同建档流程

6.5 API 新增

export const uploadAndParseReport = (filePath, childId) => { /* uni.uploadFile */ }
export const supplementGrowthRecord = (data) => request('/api/growth/record/supplement', 'POST', data)
export const checkGrowthRecordDuplicate = (data) => request('/api/growth/record/duplicate-check', 'POST', data)
export const getSupplementRecords = (recordId) => request('/api/growth/record/supplements/' + recordId, 'POST')

7. cfc-web 管理端改动

views/teacher/GrowthRecords.vue 表格增加列:来源类型、报告日期、是否最新、关联档案。补充记录折叠展示。

8. 验收标准

  • 平台购买测评完成 → 自动创建成长档案(source_type=platform_purchase)
  • 用户上传 PDF → 解析出日期/等级/得分 → 用户确认后建档
  • 解析提取不到日期 → 用户可手动填写
  • 同一孩子同一日期同一来源 → 提示重复
  • 补充报告关联原档案,原档案不覆盖
  • 档案列表展示来源标签
  • 档案详情展示报告日期、原始报告链接
  • is_latest 标记正确
  • mvn clean compile 通过