fix(aihr): expose reviewed promotion evidence scores
This commit is contained in:
+4
-1
@@ -335,7 +335,10 @@ public final class AihrPracticeDto {
|
||||
List<DimensionResponse> dimensions,
|
||||
List<RecordResponse> latestRecords,
|
||||
List<PracticeAssignmentResponse> assignments,
|
||||
List<String> evidenceItems
|
||||
List<String> evidenceItems,
|
||||
Integer humanAssessmentScore,
|
||||
Integer finalAdoptedScore,
|
||||
Integer calibrationCount
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
+38
-2
@@ -1605,11 +1605,14 @@ public class AihrPracticeSeedService {
|
||||
public PromotionEvidenceResponse promotionEvidence(String extPartyId) {
|
||||
String party = isBlank(extPartyId) ? "" : extPartyId.trim();
|
||||
CompetencyResponse profile = competency(party);
|
||||
EvidenceScoreSnapshot scores = evidenceScores(party);
|
||||
List<RecordResponse> latestRecords = mobileHistory(party, 5);
|
||||
List<PracticeAssignmentResponse> assignmentRows = assignments(party, 5);
|
||||
List<String> evidenceItems = new ArrayList<>();
|
||||
evidenceItems.add("累计完成 " + profile.completed() + " 次移动端AI陪练,训练时长约 " + profile.trainingMinutes() + " 分钟");
|
||||
evidenceItems.add("AI测评分 " + profile.assessmentScore() + ",训练画像参考等级 " + profile.aiLevel() + "(需人工复核)");
|
||||
evidenceItems.add("AI测评分 " + scores.aiScore() + ";人工校准分 "
|
||||
+ (scores.humanScore() == null ? "未采集" : scores.humanScore())
|
||||
+ ";最终采用分 " + scores.finalScore() + ";训练画像参考等级 " + profile.aiLevel() + "(需人工复核)");
|
||||
evidenceItems.add("训练贡献度 " + profile.contribution() + ",待主管复盘 " + profile.pendingReview() + " 条");
|
||||
if (!latestRecords.isEmpty()) {
|
||||
RecordResponse latest = latestRecords.get(0);
|
||||
@@ -1626,10 +1629,40 @@ public class AihrPracticeSeedService {
|
||||
profile.dimensions(),
|
||||
latestRecords,
|
||||
assignmentRows,
|
||||
evidenceItems
|
||||
evidenceItems,
|
||||
scores.humanScore(),
|
||||
scores.finalScore(),
|
||||
scores.calibrationCount()
|
||||
);
|
||||
}
|
||||
|
||||
private EvidenceScoreSnapshot evidenceScores(String extPartyId) {
|
||||
ensureCalibrationTable();
|
||||
String party = isBlank(extPartyId) ? "" : extPartyId.trim();
|
||||
List<EvidenceScoreSnapshot> rows = jdbcTemplate.query("""
|
||||
WITH latest_calibration AS (
|
||||
SELECT tenant_id, session_id, corrected_score,
|
||||
ROW_NUMBER() OVER (PARTITION BY tenant_id, session_id ORDER BY id DESC) AS row_num
|
||||
FROM aihr_practice_calibration
|
||||
WHERE tenant_id = ?
|
||||
)
|
||||
SELECT COALESCE(ROUND(AVG(s.total_score)), 0) AS ai_score,
|
||||
CASE WHEN COUNT(lc.corrected_score) = 0 THEN NULL ELSE ROUND(AVG(lc.corrected_score)) END AS human_score,
|
||||
COALESCE(ROUND(AVG(COALESCE(lc.corrected_score, s.total_score))), 0) AS final_score,
|
||||
COUNT(lc.corrected_score) AS calibration_count
|
||||
FROM aihr_practice_session s
|
||||
LEFT JOIN latest_calibration lc
|
||||
ON lc.tenant_id = s.tenant_id AND lc.session_id = s.session_id AND lc.row_num = 1
|
||||
WHERE s.tenant_id = ? AND s.mode = 'mobile' AND (? = '' OR s.ext_party_id = ?)
|
||||
""", (rs, rowNum) -> new EvidenceScoreSnapshot(
|
||||
rs.getInt("ai_score"),
|
||||
rs.getObject("human_score") == null ? null : rs.getInt("human_score"),
|
||||
rs.getInt("final_score"),
|
||||
rs.getInt("calibration_count")
|
||||
), tenantId(), tenantId(), party, party);
|
||||
return rows.isEmpty() ? new EvidenceScoreSnapshot(0, null, 0, 0) : rows.get(0);
|
||||
}
|
||||
|
||||
private String aiLevel(int completed, int score) {
|
||||
if (completed <= 0) {
|
||||
return "未评级";
|
||||
@@ -3814,6 +3847,9 @@ public class AihrPracticeSeedService {
|
||||
private record CompetencySnapshot(Integer completed, Integer score, Integer pendingReview, Integer compliance, Integer communication, Integer emotion, Integer marketing, Integer incentivePoints) {
|
||||
}
|
||||
|
||||
private record EvidenceScoreSnapshot(Integer aiScore, Integer humanScore, Integer finalScore, Integer calibrationCount) {
|
||||
}
|
||||
|
||||
private record SessionAnnotationSource(String scenarioId, String dialogueJson, String annotationsJson) {
|
||||
}
|
||||
|
||||
|
||||
+13
@@ -608,6 +608,19 @@ public class AihrPracticeSeedServiceTest {
|
||||
assertTrue(profile.growthPath().stream().allMatch(item -> Integer.valueOf(0).equals(item.progress())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void promotionEvidenceIncludesHumanCalibrationAndFinalScore() throws Exception {
|
||||
String source = Files.readString(Path.of("src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java"));
|
||||
int methodStart = source.indexOf("public PromotionEvidenceResponse promotionEvidence");
|
||||
int methodEnd = source.indexOf("private EvidenceScoreSnapshot evidenceScores", methodStart);
|
||||
assertTrue(methodStart >= 0 && methodEnd > methodStart);
|
||||
String method = source.substring(methodStart, methodEnd);
|
||||
assertTrue(method.contains("scores.humanScore()"));
|
||||
assertTrue(method.contains("scores.finalScore()"));
|
||||
assertTrue(source.contains("ROW_NUMBER() OVER (PARTITION BY tenant_id, session_id ORDER BY id DESC)"));
|
||||
assertTrue(source.contains("COALESCE(lc.corrected_score, s.total_score)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void competencyGrowthProgressStopsAtWeakRequirement() {
|
||||
JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
|
||||
|
||||
@@ -260,3 +260,4 @@
|
||||
- 2026-07-14 BRD G3 空身份路径复核:员工任务列表省略 `extPartyId` 时此前会进入空身份查询,普通系统用户可能看到当前租户任务集合;现与个人历史/画像共用同一身份归一化,APP 用户按登录手机号、HR/超级管理员按授权范围读取,普通系统用户直接拒绝;补回归断言,未改变数据库或生产数据。
|
||||
- 2026-07-14 BRD 发布证据复核:当前 HEAD 的管理端、`mobile-uni` H5 和 `ruoyi-admin` jar 均已重新构建通过;只读线上根站与 `/h5/` 均返回 `200`,线上仍加载管理端 `index-CJZ3Ax3Z.js`、H5 `index-D4-NrEpb.js`,本地当前产物已刷新为管理端 `index-Cin-vnjm.js`、H5 `index-Byx3WtY1.js`。`RELEASE_VERIFY_REMOTE_MATCH=true` 的发布预检在产物新鲜度通过后,因两份用户未提交 Figma 文档停止;本轮未修改、暂存或提交这两份文档,也未执行生产同步、后端重启或业务数据写入。
|
||||
- 2026-07-14 BRD 4.3.4 内容现状核对:本地开发租户当前启用生活顾问场景 `12` 条,但服务推介/增值场景 `0` 条、日常服务场景 `0` 条;已入库案例 `2` 条,知识附件处理数 `0` 条。严格预检继续把两类核心流程和正式内容数量作为事实门禁;本轮不自拟业务话术或伪造知识/案例数据,待内容负责人提供正式 SOP、成功条件和脱敏素材后再入库验收。
|
||||
- 2026-07-14 BRD G1/4.5 证据包评分边界修复:成长/晋升证据包此前只展示 AI 测评分,人工校准结果仅存在于试点 CSV;现复用 `aihr_practice_calibration` 最新记录,向管理端和员工端同时返回人工校准分、最终采用分和校准样本数,并在导出证据包中保留三项字段。未校准时明确显示“待采集”,不把 AI 分伪装成人工结论;AI 仍仅作训练参考,不直接生成绩效或晋升结论。
|
||||
|
||||
@@ -95,6 +95,9 @@ export type PromotionEvidence = {
|
||||
latestRecords: ReviewRecord[];
|
||||
assignments: PracticeAssignment[];
|
||||
evidenceItems: string[];
|
||||
humanAssessmentScore?: number | null;
|
||||
finalAdoptedScore?: number | null;
|
||||
calibrationCount?: number;
|
||||
};
|
||||
|
||||
export type OrgSyncRequest = {
|
||||
|
||||
@@ -92,6 +92,11 @@
|
||||
<ul class="evidence-list">
|
||||
<li v-for="item in evidence?.evidenceItems || []" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
<div class="evidence-score-row">
|
||||
<span>人工校准分:{{ evidence?.humanAssessmentScore ?? '待采集' }}</span>
|
||||
<span>最终采用分:{{ evidence?.finalAdoptedScore ?? evidence?.assessmentScore ?? '-' }}</span>
|
||||
<span>校准样本:{{ evidence?.calibrationCount ?? 0 }}</span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel">
|
||||
@@ -275,6 +280,9 @@ const exportEvidenceCsv = () => {
|
||||
appendCsvRow(rows, 'summary', 'ext_party_id', party);
|
||||
appendCsvRow(rows, 'summary', 'current_level', evidence.value.currentLevel);
|
||||
appendCsvRow(rows, 'summary', 'assessment_score', evidence.value.assessmentScore);
|
||||
appendCsvRow(rows, 'summary', 'human_assessment_score', evidence.value.humanAssessmentScore ?? '待采集');
|
||||
appendCsvRow(rows, 'summary', 'final_adopted_score', evidence.value.finalAdoptedScore ?? evidence.value.assessmentScore);
|
||||
appendCsvRow(rows, 'summary', 'calibration_count', evidence.value.calibrationCount ?? 0);
|
||||
appendCsvRow(rows, 'summary', 'training_minutes', evidence.value.trainingMinutes);
|
||||
appendCsvRow(rows, 'summary', 'contribution', evidence.value.contribution);
|
||||
appendCsvRow(rows, 'summary', 'completed', evidence.value.completed);
|
||||
@@ -447,6 +455,15 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.evidence-score-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 18px;
|
||||
margin-top: 14px;
|
||||
color: #667085;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.alert-item {
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
|
||||
@@ -101,7 +101,15 @@
|
||||
</view>
|
||||
<view class="evidence-metric">
|
||||
<text>{{ evidence.assessmentScore }}</text>
|
||||
<text>测评分</text>
|
||||
<text>AI测评分</text>
|
||||
</view>
|
||||
<view class="evidence-metric">
|
||||
<text>{{ evidence.humanAssessmentScore ?? '待采集' }}</text>
|
||||
<text>人工校准分</text>
|
||||
</view>
|
||||
<view class="evidence-metric">
|
||||
<text>{{ evidence.finalAdoptedScore ?? evidence.assessmentScore }}</text>
|
||||
<text>最终采用分</text>
|
||||
</view>
|
||||
<view class="evidence-metric">
|
||||
<text>{{ evidence.trainingMinutes }}</text>
|
||||
|
||||
@@ -509,4 +509,7 @@ export interface PromotionEvidence {
|
||||
latestRecords: PracticeRecord[];
|
||||
assignments: PracticeAssignment[];
|
||||
evidenceItems: string[];
|
||||
humanAssessmentScore?: number | null;
|
||||
finalAdoptedScore?: number | null;
|
||||
calibrationCount?: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user