|
|
/**
|
|
|
* 结合 lab-task-description、仓库扫描与「实训步骤 / 评测对齐」表生成学习建议,
|
|
|
* 与 evaluation.learning 合并(任务侧证据优先),供「学习建议(结合本实训)」区块使用。
|
|
|
*/
|
|
|
|
|
|
function buildLearningSectionHint(report) {
|
|
|
const src = report?.source || '';
|
|
|
const base =
|
|
|
'以下建议依据 config/lab-task-description.md、远程仓库脚本/数据/图表扫描与「实训步骤 / 评测对齐」表,并与对话日志信号交叉核验后生成。';
|
|
|
if (src === 'heuristic_fast') {
|
|
|
return (
|
|
|
base +
|
|
|
' 当前为快速模式(?fast=1):以可机器核验的结论为主;若需更长文深度点评,可去掉 URL 中的 fast 并配置 CURSOR_API_KEY。'
|
|
|
);
|
|
|
}
|
|
|
if (src === 'heuristic_only') {
|
|
|
return base + ' 当前未调用整页大模型;建议配置 CURSOR_API_KEY 以生成更细致的学业与工具使用分析。';
|
|
|
}
|
|
|
if (src === 'ai_full') {
|
|
|
return base + ' 已与整页大模型给出的建议合并去重;前列条目优先对应本实训交付物与扫描证据。';
|
|
|
}
|
|
|
if (src === 'heuristic_fallback') {
|
|
|
return base + ' 整页大模型生成失败,以下为启发式与仓库扫描合并结果。';
|
|
|
}
|
|
|
return base;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @param {object} ctx loadStudentRepoContext 返回值
|
|
|
* @param {object} report 报告 JSON(需含 summary.rubric_steps、evaluation.meta 等)
|
|
|
* @returns {string[]}
|
|
|
*/
|
|
|
function buildTaskAlignedLearningList(ctx, report) {
|
|
|
const scan = ctx?.scan || {};
|
|
|
const flags = scan.flags || {};
|
|
|
const steps = ctx?.rubric_steps || report?.summary?.rubric_steps || [];
|
|
|
const sig = report?.evaluation?.meta?.evaluation_signals || {};
|
|
|
const hookN = report?.summary?.hook_event_count ?? ctx?.heuristic?.summary?.hook_event_count ?? 0;
|
|
|
const convN = report?.summary?.conversation_count ?? ctx?.heuristic?.summary?.conversation_count ?? 0;
|
|
|
const winN =
|
|
|
(typeof sig.transcript_window_count === 'number' ? sig.transcript_window_count : null) ??
|
|
|
convN;
|
|
|
const labPct = report?.summary?.lab_progress_percent ?? ctx?.labPct ?? 0;
|
|
|
const tips = [];
|
|
|
const seen = new Set();
|
|
|
const add = (s) => {
|
|
|
const t = String(s).trim();
|
|
|
if (!t || seen.has(t)) return;
|
|
|
seen.add(t);
|
|
|
tips.push(t);
|
|
|
};
|
|
|
|
|
|
add(
|
|
|
'【实训目标】读取 scores.csv,用 pandas 完成个人/全班统计并在终端输出;用 matplotlib 绘制全班各科平均分柱状图并保存 score_chart.png;运行报错时附上 Traceback 与工作目录再请 AI 协助。'
|
|
|
);
|
|
|
|
|
|
for (const s of steps) {
|
|
|
if (s.done) continue;
|
|
|
const id = s.id;
|
|
|
if (id === 'prep') {
|
|
|
add(
|
|
|
'【数据交付】未稳定检出 scores.csv:请按头歌路径提交成绩表,代码中路径与评测工作区一致(如与任务说明中的 /data/workspace/myshixun/ 对齐)。'
|
|
|
);
|
|
|
} else if (id === 's1') {
|
|
|
add('【仓库】检出文件很少:请确认已 push 代码与数据,或检查报告 URL 是否指向正确学员仓库。');
|
|
|
} else if (id === 's2') {
|
|
|
add(
|
|
|
'【统计脚本】未完成或未检出 read_csv/均值:在 score_analysis.py 中使用 pandas.read_csv 与 mean 等统计,运行后核对终端输出是否与预期一致。'
|
|
|
);
|
|
|
} else if (id === 's3') {
|
|
|
add(
|
|
|
'【运行调试】未体现运行—报错—修复闭环:请在终端运行脚本,出错时把完整 Traceback、当前目录与相关文件名写进对话再问 AI。'
|
|
|
);
|
|
|
} else if (id === 's4') {
|
|
|
add(
|
|
|
'【可视化】未检出 score_chart.png 或作图未完成:用 matplotlib 柱状图展示全班各科平均分,plt.savefig("score_chart.png") 后提交图片文件。'
|
|
|
);
|
|
|
} else if (id === 's5') {
|
|
|
add('【理解代码】请选择核心统计与作图片段请 AI 解释数据流与参数含义,避免只生成代码不复盘。');
|
|
|
} else if (id === 's6') {
|
|
|
add('【提交脚本】请保存并提交 score_analysis.py 到远程仓库,确保自动评测能检出主程序。');
|
|
|
} else {
|
|
|
const note = (s.eval_note || '').trim();
|
|
|
add(note ? `【${s.label}】${note.slice(0, 200)}${note.length > 200 ? '…' : ''}` : `【${s.label}】对照任务补全该环节。`);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (scan.score_analysis_py && !flags.has_read_csv) {
|
|
|
add('【代码扫描】脚本片段未出现 read_csv:请显式使用 pandas.read_csv 指向你的 scores.csv 相对路径。');
|
|
|
}
|
|
|
if (scan.score_analysis_py && !flags.has_mean) {
|
|
|
add('【代码扫描】未识别均值/汇总:请按任务输出各科或全班平均,并用 print 自查结果。');
|
|
|
}
|
|
|
if (scan.score_analysis_py && !flags.has_matplotlib) {
|
|
|
add('【代码扫描】未识别 matplotlib:请增加柱状图与 savefig,生成评测所需的 score_chart.png。');
|
|
|
}
|
|
|
if (!scan.score_chart_png?.exists && flags.has_matplotlib) {
|
|
|
add('【产出文件】脚本含作图但未检出 png:请在本地运行脚本生成 score_chart.png 并加入仓库提交。');
|
|
|
}
|
|
|
|
|
|
if (
|
|
|
typeof sig.transcript_window_count === 'number' &&
|
|
|
typeof sig.conversation_bucket_count === 'number' &&
|
|
|
sig.conversation_bucket_count > sig.transcript_window_count
|
|
|
) {
|
|
|
add(
|
|
|
'【上下文】多条日志会话已按同一 Cursor transcript_path 合并;学习建议与作业评价以合并后的聊天窗口口径统计交互,与页面「对话与提问」一致。'
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (sig.heuristic_untrustworthy) {
|
|
|
add(
|
|
|
'【对话方式】日志多为长文单次粘贴,不利于体现个人推敲过程;请拆成小步提问,每轮附带路径、期望输出或报错。'
|
|
|
);
|
|
|
} else if (winN >= 1 && hookN > 0 && hookN < 4) {
|
|
|
add('【对话方式】交互轮次偏少:可分阶段验证「读表 → 统计 → 作图」,每轮让模型看到上一轮代码或输出。');
|
|
|
} else if (winN >= 1 && hookN >= 16) {
|
|
|
add('【对话方式】与模型交互较频:注意每轮目标单一、写清约束,避免同轮混杂无关需求导致跑偏。');
|
|
|
}
|
|
|
|
|
|
if (labPct >= 85) {
|
|
|
add('【巩固】交付物较齐:可为图表加标题与坐标轴标签,并对照头歌评测脚本再做一次本地自检。');
|
|
|
}
|
|
|
|
|
|
return tips;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 将任务侧学习建议与已有 evaluation.learning 合并(去重,任务生成的条目前置)。
|
|
|
*/
|
|
|
function mergeLearningWithTaskEvidence(ctx, report) {
|
|
|
if (!ctx || !report?.evaluation) return;
|
|
|
const taskTips = buildTaskAlignedLearningList(ctx, report);
|
|
|
const existing = Array.isArray(report.evaluation.learning) ? report.evaluation.learning : [];
|
|
|
const seen = new Set();
|
|
|
const out = [];
|
|
|
for (const x of [...taskTips, ...existing]) {
|
|
|
const t = String(x).trim();
|
|
|
if (!t || seen.has(t)) continue;
|
|
|
seen.add(t);
|
|
|
out.push(t);
|
|
|
}
|
|
|
report.evaluation.learning = out.slice(0, 16);
|
|
|
report.ui = report.ui || {};
|
|
|
report.ui.learning_section_hint = buildLearningSectionHint(report);
|
|
|
}
|
|
|
|
|
|
module.exports = {
|
|
|
buildTaskAlignedLearningList,
|
|
|
mergeLearningWithTaskEvidence,
|
|
|
buildLearningSectionHint,
|
|
|
};
|