From ffac1abef382b1ddabbe7c13371bdd9b10a5eba4 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Tue, 13 Oct 2026 09:19:50 +0000 Subject: [PATCH 1/5] feat(contests): add AALC contest type for aalc001 Classify aalc001 as AALC, rank it directly below GAME_24, label it with the official title, and seed its 24 tasks. Co-Authored-By: Claude Opus 5.5 --- .../add-aalc001-contest-table/plan.md | 89 ++++++++++ .../migration.sql | 2 + prisma/schema.prisma | 1 + prisma/tasks.ts | 168 ++++++++++++++++++ .../contests/fixtures/contest_name_labels.ts | 7 + src/lib/contests/fixtures/contest_type.ts | 7 + src/lib/contests/types/contest.ts | 1 + src/lib/contests/utils/classification.test.ts | 8 + src/lib/contests/utils/classification.ts | 1 + src/lib/contests/utils/labels/index.test.ts | 8 + src/lib/contests/utils/labels/index.ts | 1 + src/lib/contests/utils/priority.test.ts | 4 + src/lib/contests/utils/priority.ts | 21 +-- 13 files changed, 308 insertions(+), 10 deletions(-) create mode 100644 docs/dev-notes/2026-10-12/add-aalc001-contest-table/plan.md create mode 100644 prisma/migrations/20261013091828_add_aalc_to_contest_type/migration.sql diff --git a/docs/dev-notes/2026-10-12/add-aalc001-contest-table/plan.md b/docs/dev-notes/2026-10-12/add-aalc001-contest-table/plan.md new file mode 100644 index 000000000..b28261da5 --- /dev/null +++ b/docs/dev-notes/2026-10-12/add-aalc001-contest-table/plan.md @@ -0,0 +1,89 @@ +# AAL Contest 001(aalc001)の追加 + +## 概要 + +Issue #3974: [AAL Contest 001: Let's use segtree!](https://atcoder.jp/contests/aalc001)(contest_id `aalc001`、A〜X の 24 問)を ContestType `AALC` として追加し、xDPC グループの「組合せゲーム 24 題」の後に表示する。 + +参考: `.agents/skills/add-contest-table-provider/`、Game 24 追加時の PR #4120(plan は `git show 7fb10b8c:docs/dev-notes/2026-10-06/add-game-24-contest-table/plan.md`)。 + +## 前提となる事実 + +- **task_id は `aalc001_a`〜`aalc001_x`:** contest_id に `_` と小文字の問題記号を付けた一般的な形式。 +- **問題データの入手:** エージェント環境からは atcoder.jp を取得できない(curl は拒否)。seed の 24 行は、ユーザーが貼った一覧から作る。 +- **既存の分類ルールとの衝突はない:** `prefixes.ts` に `a` で始まる接頭辞はなく、`/^abc\d{3}$/` などの正規表現にも当たらない。 +- **本番への反映:** マイグレーションは main への push 時に CI が `prisma migrate deploy` で適用する。問題データは反映後に管理画面 `/tasks` からインポートする(AtCoder Problems に aalc001 が反映されている必要がある)。 + +## 決定事項 + +| 項目 | 決定 | 理由・補足 | +| ------------------ | ------------------------------------------------------ | ----------------------------------------------------------------------------- | +| ContestType | `AALC` | ユーザー指定 | +| 位置 | enum・priority ともに `GAME_24` の直後 | ユーザー指定。priority は 21 で、`ATCODER_MAIN_OFFICIAL_ONSITE` 以降は +1 | +| 表示名 | `AAL Contest 001: Let's use segtree!` | ユーザー指定。名前ラベル(`LABEL_GENERATORS`)とテーブルのタイトルで共通 | +| enum コメント | `AAL Contest 001: Let's use segtree!` | 他の enum コメントと同様に英語の正式名 | +| 分類 | `CONTEST_TYPES_BY_ID` に `aalc001` を完全一致で追加 | aalc001 だけ扱う(ユーザー選択)。002 以降が出たら接頭辞ルールに拡張する | +| Provider | `aalc_provider.ts` に `AALCProvider` を作る | Game24Provider をクラスごと複製(既存の単一コンテスト Provider の慣例) | +| `abbreviationName` | `aalc001` | contest_id と同じ(game-24・fps-24 と同じ方針) | +| 表示設定 | Game 24 と同じ | これまでの 24 問コンテストとスタイルを揃える | +| グループ | `dps`(`xDPC・x 24 題`)の末尾に追加。名前・キーは不変 | AALC も 24 問なので「x 24 題」に含める(ユーザー選択)。localStorage キー維持 | +| seed の grade | 付けない | game-24・fps-24 と同様に PENDING | +| E2E | `XDPC_GROUP_TABLE_TITLES` の末尾に 1 件追加 | 見出しの並びを検証する既存テストを拡張するだけで足りる | +| コミット | plan / Layer 1–3 と seed / Layer 4–5 と E2E | 1 PR・3 コミット(#4120 と同じ) | + +## 主なシグネチャ + +```typescript +// src/features/tasks/utils/contest-table/aalc_provider.ts +export class AALCProvider extends ContestTableProviderBase { + protected setFilterCondition(): (taskResult: TaskResult) => boolean; // classifyContest === AALC && contest_id === 'aalc001' + getMetadata(): ContestTableMetaData; // { title: "AAL Contest 001: Let's use segtree!", abbreviationName: 'aalc001' } + getDisplayConfig(): ContestTableDisplayConfig; // Game24Provider と同じ値 + getContestRoundLabel(_contestId: string): string; // '' +} +``` + +## 見送り案 + +- **`aalc` の接頭辞で分類する:** 現時点で 002 以降の予定はなく、YAGNI。出たときに Pattern 1 か 4 で拡張する。 +- **表示名を短くする(`AALC 001` / `セグ木 24 題`):** ユーザーが正式名を選んだ。 +- **グループ名を変える:** プリセットキー・aria-label・E2E に波及するうえ、「x 24 題」に収まる。 +- **単一 contest_id 用の共通基底クラスを抽出する:** EDPC〜Game 24 の 5 クラスに波及し、本タスクの範囲を超える(#4120 と同じ判断)。 + +## フェーズ(低リスク → 高リスク) + +### Phase 1: ContestType の追加(Layer 1–2) + +- `prisma/schema.prisma` の enum で `GAME_24` の直後に `AALC` を追加 → `pnpm exec prisma migrate dev --name add_aalc_to_contest_type` +- `src/lib/contests/types/contest.ts` に同じ位置で追加 +- `pnpm check` + +### Phase 2: 分類・priority・ラベル(Layer 3、TDD) + +- テスト先行 + - `fixtures/contest_type.ts` に `aalc001`(expected `ContestType.AALC`)、`fixtures/contest_name_labels.ts` に `aalc001`(expected `AAL Contest 001: Let's use segtree!`) + - `classification.test.ts` / `labels/index.test.ts` に describe を追加 + - `priority.test.ts` に `ranks AALC directly below GAME_24` + - RED を確認 +- 実装 + - `CONTEST_TYPES_BY_ID` に `['aalc001', ContestType.AALC]` + - `contestTypePriorities` に `[ContestType.AALC, 21]`、後続を +1、JSDoc の範囲を更新(0 = Highest, 28 = Lowest / Special 18-23 / External 24-28) + - `LABEL_GENERATORS` に `[ContestType.AALC, () => "AAL Contest 001: Let's use segtree!"]` + - GREEN。`src/test/lib/utils/task.test.ts` は priority の差分の期待値に後続種別を使っていないので変更不要の見込み(実行して確認) +- `prisma/tasks.ts` に 24 行を追加(貼られた一覧どおり) + +### Phase 3: Provider とグループ登録(Layer 4–5、TDD) + +- `aalc_provider.test.ts`(`game24_provider.test.ts` を複製): フィルタ(aalc001 のみ・他 contest_id を除外)、メタデータ、表示設定、round label + - test 名の例: `filters only aalc001 tasks`, `returns metadata for AAL Contest 001` +- RED → `aalc_provider.ts` 実装 → GREEN +- `contest_table_provider_groups.test.ts`: `dps` の contestTypes に `ContestType.AALC` を追加、`getSize()` を 6 に +- RED → `contest_table_provider_groups.ts` で `new AALCProvider(ContestType.AALC)` を `Game24Provider` の後に追加、JSDoc を `(EDPC, TDPC, NDPC, FPS 24, Game 24, and AAL Contest 001)` に → GREEN + +### Phase 4: E2E + +- `e2e/problems_contest_table.spec.ts` の `XDPC_GROUP_TABLE_TITLES` 末尾に `AAL Contest 001: Let's use segtree!` を追加し、test 名を `shows Game 24 and AAL Contest 001 tables after FPS 24 in the xDPC group` に変更 + +## 最終確認 + +- `pnpm format` / `pnpm lint` / `pnpm check` / `pnpm test:unit` / 該当 E2E / `git diff --check` +- DB スキーマとマイグレーションの変更を含むので、AGENTS.md に従い Codex でクロスレビューする diff --git a/prisma/migrations/20261013091828_add_aalc_to_contest_type/migration.sql b/prisma/migrations/20261013091828_add_aalc_to_contest_type/migration.sql new file mode 100644 index 000000000..6b16f97cc --- /dev/null +++ b/prisma/migrations/20261013091828_add_aalc_to_contest_type/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "ContestType" ADD VALUE 'AALC'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 61114f9d1..66429fced 100755 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -315,6 +315,7 @@ enum ContestType { UNIVERSITY // University Programming Contests in AtCoder (e.g., UTPC) FPS_24 // 24 Problems on Formal Power Series GAME_24 // 24 Problems on Combinatorial Games + AALC // AAL Contest 001: Let's use segtree! ATCODER_MAIN_OFFICIAL_ONSITE // AtCoder World Tour Finals (official onsite finals) OTHERS // AtCoder (その他) AOJ_COURSES // AIZU ONLINE JUDGE Courses diff --git a/prisma/tasks.ts b/prisma/tasks.ts index 25279adb2..348f5f7bb 100755 --- a/prisma/tasks.ts +++ b/prisma/tasks.ts @@ -8935,6 +8935,174 @@ export const tasks = [ name: 'Bacteria Growth Experiment', title: 'A. Bacteria Growth Experiment', }, + { + id: 'aalc001_x', + contest_id: 'aalc001', + problem_index: 'X', + name: 'Unevenness', + title: 'X. Unevenness', + }, + { + id: 'aalc001_w', + contest_id: 'aalc001', + problem_index: 'W', + name: 'Adjacent Add', + title: 'W. Adjacent Add', + }, + { + id: 'aalc001_v', + contest_id: 'aalc001', + problem_index: 'V', + name: 'Connectivity 2', + title: 'V. Connectivity 2', + }, + { + id: 'aalc001_u', + contest_id: 'aalc001', + problem_index: 'U', + name: 'Second', + title: 'U. Second', + }, + { + id: 'aalc001_t', + contest_id: 'aalc001', + problem_index: 'T', + name: 'K-Inversion Count', + title: 'T. K-Inversion Count', + }, + { + id: 'aalc001_s', + contest_id: 'aalc001', + problem_index: 'S', + name: 'Absolute Value', + title: 'S. Absolute Value', + }, + { + id: 'aalc001_r', + contest_id: 'aalc001', + problem_index: 'R', + name: 'Connectivity', + title: 'R. Connectivity', + }, + { + id: 'aalc001_q', + contest_id: 'aalc001', + problem_index: 'Q', + name: 'Number of Moves', + title: 'Q. Number of Moves', + }, + { + id: 'aalc001_p', + contest_id: 'aalc001', + problem_index: 'P', + name: 'Connect the Dots', + title: 'P. Connect the Dots', + }, + { + id: 'aalc001_o', + contest_id: 'aalc001', + problem_index: 'O', + name: 'Minimum Value', + title: 'O. Minimum Value', + }, + { + id: 'aalc001_n', + contest_id: 'aalc001', + problem_index: 'N', + name: 'Times Tables', + title: 'N. Times Tables', + }, + { + id: 'aalc001_m', + contest_id: 'aalc001', + problem_index: 'M', + name: 'NAND', + title: 'M. NAND', + }, + { + id: 'aalc001_l', + contest_id: 'aalc001', + problem_index: 'L', + name: 'Swap between Arrays', + title: 'L. Swap between Arrays', + }, + { + id: 'aalc001_k', + contest_id: 'aalc001', + problem_index: 'K', + name: 'Multiset', + title: 'K. Multiset', + }, + { + id: 'aalc001_j', + contest_id: 'aalc001', + problem_index: 'J', + name: 'Long Binary String', + title: 'J. Long Binary String', + }, + { + id: 'aalc001_i', + contest_id: 'aalc001', + problem_index: 'I', + name: 'Subarray', + title: 'I. Subarray', + }, + { + id: 'aalc001_h', + contest_id: 'aalc001', + problem_index: 'H', + name: 'Add and Mod', + title: 'H. Add and Mod', + }, + { + id: 'aalc001_g', + contest_id: 'aalc001', + problem_index: 'G', + name: 'Sum of k-th Powers', + title: 'G. Sum of k-th Powers', + }, + { + id: 'aalc001_f', + contest_id: 'aalc001', + problem_index: 'F', + name: 'Run', + title: 'F. Run', + }, + { + id: 'aalc001_e', + contest_id: 'aalc001', + problem_index: 'E', + name: 'RGB', + title: 'E. RGB', + }, + { + id: 'aalc001_d', + contest_id: 'aalc001', + problem_index: 'D', + name: 'Inversion Count', + title: 'D. Inversion Count', + }, + { + id: 'aalc001_c', + contest_id: 'aalc001', + problem_index: 'C', + name: 'Binary Search on a Segment Tree', + title: 'C. Binary Search on a Segment Tree', + }, + { + id: 'aalc001_b', + contest_id: 'aalc001', + problem_index: 'B', + name: 'Lazy Segment Tree', + title: 'B. Lazy Segment Tree', + }, + { + id: 'aalc001_a', + contest_id: 'aalc001', + problem_index: 'A', + name: 'Segment Tree', + title: 'A. Segment Tree', + }, { id: 'game_x', contest_id: 'game-24', diff --git a/src/lib/contests/fixtures/contest_name_labels.ts b/src/lib/contests/fixtures/contest_name_labels.ts index 5b32407d7..bc4560b5f 100644 --- a/src/lib/contests/fixtures/contest_name_labels.ts +++ b/src/lib/contests/fixtures/contest_name_labels.ts @@ -146,6 +146,13 @@ export const game24 = [ }), ]; +export const aalc001 = [ + createTestCaseForContestNameLabel('AAL Contest 001')({ + contestId: 'aalc001', + expected: "AAL Contest 001: Let's use segtree!", + }), +]; + export const aojCourses = [ createTestCaseForContestNameLabel('AOJ, ITP1')({ contestId: 'ITP1', diff --git a/src/lib/contests/fixtures/contest_type.ts b/src/lib/contests/fixtures/contest_type.ts index c8b3670a0..051e5effa 100644 --- a/src/lib/contests/fixtures/contest_type.ts +++ b/src/lib/contests/fixtures/contest_type.ts @@ -177,6 +177,13 @@ export const game24 = [ }), ]; +export const aalc001 = [ + createTestCaseForContestType('AAL Contest 001')({ + contestId: 'aalc001', + expected: ContestType.AALC, + }), +]; + const arcContestIds = [ 'arc001', 'arc002', diff --git a/src/lib/contests/types/contest.ts b/src/lib/contests/types/contest.ts index 98200fff5..66d49ed9e 100644 --- a/src/lib/contests/types/contest.ts +++ b/src/lib/contests/types/contest.ts @@ -49,6 +49,7 @@ export const ContestType: { [key in ContestTypeOrigin]: key } = { UNIVERSITY: 'UNIVERSITY', // University Programming Contests in AtCoder (e.g., UTPC) FPS_24: 'FPS_24', // 24 Problems on Formal Power Series GAME_24: 'GAME_24', // 24 Problems on Combinatorial Games + AALC: 'AALC', // AAL Contest 001: Let's use segtree! ATCODER_MAIN_OFFICIAL_ONSITE: 'ATCODER_MAIN_OFFICIAL_ONSITE', // AtCoder World Tour Finals (official onsite finals) OTHERS: 'OTHERS', // AtCoder (その他) AOJ_COURSES: 'AOJ_COURSES', // AIZU ONLINE JUDGE Courses diff --git a/src/lib/contests/utils/classification.test.ts b/src/lib/contests/utils/classification.test.ts index 2dd2e0d4e..878545c48 100644 --- a/src/lib/contests/utils/classification.test.ts +++ b/src/lib/contests/utils/classification.test.ts @@ -120,6 +120,14 @@ describe('classify contest', () => { }); }); + describe('when contest_id is aalc001', () => { + TestCasesForContestType.aalc001.forEach(({ name, value }) => { + runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestType) => { + expect(classifyContest(contestId)).toEqual(expected); + }); + }); + }); + describe('when contest_id contains arc', () => { TestCasesForContestType.arc.forEach(({ name, value }) => { runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestType) => { diff --git a/src/lib/contests/utils/classification.ts b/src/lib/contests/utils/classification.ts index e4eb58ab0..e409b1743 100644 --- a/src/lib/contests/utils/classification.ts +++ b/src/lib/contests/utils/classification.ts @@ -32,6 +32,7 @@ const CONTEST_TYPES_BY_ID: ReadonlyMap = new Map([ ['math-and-algorithm', ContestType.MATH_AND_ALGORITHM], ['fps-24', ContestType.FPS_24], ['game-24', ContestType.GAME_24], + ['aalc001', ContestType.AALC], ]); type ClassificationRule = { diff --git a/src/lib/contests/utils/labels/index.test.ts b/src/lib/contests/utils/labels/index.test.ts index c6fd1856e..8acc9d8b0 100644 --- a/src/lib/contests/utils/labels/index.test.ts +++ b/src/lib/contests/utils/labels/index.test.ts @@ -106,6 +106,14 @@ describe('get contest name label', () => { }); }); + describe('when contest_id is aalc001', () => { + TestCasesForContestNameLabel.aalc001.forEach(({ name, value }) => { + runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => { + expect(getContestNameLabel(contestId)).toEqual(expected); + }); + }); + }); + describe('when contest_id contains awc', () => { TestCasesForContestNameLabel.awc.forEach(({ name, value }) => { runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => { diff --git a/src/lib/contests/utils/labels/index.ts b/src/lib/contests/utils/labels/index.ts index 4f04ccd94..09be766a3 100644 --- a/src/lib/contests/utils/labels/index.ts +++ b/src/lib/contests/utils/labels/index.ts @@ -35,6 +35,7 @@ export const LABEL_GENERATORS: ReadonlyMap = new Ma [ContestType.MATH_AND_ALGORITHM, () => 'アルゴリズムと数学'], [ContestType.FPS_24, () => 'FPS 24 題'], [ContestType.GAME_24, () => '組合せゲーム 24 題'], + [ContestType.AALC, () => "AAL Contest 001: Let's use segtree!"], [ContestType.ATCODER_MAIN_OFFICIAL_ONSITE, (id) => getWorldTourFinalsLabel(id)], [ContestType.UNIVERSITY, (id) => getAtCoderUniversityContestLabel(id)], [ContestType.OTHERS, generateOthersLabel], diff --git a/src/lib/contests/utils/priority.test.ts b/src/lib/contests/utils/priority.test.ts index 8782099f1..2e088f0dc 100644 --- a/src/lib/contests/utils/priority.test.ts +++ b/src/lib/contests/utils/priority.test.ts @@ -128,5 +128,9 @@ describe('contest type priorities', () => { test('ranks GAME_24 directly below FPS_24', () => { expect(priorityOf(ContestType.GAME_24)).toBe(priorityOf(ContestType.FPS_24) + 1); }); + + test('ranks AALC directly below GAME_24', () => { + expect(priorityOf(ContestType.AALC)).toBe(priorityOf(ContestType.GAME_24) + 1); + }); }); }); diff --git a/src/lib/contests/utils/priority.ts b/src/lib/contests/utils/priority.ts index 97ce14819..878498b40 100644 --- a/src/lib/contests/utils/priority.ts +++ b/src/lib/contests/utils/priority.ts @@ -2,13 +2,13 @@ import { ContestType } from '$lib/contests/types/contest'; import { classifyContest } from './classification'; /** - * Contest type priorities (0 = Highest, 27 = Lowest) + * Contest type priorities (0 = Highest, 28 = Lowest) * * Priority assignment rationale: * - Educational contests (0-11, 17): ABS, ABC, APG4B and AWC etc. * - Contests for genius (12-16): ARC, AGC, and their variants - * - Special contests (18-22): UNIVERSITY, FPS_24, GAME_24, ATCODER_MAIN_OFFICIAL_ONSITE, OTHERS - * - External platforms (23-27): AOJ_COURSES, AOJ_PCK, AOJ_ICPC, AOJ_JAG, AOJ_UNIVERSITY + * - Special contests (18-23): UNIVERSITY, FPS_24, GAME_24, AALC, ATCODER_MAIN_OFFICIAL_ONSITE, OTHERS + * - External platforms (24-28): AOJ_COURSES, AOJ_PCK, AOJ_ICPC, AOJ_JAG, AOJ_UNIVERSITY * * @remarks * HACK: The priorities for ARC, AGC, UNIVERSITY, AOJ_COURSES, and AOJ_PCK are temporary @@ -39,13 +39,14 @@ export const contestTypePriorities: Map = new Map([ [ContestType.UNIVERSITY, 18], [ContestType.FPS_24, 19], [ContestType.GAME_24, 20], - [ContestType.ATCODER_MAIN_OFFICIAL_ONSITE, 21], - [ContestType.OTHERS, 22], // AtCoder (その他) - [ContestType.AOJ_COURSES, 23], - [ContestType.AOJ_PCK, 24], - [ContestType.AOJ_ICPC, 25], - [ContestType.AOJ_JAG, 26], - [ContestType.AOJ_UNIVERSITY, 27], + [ContestType.AALC, 21], + [ContestType.ATCODER_MAIN_OFFICIAL_ONSITE, 22], + [ContestType.OTHERS, 23], // AtCoder (その他) + [ContestType.AOJ_COURSES, 24], + [ContestType.AOJ_PCK, 25], + [ContestType.AOJ_ICPC, 26], + [ContestType.AOJ_JAG, 27], + [ContestType.AOJ_UNIVERSITY, 28], ]); /** Priority given to a contest_id that no classification rule matches, so it sorts last. */ From 25edf214b5645146bea912768c5b73dec4d9800b Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Tue, 13 Oct 2026 09:20:35 +0000 Subject: [PATCH 2/5] feat(table): add AAL Contest 001 to xDPC contest table group Place it after Game 24 with the same display config as the other 24-problem contests. Co-Authored-By: Claude Opus 5.5 --- .../utils/contest-table/aalc_provider.test.ts | 108 ++++++++++++++++++ .../utils/contest-table/aalc_provider.ts | 42 +++++++ .../contest_table_provider_groups.test.ts | 5 +- .../contest_table_provider_groups.ts | 4 +- 4 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 src/features/tasks/utils/contest-table/aalc_provider.test.ts create mode 100644 src/features/tasks/utils/contest-table/aalc_provider.ts diff --git a/src/features/tasks/utils/contest-table/aalc_provider.test.ts b/src/features/tasks/utils/contest-table/aalc_provider.test.ts new file mode 100644 index 000000000..b89fd0200 --- /dev/null +++ b/src/features/tasks/utils/contest-table/aalc_provider.test.ts @@ -0,0 +1,108 @@ +import { describe, test, expect } from 'vitest'; + +import { ContestType } from '$lib/contests/types/contest'; +import type { TaskResults } from '$lib/types/task'; + +import { AALCProvider } from './aalc_provider'; + +describe('AALCProvider', () => { + test('expects to get correct metadata', () => { + const provider = new AALCProvider(ContestType.AALC); + const metadata = provider.getMetadata(); + + expect(metadata.title).toBe("AAL Contest 001: Let's use segtree!"); + expect(metadata.abbreviationName).toBe('aalc001'); + }); + + test('expects to get correct display configuration', () => { + const provider = new AALCProvider(ContestType.AALC); + const displayConfig = provider.getDisplayConfig(); + + expect(displayConfig.isShownHeader).toBe(false); + expect(displayConfig.isShownRoundLabel).toBe(false); + expect(displayConfig.roundLabelWidth).toBe(''); + expect(displayConfig.tableBodyCellsWidth).toBe( + 'w-1/2 xs:w-1/3 sm:w-1/4 md:w-1/5 lg:w-1/6 2xl:w-1/7 px-1 py-2', + ); + expect(displayConfig.isShownTaskIndex).toBe(true); + }); + + test('expects to format contest round label correctly', () => { + const provider = new AALCProvider(ContestType.AALC); + const label = provider.getContestRoundLabel(''); + + expect(label).toBe(''); + }); + + test('expects to filter tasks to include only aalc001 contest', () => { + const provider = new AALCProvider(ContestType.AALC); + const mixedTasks = [ + { contest_id: 'abc123', task_id: 'abc123_a', task_table_index: 'A' }, + { contest_id: 'aalc001', task_id: 'aalc001_a', task_table_index: 'A' }, + { contest_id: 'game-24', task_id: 'game_a', task_table_index: 'A' }, + { contest_id: 'aalc001', task_id: 'aalc001_b', task_table_index: 'B' }, + ]; + const filtered = provider.filter(mixedTasks as TaskResults); + + expect(filtered?.map((task) => task.task_id)).toEqual(['aalc001_a', 'aalc001_b']); + }); + + test('expects to generate correct table structure', () => { + const provider = new AALCProvider(ContestType.AALC); + const tasks = [ + { contest_id: 'aalc001', task_id: 'aalc001_a', task_table_index: 'A' }, + { contest_id: 'aalc001', task_id: 'aalc001_b', task_table_index: 'B' }, + { contest_id: 'aalc001', task_id: 'aalc001_x', task_table_index: 'X' }, + ]; + const table = provider.generateTable(tasks as TaskResults); + + expect(table).toHaveProperty('aalc001'); + expect(table['aalc001']).toHaveProperty('A'); + expect(table['aalc001']).toHaveProperty('B'); + expect(table['aalc001']).toHaveProperty('X'); + expect(table['aalc001']['A']).toEqual(expect.objectContaining({ task_id: 'aalc001_a' })); + }); + + test('expects to get contest round IDs correctly', () => { + const provider = new AALCProvider(ContestType.AALC); + const tasks = [ + { contest_id: 'aalc001', task_id: 'aalc001_a', task_table_index: 'A' }, + { contest_id: 'aalc001', task_id: 'aalc001_x', task_table_index: 'X' }, + ]; + const roundIds = provider.getContestRoundIds(tasks as TaskResults); + + expect(roundIds).toEqual(['aalc001']); + }); + + test('expects to get header IDs for tasks correctly in ascending order', () => { + const provider = new AALCProvider(ContestType.AALC); + const tasks = [ + { contest_id: 'aalc001', task_id: 'aalc001_a', task_table_index: 'A' }, + { contest_id: 'aalc001', task_id: 'aalc001_x', task_table_index: 'X' }, + { contest_id: 'aalc001', task_id: 'aalc001_m', task_table_index: 'M' }, + { contest_id: 'aalc001', task_id: 'aalc001_b', task_table_index: 'B' }, + ]; + const headerIds = provider.getHeaderIdsForTask(tasks as TaskResults); + + expect(headerIds).toEqual(['A', 'B', 'M', 'X']); + }); + + test('expects to handle empty task results', () => { + const provider = new AALCProvider(ContestType.AALC); + const filtered = provider.filter([] as TaskResults); + + expect(filtered).toEqual([] as TaskResults); + }); + + test('expects to handle task results with different contest types', () => { + const provider = new AALCProvider(ContestType.AALC); + const mockMixedTasks = [ + { contest_id: 'abc123', task_id: 'abc123_a', task_table_index: 'A' }, + { contest_id: 'practice2', task_id: 'practice2_a', task_table_index: 'A' }, + { contest_id: 'game-24', task_id: 'game_a', task_table_index: 'A' }, + ]; + const filtered = provider.filter(mockMixedTasks as TaskResults); + + expect(filtered).toEqual([] as TaskResults); + }); +}); diff --git a/src/features/tasks/utils/contest-table/aalc_provider.ts b/src/features/tasks/utils/contest-table/aalc_provider.ts new file mode 100644 index 000000000..29dc72046 --- /dev/null +++ b/src/features/tasks/utils/contest-table/aalc_provider.ts @@ -0,0 +1,42 @@ +import { + type ContestTableMetaData, + type ContestTableDisplayConfig, +} from '$features/tasks/types/contest-table/contest_table_provider'; +import type { TaskResult } from '$lib/types/task'; + +import { classifyContest } from '$lib/contests'; + +import { ContestTableProviderBase } from './contest_table_provider_base'; + +export class AALCProvider extends ContestTableProviderBase { + protected setFilterCondition(): (taskResult: TaskResult) => boolean { + return (taskResult: TaskResult) => { + if (classifyContest(taskResult.contest_id) !== this.contestType) { + return false; + } + + return taskResult.contest_id === 'aalc001'; + }; + } + + getMetadata(): ContestTableMetaData { + return { + title: "AAL Contest 001: Let's use segtree!", + abbreviationName: 'aalc001', + }; + } + + getDisplayConfig(): ContestTableDisplayConfig { + return { + isShownHeader: false, + isShownRoundLabel: false, + roundLabelWidth: '', // No specific width for task index in AAL Contest 001 + tableBodyCellsWidth: 'w-1/2 xs:w-1/3 sm:w-1/4 md:w-1/5 lg:w-1/6 2xl:w-1/7 px-1 py-2', + isShownTaskIndex: true, + }; + } + + getContestRoundLabel(_contestId: string): string { + return ''; + } +} diff --git a/src/features/tasks/utils/contest-table/contest_table_provider_groups.test.ts b/src/features/tasks/utils/contest-table/contest_table_provider_groups.test.ts index 0c6d0c6f1..fd503cce1 100644 --- a/src/features/tasks/utils/contest-table/contest_table_provider_groups.test.ts +++ b/src/features/tasks/utils/contest-table/contest_table_provider_groups.test.ts @@ -168,7 +168,7 @@ describe('prepareContestProviderPresets', () => { } }); - test('dps registers EDPC, TDPC, NDPC, FPS 24, and Game 24 providers', () => { + test('dps registers EDPC, TDPC, NDPC, FPS 24, Game 24, and AAL Contest 001 providers', () => { const group = presets.dps(); expect(group.getGroupName()).toBe('xDPC・x 24 題'); @@ -176,7 +176,7 @@ describe('prepareContestProviderPresets', () => { buttonLabel: 'xDPC・x 24 題', ariaLabel: 'xDPC and x 24 contests', }); - expect(group.getSize()).toBe(5); + expect(group.getSize()).toBe(6); const contestTypes = [ ContestType.EDPC, @@ -184,6 +184,7 @@ describe('prepareContestProviderPresets', () => { ContestType.NDPC, ContestType.FPS_24, ContestType.GAME_24, + ContestType.AALC, ]; for (const contestType of contestTypes) { diff --git a/src/features/tasks/utils/contest-table/contest_table_provider_groups.ts b/src/features/tasks/utils/contest-table/contest_table_provider_groups.ts index f1733a393..cd5478b23 100644 --- a/src/features/tasks/utils/contest-table/contest_table_provider_groups.ts +++ b/src/features/tasks/utils/contest-table/contest_table_provider_groups.ts @@ -26,6 +26,7 @@ import { MathAndAlgorithmProvider } from './math_and_algorithm_provider'; import { EDPCProvider, TDPCProvider, NDPCProvider } from './dp_providers'; import { FPS24Provider } from './fps24_provider'; import { Game24Provider } from './game24_provider'; +import { AALCProvider } from './aalc_provider'; import { ACLPracticeProvider, ACLBeginnerProvider, ACLProvider } from './acl_providers'; import { JOIFirstQualRoundProvider, @@ -240,7 +241,7 @@ export const prepareContestProviderPresets = () => { }).addProvider(new MathAndAlgorithmProvider(ContestType.MATH_AND_ALGORITHM)), /** - * DP group (EDPC, TDPC, NDPC, FPS 24, and Game 24) + * DP group (EDPC, TDPC, NDPC, FPS 24, Game 24, and AAL Contest 001) */ dps: () => new ContestTableProviderGroup(`xDPC・x 24 題`, { @@ -252,6 +253,7 @@ export const prepareContestProviderPresets = () => { new NDPCProvider(ContestType.NDPC), new FPS24Provider(ContestType.FPS_24), new Game24Provider(ContestType.GAME_24), + new AALCProvider(ContestType.AALC), ), /** From a25884d551f3edb252db6f4514fca77d88b63888 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Tue, 13 Oct 2026 09:21:48 +0000 Subject: [PATCH 3/5] test(e2e): verify AAL Contest 001 table follows Game 24 in xDPC group Co-Authored-By: Claude Opus 5.5 --- e2e/problems_contest_table.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/e2e/problems_contest_table.spec.ts b/e2e/problems_contest_table.spec.ts index 669eb54f4..e80757b1e 100644 --- a/e2e/problems_contest_table.spec.ts +++ b/e2e/problems_contest_table.spec.ts @@ -7,10 +7,13 @@ const XDPC_GROUP_TABLE_TITLES = [ 'Next DP Contest', 'FPS 24 題', '組合せゲーム 24 題', + "AAL Contest 001: Let's use segtree!", ]; test.describe('xDPC group in contest table', () => { - test('shows Game 24 table right after FPS 24 in the xDPC group', async ({ page }) => { + test('shows Game 24 and AAL Contest 001 tables after FPS 24 in the xDPC group', async ({ + page, + }) => { await page.goto('/problems'); await page.getByRole('button', { name: XDPC_GROUP_ARIA_LABEL }).click(); From 1cd86b5d375a4495c68d1f38bdb1a2fd1713bc9b Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Tue, 13 Oct 2026 12:56:36 +0000 Subject: [PATCH 4/5] docs: rewrite aalc001 plan as completed record and update provider guide Co-Authored-By: Claude Sonnet 5.5 --- .../add-aalc001-contest-table/plan.md | 92 ++++--------------- .../how-to-add-contest-table-provider.md | 5 +- 2 files changed, 23 insertions(+), 74 deletions(-) diff --git a/docs/dev-notes/2026-10-12/add-aalc001-contest-table/plan.md b/docs/dev-notes/2026-10-12/add-aalc001-contest-table/plan.md index b28261da5..b3d9b4af5 100644 --- a/docs/dev-notes/2026-10-12/add-aalc001-contest-table/plan.md +++ b/docs/dev-notes/2026-10-12/add-aalc001-contest-table/plan.md @@ -2,88 +2,36 @@ ## 概要 -Issue #3974: [AAL Contest 001: Let's use segtree!](https://atcoder.jp/contests/aalc001)(contest_id `aalc001`、A〜X の 24 問)を ContestType `AALC` として追加し、xDPC グループの「組合せゲーム 24 題」の後に表示する。 +Issue #3974: [AAL Contest 001: Let's use segtree!](https://atcoder.jp/contests/aalc001)(contest_id `aalc001`、A〜X の 24 問)を ContestType `AALC` として追加し、xDPC グループの「組合せゲーム 24 題」の後に表示した。 -参考: `.agents/skills/add-contest-table-provider/`、Game 24 追加時の PR #4120(plan は `git show 7fb10b8c:docs/dev-notes/2026-10-06/add-game-24-contest-table/plan.md`)。 +PR #4120(Game 24)の手順をそのまま踏襲した。経緯は `git show 7fb10b8c:docs/dev-notes/2026-10-06/add-game-24-contest-table/plan.md` を参照。 ## 前提となる事実 -- **task_id は `aalc001_a`〜`aalc001_x`:** contest_id に `_` と小文字の問題記号を付けた一般的な形式。 -- **問題データの入手:** エージェント環境からは atcoder.jp を取得できない(curl は拒否)。seed の 24 行は、ユーザーが貼った一覧から作る。 -- **既存の分類ルールとの衝突はない:** `prefixes.ts` に `a` で始まる接頭辞はなく、`/^abc\d{3}$/` などの正規表現にも当たらない。 -- **本番への反映:** マイグレーションは main への push 時に CI が `prisma migrate deploy` で適用する。問題データは反映後に管理画面 `/tasks` からインポートする(AtCoder Problems に aalc001 が反映されている必要がある)。 +- **問題データの入手:** エージェント環境からは atcoder.jp を取得できない(curl は拒否された)。seed の 24 行は、ユーザーが貼った一覧から作った。 +- **本番への反映:** マイグレーションは、main への push 時に CI が `prisma migrate deploy` で適用する。問題データは、反映後に管理画面 `/tasks` からインポートする(AtCoder Problems に aalc001 が反映されている必要がある)。 +- **既存の分類ルールとは衝突しない:** `prefixes.ts` に `a` で始まる接頭辞はなく、`/^abc\d{3}$/` などの正規表現にも当たらない。 ## 決定事項 -| 項目 | 決定 | 理由・補足 | -| ------------------ | ------------------------------------------------------ | ----------------------------------------------------------------------------- | -| ContestType | `AALC` | ユーザー指定 | -| 位置 | enum・priority ともに `GAME_24` の直後 | ユーザー指定。priority は 21 で、`ATCODER_MAIN_OFFICIAL_ONSITE` 以降は +1 | -| 表示名 | `AAL Contest 001: Let's use segtree!` | ユーザー指定。名前ラベル(`LABEL_GENERATORS`)とテーブルのタイトルで共通 | -| enum コメント | `AAL Contest 001: Let's use segtree!` | 他の enum コメントと同様に英語の正式名 | -| 分類 | `CONTEST_TYPES_BY_ID` に `aalc001` を完全一致で追加 | aalc001 だけ扱う(ユーザー選択)。002 以降が出たら接頭辞ルールに拡張する | -| Provider | `aalc_provider.ts` に `AALCProvider` を作る | Game24Provider をクラスごと複製(既存の単一コンテスト Provider の慣例) | -| `abbreviationName` | `aalc001` | contest_id と同じ(game-24・fps-24 と同じ方針) | -| 表示設定 | Game 24 と同じ | これまでの 24 問コンテストとスタイルを揃える | -| グループ | `dps`(`xDPC・x 24 題`)の末尾に追加。名前・キーは不変 | AALC も 24 問なので「x 24 題」に含める(ユーザー選択)。localStorage キー維持 | -| seed の grade | 付けない | game-24・fps-24 と同様に PENDING | -| E2E | `XDPC_GROUP_TABLE_TITLES` の末尾に 1 件追加 | 見出しの並びを検証する既存テストを拡張するだけで足りる | -| コミット | plan / Layer 1–3 と seed / Layer 4–5 と E2E | 1 PR・3 コミット(#4120 と同じ) | - -## 主なシグネチャ - -```typescript -// src/features/tasks/utils/contest-table/aalc_provider.ts -export class AALCProvider extends ContestTableProviderBase { - protected setFilterCondition(): (taskResult: TaskResult) => boolean; // classifyContest === AALC && contest_id === 'aalc001' - getMetadata(): ContestTableMetaData; // { title: "AAL Contest 001: Let's use segtree!", abbreviationName: 'aalc001' } - getDisplayConfig(): ContestTableDisplayConfig; // Game24Provider と同じ値 - getContestRoundLabel(_contestId: string): string; // '' -} -``` +| 項目 | 決定 | 理由・補足 | +| ------------------ | -------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| 種別名・位置 | `AALC`、enum・priority とも `GAME_24` の後 | ユーザー指定 | +| 表示名 | 正式名 `AAL Contest 001: Let's use segtree!` | ユーザー選択。短縮形(`AALC 001` / `セグ木 24 題`)は採らなかった | +| 分類 | `aalc001` の完全一致 | ユーザー選択。種別名は回数を含まないが、002 以降は予定がないため(YAGNI) | +| ファイル・クラス名 | `aalc_provider.ts` / `AALCProvider` | ユーザー指定。当初案の `aalc001_provider.ts` / `AALC001Provider` から変更 | +| グループ | `xDPC・x 24 題` の名前とキー `dps` は不変 | ユーザー選択。AALC も 24 問なので「x 24 題」に含まれる。キーは localStorage に保存されるため変えない | +| seed の並び | X→A の降順、`game_x` の直前 | `prisma/tasks.ts` の既存ブロック(game-24・fps-24)の並びに合わせた。貼られた一覧は A→X だった | +| seed の grade | 付けない | game-24・fps-24 と同様に PENDING とし、本番で投票か管理画面から設定する | ## 見送り案 -- **`aalc` の接頭辞で分類する:** 現時点で 002 以降の予定はなく、YAGNI。出たときに Pattern 1 か 4 で拡張する。 -- **表示名を短くする(`AALC 001` / `セグ木 24 題`):** ユーザーが正式名を選んだ。 -- **グループ名を変える:** プリセットキー・aria-label・E2E に波及するうえ、「x 24 題」に収まる。 +- **`aalc` の接頭辞で分類する:** 002 以降の予定がない。開かれたときに Pattern 1(範囲)か Pattern 4(コンストラクタ引数)で拡張する。 +- **グループ名を変える:** プリセットキー・aria-label・E2E に波及するうえ、「x 24 題」に収まっている。 - **単一 contest_id 用の共通基底クラスを抽出する:** EDPC〜Game 24 の 5 クラスに波及し、本タスクの範囲を超える(#4120 と同じ判断)。 -## フェーズ(低リスク → 高リスク) - -### Phase 1: ContestType の追加(Layer 1–2) - -- `prisma/schema.prisma` の enum で `GAME_24` の直後に `AALC` を追加 → `pnpm exec prisma migrate dev --name add_aalc_to_contest_type` -- `src/lib/contests/types/contest.ts` に同じ位置で追加 -- `pnpm check` - -### Phase 2: 分類・priority・ラベル(Layer 3、TDD) - -- テスト先行 - - `fixtures/contest_type.ts` に `aalc001`(expected `ContestType.AALC`)、`fixtures/contest_name_labels.ts` に `aalc001`(expected `AAL Contest 001: Let's use segtree!`) - - `classification.test.ts` / `labels/index.test.ts` に describe を追加 - - `priority.test.ts` に `ranks AALC directly below GAME_24` - - RED を確認 -- 実装 - - `CONTEST_TYPES_BY_ID` に `['aalc001', ContestType.AALC]` - - `contestTypePriorities` に `[ContestType.AALC, 21]`、後続を +1、JSDoc の範囲を更新(0 = Highest, 28 = Lowest / Special 18-23 / External 24-28) - - `LABEL_GENERATORS` に `[ContestType.AALC, () => "AAL Contest 001: Let's use segtree!"]` - - GREEN。`src/test/lib/utils/task.test.ts` は priority の差分の期待値に後続種別を使っていないので変更不要の見込み(実行して確認) -- `prisma/tasks.ts` に 24 行を追加(貼られた一覧どおり) - -### Phase 3: Provider とグループ登録(Layer 4–5、TDD) - -- `aalc_provider.test.ts`(`game24_provider.test.ts` を複製): フィルタ(aalc001 のみ・他 contest_id を除外)、メタデータ、表示設定、round label - - test 名の例: `filters only aalc001 tasks`, `returns metadata for AAL Contest 001` -- RED → `aalc_provider.ts` 実装 → GREEN -- `contest_table_provider_groups.test.ts`: `dps` の contestTypes に `ContestType.AALC` を追加、`getSize()` を 6 に -- RED → `contest_table_provider_groups.ts` で `new AALCProvider(ContestType.AALC)` を `Game24Provider` の後に追加、JSDoc を `(EDPC, TDPC, NDPC, FPS 24, Game 24, and AAL Contest 001)` に → GREEN - -### Phase 4: E2E - -- `e2e/problems_contest_table.spec.ts` の `XDPC_GROUP_TABLE_TITLES` 末尾に `AAL Contest 001: Let's use segtree!` を追加し、test 名を `shows Game 24 and AAL Contest 001 tables after FPS 24 in the xDPC group` に変更 - -## 最終確認 +## 補足 -- `pnpm format` / `pnpm lint` / `pnpm check` / `pnpm test:unit` / 該当 E2E / `git diff --check` -- DB スキーマとマイグレーションの変更を含むので、AGENTS.md に従い Codex でクロスレビューする +- priority の挿入で後続の種別が 1 つずつ下がったが、`src/test/lib/utils/task.test.ts` は該当する種別を使っていないので変更が不要だった。 +- 作業ブランチ `#3974` は着手時点で既にあり、staging と同じコミットを指していたのでそのまま使った。 +- Codex のクロスレビュー(`codex review --base staging`)では指摘がなかった。 diff --git a/docs/guides/how-to-add-contest-table-provider.md b/docs/guides/how-to-add-contest-table-provider.md index dd43c4758..db29b19db 100644 --- a/docs/guides/how-to-add-contest-table-provider.md +++ b/docs/guides/how-to-add-contest-table-provider.md @@ -116,11 +116,12 @@ AOJ/JAG 固有の詳細(`AOJ_LABEL_OVERRIDES`、`titleStyle`、同一年2回 | NDPC | `'ndpc'` | 20問 | A~T | | FPS_24 | `'fps-24'` | 24問 | A~X | | GAME_24‡ | `'game-24'` | 24問 | A~X | +| AALC | `'aalc001'` | 24問 | A~X | | ACL_PRACTICE | `'practice2'` | 12問 | A~L | | ACL_BEGINNER / ACL_CONTEST1 | `'abl'`/`'acl1'` | 6問 | A~F | | AWC0100 / AWC0150† | `'awc0100'`/`'awc0150'` | 15問 | A~O | -†注: ContestType.AWC を再利用し、section(`'0100'`, `'0150'`, `'0151Onwards'`)で provider key を一意化。ACL 系は `Acl` グループ、DP 系は `dps` グループで統一管理。 +†注: ContestType.AWC を再利用し、section(`'0100'`, `'0150'`, `'0151Onwards'`)で provider key を一意化。ACL 系は `Acl` グループ、DP 系と x 24 題系(FPS_24 / GAME_24 / AALC)は `dps` グループで統一管理。 ‡注: task_id は `game_a`〜`game_x` で、contest_id の数字を含まない(`fps-24` は `fps_24_a`)。 @@ -169,4 +170,4 @@ AOJ/JAG 固有の詳細(`AOJ_LABEL_OVERRIDES`、`titleStyle`、同一年2回 --- -**最終更新**: 2026-09-05 +**最終更新**: 2026-10-13 From 1574d14b3f88c2290fbb06de88c3b4adc4f9b623 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Tue, 13 Oct 2026 12:57:29 +0000 Subject: [PATCH 5/5] docs: remove add-aalc001-contest-table plan Co-Authored-By: Claude Sonnet 5.5 --- .../add-aalc001-contest-table/plan.md | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 docs/dev-notes/2026-10-12/add-aalc001-contest-table/plan.md diff --git a/docs/dev-notes/2026-10-12/add-aalc001-contest-table/plan.md b/docs/dev-notes/2026-10-12/add-aalc001-contest-table/plan.md deleted file mode 100644 index b3d9b4af5..000000000 --- a/docs/dev-notes/2026-10-12/add-aalc001-contest-table/plan.md +++ /dev/null @@ -1,37 +0,0 @@ -# AAL Contest 001(aalc001)の追加 - -## 概要 - -Issue #3974: [AAL Contest 001: Let's use segtree!](https://atcoder.jp/contests/aalc001)(contest_id `aalc001`、A〜X の 24 問)を ContestType `AALC` として追加し、xDPC グループの「組合せゲーム 24 題」の後に表示した。 - -PR #4120(Game 24)の手順をそのまま踏襲した。経緯は `git show 7fb10b8c:docs/dev-notes/2026-10-06/add-game-24-contest-table/plan.md` を参照。 - -## 前提となる事実 - -- **問題データの入手:** エージェント環境からは atcoder.jp を取得できない(curl は拒否された)。seed の 24 行は、ユーザーが貼った一覧から作った。 -- **本番への反映:** マイグレーションは、main への push 時に CI が `prisma migrate deploy` で適用する。問題データは、反映後に管理画面 `/tasks` からインポートする(AtCoder Problems に aalc001 が反映されている必要がある)。 -- **既存の分類ルールとは衝突しない:** `prefixes.ts` に `a` で始まる接頭辞はなく、`/^abc\d{3}$/` などの正規表現にも当たらない。 - -## 決定事項 - -| 項目 | 決定 | 理由・補足 | -| ------------------ | -------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -| 種別名・位置 | `AALC`、enum・priority とも `GAME_24` の後 | ユーザー指定 | -| 表示名 | 正式名 `AAL Contest 001: Let's use segtree!` | ユーザー選択。短縮形(`AALC 001` / `セグ木 24 題`)は採らなかった | -| 分類 | `aalc001` の完全一致 | ユーザー選択。種別名は回数を含まないが、002 以降は予定がないため(YAGNI) | -| ファイル・クラス名 | `aalc_provider.ts` / `AALCProvider` | ユーザー指定。当初案の `aalc001_provider.ts` / `AALC001Provider` から変更 | -| グループ | `xDPC・x 24 題` の名前とキー `dps` は不変 | ユーザー選択。AALC も 24 問なので「x 24 題」に含まれる。キーは localStorage に保存されるため変えない | -| seed の並び | X→A の降順、`game_x` の直前 | `prisma/tasks.ts` の既存ブロック(game-24・fps-24)の並びに合わせた。貼られた一覧は A→X だった | -| seed の grade | 付けない | game-24・fps-24 と同様に PENDING とし、本番で投票か管理画面から設定する | - -## 見送り案 - -- **`aalc` の接頭辞で分類する:** 002 以降の予定がない。開かれたときに Pattern 1(範囲)か Pattern 4(コンストラクタ引数)で拡張する。 -- **グループ名を変える:** プリセットキー・aria-label・E2E に波及するうえ、「x 24 題」に収まっている。 -- **単一 contest_id 用の共通基底クラスを抽出する:** EDPC〜Game 24 の 5 クラスに波及し、本タスクの範囲を超える(#4120 と同じ判断)。 - -## 補足 - -- priority の挿入で後続の種別が 1 つずつ下がったが、`src/test/lib/utils/task.test.ts` は該当する種別を使っていないので変更が不要だった。 -- 作業ブランチ `#3974` は着手時点で既にあり、staging と同じコミットを指していたのでそのまま使った。 -- Codex のクロスレビュー(`codex review --base staging`)では指摘がなかった。