You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

109 lines
4.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 根据 config/lab-task-description.md 与仓库扫描结果生成「实训步骤 / 评测对齐」表(服务端权威,不依赖大模型编造)。
* 可选合并 chat_logs 启发式结果中的 s3/s5。
*/
function heuristicStepDone(heuristicReport, id) {
if (!heuristicReport?.summary?.rubric_steps) return false;
const row = heuristicReport.summary.rubric_steps.find((s) => s.id === id);
return Boolean(row?.done);
}
function pySnippet(scan) {
return scan?.score_analysis_py?.snippet || '';
}
/**
* @param {string} taskMd
* @param {object} scan scanStudentRepo 返回值
* @param {number} fileCount
* @param {string} gitUrl
* @param {object|null} heuristicReport analyzeChatLogsFile 完整结果或 null
*/
function buildRubricFromTaskAndScan(taskMd, scan, fileCount, gitUrl, heuristicReport) {
scan = scan || {};
const taskBrief = (taskMd || '').trim().slice(0, 200).replace(/\s+/g, ' ');
const py = pySnippet(scan);
const hasPy = Boolean(scan?.score_analysis_py);
const hasCsv = Boolean(scan?.scores_csv);
const pngOk = Boolean(scan?.score_chart_png?.exists);
const f = scan?.flags || {};
const rows = [];
rows.push({
id: 'prep',
label: '准备数据 scores.csv',
done: hasCsv,
eval_note: hasCsv
? `任务要求准备成绩表;仓库中已找到 ${scan.scores_csv.path}${scan.scores_csv.bytes} 字节)。${taskBrief ? `任务描述摘要:${taskBrief}` : ''}`
: `任务要求准备 scores.csv 等成绩数据;当前仓库扫描未匹配到典型 CSV请确认是否放在子目录或文件名不同`,
});
rows.push({
id: 's1',
label: '步骤一:打开 Cursor / 新建项目',
done: fileCount > 0,
eval_note:
fileCount > 0
? `已从远程仓库拉取共 ${fileCount} 个文件,说明学员工作区非空,可认为具备开展实训的工程基础。`
: '仓库内未发现文件,无法验证本地 IDE 操作步骤。',
});
const s2Done = hasPy && (f.has_read_csv || f.has_mean);
rows.push({
id: 's2',
label: '步骤二:自然语言生成成绩统计代码',
done: s2Done,
eval_note: hasPy
? `已发现主脚本 ${scan.score_analysis_py.path}${f.has_read_csv ? '含 read_csv/pandas 读取迹象。' : '未从片段中检出 read_csv。'}${f.has_mean ? '含 mean/统计迹象。' : '未从片段中检出 mean。'}`
: '未在仓库中定位 score_analysis.py 或同类分析脚本,无法对齐头歌「脚本含 read_csv、mean」评测项。',
});
const s3FromChat = heuristicStepDone(heuristicReport, 's3');
const s3FromPy = /traceback|exception|raise |except |error|报错/i.test(py);
rows.push({
id: 's3',
label: '步骤三:运行与调试',
done: s3FromChat || s3FromPy,
eval_note: s3FromChat
? '对话日志侧曾出现与排错/修复相关的关键词,记为已体现调试环节。'
: s3FromPy
? '脚本片段中出现异常处理或错误相关关键字,可能经历过排错(弱推断)。'
: '仓库脚本片段与对话均未明确体现「运行—报错—修复」闭环,建议结合终端截图补充。',
});
const pngPath = scan.score_chart_png?.path || 'score_chart.png';
rows.push({
id: 's4',
label: '步骤四matplotlib 柱状图与 score_chart.png',
done: pngOk && f.has_matplotlib,
eval_note: pngOk
? `已发现图表文件 ${pngPath}${scan.score_chart_png.bytes} 字节)。${f.has_matplotlib ? '脚本中含 matplotlib/pyplot 迹象。' : '脚本片段中未检出 matplotlib请人工核对图表是否由其他方式生成。'}`
: `未检出 score_chart.png。任务要求保存柱状图请对照头歌评测「score_chart.png 存在」。${f.has_matplotlib ? '脚本中已有作图代码,可能尚未运行保存。' : ''}`,
});
const s5FromChat = heuristicStepDone(heuristicReport, 's5');
rows.push({
id: 's5',
label: '步骤五:请 AI 解释代码',
done: s5FromChat,
eval_note: s5FromChat
? '对话日志侧出现「解释」等与代码理解相关的提问,记为已体现。'
: '仓库无法直接证明「请 AI 解释」步骤;若学员未导出相关对话,此项可能为否。',
});
rows.push({
id: 's6',
label: '步骤六:保存 score_analysis.py',
done: hasPy,
eval_note: hasPy
? `仓库中存在 ${scan.score_analysis_py.path},满足「提交主脚本」类评测的文件存在性检查。`
: '未找到 score_analysis.py与头歌预期交付物不一致。',
});
return rows;
}
module.exports = { buildRubricFromTaskAndScan };