From d45f1a945a91dc63d747b5bcbc8b1a4d72204148 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sat, 14 Feb 2026 02:42:02 +0000 Subject: [PATCH 1/4] docs: Add plan (#3153) --- .../add-awc-to-contest-table/plan.md | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 docs/dev-notes/2026-02-14/add-awc-to-contest-table/plan.md diff --git a/docs/dev-notes/2026-02-14/add-awc-to-contest-table/plan.md b/docs/dev-notes/2026-02-14/add-awc-to-contest-table/plan.md new file mode 100644 index 000000000..a2c9d5250 --- /dev/null +++ b/docs/dev-notes/2026-02-14/add-awc-to-contest-table/plan.md @@ -0,0 +1,202 @@ +# Plan: AtCoder Weekday Contest (AWC) テーブル追加 + +## Context + +Issue [#3153](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/3153) の対応。新しいコンテストシリーズ「AtCoder Weekday Contest」のテーブルを追加する。パターン1(範囲フィルタ型)で、ARC104Onwards / AGC001Onwards と同じ構造。 + +**仕様**: + +- ContestType: `AWC`(新規) +- contest_id: `awc0001`, `awc0002`, ...(awc + 4桁数字) +- 範囲: awc0001〜(上限なし) +- セクション: A〜E(5問/回) +- タイトル: "AtCoder Weekday Contest" + +## 実装手順 + +### Step 1: Prisma スキーマに `AWC` 追加 + +**File**: [prisma/schema.prisma](prisma/schema.prisma) + +`ContestType` enum に `AWC` を追加(`AGC_LIKE` の次)。 + +``` +pnpm exec prisma migrate dev --name add_awc_to_contest_type +``` + +### Step 2: TypeScript 型更新 + +**File**: [src/lib/types/contest.ts](src/lib/types/contest.ts) + +- `ContestType` オブジェクトに `AWC: 'AWC'` 追加(`AGC_LIKE` の次) +- `contestTypePriorities` に `[ContestType.AWC, 16]` 追加し、以降(UNIVERSITY 〜 AOJ_JAG)の優先度を +1 シフト: + - UNIVERSITY: 16 → 17 + - FPS_24: 17 → 18 + - OTHERS: 18 → 19 + - AOJ_COURSES: 19 → 20 + - AOJ_PCK: 20 → 21 + - AOJ_JAG: 21 → 22 + +### Step 3: `classifyContest()` / `getContestNameLabel()` 更新 + +**File**: [src/lib/utils/contest.ts](src/lib/utils/contest.ts) + +- `classifyContest()` に AWC パターン追加(L17付近、AGC の後): + ```typescript + if (/^awc\d{4}$/.exec(contest_id)) { + return ContestType.AWC; + } + ``` +- `regexForAxc` の下に `regexForAwc = /^(awc)(\d{4})/i` 追加 +- `getContestNameLabel()` に AWC 分岐追加(`regexForAxc` の後): + → `"awc0001"` → `"AWC 0001"` + +#### 単体テスト + +既存テストの構造に合わせて、3つのテストケースファイルに AWC を追加し、テスト本体にも describe ブロックを追加する。 + +**1. `classifyContest()` テスト** + +- **File**: [src/test/lib/utils/test_cases/contest_type.ts](src/test/lib/utils/test_cases/contest_type.ts) + - `awc` エクスポートを追加。テストケース: + - `awc0001` → `ContestType.AWC` + - `awc0002` → `ContestType.AWC` + - `awc9999` → `ContestType.AWC` + +- **File**: [src/test/lib/utils/contest.test.ts](src/test/lib/utils/contest.test.ts) + - `classify contest > AtCoder` に `when contest_id contains awc` describe ブロック追加(`agc-like` の後) + - `get contest priority > AtCoder` にも同様の describe ブロック追加 + +**2. `getContestNameLabel()` テスト** + +- **File**: [src/test/lib/utils/test_cases/contest_name_labels.ts](src/test/lib/utils/test_cases/contest_name_labels.ts) + - `awc` エクスポートを追加。テストケース: + - `awc0001` → `"AWC 0001"` + - `awc0002` → `"AWC 0002"` + - `awc9999` → `"AWC 9999"` + +- **File**: [src/test/lib/utils/contest.test.ts](src/test/lib/utils/contest.test.ts) + - `get contest name label > AtCoder` に `when contest_id contains awc` describe ブロック追加 + +**3. `addContestNameToTaskIndex()` テスト** + +- **File**: [src/test/lib/utils/test_cases/contest_name_and_task_index.ts](src/test/lib/utils/test_cases/contest_name_and_task_index.ts) + - `awc` エクスポートを追加。テストケース: + - `awc0001`, task `A` → `"AWC 0001 - A"` + - `awc0001`, task `E` → `"AWC 0001 - E"` + +- **File**: [src/test/lib/utils/contest.test.ts](src/test/lib/utils/contest.test.ts) + - `add contest name to task index > AtCoder` に `when contest_id contains awc` describe ブロック追加 + +### Step 4: Provider クラス実装 + +**File**: [src/lib/utils/contest_table_provider.ts](src/lib/utils/contest_table_provider.ts) + +`ABCLikeProvider`(L468)の後に `AWC0001OnwardsProvider` を追加。`ARC104OnwardsProvider`(L336-359)をテンプレートとして使用: + +```typescript +export class AWC0001OnwardsProvider extends ContestTableProviderBase { + protected setFilterCondition(): (taskResult: TaskResult) => boolean { + return (taskResult: TaskResult) => { + if (classifyContest(taskResult.contest_id) !== this.contestType) { + return false; + } + const contestRound = parseContestRound(taskResult.contest_id, 'awc'); + return contestRound >= 1 && contestRound <= 9999; + }; + } + + getMetadata(): ContestTableMetaData { + return { + title: 'AtCoder Weekday Contest 0001 〜 ', + abbreviationName: 'awc0001Onwards', + }; + } + + getContestRoundLabel(contestId: string): string { + const contestNameLabel = getContestNameLabel(contestId); + return contestNameLabel.replace('AWC ', ''); + } +} +``` + +- `getDisplayConfig()` はベースクラスのデフォルト(`isShownHeader: true`, `isShownRoundLabel: true`, `isShownTaskIndex: false`)をそのまま使用 + +### Step 5: プリセット登録 + +**File**: [src/lib/utils/contest_table_provider.ts](src/lib/utils/contest_table_provider.ts) + +- `prepareContestProviderPresets()`(L1128)に追加: + ```typescript + AWC0001Onwards: () => + new ContestTableProviderGroup(`AWC 0001 Onwards`, { + buttonLabel: 'AWC 0001 〜 ', + ariaLabel: 'Filter contests from AWC 0001 onwards', + }).addProvider(new AWC0001OnwardsProvider(ContestType.AWC)), + ``` +- `contestTableProviderGroups`(L1307)に追加: + ```typescript + awc0001Onwards: prepareContestProviderPresets().AWC0001Onwards(), + ``` + +### Step 6: シードデータ追加 + +**File**: [prisma/tasks.ts](prisma/tasks.ts) + +AWC のタスクデータを追加(実際の問題名は AtCoder Problems API から確認が必要)。 + +### Step 7: テスト実装 + +**Files**: + +- [src/test/lib/utils/test_cases/contest_table_provider.ts](src/test/lib/utils/test_cases/contest_table_provider.ts) — モックデータ追加 +- [src/test/lib/utils/contest_table_provider.test.ts](src/test/lib/utils/contest_table_provider.test.ts) — テストスイート追加 + +テスト内容: + +1. フィルタリング検証(AWC のみ通過、他を除外) +2. メタデータ(title, abbreviationName) +3. ディスプレイ設定(isShownHeader, isShownRoundLabel, isShownTaskIndex) +4. ラウンドラベル(`"awc0001"` → `"0001"`) +5. テーブル構造生成(5問 A〜E) +6. 空入力ハンドリング +7. プリセットグループの検証 + +`classifyContest` モックに AWC 分岐を追加: + +```typescript +} else if (/^awc\d{4}$/.test(contestId)) { + return ContestType.AWC; +``` + +## 検証 + +```bash +pnpm exec prisma migrate dev --name add_awc_contest_type +pnpm test:unit src/test/lib/utils/contest_table_provider.test.ts +pnpm check +pnpm lint +pnpm format +``` + +## 実装完了後の教訓 + +### Step 3 までのフロー(Utility関数) + +- `classifyContest()` → コンテスト文字列を認識(パターン照合) +- `getContestNameLabel()` → 表示用フォーマット("awc0001" → "AWC 0001") +- コンテスト型を優先度マップに登録 → 画面表示順を制御 + +**重要**: regex パターンの桁数に注意。AWC は `\d{4}` (4桁)、ARC/AGC は `\d{3}` (3桁) だが、分類ロジック (`classifyContest`) の順序が重要。前後の誤判定を避けるため、より特異度の高いパターン(4桁)を先に配置。 + +### Step 4-5 のフロー(Provider クラス + 登録) + +- `ContestTableProviderBase` を拡張し `setFilterCondition()`・`getMetadata()`・`getContestRoundLabel()` 実装 +- `prepareContestProviderPresets()` → Provider ファクトリ関数 +- `contestTableProviderGroups` → 名前ごとにインスタンス化 + +**重要**: Provider クラスは `ABCLikeProvider` の直後に配置するのではなく、コンテスト系列ごとにグループ化するのが推奨。AWC はコンテスト系列なので、AGC や ARC の近くに配置。 + +### テスト戦略 + +Unit テスト(`contest.test.ts`)で基本分類を検証済みなので、Provider テストは省略可能。ただし、複雑な範囲フィルタを使う場合は Provider 単体テストを追加推奨。 From 1ff332a04dcd9112bd97b5fb0fefe6ea849f2985 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sat, 14 Feb 2026 02:42:46 +0000 Subject: [PATCH 2/4] docs: Remove old plan (#3153) --- .../add-awc-to-contest-table/plan.md | 202 ------------------ 1 file changed, 202 deletions(-) delete mode 100644 docs/dev-notes/2026-02-14/add-awc-to-contest-table/plan.md diff --git a/docs/dev-notes/2026-02-14/add-awc-to-contest-table/plan.md b/docs/dev-notes/2026-02-14/add-awc-to-contest-table/plan.md deleted file mode 100644 index a2c9d5250..000000000 --- a/docs/dev-notes/2026-02-14/add-awc-to-contest-table/plan.md +++ /dev/null @@ -1,202 +0,0 @@ -# Plan: AtCoder Weekday Contest (AWC) テーブル追加 - -## Context - -Issue [#3153](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/3153) の対応。新しいコンテストシリーズ「AtCoder Weekday Contest」のテーブルを追加する。パターン1(範囲フィルタ型)で、ARC104Onwards / AGC001Onwards と同じ構造。 - -**仕様**: - -- ContestType: `AWC`(新規) -- contest_id: `awc0001`, `awc0002`, ...(awc + 4桁数字) -- 範囲: awc0001〜(上限なし) -- セクション: A〜E(5問/回) -- タイトル: "AtCoder Weekday Contest" - -## 実装手順 - -### Step 1: Prisma スキーマに `AWC` 追加 - -**File**: [prisma/schema.prisma](prisma/schema.prisma) - -`ContestType` enum に `AWC` を追加(`AGC_LIKE` の次)。 - -``` -pnpm exec prisma migrate dev --name add_awc_to_contest_type -``` - -### Step 2: TypeScript 型更新 - -**File**: [src/lib/types/contest.ts](src/lib/types/contest.ts) - -- `ContestType` オブジェクトに `AWC: 'AWC'` 追加(`AGC_LIKE` の次) -- `contestTypePriorities` に `[ContestType.AWC, 16]` 追加し、以降(UNIVERSITY 〜 AOJ_JAG)の優先度を +1 シフト: - - UNIVERSITY: 16 → 17 - - FPS_24: 17 → 18 - - OTHERS: 18 → 19 - - AOJ_COURSES: 19 → 20 - - AOJ_PCK: 20 → 21 - - AOJ_JAG: 21 → 22 - -### Step 3: `classifyContest()` / `getContestNameLabel()` 更新 - -**File**: [src/lib/utils/contest.ts](src/lib/utils/contest.ts) - -- `classifyContest()` に AWC パターン追加(L17付近、AGC の後): - ```typescript - if (/^awc\d{4}$/.exec(contest_id)) { - return ContestType.AWC; - } - ``` -- `regexForAxc` の下に `regexForAwc = /^(awc)(\d{4})/i` 追加 -- `getContestNameLabel()` に AWC 分岐追加(`regexForAxc` の後): - → `"awc0001"` → `"AWC 0001"` - -#### 単体テスト - -既存テストの構造に合わせて、3つのテストケースファイルに AWC を追加し、テスト本体にも describe ブロックを追加する。 - -**1. `classifyContest()` テスト** - -- **File**: [src/test/lib/utils/test_cases/contest_type.ts](src/test/lib/utils/test_cases/contest_type.ts) - - `awc` エクスポートを追加。テストケース: - - `awc0001` → `ContestType.AWC` - - `awc0002` → `ContestType.AWC` - - `awc9999` → `ContestType.AWC` - -- **File**: [src/test/lib/utils/contest.test.ts](src/test/lib/utils/contest.test.ts) - - `classify contest > AtCoder` に `when contest_id contains awc` describe ブロック追加(`agc-like` の後) - - `get contest priority > AtCoder` にも同様の describe ブロック追加 - -**2. `getContestNameLabel()` テスト** - -- **File**: [src/test/lib/utils/test_cases/contest_name_labels.ts](src/test/lib/utils/test_cases/contest_name_labels.ts) - - `awc` エクスポートを追加。テストケース: - - `awc0001` → `"AWC 0001"` - - `awc0002` → `"AWC 0002"` - - `awc9999` → `"AWC 9999"` - -- **File**: [src/test/lib/utils/contest.test.ts](src/test/lib/utils/contest.test.ts) - - `get contest name label > AtCoder` に `when contest_id contains awc` describe ブロック追加 - -**3. `addContestNameToTaskIndex()` テスト** - -- **File**: [src/test/lib/utils/test_cases/contest_name_and_task_index.ts](src/test/lib/utils/test_cases/contest_name_and_task_index.ts) - - `awc` エクスポートを追加。テストケース: - - `awc0001`, task `A` → `"AWC 0001 - A"` - - `awc0001`, task `E` → `"AWC 0001 - E"` - -- **File**: [src/test/lib/utils/contest.test.ts](src/test/lib/utils/contest.test.ts) - - `add contest name to task index > AtCoder` に `when contest_id contains awc` describe ブロック追加 - -### Step 4: Provider クラス実装 - -**File**: [src/lib/utils/contest_table_provider.ts](src/lib/utils/contest_table_provider.ts) - -`ABCLikeProvider`(L468)の後に `AWC0001OnwardsProvider` を追加。`ARC104OnwardsProvider`(L336-359)をテンプレートとして使用: - -```typescript -export class AWC0001OnwardsProvider extends ContestTableProviderBase { - protected setFilterCondition(): (taskResult: TaskResult) => boolean { - return (taskResult: TaskResult) => { - if (classifyContest(taskResult.contest_id) !== this.contestType) { - return false; - } - const contestRound = parseContestRound(taskResult.contest_id, 'awc'); - return contestRound >= 1 && contestRound <= 9999; - }; - } - - getMetadata(): ContestTableMetaData { - return { - title: 'AtCoder Weekday Contest 0001 〜 ', - abbreviationName: 'awc0001Onwards', - }; - } - - getContestRoundLabel(contestId: string): string { - const contestNameLabel = getContestNameLabel(contestId); - return contestNameLabel.replace('AWC ', ''); - } -} -``` - -- `getDisplayConfig()` はベースクラスのデフォルト(`isShownHeader: true`, `isShownRoundLabel: true`, `isShownTaskIndex: false`)をそのまま使用 - -### Step 5: プリセット登録 - -**File**: [src/lib/utils/contest_table_provider.ts](src/lib/utils/contest_table_provider.ts) - -- `prepareContestProviderPresets()`(L1128)に追加: - ```typescript - AWC0001Onwards: () => - new ContestTableProviderGroup(`AWC 0001 Onwards`, { - buttonLabel: 'AWC 0001 〜 ', - ariaLabel: 'Filter contests from AWC 0001 onwards', - }).addProvider(new AWC0001OnwardsProvider(ContestType.AWC)), - ``` -- `contestTableProviderGroups`(L1307)に追加: - ```typescript - awc0001Onwards: prepareContestProviderPresets().AWC0001Onwards(), - ``` - -### Step 6: シードデータ追加 - -**File**: [prisma/tasks.ts](prisma/tasks.ts) - -AWC のタスクデータを追加(実際の問題名は AtCoder Problems API から確認が必要)。 - -### Step 7: テスト実装 - -**Files**: - -- [src/test/lib/utils/test_cases/contest_table_provider.ts](src/test/lib/utils/test_cases/contest_table_provider.ts) — モックデータ追加 -- [src/test/lib/utils/contest_table_provider.test.ts](src/test/lib/utils/contest_table_provider.test.ts) — テストスイート追加 - -テスト内容: - -1. フィルタリング検証(AWC のみ通過、他を除外) -2. メタデータ(title, abbreviationName) -3. ディスプレイ設定(isShownHeader, isShownRoundLabel, isShownTaskIndex) -4. ラウンドラベル(`"awc0001"` → `"0001"`) -5. テーブル構造生成(5問 A〜E) -6. 空入力ハンドリング -7. プリセットグループの検証 - -`classifyContest` モックに AWC 分岐を追加: - -```typescript -} else if (/^awc\d{4}$/.test(contestId)) { - return ContestType.AWC; -``` - -## 検証 - -```bash -pnpm exec prisma migrate dev --name add_awc_contest_type -pnpm test:unit src/test/lib/utils/contest_table_provider.test.ts -pnpm check -pnpm lint -pnpm format -``` - -## 実装完了後の教訓 - -### Step 3 までのフロー(Utility関数) - -- `classifyContest()` → コンテスト文字列を認識(パターン照合) -- `getContestNameLabel()` → 表示用フォーマット("awc0001" → "AWC 0001") -- コンテスト型を優先度マップに登録 → 画面表示順を制御 - -**重要**: regex パターンの桁数に注意。AWC は `\d{4}` (4桁)、ARC/AGC は `\d{3}` (3桁) だが、分類ロジック (`classifyContest`) の順序が重要。前後の誤判定を避けるため、より特異度の高いパターン(4桁)を先に配置。 - -### Step 4-5 のフロー(Provider クラス + 登録) - -- `ContestTableProviderBase` を拡張し `setFilterCondition()`・`getMetadata()`・`getContestRoundLabel()` 実装 -- `prepareContestProviderPresets()` → Provider ファクトリ関数 -- `contestTableProviderGroups` → 名前ごとにインスタンス化 - -**重要**: Provider クラスは `ABCLikeProvider` の直後に配置するのではなく、コンテスト系列ごとにグループ化するのが推奨。AWC はコンテスト系列なので、AGC や ARC の近くに配置。 - -### テスト戦略 - -Unit テスト(`contest.test.ts`)で基本分類を検証済みなので、Provider テストは省略可能。ただし、複雑な範囲フィルタを使う場合は Provider 単体テストを追加推奨。 From 8ee6d951f5c8adaf2fd96ca2088940b4f6caac38 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sat, 14 Feb 2026 05:45:48 +0000 Subject: [PATCH 3/4] feat: Add table for AWC (#3153) --- prisma/ERD.md | 40 ++-- .../migration.sql | 2 + prisma/schema.prisma | 1 + prisma/tasks.ts | 175 ++++++++++++++++++ src/lib/types/contest.ts | 1 + src/lib/utils/contest.ts | 36 +++- src/lib/utils/contest_table_provider.ts | 46 +++++ src/test/lib/utils/contest.test.ts | 36 ++++ .../lib/utils/contest_table_provider.test.ts | 83 +++++++++ .../test_cases/contest_name_and_task_index.ts | 28 +++ .../utils/test_cases/contest_name_labels.ts | 15 ++ .../test_cases/contest_table_provider.ts | 44 +++++ src/test/lib/utils/test_cases/contest_type.ts | 9 + 13 files changed, 482 insertions(+), 34 deletions(-) create mode 100644 prisma/migrations/20260214020122_add_awc_to_contest_type/migration.sql diff --git a/prisma/ERD.md b/prisma/ERD.md index f38aea140..c1e1e2d86 100644 --- a/prisma/ERD.md +++ b/prisma/ERD.md @@ -25,6 +25,7 @@ AGC AGC ABC_LIKE ABC_LIKE ARC_LIKE ARC_LIKE AGC_LIKE AGC_LIKE +AWC AWC UNIVERSITY UNIVERSITY FPS_24 FPS_24 OTHERS OTHERS @@ -205,28 +206,19 @@ OTHERS OTHERS DateTime updatedAt } - "user" o|--|| "Roles" : "enum:role" - "user" o{--}o "session" : "" - "user" o{--}o "key" : "" - "user" o{--}o "taskanswer" : "" - "user" o{--}o "workbook" : "" - "session" o|--|| "user" : "user" - "key" o|--|| "user" : "user" - "task" o|--|| "ContestType" : "enum:contest_type" - "task" o|--|| "TaskGrade" : "enum:grade" - "task" o|--|| "AtcoderProblemsDifficulty" : "enum:atcoder_problems_difficulty" - "task" o{--}o "tasktag" : "" - "task" o{--}o "taskanswer" : "" - "task" o{--}o "workbooktask" : "" - "tag" o{--}o "tasktag" : "" - "tasktag" o|--|o "task" : "task" - "tasktag" o|--|o "tag" : "tag" - "taskanswer" o|--|o "task" : "task" - "taskanswer" o|--|o "user" : "user" - "taskanswer" o|--|o "submissionstatus" : "status" - "workbook" o|--|| "WorkBookType" : "enum:workBookType" - "workbook" o|--|| "user" : "user" - "workbook" o{--}o "workbooktask" : "" - "workbooktask" o|--|| "workbook" : "workBook" - "workbooktask" o|--|| "task" : "task" + "user" |o--|| "Roles" : "enum:role" + "session" }o--|| user : "user" + "key" }o--|| user : "user" + "task" |o--|| "ContestType" : "enum:contest_type" + "task" |o--|| "TaskGrade" : "enum:grade" + "task" |o--|| "AtcoderProblemsDifficulty" : "enum:atcoder_problems_difficulty" + "tasktag" }o--|o task : "task" + "tasktag" }o--|o tag : "tag" + "taskanswer" }o--|o task : "task" + "taskanswer" }o--|o user : "user" + "taskanswer" }o--|o submissionstatus : "status" + "workbook" |o--|| "WorkBookType" : "enum:workBookType" + "workbook" }o--|| user : "user" + "workbooktask" }o--|| workbook : "workBook" + "workbooktask" }o--|| task : "task" ``` diff --git a/prisma/migrations/20260214020122_add_awc_to_contest_type/migration.sql b/prisma/migrations/20260214020122_add_awc_to_contest_type/migration.sql new file mode 100644 index 000000000..f4795fb1a --- /dev/null +++ b/prisma/migrations/20260214020122_add_awc_to_contest_type/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "ContestType" ADD VALUE 'AWC'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 026bd5952..83b6ab1cb 100755 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -237,6 +237,7 @@ enum ContestType { ABC_LIKE // AtCoder Beginner Contest (ABC) 相当のコンテスト ARC_LIKE // AtCoder Regular Contest (ARC) 相当のコンテスト AGC_LIKE // AtCoder Grand Contest (AGC) 相当のコンテスト + AWC // AtCoder Weekday Contest UNIVERSITY // University Programming Contests in AtCoder (e.g., UTPC) FPS_24 // 24 Problems on Formal Power Series OTHERS // AtCoder (その他) diff --git a/prisma/tasks.ts b/prisma/tasks.ts index 53364e4cc..4478415ea 100755 --- a/prisma/tasks.ts +++ b/prisma/tasks.ts @@ -8116,6 +8116,181 @@ export const tasks = [ name: '4/N', title: 'C. 4/N', }, + { + id: 'awc0005_e', + contest_id: 'awc0005', + problem_index: 'E', + name: 'Mountain Height Survey', + title: 'E. Mountain Height Survey', + }, + { + id: 'awc0005_d', + contest_id: 'awc0005', + problem_index: 'D', + name: 'Splitting Delivery Packages', + title: 'D. Splitting Delivery Packages', + }, + { + id: 'awc0005_c', + contest_id: 'awc0005', + problem_index: 'C', + name: 'Staircase-Shaped Flower Bed', + title: 'C. Staircase-Shaped Flower Bed', + }, + { + id: 'awc0005_b', + contest_id: 'awc0005', + problem_index: 'B', + name: 'Updating the Report Card', + title: 'B. Updating the Report Card', + }, + { + id: 'awc0005_a', + contest_id: 'awc0005', + problem_index: 'A', + name: 'Reward of Multiples', + title: 'A. Reward of Multiples', + }, + { + id: 'awc0004_e', + contest_id: 'awc0004', + problem_index: 'E', + name: 'Sum of Intervals', + title: 'E. Sum of Intervals', + }, + { + id: 'awc0004_d', + contest_id: 'awc0004', + problem_index: 'D', + name: 'Parking Lot Assignment', + title: 'D. Parking Lot Assignment', + }, + { + id: 'awc0004_c', + contest_id: 'awc0004', + problem_index: 'C', + name: 'Minimum Cost of Temperature Adjustment', + title: 'C. Minimum Cost of Temperature Adjustment', + }, + { + id: 'awc0004_b', + contest_id: 'awc0004', + problem_index: 'B', + name: 'Battery Level', + title: 'B. Battery Level', + }, + { + id: 'awc0004_a', + contest_id: 'awc0004', + problem_index: 'A', + name: 'Preparations Before Departure', + title: 'A. Preparations Before Departure', + }, + { + id: 'awc0003_e', + contest_id: 'awc0003', + problem_index: 'E', + name: 'Cargo Delivery Truck', + title: 'E. Cargo Delivery Truck', + }, + { + id: 'awc0003_d', + contest_id: 'awc0003', + problem_index: 'D', + name: 'Consecutive Practice Days', + title: 'D. Consecutive Practice Days', + }, + { + id: 'awc0003_c', + contest_id: 'awc0003', + problem_index: 'C', + name: 'Bargain Sale Selection', + title: 'C. Bargain Sale Selection', + }, + { + id: 'awc0003_b', + contest_id: 'awc0003', + problem_index: 'B', + name: 'Line of Handshakes', + title: 'B. Line of Handshakes', + }, + { + id: 'awc0003_a', + contest_id: 'awc0003', + problem_index: 'A', + name: 'Product Quality Evaluation', + title: 'A. Product Quality Evaluation', + }, + { + id: 'awc0002_e', + contest_id: 'awc0002', + problem_index: 'E', + name: 'Assortment of Sweets', + title: 'E. Assortment of Sweets', + }, + { + id: 'awc0002_d', + contest_id: 'awc0002', + problem_index: 'D', + name: 'Keys and Treasure Boxes', + title: 'D. Keys and Treasure Boxes', + }, + { + id: 'awc0002_c', + contest_id: 'awc0002', + problem_index: 'C', + name: 'Observing Plant Growth', + title: 'C. Observing Plant Growth', + }, + { + id: 'awc0002_b', + contest_id: 'awc0002', + problem_index: 'B', + name: 'Fruit Sorting', + title: 'B. Fruit Sorting', + }, + { + id: 'awc0002_a', + contest_id: 'awc0002', + problem_index: 'A', + name: 'Organizing the Bookshelf', + title: 'A. Organizing the Bookshelf', + }, + { + id: 'awc0001_e', + contest_id: 'awc0001', + problem_index: 'E', + name: 'Temperature Fluctuation Range', + title: 'E. Temperature Fluctuation Range', + }, + { + id: 'awc0001_d', + contest_id: 'awc0001', + problem_index: 'D', + name: 'Merchant on the Highway', + title: 'D. Merchant on the Highway', + }, + { + id: 'awc0001_c', + contest_id: 'awc0001', + problem_index: 'C', + name: 'Discount Coupon', + title: 'C. Discount Coupon', + }, + { + id: 'awc0001_b', + contest_id: 'awc0001', + problem_index: 'B', + name: 'Exam Passers', + title: 'B. Exam Passers', + }, + { + id: 'awc0001_a', + contest_id: 'awc0001', + problem_index: 'A', + name: 'Bacteria Growth Experiment', + title: 'A. Bacteria Growth Experiment', + }, { id: 'fps_24_x', contest_id: 'fps-24', diff --git a/src/lib/types/contest.ts b/src/lib/types/contest.ts index 6d3b79a7d..c44adbaf4 100644 --- a/src/lib/types/contest.ts +++ b/src/lib/types/contest.ts @@ -44,6 +44,7 @@ export const ContestType: { [key in ContestTypeOrigin]: key } = { ABC_LIKE: 'ABC_LIKE', // AtCoder Beginner Contest (ABC) 相当のコンテスト ARC_LIKE: 'ARC_LIKE', // AtCoder Regular Contest (ARC) 相当のコンテスト AGC_LIKE: 'AGC_LIKE', // AtCoder Grand Contest (AGC) 相当のコンテスト + AWC: 'AWC', // AtCoder Weekday Contest UNIVERSITY: 'UNIVERSITY', // University Programming Contests in AtCoder (e.g., UTPC) FPS_24: 'FPS_24', // 24 Problems on Formal Power Series OTHERS: 'OTHERS', // AtCoder (その他) diff --git a/src/lib/utils/contest.ts b/src/lib/utils/contest.ts index 0e4f8f0a8..f17a13eca 100644 --- a/src/lib/utils/contest.ts +++ b/src/lib/utils/contest.ts @@ -16,6 +16,10 @@ export const classifyContest = (contest_id: string) => { return ContestType.AGC; } + if (/^awc\d{4}$/.exec(contest_id)) { + return ContestType.AWC; + } + if (contest_id.startsWith('APG4b')) { return ContestType.APG4B; } @@ -242,10 +246,10 @@ export function getContestPrefixes(contestPrefixes: Record) { * Contest type priorities (0 = Highest, 21 = Lowest) * * Priority assignment rationale: - * - Educational contests (0-10): ABS, ABC, APG4B, etc. + * - Educational contests (0-10, 16): ABS, ABC, APG4B and AWC etc. * - Contests for genius (11-15): ARC, AGC, and their variants - * - Special contests (16-18): UNIVERSITY, FPS_24, OTHERS - * - External platforms (19-21): AOJ_COURSES, AOJ_PCK, AOJ_JAG + * - Special contests (17-19): UNIVERSITY, FPS_24, OTHERS + * - External platforms (20-22): AOJ_COURSES, AOJ_PCK, AOJ_JAG * * @remarks * HACK: The priorities for ARC, AGC, UNIVERSITY, AOJ_COURSES, and AOJ_PCK are temporary @@ -271,12 +275,13 @@ export const contestTypePriorities: Map = new Map([ [ContestType.ABC_LIKE, 13], [ContestType.ARC_LIKE, 14], [ContestType.AGC_LIKE, 15], - [ContestType.UNIVERSITY, 16], - [ContestType.FPS_24, 17], - [ContestType.OTHERS, 18], // AtCoder (その他) - [ContestType.AOJ_COURSES, 19], - [ContestType.AOJ_PCK, 20], - [ContestType.AOJ_JAG, 21], + [ContestType.AWC, 16], + [ContestType.UNIVERSITY, 17], + [ContestType.FPS_24, 18], + [ContestType.OTHERS, 19], // AtCoder (その他) + [ContestType.AOJ_COURSES, 20], + [ContestType.AOJ_PCK, 21], + [ContestType.AOJ_JAG, 22], ]); export function getContestPriority(contestId: string): number { @@ -297,20 +302,24 @@ export function getContestPriority(contestId: string): number { * - "abc" * - "arc" * - "agc" + * - "awc" * - * followed by exactly three digits. The matching is case-insensitive. + * followed by exactly three or four digits. The matching is case-insensitive. * * Example matches: * - "abc376" * - "ARC128" * - "agc045" + * - "awc0001" * * Example non-matches: * - "xyz123" * - "abc12" * - "abc1234" + * - "awc12345" */ const regexForAxc = /^(abc|arc|agc)(\d{3})/i; +const regexForAwc = /^(awc)(\d{4})/i; /** * Regular expression to match AtCoder University contest identifiers. @@ -338,6 +347,13 @@ export const getContestNameLabel = (contestId: string) => { ); } + if (regexForAwc.exec(contestId)) { + return contestId.replace( + regexForAwc, + (_, contestType, contestNumber) => `${contestType.toUpperCase()} ${contestNumber}`, + ); + } + if (contestId === 'APG4b' || contestId === 'APG4bPython') { return contestId; } diff --git a/src/lib/utils/contest_table_provider.ts b/src/lib/utils/contest_table_provider.ts index 31a32781f..6c43f945e 100644 --- a/src/lib/utils/contest_table_provider.ts +++ b/src/lib/utils/contest_table_provider.ts @@ -496,6 +496,42 @@ export class ABCLikeProvider extends ContestTableProviderBase { } } +// AWC0001 〜 (2026/02/09 〜 ) +// 5 tasks per contest +export class AWC0001OnwardsProvider extends ContestTableProviderBase { + protected setFilterCondition(): (taskResult: TaskResult) => boolean { + return (taskResult: TaskResult) => { + if (classifyContest(taskResult.contest_id) !== this.contestType) { + return false; + } + const contestRound = parseContestRound(taskResult.contest_id, 'awc'); + return contestRound >= 1 && contestRound <= 9999; + }; + } + + getMetadata(): ContestTableMetaData { + return { + title: 'AtCoder Weekday Contest 0001 〜 ', + abbreviationName: 'awc0001Onwards', + }; + } + + getDisplayConfig(): ContestTableDisplayConfig { + return { + isShownHeader: true, + isShownRoundLabel: true, + roundLabelWidth: 'xl:w-16', // Default width for task index column + tableBodyCellsWidth: 'w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/5 px-1 py-1', // Default width for table body cells + isShownTaskIndex: false, + }; + } + + getContestRoundLabel(contestId: string): string { + const contestNameLabel = getContestNameLabel(contestId); + return contestNameLabel.replace('AWC ', ''); + } +} + function parseContestRound(contestId: string, prefix: string): number { const withoutPrefix = contestId.replace(prefix, ''); @@ -1226,6 +1262,15 @@ export const prepareContestProviderPresets = () => { ariaLabel: 'Filter contests from ABC-Like', }).addProvider(new ABCLikeProvider(ContestType.ABC_LIKE)), + /** + * Single group for AWC 0001 onwards + */ + AWC0001Onwards: () => + new ContestTableProviderGroup(`AWC 0001 Onwards`, { + buttonLabel: 'AWC 0001 〜 ', + ariaLabel: 'Filter contests from AWC 0001 onwards', + }).addProvider(new AWC0001OnwardsProvider(ContestType.AWC)), + /** * Single group for Typical 90 Problems */ @@ -1314,6 +1359,7 @@ export const contestTableProviderGroups = { fromArc058ToArc103: prepareContestProviderPresets().ARC058ToARC103(), agc001Onwards: prepareContestProviderPresets().AGC001Onwards(), abcLike: prepareContestProviderPresets().ABCLike(), + awc0001Onwards: prepareContestProviderPresets().AWC0001Onwards(), fromAbc001ToAbc041: prepareContestProviderPresets().ABC001ToABC041(), fromArc001ToArc057: prepareContestProviderPresets().ARC001ToARC057(), typical90: prepareContestProviderPresets().Typical90(), diff --git a/src/test/lib/utils/contest.test.ts b/src/test/lib/utils/contest.test.ts index 1149ef356..3267d11aa 100644 --- a/src/test/lib/utils/contest.test.ts +++ b/src/test/lib/utils/contest.test.ts @@ -155,6 +155,14 @@ describe('Contest', () => { }); }); + describe('when contest_id contains awc', () => { + TestCasesForContestType.awc.forEach(({ name, value }) => { + runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestType) => { + expect(classifyContest(contestId)).toEqual(expected); + }); + }); + }); + describe('when contest_id matches contests held by university students', () => { TestCasesForContestType.universities.forEach(({ name, value }) => { runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestType) => { @@ -337,6 +345,14 @@ describe('Contest', () => { }); }); + describe('when contest_id contains awc', () => { + TestCasesForContestType.awc.forEach(({ name, value }) => { + runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestType) => { + expect(getContestPriority(contestId)).toEqual(contestTypePriorities.get(expected)); + }); + }); + }); + describe('when contest_id matches contests held by university students', () => { TestCasesForContestType.universities.forEach(({ name, value }) => { runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestType) => { @@ -430,6 +446,14 @@ describe('Contest', () => { }); }); }); + + describe('when contest_id contains awc', () => { + TestCasesForContestNameLabel.awc.forEach(({ name, value }) => { + runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => { + expect(getContestNameLabel(contestId)).toEqual(expected); + }); + }); + }); }); }); @@ -543,6 +567,18 @@ describe('Contest', () => { }); }); + describe('when contest_id contains awc', () => { + TestCasesForContestNameAndTaskIndex.awc.forEach(({ name, value }) => { + runTests( + `${name}`, + [value], + ({ contestId, taskTableIndex, expected }: TestCaseForContestNameAndTaskIndex) => { + expect(addContestNameToTaskIndex(contestId, taskTableIndex)).toEqual(expected); + }, + ); + }); + }); + describe('when contest_id matches contests held by university students', () => { TestCasesForContestNameAndTaskIndex.universities.forEach(({ name, value }) => { runTests( diff --git a/src/test/lib/utils/contest_table_provider.test.ts b/src/test/lib/utils/contest_table_provider.test.ts index 9eb7565a6..92b9e2987 100644 --- a/src/test/lib/utils/contest_table_provider.test.ts +++ b/src/test/lib/utils/contest_table_provider.test.ts @@ -15,6 +15,7 @@ import { ARC001ToARC057Provider, AGC001OnwardsProvider, ABCLikeProvider, + AWC0001OnwardsProvider, ACLPracticeProvider, ACLBeginnerProvider, ACLProvider, @@ -45,6 +46,7 @@ import { taskResultsForACLBeginnerProvider, taskResultsForACLProvider, taskResultsForABCLikeProvider, + taskResultsForAWC0001OnwardsProvider, } from './test_cases/contest_table_provider'; // Mock the imported functions @@ -58,6 +60,8 @@ vi.mock('$lib/utils/contest', () => ({ return ContestType.ARC; } else if (contestId.startsWith('agc')) { return ContestType.AGC; + } else if (contestId.startsWith('awc')) { + return ContestType.AWC; } else if (contestId === 'dp') { return ContestType.EDPC; } else if (contestId === 'tdpc') { @@ -110,6 +114,8 @@ vi.mock('$lib/utils/contest', () => ({ return `ARC ${contestId.replace('arc', '')}`; } else if (contestId.startsWith('agc')) { return `AGC ${contestId.replace('agc', '')}`; + } else if (contestId.startsWith('awc')) { + return `AWC ${contestId.replace('awc', '')}`; } else if (contestId === 'dp' || contestId === 'tdpc' || contestId === 'typical90') { return ''; } else if (contestId.startsWith('joi')) { @@ -1619,6 +1625,83 @@ describe('ContestTableProviderBase and implementations', () => { }); }); + describe('AWC 0001 Onwards Provider', () => { + test('expects to filter tasks to include only AWC contests', () => { + const provider = new AWC0001OnwardsProvider(ContestType.AWC); + const filtered = provider.filter(taskResultsForAWC0001OnwardsProvider); + + expect(filtered.length).toBeGreaterThan(0); + expect(filtered.every((task) => task.contest_id.startsWith('awc'))).toBe(true); + expect(filtered.every((task) => /^awc\d{4}$/.test(task.contest_id))).toBe(true); + }); + + test('expects to filter by range (awc0001 to awc9999)', () => { + const provider = new AWC0001OnwardsProvider(ContestType.AWC); + const filtered = provider.filter(taskResultsForAWC0001OnwardsProvider); + + expect(filtered.some((task) => task.contest_id === 'awc0001')).toBe(true); + expect(filtered.some((task) => task.contest_id === 'awc0002')).toBe(true); + expect(filtered.some((task) => task.contest_id === 'awc0099')).toBe(true); + }); + + test('expects to get correct metadata', () => { + const provider = new AWC0001OnwardsProvider(ContestType.AWC); + const metadata = provider.getMetadata(); + + expect(metadata.title).toBe('AtCoder Weekday Contest 0001 〜 '); + expect(metadata.abbreviationName).toBe('awc0001Onwards'); + }); + + test('expects to return correct display config', () => { + const provider = new AWC0001OnwardsProvider(ContestType.AWC); + const config = provider.getDisplayConfig(); + + expect(config.isShownHeader).toBe(true); + expect(config.isShownRoundLabel).toBe(true); + expect(config.tableBodyCellsWidth).toBe('w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/5 px-1 py-1'); + expect(config.roundLabelWidth).toBe('xl:w-16'); + expect(config.isShownTaskIndex).toBe(false); + }); + + test('expects to format contest round label correctly', () => { + const provider = new AWC0001OnwardsProvider(ContestType.AWC); + + expect(provider.getContestRoundLabel('awc0001')).toBe('0001'); + expect(provider.getContestRoundLabel('awc0002')).toBe('0002'); + expect(provider.getContestRoundLabel('awc0099')).toBe('0099'); + }); + + test('expects to generate table for multiple AWC contests', () => { + const provider = new AWC0001OnwardsProvider(ContestType.AWC); + const filtered = provider.filter(taskResultsForAWC0001OnwardsProvider); + const table = provider.generateTable(filtered); + + expect(Object.keys(table).length).toBeGreaterThan(0); + expect(table).toHaveProperty('awc0001'); + expect(table).toHaveProperty('awc0002'); + expect(table).toHaveProperty('awc0099'); + }); + + test('expects each AWC contest to have 5 problems (A-E)', () => { + const provider = new AWC0001OnwardsProvider(ContestType.AWC); + const filtered = provider.filter(taskResultsForAWC0001OnwardsProvider); + const table = provider.generateTable(filtered); + + Object.entries(table).forEach(([contestId, problems]) => { + const problemCount = Object.keys(problems).length; + expect(problemCount).toBe(5); + expect(Object.keys(problems)).toEqual(['A', 'B', 'C', 'D', 'E']); + }); + }); + + test('expects to handle empty task results', () => { + const provider = new AWC0001OnwardsProvider(ContestType.AWC); + const filtered = provider.filter([] as TaskResults); + + expect(filtered).toEqual([] as TaskResults); + }); + }); + describe('Typical90 provider', () => { test('expects to filter tasks to include only typical90 contest', () => { const provider = new Typical90Provider(ContestType.TYPICAL90); diff --git a/src/test/lib/utils/test_cases/contest_name_and_task_index.ts b/src/test/lib/utils/test_cases/contest_name_and_task_index.ts index e12f80812..5f11f3005 100644 --- a/src/test/lib/utils/test_cases/contest_name_and_task_index.ts +++ b/src/test/lib/utils/test_cases/contest_name_and_task_index.ts @@ -348,6 +348,34 @@ export const mathAndAlgorithm = [ }), ]; +export const awc = [ + createTestCaseForContestNameAndTaskIndex('AWC 0001, task A')({ + contestId: 'awc0001', + taskTableIndex: 'A', + expected: 'AWC 0001 - A', + }), + createTestCaseForContestNameAndTaskIndex('AWC 0001, task E')({ + contestId: 'awc0001', + taskTableIndex: 'E', + expected: 'AWC 0001 - E', + }), + createTestCaseForContestNameAndTaskIndex('AWC 0002, task A')({ + contestId: 'awc0002', + taskTableIndex: 'A', + expected: 'AWC 0002 - A', + }), + createTestCaseForContestNameAndTaskIndex('AWC 0002, task E')({ + contestId: 'awc0002', + taskTableIndex: 'E', + expected: 'AWC 0002 - E', + }), + createTestCaseForContestNameAndTaskIndex('AWC 9999, task E')({ + contestId: 'awc9999', + taskTableIndex: 'E', + expected: 'AWC 9999 - E', + }), +]; + const generateArcTestCases = ( contestIds: string[], taskIndices: string[], diff --git a/src/test/lib/utils/test_cases/contest_name_labels.ts b/src/test/lib/utils/test_cases/contest_name_labels.ts index 4d3672001..89a6d32d4 100644 --- a/src/test/lib/utils/test_cases/contest_name_labels.ts +++ b/src/test/lib/utils/test_cases/contest_name_labels.ts @@ -46,6 +46,21 @@ export const mathAndAlgorithm = [ }), ]; +export const awc = [ + createTestCaseForContestNameLabel('AWC 0001')({ + contestId: 'awc0001', + expected: 'AWC 0001', + }), + createTestCaseForContestNameLabel('AWC 0002')({ + contestId: 'awc0002', + expected: 'AWC 0002', + }), + createTestCaseForContestNameLabel('AWC 9999')({ + contestId: 'awc9999', + expected: 'AWC 9999', + }), +]; + export const fps24 = [ createTestCaseForContestNameLabel('FPS 24')({ contestId: 'fps-24', diff --git a/src/test/lib/utils/test_cases/contest_table_provider.ts b/src/test/lib/utils/test_cases/contest_table_provider.ts index 5deab83e6..513609af7 100644 --- a/src/test/lib/utils/test_cases/contest_table_provider.ts +++ b/src/test/lib/utils/test_cases/contest_table_provider.ts @@ -835,3 +835,47 @@ export const taskResultsForACLProvider: TaskResults = [ acl1_e, acl1_f, ]; + +// AWC 0001 onwards: 5 tasks (A, B, C, D, E) +// Multiple contests to test range filtering +const [awc0001_a, awc0001_b, awc0001_c, awc0001_d, awc0001_e] = createContestTasks('awc0001', [ + { taskTableIndex: 'A', statusName: AC }, + { taskTableIndex: 'B', statusName: AC }, + { taskTableIndex: 'C', statusName: AC_WITH_EDITORIAL }, + { taskTableIndex: 'D', statusName: TRYING }, + { taskTableIndex: 'E', statusName: PENDING }, +]); + +const [awc0002_a, awc0002_b, awc0002_c, awc0002_d, awc0002_e] = createContestTasks('awc0002', [ + { taskTableIndex: 'A', statusName: AC }, + { taskTableIndex: 'B', statusName: AC }, + { taskTableIndex: 'C', statusName: TRYING }, + { taskTableIndex: 'D', statusName: AC_WITH_EDITORIAL }, + { taskTableIndex: 'E', statusName: AC }, +]); + +const [awc0099_a, awc0099_b, awc0099_c, awc0099_d, awc0099_e] = createContestTasks('awc0099', [ + { taskTableIndex: 'A', statusName: AC }, + { taskTableIndex: 'B', statusName: PENDING }, + { taskTableIndex: 'C', statusName: AC }, + { taskTableIndex: 'D', statusName: TRYING }, + { taskTableIndex: 'E', statusName: AC_WITH_EDITORIAL }, +]); + +export const taskResultsForAWC0001OnwardsProvider: TaskResults = [ + awc0001_a, + awc0001_b, + awc0001_c, + awc0001_d, + awc0001_e, + awc0002_a, + awc0002_b, + awc0002_c, + awc0002_d, + awc0002_e, + awc0099_a, + awc0099_b, + awc0099_c, + awc0099_d, + awc0099_e, +]; diff --git a/src/test/lib/utils/test_cases/contest_type.ts b/src/test/lib/utils/test_cases/contest_type.ts index 3eefd3a77..5299c13b9 100644 --- a/src/test/lib/utils/test_cases/contest_type.ts +++ b/src/test/lib/utils/test_cases/contest_type.ts @@ -338,6 +338,15 @@ export const agcLike = [ }), ]; +const awcContestIds = ['awc0001', 'awc0002', 'awc9999']; + +export const awc = awcContestIds.map((contestId) => + createTestCaseForContestType(contestId.toUpperCase())({ + contestId, + expected: ContestType.AWC, + }), +); + // Note: // KUPC contests on AtCoder: 2012-2021 and 2024- (not held during 2022-2023) // QUPC contests on AtCoder: 2014, 2018 (not held during 2015-2017, 2019-) From ee09bad4503fa351498ebf7c4c655a0f9b8f23a7 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sat, 14 Feb 2026 05:49:49 +0000 Subject: [PATCH 4/4] docs: Update guides (#3153) --- .../how-to-add-contest-table-provider.md | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/guides/how-to-add-contest-table-provider.md b/docs/guides/how-to-add-contest-table-provider.md index 9e2f2a338..48a2bfb1a 100644 --- a/docs/guides/how-to-add-contest-table-provider.md +++ b/docs/guides/how-to-add-contest-table-provider.md @@ -31,6 +31,11 @@ - **パターン3(複合ソース型)**: ABS、ABC-Like など → 複数 contest_id を統一表示 - 対応セクション: [実装パターン](#実装パターン) +- [ ] **コンテスト分類テスト確認** + - `classifyContest()` で新規 `ContestType.NewType` が正しく分類されているか確認 + - 既存の競合するパターン(同じ contest_id 形式、重複する正規表現など)がないか確認 + - 他のコンテスト型との分離テストケース:正しく分類されているか確認 + - [ ] **ガイドの実装例の確認** - 判定したパターンの実装例を確認してテンプレート理解 - モック設定時に必要な `classifyContest()` の戻り値を確認 @@ -246,6 +251,7 @@ class TessokuBookSectionProvider extends TessokuBookProvider { | ARC 058-103 | 058~103 | 058, 103 | C~F | あり | 共有問題(ABC) | | ARC 104- | 104~ | 104 | 4~6問 | あり | - | | AGC 001- | 001~ | 001 | 4~7問 | あり | - | +| AWC 0001- | 0001~ | 0001 | A~E | あり | - | ### 単一ソース型 @@ -500,11 +506,12 @@ describe('CustomProvider with unique config', () => { ### ドキュメント更新チェックリスト -- [ ] 各コンテスト種別テーブル に新規 Provider の行を追加 -- [ ] 複合型参照情報がある場合は複合型コンテストの実装パターン に追加 -- [ ] テストデータ参考ファイル に新規ファイルがあれば追加 -- [ ] GitHub Issues に当該 Provider のリンクを追加 -- [ ] 最終更新日を現在日付に変更 +- [ ] **実装例・テスト結果の報告** — dev-notes に実装教訓を記載 +- [ ] **各コンテスト種別テーブル** — 新規 Provider の行を追加(範囲フィルタ型 / 単一ソース型 / 複合ソース型) +- [ ] **実装パターン説明** — 複合型参照情報がある場合は該当セクション に追加 +- [ ] **このガイド(how-to-add-contest-table-provider.md)** — 「事前確認チェックリスト」に新規学習項目を追加 +- [ ] **参考資料** — GitHub Issues に当該 Provider (#xxxx) のリンクを追加 +- [ ] **最終更新日** — 現在日付に変更 --- @@ -519,6 +526,7 @@ describe('CustomProvider with unique config', () => { - [#2837](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2837) - AGC001OnwardsProvider - [#2838](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2838) - ABC001~041 & ARC001~057 - [#2840](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2840)、[#3108](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/3108) - ABCLikeProvider +- [#3153](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/3153) - AWC0001OnwardsProvider - [#2776](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2776) - TessokuBookProvider - [#2785](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2785) - MathAndAlgorithmProvider - [#2797](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2797) - FPS24Provider @@ -532,4 +540,4 @@ describe('CustomProvider with unique config', () => { --- -**最終更新**: 2026-02-05 +**最終更新**: 2026-02-14