From 9c35949984baa1d6495e558dc9c9284897a56557 Mon Sep 17 00:00:00 2001 From: let5sne Date: Tue, 14 Jul 2026 11:35:57 +0800 Subject: [PATCH] fix(aihr): persist daily drill scores --- .../dromara/aihr/domain/AihrPracticeDto.java | 1 + .../aihr/service/AihrPracticeSeedService.java | 17 +++++++++++------ .../service/AihrPracticeSeedServiceTest.java | 14 ++++++++++++++ backend/script/sql/aihr_practice_mysql8.sql | 1 + docs/BRD_IMPLEMENTATION_AUDIT.md | 1 + mobile-uni/src/pages/user/practice/index.vue | 4 ++++ mobile-uni/src/types/api.ts | 1 + 7 files changed, 33 insertions(+), 6 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java index 1d939df5..153c5532 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java @@ -262,6 +262,7 @@ public final class AihrPracticeDto { String userAnswer, String feedback, Boolean correct, + Integer score, String audioUrl, Long audioOssId ) { diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java index 81f5d208..dd9d831b 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java @@ -993,7 +993,7 @@ public class AihrPracticeSeedService { args.add(limit); String sql = """ SELECT id, ext_party_id, trainee_name, scenario_id, scenario_name, source, reason, status, create_time, - answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct + answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct, score FROM aihr_practice_assignment %s ORDER BY create_time DESC, id DESC @@ -1716,7 +1716,7 @@ public class AihrPracticeSeedService { ensureDailyDrills(party); return jdbcTemplate.query(""" SELECT id, ext_party_id, trainee_name, scenario_id, scenario_name, source, reason, status, create_time, - answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct + answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct, score FROM aihr_practice_assignment WHERE tenant_id = ? AND (? = '' OR ext_party_id = ?) @@ -1754,7 +1754,7 @@ public class AihrPracticeSeedService { args.add(normalizeLimit(limit)); return jdbcTemplate.query(""" SELECT id, ext_party_id, trainee_name, scenario_id, scenario_name, source, reason, status, create_time, - answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct + answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct, score FROM aihr_practice_assignment WHERE tenant_id = ? AND ext_party_id IN (%s) @@ -1857,13 +1857,13 @@ public class AihrPracticeSeedService { """, tenantId(), extPartyId, traineeName, scenario.id(), firstNonBlank(request == null ? null : request.scenarioName(), scenario.name()), source, reason, createdAt, createdAt); List rows = jdbcTemplate.query(""" SELECT id, ext_party_id, trainee_name, scenario_id, scenario_name, source, reason, status, create_time, - answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct + answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct, score FROM aihr_practice_assignment WHERE tenant_id = ? AND ext_party_id = ? AND scenario_id = ? AND source = ? AND reason = ? ORDER BY id DESC LIMIT 1 """, this::mapAssignment, tenantId(), extPartyId, scenario.id(), source, reason); - return rows.isEmpty() ? new PracticeAssignmentResponse(null, extPartyId, traineeName, scenario.id(), scenario.name(), source, reason, "待训练", now.format(TIME_FORMATTER), null, null, null, null, null, null, null) : rows.get(0); + return rows.isEmpty() ? new PracticeAssignmentResponse(null, extPartyId, traineeName, scenario.id(), scenario.name(), source, reason, "待训练", now.format(TIME_FORMATTER), null, null, null, null, null, null, null, null) : rows.get(0); } public PracticeAssignmentResponse createAssignment(PracticeAssignmentRequest request, String supervisorExtPartyId) { @@ -1935,6 +1935,7 @@ public class AihrPracticeSeedService { answer_audio_oss_id = ?, feedback = ?, correct = ?, + score = ?, finished_time = ?, update_time = ? WHERE tenant_id = ? AND id = ? AND source = 'daily' @@ -1946,6 +1947,7 @@ public class AihrPracticeSeedService { audioOssId, score.feedback(), score.correct() ? 1 : 0, + score.score(), Timestamp.valueOf(now), Timestamp.valueOf(now), tenantId(), @@ -2509,6 +2511,7 @@ public class AihrPracticeSeedService { rs.getString("answer_text"), "daily".equals(source) ? stripReferenceAnswer(rs.getString("feedback")) : rs.getString("feedback"), correctValue == null ? null : rs.getBoolean("correct"), + rs.getObject("score") == null ? null : rs.getInt("score"), rs.getString("answer_audio_url"), rs.getObject("answer_audio_oss_id") == null ? null : rs.getLong("answer_audio_oss_id") ); @@ -3315,6 +3318,7 @@ public class AihrPracticeSeedService { `answer_audio_oss_id` bigint DEFAULT NULL COMMENT '每日三题语音OSS编号', `feedback` varchar(1000) DEFAULT NULL COMMENT '每日三题反馈', `correct` tinyint DEFAULT NULL COMMENT '是否答对', + `score` int DEFAULT NULL COMMENT '每日三题得分', `finished_time` datetime DEFAULT NULL COMMENT '完成时间', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', @@ -3328,7 +3332,8 @@ public class AihrPracticeSeedService { ensureColumn("aihr_practice_assignment", "answer_audio_oss_id", "ALTER TABLE aihr_practice_assignment ADD COLUMN `answer_audio_oss_id` bigint DEFAULT NULL COMMENT '每日三题语音OSS编号' AFTER `answer_audio_url`"); ensureColumn("aihr_practice_assignment", "feedback", "ALTER TABLE aihr_practice_assignment ADD COLUMN `feedback` varchar(1000) DEFAULT NULL COMMENT '每日三题反馈' AFTER `answer_audio_oss_id`"); ensureColumn("aihr_practice_assignment", "correct", "ALTER TABLE aihr_practice_assignment ADD COLUMN `correct` tinyint DEFAULT NULL COMMENT '是否答对' AFTER `feedback`"); - ensureColumn("aihr_practice_assignment", "finished_time", "ALTER TABLE aihr_practice_assignment ADD COLUMN `finished_time` datetime DEFAULT NULL COMMENT '完成时间' AFTER `correct`"); + ensureColumn("aihr_practice_assignment", "score", "ALTER TABLE aihr_practice_assignment ADD COLUMN `score` int DEFAULT NULL COMMENT '每日三题得分' AFTER `correct`"); + ensureColumn("aihr_practice_assignment", "finished_time", "ALTER TABLE aihr_practice_assignment ADD COLUMN `finished_time` datetime DEFAULT NULL COMMENT '完成时间' AFTER `score`"); assignmentTableReady = true; } } diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java index b08b59b5..25b649a9 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java @@ -135,6 +135,20 @@ public class AihrPracticeSeedServiceTest { assertTrue(method.contains("if (updated == 0)")); } + @Test + public void dailyDrillScoreSurvivesAssignmentRefresh() throws Exception { + String source = Files.readString(Path.of("src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java")); + int answerStart = source.indexOf("public DailyDrillAnswerResponse answerDailyDrill"); + int answerEnd = source.indexOf("public List promptTemplates", answerStart); + assertTrue(answerStart >= 0 && answerEnd > answerStart); + String answerMethod = source.substring(answerStart, answerEnd); + assertTrue(answerMethod.contains("score = ?")); + assertTrue(answerMethod.contains("score.score()")); + assertTrue(source.contains("feedback, correct, score")); + assertTrue(source.contains("rs.getObject(\"score\")")); + assertTrue(source.contains("ADD COLUMN `score` int")); + } + @Test public void mobileAssignmentStartValidatesOwnerStatusAndScenario() throws Exception { String source = Files.readString(Path.of("src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java")); diff --git a/backend/script/sql/aihr_practice_mysql8.sql b/backend/script/sql/aihr_practice_mysql8.sql index 06a6961c..d01b08f9 100644 --- a/backend/script/sql/aihr_practice_mysql8.sql +++ b/backend/script/sql/aihr_practice_mysql8.sql @@ -103,6 +103,7 @@ CREATE TABLE IF NOT EXISTS `aihr_practice_assignment` ( `answer_audio_oss_id` bigint DEFAULT NULL COMMENT '每日三题语音OSS编号', `feedback` varchar(1000) DEFAULT NULL COMMENT '每日三题反馈', `correct` tinyint DEFAULT NULL COMMENT '是否答对', + `score` int DEFAULT NULL COMMENT '每日三题得分', `finished_time` datetime DEFAULT NULL COMMENT '完成时间', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', diff --git a/docs/BRD_IMPLEMENTATION_AUDIT.md b/docs/BRD_IMPLEMENTATION_AUDIT.md index f6bb1aef..03d43d43 100644 --- a/docs/BRD_IMPLEMENTATION_AUDIT.md +++ b/docs/BRD_IMPLEMENTATION_AUDIT.md @@ -193,4 +193,5 @@ - 2026-07-14 BRD 案例提交能力边界复核:移动端案例页原先对所有登录员工展示“提交素材”表单,但后端仅允许主管/项目负责人提交,普通员工点击后才得到权限错误;现新增只读 `GET /api/knowledge/case/capabilities`,复用服务端唯一贡献者判定,员工端仅在 `canSubmit=true` 时展示提交入口,其他员工显示案例学习说明。后端权限仍保留为最终防线。 - 2026-07-14 线上只读发布差异:生产根站和 `/h5/` 返回 `200`,生产仍加载 H5 `assets/index-D4-NrEpb.js`;本地 `b8be1bb6` 构建已生成 `mobile-uni/dist/build/h5/assets/index-u8NWYNWX.js`,案例状态过滤和提交能力入口尚未发布。本轮未执行生产静态资源同步、后端重启或业务数据写入。 - 2026-07-14 BRD 4.7 SOP 指标口径复核:正式试点的 SOP 可用率与待评审数现只统计评审表中 `category='sop'` 或名称包含 `SOP` 的记录,并继续按租户和试点日期窗口过滤;培训类或其他知识问答不会混入正式 SOP 分母。后端导出、严格预检和单测夹具已统一该条件;岗位与住宅项目的正式适用性仍需组织/HR 规则输入,不能仅靠分类字符串替代。 +- 2026-07-14 BRD 4.4 每日三题结果留痕补强:每日三题提交返回的分数此前只存在响应内,刷新任务列表后无法恢复数值分数;现新增 `aihr_practice_assignment.score`,提交、查询、员工学习页均保留“得分 + 反馈”。当前得分仍来自可解释关键词规则兜底,不把它宣称为语义级 AI 评分;后续若启用模型评估,需单独补评分模式、成本闸门和人工抽检证据。 - 2026-07-14 BRD 4.4 每日一练评分能力复核:当前 `scoreDailyDrill` 使用参考答案关键词命中和长度规则即时反馈,属于可解释的规则兜底,不等同于 BRD 所称的语义级“AI 秒评”;正式升级前需确认模型、成本闸门、评分维度和人工抽检样本,当前不把规则分数宣称为 AI 评测结果。 diff --git a/mobile-uni/src/pages/user/practice/index.vue b/mobile-uni/src/pages/user/practice/index.vue index 166d2f91..cbefd50a 100644 --- a/mobile-uni/src/pages/user/practice/index.vue +++ b/mobile-uni/src/pages/user/practice/index.vue @@ -363,6 +363,10 @@ const roleLabel = (role: PracticeRole) => { const assignmentDesc = (item: PracticeAssignment) => { if (item.source === 'daily' && item.status !== '已完成') return '提交后显示反馈与参考答案'; + if (item.source === 'daily' && item.status === '已完成') { + const score = typeof item.score === 'number' ? `得分 ${item.score} 分` : ''; + return [score, item.feedback || item.reason].filter(Boolean).join(' · '); + } return item.feedback || item.reason; }; diff --git a/mobile-uni/src/types/api.ts b/mobile-uni/src/types/api.ts index 7f3971f7..ea9e4eb9 100644 --- a/mobile-uni/src/types/api.ts +++ b/mobile-uni/src/types/api.ts @@ -301,6 +301,7 @@ export interface PracticeAssignment { userAnswer?: string; feedback?: string; correct?: boolean | null; + score?: number | null; audioUrl?: string; audioOssId?: number | string; }