feat(aihr): complete phase two pilot readiness

This commit is contained in:
2026-07-10 19:13:31 +08:00
parent 0219de6fa8
commit 13230e6b9a
48 changed files with 7040 additions and 1647 deletions
+92 -1
View File
@@ -3,6 +3,7 @@ import { execFileSync } from 'node:child_process';
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { verifyPilotExportContract } from './lib/pilot-export-contract.mjs';
const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
const args = new Map(process.argv.slice(2).map((arg) => {
@@ -21,6 +22,10 @@ const file = path.resolve(root, args.get('file') || 'docs/20260708/发布会演
const prefixes = (args.get('prefix') || 'Q').split(',').map((item) => item.trim()).filter(Boolean);
const ids = new Set((args.get('ids') || '').split(',').map((item) => item.trim()).filter(Boolean));
const limit = Number(args.get('limit') || 0);
const orgDryRun = args.has('org-dry-run');
const pilotExport = args.has('pilot-export');
const pilotStartDate = args.get('start-date') || '';
const pilotEndDate = args.get('end-date') || '';
function readEnv(filePath) {
const env = {};
@@ -100,6 +105,26 @@ async function request(method, endpoint, body, token, encrypted = false) {
}
}
async function requestText(method, endpoint, token) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 70000);
try {
const response = await fetch(`${baseUrl}${endpoint}`, {
method,
headers: { clientid, 'Content-Language': 'zh_CN', Authorization: `Bearer ${token}` },
signal: controller.signal
});
return {
status: response.status,
contentType: response.headers.get('content-type') || '',
disposition: response.headers.get('content-disposition') || '',
text: await response.text()
};
} finally {
clearTimeout(timeout);
}
}
async function login() {
if (process.env.AIHR_VERIFY_TOKEN) return process.env.AIHR_VERIFY_TOKEN;
const codeResponse = await request('GET', '/auth/code');
@@ -160,13 +185,79 @@ function evidenceTitles(result) {
].filter(Boolean);
}
const token = await login();
if (pilotExport) {
if (!/^\d{4}-\d{2}-\d{2}$/.test(pilotStartDate) || !/^\d{4}-\d{2}-\d{2}$/.test(pilotEndDate)) {
throw new Error('--pilot-export requires --start-date=YYYY-MM-DD and --end-date=YYYY-MM-DD');
}
const missingWindow = await requestText('GET', '/api/train/practice/export', token);
let missingWindowData;
try {
missingWindowData = JSON.parse(missingWindow.text);
} catch {
missingWindowData = null;
}
if (missingWindow.status < 400 && (missingWindow.contentType.includes('text/csv') || missingWindowData?.code === 200)) {
console.error('FAIL: pilot export accepted a request without an explicit date window');
process.exit(1);
}
const endpoint = `/api/train/practice/export?startDate=${encodeURIComponent(pilotStartDate)}&endDate=${encodeURIComponent(pilotEndDate)}`;
const response = await requestText('GET', endpoint, token);
if (response.status !== 200 || !response.contentType.includes('text/csv')) {
console.error(`FAIL: pilot export HTTP ${response.status}, content-type=${response.contentType}`);
if (verbose) console.error(response.text.slice(0, 1000));
process.exit(1);
}
let verifiedExport;
try {
verifiedExport = verifyPilotExportContract({
csv: response.text,
startDate: pilotStartDate,
endDate: pilotEndDate,
contentDisposition: response.disposition
});
} catch (error) {
console.error(`FAIL: ${error.message}`);
process.exit(1);
}
const { metrics } = verifiedExport;
console.log(
`PILOT_EXPORT window=${pilotStartDate}..${pilotEndDate}`
+ ` org_login=${metrics.get('formal_org_login_count')}`
+ ` people=${metrics.get('formal_pilot_people_count')}`
+ ` projects=${metrics.get('formal_pilot_project_count')}`
+ ` ten_sessions=${metrics.get('completion_people_count')}/${metrics.get('completion_people_total')}`
+ ` calibration=${metrics.get('calibration_sample_count')}`
+ ` sop=${metrics.get('sop_review_sample_count')}`
+ ` ready=${metrics.get('pilot_metric_ready')}`
);
process.exit(0);
}
if (orgDryRun) {
const response = await request('POST', '/api/aihr/org/sync', {
dryRun: true,
replaceExisting: true,
pageSize: 500,
maxPages: 200
}, token);
const data = response.data?.data;
if (response.status !== 200 || response.data?.code !== 200 || !data?.dryRun) {
console.error(`FAIL: org dry-run failed: ${response.data?.msg || response.status}`);
process.exit(1);
}
console.log(`ORG_DRY_RUN employees=${data.employeeCount} phone_linked=${data.phoneLinked} masked_phone=${data.maskedPhone} suspect_text=${data.suspectText} synced=${data.syncedCount}`);
for (const warning of data.warnings || []) {
console.log(`WARN ${warning}`);
}
process.exit(0);
}
const questions = parseQuestions(fs.readFileSync(file, 'utf8'));
if (!questions.length) {
console.error(`FAIL: no questions found in ${file} for prefix ${prefixes.join(',')}`);
process.exit(1);
}
const token = await login();
const vector = await request('GET', '/api/knowledge/doc/vector-index-status', undefined, token);
const vectorStatus = vector.data?.data;
if (!vectorStatus?.matched) {