Files
prop-ai-hr/scripts/demo-check.sh
T

550 lines
38 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
FRONTEND_URL="${FRONTEND_URL:-https://wygj-admin.localhost}"
MOBILE_URL="${MOBILE_URL:-https://wygj-mobile-uni.localhost}"
cd "$ROOT_DIR"
fail() {
echo "FAIL: $*" >&2
exit 1
}
need() {
command -v "$1" >/dev/null || fail "missing command: $1"
}
contains() {
local file="$1"
local text="$2"
grep -Fq "$text" "$file" || fail "$file missing marker: $text"
}
check_url() {
local name="$1"
local url="$2"
local code
code="$(curl -k -sS -o /tmp/wygj-demo-check.body -w '%{http_code}' "$url")"
[[ "$code" == "200" ]] || fail "$name returned HTTP $code"
echo "OK: $name"
}
check_mobile_h5_shell() {
local html href icon_url code
html="$(curl -k -fsS "$MOBILE_URL/h5/")"
[[ "$html" != *"tongji-collector"* && "$html" != *"WebTrack.gif"* && "$html" != *"uni-stat"* ]] || fail "mobile H5 includes uni statistics runtime"
[[ "$html" != *"/h5/h5/"* ]] || fail "mobile H5 has duplicated /h5 base path"
href="$(printf '%s' "$html" | sed -nE 's/.*<link rel="icon" href="([^"]+)".*/\1/p' | head -n 1)"
[[ -n "$href" ]] || fail "mobile H5 missing favicon link"
[[ "$href" == /* ]] || fail "mobile H5 favicon must use an absolute path"
icon_url="${MOBILE_URL%/}${href}"
code="$(curl -k -sS -o /tmp/wygj-demo-check.favicon -w '%{http_code}' "$icon_url")"
[[ "$code" == "200" ]] || fail "mobile H5 favicon returned HTTP $code: $icon_url"
contains mobile-uni/src/manifest.json '"enable": false'
echo "OK: mobile H5 shell"
}
pilot_sql_scalar() {
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() {
if ! command -v docker >/dev/null || ! docker ps --format '{{.Names}}' | grep -qx 'wygj-mysql'; then
[[ "${AIHR_PILOT_STRICT:-false}" != "true" ]] || fail "pilot DB sample counts unavailable (wygj-mysql not running)"
echo "SKIP: pilot DB sample counts (wygj-mysql not running)"
return
fi
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 audio_table_count prompt_version_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 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")"
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'")"
sop_document_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_knowledge_attach WHERE tenant_id = '000000' AND type = 'sop' AND status = 2")"
case_stored_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_case_record WHERE tenant_id = '000000' AND status = '已入库'")"
case_unmasked_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_case_record WHERE tenant_id = '000000' 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}'")"
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 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 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 = '已评审' 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 "
$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 sop_docs=${sop_document_count}/5 stored_cases=${case_stored_count}/20 case_unmasked=${case_unmasked_count} 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" ]] || {
(( scenario_count >= 12 )) || fail "pilot enabled scenarios ${scenario_count}/12"
(( sop_document_count >= 5 )) || fail "pilot residential SOP documents ${sop_document_count}/5"
(( case_stored_count >= 20 )) || fail "pilot stored cases ${case_stored_count}/20"
(( 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)"
(( prompt_version_schema_count == 1 )) || fail "pilot RAG traceability schema unavailable (aihr_sop_answer_review.prompt_version missing)"
(( 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"
(( sop_usable * 100 >= sop_total * 80 )) || fail "pilot SOP usable rate below 80%"
(( satisfaction_count > 0 )) || fail "pilot satisfaction responses missing"
awk -v average="$satisfaction_average" 'BEGIN { exit !(average >= 4.0) }' || fail "pilot satisfaction average below 4/5"
}
}
if [[ "${AIHR_DEMO_CHECK_LIBRARY_ONLY:-false}" == "true" ]]; then
return 0 2>/dev/null || exit 0
fi
need curl
check_url "frontend" "$FRONTEND_URL/"
check_url "mobile-uni" "$MOBILE_URL/h5/"
check_mobile_h5_shell
contains docs/DEMO_ACCEPTANCE.md "capture-demo-screenshots.mjs"
contains scripts/capture-demo-screenshots.mjs "Page.captureScreenshot"
contains scripts/capture-demo-screenshots.mjs "wygj-ai-interview-flow.png"
tenant_payload="$(curl -k -fsS "$FRONTEND_URL/dev-api/auth/tenant/list")"
[[ "$tenant_payload" == *'"tenantId":"000000"'* ]] || fail "tenant list missing tenant 000000"
echo "OK: tenant list"
code_payload="$(curl -k -fsS "$FRONTEND_URL/dev-api/auth/code")"
[[ "$code_payload" == *'"uuid"'* ]] || fail "captcha response missing uuid"
echo "OK: captcha"
contains frontend/src/router/index.ts "/recruit/interview"
contains frontend/src/router/index.ts "/train/practice"
contains frontend/src/router/index.ts "/knowledge/cases"
contains frontend/src/router/index.ts "/knowledge/sop"
contains frontend/src/router/index.ts "/system/model"
contains frontend/src/router/index.ts "/admin/org-permission"
contains frontend/src/views/admin/org-permission.vue "dryRun ? '同步预检' : '执行同步'"
contains frontend/src/views/admin/org-permission.vue "组织同步接口不可用,请检查外部同步配置"
contains mobile-uni/package.json "aihr-mobile-uni"
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/"
contains mobile-uni/src/services/practice.ts "/api/train/practice/start"
contains mobile-uni/src/services/practice.ts "/api/aihr/mobile/practice/history"
contains mobile-uni/src/services/practice.ts "/api/aihr/mobile/profile/promotion-evidence"
contains mobile-uni/src/pages/user/practice/index.vue "训练完成,主管端待复盘已更新"
contains mobile-uni/src/pages/user/profile/index.vue "贡献度"
contains mobile-uni/src/services/knowledge.ts "/api/knowledge/answer-feedback"
contains mobile-uni/src/pages/user/sop/index.vue "有帮助"
contains mobile-uni/src/pages/user/sop/index.vue "没解决"
contains mobile-uni/src/services/knowledge.ts "/api/knowledge/summary-card"
contains mobile-uni/src/services/knowledge.ts "/api/knowledge/position-sop"
contains mobile-uni/src/services/position.ts "removeStorageSync(roleKey)"
contains mobile-uni/src/services/position.ts "/api/aihr/mobile/me"
contains mobile-uni/src/services/case.ts "/api/knowledge/case/upload"
contains mobile-uni/src/services/case.ts "/api/knowledge/case/records/"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrCaseController.java "/records/{caseId}"
contains mobile-uni/src/services/candidate.ts "/api/recruit/interview/start"
contains mobile-uni/src/services/candidate.ts "/api/aihr/mobile/candidate/materials"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrCandidateMaterialController.java "候选人登录态无效"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrCandidateMaterialController.java "@SaCheckLogin"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCandidateMaterialService.java "SUPPORTED_MATERIAL_EXTENSIONS"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java "SUPPORTED_AUDIO_EXTENSIONS"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrInterviewController.java "@SaCheckLogin"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSpeechController.java "@SaCheckLogin"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrCaseController.java "@SaCheckLogin"
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 "当前摘要不代表正式上岗资格"
contains mobile-uni/src/pages/user/learning/index.vue "岗位学习范围加载失败"
contains mobile-uni/src/pages/user/learning/index.vue "getPracticeAssignments"
contains mobile-uni/src/pages/user/learning/index.vue "searchKnowledge"
contains mobile-uni/src/pages/user/learning/index.vue "getPracticePrepCard"
contains frontend/src/views/recruit/interview.vue "建议复试"
contains frontend/src/views/train/practice.vue "导师改写"
contains frontend/src/views/knowledge/cases.vue "真实语音"
contains frontend/src/views/knowledge/cases.vue "案例所属项目"
contains frontend/src/views/knowledge/cases.vue "请先选择案例所属项目"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java "系统用户上传案例必须明确所属项目"
contains frontend/src/views/knowledge/sop.vue "训练题已生成"
contains frontend/src/views/system/model/index.vue "调用优先级"
contains frontend/src/views/system/model/index.vue "提示词模板"
contains frontend/src/views/system/prompts.vue "保存并启用"
contains frontend/src/views/competency/growth.vue "当前运营口径(只读)"
contains frontend/src/views/competency/growth.vue "训练画像等级(参考)"
contains mobile-uni/src/pages/user/profile/index.vue "成长证据包(待人工复核)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "训练画像参考等级"
contains frontend/src/router/index.ts "/system/prompts"
contains frontend/src/api/aihr/interview.ts "/api/recruit/interview/start"
contains frontend/src/api/aihr/interview.ts "/api/recruit/interview/candidate-links"
contains frontend/src/api/aihr/interview.ts "/records/\${encodeURIComponent(sessionId)}/review"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrInterviewController.java "/api/recruit/interview"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrInterviewController.java "无权查看面试记录"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrInterviewController.java "/records/{sessionId}/review"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrInterviewService.java "reviewed_score"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrInterviewController.java "interviewService.answer(request, currentAppUsername())"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrInterviewController.java "interviewService.finish(request, currentAppUsername())"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrInterviewService.java "面试会话不存在或无权访问"
contains frontend/src/views/recruit/interview.vue "人工复核 AI 面试结果"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrInterviewController.java "@SaCheckRole"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrInterviewService.java "aihr_candidate_employee_link"
contains frontend/src/api/aihr/practice.ts "/api/train/practice/start"
contains frontend/src/api/aihr/practice.ts "/api/aihr/prompt-templates"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPracticeController.java "/api/train/practice"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPracticeController.java "practiceSeedService.turn(request, currentAppUsername())"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPracticeController.java "practiceSeedService.finish(request, currentAppUsername())"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "训练会话不存在或无权访问"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPracticeController.java "@SaCheckLogin"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPracticeController.java "sessions/{sessionId}/annotations"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPromptTemplateController.java "/api/aihr/prompt-templates"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPromptTemplateController.java "public R<List<PromptTemplateResponse>> list"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "mobileCompletedCount"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "任务完成度"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "响应时效"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java "MistakeAggregateResponse"
contains mobile-uni/src/pages/supervisor/team/index.vue "团队错题本"
contains mobile-uni/src/types/api.ts "PracticeMistakeAggregate"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "/practice/mistakes"
contains mobile-uni/src/pages/user/learning/index.vue "错题本"
contains mobile-uni/src/pages/user/learning/index.vue "mistakeFilters"
contains mobile-uni/src/pages/user/profile/index.vue "AI评分仅作训练参考"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "aihr_practice_session"
contains backend/script/sql/aihr_practice_mysql8.sql "aihr_practice_scenario"
contains backend/script/sql/aihr_practice_mysql8.sql "aihr_prompt_template"
contains backend/script/sql/aihr_practice_mysql8.sql "summary_card"
contains frontend/src/views/train/practice.vue "训练前预习卡"
contains frontend/src/views/train/practice.vue "Rubric 预览"
contains frontend/src/views/train/practice.vue "提示词模板"
contains frontend/src/views/train/practice.vue "导出试点CSV"
contains frontend/src/views/train/reviews.vue "导出试点 CSV"
contains frontend/src/views/train/reviews.vue "人工校准总分"
contains frontend/src/api/aihr/practice.ts "/api/train/practice/export"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "final_adopted_score"
contains backend/script/sql/aihr_practice_mysql8.sql "aihr_practice_calibration"
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/AihrModelController.java "@SaCheckRole"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "@SaCheckRole"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrSopDto.java "String position"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "/position-sop"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "@SaIgnore"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrDashboardController.java "@SaCheckLogin"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrDashboardController.java "@SaCheckRole"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java 'firstNonBlank(position, "生活顾问")'
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "normalizeSearchSource"
contains frontend/src/views/train/practice.vue "practice_help"
contains mobile-uni/src/pages/user/practice/index.vue "loadHelp"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "turnEvidence"
contains mobile-uni/src/pages/user/practice/index.vue "每日三题"
contains mobile-uni/src/pages/user/practice/index.vue "每日一练语音已转成文字"
contains mobile-uni/src/pages/user/practice/index.vue "dailyAudioOssIds"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "answer_audio_oss_id"
contains backend/script/sql/aihr_practice_mysql8.sql "answer_audio_url"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "pilot_metric_ready"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "/practice/satisfaction"
contains backend/script/sql/aihr_practice_mysql8.sql "satisfaction_score"
contains mobile-uni/src/services/practice.ts "/api/aihr/mobile/practice/satisfaction"
contains mobile-uni/src/pages/user/practice/index.vue "本次训练体验如何"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "incentive_point"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrModelSeedService.java "cost-guard-fallback"
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 backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "StpUtil.hasRoleOr(TenantConstants.SUPER_ADMIN_ROLE_KEY, \"hr_operator\")"
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/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/AihrOrgSyncService.java "instr(coalesce(person_name, '')"
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"
contains frontend/src/api/aihr/case.ts "/api/knowledge/case/upload"
contains frontend/src/api/aihr/case.ts "/api/knowledge/case/records?limit="
contains frontend/src/api/aihr/case.ts "/api/knowledge/case/records/"
contains frontend/src/views/knowledge/cases.vue "loadCaseRecords"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrCaseController.java "currentProjectScopes"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java "projectScopesForAppUser"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrCaseController.java "/api/knowledge/case"
contains frontend/src/api/aihr/sop.ts "/api/knowledge/search"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "@SaCheckLogin"
contains frontend/src/api/aihr/sop.ts "/api/knowledge/doc/upload"
contains frontend/src/api/aihr/sop.ts "/api/knowledge/reviews"
contains frontend/src/api/aihr/sop.ts "/api/knowledge/answer-feedback"
contains frontend/src/views/knowledge/sop.vue "SOP 问答评审已记录"
contains frontend/src/views/knowledge/sop.vue "答案待复核"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "/api/knowledge"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "/doc/upload"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "仅支持 txt/md/markdown/pdf/doc/docx/xls/xlsx/ppt/pptx/图片文件"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "/embeddings"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "aihr_knowledge_gap"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrVideoService.java "处理输出已隐藏"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrUploadQueueService.java "资料加工失败,请重试或联系管理员"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "目录导入失败,请重试或联系管理员"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "文件处理失败,请重试或联系管理员"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java "case audio upload failed(处理错误已隐藏)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSpeechController.java "tts audio upload failed(处理错误已隐藏)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSensitiveText.java "外部 AI 模型前"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrModelSeedService.java "AihrSensitiveText.forModel(prompt)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrModelSeedService.java "外部响应体已隐藏"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "AihrSensitiveText.forModel(renderPrompt"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSpeechService.java "AihrSensitiveText.forModel(text.trim())"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSpeechService.java "asr http {}(外部响应体已隐藏)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrVideoService.java "AihrSensitiveText.forModel(fileName)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java "外部响应体已隐藏"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrCaseController.java "仅项目负责人或主管可提交案例素材"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrCaseController.java "StpUtil.hasRoleOr"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrCaseController.java "private boolean canViewCases()"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java "position_level IN ('主管', '项目经理')"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java "media_oss_id"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java "storeSourceAudio"
contains mobile-uni/src/pages/user/cases/index.vue "case-audio"
contains frontend/src/views/knowledge/cases.vue "case-audio"
contains backend/script/sql/aihr_practice_mysql8.sql "aihr_case_record"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "prompt_version"
contains backend/script/sql/aihr_practice_mysql8.sql "prompt_version"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "review_id"
contains mobile-uni/src/services/knowledge.ts "reviewId: params.reviewId"
contains backend/script/sql/aihr_knowledge_mysql8.sql "embedding_json"
contains frontend/src/views/knowledge/sop.vue "上传文档"
contains frontend/src/views/knowledge/sop.vue ".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
contains docs/RUOYI_AI_INCREMENTAL_MIGRATION.md "ruoyi-ai"
contains docs/RUOYI_AI_INCREMENTAL_MIGRATION.md "POST /api/knowledge/doc/upload"
contains backend/script/sql/aihr_knowledge_mysql8.sql "aihr_knowledge_fragment"
contains backend/script/sql/aihr_model_mysql8.sql "aihr_model_config"
contains backend/script/sql/aihr_practice_mysql8.sql "aihr_practice_session"
contains backend/script/sql/aihr_practice_mysql8.sql "dialogue_json"
contains backend/script/sql/aihr_practice_mysql8.sql "incentive_point"
contains backend/script/sql/aihr_model_mysql8.sql "api_key"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrModelController.java "/api/aihr/model"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrModelController.java "/providers/{providerCode}"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrModelController.java "/configs/{id}/enabled"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrModelSeedService.java "aihr_model_provider"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrModelSeedService.java "模型调用失败,已回退内置兜底"
contains frontend/src/api/aihr/model.ts "/api/aihr/model/providers"
contains frontend/src/api/aihr/model.ts "/api/aihr/model/configs"
contains frontend/src/views/system/model/index.vue "submitProvider"
contains frontend/src/views/system/model/index.vue "submitModel"
contains backend/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java "/mobile/sms-login"
contains backend/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java "aihr.sms.login-template-id"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "/api/aihr/mobile"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "@SaCheckLogin"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrMobileSeedService.java "candidateHome"
echo "OK: demo routes and real-flow markers"
echo "OK: AI interview API markers"
echo "OK: AI practice API markers"
echo "OK: AI case API markers"
echo "OK: AI SOP API markers"
echo "OK: ruoyi-ai incremental migration markers"
echo "OK: AI model migration markers"
echo "OK: AI model config page markers"
echo "OK: AI mobile role home markers"
check_pilot_samples
echo "PASS: demo preflight"