feat(aihr): complete phase two pilot readiness
This commit is contained in:
+183
-19
@@ -47,7 +47,9 @@ check_mobile_h5_shell() {
|
||||
}
|
||||
|
||||
pilot_sql_scalar() {
|
||||
docker exec wygj-mysql mysql -uroot -proot --default-character-set=utf8mb4 -N -B -D ry-vue -e "$1" 2>/dev/null
|
||||
local database="${AIHR_PILOT_DB:-ry-vue}"
|
||||
[[ "$database" =~ ^[A-Za-z0-9_-]+$ ]] || fail "invalid AIHR_PILOT_DB: $database"
|
||||
docker exec wygj-mysql mysql -uroot -proot --default-character-set=utf8mb4 -N -B -D "$database" -e "$1" 2>/dev/null
|
||||
}
|
||||
|
||||
check_pilot_samples() {
|
||||
@@ -57,46 +59,196 @@ check_pilot_samples() {
|
||||
return
|
||||
fi
|
||||
|
||||
local scenario_count completed_people pilot_people calibration_stats calibration_count calibration_matched sop_total sop_usable sop_pending
|
||||
if ! pilot_sql_scalar "SELECT 1" >/dev/null; then
|
||||
[[ "${AIHR_PILOT_STRICT:-false}" != "true" ]] || fail "pilot DB sample counts unavailable (database connection failed)"
|
||||
echo "SKIP: pilot DB sample counts (database connection failed)"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "${AIHR_PILOT_STRICT:-false}" == "true" ]] && {
|
||||
[[ -z "${AIHR_PILOT_START_DATE:-}" ]] || [[ -z "${AIHR_PILOT_END_DATE:-}" ]]
|
||||
}; then
|
||||
fail "strict pilot check requires AIHR_PILOT_START_DATE and AIHR_PILOT_END_DATE"
|
||||
fi
|
||||
|
||||
local pilot_start="${AIHR_PILOT_START_DATE:-$(date +%F)}"
|
||||
local pilot_end="${AIHR_PILOT_END_DATE:-$pilot_start}"
|
||||
[[ "$pilot_start" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || fail "invalid AIHR_PILOT_START_DATE: $pilot_start"
|
||||
[[ "$pilot_end" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || fail "invalid AIHR_PILOT_END_DATE: $pilot_end"
|
||||
[[ "$(pilot_sql_scalar "SELECT DATE('$pilot_start') IS NOT NULL")" == "1" ]] || fail "invalid AIHR_PILOT_START_DATE: $pilot_start"
|
||||
[[ "$(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"
|
||||
|
||||
local scenario_count completed_people pilot_people org_active org_phone_linked org_pilot_people org_pilot_projects org_completed_people
|
||||
local calibration_stats calibration_count calibration_matched sop_total sop_usable sop_pending
|
||||
local org_identity_cte="
|
||||
WITH active_org AS (
|
||||
SELECT DISTINCT ext_party_id, project_code, person_phone
|
||||
FROM aihr_org_snapshot
|
||||
WHERE tenant_id = '000000' AND employment_status = 'active'
|
||||
AND person_phone IS NOT NULL AND person_phone <> ''
|
||||
), all_org_keys AS (
|
||||
SELECT ext_party_id AS identity_key, ext_party_id
|
||||
FROM active_org
|
||||
WHERE ext_party_id IS NOT NULL AND ext_party_id <> ''
|
||||
UNION ALL
|
||||
SELECT person_phone AS identity_key, ext_party_id
|
||||
FROM active_org
|
||||
), conflict_keys AS (
|
||||
SELECT identity_key
|
||||
FROM all_org_keys
|
||||
GROUP BY identity_key
|
||||
HAVING COUNT(DISTINCT ext_party_id) > 1
|
||||
), unsafe_org AS (
|
||||
SELECT DISTINCT k.ext_party_id
|
||||
FROM all_org_keys k
|
||||
JOIN conflict_keys c ON c.identity_key = k.identity_key
|
||||
), safe_org AS (
|
||||
SELECT o.ext_party_id, o.project_code, o.person_phone
|
||||
FROM active_org o
|
||||
LEFT JOIN unsafe_org u ON u.ext_party_id = o.ext_party_id
|
||||
WHERE u.ext_party_id IS NULL
|
||||
), org_key_candidates AS (
|
||||
SELECT ext_party_id AS identity_key, ext_party_id
|
||||
FROM safe_org
|
||||
WHERE ext_party_id IS NOT NULL AND ext_party_id <> ''
|
||||
UNION ALL
|
||||
SELECT person_phone AS identity_key, ext_party_id
|
||||
FROM safe_org
|
||||
), org_identity AS (
|
||||
SELECT identity_key, MIN(ext_party_id) AS ext_party_id
|
||||
FROM org_key_candidates
|
||||
GROUP BY identity_key
|
||||
HAVING COUNT(DISTINCT ext_party_id) = 1
|
||||
)"
|
||||
scenario_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_practice_scenario WHERE tenant_id = '000000' AND position = '生活顾问' AND enabled = 1")"
|
||||
completed_people="$(pilot_sql_scalar "SELECT COUNT(*) FROM (SELECT DISTINCT ext_party_id FROM aihr_practice_session WHERE tenant_id = '000000' AND mode = 'mobile') completed_people")"
|
||||
completed_people="$(pilot_sql_scalar "
|
||||
SELECT COUNT(*)
|
||||
FROM (
|
||||
SELECT ext_party_id
|
||||
FROM aihr_practice_session
|
||||
WHERE tenant_id = '000000' AND mode = 'mobile'
|
||||
AND finished_time >= '${pilot_start} 00:00:00'
|
||||
AND finished_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)
|
||||
GROUP BY ext_party_id
|
||||
HAVING COUNT(*) >= 10
|
||||
) completed_people")"
|
||||
pilot_people="$(pilot_sql_scalar "
|
||||
SELECT COUNT(*)
|
||||
FROM (
|
||||
SELECT ext_party_id
|
||||
FROM aihr_practice_session
|
||||
WHERE tenant_id = '000000' AND mode = 'mobile'
|
||||
AND finished_time >= '${pilot_start} 00:00:00'
|
||||
AND finished_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)
|
||||
UNION
|
||||
SELECT ext_party_id
|
||||
FROM aihr_practice_assignment
|
||||
WHERE tenant_id = '000000'
|
||||
AND status = '待训练'
|
||||
AND (source <> 'daily' OR DATE(create_time) = CURRENT_DATE())
|
||||
AND create_time >= '${pilot_start} 00:00:00'
|
||||
AND create_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)
|
||||
) pilot_people")"
|
||||
org_active="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_org_snapshot WHERE tenant_id = '000000' AND employment_status = 'active'")"
|
||||
org_phone_linked="$(pilot_sql_scalar "$org_identity_cte
|
||||
SELECT COUNT(DISTINCT ext_party_id)
|
||||
FROM safe_org")"
|
||||
org_pilot_people="$(pilot_sql_scalar "
|
||||
$org_identity_cte,
|
||||
pilot_identity AS (
|
||||
SELECT ext_party_id identity_key
|
||||
FROM aihr_practice_session
|
||||
WHERE tenant_id = '000000' AND mode = 'mobile'
|
||||
AND finished_time >= '${pilot_start} 00:00:00'
|
||||
AND finished_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)
|
||||
UNION
|
||||
SELECT ext_party_id identity_key
|
||||
FROM aihr_practice_assignment
|
||||
WHERE tenant_id = '000000'
|
||||
AND create_time >= '${pilot_start} 00:00:00'
|
||||
AND create_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)
|
||||
)
|
||||
SELECT COUNT(DISTINCT o.ext_party_id)
|
||||
FROM pilot_identity p
|
||||
JOIN org_identity o ON o.identity_key = p.identity_key")"
|
||||
org_pilot_projects="$(pilot_sql_scalar "
|
||||
$org_identity_cte,
|
||||
pilot_identity AS (
|
||||
SELECT ext_party_id identity_key
|
||||
FROM aihr_practice_session
|
||||
WHERE tenant_id = '000000' AND mode = 'mobile'
|
||||
AND finished_time >= '${pilot_start} 00:00:00'
|
||||
AND finished_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)
|
||||
UNION
|
||||
SELECT ext_party_id identity_key
|
||||
FROM aihr_practice_assignment
|
||||
WHERE tenant_id = '000000'
|
||||
AND create_time >= '${pilot_start} 00:00:00'
|
||||
AND create_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)
|
||||
), pilot_org AS (
|
||||
SELECT DISTINCT o.ext_party_id
|
||||
FROM pilot_identity p
|
||||
JOIN org_identity o ON o.identity_key = p.identity_key
|
||||
)
|
||||
SELECT COUNT(DISTINCT o.project_code)
|
||||
FROM pilot_org p
|
||||
JOIN safe_org o ON o.ext_party_id = p.ext_party_id
|
||||
WHERE o.project_code IS NOT NULL AND o.project_code <> ''")"
|
||||
org_completed_people="$(pilot_sql_scalar "
|
||||
$org_identity_cte,
|
||||
session_identity AS (
|
||||
SELECT ext_party_id AS identity_key, COUNT(*) AS session_count
|
||||
FROM aihr_practice_session
|
||||
WHERE tenant_id = '000000' AND mode = 'mobile'
|
||||
AND finished_time >= '${pilot_start} 00:00:00'
|
||||
AND finished_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)
|
||||
GROUP BY ext_party_id
|
||||
), completed_org AS (
|
||||
SELECT o.ext_party_id
|
||||
FROM session_identity s
|
||||
JOIN org_identity o ON o.identity_key = s.identity_key
|
||||
GROUP BY o.ext_party_id
|
||||
HAVING SUM(s.session_count) >= 10
|
||||
)
|
||||
SELECT COUNT(*)
|
||||
FROM completed_org")"
|
||||
calibration_stats="$(pilot_sql_scalar "
|
||||
$org_identity_cte,
|
||||
formal_sessions AS (
|
||||
SELECT s.session_id
|
||||
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)
|
||||
), latest_calibration AS (
|
||||
SELECT c.session_id, MAX(c.id) AS id
|
||||
FROM aihr_practice_calibration c
|
||||
JOIN formal_sessions s ON s.session_id = c.session_id
|
||||
WHERE c.tenant_id = '000000' AND c.original_score IS NOT NULL AND c.corrected_score IS NOT NULL
|
||||
AND c.create_time >= '${pilot_start} 00:00:00'
|
||||
AND c.create_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)
|
||||
GROUP BY c.session_id
|
||||
)
|
||||
SELECT COUNT(*) AS total,
|
||||
COALESCE(SUM(CASE
|
||||
WHEN (CASE WHEN c.original_score < 60 THEN 0 WHEN c.original_score < 75 THEN 1 WHEN c.original_score < 90 THEN 2 ELSE 3 END)
|
||||
= (CASE WHEN c.corrected_score < 60 THEN 0 WHEN c.corrected_score < 75 THEN 1 WHEN c.corrected_score < 90 THEN 2 ELSE 3 END)
|
||||
THEN 1 ELSE 0 END), 0) AS matched
|
||||
FROM aihr_practice_calibration c
|
||||
JOIN (
|
||||
SELECT session_id, MAX(id) AS id
|
||||
FROM aihr_practice_calibration
|
||||
WHERE tenant_id = '000000' AND original_score IS NOT NULL AND corrected_score IS NOT NULL
|
||||
GROUP BY session_id
|
||||
) latest ON latest.id = c.id
|
||||
JOIN latest_calibration latest ON latest.id = c.id
|
||||
WHERE c.tenant_id = '000000'")"
|
||||
read -r calibration_count calibration_matched <<<"$calibration_stats"
|
||||
sop_total="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_sop_answer_review WHERE tenant_id = '000000' AND status = '已评审'")"
|
||||
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 = '已评审'")"
|
||||
sop_pending="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_sop_answer_review WHERE tenant_id = '000000' AND status <> '已评审'")"
|
||||
echo "OK: pilot gates scenarios=${scenario_count}/12 completion=${completed_people}/${pilot_people} calibration=${calibration_count}/20 consistency=${calibration_matched}/${calibration_count} sop_usable=${sop_usable}/${sop_total} sop_pending=${sop_pending}"
|
||||
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)")"
|
||||
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}"
|
||||
|
||||
[[ "${AIHR_PILOT_STRICT:-false}" != "true" ]] || {
|
||||
(( scenario_count >= 12 )) || fail "pilot enabled scenarios ${scenario_count}/12"
|
||||
(( pilot_people > 0 )) || fail "pilot completion samples missing"
|
||||
(( completed_people * 100 >= pilot_people * 80 )) || fail "pilot completion rate below 80% (${completed_people}/${pilot_people})"
|
||||
(( org_phone_linked >= 20 )) || fail "pilot mobile-login org members ${org_phone_linked}/20 (active org ${org_active})"
|
||||
(( 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_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})"
|
||||
(( calibration_count >= 20 )) || fail "pilot calibration samples ${calibration_count}/20"
|
||||
(( calibration_matched * 100 >= calibration_count * 70 )) || fail "pilot AI/human band consistency below 70%"
|
||||
(( sop_total > 0 )) || fail "pilot SOP review samples missing"
|
||||
@@ -104,6 +256,10 @@ check_pilot_samples() {
|
||||
}
|
||||
}
|
||||
|
||||
if [[ "${AIHR_DEMO_CHECK_LIBRARY_ONLY:-false}" == "true" ]]; then
|
||||
return 0 2>/dev/null || exit 0
|
||||
fi
|
||||
|
||||
need curl
|
||||
|
||||
check_url "frontend" "$FRONTEND_URL/"
|
||||
@@ -132,6 +288,10 @@ contains mobile-uni/src/pages.json "pages/user/today/index"
|
||||
contains mobile-uni/src/pages.json "pages/user/learning/index"
|
||||
contains mobile-uni/src/pages.json "pages/supervisor/index/index"
|
||||
contains mobile-uni/src/pages.json "pages/candidate/index/index"
|
||||
contains mobile-uni/src/pages.json "pages/candidate/interview/index"
|
||||
contains mobile-uni/src/pages.json "pages/candidate/materials/index"
|
||||
contains mobile-uni/src/pages.json "pages/candidate/progress/index"
|
||||
contains mobile-uni/src/pages.json "pages/candidate/study/index"
|
||||
contains mobile-uni/src/pages/auth/login/index.vue "/resource/sms/code"
|
||||
contains mobile-uni/src/pages/auth/login/index.vue "/auth/mobile/sms-login"
|
||||
contains mobile-uni/src/services/home.ts "/api/aihr/mobile/home/"
|
||||
@@ -145,6 +305,10 @@ contains mobile-uni/src/services/knowledge.ts "/api/knowledge/summary-card"
|
||||
contains mobile-uni/src/services/case.ts "/api/knowledge/case/upload"
|
||||
contains mobile-uni/src/services/candidate.ts "/api/recruit/interview/start"
|
||||
contains mobile-uni/src/services/candidate.ts "/api/aihr/mobile/candidate/materials"
|
||||
contains mobile-uni/src/pages/candidate/interview/index.vue "startCandidateInterview"
|
||||
contains mobile-uni/src/pages/candidate/materials/index.vue "uploadCandidateMaterial"
|
||||
contains mobile-uni/src/pages/candidate/progress/index.vue "buildCandidateFlow"
|
||||
contains mobile-uni/src/pages/candidate/study/index.vue "searchKnowledge"
|
||||
contains mobile-uni/src/pages/user/learning/index.vue "岗位 SOP"
|
||||
contains mobile-uni/src/pages/user/learning/index.vue "getPracticeAssignments"
|
||||
contains mobile-uni/src/pages/user/learning/index.vue "searchKnowledge"
|
||||
@@ -186,9 +350,9 @@ contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service
|
||||
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSpeechService.java "Duration.ofSeconds(5)"
|
||||
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrMobileSeedService.java "待复盘对练"
|
||||
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "/practice/reviews"
|
||||
contains mobile-uni/src/pages/supervisor/index/index.vue "人工贡献分"
|
||||
contains mobile-uni/src/pages/supervisor/review/index.vue "人工贡献分"
|
||||
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "/practice/reviews/{id}/reviewed"
|
||||
contains mobile-uni/src/pages/supervisor/index/index.vue "标记已复盘"
|
||||
contains mobile-uni/src/pages/supervisor/review/index.vue "标记已复盘"
|
||||
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "source = 'retry'"
|
||||
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "maskSensitiveText"
|
||||
contains mobile-uni/src/services/practice.ts "/api/aihr/mobile/practice/assignments"
|
||||
|
||||
Reference in New Issue
Block a user