fix(aihr): persist daily drill scores

This commit is contained in:
2026-07-14 11:35:57 +08:00
parent b7111fe017
commit 9c35949984
7 changed files with 33 additions and 6 deletions
@@ -262,6 +262,7 @@ public final class AihrPracticeDto {
String userAnswer,
String feedback,
Boolean correct,
Integer score,
String audioUrl,
Long audioOssId
) {
@@ -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<PracticeAssignmentResponse> 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;
}
}
@@ -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<PromptTemplateResponse> 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"));
@@ -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 '更新时间',
+1
View File
@@ -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 评测结果。
@@ -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;
};
+1
View File
@@ -301,6 +301,7 @@ export interface PracticeAssignment {
userAnswer?: string;
feedback?: string;
correct?: boolean | null;
score?: number | null;
audioUrl?: string;
audioOssId?: number | string;
}