feat(aihr): add pilot satisfaction evidence loop
This commit is contained in:
+8
@@ -16,6 +16,8 @@ import org.dromara.aihr.domain.AihrPracticeDto.RecordResponse;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.MistakeResponse;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.ReviewAdviceRequest;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.ReviewDetailResponse;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.SatisfactionRequest;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.SatisfactionResponse;
|
||||
import org.dromara.aihr.domain.vo.AihrMobileDto.HomeResponse;
|
||||
import org.dromara.aihr.domain.vo.AihrMobileDto.IdentityResponse;
|
||||
import org.dromara.aihr.service.AihrOrgSyncService;
|
||||
@@ -156,6 +158,12 @@ public class AihrMobileController {
|
||||
return response == null ? R.fail("每日三题不存在") : R.ok(response);
|
||||
}
|
||||
|
||||
@PostMapping("/practice/satisfaction")
|
||||
public R<SatisfactionResponse> submitSatisfaction(@RequestBody SatisfactionRequest request) {
|
||||
SatisfactionResponse response = mobileSeedService.submitSatisfaction(request, ownMobileExtPartyId(null));
|
||||
return response == null ? R.fail("满意度提交失败,请确认训练已完成且评分为1-5分") : R.ok(response);
|
||||
}
|
||||
|
||||
private static String ownMobileExtPartyId(String requested) {
|
||||
String username = currentAppUsername();
|
||||
return username.isBlank() ? requested : username;
|
||||
|
||||
+6
@@ -136,6 +136,12 @@ public final class AihrPracticeDto {
|
||||
) {
|
||||
}
|
||||
|
||||
public record SatisfactionRequest(String sessionId, Integer score, String comment) {
|
||||
}
|
||||
|
||||
public record SatisfactionResponse(String sessionId, Integer score, String comment, String submittedAt) {
|
||||
}
|
||||
|
||||
public record DimensionResponse(String label, Object value, String note) {
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -12,6 +12,8 @@ import org.dromara.aihr.domain.AihrPracticeDto.RecordResponse;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.MistakeResponse;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.ReviewAdviceRequest;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.ReviewDetailResponse;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.SatisfactionRequest;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.SatisfactionResponse;
|
||||
import org.dromara.aihr.domain.vo.AihrMobileDto.HomeResponse;
|
||||
import org.dromara.aihr.domain.vo.AihrMobileDto.MetricResponse;
|
||||
import org.dromara.aihr.domain.vo.AihrMobileDto.MiniStatResponse;
|
||||
@@ -98,6 +100,10 @@ public class AihrMobileSeedService {
|
||||
return practiceSeedService.markReviewed(id, request, supervisorExtPartyId);
|
||||
}
|
||||
|
||||
public SatisfactionResponse submitSatisfaction(SatisfactionRequest request, String extPartyId) {
|
||||
return practiceSeedService.submitSatisfaction(request, extPartyId);
|
||||
}
|
||||
|
||||
public CompetencyResponse profile(String extPartyId) {
|
||||
return practiceSeedService.competency(extPartyId);
|
||||
}
|
||||
|
||||
+47
-2
@@ -36,6 +36,8 @@ import org.dromara.aihr.domain.AihrPracticeDto.RubricSaveRequest;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.ScenarioEnabledRequest;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.ScenarioResponse;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.ScenarioSaveRequest;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.SatisfactionRequest;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.SatisfactionResponse;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.StartRequest;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.StartResponse;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.TurnRequest;
|
||||
@@ -176,6 +178,8 @@ public class AihrPracticeSeedService {
|
||||
(SELECT COUNT(*) FROM formal_sessions WHERE status = '已复盘') AS reviewed_count,
|
||||
(SELECT COUNT(*) FROM formal_sessions WHERE total_score < 80 OR status = '待复盘') AS alert_count,
|
||||
COALESCE((SELECT AVG(total_score) FROM formal_sessions), 0) AS average_score,
|
||||
(SELECT COUNT(*) FROM formal_sessions WHERE satisfaction_score IS NOT NULL) AS satisfaction_count,
|
||||
COALESCE((SELECT AVG(satisfaction_score) FROM formal_sessions WHERE satisfaction_score IS NOT NULL), 0) AS satisfaction_average,
|
||||
(SELECT COUNT(*) FROM calibration_rows) AS calibration_count,
|
||||
COALESCE((SELECT SUM(CASE
|
||||
WHEN (CASE WHEN original_score < 60 THEN 0 WHEN original_score < 75 THEN 1 WHEN original_score < 90 THEN 2 ELSE 3 END)
|
||||
@@ -188,6 +192,7 @@ public class AihrPracticeSeedService {
|
||||
FROM safe_org o WHERE o.ext_party_id = s.formal_ext_party_id) AS project_codes,
|
||||
s.session_id, s.trainee_name, s.scenario_name, s.total_score, s.status,
|
||||
s.finished_time, s.summary, s.review_advice, s.incentive_point,
|
||||
s.satisfaction_score, s.satisfaction_comment,
|
||||
s.total_score AS ai_score,
|
||||
c.original_score AS calibration_original_score,
|
||||
c.corrected_score AS human_score,
|
||||
@@ -1005,6 +1010,8 @@ public class AihrPracticeSeedService {
|
||||
rs.getInt("reviewed_count"),
|
||||
rs.getInt("alert_count"),
|
||||
rs.getDouble("average_score"),
|
||||
rs.getInt("satisfaction_count"),
|
||||
rs.getDouble("satisfaction_average"),
|
||||
rs.getInt("calibration_count"),
|
||||
rs.getInt("calibration_matched")
|
||||
), TENANT_ID, startTime, endTime);
|
||||
@@ -1027,6 +1034,7 @@ public class AihrPracticeSeedService {
|
||||
boolean calibrationReady = metrics.calibrationCount() >= 20
|
||||
&& metrics.calibrationMatched() * 100 >= metrics.calibrationCount() * 70;
|
||||
boolean sopReady = sopStats.total() > 0 && sopStats.usable() * 100 >= sopStats.total() * 80;
|
||||
boolean satisfactionReady = metrics.satisfactionCount() > 0 && metrics.satisfactionAverage() >= 4.0D;
|
||||
StringBuilder csv = new StringBuilder();
|
||||
appendCsvRow(csv, "metric", "value");
|
||||
appendCsvRow(csv, "pilot_start_date", startDate);
|
||||
@@ -1062,9 +1070,13 @@ public class AihrPracticeSeedService {
|
||||
appendCsvRow(csv, "sop_answer_usable_rate", percent(sopStats.usable(), sopStats.total()));
|
||||
appendCsvRow(csv, "sop_answer_usable_target", "80.0%");
|
||||
appendCsvRow(csv, "sop_answer_usable_ready", sopReady);
|
||||
appendCsvRow(csv, "pilot_metric_ready", metrics.scenarioCount() >= 12 && completionReady && calibrationReady && sopReady);
|
||||
appendCsvRow(csv, "satisfaction_response_count", metrics.satisfactionCount());
|
||||
appendCsvRow(csv, "satisfaction_average", String.format(java.util.Locale.ROOT, "%.1f", metrics.satisfactionAverage()));
|
||||
appendCsvRow(csv, "satisfaction_target", ">=4.0/5");
|
||||
appendCsvRow(csv, "satisfaction_ready", satisfactionReady);
|
||||
appendCsvRow(csv, "pilot_metric_ready", metrics.scenarioCount() >= 12 && completionReady && calibrationReady && sopReady && satisfactionReady);
|
||||
csv.append('\n');
|
||||
appendCsvRow(csv, "formal_ext_party_id", "project_codes", "session_id", "trainee", "scene", "score", "status", "finished_time", "summary", "review_advice", "incentive_point", "ai_score", "calibration_original_score", "human_score", "calibration_reviewer", "calibration_time", "final_adopted_score");
|
||||
appendCsvRow(csv, "formal_ext_party_id", "project_codes", "session_id", "trainee", "scene", "score", "status", "finished_time", "summary", "review_advice", "incentive_point", "satisfaction_score", "satisfaction_comment", "ai_score", "calibration_original_score", "human_score", "calibration_reviewer", "calibration_time", "final_adopted_score");
|
||||
sessionRows.forEach(row -> appendCsvRow(
|
||||
csv,
|
||||
row.get("formal_ext_party_id"),
|
||||
@@ -1078,6 +1090,8 @@ public class AihrPracticeSeedService {
|
||||
row.get("summary"),
|
||||
row.get("review_advice"),
|
||||
row.get("incentive_point"),
|
||||
row.get("satisfaction_score"),
|
||||
row.get("satisfaction_comment"),
|
||||
row.get("ai_score"),
|
||||
row.get("calibration_original_score"),
|
||||
row.get("human_score"),
|
||||
@@ -1216,6 +1230,31 @@ public class AihrPracticeSeedService {
|
||||
return markReviewed(id, request);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public SatisfactionResponse submitSatisfaction(SatisfactionRequest request, String extPartyId) {
|
||||
ensurePracticeTable();
|
||||
if (request == null || isBlank(request.sessionId()) || isBlank(extPartyId)
|
||||
|| request.score() == null || request.score() < 1 || request.score() > 5) {
|
||||
return null;
|
||||
}
|
||||
String comment = request.comment() == null ? "" : request.comment().trim();
|
||||
if (comment.length() > 500) {
|
||||
comment = comment.substring(0, 500);
|
||||
}
|
||||
comment = maskSensitiveText(comment);
|
||||
Timestamp submittedAt = Timestamp.valueOf(LocalDateTime.now());
|
||||
int updated = jdbcTemplate.update("""
|
||||
UPDATE aihr_practice_session
|
||||
SET satisfaction_score = ?, satisfaction_comment = ?, update_time = ?
|
||||
WHERE tenant_id = ? AND mode = 'mobile' AND session_id = ? AND ext_party_id = ? AND finished_time IS NOT NULL
|
||||
""",
|
||||
request.score(), comment, submittedAt, TENANT_ID, request.sessionId().trim(), extPartyId.trim());
|
||||
if (updated == 0) {
|
||||
return null;
|
||||
}
|
||||
return new SatisfactionResponse(request.sessionId().trim(), request.score(), comment, submittedAt.toString());
|
||||
}
|
||||
|
||||
public List<AnnotationResponse> saveAnnotations(String sessionId, AnnotationSaveRequest request) {
|
||||
ensurePracticeTable();
|
||||
if (isBlank(sessionId)) {
|
||||
@@ -2902,6 +2941,8 @@ public class AihrPracticeSeedService {
|
||||
`ai_comment` varchar(1000) DEFAULT NULL COMMENT 'AI点评',
|
||||
`review_advice` varchar(1000) DEFAULT NULL COMMENT '主管复盘建议',
|
||||
`incentive_point` int DEFAULT NULL COMMENT '主管人工贡献分',
|
||||
`satisfaction_score` tinyint DEFAULT NULL COMMENT '员工训练满意度1-5分',
|
||||
`satisfaction_comment` varchar(500) DEFAULT NULL COMMENT '员工训练满意度意见',
|
||||
`summary` varchar(1000) DEFAULT NULL COMMENT '摘要',
|
||||
`dialogue_json` text DEFAULT NULL COMMENT '对练话术JSON',
|
||||
`annotations_json` text DEFAULT NULL COMMENT '逐句标注JSON',
|
||||
@@ -2921,6 +2962,8 @@ public class AihrPracticeSeedService {
|
||||
ensurePracticeColumn("annotations_json", "ALTER TABLE aihr_practice_session ADD COLUMN `annotations_json` text DEFAULT NULL COMMENT '逐句标注JSON' AFTER `dialogue_json`");
|
||||
ensurePracticeColumn("review_advice", "ALTER TABLE aihr_practice_session ADD COLUMN `review_advice` varchar(1000) DEFAULT NULL COMMENT '主管复盘建议' AFTER `ai_comment`");
|
||||
ensurePracticeColumn("incentive_point", "ALTER TABLE aihr_practice_session ADD COLUMN `incentive_point` int DEFAULT NULL COMMENT '主管人工贡献分' AFTER `review_advice`");
|
||||
ensurePracticeColumn("satisfaction_score", "ALTER TABLE aihr_practice_session ADD COLUMN `satisfaction_score` tinyint DEFAULT NULL COMMENT '员工训练满意度1-5分' AFTER `incentive_point`");
|
||||
ensurePracticeColumn("satisfaction_comment", "ALTER TABLE aihr_practice_session ADD COLUMN `satisfaction_comment` varchar(500) DEFAULT NULL COMMENT '员工训练满意度意见' AFTER `satisfaction_score`");
|
||||
practiceTableReady = true;
|
||||
}
|
||||
}
|
||||
@@ -3450,6 +3493,8 @@ public class AihrPracticeSeedService {
|
||||
int reviewedCount,
|
||||
int alertCount,
|
||||
double averageScore,
|
||||
int satisfactionCount,
|
||||
double satisfactionAverage,
|
||||
int calibrationCount,
|
||||
int calibrationMatched
|
||||
) {
|
||||
|
||||
@@ -19,6 +19,8 @@ CREATE TABLE IF NOT EXISTS `aihr_practice_session` (
|
||||
`ai_comment` varchar(1000) DEFAULT NULL COMMENT 'AI点评',
|
||||
`review_advice` varchar(1000) DEFAULT NULL COMMENT '主管复盘建议',
|
||||
`incentive_point` int DEFAULT NULL COMMENT '主管人工贡献分',
|
||||
`satisfaction_score` tinyint DEFAULT NULL COMMENT '员工训练满意度1-5分',
|
||||
`satisfaction_comment` varchar(500) DEFAULT NULL COMMENT '员工训练满意度意见',
|
||||
`summary` varchar(1000) DEFAULT NULL COMMENT '摘要',
|
||||
`dialogue_json` text DEFAULT NULL COMMENT '对练话术JSON',
|
||||
`annotations_json` text DEFAULT NULL COMMENT '逐句标注JSON',
|
||||
|
||||
Reference in New Issue
Block a user