#!/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/.*/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 local scenario_count completed_people pilot_people calibration_stats calibration_count calibration_matched sop_total sop_usable sop_pending scenario_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_practice_scenario WHERE tenant_id = '000000' AND position = '生活顾问' AND enabled = 1")" completed_people="$(pilot_sql_scalar "SELECT COUNT(*) FROM (SELECT DISTINCT ext_party_id FROM aihr_practice_session WHERE tenant_id = '000000' AND mode = 'mobile') completed_people")" pilot_people="$(pilot_sql_scalar " SELECT COUNT(*) FROM ( SELECT ext_party_id FROM aihr_practice_session WHERE tenant_id = '000000' AND mode = 'mobile' UNION SELECT ext_party_id FROM aihr_practice_assignment WHERE tenant_id = '000000' AND status = '待训练' AND (source <> 'daily' OR DATE(create_time) = CURRENT_DATE()) ) pilot_people")" calibration_stats="$(pilot_sql_scalar " SELECT COUNT(*) AS total, COALESCE(SUM(CASE WHEN (CASE WHEN c.original_score < 60 THEN 0 WHEN c.original_score < 75 THEN 1 WHEN c.original_score < 90 THEN 2 ELSE 3 END) = (CASE WHEN c.corrected_score < 60 THEN 0 WHEN c.corrected_score < 75 THEN 1 WHEN c.corrected_score < 90 THEN 2 ELSE 3 END) THEN 1 ELSE 0 END), 0) AS matched FROM aihr_practice_calibration c JOIN ( SELECT session_id, MAX(id) AS id FROM aihr_practice_calibration WHERE tenant_id = '000000' AND original_score IS NOT NULL AND corrected_score IS NOT NULL GROUP BY session_id ) latest ON latest.id = c.id WHERE c.tenant_id = '000000'")" read -r calibration_count calibration_matched <<<"$calibration_stats" sop_total="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_sop_answer_review WHERE tenant_id = '000000' AND status = '已评审'")" sop_usable="$(pilot_sql_scalar "SELECT COALESCE(SUM(CASE WHEN usable = 1 THEN 1 ELSE 0 END), 0) FROM aihr_sop_answer_review WHERE tenant_id = '000000' AND status = '已评审'")" sop_pending="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_sop_answer_review WHERE tenant_id = '000000' AND status <> '已评审'")" echo "OK: pilot gates scenarios=${scenario_count}/12 completion=${completed_people}/${pilot_people} calibration=${calibration_count}/20 consistency=${calibration_matched}/${calibration_count} sop_usable=${sop_usable}/${sop_total} sop_pending=${sop_pending}" [[ "${AIHR_PILOT_STRICT:-false}" != "true" ]] || { (( scenario_count >= 12 )) || fail "pilot enabled scenarios ${scenario_count}/12" (( pilot_people > 0 )) || fail "pilot completion samples missing" (( completed_people * 100 >= pilot_people * 80 )) || fail "pilot completion rate below 80% (${completed_people}/${pilot_people})" (( 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%" } } 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 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/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/services/knowledge.ts "/api/knowledge/summary-card" contains mobile-uni/src/services/case.ts "/api/knowledge/case/upload" contains mobile-uni/src/services/candidate.ts "/api/recruit/interview/start" contains mobile-uni/src/services/candidate.ts "/api/aihr/mobile/candidate/materials" contains mobile-uni/src/pages/user/learning/index.vue "岗位 SOP" contains mobile-uni/src/pages/user/learning/index.vue "getPracticeAssignments" contains mobile-uni/src/pages/user/learning/index.vue "searchKnowledge" 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/sop.vue "训练题已生成" contains frontend/src/views/system/model/index.vue "调用优先级" contains frontend/src/views/system/model/index.vue "提示词模板" contains frontend/src/api/aihr/interview.ts "/api/recruit/interview/start" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrInterviewController.java "/api/recruit/interview" 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/AihrPromptTemplateController.java "/api/aihr/prompt-templates" 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 "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/api/aihr/practice.ts "/api/train/practice/export" 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/domain/AihrSopDto.java "String position" 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 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/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 mobile-uni/src/pages/supervisor/index/index.vue "人工贡献分" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "/practice/reviews/{id}/reviewed" contains mobile-uni/src/pages/supervisor/index/index.vue "标记已复盘" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "source = 'retry'" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "maskSensitiveText" contains mobile-uni/src/services/practice.ts "/api/aihr/mobile/practice/assignments" contains frontend/src/api/aihr/case.ts "/api/knowledge/case/upload" 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 frontend/src/api/aihr/sop.ts "/api/knowledge/doc/upload" contains frontend/src/api/aihr/sop.ts "/api/knowledge/reviews" contains frontend/src/views/knowledge/sop.vue "SOP 问答评审已记录" 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/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 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/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"