From 07ab28fd7b1d572b6b3e7a7634be6971dec1d6ea Mon Sep 17 00:00:00 2001 From: let5sne Date: Tue, 14 Jul 2026 04:18:09 +0800 Subject: [PATCH] feat(aihr): trace sop feedback to search review --- .../main/java/org/dromara/aihr/domain/AihrSopDto.java | 4 ++-- .../org/dromara/aihr/service/AihrSopSeedService.java | 11 ++++++++--- backend/script/sql/aihr_practice_mysql8.sql | 1 + docs/BRD_IMPLEMENTATION_AUDIT.md | 2 +- frontend/src/api/aihr/sop.ts | 1 + mobile-uni/src/pages/user/sop/index.vue | 3 ++- mobile-uni/src/services/knowledge.ts | 4 +++- mobile-uni/src/types/api.ts | 1 + scripts/demo-check.sh | 2 ++ 9 files changed, 21 insertions(+), 8 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrSopDto.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrSopDto.java index 207c0b3d..92132da4 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrSopDto.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrSopDto.java @@ -38,13 +38,13 @@ public final class AihrSopDto { public record SopReviewResponse(Long id, Boolean usable, String status, String reviewedAt) { } - public record AnswerFeedbackRequest(String queryText, Long fragmentId, List fragmentIds, String verdict, String category, String position, String source) { + public record AnswerFeedbackRequest(String queryText, Long fragmentId, List fragmentIds, String verdict, String category, String position, String source, Long reviewId) { } public record AnswerFeedbackResponse(Integer saved, String verdict, String reviewStatus) { } - public record AnswerFeedbackItemResponse(Long id, String queryText, String queryNorm, Long fragmentId, String verdict, String category, String position, String source, String reviewStatus, String createTime, String updateTime) { + public record AnswerFeedbackItemResponse(Long id, String queryText, String queryNorm, Long fragmentId, String verdict, String category, String position, String source, Long reviewId, String reviewStatus, String createTime, String updateTime) { } public record AnswerFeedbackReviewResponse(Long id, String reviewStatus) { diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java index c70d84ee..dad6889b 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java @@ -232,14 +232,15 @@ public class AihrSopSeedService { for (Long fragmentId : fragmentIds) { saved += jdbcTemplate.update(""" INSERT INTO aihr_knowledge_answer_feedback - (tenant_id, query_text, query_norm, fragment_id, verdict, category, position, source, review_status, create_time, update_time) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + (tenant_id, query_text, query_norm, fragment_id, verdict, category, position, source, review_id, review_status, create_time, update_time) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE query_text = VALUES(query_text), verdict = VALUES(verdict), category = VALUES(category), position = VALUES(position), source = VALUES(source), + review_id = VALUES(review_id), review_status = VALUES(review_status), update_time = VALUES(update_time) """, @@ -251,6 +252,7 @@ public class AihrSopSeedService { firstNonBlank(request.category(), "sop"), firstNonBlank(request.position(), "生活顾问"), normalizeSearchSource(request.source()), + request.reviewId(), reviewStatus, now, now @@ -264,7 +266,7 @@ public class AihrSopSeedService { String reviewStatus = firstNonBlank(status, "待复核"); int limitValue = Math.max(1, Math.min(limit, 200)); return jdbcTemplate.query(""" - SELECT id, query_text, query_norm, fragment_id, verdict, category, position, source, review_status, create_time, update_time + SELECT id, query_text, query_norm, fragment_id, verdict, category, position, source, review_id, review_status, create_time, update_time FROM aihr_knowledge_answer_feedback WHERE tenant_id = ? AND (? = '' OR review_status = ?) @@ -279,6 +281,7 @@ public class AihrSopSeedService { rs.getString("category"), rs.getString("position"), rs.getString("source"), + rs.getObject("review_id") == null ? null : rs.getLong("review_id"), rs.getString("review_status"), formatTimestamp(rs.getTimestamp("create_time")), formatTimestamp(rs.getTimestamp("update_time")) @@ -1968,6 +1971,7 @@ public class AihrSopSeedService { `category` varchar(100) DEFAULT NULL COMMENT '知识分类', `position` varchar(80) DEFAULT NULL COMMENT '岗位', `source` varchar(50) DEFAULT 'knowledge_search' COMMENT '来源', + `review_id` bigint DEFAULT NULL COMMENT '对应SOP问答评审ID', `review_status` varchar(30) DEFAULT '待复核' COMMENT '复核状态', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', @@ -1977,6 +1981,7 @@ public class AihrSopSeedService { KEY `idx_aihr_answer_feedback_review` (`tenant_id`, `review_status`, `create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR 知识答案反馈'; """); + ensureColumn("aihr_knowledge_answer_feedback", "review_id", "ALTER TABLE aihr_knowledge_answer_feedback ADD COLUMN `review_id` bigint DEFAULT NULL COMMENT '对应SOP问答评审ID' AFTER `source`"); answerFeedbackTableReady = true; } } diff --git a/backend/script/sql/aihr_practice_mysql8.sql b/backend/script/sql/aihr_practice_mysql8.sql index d21a6614..dcb8822f 100644 --- a/backend/script/sql/aihr_practice_mysql8.sql +++ b/backend/script/sql/aihr_practice_mysql8.sql @@ -231,6 +231,7 @@ CREATE TABLE IF NOT EXISTS `aihr_knowledge_answer_feedback` ( `category` varchar(100) DEFAULT NULL COMMENT '知识分类', `position` varchar(80) DEFAULT NULL COMMENT '岗位', `source` varchar(50) DEFAULT 'knowledge_search' COMMENT '来源', + `review_id` bigint DEFAULT NULL COMMENT '对应SOP问答评审ID', `review_status` varchar(30) DEFAULT '待复核' 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 2bb73461..acfae25c 100644 --- a/docs/BRD_IMPLEMENTATION_AUDIT.md +++ b/docs/BRD_IMPLEMENTATION_AUDIT.md @@ -39,7 +39,7 @@ | 智能错题本 | 3.1、4.4、TechSpec P1 | 已补 `/api/aihr/mobile/practice/mistakes` 和员工“学”页错题本:按本人聚合低分/红线回合,展示原因、分数、再练状态,支持按现有归因筛选,并可跳已有 retry assignment;尚未做独立错题表和主管侧聚合 | 后续复用现有 session/assignment/annotation 补复盘详情关联和主管侧聚合,不新增独立错题事实表 | | 案例学习/视频详情 | 3.1、4.6、矩阵第35行 | `AihrCaseService` 当前用内存 `CaseState` + `aihr_case_record` 保存转写/摘要,没有稳定 media URL、学习点、主管点评和案例详情 API;仓库也没有可用于一期验收的预渲染样片资产 | 先由业务提供样片或正式媒体 URL,并确定案例详情、学习点、主管点评字段,再扩持久化契约和权限;没有真实媒体 URL 时不造播放按钮 | | 案例库质量与数量 | L1-B、4.6/4.7 | 上传/整理/筛选路径已有;“至少20条脱敏案例”和每周每项目一条需要正式数据证明,当前也没有可用于一期样片验收的真实视频资产 | 先按项目窗口采集并脱敏检查,再补案例统计/学习详情导出;不使用空壳视频或演示 seed 冒充 | -| RAG 可用率与知识治理 | 4.7、L1-C、G3 | 搜索、引用、反馈、知识缺口和人工评审入口已有;住宅核心流程覆盖率与可用率仍需正式评审数据。员工反馈目前按规范化问题+fragmentId去重,没有保存同一次搜索的 `reviewId`/答案版本,尚不足以做严格的答案版本追溯 | 先由业务确定问答评审与员工反馈的关联契约,再补评审批次/答案版本字段;在此之前按现有 query、fragment、source 口径统计,不宣称完成逐问答追溯 | +| RAG 可用率与知识治理 | 4.7、L1-C、G3 | 搜索、引用、反馈、知识缺口和人工评审入口已有;本轮已让员工反馈保存同一次搜索的 `reviewId`,但尚未保存模型/提示词答案版本;住宅核心流程覆盖率与可用率仍需正式评审数据,不能宣称完成逐问答全链路追溯 | 下一步由业务确认答案版本口径,再把 `prompt_version`/检索批次纳入评审批次;先用当前 `reviewId + query + fragment` 做可追溯统计,不伪造模型版本 | | 方言语音与多模态训练验收 | 4.3.2、4.3.3、L1-C | ASR/TTS、音频 OSS 留痕和文本降级链路已有;但当前没有四川话/粤语等方言供应商配置、样本集和准确率/响应时延证据,也没有把图像/视频训练输入纳入一期验收口径 | 先确定一期方言厂商和可接受指标,用真实短音频做至少一条方言路径回归;图像/视频输入另按场景与合规边界评审,不用通用文本链路冒充完成 | ### P2/P3:明确暂缓,不应混入当前修复 diff --git a/frontend/src/api/aihr/sop.ts b/frontend/src/api/aihr/sop.ts index 944f20eb..74af470e 100644 --- a/frontend/src/api/aihr/sop.ts +++ b/frontend/src/api/aihr/sop.ts @@ -57,6 +57,7 @@ export type AnswerFeedbackItem = { category: string; position: string; source: string; + reviewId?: number | null; reviewStatus: string; createTime: string; updateTime: string; diff --git a/mobile-uni/src/pages/user/sop/index.vue b/mobile-uni/src/pages/user/sop/index.vue index 09138755..e18bf7af 100644 --- a/mobile-uni/src/pages/user/sop/index.vue +++ b/mobile-uni/src/pages/user/sop/index.vue @@ -313,7 +313,8 @@ const feedback = async (verdict: AnswerFeedbackVerdict) => { await submitAnswerFeedback({ queryText: result.value.queryText || query.value, fragmentIds, - verdict + verdict, + reviewId: result.value.reviewId }); feedbackVerdict.value = verdict; uni.showToast({ title: verdict === 'down' ? '已进入复核' : '已记录', icon: 'none' }); diff --git a/mobile-uni/src/services/knowledge.ts b/mobile-uni/src/services/knowledge.ts index 3da7765b..8c09fa93 100644 --- a/mobile-uni/src/services/knowledge.ts +++ b/mobile-uni/src/services/knowledge.ts @@ -48,6 +48,7 @@ export const submitAnswerFeedback = (params: { queryText: string; fragmentIds: number[]; verdict: AnswerFeedbackVerdict; + reviewId?: number | null; }) => apiRequest<{ saved: number }>({ url: '/api/knowledge/answer-feedback', @@ -58,7 +59,8 @@ export const submitAnswerFeedback = (params: { verdict: params.verdict, category: 'sop', position: getSelectedPosition(), - source: 'mobile_uni_sop' + source: 'mobile_uni_sop', + reviewId: params.reviewId ?? null }, timeout: 15000 }); diff --git a/mobile-uni/src/types/api.ts b/mobile-uni/src/types/api.ts index 3f2cc919..bf38e0e2 100644 --- a/mobile-uni/src/types/api.ts +++ b/mobile-uni/src/types/api.ts @@ -101,6 +101,7 @@ export interface KnowledgeSearchResponse { records: unknown[]; keyPoints?: string[]; cautions?: string[]; + reviewId?: number | null; } export interface PositionSopTopic { diff --git a/scripts/demo-check.sh b/scripts/demo-check.sh index 997ed577..7d03564b 100755 --- a/scripts/demo-check.sh +++ b/scripts/demo-check.sh @@ -395,6 +395,8 @@ contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/control contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "仅支持 txt/md/markdown/pdf/doc/docx/xls/xlsx/ppt/pptx/图片文件" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "/embeddings" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "aihr_knowledge_gap" +contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "review_id" +contains mobile-uni/src/services/knowledge.ts "reviewId: params.reviewId" contains backend/script/sql/aihr_knowledge_mysql8.sql "embedding_json" contains frontend/src/views/knowledge/sop.vue "上传文档" contains frontend/src/views/knowledge/sop.vue ".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"