import { ExamScore } from '../entities/exam-score.entity';
import { esc, sectionFrame, sectionHeader } from './archive-report.helpers';
export function buildExamOverview(exams: ExamScore[], now: string): string {
if (exams.length === 0) return '';
const cultureExams = exams.filter(
(e) => e.examType && e.examType.includes('文化'),
);
const entranceExam = exams.find((e) => e.examType === '入学测试');
const highestExam = [...exams].sort((a, b) => (b.score ?? 0) - (a.score ?? 0))[0];
const entranceScore = entranceExam?.score?.toFixed(1) ?? '-';
const highestScore = highestExam?.score?.toFixed(1) ?? '-';
const highestName = highestExam?.examName ?? '-';
// Improvement: last exam score minus first exam score
const sortedScores = cultureExams
.map((exam) => exam.score)
.filter((score): score is number => score !== null && score !== undefined);
let improvement = '—';
if (sortedScores.length >= 2) {
const first = sortedScores[0];
const last = sortedScores[sortedScores.length - 1];
improvement = (last - first).toFixed(1);
}
const avgScore =
cultureExams.length > 0
? (
cultureExams.reduce((sum, e) => sum + (e.score ?? 0), 0) /
cultureExams.length
).toFixed(1)
: '-';
const metricHtml = `
${esc(now)} · 系统生成
考试成绩总览
入学测试成绩
${esc(entranceScore)}
入学摸底测试
最高分
${esc(highestScore)}
${esc(highestName)}
进步幅度
${esc(improvement)}
首考 → 末考变化
平均分
${esc(avgScore)}
文化课考试均分
`;
const scoreTable = renderScoreTable(cultureExams);
const trendChart = renderScoreTrendChart(cultureExams);
return sectionFrame(`
${sectionHeader('考试成绩总览')}
${metricHtml}
${scoreTable}
${trendChart}
`);
}
export function renderScoreTable(exams: ExamScore[]): string {
if (exams.length === 0) return '';
return `
文化课考试成绩
| 类型 | 名称 | 科目 |
分数 | 班均 | 排名 | 日期 |
${exams
.map(
(e) =>
`
| ${esc(e.examType || '-')} |
${esc(e.examName || '-')} |
${esc(e.subject || '-')} |
${e.score != null ? e.score : '-'} |
${e.classAvg != null ? e.classAvg : '-'} |
${e.rank != null ? e.rank : '-'} |
${esc(e.examDate || '-')} |
`,
)
.join('')}
`;
}
export function renderScoreTrendChart(exams: ExamScore[]): string {
const cultureExams = exams.filter((e) => e.score != null);
if (cultureExams.length === 0) return '';
const scores = cultureExams.map((e) => Number(e.score));
const labels = cultureExams.map((e) => {
const d = e.examDate || '-';
return d.length > 7 ? d.slice(5) : d;
});
const w = 600;
const h = 180;
const pad = { top: 20, right: 20, bottom: 30, left: 40 };
const plotW = w - pad.left - pad.right;
const plotH = h - pad.top - pad.bottom;
const minScore = Math.min(...scores);
const maxScore = Math.max(...scores);
const scoreRange = maxScore - minScore || 1;
const scaleY = (s: number): number =>
pad.top + plotH - ((s - minScore) / scoreRange) * plotH;
let points = '';
let lines = '';
for (let i = 0; i < scores.length; i++) {
const x = pad.left + (i / Math.max(scores.length - 1, 1)) * plotW;
const y = scaleY(scores[i]);
points += ``;
if (i > 0) {
const px = pad.left + ((i - 1) / Math.max(scores.length - 1, 1)) * plotW;
const py = scaleY(scores[i - 1]);
lines += ``;
}
}
// Y-axis labels
const ySteps = 4;
let yLabels = '';
for (let i = 0; i <= ySteps; i++) {
const val = minScore + (scoreRange * i) / ySteps;
const y = scaleY(val);
yLabels += `${val.toFixed(0)}`;
if (i > 0) {
yLabels += ``;
}
}
// X-axis labels
let xLabels = '';
const labelStep = Math.max(1, Math.floor(labels.length / 6));
for (let i = 0; i < labels.length; i += labelStep) {
const x = pad.left + (i / Math.max(scores.length - 1, 1)) * plotW;
xLabels += `${esc(labels[i])}`;
}
return `
成绩趋势
趋势图展示文化课考试成绩的变化轨迹,点数代每次考试的分数
`;
}
export function buildExamDetail(exams: ExamScore[], now: string): string {
const cultureExams = exams.filter(
(e) => e.examType && e.examType.includes('文化'),
);
if (cultureExams.length === 0) return '';
// Group by subject
const subjectMap = new Map();
for (const e of cultureExams) {
const subject = e.subject || '其他';
const existing = subjectMap.get(subject) ?? [];
existing.push(e);
subjectMap.set(subject, existing);
}
let subjectCards = '';
for (const [subject, subExams] of subjectMap) {
const best = Math.max(...subExams.map((e) => e.score ?? 0));
const avg = (
subExams.reduce((sum, e) => sum + (e.score ?? 0), 0) / subExams.length
).toFixed(1);
let rows = '';
for (const e of subExams) {
rows += `
| ${esc(e.examName || '-')} |
${e.score != null ? e.score : '-'} |
${e.classAvg != null ? e.classAvg : '-'} |
${e.rank != null ? e.rank : '-'} |
${esc(e.examDate || '-')} |
`;
}
subjectCards += `
${esc(subject)} · 最佳 ${best} · 均分 ${esc(avg)}
`;
}
return sectionFrame(`
${sectionHeader('文化课考试成绩')}
${esc(now)} · 系统生成
文化课考试成绩
${subjectCards}
`);
}