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

710 lines
58 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}"
local pilot_tenant="${AIHR_PILOT_TENANT_ID:-000000}"
[[ "$pilot_tenant" =~ ^[A-Za-z0-9_-]+$ ]] || fail "invalid AIHR_PILOT_TENANT_ID: $pilot_tenant"
[[ "$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 scenario_service_promotion_count scenario_daily_service_count rubric_expected_count rubric_five_dimension_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 requester_identity_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 org_identity_cte="
WITH active_org AS (
SELECT DISTINCT ext_party_id, project_code, person_phone
FROM aihr_org_snapshot
WHERE tenant_id = '$pilot_tenant' 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 = '$pilot_tenant' AND position = '生活顾问' AND enabled = 1")"
# ponytail: presence-only core-flow gate; content quality and SOP applicability still require HR review.
scenario_service_promotion_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_type LIKE '%增值%' OR scenario_name 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 '%日常%')")"
rubric_expected_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_practice_rubric r JOIN aihr_practice_scenario s ON s.tenant_id = r.tenant_id AND s.scenario_code = r.scenario_code WHERE r.tenant_id = '$pilot_tenant' AND s.position = '生活顾问' AND s.enabled = 1 AND r.enabled = 1")"
rubric_five_dimension_count="$(pilot_sql_scalar "
SELECT COUNT(*)
FROM (
SELECT r.rubric_code
FROM aihr_practice_rubric r
JOIN aihr_practice_scenario s ON s.tenant_id = r.tenant_id AND s.scenario_code = r.scenario_code
JOIN aihr_practice_rubric_dimension d ON d.tenant_id = r.tenant_id AND d.rubric_code = r.rubric_code
WHERE r.tenant_id = '$pilot_tenant' AND s.position = '生活顾问' AND s.enabled = 1 AND r.enabled = 1
GROUP BY r.rubric_code
HAVING COUNT(*) = 5 AND ABS(SUM(d.weight) - 1.00) < 0.001
) ready_rubrics")"
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'")"
requester_identity_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 = 'requester_ext_party_id'")"
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")"
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}'")"
completed_people="$(pilot_sql_scalar "
SELECT COUNT(*)
FROM (
SELECT ext_party_id
FROM aihr_practice_session
WHERE tenant_id = '$pilot_tenant' 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 = '$pilot_tenant' 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 = '$pilot_tenant'
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 = '$pilot_tenant' AND employment_status = 'active'")"
org_phone_linked="$(pilot_sql_scalar "$org_identity_cte
SELECT COUNT(DISTINCT ext_party_id)
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_identity_cte,
pilot_identity AS (
SELECT ext_party_id identity_key
FROM aihr_practice_session
WHERE tenant_id = '$pilot_tenant' 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 = '$pilot_tenant'
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 = '$pilot_tenant' 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 = '$pilot_tenant'
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 = '$pilot_tenant' 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 = '$pilot_tenant' 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 = '$pilot_tenant' 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 = '$pilot_tenant'")"
read -r calibration_count calibration_matched <<<"$calibration_stats"
sop_total="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_sop_answer_review r WHERE r.tenant_id = '$pilot_tenant' AND r.status = '已评审' AND NULLIF(TRIM(r.requester_ext_party_id), '') IS NOT NULL AND EXISTS (SELECT 1 FROM aihr_org_snapshot o WHERE o.tenant_id = r.tenant_id AND o.employment_status = 'active' AND (o.ext_party_id = r.requester_ext_party_id OR o.person_phone = r.requester_ext_party_id)) AND (LOWER(TRIM(COALESCE(r.category, ''))) = 'sop' OR r.category LIKE '%SOP%') AND r.reviewed_time >= '${pilot_start} 00:00:00' AND r.reviewed_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)")"
sop_usable="$(pilot_sql_scalar "SELECT COALESCE(SUM(CASE WHEN r.usable = 1 THEN 1 ELSE 0 END), 0) FROM aihr_sop_answer_review r WHERE r.tenant_id = '$pilot_tenant' AND r.status = '已评审' AND NULLIF(TRIM(r.requester_ext_party_id), '') IS NOT NULL AND EXISTS (SELECT 1 FROM aihr_org_snapshot o WHERE o.tenant_id = r.tenant_id AND o.employment_status = 'active' AND (o.ext_party_id = r.requester_ext_party_id OR o.person_phone = r.requester_ext_party_id)) AND (LOWER(TRIM(COALESCE(r.category, ''))) = 'sop' OR r.category LIKE '%SOP%') AND r.reviewed_time >= '${pilot_start} 00:00:00' AND r.reviewed_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)")"
sop_pending="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_sop_answer_review r WHERE r.tenant_id = '$pilot_tenant' AND r.status <> '已评审' AND NULLIF(TRIM(r.requester_ext_party_id), '') IS NOT NULL AND EXISTS (SELECT 1 FROM aihr_org_snapshot o WHERE o.tenant_id = r.tenant_id AND o.employment_status = 'active' AND (o.ext_party_id = r.requester_ext_party_id OR o.person_phone = r.requester_ext_party_id)) AND (LOWER(TRIM(COALESCE(r.category, ''))) = 'sop' OR r.category LIKE '%SOP%') AND r.create_time >= '${pilot_start} 00:00:00' AND r.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 = '$pilot_tenant' 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 = '$pilot_tenant' 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 rubric_five_dimensions=${rubric_five_dimension_count}/${rubric_expected_count} 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} requester_identity_schema=${requester_identity_schema_count} hire_date_schema=${hire_date_schema_count}"
[[ "${AIHR_PILOT_STRICT:-false}" != "true" ]] || {
(( scenario_count >= 12 )) || fail "pilot enabled scenarios ${scenario_count}/12"
(( rubric_expected_count >= scenario_count && rubric_five_dimension_count >= rubric_expected_count )) || fail "pilot five-dimension rubrics ${rubric_five_dimension_count}/${rubric_expected_count}"
(( scenario_service_promotion_count > 0 )) || fail "pilot service-promotion scenarios missing"
(( scenario_daily_service_count > 0 )) || fail "pilot daily-service scenarios missing"
(( 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)"
(( requester_identity_schema_count == 1 )) || fail "pilot SOP requester identity schema unavailable (aihr_sop_answer_review.requester_ext_party_id 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_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_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
[[ "${AIHR_PILOT_STRICT:-false}" != "true" ]] || fail "AIHR_DEMO_CHECK_LIBRARY_ONLY cannot bypass strict pilot checks"
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 frontend/src/views/admin/org-permission.vue "row.personName || row.extPartyId"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java "storeDisplayFields"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java "rows.stream().filter(OrgRow::suspectText)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java "boolean suspectText = hasQuestionMark(projectName)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java "组织同步必须明确传 dryRun"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrOrgSyncController.java "@RequestBody(required = true)"
contains backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrOrgSyncServiceTest.java "syncRequiresExplicitDryRunMode"
contains backend/ruoyi-admin/src/main/resources/application-prod.yml "store-display-fields: false"
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/home.ts "auth: isLoggedIn()"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrMobileSeedService.java "public HomeResponse publicHome(String role)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "return R.ok(mobileSeedService.publicHome(role))"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "mobileSeedService.home(role, identity)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "!UserType.APP_USER.getUserType().equals(loginUser.getUserType())"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "无权访问该录音"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "supervisorRoleRequested"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "public MobileTeamMetrics mobileTeamMetrics(String supervisorExtPartyId)"
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/position.ts "role === 'user' && isSupervisorRoute(requested)"
contains mobile-uni/src/services/case.ts "/api/knowledge/case/upload"
contains mobile-uni/src/services/case.ts "/api/knowledge/case/capabilities"
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/controller/AihrCandidateMaterialController.java "review(id, request, operator)"
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/AihrCandidateMaterialService.java "审核接口必须明确提交"
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 backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "restrictDemoVerificationSource(request)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "StpUtil.hasRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)"
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 "const loadPage = async () =>"
contains mobile-uni/src/pages/candidate/index/index.vue "clearData();"
contains mobile-uni/src/pages/candidate/materials/index.vue "clearData();"
contains mobile-uni/src/pages/candidate/progress/index.vue "const clearData = () =>"
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/recruit/interview.vue 'v-loading="recordsLoading"'
contains frontend/src/views/recruit/interview.vue "const recordsLoading = ref(true)"
contains frontend/src/views/train/practice.vue "导师改写"
contains frontend/src/views/train/practice.vue 'v-loading="recordsLoading"'
contains frontend/src/views/train/practice.vue "const records = ref<Array<(typeof baseRecords)[number]>>([])"
contains frontend/src/views/knowledge/cases.vue "真实语音"
contains frontend/src/views/knowledge/cases.vue 'v-loading="recordsLoading"'
contains frontend/src/views/knowledge/cases.vue "const records = ref<CaseRecord[]>([])"
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/index.vue "核心流程覆盖待内容确认"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrDashboardService.java "核心流程覆盖待内容确认"
contains frontend/src/views/index.vue "不等于端到端验收"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrDashboardService.java "不等于端到端验收"
contains frontend/src/views/index.vue "正式试点身份待核对"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrDashboardService.java "正式试点身份待核对"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrDashboardService.java "String traineeExpression = storeDisplayFields"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "String nameExpression = !storeDisplayFields"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "PILOT_SESSION_ROWS_SQL.formatted(storeDisplayFields"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "private String persistedTraineeName"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "persistedTraineeName(extPartyId.matches"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "if (!orgSnapshotTableExists() && !isDemoProfile())"
contains frontend/src/views/index.vue "待同步"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrDashboardService.java "核心流程待确认"
contains frontend/src/views/knowledge/sop.vue "训练题已生成"
contains frontend/src/views/knowledge/sop.vue "不代表这些文档已真实入库"
contains frontend/src/views/knowledge/sop.vue "内置演示"
contains frontend/src/views/knowledge/sop.vue "apiResult.value?.records"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrSopDto.java "String source"
contains frontend/src/views/knowledge/processing.vue "当前显示演示数据,不代表真实库统计"
contains frontend/src/views/knowledge/cases.vue "不代表真实案例库数据"
contains frontend/src/views/recruit/interview.vue "不代表真实面试数据"
contains frontend/src/views/system/model/index.vue "调用优先级"
contains frontend/src/views/system/model/index.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 "学分规则(待 HR 配置)"
contains frontend/src/views/competency/growth.vue "规划参考,当前不生效"
contains frontend/src/views/competency/growth.vue "训练画像等级(参考)"
contains frontend/src/views/competency/growth.vue "人工贡献分"
contains frontend/src/views/train/reviews.vue "人工贡献分"
contains frontend/src/views/train/reviews.vue "incentivePoint: null as number | null"
contains frontend/src/views/train/reviews.vue "incentivePoint: reviewForm.incentivePoint ?? undefined"
contains frontend/src/views/competency/growth.vue "person.personName || person.extPartyId"
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/service/AihrPracticeSeedService.java "finished_time IS NOT NULL"
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/AihrInterviewService.java "finished_time IS NOT NULL AND total_score IS NOT NULL"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrInterviewService.java "reviewed_score = NULL"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrInterviewService.java "请先完成全部面试回答"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrInterviewService.java "面试题不存在"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "noneMatch(AihrPracticeSeedService::canSeeProject)"
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/AihrPracticeController.java "bindCalibrationReviewer(request)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "bindReviewOperator(request)"
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/service/AihrPracticeSeedService.java "new DimensionResponse(\"响应时效\", \"待采集\""
contains frontend/src/views/train/practice.vue "label: '任务完成度'"
contains backend/script/sql/aihr_practice_mysql8.sql "task_completion"
contains backend/script/sql/update/aihr_20260714_practice_five_dimensions_mysql8.sql "aihr_brd_rubric_codes"
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/practice.vue "训练记录接口暂不可用,当前显示演示记录"
contains frontend/src/views/train/practice.vue "listPracticeReviews"
contains frontend/src/views/train/reviews.vue "导出试点 CSV"
contains frontend/src/views/train/reviews.vue "人工校准总分"
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 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/aihr_org_snapshot_mysql8.sql "hire_date"
contains backend/script/sql/update/aihr_20260714_org_hire_date_mysql8.sql "hire_date"
contains backend/script/sql/update/aihr_20260714_org_hire_date_mysql8.sql "aihr_org_snapshot_table_exists"
contains backend/script/sql/update/aihr_20260714_sop_requester_identity_mysql8.sql "aihr_sop_source_exists"
contains backend/script/sql/update/aihr_20260714_sop_requester_identity_mysql8.sql "aihr_sop_prompt_version_exists"
contains scripts/tests/aihr-schema-migrations.test.sh "AIHR legacy schema migrations are idempotent"
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 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/AihrPracticeSeedService.java "score_mode"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeLlmService.java "scoreDailyDrill"
contains backend/script/sql/update/aihr_20260714_daily_drill_score_mode_mysql8.sql "score_mode"
contains backend/script/sql/update/aihr_20260714_daily_drill_score_mode_mysql8.sql "ADD COLUMN score int"
contains mobile-uni/src/pages/user/practice/index.vue "AI语义评估"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java 'String source = "manual"'
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 "public PrepCardResponse prepCard(String scenarioId)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "scenario.id(), scenario.name(), source"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "return rows;"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "isWithinNewHireWindow"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "allow-legacy-daily-drill-fallback"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "isLegacyDailyDrillFallbackEnabled"
contains backend/ruoyi-admin/src/main/resources/application-dev.yml "allow-legacy-daily-drill-fallback: true"
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 mobile-uni/src/services/practice.ts "/api/train/practice/scenarios?position="
contains mobile-uni/src/pages/supervisor/assign/index.vue "scenarioOptions"
contains mobile-uni/src/pages/supervisor/assign/index.vue "暂无可派发的已启用场景"
contains mobile-uni/src/pages/supervisor/review/index.vue "scenarioOptions"
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 backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "reviewAnswerFeedback(id, operator)"
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/controller/AihrSpeechController.java "oss.getOssId(), \"\")"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSpeechController.java "asr audio upload failed(处理错误已隐藏)"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrModelSeedService.java "visionAllowed"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "!modelService.visionAllowed()"
contains backend/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java '!environment.acceptsProfiles(Profiles.of("prod"))'
contains backend/ruoyi-admin/src/test/java/org/dromara/web/controller/CaptchaControllerSecurityTest.java "CaptchaControllerSecurityTest"
contains backend/ruoyi-admin/src/main/resources/application-dev.yml "AIHR_MYSQL_PORT:13306"
contains backend/ruoyi-admin/src/main/resources/application-dev.yml "AIHR_REDIS_PORT:16379"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java "person_phone like ?"
contains backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrOrgSyncServiceTest.java "hiddenDisplayFieldsStillAllowIdentitySearchByPhone"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "requester_ext_party_id"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "currentRequesterExtPartyId"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "hasExplicitSopReviewDecision"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "validateFeedbackFragmentOwnership"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "normalizeFeedbackReviewId"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "AND query_text = ?"
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 "@GetMapping(\"/capabilities\")"
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 backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java "currentCaseOwner"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrInterviewService.java "候选人不存在,请先创建或完成一次面试记录"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "reviewSearch(id, request, operator)"
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/controller/AihrMobileController.java "mobile identity lookup failed, fallback to employee role"
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"