新),分数有高有低。 */ 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);