|
| 1 | +# FPS24(24 Problems on Formal Power Series)のコンテスト対応実装計画 |
| 2 | + |
| 3 | +## 📋 概要 |
| 4 | + |
| 5 | +FPS24 コンテスト対応として、`ContestType` enum に `FPS_24` を追加し、コンテスト ID `fps-24` の問題をインポートできるようにする実装計画です。 |
| 6 | + |
| 7 | +参考実装: [PR #1335: Add AXC-like contests](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/commit/193bd801eca7e3274e980e31758edd1d46a9a27b) |
| 8 | + |
| 9 | +## 🎯 実装スコープ |
| 10 | + |
| 11 | +### ✅ 実装対象 |
| 12 | + |
| 13 | +1. **Prisma スキーマ更新** |
| 14 | + - `prisma/schema.prisma` の `ContestType` enum に `FPS_24` を追加 |
| 15 | + - 配置: `UNIVERSITY` の後、`OTHERS` の前 |
| 16 | + |
| 17 | +2. **Prisma マイグレーション** |
| 18 | + - 新規マイグレーションファイル作成: `add_fps_24_to_contest_type` |
| 19 | + - コマンド: `pnpm dlx prisma migrate dev --name add_fps_24_to_contest_type` |
| 20 | + |
| 21 | +3. **ERD.md の手動更新** |
| 22 | + - マイグレーション実行後、ER図に `FPS_24` を追加 |
| 23 | + |
| 24 | +4. **型定義の更新** |
| 25 | + - `src/lib/types/contest.ts` に `FPS_24` を追加 |
| 26 | + |
| 27 | +5. **コンテスト分類ロジックの更新** |
| 28 | + - `src/lib/utils/contest.ts` の `classifyContest` 関数に FPS24 判定ロジックを追加 |
| 29 | + - 判定条件: `contest_id === 'fps-24'` (完全一致) |
| 30 | + - 戻り値: `ContestType.FPS_24` |
| 31 | + |
| 32 | +6. **優先度定義の更新** |
| 33 | + - `src/lib/utils/contest.ts` の `contestTypePriorities` Map に FPS24 を追加 |
| 34 | + - 優先度: 17(UNIVERSITY の次) |
| 35 | + - 優先度: UNIVERSITY は 16、FPS_24 は 17、OTHERS は 18 に変更 |
| 36 | + |
| 37 | +7. **コンテスト名ラベルの更新** |
| 38 | + - `src/lib/utils/contest.ts` の `getContestNameLabel` 関数に FPS24 判定ロジックを追加 |
| 39 | + - 判定条件: `contest_id === 'fps-24'` (完全一致) |
| 40 | + - 戻り値: `'FPS 24 題'` |
| 41 | + - 追加位置: `math-and-algorithm` の判定の後、`regexForAtCoderUniversity` の判定の前 |
| 42 | + |
| 43 | +8. **テスト実装** |
| 44 | + - `src/test/lib/utils/contest.test.ts` に FPS24 相当テストケースを追加 |
| 45 | + - テストコンテスト ID: `fps-24` |
| 46 | + |
| 47 | +9. **ERD.md(自動生成後の手動確認)** |
| 48 | + - マイグレーション後に `prisma generate` で ERD.md が自動更新される |
| 49 | + - 手動で `FPS_24` が正しく反映されているか確認 |
| 50 | + |
| 51 | +## 🔍 実装パターン(参考 PR より) |
| 52 | + |
| 53 | +### enum 値の追加パターン |
| 54 | + |
| 55 | +```prisma |
| 56 | +enum ContestType { |
| 57 | + // 既存の値... |
| 58 | + UNIVERSITY // University Programming Contests in AtCoder (e.g., UTPC) |
| 59 | + FPS_24 // 24 Problems on Formal Power Series |
| 60 | + OTHERS // AtCoder (その他) |
| 61 | + // その他... |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +### 型定義のパターン |
| 66 | + |
| 67 | +```typescript |
| 68 | +export const ContestType: { [key in ContestTypeOrigin]: key } = { |
| 69 | + // 既存の値... |
| 70 | + UNIVERSITY: 'UNIVERSITY', |
| 71 | + FPS_24: 'FPS_24', |
| 72 | + OTHERS: 'OTHERS', |
| 73 | + // その他... |
| 74 | +}; |
| 75 | +``` |
| 76 | + |
| 77 | +### コンテスト分類ロジックのパターン |
| 78 | + |
| 79 | +```typescript |
| 80 | +export const classifyContest = (contest_id: string) => { |
| 81 | + // ... 既存のロジック ... |
| 82 | + |
| 83 | + // FPS24 判定(新規追加) |
| 84 | + if (contest_id === 'fps-24') { |
| 85 | + return ContestType.FPS_24; |
| 86 | + } |
| 87 | + |
| 88 | + // ... その他のロジック ... |
| 89 | +}; |
| 90 | +``` |
| 91 | + |
| 92 | +### 優先度定義のパターン |
| 93 | + |
| 94 | +```typescript |
| 95 | +export const contestTypePriorities: Map<ContestType, number> = new Map([ |
| 96 | + // 既存の値... |
| 97 | + [ContestType.UNIVERSITY, 16], |
| 98 | + [ContestType.FPS_24, 17], // 新規追加 |
| 99 | + [ContestType.OTHERS, 18], // 従来の 17 から 18 に変更 |
| 100 | + // その他... |
| 101 | +]); |
| 102 | +``` |
| 103 | + |
| 104 | +### コンテスト名ラベルのパターン |
| 105 | + |
| 106 | +```typescript |
| 107 | +export const getContestNameLabel = (contestId: string) => { |
| 108 | + // ... 既存のロジック ... |
| 109 | + |
| 110 | + if (contestId === 'math-and-algorithm') { |
| 111 | + return 'アルゴリズムと数学'; |
| 112 | + } |
| 113 | + |
| 114 | + // FPS24 判定(新規追加) |
| 115 | + if (contestId === 'fps-24') { |
| 116 | + return 'FPS 24 題'; |
| 117 | + } |
| 118 | + |
| 119 | + // ... その他のロジック ... |
| 120 | +}; |
| 121 | +``` |
| 122 | + |
| 123 | +## 📝 実装ファイル一覧 |
| 124 | + |
| 125 | +| ファイル | 操作 | 内容 | |
| 126 | +| --------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------------------- | |
| 127 | +| `prisma/schema.prisma` | 更新 | `ContestType` enum に `FPS_24` を追加 | |
| 128 | +| `prisma/migrations/YYYYMMDDHHMMSS_add_fps_24_to_contest_type/migration.sql` | 作成 | ALTER TYPE コマンド(自動生成) | |
| 129 | +| `prisma/ERD.md` | 更新 | `FPS_24` が enum 値に反映されているか確認 | |
| 130 | +| `src/lib/types/contest.ts` | 更新 | `ContestType` 型定義に `FPS_24` を追加 | |
| 131 | +| `src/lib/utils/contest.ts` | 更新 | `classifyContest`, `contestTypePriorities`, `getContestNameLabel` に FPS24 ロジック追加 | |
| 132 | +| `src/test/lib/utils/contest.test.ts` | 更新 | FPS24 関連テストケースを追加 | |
| 133 | + |
| 134 | +## 🧪 テストケース例 |
| 135 | + |
| 136 | +### コンテスト分類テスト |
| 137 | + |
| 138 | +```typescript |
| 139 | +describe('when contest_id means fps-24', () => { |
| 140 | + const testCases = [{ contestId: 'fps-24', expected: ContestType.FPS_24 }]; |
| 141 | + |
| 142 | + runTests('classifyContest', testCases, ({ contestId, expected }: TestCaseForContestType) => { |
| 143 | + expect(classifyContest(contestId)).toEqual(expected); |
| 144 | + }); |
| 145 | +}); |
| 146 | +``` |
| 147 | + |
| 148 | +### 優先度テスト |
| 149 | + |
| 150 | +```typescript |
| 151 | +describe('when contest_id means fps-24 (priority)', () => { |
| 152 | + const testCases = [{ contestId: 'fps-24', expected: ContestType.FPS_24 }]; |
| 153 | + |
| 154 | + runTests('getContestPriority', testCases, ({ contestId, expected }: TestCaseForContestType) => { |
| 155 | + expect(getContestPriority(contestId)).toEqual(contestTypePriorities.get(expected)); |
| 156 | + }); |
| 157 | +}); |
| 158 | +``` |
| 159 | + |
| 160 | +### コンテスト名ラベルテスト |
| 161 | + |
| 162 | +```typescript |
| 163 | +describe('when contest_id is fps-24', () => { |
| 164 | + const testCases = [{ contestId: 'fps-24', expected: 'FPS 24 題' }]; |
| 165 | + |
| 166 | + runTests( |
| 167 | + 'getContestNameLabel', |
| 168 | + testCases, |
| 169 | + ({ contestId, expected }: TestCaseForContestNameLabel) => { |
| 170 | + expect(getContestNameLabel(contestId)).toEqual(expected); |
| 171 | + }, |
| 172 | + ); |
| 173 | +}); |
| 174 | +``` |
| 175 | + |
| 176 | +## 📋 実装手順 |
| 177 | + |
| 178 | +### フェーズ 1: Prisma スキーマ更新 |
| 179 | + |
| 180 | +1. `prisma/schema.prisma` を開く |
| 181 | +2. `ContestType` enum の `UNIVERSITY` の後に `FPS_24` を追加 |
| 182 | +3. コマンドを実行: `pnpm dlx prisma migrate dev --name add_fps_24_to_contest_type` |
| 183 | +4. マイグレーション SQL ファイルが自動生成される |
| 184 | +5. `prisma/ERD.md` を確認して `FPS_24` が反映されているか確認 |
| 185 | + |
| 186 | +### フェーズ 2: 型定義とロジックの更新 |
| 187 | + |
| 188 | +1. `src/lib/types/contest.ts` を更新(`FPS_24` を追加) |
| 189 | +2. `src/lib/utils/contest.ts` を更新: |
| 190 | + - `classifyContest` 関数に FPS24 判定ロジックを追加 |
| 191 | + - `contestTypePriorities` Map に `[ContestType.FPS_24, 17]` を追加 |
| 192 | + - `UNIVERSITY` は 16 のまま、`OTHERS` の優先度を 18 に変更 |
| 193 | + - `getContestNameLabel` 関数に FPS24 ラベル処理を追加(戻り値: `'FPS 24 題'`) |
| 194 | + |
| 195 | +### フェーズ 3: テスト実装 |
| 196 | + |
| 197 | +1. `src/test/lib/utils/test_cases/contest_type.ts` を更新: |
| 198 | + - FPS24 テストケースデータを追加 |
| 199 | +2. `src/test/lib/utils/test_cases/contest_name_labels.ts` を更新: |
| 200 | + - FPS24 ラベルテストケースデータを追加(`contestId: 'fps-24'`, `expected: 'FPS 24 題'`) |
| 201 | +3. `src/test/lib/utils/contest.test.ts` を更新: |
| 202 | + - FPS24 分類テスト(`classifyContest`)を追加 |
| 203 | + - FPS24 優先度テスト(`getContestPriority`)を追加 |
| 204 | + - FPS24 ラベルテスト(`getContestNameLabel`)を追加 |
| 205 | +4. テスト実行: `pnpm test:unit` |
| 206 | + |
| 207 | +### フェーズ 4: 検証 |
| 208 | + |
| 209 | +1. ローカルでテスト実行: `pnpm test:unit` |
| 210 | +2. 全テストが PASS であることを確認 |
| 211 | +3. Prisma Client が正しく生成されているか確認: `pnpm dlx prisma generate` |
| 212 | + |
| 213 | +## ✅ チェックリスト |
| 214 | + |
| 215 | +### 実装完了時の確認項目 |
| 216 | + |
| 217 | +- [ ] Prisma スキーマに `FPS_24` が追加されている |
| 218 | +- [ ] マイグレーション SQL が正しく生成されている |
| 219 | +- [ ] `prisma/ERD.md` に `FPS_24` が反映されている |
| 220 | +- [ ] `src/lib/types/contest.ts` に `FPS_24: 'FPS_24'` が追加されている |
| 221 | +- [ ] `src/lib/utils/contest.ts` の `classifyContest` に FPS24 ロジックが実装されている |
| 222 | +- [ ] `src/lib/utils/contest.ts` の `contestTypePriorities` に `[ContestType.FPS_24, 17]` が追加されている |
| 223 | +- [ ] `src/lib/utils/contest.ts` の `OTHERS` の優先度が 18 に更新されている |
| 224 | +- [ ] `src/lib/utils/contest.ts` の `getContestNameLabel` に FPS24 ラベル処理が実装されている(戻り値: `'FPS 24 題'`) |
| 225 | +- [ ] `src/test/lib/utils/test_cases/contest_type.ts` に FPS24 テストケースが追加されている |
| 226 | +- [ ] `src/test/lib/utils/test_cases/contest_name_labels.ts` に FPS24 ラベルテストケースが追加されている |
| 227 | +- [ ] `src/test/lib/utils/contest.test.ts` に FPS24 テストが追加されている |
| 228 | +- [ ] `pnpm test:unit` が全て PASS している |
| 229 | +- [ ] Prisma Client が正しく生成されている(`pnpm dlx prisma generate`) |
| 230 | + |
| 231 | +## 📚 参考ドキュメント |
| 232 | + |
| 233 | +### 指示ファイル(プロジェクト品質基準) |
| 234 | + |
| 235 | +- `global.instructions.md`: プロジェクト全体の統一設定 |
| 236 | +- `source-code.instructions.md`: ソースコード構造とアーキテクチャ |
| 237 | +- `tests.instructions.md`: テスト戦略(Vitest v3.2.4) |
| 238 | +- `ci.instructions.md`: CI/CD 設定 |
| 239 | + |
| 240 | +### 重要な制約 |
| 241 | + |
| 242 | +- **TypeScript**: `strict: true` 必須 |
| 243 | +- **テスト**: Vitest v3.2.4 以上 |
| 244 | +- **命名規則**: enum は UPPER_SNAKE_CASE |
| 245 | +- **Prisma**: キャメルケースでモデル定義 |
| 246 | + |
| 247 | +### 外部参照 |
| 248 | + |
| 249 | +- [AtCoder FPS24](https://atcoder.jp/contests/fps-24) |
| 250 | +- [Prisma Documentation](https://www.prisma.io/docs/) |
| 251 | +- [参考 PR: Add AXC-like contests](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/commit/193bd801eca7e3274e980e31758edd1d46a9a27b) |
| 252 | + |
| 253 | +## 🔗 関連タスク |
| 254 | + |
| 255 | +- FPS24 の問題インポート処理の実装(別タスク) |
| 256 | +- CI/CD でのテスト実行確認 |
| 257 | +- ドキュメント更新(必要に応じて) |
| 258 | + |
| 259 | +## 🚀 将来の課題: FPS24 のコンテスト表テーブル実装 |
| 260 | + |
| 261 | +> **状態**: 未着手(将来の拡張予定) |
| 262 | +> **参考**: [PR #2786: Add math and algo book to contest table](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/pull/2786) |
| 263 | +
|
| 264 | +### 概要 |
| 265 | + |
| 266 | +本実装(Prisma スキーマ更新)の後、FPS24 をコンテスト表テーブルに表示するための UI Provider を実装する予定です。 |
| 267 | + |
| 268 | +参考 PR #2786 では「Math and Algorithm」コンテストについて、以下を実装しています: |
| 269 | + |
| 270 | +- 問題データの追加(`prisma/tasks.ts`) |
| 271 | +- UI Provider の実装(`src/lib/utils/contest_table_provider.ts`) |
| 272 | +- テストケースの追加 |
| 273 | + |
| 274 | +### 実装予定 |
| 275 | + |
| 276 | +1. **問題データの追加** |
| 277 | + - `prisma/tasks.ts` に FPS24 の問題定義を追加 |
| 278 | + - 問題ペアデータはコンテスト内で完結(`contest_task_pairs` は不使用) |
| 279 | + |
| 280 | +2. **UI Provider の実装** |
| 281 | + - `src/lib/utils/contest_table_provider.ts` に `FPS24Provider` を実装 |
| 282 | + - または既存の provider フレームワークを拡張 |
| 283 | + - 以下の機能を実装: |
| 284 | + - 問題フィルタリング |
| 285 | + - メタデータ管理 |
| 286 | + - テーブルレイアウト設定(ヘッダーやラウンドラベルの表示/非表示) |
| 287 | + |
| 288 | +3. **テストの追加** |
| 289 | + - `src/test/lib/utils/contest_table_provider.test.ts` に FPS24 Provider テストを追加 |
| 290 | + - モックデータの作成(`src/test/lib/utils/test_cases/contest_table_provider.ts`) |
| 291 | + |
| 292 | +### 関連ファイル |
| 293 | + |
| 294 | +| ファイル | 説明 | |
| 295 | +| --------------------------------------------------------- | -------------------- | |
| 296 | +| `prisma/tasks.ts` | FPS24 問題定義の追加 | |
| 297 | +| `src/lib/utils/contest_table_provider.ts` | UI Provider 実装 | |
| 298 | +| `src/test/lib/utils/contest_table_provider.test.ts` | Provider テスト | |
| 299 | +| `src/test/lib/utils/test_cases/contest_table_provider.ts` | モックデータ定義 | |
| 300 | + |
| 301 | +### 優先度 |
| 302 | + |
| 303 | +このタスクは、本実装(Prisma スキーマ + 基本ロジック)が完了した後に着手することを想定しています。 |
| 304 | + |
| 305 | +## 📚 実装から得られた教訓 |
| 306 | + |
| 307 | +### 実装完了時の状態 |
| 308 | + |
| 309 | +✅ 全チェックリスト項目を完了 |
| 310 | +✅ テスト 817 件全て PASS(FPS24 関連テスト含む) |
| 311 | +✅ Prisma Client 正常に生成 |
| 312 | + |
| 313 | +### 主な学習ポイント |
| 314 | + |
| 315 | +1. **Prisma Enum 追加の手順** |
| 316 | + - `schema.prisma` の enum 定義に追加 |
| 317 | + - `pnpm dlx prisma migrate dev --name` で自動マイグレーション生成 |
| 318 | + - 型安全性を保つために Prisma Client が自動再生成される |
| 319 | + |
| 320 | +2. **型定義と実装の対応** |
| 321 | + - Prisma enum → TypeScript 型オブジェクト(`as const` 指定) |
| 322 | + - テストケースデータの構造化により、テスト追加時の漏れを防止 |
| 323 | + - ランダムテスト名の生成で重複チェック可能 |
| 324 | + |
| 325 | +3. **コンテスト分類ロジック(優先度順)の統一性** |
| 326 | + - 判定は具体的 → 一般的へ段階的に実施 |
| 327 | + - 完全一致(`contest_id === 'fps-24'`)を前にチェック |
| 328 | + - 優先度 Map で順序を一元管理(追加時に他の値も調整必要) |
| 329 | + |
| 330 | +4. **テスト構造の再利用性** |
| 331 | + - テストケースを「data」ファイルと「test」ファイルで分離 |
| 332 | + - 複数テスト関数(`classifyContest`, `getContestPriority`, `getContestNameLabel`)を同じデータで検証 |
| 333 | + - テスト数が増えても保守性が維持される設計 |
| 334 | + |
| 335 | +5. **参考 PR パターンの活用** |
| 336 | + - 小さな変更でも参考 PR から実装パターンを確認 |
| 337 | + - UPPER_SNAKE_CASE、判定条件のルール、優先度番号付けなど |
| 338 | + - 既存コードとの一貫性が重要 |
| 339 | + |
| 340 | +### 今後の参考 |
| 341 | + |
| 342 | +- UI Provider 実装時も同じテストケース構造を踏襲 |
| 343 | +- `contest_table_provider.ts` に新規ロジック追加時の参考になる |
| 344 | +- テスト駆動で実装する場合も、テストケースデータを先に定義すると効率的 |
0 commit comments