Skip to content

Commit 8fce945

Browse files
committed
feat: Add contest task pairs to seeds (#2734)
1 parent 5339224 commit 8fce945

File tree

4 files changed

+459
-0
lines changed

4 files changed

+459
-0
lines changed
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
# contest_task_pairs データ投入処理の実装
2+
3+
## 概要
4+
5+
`prisma/seed.ts``prisma/contest_task_pairs.ts` のデータ投入処理を追加します。
6+
7+
`addTasks()``addTask()` の実装パターンを参考に、`addContestTaskPairs()``addContestTaskPair()` を実装しています。
8+
9+
## 追加内容
10+
11+
### 1. インポート追加
12+
13+
`.fabbrica` から `defineContestTaskPairFactory` をインポート:
14+
15+
```typescript
16+
import {
17+
initialize,
18+
defineContestTaskPairFactory,
19+
defineUserFactory,
20+
defineKeyFactory,
21+
defineTaskFactory,
22+
defineTagFactory,
23+
defineTaskTagFactory,
24+
defineTaskAnswerFactory,
25+
defineSubmissionStatusFactory,
26+
defineWorkBookFactory,
27+
} from './.fabbrica';
28+
```
29+
30+
`contest_task_pairs` データをインポート:
31+
32+
```typescript
33+
import { contest_task_pairs } from './contest_task_pairs';
34+
```
35+
36+
### 2. 並行処理設定追加
37+
38+
`QUEUE_CONCURRENCY``contestTaskPairs` を追加:
39+
40+
```typescript
41+
const QUEUE_CONCURRENCY = {
42+
users: Number(process.env.SEED_USERS_CONCURRENCY) || 2,
43+
tasks: Number(process.env.SEED_TASKS_CONCURRENCY) || 3,
44+
contestTaskPairs: Number(process.env.SEED_CONTEST_TASK_PAIRS_CONCURRENCY) || 2,
45+
tags: Number(process.env.SEED_TAGS_CONCURRENCY) || 2,
46+
taskTags: Number(process.env.SEED_TASK_TAGS_CONCURRENCY) || 2,
47+
submissionStatuses: Number(process.env.SEED_SUBMISSION_STATUSES_CONCURRENCY) || 2,
48+
answers: Number(process.env.SEED_ANSWERS_CONCURRENCY) || 2,
49+
} as const;
50+
```
51+
52+
### 3. main 関数に処理追加
53+
54+
```typescript
55+
async function main() {
56+
try {
57+
console.log('Seeding has been started.');
58+
59+
await addUsers();
60+
await addTasks();
61+
await addContestTaskPairs();
62+
await addWorkBooks();
63+
await addTags();
64+
await addTaskTags();
65+
await addSubmissionStatuses();
66+
await addAnswers();
67+
68+
console.log('Seeding has been completed.');
69+
} catch (e) {
70+
console.error('Failed to seed:', e);
71+
throw e;
72+
}
73+
}
74+
```
75+
76+
### 4. 投入処理関数追加
77+
78+
#### `addContestTaskPairs()` 関数
79+
80+
```typescript
81+
async function addContestTaskPairs() {
82+
console.log('Start adding contest task pairs...');
83+
84+
const contestTaskPairFactory = defineContestTaskPairFactory();
85+
86+
// Create a queue with limited concurrency for contest task pair operations
87+
const contestTaskPairQueue = new PQueue({ concurrency: QUEUE_CONCURRENCY.contestTaskPairs });
88+
89+
for (const pair of contest_task_pairs) {
90+
contestTaskPairQueue.add(async () => {
91+
try {
92+
const registeredPair = await prisma.contestTaskPair.findUnique({
93+
where: {
94+
contest_id_task_id: {
95+
contest_id: pair.contest_id,
96+
task_id: pair.task_id,
97+
},
98+
},
99+
});
100+
101+
if (!registeredPair) {
102+
await addContestTaskPair(pair, contestTaskPairFactory);
103+
console.log(
104+
'contest_id:',
105+
pair.contest_id,
106+
'problem_index:',
107+
pair.problem_index,
108+
'task_id:',
109+
pair.task_id,
110+
'was registered.',
111+
);
112+
}
113+
} catch (e) {
114+
console.error('Failed to add contest task pair', pair, e);
115+
}
116+
});
117+
}
118+
119+
await contestTaskPairQueue.onIdle(); // Wait for all contest task pairs to complete
120+
console.log('Finished adding contest task pairs.');
121+
}
122+
```
123+
124+
#### `addContestTaskPair()` 関数
125+
126+
```typescript
127+
async function addContestTaskPair(
128+
pairs: (typeof contest_task_pairs)[number],
129+
contestTaskPairFactory: ReturnType<typeof defineContestTaskPairFactory>,
130+
) {
131+
await contestTaskPairFactory.create({
132+
contestId: pairs.contest_id,
133+
taskTableIndex: pairs.problem_index,
134+
taskId: pairs.task_id,
135+
});
136+
}
137+
```
138+
139+
## 実装パターン
140+
141+
`addTasks()` / `addTask()` と同じパターンを採用:
142+
143+
- **重複チェック**`findUnique()` で既存データをチェック
144+
- **並行処理**`PQueue` を使用した並行処理制御
145+
- **エラーハンドリング**:try-catch で例外処理
146+
- **ログ出力**:処理開始・完了・エラーをログ出力
147+
148+
## contest_task_pairs データ構造
149+
150+
```typescript
151+
{
152+
contest_id: string; // コンテストID(例:'tessoku-book')
153+
task_id: string; // タスクID(例:'typical90_s')
154+
problem_index: string; // 問題インデックス(例:'C18')
155+
}
156+
```
157+
158+
## 実行方法
159+
160+
```bash
161+
pnpm db:seed
162+
```
163+
164+
通常のシード実行で `addContestTaskPairs()` が呼び出されます。
165+
166+
## 環境変数による並行数調整
167+
168+
```bash
169+
SEED_CONTEST_TASK_PAIRS_CONCURRENCY=4 pnpm db:seed
170+
```
171+
172+
## 実装完了
173+
174+
2025-10-22 に実装完了。合計 13 個の `ContestTaskPair` レコードが正常に投入されました。
175+
176+
## 教訓と抽象化
177+
178+
### 1. ファクトリ再生成の必要性
179+
180+
**問題**: Prisma スキーマに新しいモデルを追加しても、`.fabbrica` に自動生成されない場合がある。
181+
182+
**原因**: スキーマ変更後に `prisma generate` を実行する必要があります。
183+
184+
**解決策**:
185+
186+
```bash
187+
pnpm prisma generate
188+
```
189+
190+
このコマンドにより、新しいモデル用のファクトリが生成されます。
191+
192+
### 2. 既存パターンの活用による効率化
193+
194+
**パターン**: データ投入処理の実装パターンは統一する。
195+
196+
**利点**:
197+
198+
- コードの一貫性が保たれる
199+
- デバッグやメンテナンスが容易
200+
- 新しい開発者の理解が速い
201+
202+
**実装パターン** (`addTasks()` と同じ):
203+
204+
1. ファクトリをインスタンス化
205+
2. `PQueue` で並行処理制御
206+
3. `findUnique()` で重複チェック
207+
4. キューが空になるまで待機
208+
5. 処理結果をログ出力
209+
210+
### 3. データ構造の名前の統一性
211+
212+
**注意点**: `contest_task_pairs.ts` ファイルのフィールド名が `problem_id` ですが、Prisma スキーマでは `taskId` です。
213+
214+
**推奨**: データファイルとスキーマのフィールド名を統一する、または明確なマッピングを文書化する。
215+
216+
**現在の対応**:
217+
218+
```typescript
219+
// contest_task_pairs.ts から読み込まれるデータ
220+
{
221+
contest_id: 'tessoku-book',
222+
problem_id: 'typical90_s', // ← 注意:problem_id
223+
problem_index: 'C18'
224+
}
225+
226+
// Prisma への投入時にマッピング
227+
contestId: pair.contest_id,
228+
taskId: pair.problem_id, // ← problem_id を taskId に
229+
taskTableIndex: pair.problem_index
230+
```
231+
232+
### 4. 処理順序の設計
233+
234+
**重要**: `addContestTaskPairs()``addTasks()` の後に実行する。
235+
236+
**理由**: `ContestTaskPair``taskId` を参照します。外部キー制約により、参照先が存在する必要があります。
237+
238+
**処理順序**:
239+
240+
1. `addUsers()` - ユーザー作成
241+
2. `addTasks()` - タスク作成 ⭐ 先
242+
3. `addContestTaskPairs()` - コンテスト-タスク ペア ⭐ 後
243+
4. `addWorkBooks()` - ワークブック作成
244+
245+
### 5. 環境変数による動的調整
246+
247+
**利点**: 環境に応じて並行処理数を調整可能。
248+
249+
**フォールバック**: デフォルト値を用意することで、環境変数が設定されていない場合も動作します。
250+
251+
```typescript
252+
const QUEUE_CONCURRENCY = {
253+
contestTaskPairs: Number(process.env.SEED_CONTEST_TASK_PAIRS_CONCURRENCY) || 2,
254+
};
255+
```
256+
257+
### 6. ログ出力の重要性
258+
259+
**ポイント**:
260+
261+
- 処理開始・完了ログで全体的な進捗を把握
262+
- エラー発生時は詳細をログ出力
263+
- 既存データとの重複は警告またはスキップログを出力
264+
265+
**効果**: トレーニング・デバッグ時の問題特定が容易

prisma/contest_task_pairs.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
export const contest_task_pairs = [
2+
{
3+
contest_id: 'tessoku-book',
4+
problem_id: 'typical90_s',
5+
problem_index: 'C18',
6+
},
7+
{
8+
contest_id: 'tessoku-book',
9+
problem_id: 'math_and_algorithm_ac',
10+
problem_index: 'C09',
11+
},
12+
{
13+
contest_id: 'tessoku-book',
14+
problem_id: 'abc007_3',
15+
problem_index: 'B63',
16+
},
17+
{
18+
contest_id: 'tessoku-book',
19+
problem_id: 'math_and_algorithm_ap',
20+
problem_index: 'B28',
21+
},
22+
{
23+
contest_id: 'tessoku-book',
24+
problem_id: 'dp_a',
25+
problem_index: 'B16',
26+
},
27+
{
28+
contest_id: 'tessoku-book',
29+
problem_id: 'math_and_algorithm_al',
30+
problem_index: 'B07',
31+
},
32+
{
33+
contest_id: 'tessoku-book',
34+
problem_id: 'typical90_a',
35+
problem_index: 'A77',
36+
},
37+
{
38+
contest_id: 'tessoku-book',
39+
problem_id: 'math_and_algorithm_an',
40+
problem_index: 'A63',
41+
},
42+
{
43+
contest_id: 'tessoku-book',
44+
problem_id: 'math_and_algorithm_am',
45+
problem_index: 'A62',
46+
},
47+
{
48+
contest_id: 'tessoku-book',
49+
problem_id: 'math_and_algorithm_bn',
50+
problem_index: 'A39',
51+
},
52+
{
53+
contest_id: 'tessoku-book',
54+
problem_id: 'math_and_algorithm_aq',
55+
problem_index: 'A29',
56+
},
57+
{
58+
contest_id: 'tessoku-book',
59+
problem_id: 'math_and_algorithm_o',
60+
problem_index: 'A27',
61+
},
62+
{
63+
contest_id: 'tessoku-book',
64+
problem_id: 'math_and_algorithm_ai',
65+
problem_index: 'A06',
66+
},
67+
];

0 commit comments

Comments
 (0)