日期: 2026-06-22
状态: 待评审
关联计划: .omo/plans/2026-06-22-growth-record-rebuild.md
范围: cfc-backend + cfc-frontend + cfc-web
当前 growth_records 表和 GrowthRecordService 存在的问题:
| 问题 | 现状 |
|---|---|
| 无档案类型 | 仅 externalSync 布尔值,无法区分平台购买/自己上传/补充 |
| 无去重机制 | 同一测评可重复建档 |
| 无补充机制 | 无法在已有档案上追加新报告 |
| 无报告日期提取 | 日期全靠手动,无法从报告中自动提取 |
| 前端无上传入口 | create.vue 只填基本信息,无文件上传 |
家长购买测评 → 规划师完成测评 → 提交 DanAssessmentResult
→ 系统自动创建 GrowthRecord(source_type=platform_purchase)
→ report_date 从 DanAssessmentResult.assessmentDate 取
→ danLevel/overallScore/analysisReport 等字段从 DanAssessmentResult 直接复制
→ 去重校验(同一 childId + assessmentDate 不重复建档)
触发时机: 规划师调用 POST /api/dan-assessment/result/record 提交测评结果时,自动触发。
用户点击"上传报告" → 选择 PDF/图片文件
→ 后端存储文件(复用 StorageService)
→ 后端解析报告提取结构化数据(ReportParseService)
→ 返回提取结果(含 report_date + 关键字段)给前端预览
→ 用户确认/修改提取结果
→ 后端创建 GrowthRecord(source_type=self_upload)
→ 去重校验(同一 childId + reportDate 不重复建档)
报告日期提取规则:
已有档案详情页 → 点击"补充报告"
→ 两条路径(二选一):
a. 平台购买(同 2.1,额外传 parent_record_id)
b. 自己上传(同 2.2,额外传 parent_record_id)
→ parent_record_id 关联原档案
→ 补充后原档案仍保留(不覆盖),形成时间线
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 |
dedupHash = SHA256(childId + "|" + reportDate(yyyy-MM-dd) + "|" + sourceType)
去重逻辑:
WHERE dedup_hash = ? AND is_latest = 1is_latest=0,插入新记录)或取消supplement 类型 dedupHash 包含 parent_record_id,同一原始档案可有多份不同日期的补充。
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.31</version>
</dependency>
parseFromResult(DanAssessmentResult) — 结构化数据直接提取(平台购买)parseReport(String localFilePath) — PDFBox 提取文本 + 正则匹配(上传报告)日期:
(测评|报告|评估)日期[::]\\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 之间的文本
@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(); }
}
| 接口 | 修改 |
|---|---|
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} |
返回增加新字段 |
| 接口 | 说明 |
|---|---|
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} |
获取某档案的所有补充记录 |
规划师提交测评结果 POST /api/dan-assessment/result/record 时,自动调用 growthRecordService.createFromAssessment()。
两种入口:平台购买测评 / 上传报告建档。上传流程:选文件 → 上传解析 → 预览提取结果 → 用户确认 → 建档。日期提取失败时显示手动输入框。
来源标签、报告日期、原始报告链接、"补充报告"按钮、补充记录列表。
来源标签(平台同步/自己上传/补充)、去重提示。
| 页面 | 路径 | 说明 |
|---|---|---|
| 上传报告建档 | pages/growth/upload.vue |
上传→解析→确认 |
| 补充报告 | pages/growth/supplement.vue |
选方式→同建档流程 |
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')
views/teacher/GrowthRecords.vue 表格增加列:来源类型、报告日期、是否最新、关联档案。补充记录折叠展示。
mvn clean compile 通过