146 lines
6.0 KiB
JavaScript
146 lines
6.0 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import { parseCsv, verifyPilotExportContract } from '../lib/pilot-export-contract.mjs';
|
|
|
|
const startDate = '2026-07-07';
|
|
const endDate = '2026-07-10';
|
|
|
|
function validCsv() {
|
|
const metrics = {
|
|
pilot_start_date: startDate,
|
|
pilot_end_date: endDate,
|
|
data_scope: 'active_org_unique_identity',
|
|
scenario_count: '12',
|
|
scenario_count_target: '12',
|
|
formal_org_login_count: '20',
|
|
formal_pilot_people_count: '20',
|
|
formal_pilot_people_target: '20',
|
|
formal_pilot_project_count: '1',
|
|
formal_pilot_project_target: '1-2',
|
|
formal_pilot_scope_ready: 'true',
|
|
training_count: '1',
|
|
completion_definition: 'at_least_10_sessions_per_person',
|
|
completion_people_count: '1',
|
|
completion_people_total: '20',
|
|
completion_rate: '5.0%',
|
|
completion_rate_target: '80.0%',
|
|
completion_rate_ready: 'false',
|
|
average_score: '86.0',
|
|
alert_count: '0',
|
|
review_rate: '100.0%',
|
|
calibration_sample_count: '1',
|
|
ai_human_band_matched_count: '1',
|
|
calibration_sample_target: '20',
|
|
calibration_sample_ready: 'false',
|
|
ai_human_band_consistency_rate: '100.0%',
|
|
ai_human_band_consistency_target: '70.0%',
|
|
ai_human_band_consistency_ready: 'false',
|
|
sop_review_sample_count: '2',
|
|
sop_answer_usable_count: '2',
|
|
sop_answer_usable_rate: '100.0%',
|
|
sop_answer_usable_target: '80.0%',
|
|
sop_answer_usable_ready: 'true',
|
|
pilot_metric_ready: 'false'
|
|
};
|
|
const lines = ['metric,value', ...Object.entries(metrics).map(([key, value]) => `${key},${value}`)];
|
|
lines.push('', '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');
|
|
lines.push('FORMAL-1,P1,session-1,员工甲,投诉处理,86,已复盘,2026-07-08 10:00:00,正式记录,保持回访,3,86,86,78,主管甲,2026-07-08 11:00:00,78');
|
|
return lines.join('\n');
|
|
}
|
|
|
|
test('accepts a complete formal export whose training summary matches its details', () => {
|
|
const result = verifyPilotExportContract({
|
|
csv: validCsv(),
|
|
startDate,
|
|
endDate,
|
|
contentDisposition: 'attachment; filename=aihr-practice-pilot-2026-07-07-2026-07-10.csv'
|
|
});
|
|
|
|
assert.equal(result.metrics.get('training_count'), '1');
|
|
assert.equal(result.detailRows.length, 1);
|
|
});
|
|
|
|
test('rejects a missing metric and a detail row outside the requested date window', () => {
|
|
assert.throws(() => verifyPilotExportContract({
|
|
csv: validCsv().replace('calibration_sample_count,1\n', ''),
|
|
startDate,
|
|
endDate,
|
|
contentDisposition: 'attachment; filename=aihr-practice-pilot-2026-07-07-2026-07-10.csv'
|
|
}), /missing metric calibration_sample_count/);
|
|
|
|
assert.throws(() => verifyPilotExportContract({
|
|
csv: validCsv().replace('2026-07-08 10:00:00', '2026-07-11 10:00:00'),
|
|
startDate,
|
|
endDate,
|
|
contentDisposition: 'attachment; filename=aihr-practice-pilot-2026-07-07-2026-07-10.csv'
|
|
}), /outside requested window/);
|
|
});
|
|
|
|
test('rejects contradictory pilot metrics and incomplete formal details', () => {
|
|
assert.throws(() => verifyPilotExportContract({
|
|
csv: validCsv().replace('completion_people_total,20', 'completion_people_total,19'),
|
|
startDate,
|
|
endDate,
|
|
contentDisposition: 'attachment; filename=aihr-practice-pilot-2026-07-07-2026-07-10.csv'
|
|
}), /completion_people_total/);
|
|
|
|
assert.throws(() => verifyPilotExportContract({
|
|
csv: validCsv().replace('training_count,1', 'training_count,2'),
|
|
startDate,
|
|
endDate,
|
|
contentDisposition: 'attachment; filename=aihr-practice-pilot-2026-07-07-2026-07-10.csv'
|
|
}), /does not match detail rows/);
|
|
|
|
assert.throws(() => verifyPilotExportContract({
|
|
csv: validCsv().replace('FORMAL-1,P1,session-1', ',P1,session-1'),
|
|
startDate,
|
|
endDate,
|
|
contentDisposition: 'attachment; filename=wrong.csv'
|
|
}), /missing formal identity/);
|
|
|
|
assert.throws(() => verifyPilotExportContract({
|
|
csv: validCsv().replace('ai_human_band_consistency_rate,100.0%', 'ai_human_band_consistency_rate,0.0%'),
|
|
startDate,
|
|
endDate,
|
|
contentDisposition: 'attachment; filename=aihr-practice-pilot-2026-07-07-2026-07-10.csv'
|
|
}), /ai_human_band_consistency_rate/);
|
|
});
|
|
|
|
test('parses valid quoted CSV fields and rejects malformed quoting', () => {
|
|
assert.deepEqual(parseCsv('metric,value\nsummary,"逗号,转义""引号""\n第二行"\n'), [
|
|
['metric', 'value'],
|
|
['summary', '逗号,转义"引号"\n第二行']
|
|
]);
|
|
assert.deepEqual(parseCsv('""'), [['']]);
|
|
|
|
assert.throws(() => parseCsv('metric,value\nsummary,"未闭合'), /unclosed quoted field/);
|
|
assert.throws(() => parseCsv('metric,value\nsummary,值"错误'), /quote must start a field/);
|
|
assert.throws(() => parseCsv('metric,value\nsummary,"值"错误'), /unexpected character after closing quote/);
|
|
assert.throws(() => verifyPilotExportContract({
|
|
csv: validCsv().replace('metric,value', '"metric,value"'),
|
|
startDate,
|
|
endDate,
|
|
contentDisposition: 'attachment; filename=aihr-practice-pilot-2026-07-07-2026-07-10.csv'
|
|
}), /missing metric,value header/);
|
|
});
|
|
|
|
test('uses raw numerators for threshold readiness instead of rounded percentages', () => {
|
|
const csv = validCsv()
|
|
.replace('calibration_sample_count,1', 'calibration_sample_count,203')
|
|
.replace('ai_human_band_matched_count,1', 'ai_human_band_matched_count,142')
|
|
.replace('ai_human_band_consistency_rate,100.0%', 'ai_human_band_consistency_rate,70.0%')
|
|
.replace('calibration_sample_ready,false', 'calibration_sample_ready,true')
|
|
.replace('sop_review_sample_count,2', 'sop_review_sample_count,2049')
|
|
.replace('sop_answer_usable_count,2', 'sop_answer_usable_count,1639')
|
|
.replace('sop_answer_usable_rate,100.0%', 'sop_answer_usable_rate,80.0%')
|
|
.replace('sop_answer_usable_ready,true', 'sop_answer_usable_ready,false');
|
|
|
|
assert.doesNotThrow(() => verifyPilotExportContract({
|
|
csv,
|
|
startDate,
|
|
endDate,
|
|
contentDisposition: 'attachment; filename=aihr-practice-pilot-2026-07-07-2026-07-10.csv'
|
|
}));
|
|
});
|