diff --git a/docs/BRD_IMPLEMENTATION_AUDIT.md b/docs/BRD_IMPLEMENTATION_AUDIT.md index 8a16b0d5..69ef5770 100644 --- a/docs/BRD_IMPLEMENTATION_AUDIT.md +++ b/docs/BRD_IMPLEMENTATION_AUDIT.md @@ -76,4 +76,5 @@ - 生产浏览器回归:管理员可登录首页和 `/system/model`;页面可见“提示词模板”列表、新增模板入口和启停控件,说明本轮管理端 UI 已加载。当前验证使用生产 admin 测试账号和 Redis 验证码,不在文档记录账号或个人数据。 - 2026-07-14 04:05 后续发布:移动端满意度采集、CSV 指标和后端接口已同步到生产;备份为 `/opt/wygj/backups/release-20260714040515`,H5 主 JS `index-5mNjsnek.js` 本地与线上 SHA-256 均为 `5c5af11be7b2b3ef7ceafd5b820fdd3e1326fcfc612b89c69b00dfe06635573c`,后端 jar SHA-256 为 `aa0bd917a2ab94ffd740ac709af2c59dd5bed6c8db995fa9aa1e80bcc4662c07`;生产浏览器实际完成训练、提交 1–5 分满意度并收到成功提示,随后已清理临时训练和派发数据。 - 2026-07-14 04:18 后续发布:SOP 反馈追溯已同步到生产;备份为 `/opt/wygj/backups/release-20260714041836`,H5 主 JS `index-Dh7w59t7.js` 本地与线上 SHA-256 均为 `0fce49deff7f95b3816cd97cf7b5f1733f31b2815162d141c27be9d1257a1f58`,后端 jar SHA-256 为 `18f4643f401aafeab3ee896d7d6341733f31b2815162d141c27be9d1257a1f58`;生产 API smoke 已验证搜索 `reviewId`、反馈写入 `review_id`,并清理 smoke 记录。 +- 2026-07-14 后续审查:移动端 `/me` 在同一手机号对应多条 active 组织记录时优先识别“主管/项目经理”,避免按首条一线记录误判员工端;正式试点满意度统计改为复用安全 `org_identity`,冲突或无法唯一映射的组织身份不计入满意度分母;脚本行为测试已覆盖该边界,但这仍属于数据口径加固,不替代正式组织同步和试点证据。 - 本次线上核对发现 schema 漂移:生产库已存在 `aihr_practice_session.satisfaction_score`、`satisfaction_comment`,但尚未显式创建 `aihr_practice_audio`。当前代码仍有运行时懒建表兜底,满意度链路不受影响;正式音频留痕试点前必须执行并核验显式迁移,不能依赖首次请求触发;严格预检已增加该表存在性检查。 diff --git a/scripts/demo-check.sh b/scripts/demo-check.sh index 6c84404a..3bb3ffbb 100755 --- a/scripts/demo-check.sh +++ b/scripts/demo-check.sh @@ -243,8 +243,28 @@ check_pilot_samples() { sop_total="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_sop_answer_review WHERE tenant_id = '000000' AND status = '已评审' AND reviewed_time >= '${pilot_start} 00:00:00' AND reviewed_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)")" sop_usable="$(pilot_sql_scalar "SELECT COALESCE(SUM(CASE WHEN usable = 1 THEN 1 ELSE 0 END), 0) FROM aihr_sop_answer_review WHERE tenant_id = '000000' AND status = '已评审' AND reviewed_time >= '${pilot_start} 00:00:00' AND reviewed_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)")" sop_pending="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_sop_answer_review WHERE tenant_id = '000000' AND status <> '已评审' AND create_time >= '${pilot_start} 00:00:00' AND create_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)")" - satisfaction_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_practice_session s WHERE s.tenant_id = '000000' AND s.mode = 'mobile' AND s.finished_time >= '${pilot_start} 00:00:00' AND s.finished_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY) AND s.satisfaction_score IS NOT NULL AND EXISTS (SELECT 1 FROM aihr_org_snapshot o WHERE o.tenant_id = s.tenant_id AND o.employment_status = 'active' AND (o.ext_party_id = s.ext_party_id OR o.person_phone = s.ext_party_id))")" - satisfaction_average="$(pilot_sql_scalar "SELECT COALESCE(AVG(s.satisfaction_score), 0) FROM aihr_practice_session s WHERE s.tenant_id = '000000' AND s.mode = 'mobile' AND s.finished_time >= '${pilot_start} 00:00:00' AND s.finished_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY) AND s.satisfaction_score IS NOT NULL AND EXISTS (SELECT 1 FROM aihr_org_snapshot o WHERE o.tenant_id = s.tenant_id AND o.employment_status = 'active' AND (o.ext_party_id = s.ext_party_id OR o.person_phone = s.ext_party_id))")" + satisfaction_count="$(pilot_sql_scalar " + $org_identity_cte, + formal_sessions AS ( + SELECT s.satisfaction_score + FROM aihr_practice_session s + JOIN org_identity o ON o.identity_key = s.ext_party_id + WHERE s.tenant_id = '000000' AND s.mode = 'mobile' + AND s.finished_time >= '${pilot_start} 00:00:00' + AND s.finished_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY) + ) + SELECT COUNT(*) FROM formal_sessions WHERE satisfaction_score IS NOT NULL")" + satisfaction_average="$(pilot_sql_scalar " + $org_identity_cte, + formal_sessions AS ( + SELECT s.satisfaction_score + FROM aihr_practice_session s + JOIN org_identity o ON o.identity_key = s.ext_party_id + WHERE s.tenant_id = '000000' AND s.mode = 'mobile' + AND s.finished_time >= '${pilot_start} 00:00:00' + 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")" echo "OK: pilot gates window=${pilot_start}..${pilot_end} scenarios=${scenario_count}/12 ten_sessions=${completed_people}/${pilot_people} org_login=${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}" [[ "${AIHR_PILOT_STRICT:-false}" != "true" ]] || { diff --git a/scripts/tests/demo-check-pilot-gates.test.sh b/scripts/tests/demo-check-pilot-gates.test.sh index 16196641..d4a6b1ed 100644 --- a/scripts/tests/demo-check-pilot-gates.test.sh +++ b/scripts/tests/demo-check-pilot-gates.test.sh @@ -56,7 +56,7 @@ docker exec -i wygj-mysql mysql -uroot -proot --default-character-set=utf8mb4 << CREATE DATABASE \`$TEST_DB\` CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; USE \`$TEST_DB\`; CREATE TABLE aihr_practice_scenario (tenant_id varchar(20), position varchar(80), enabled tinyint); -CREATE TABLE aihr_practice_session (tenant_id varchar(20), session_id varchar(100), ext_party_id varchar(100), mode varchar(30), finished_time datetime); +CREATE TABLE aihr_practice_session (tenant_id varchar(20), session_id varchar(100), ext_party_id varchar(100), mode varchar(30), finished_time datetime, satisfaction_score tinyint); CREATE TABLE aihr_practice_assignment (tenant_id varchar(20), ext_party_id varchar(100), create_time datetime); CREATE TABLE aihr_org_snapshot ( tenant_id varchar(20), employment_status varchar(20), ext_party_id varchar(100), @@ -89,20 +89,22 @@ INSERT INTO aihr_practice_assignment VALUES ('000000','E','2026-07-08 08:00:00'), ('000000','13900000005','2026-07-08 08:00:00'), ('000000','F','2026-07-08 08:00:00'); -INSERT INTO aihr_practice_session +INSERT INTO aihr_practice_session (tenant_id, session_id, ext_party_id, mode, finished_time) SELECT '000000', CONCAT('e-', n), CASE WHEN n <= 5 THEN 'E' ELSE '13900000005' END, 'mobile', '2026-07-08 09:00:00' FROM ( SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 ) n; -INSERT INTO aihr_practice_session +INSERT INTO aihr_practice_session (tenant_id, session_id, ext_party_id, mode, finished_time) SELECT '000000', CONCAT('f-', n), 'F','mobile','2026-07-08 10:00:00' FROM ( SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 ) n; -INSERT INTO aihr_practice_session VALUES +INSERT INTO aihr_practice_session (tenant_id, session_id, ext_party_id, mode, finished_time) +VALUES ('000000','e-out','E','mobile','2026-07-06 10:00:00'), ('000000','unsafe-inside','D','mobile','2026-07-08 10:00:00'); +UPDATE aihr_practice_session SET satisfaction_score = 5 WHERE finished_time >= '2026-07-07 00:00:00'; INSERT INTO aihr_practice_calibration (tenant_id,session_id,original_score,corrected_score,create_time) VALUES ('000000','f-1',80,82,'2026-07-08 11:00:00'), ('000000','unsafe-inside',80,40,'2026-07-08 11:00:00'), @@ -123,10 +125,12 @@ pilot_output="$(AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10 [[ "$pilot_output" == *'org_ten_sessions=1/2'* ]] || { echo "FAIL: formal 9/10 boundary: $pilot_output" >&2; exit 1; } [[ "$pilot_output" == *'calibration=1/20 consistency=1/1'* ]] || { echo "FAIL: calibration window: $pilot_output" >&2; exit 1; } [[ "$pilot_output" == *'sop_usable=1/1 sop_pending=1'* ]] || { echo "FAIL: SOP window: $pilot_output" >&2; exit 1; } +[[ "$pilot_output" == *'satisfaction=5.0/5 responses=19'* ]] || { echo "FAIL: safe satisfaction identity scope: $pilot_output" >&2; exit 1; } -pilot_sql_scalar "INSERT INTO aihr_practice_session VALUES ('000000','e-10','13900000005','mobile','2026-07-09 09:00:00')" >/dev/null +pilot_sql_scalar "INSERT INTO aihr_practice_session (tenant_id, session_id, ext_party_id, mode, finished_time, satisfaction_score) VALUES ('000000','e-10','13900000005','mobile','2026-07-09 09:00:00',5)" >/dev/null pilot_output="$(AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10 check_pilot_samples)" [[ "$pilot_output" == *'org_ten_sessions=2/2'* ]] || { echo "FAIL: formal 10/10 boundary: $pilot_output" >&2; exit 1; } +[[ "$pilot_output" == *'satisfaction=5.0/5 responses=20'* ]] || { echo "FAIL: formal satisfaction scope after window update: $pilot_output" >&2; exit 1; } if (AIHR_PILOT_STRICT=true AIHR_PILOT_START_DATE= AIHR_PILOT_END_DATE= check_pilot_samples) >/tmp/wygj-pilot-strict-test.log 2>&1; then echo "FAIL: strict mode accepted an implicit date window" >&2