- aggregateByStudent 修复:attempts(真实次数) 与 attempts_list(每次提交明细) 无论 dedupe 取值都使用全部提交;dedupe 仅决定主明细表代表成绩取最新 还是最高分。此前 dedupe=true(默认)把 attempts 写成 1、attempts_list 只留 最新一份,导致考试次数列和点击历史都只显示 1 条,多次刷题数据丢失。 - best_score 改为跨全部尝试的真实最高分,修复历史弹窗标题最高分错误。 - 新增 tests/test-aggregate.php 覆盖:11次/2次/1次提交、dedupe 两种取值、 重考低分、导出展开全部记录,共 17 项断言全部通过。 数据印证:第二周测试(1).xlsx 共 146 份提交 / 35 人,31 人多次提交 (最多 11 次),修复后考试次数与历史均完整呈现。
95 lines
4.6 KiB
PHP
95 lines
4.6 KiB
PHP
<?php
|
|
/**
|
|
* 验证 aggregateByStudent 的"考试次数 / 历史明细"修复:
|
|
* 多次提交的学生在 dedupe=true(默认)与 dedupe=false 两种情况下,
|
|
* attempts 都应等于真实提交次数、attempts_list 都应包含全部提交;
|
|
* dedupe 仅影响"代表成绩"那一行取最新或最高分。
|
|
*/
|
|
require __DIR__ . '/../bootstrap.php';
|
|
|
|
$fail = 0;
|
|
$pass = 0;
|
|
function check(string $name, bool $cond, string $detail = ''): void
|
|
{
|
|
global $fail, $pass;
|
|
if ($cond) {
|
|
$pass++;
|
|
echo " ✅ {$name}\n";
|
|
} else {
|
|
$fail++;
|
|
echo " ❌ {$name} {$detail}\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 构造一个学生的多次提交(模拟 第二周测试 中"陈苏豫 11次"这类数据)
|
|
* 提交时间递增(旧 -> 新),分数有高有低。
|
|
*/
|
|
function makeRows(string $sid, array $scoresByTime): array
|
|
{
|
|
$rows = [];
|
|
$t = 1000; // 递增时间戳
|
|
foreach ($scoresByTime as $sc) {
|
|
$t += 600;
|
|
$rows[] = [
|
|
'sid' => $sid,
|
|
'name' => '测试生',
|
|
'cls' => '1班',
|
|
'grp' => 'A',
|
|
'score' => $sc,
|
|
'submit_ts' => $t,
|
|
'submit_at' => date('Y-m-d H:i:s', $t),
|
|
'duration' => 120,
|
|
'duration_txt'=> '2分0秒',
|
|
'answer_id' => 'ans-' . $t,
|
|
'matched' => true,
|
|
'temp_save' => false,
|
|
];
|
|
}
|
|
return $rows;
|
|
}
|
|
|
|
echo "== dedupe=true(默认:代表成绩取最新一份)==\n";
|
|
// 11 次提交:分数各不相同,最新一份是 84.0,最高是 98.0
|
|
$scores = [84.0, 98.0, 96.0, 96.0, 96.0, 94.0, 94.0, 96.0, 90.0, 88.0, 98.0];
|
|
$rows = makeRows('S001', $scores);
|
|
$agg = DataService::aggregateByStudent($rows, true);
|
|
check('聚合后只有 1 行(一人一行)', count($agg['rows']) === 1);
|
|
$r = $agg['rows'][0];
|
|
check('attempts = 11(真实次数,而非被吞成 1)', ($r['attempts'] ?? 0) === 11, 'got ' . var_export($r['attempts'] ?? null, true));
|
|
check('attempts_list 含 11 条(历史可展开全部)', count($r['attempts_list'] ?? []) === 11, 'got ' . count($r['attempts_list'] ?? []));
|
|
check('best_score = 98.0(跨全部尝试的真实最高分)', (float)($r['best_score'] ?? -1) === 98.0, 'got ' . var_export($r['best_score'] ?? null, true));
|
|
// 代表成绩 = 最新一份(数组中最后一个时间 = 98.0,因为 scores 数组最后一个是 98.0;注意最新提交对应最后构造的 98.0)
|
|
check('代表成绩取最新提交 = 98.0', (float)($r['score'] ?? -1) === 98.0, 'got ' . var_export($r['score'] ?? null, true));
|
|
check('attempts_list 项数 idx 连续 1..11', (int)($r['attempts_list'][10]['idx'] ?? 0) === 11);
|
|
check('attempts_list 至少一条 is_best(最高分标记)', count(array_filter($r['attempts_list'], fn($a) => !empty($a['is_best']))) >= 1);
|
|
|
|
echo "\n== dedupe=false(代表成绩取最高分)==\n";
|
|
$agg2 = DataService::aggregateByStudent($rows, false);
|
|
$r2 = $agg2['rows'][0];
|
|
check('attempts = 11(与 dedupe 无关,始终真实)', ($r2['attempts'] ?? 0) === 11, 'got ' . var_export($r2['attempts'] ?? null, true));
|
|
check('attempts_list 含 11 条', count($r2['attempts_list'] ?? []) === 11, 'got ' . count($r2['attempts_list'] ?? []));
|
|
check('代表成绩取最高分 = 98.0', (float)($r2['score'] ?? -1) === 98.0, 'got ' . var_export($r2['score'] ?? null, true));
|
|
check('best_score = 98.0', (float)($r2['best_score'] ?? -1) === 98.0, 'got ' . var_export($r2['best_score'] ?? null, true));
|
|
|
|
echo "\n== 重考低分也应被统计(历史含低分,且因提交时间更新能被发现)==\n";
|
|
// 先考 90,后重考 70(更低):考试次数必须=2,最新提交=70
|
|
$rows3 = makeRows('S002', [90.0, 70.0]);
|
|
$agg3 = DataService::aggregateByStudent($rows3, true);
|
|
$r3 = $agg3['rows'][0];
|
|
check('attempts = 2', ($r3['attempts'] ?? 0) === 2, 'got ' . var_export($r3['attempts'] ?? null, true));
|
|
check('attempts_list = 2 条(含低分重考)', count($r3['attempts_list'] ?? []) === 2);
|
|
check('代表成绩取最新 = 70.0', (float)($r3['score'] ?? -1) === 70.0, 'got ' . var_export($r3['score'] ?? null, true));
|
|
check('last_submit_ts 指向最新提交(重考可被高亮发现)', ($r3['last_submit_ts'] ?? 0) === $rows3[1]['submit_ts']);
|
|
|
|
echo "\n== 仅 1 次提交不受影响 ==\n";
|
|
$rows4 = makeRows('S003', [85.0]);
|
|
$agg4 = DataService::aggregateByStudent($rows4, true);
|
|
$r4 = $agg4['rows'][0];
|
|
check('单次提交 attempts = 1', ($r4['attempts'] ?? 0) === 1);
|
|
check('单次提交 attempts_list = 1', count($r4['attempts_list'] ?? []) === 1);
|
|
|
|
echo "\n========================================\n";
|
|
echo "通过 {$pass} · 失败 {$fail}\n";
|
|
exit($fail === 0 ? 0 : 1);
|