fix(aihr): persist org hire dates

This commit is contained in:
2026-07-14 14:48:48 +08:00
parent 96a9cffc82
commit 3f39be7fee
8 changed files with 75 additions and 14 deletions
@@ -27,6 +27,7 @@ import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -353,8 +354,8 @@ public class AihrOrgSyncService {
jdbcTemplate.batchUpdate(""" jdbcTemplate.batchUpdate("""
insert into aihr_org_snapshot insert into aihr_org_snapshot
(tenant_id, project_code, project_name, dept_name, ext_party_id, person_phone, person_name, (tenant_id, project_code, project_name, dept_name, ext_party_id, person_phone, person_name,
position_name, position_level, employment_status, snapshot_date, create_time) position_name, position_level, employment_status, hire_date, snapshot_date, create_time)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, now()) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, now())
on duplicate key update on duplicate key update
project_code = values(project_code), project_code = values(project_code),
project_name = values(project_name), project_name = values(project_name),
@@ -364,6 +365,7 @@ public class AihrOrgSyncService {
position_name = values(position_name), position_name = values(position_name),
position_level = values(position_level), position_level = values(position_level),
employment_status = values(employment_status), employment_status = values(employment_status),
hire_date = values(hire_date),
snapshot_date = values(snapshot_date), snapshot_date = values(snapshot_date),
create_time = now() create_time = now()
""", rows.stream().map(row -> row.args(tenantId())).toList()); """, rows.stream().map(row -> row.args(tenantId())).toList());
@@ -397,6 +399,8 @@ public class AihrOrgSyncService {
String positionName = firstNonBlank(text(employee, "position_name", "positionName", "job_title", "jobTitle", String positionName = firstNonBlank(text(employee, "position_name", "positionName", "job_title", "jobTitle",
"post_name", "postName", "job_name", "jobName", "role_name", "roleName", "position", "title"), "员工"); "post_name", "postName", "job_name", "jobName", "role_name", "roleName", "position", "title"), "员工");
String positionLevel = firstNonBlank(text(employee, "position_level", "positionLevel", "job_level", "jobLevel"), level(positionName)); String positionLevel = firstNonBlank(text(employee, "position_level", "positionLevel", "job_level", "jobLevel"), level(positionName));
LocalDate hireDate = parseDate(firstNonBlank(
text(employee, "hire_date", "hireDate", "entry_date", "entryDate", "employment_date", "employmentDate")));
boolean suspectText = hasQuestionMark(projectName) boolean suspectText = hasQuestionMark(projectName)
|| hasQuestionMark(deptName) || hasQuestionMark(deptName)
|| hasQuestionMark(personName) || hasQuestionMark(personName)
@@ -411,6 +415,7 @@ public class AihrOrgSyncService {
positionName, positionName,
positionLevel(positionLevel, positionName), positionLevel(positionLevel, positionName),
status(text(employee, "status", "employment_status", "employmentStatus")), status(text(employee, "status", "employment_status", "employmentStatus")),
hireDate,
LocalDate.now(), LocalDate.now(),
suspectText suspectText
); );
@@ -530,6 +535,7 @@ public class AihrOrgSyncService {
throw new IllegalArgumentException("请先执行 aihr_org_snapshot_mysql8.sql 初始化组织人员快照表"); throw new IllegalArgumentException("请先执行 aihr_org_snapshot_mysql8.sql 初始化组织人员快照表");
} }
ensureSnapshotColumn("person_phone", "ALTER TABLE aihr_org_snapshot ADD COLUMN `person_phone` varchar(30) DEFAULT NULL COMMENT '手机号(仅用于登录身份映射)' AFTER `ext_party_id`"); ensureSnapshotColumn("person_phone", "ALTER TABLE aihr_org_snapshot ADD COLUMN `person_phone` varchar(30) DEFAULT NULL COMMENT '手机号(仅用于登录身份映射)' AFTER `ext_party_id`");
ensureSnapshotColumn("hire_date", "ALTER TABLE aihr_org_snapshot ADD COLUMN `hire_date` date DEFAULT NULL COMMENT '入职日期(用于新员工三个月资格判断)' AFTER `employment_status`");
ensureSnapshotIndex("idx_aihr_org_phone", "ALTER TABLE aihr_org_snapshot ADD KEY `idx_aihr_org_phone` (`tenant_id`, `person_phone`)"); ensureSnapshotIndex("idx_aihr_org_phone", "ALTER TABLE aihr_org_snapshot ADD KEY `idx_aihr_org_phone` (`tenant_id`, `person_phone`)");
} catch (DataAccessException e) { } catch (DataAccessException e) {
throw new IllegalStateException("组织人员快照表检查失败(处理错误已隐藏)", e); throw new IllegalStateException("组织人员快照表检查失败(处理错误已隐藏)", e);
@@ -705,6 +711,19 @@ public class AihrOrgSyncService {
return value == null ? "" : value.trim(); return value == null ? "" : value.trim();
} }
private static LocalDate parseDate(String value) {
String text = clean(value);
if (text.isBlank()) {
return null;
}
String dateText = text.length() >= 10 ? text.substring(0, 10) : text;
try {
return LocalDate.parse(dateText);
} catch (DateTimeParseException ignored) {
return null;
}
}
private static String truncate(String value, int maxChars) { private static String truncate(String value, int maxChars) {
String text = clean(value); String text = clean(value);
return text.length() <= maxChars ? text : text.substring(0, maxChars); return text.length() <= maxChars ? text : text.substring(0, maxChars);
@@ -726,13 +745,14 @@ public class AihrOrgSyncService {
String positionName, String positionName,
String positionLevel, String positionLevel,
String employmentStatus, String employmentStatus,
LocalDate hireDate,
LocalDate snapshotDate, LocalDate snapshotDate,
boolean suspectText boolean suspectText
) { ) {
Object[] args(String tenantId) { Object[] args(String tenantId) {
return new Object[] { return new Object[] {
tenantId, projectCode, projectName, deptName, extPartyId, personPhone, personName, tenantId, projectCode, projectName, deptName, extPartyId, personPhone, personName,
positionName, positionLevel, employmentStatus, snapshotDate positionName, positionLevel, employmentStatus, hireDate, snapshotDate
}; };
} }
} }
@@ -1858,7 +1858,11 @@ public class AihrPracticeSeedService {
if (orgSnapshotTableExists() && orgSnapshotColumnExists("hire_date")) { if (orgSnapshotTableExists() && orgSnapshotColumnExists("hire_date")) {
return findHireDate(extPartyId) return findHireDate(extPartyId)
.map(hireDate -> isWithinNewHireWindow(hireDate, LocalDate.now())) .map(hireDate -> isWithinNewHireWindow(hireDate, LocalDate.now()))
.orElse(false); .orElseGet(() -> isLegacyDailyDrillEligible(
isLegacyDailyDrillFallbackEnabled(allowLegacyDailyDrillFallback, activeProfiles),
extPartyId,
countMobileRecords(extPartyId)
));
} }
// Formal production data must provide hire_date. The record-count fallback is // Formal production data must provide hire_date. The record-count fallback is
// intentionally opt-in for the local Demo only; it must not infer employment // intentionally opt-in for the local Demo only; it must not infer employment
@@ -12,6 +12,7 @@ import org.springframework.transaction.support.TransactionTemplate;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -62,18 +63,20 @@ public class AihrOrgSyncServiceTest {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
AihrOrgSyncService service = new AihrOrgSyncService(new ObjectMapper(), mock(JdbcTemplate.class), mock(TransactionTemplate.class)); AihrOrgSyncService service = new AihrOrgSyncService(new ObjectMapper(), mock(JdbcTemplate.class), mock(TransactionTemplate.class));
var employee = mapper.readTree(""" var employee = mapper.readTree("""
{"id":"EMP-1","name":"测试员工","department_name":"项目管理","project_name":"翡翠湾","position_name":"主管","phone":"13900001111"} {"id":"EMP-1","name":"测试员工","department_name":"项目管理","project_name":"翡翠湾","position_name":"主管","phone":"13900001111","hire_date":"2026-07-01"}
"""); """);
ReflectionTestUtils.setField(service, "storeDisplayFields", false); ReflectionTestUtils.setField(service, "storeDisplayFields", false);
Object masked = ReflectionTestUtils.invokeMethod(service, "orgRow", employee, Map.of(), Map.of()); Object masked = ReflectionTestUtils.invokeMethod(service, "orgRow", employee, Map.of(), Map.of());
assertEquals("", ReflectionTestUtils.getField(masked, "personName")); assertEquals("", ReflectionTestUtils.getField(masked, "personName"));
assertEquals("", ReflectionTestUtils.getField(masked, "deptName")); assertEquals("", ReflectionTestUtils.getField(masked, "deptName"));
assertEquals(LocalDate.of(2026, 7, 1), ReflectionTestUtils.getField(masked, "hireDate"));
ReflectionTestUtils.setField(service, "storeDisplayFields", true); ReflectionTestUtils.setField(service, "storeDisplayFields", true);
Object demo = ReflectionTestUtils.invokeMethod(service, "orgRow", employee, Map.of(), Map.of()); Object demo = ReflectionTestUtils.invokeMethod(service, "orgRow", employee, Map.of(), Map.of());
assertEquals("测试员工", ReflectionTestUtils.getField(demo, "personName")); assertEquals("测试员工", ReflectionTestUtils.getField(demo, "personName"));
assertEquals("项目管理", ReflectionTestUtils.getField(demo, "deptName")); assertEquals("项目管理", ReflectionTestUtils.getField(demo, "deptName"));
assertEquals(LocalDate.of(2026, 7, 1), ReflectionTestUtils.getField(demo, "hireDate"));
} }
@Test @Test
@@ -16,6 +16,7 @@ CREATE TABLE `aihr_org_snapshot` (
`position_name` varchar(100) DEFAULT NULL COMMENT '岗位', `position_name` varchar(100) DEFAULT NULL COMMENT '岗位',
`position_level` varchar(30) DEFAULT NULL COMMENT '岗位层级(一线/主管/项目经理)', `position_level` varchar(30) DEFAULT NULL COMMENT '岗位层级(一线/主管/项目经理)',
`employment_status` varchar(20) DEFAULT 'active' COMMENT '在职状态', `employment_status` varchar(20) DEFAULT 'active' COMMENT '在职状态',
`hire_date` date DEFAULT NULL COMMENT '入职日期(用于新员工三个月资格判断)',
`snapshot_date` date DEFAULT NULL COMMENT '快照日期', `snapshot_date` date DEFAULT NULL COMMENT '快照日期',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
@@ -0,0 +1,17 @@
-- AIHR 组织快照入职日期迁移(MySQL 8.x)
-- 仅补齐结构,不写入业务数据;可重复执行。
SET @aihr_hire_date_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'aihr_org_snapshot'
AND COLUMN_NAME = 'hire_date'
);
SET @aihr_hire_date_ddl := IF(
@aihr_hire_date_exists = 0,
'ALTER TABLE aihr_org_snapshot ADD COLUMN `hire_date` date DEFAULT NULL COMMENT ''入职日期(用于新员工三个月资格判断)'' AFTER `employment_status`',
'SELECT 1'
);
PREPARE aihr_hire_date_stmt FROM @aihr_hire_date_ddl;
EXECUTE aihr_hire_date_stmt;
DEALLOCATE PREPARE aihr_hire_date_stmt;
+1
View File
@@ -291,3 +291,4 @@
- 2026-07-14 BRD 4.1 面试证据输入边界修复:`answer/finish` 此前接受不存在于服务端题目列表的 `questionId`,可能把伪造字段写入回答快照;现统一拒绝未知题目 ID,评分和持久化只接受本次服务端生成的题目集合。新增 answer/finish 回归测试和 `demo-check` marker,未修改业务数据。 - 2026-07-14 BRD 4.1 面试证据输入边界修复:`answer/finish` 此前接受不存在于服务端题目列表的 `questionId`,可能把伪造字段写入回答快照;现统一拒绝未知题目 ID,评分和持久化只接受本次服务端生成的题目集合。新增 answer/finish 回归测试和 `demo-check` marker,未修改业务数据。
- 2026-07-14 BRD 生产迁移资产补齐:`aihr_practice_audio`、`aihr_practice_calibration` 此前只有 reset SQL 和运行时懒建表,生产曾依赖人工建表;现新增可重复执行的 MySQL 8 版本化迁移脚本,仅补齐结构、不写入业务数据。仍需由运维在正式发布窗口执行并核对生产 schema,不能把脚本存在视为迁移已执行。 - 2026-07-14 BRD 生产迁移资产补齐:`aihr_practice_audio`、`aihr_practice_calibration` 此前只有 reset SQL 和运行时懒建表,生产曾依赖人工建表;现新增可重复执行的 MySQL 8 版本化迁移脚本,仅补齐结构、不写入业务数据。仍需由运维在正式发布窗口执行并核对生产 schema,不能把脚本存在视为迁移已执行。
- 2026-07-14 最新线上只读复核:生产根站、`/h5/` 和 `/prod-api/auth/tenant/list` 均返回 `200`;线上仍加载管理端 `assets/index-CJZ3Ax3Z.js`(SHA-256 `8b9278a26ccb760abce489b12a20b221748c64d4c4153613d2511777bb9677bc`)与 H5 `assets/index-D4-NrEpb.js`(SHA-256 `ab15bfd17cabe58e2f34b0ac61ee198a837128ed85dc6bb0ddf77bc3e22dcd9c`),本地最新提交 `e0da5f0a` 尚未发布;本轮未执行生产同步、后端重启或业务数据写入。 - 2026-07-14 最新线上只读复核:生产根站、`/h5/` 和 `/prod-api/auth/tenant/list` 均返回 `200`;线上仍加载管理端 `assets/index-CJZ3Ax3Z.js`(SHA-256 `8b9278a26ccb760abce489b12a20b221748c64d4c4153613d2511777bb9677bc`)与 H5 `assets/index-D4-NrEpb.js`(SHA-256 `ab15bfd17cabe58e2f34b0ac61ee198a837128ed85dc6bb0ddf77bc3e22dcd9c`),本地最新提交 `e0da5f0a` 尚未发布;本轮未执行生产同步、后端重启或业务数据写入。
- 2026-07-14 BRD 4.4 入职日期数据链路补齐:组织快照初始化 DDL、外部组织同步和旧库懒迁移现在统一保存 `hire_date`,每日三题在存在该字段但记录缺日期时仍只允许本地 `dev/local` 的显式训练次数兼容回退,生产不会用训练次数推断新员工;新增 MySQL 8 版本迁移与严格预检字段/身份门禁。未写入试点业务数据,正式环境仍需外部组织同步实际提供入职日期并执行迁移。
+17 -2
View File
@@ -81,7 +81,7 @@ check_pilot_samples() {
[[ "$(pilot_sql_scalar "SELECT DATE('$pilot_end') IS NOT NULL")" == "1" ]] || fail "invalid AIHR_PILOT_END_DATE: $pilot_end" [[ "$(pilot_sql_scalar "SELECT DATE('$pilot_end') IS NOT NULL")" == "1" ]] || fail "invalid AIHR_PILOT_END_DATE: $pilot_end"
[[ "$pilot_start" > "$pilot_end" ]] && fail "pilot start date must not be after end date" [[ "$pilot_start" > "$pilot_end" ]] && fail "pilot start date must not be after end date"
local scenario_count scenario_service_promotion_count scenario_daily_service_count completed_people pilot_people org_active org_phone_linked org_pilot_people org_pilot_projects org_completed_people audio_table_count prompt_version_schema_count sop_document_count case_stored_count case_unmasked_count local scenario_count scenario_service_promotion_count scenario_daily_service_count completed_people pilot_people org_active org_phone_linked org_hire_date_linked org_pilot_people org_pilot_projects org_completed_people audio_table_count prompt_version_schema_count hire_date_schema_count sop_document_count case_stored_count case_unmasked_count
local calibration_stats calibration_count calibration_matched sop_total sop_usable sop_pending satisfaction_count satisfaction_average local calibration_stats calibration_count calibration_matched sop_total sop_usable sop_pending satisfaction_count satisfaction_average
local org_identity_cte=" local org_identity_cte="
WITH active_org AS ( WITH active_org AS (
@@ -129,6 +129,7 @@ check_pilot_samples() {
scenario_daily_service_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_practice_scenario WHERE tenant_id = '$pilot_tenant' AND position = '生活顾问' AND enabled = 1 AND (scenario_type LIKE '%日常%' OR scenario_name LIKE '%日常%')")" scenario_daily_service_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_practice_scenario WHERE tenant_id = '$pilot_tenant' AND position = '生活顾问' AND enabled = 1 AND (scenario_type LIKE '%日常%' OR scenario_name LIKE '%日常%')")"
audio_table_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'aihr_practice_audio'")" audio_table_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'aihr_practice_audio'")"
prompt_version_schema_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'aihr_sop_answer_review' AND column_name = 'prompt_version'")" prompt_version_schema_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'aihr_sop_answer_review' AND column_name = 'prompt_version'")"
hire_date_schema_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'aihr_org_snapshot' AND column_name = 'hire_date'")"
sop_document_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_knowledge_attach WHERE tenant_id = '$pilot_tenant' AND type = 'sop' AND status = 2")" sop_document_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_knowledge_attach WHERE tenant_id = '$pilot_tenant' AND type = 'sop' AND status = 2")"
case_stored_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_case_record WHERE tenant_id = '$pilot_tenant' AND status = '已入库'")" case_stored_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_case_record WHERE tenant_id = '$pilot_tenant' AND status = '已入库'")"
case_unmasked_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_case_record WHERE tenant_id = '$pilot_tenant' AND status = '已入库' AND CONCAT_WS(' ', title, transcript, summary) REGEXP '1[3-9][0-9]{9}|[[:alnum:]._%+-]+@[[:alnum:].-]+\\.[[:alpha:]]{2,}|[0-9]{1,2}(栋|幢|号楼|单元)[0-9]{3,4}|[0-9]{1,2}[--][0-9]{3,4}'")" case_unmasked_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_case_record WHERE tenant_id = '$pilot_tenant' AND status = '已入库' AND CONCAT_WS(' ', title, transcript, summary) REGEXP '1[3-9][0-9]{9}|[[:alnum:]._%+-]+@[[:alnum:].-]+\\.[[:alpha:]]{2,}|[0-9]{1,2}(栋|幢|号楼|单元)[0-9]{3,4}|[0-9]{1,2}[--][0-9]{3,4}'")"
@@ -162,6 +163,15 @@ check_pilot_samples() {
org_phone_linked="$(pilot_sql_scalar "$org_identity_cte org_phone_linked="$(pilot_sql_scalar "$org_identity_cte
SELECT COUNT(DISTINCT ext_party_id) SELECT COUNT(DISTINCT ext_party_id)
FROM safe_org")" FROM safe_org")"
if (( hire_date_schema_count == 1 )); then
org_hire_date_linked="$(pilot_sql_scalar "$org_identity_cte
SELECT COUNT(DISTINCT s.ext_party_id)
FROM safe_org s
JOIN aihr_org_snapshot o ON o.tenant_id = '$pilot_tenant' AND o.ext_party_id = s.ext_party_id
WHERE o.employment_status = 'active' AND o.hire_date IS NOT NULL")"
else
org_hire_date_linked=0
fi
org_pilot_people="$(pilot_sql_scalar " org_pilot_people="$(pilot_sql_scalar "
$org_identity_cte, $org_identity_cte,
pilot_identity AS ( pilot_identity AS (
@@ -273,7 +283,7 @@ check_pilot_samples() {
AND s.finished_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY) AND s.finished_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)
) )
SELECT COALESCE(ROUND(AVG(satisfaction_score), 1), 0) FROM formal_sessions WHERE satisfaction_score IS NOT NULL")" SELECT COALESCE(ROUND(AVG(satisfaction_score), 1), 0) FROM formal_sessions WHERE satisfaction_score IS NOT NULL")"
echo "OK: pilot gates window=${pilot_start}..${pilot_end} scenarios=${scenario_count}/12 core_flow=service_promotion:${scenario_service_promotion_count} daily_service:${scenario_daily_service_count} sop_docs=${sop_document_count}/5 stored_cases=${case_stored_count}/20 case_unmasked=${case_unmasked_count} ten_sessions=${completed_people}/${pilot_people} org_identity=${org_phone_linked}/${org_active} org_pilot=${org_pilot_people}/20 projects=${org_pilot_projects}/1-2 org_ten_sessions=${org_completed_people}/${org_pilot_people} calibration=${calibration_count}/20 consistency=${calibration_matched}/${calibration_count} sop_usable=${sop_usable}/${sop_total} sop_pending=${sop_pending} satisfaction=${satisfaction_average}/5 responses=${satisfaction_count} audio_schema=${audio_table_count} prompt_version_schema=${prompt_version_schema_count}" echo "OK: pilot gates window=${pilot_start}..${pilot_end} scenarios=${scenario_count}/12 core_flow=service_promotion:${scenario_service_promotion_count} daily_service:${scenario_daily_service_count} sop_docs=${sop_document_count}/5 stored_cases=${case_stored_count}/20 case_unmasked=${case_unmasked_count} ten_sessions=${completed_people}/${pilot_people} org_identity=${org_phone_linked}/${org_active} org_hire_date=${org_hire_date_linked}/${org_active} org_pilot=${org_pilot_people}/20 projects=${org_pilot_projects}/1-2 org_ten_sessions=${org_completed_people}/${org_pilot_people} calibration=${calibration_count}/20 consistency=${calibration_matched}/${calibration_count} sop_usable=${sop_usable}/${sop_total} sop_pending=${sop_pending} satisfaction=${satisfaction_average}/5 responses=${satisfaction_count} audio_schema=${audio_table_count} prompt_version_schema=${prompt_version_schema_count} hire_date_schema=${hire_date_schema_count}"
[[ "${AIHR_PILOT_STRICT:-false}" != "true" ]] || { [[ "${AIHR_PILOT_STRICT:-false}" != "true" ]] || {
(( scenario_count >= 12 )) || fail "pilot enabled scenarios ${scenario_count}/12" (( scenario_count >= 12 )) || fail "pilot enabled scenarios ${scenario_count}/12"
@@ -284,7 +294,9 @@ check_pilot_samples() {
(( case_unmasked_count == 0 )) || fail "pilot stored cases contain unmasked known PII (${case_unmasked_count})" (( case_unmasked_count == 0 )) || fail "pilot stored cases contain unmasked known PII (${case_unmasked_count})"
(( audio_table_count == 1 )) || fail "pilot audio schema unavailable (aihr_practice_audio missing)" (( audio_table_count == 1 )) || fail "pilot audio schema unavailable (aihr_practice_audio missing)"
(( prompt_version_schema_count == 1 )) || fail "pilot RAG traceability schema unavailable (aihr_sop_answer_review.prompt_version missing)" (( prompt_version_schema_count == 1 )) || fail "pilot RAG traceability schema unavailable (aihr_sop_answer_review.prompt_version missing)"
(( hire_date_schema_count == 1 )) || fail "pilot org hire_date schema unavailable"
(( org_phone_linked >= 20 )) || fail "pilot mobile-login org members ${org_phone_linked}/20 (active org ${org_active})" (( org_phone_linked >= 20 )) || fail "pilot mobile-login org members ${org_phone_linked}/20 (active org ${org_active})"
(( org_hire_date_linked >= 20 )) || fail "pilot org hire_date identities ${org_hire_date_linked}/20"
(( org_pilot_people >= 20 )) || fail "pilot activity matched to active org ${org_pilot_people}/20" (( org_pilot_people >= 20 )) || fail "pilot activity matched to active org ${org_pilot_people}/20"
(( org_pilot_projects >= 1 && org_pilot_projects <= 2 )) || fail "pilot project scope must be 1-2 projects (actual ${org_pilot_projects})" (( org_pilot_projects >= 1 && org_pilot_projects <= 2 )) || fail "pilot project scope must be 1-2 projects (actual ${org_pilot_projects})"
(( org_completed_people * 100 >= org_pilot_people * 80 )) || fail "pilot org members with at least 10 completed sessions below 80% (${org_completed_people}/${org_pilot_people})" (( org_completed_people * 100 >= org_pilot_people * 80 )) || fail "pilot org members with at least 10 completed sessions below 80% (${org_completed_people}/${org_pilot_people})"
@@ -497,6 +509,9 @@ contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service
contains backend/script/sql/aihr_practice_mysql8.sql "aihr_practice_calibration" contains backend/script/sql/aihr_practice_mysql8.sql "aihr_practice_calibration"
contains backend/script/sql/update/aihr_20260714_practice_evidence_mysql8.sql "aihr_practice_audio" contains backend/script/sql/update/aihr_20260714_practice_evidence_mysql8.sql "aihr_practice_audio"
contains backend/script/sql/update/aihr_20260714_practice_evidence_mysql8.sql "aihr_practice_calibration" contains backend/script/sql/update/aihr_20260714_practice_evidence_mysql8.sql "aihr_practice_calibration"
contains backend/script/sql/aihr_org_snapshot_mysql8.sql "hire_date"
contains backend/script/sql/update/aihr_20260714_org_hire_date_mysql8.sql "hire_date"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java "hireDate"
contains frontend/src/api/aihr/practice.ts '/api/train/practice/scenarios/${id}/enabled' contains frontend/src/api/aihr/practice.ts '/api/train/practice/scenarios/${id}/enabled'
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPracticeController.java "/scenarios/{id}/enabled" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPracticeController.java "/scenarios/{id}/enabled"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrModelController.java "@SaCheckRole" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrModelController.java "@SaCheckRole"
+7 -7
View File
@@ -65,7 +65,7 @@ CREATE TABLE aihr_practice_session (tenant_id varchar(20), session_id varchar(10
CREATE TABLE aihr_practice_assignment (tenant_id varchar(20), ext_party_id varchar(100), create_time datetime); CREATE TABLE aihr_practice_assignment (tenant_id varchar(20), ext_party_id varchar(100), create_time datetime);
CREATE TABLE aihr_org_snapshot ( CREATE TABLE aihr_org_snapshot (
tenant_id varchar(20), employment_status varchar(20), ext_party_id varchar(100), tenant_id varchar(20), employment_status varchar(20), ext_party_id varchar(100),
project_code varchar(50), person_phone varchar(30) project_code varchar(50), person_phone varchar(30), hire_date date
); );
CREATE TABLE aihr_practice_calibration ( CREATE TABLE aihr_practice_calibration (
id bigint auto_increment primary key, tenant_id varchar(20), session_id varchar(100), id bigint auto_increment primary key, tenant_id varchar(20), session_id varchar(100),
@@ -104,12 +104,12 @@ INSERT INTO aihr_case_record VALUES
('000000','已转写','pending','',''), ('000000','已转写','pending','',''),
('other','已入库','other','',''); ('other','已入库','other','','');
INSERT INTO aihr_org_snapshot VALUES INSERT INTO aihr_org_snapshot VALUES
('000000','active','A','P1','13900000001'), ('000000','active','A','P1','13900000001','2026-01-01'),
('000000','active','B','P1','13900000001'), ('000000','active','B','P1','13900000001','2026-01-01'),
('000000','active','C','P2','D'), ('000000','active','C','P2','D','2026-01-01'),
('000000','active','D','P2','13900000004'), ('000000','active','D','P2','13900000004','2026-01-01'),
('000000','active','E','P3','13900000005'), ('000000','active','E','P3','13900000005','2026-01-01'),
('000000','active','F','P3','13900000006'); ('000000','active','F','P3','13900000006','2026-01-01');
INSERT INTO aihr_practice_assignment VALUES INSERT INTO aihr_practice_assignment VALUES
('000000','D','2026-07-08 08:00:00'), ('000000','D','2026-07-08 08:00:00'),
('000000','13900000001','2026-07-08 08:00:00'), ('000000','13900000001','2026-07-08 08:00:00'),