const REQUIRED_METRICS = [ 'pilot_start_date', 'pilot_end_date', 'data_scope', 'scenario_count', 'scenario_count_target', 'formal_org_identity_count', 'formal_pilot_people_count', 'formal_pilot_people_target', 'formal_pilot_project_count', 'formal_pilot_project_target', 'formal_pilot_scope_ready', 'training_count', 'completion_definition', 'completion_people_count', 'completion_people_total', 'completion_rate', 'completion_rate_target', 'completion_rate_ready', 'average_score', 'alert_count', 'review_rate', 'calibration_sample_count', 'ai_human_band_matched_count', 'calibration_sample_target', 'calibration_sample_ready', 'ai_human_band_consistency_rate', 'ai_human_band_consistency_target', 'ai_human_band_consistency_ready', 'sop_review_sample_count', 'sop_answer_usable_count', 'sop_answer_usable_rate', 'sop_answer_usable_target', 'sop_answer_usable_ready', 'pilot_metric_ready' ]; const INTEGER_METRICS = [ 'scenario_count', 'scenario_count_target', 'formal_org_identity_count', 'formal_pilot_people_count', 'formal_pilot_people_target', 'formal_pilot_project_count', 'training_count', 'completion_people_count', 'completion_people_total', 'alert_count', 'calibration_sample_count', 'ai_human_band_matched_count', 'calibration_sample_target', 'sop_review_sample_count', 'sop_answer_usable_count' ]; const PERCENTAGE_METRICS = [ 'completion_rate', 'completion_rate_target', 'review_rate', 'ai_human_band_consistency_rate', 'ai_human_band_consistency_target', 'sop_answer_usable_rate', 'sop_answer_usable_target' ]; const BOOLEAN_METRICS = [ 'formal_pilot_scope_ready', 'completion_rate_ready', 'calibration_sample_ready', 'ai_human_band_consistency_ready', 'sop_answer_usable_ready', 'pilot_metric_ready' ]; const DETAIL_HEADERS = [ 'formal_ext_party_id', 'project_codes', 'session_id', 'trainee', 'scene', 'score', 'status', 'finished_time', 'summary', 'review_advice', 'incentive_point', 'ai_score', 'calibration_original_score', 'human_score', 'calibration_reviewer', 'calibration_time', 'final_adopted_score' ]; function fail(message) { throw new Error(`pilot export contract: ${message}`); } export function parseCsv(csv) { const rows = []; let row = []; let cell = ''; let quoted = false; let quoteClosed = false; let fieldStart = true; const text = String(csv || '').replace(/^\uFEFF/, ''); for (let index = 0; index < text.length; index += 1) { const char = text[index]; if (quoted) { if (char === '"' && text[index + 1] === '"') { cell += '"'; index += 1; } else if (char === '"') { quoted = false; quoteClosed = true; } else { cell += char; } continue; } if (quoteClosed) { if (char !== ',' && char !== '\n' && char !== '\r') { fail('unexpected character after closing quote'); } quoteClosed = false; } if (char === '"') { if (!fieldStart) fail('quote must start a field'); quoted = true; fieldStart = false; continue; } if (char === ',') { row.push(cell); cell = ''; fieldStart = true; continue; } if (char === '\n' || char === '\r') { if (char === '\r' && text[index + 1] === '\n') index += 1; row.push(cell); rows.push(row); row = []; cell = ''; fieldStart = true; continue; } cell += char; fieldStart = false; } if (quoted) fail('unclosed quoted field'); if (cell || row.length || !fieldStart) { row.push(cell); rows.push(row); } return rows; } function isInteger(value) { return /^\d+$/.test(value); } function isPercentage(value) { return /^\d+(?:\.\d+)?%$/.test(value); } function integerMetric(metrics, key) { return Number(metrics.get(key)); } function percentageMetric(metrics, key) { return Number(metrics.get(key).slice(0, -1)); } function expectedPercentage(part, total) { return total <= 0 ? '0.0%' : `${(Math.round((part * 1000) / total) / 10).toFixed(1)}%`; } function expectedAverage(scores) { if (!scores.length) return '0.0'; const total = scores.reduce((sum, score) => sum + score, 0); return (Math.round((total * 10) / scores.length) / 10).toFixed(1); } function expectMetric(metrics, key, expected) { if (metrics.get(key) !== expected) fail(`metric ${key}=${metrics.get(key)}, expected ${expected}`); } export function verifyPilotExportContract({ csv, startDate, endDate, contentDisposition }) { const rows = parseCsv(csv); const blankRow = rows.findIndex((row) => row.length === 1 && row[0] === ''); if (blankRow < 0) fail('missing summary/detail separator'); const summaryRows = rows.slice(0, blankRow); const detailRows = rows.slice(blankRow + 1).filter((row) => row.some((cell) => cell !== '')); if (summaryRows.length < 2 || summaryRows[0].length !== 2 || summaryRows[0][0] !== 'metric' || summaryRows[0][1] !== 'value') { fail('missing metric,value header'); } const metrics = new Map(); for (const row of summaryRows.slice(1)) { if (row.length !== 2 || !row[0]) fail('invalid metric row'); if (metrics.has(row[0])) fail(`duplicate metric ${row[0]}`); metrics.set(row[0], row[1]); } for (const key of REQUIRED_METRICS) { if (!metrics.has(key)) fail(`missing metric ${key}`); } const exactValues = new Map([ ['pilot_start_date', startDate], ['pilot_end_date', endDate], ['data_scope', 'active_org_unique_identity'], ['scenario_count_target', '12'], ['formal_pilot_people_target', '20'], ['formal_pilot_project_target', '1-2'], ['completion_definition', 'at_least_10_sessions_per_person'], ['completion_rate_target', '80.0%'], ['calibration_sample_target', '20'], ['ai_human_band_consistency_target', '70.0%'], ['sop_answer_usable_target', '80.0%'] ]); for (const [key, expected] of exactValues) { if (metrics.get(key) !== expected) fail(`metric ${key}=${metrics.get(key)}, expected ${expected}`); } for (const key of INTEGER_METRICS) { if (!isInteger(metrics.get(key))) fail(`metric ${key} must be a non-negative integer`); } if (!/^\d+(?:\.\d+)?$/.test(metrics.get('average_score'))) { fail('metric average_score must be numeric'); } for (const key of PERCENTAGE_METRICS) { if (!isPercentage(metrics.get(key))) fail(`metric ${key} must be a percentage`); } for (const key of BOOLEAN_METRICS) { if (!['true', 'false'].includes(metrics.get(key))) fail(`metric ${key} must be true or false`); } for (const key of PERCENTAGE_METRICS) { const percentage = percentageMetric(metrics, key); if (percentage < 0 || percentage > 100) fail(`metric ${key} must be between 0% and 100%`); } const formalPeople = integerMetric(metrics, 'formal_pilot_people_count'); const completionTotal = integerMetric(metrics, 'completion_people_total'); const completedPeople = integerMetric(metrics, 'completion_people_count'); const projects = integerMetric(metrics, 'formal_pilot_project_count'); const orgIdentityCount = integerMetric(metrics, 'formal_org_identity_count'); if (completionTotal !== formalPeople) { fail(`completion_people_total ${completionTotal} does not match formal_pilot_people_count ${formalPeople}`); } if (completedPeople > completionTotal) fail('completion_people_count exceeds completion_people_total'); const scopeReady = orgIdentityCount >= 20 && formalPeople >= 20 && projects >= 1 && projects <= 2; expectMetric(metrics, 'formal_pilot_scope_ready', String(scopeReady)); expectMetric(metrics, 'completion_rate', expectedPercentage(completedPeople, completionTotal)); const completionReady = scopeReady && completedPeople * 100 >= completionTotal * 80; expectMetric(metrics, 'completion_rate_ready', String(completionReady)); const calibrationSamples = integerMetric(metrics, 'calibration_sample_count'); const calibrationMatched = integerMetric(metrics, 'ai_human_band_matched_count'); if (calibrationMatched > calibrationSamples) fail('ai_human_band_matched_count exceeds calibration_sample_count'); expectMetric(metrics, 'ai_human_band_consistency_rate', expectedPercentage(calibrationMatched, calibrationSamples)); const calibrationReady = calibrationSamples >= 20 && calibrationMatched * 100 >= calibrationSamples * 70; expectMetric(metrics, 'calibration_sample_ready', String(calibrationSamples >= 20)); expectMetric(metrics, 'ai_human_band_consistency_ready', String(calibrationReady)); const sopSamples = integerMetric(metrics, 'sop_review_sample_count'); const sopUsable = integerMetric(metrics, 'sop_answer_usable_count'); if (sopUsable > sopSamples) fail('sop_answer_usable_count exceeds sop_review_sample_count'); expectMetric(metrics, 'sop_answer_usable_rate', expectedPercentage(sopUsable, sopSamples)); const sopReady = sopSamples > 0 && sopUsable * 100 >= sopSamples * 80; expectMetric(metrics, 'sop_answer_usable_ready', String(sopReady)); const pilotReady = integerMetric(metrics, 'scenario_count') >= 12 && completionReady && calibrationReady && sopReady; expectMetric(metrics, 'pilot_metric_ready', String(pilotReady)); if (!detailRows.length || detailRows[0].length !== DETAIL_HEADERS.length || detailRows[0].some((header, index) => header !== DETAIL_HEADERS[index])) { fail('missing formal detail header'); } const detailDataRows = detailRows.slice(1); if (Number(metrics.get('training_count')) !== detailDataRows.length) { fail(`training_count ${metrics.get('training_count')} does not match detail rows ${detailDataRows.length}`); } const scores = []; let reviewed = 0; let alerts = 0; for (const row of detailDataRows) { if (row.length !== DETAIL_HEADERS.length) fail(`detail row has ${row.length} columns`); if (!row[0] || !row[1] || !row[2]) fail('detail row is missing formal identity, project, or session id'); const finishedDate = row[7]?.slice(0, 10); if (!/^\d{4}-\d{2}-\d{2}$/.test(finishedDate) || finishedDate < startDate || finishedDate > endDate) { fail(`detail finished_time ${row[7] || ''} is outside requested window`); } const score = Number(row[5]); if (!Number.isFinite(score) || score < 0 || score > 100) fail(`detail score ${row[5] || ''} is invalid`); if (Number(row[11]) !== score) fail(`detail ai_score ${row[11] || ''} does not match score ${row[5]}`); const calibrationOriginal = row[12] === '' ? null : Number(row[12]); const humanScore = row[13] === '' ? null : Number(row[13]); for (const [label, value] of [['calibration_original_score', calibrationOriginal], ['human_score', humanScore]]) { if (value !== null && (!Number.isFinite(value) || value < 0 || value > 100)) { fail(`detail ${label} ${value} is invalid`); } } const finalAdopted = Number(row[16]); if (!Number.isFinite(finalAdopted) || finalAdopted < 0 || finalAdopted > 100) { fail(`detail final_adopted_score ${row[16] || ''} is invalid`); } if (humanScore === null) { if (row[14] || row[15] || finalAdopted !== score) fail('detail without human score has inconsistent calibration evidence'); } else if (!row[14] || !row[15] || finalAdopted !== humanScore) { fail('detail human score is missing reviewer/time or final adopted score'); } scores.push(score); if (row[6] === '已复盘') reviewed += 1; if (score < 80 || row[6] === '待复盘') alerts += 1; } expectMetric(metrics, 'average_score', expectedAverage(scores)); expectMetric(metrics, 'alert_count', String(alerts)); expectMetric(metrics, 'review_rate', expectedPercentage(reviewed, detailDataRows.length)); const expectedFilename = `filename=aihr-practice-pilot-${startDate}-${endDate}.csv`; if (!String(contentDisposition || '').toLowerCase().includes(expectedFilename)) { fail(`Content-Disposition is missing ${expectedFilename}`); } return { metrics, detailRows: detailDataRows }; }