記事 API
レースに紐づく「レース結果10秒サマリー」記事(article)を管理するエンドポイントです。
記事とレースの関係
各記事は race_id により auto_racing / bicycle_racing / horse_racing テーブルのいずれかのレース(_id)に紐づきます。
記事はカテゴリを持ちません(カテゴリを持つのは blog / old_blog のみ)。
記事のステータス
記事は status フィールドで状態を管理します。
| ステータス | 説明 |
|---|---|
| generating | 生成中。AI による記事生成・ビルドが実行中 |
| draft | 下書き。デフォルト |
| published | 公開 |
| failed | 生成失敗。generation_error に理由が入る。再生成の起点になる |
generating / failed は生成パイプラインが付けるステータスです。
POST /v1/articles・PUT /v1/articles/{article_id} の入力としては指定できません(400 を返します)。
記事スキーマ
| フィールド | データ型 | 備考 |
|---|---|---|
| id | string | _id |
| race_id | string | auto_racing / bicycle_racing / horse_racing テーブルの _id |
| title | string | |
| body_html | string | HTML形式。AI 生成時は article_builder Lambda が React コンポーネント(RaceSummary)を renderToStaticMarkup で HTML 化したもの |
| status | enum | generating / draft / published / failed。デフォルト: draft |
| opening_at | number | 公開予定日時。UNIXタイムスタンプ(ミリ秒) |
| closing_at | number | null | 公開終了日時。UNIXタイムスタンプ(ミリ秒) |
| accuracy_test_data | object | null | 精度検証用データ。scripts/article_accuracy_test/audit.py が参照する |
| generation_error | string | null | status=failed のときの失敗理由 |
| custom_prompt | string | null | 前回の再生成で使った追加指示。入力欄の初期値に使う |
| created_at | number | UNIXタイムスタンプ(ミリ秒) |
| updated_at | number | UNIXタイムスタンプ(ミリ秒) |
POST /v1/articles/generation
概要
生成AIを用いて記事を生成します。
記事を generating 状態(body_html は空)で作成し、SQS(article-generation キュー)経由で
article_generation Lambda へ記事生成ジョブを非同期で依頼します。
リクエスト受理後すぐに 202 を返し、バックグラウンドで生成が実行されます。
生成結果は別の SQS(article-builder キュー)経由で article_builder Lambda に渡り、
記事 HTML(body_html)を構築して DocumentDB に保存し、status を draft に遷移させます。
AI生成を伴わずに記事を作成する場合は POST /v1/articles を使用してください。
不成立・中止レースは生成できない
着順が無いレース(不成立・中止)は記事を生成しません。着順を書ける材料が無いまま生成させると AI が勝者を創作してしまうためです。該当する場合は 422 を返します。
リクエストとレスポンス
ヘッダー
リクエストヘッダー
Content-Type: application/json
レスポンスヘッダー
Content-Type: application/json
URI
リクエストボディ
{
"race_id": "string",
"title": "string",
"opening_at": 1234567890000,
"closing_at": 1234567890000
}
| フィールド | データ型 | 必須 | 備考 |
|---|---|---|---|
| race_id | string | ◯ | auto_racing / bicycle_racing / horse_racing テーブルの _id |
| title | string | ◯ | 生成する記事のタイトル・トピック |
| opening_at | number | ◯ | UNIXタイムスタンプ(ミリ秒) |
| closing_at | number | UNIXタイムスタンプ(ミリ秒) |
レスポンス
202 Accepted
| フィールド | データ型 | 備考 |
|---|---|---|
| id | string | 作成された記事の _id |
例外処理
| 説明 | ステータスコード | ステータス名 |
|---|---|---|
| リクエストボディが不正 | 422 | Unprocessable Entity |
| race_id が auto_racing / bicycle_racing / horse_racing テーブルのいずれにも存在しない | 400 | Bad Request |
| 着順が無いレース(不成立・中止)で記事を生成できない | 422 | Unprocessable Entity |
| 内部サーバーエラー | 500 | Internal Server Error |
処理フロー
- リクエストを受信する
race_idが3つのレースコレクションのいずれかに存在するか検証する- レース結果に着順があるか(不成立・中止でないか)を検証する
- 記事を
generating状態(body_htmlは空)で新規作成して保存する - SQS(
article-generationキュー)へ{article_id, race_id, racing_type, title, race_result}を送信する - 202 Accepted と作成した
idを返す - (非同期)article_generation Lambda がレース解析と記事執筆を行い、
article-builderキューへ送信する - (非同期)article_builder Lambda が
body_htmlを構築して保存し、statusをdraftに遷移させる
flowchart TD
Start([POST /v1/articles/generation]) --> RaceCheck[race_id の存在確認]
RaceCheck -->|存在しない| E400[400 Bad Request]
RaceCheck -->|OK| VoidCheck[着順の有無を確認]
VoidCheck -->|着順なし| E422[422 Unprocessable Entity]
VoidCheck -->|OK| SaveArticle[記事を generating で保存]
SaveArticle --> SendSQS[SQS article-generation キューへ送信]
SendSQS --> Success[202 Accepted]
SendSQS -.->|非同期| Generate[article_generation がレース解析 + 記事執筆]
Generate -.-> Build[article_builder が HTML を構築して保存]
Build -.-> Draft[status を draft に遷移]
Generate -.->|最終試行で失敗| Failed[status を failed に遷移]
POST /v1/articles/{article_id}/regeneration
概要
生成に失敗した記事(status=failed)を、追加指示つきで作り直します。
同じ入力で作り直しても同じ理由で失敗するため、運用者が書き方の指示(custom_prompt)を
与えられるようにしています。指示を省いた場合は前回の指示をそのまま引き継ぐため、
一覧から連打しても指示が失われません。
対象は status=failed の記事だけです。draft / published を作り直すと、
確認済みの本文を上書きしてしまうため許可していません。
リクエストとレスポンス
ヘッダー
リクエストヘッダー
Content-Type: application/json
URI
| パスパラメータ | データ型 | 備考 |
|---|---|---|
| article_id | string | article._id |
リクエストボディ
| フィールド | データ型 | 必須 | 備考 |
|---|---|---|---|
| custom_prompt | string | 運用者からの追加指示(書き方の希望)。省略・空文字の場合は前回の指示を引き継ぐ |
レスポンス
202 Accepted
例外処理
| 説明 | ステータスコード | ステータス名 |
|---|---|---|
| 記事が存在しない | 404 | Not Found |
記事のステータスが failed ではない |
409 | Conflict |
| race_id が存在しない | 400 | Bad Request |
| 着順が無いレース(不成立・中止)で記事を生成できない | 422 | Unprocessable Entity |
| 内部サーバーエラー | 500 | Internal Server Error |
処理フロー
article_idで記事を取得するstatus=failedであることを確認するrace_idのレースを取得し、着順があるか検証する- 追加指示を決定する(リクエストが空なら前回の
custom_promptを使う) - 記事を
generatingに戻し、generation_errorをクリアしてcustom_promptを保存する - SQS(
article-generationキュー)へcustom_promptを含めて送信する - 202 Accepted を返す
flowchart TD
Start([POST /v1/articles/article_id/regeneration]) --> Fetch[記事を取得]
Fetch -->|存在しない| E404[404 Not Found]
Fetch -->|存在する| StatusCheck[status=failed か確認]
StatusCheck -->|failed 以外| E409[409 Conflict]
StatusCheck -->|OK| VoidCheck[レースの着順を確認]
VoidCheck -->|着順なし| E422[422 Unprocessable Entity]
VoidCheck -->|OK| Reset[status を generating に戻し custom_prompt を保存]
Reset --> SendSQS[SQS article-generation キューへ送信]
SendSQS --> Success[202 Accepted]
キュー投入前にステータスを戻す
article_generation は status=generating の記事だけを処理するため、
キューへ入れる前に generating へ更新しています。
POST /v1/articles
概要
記事を手動で新規作成します。
リクエストとレスポンス
ヘッダー
リクエストヘッダー
Content-Type: application/json
レスポンスヘッダー
Content-Type: application/json
URI
リクエストボディ
{
"race_id": "string",
"title": "string",
"body_html": "string",
"status": "draft",
"opening_at": 1234567890000,
"closing_at": 1234567890000
}
| フィールド | データ型 | 必須 | 備考 |
|---|---|---|---|
| race_id | string | ◯ | auto_racing / bicycle_racing / horse_racing テーブルの _id |
| title | string | ◯ | |
| body_html | string | ◯ | HTML形式 |
| status | enum | draft / published。省略時は draft。generating / failed は指定できない |
|
| opening_at | number | ◯ | UNIXタイムスタンプ(ミリ秒) |
| closing_at | number | UNIXタイムスタンプ(ミリ秒) |
レスポンス
201 Created
記事スキーマを返します。
例外処理
| 説明 | ステータスコード | ステータス名 |
|---|---|---|
| リクエストボディが不正 | 422 | Unprocessable Entity |
status に generating / failed を指定した |
400 | Bad Request |
| race_id が存在しない | 400 | Bad Request |
| 内部サーバーエラー | 500 | Internal Server Error |
GET /v1/articles
概要
記事の一覧を取得します。
リクエストとレスポンス
URI
クエリパラメータ
| パラメータ | データ型 | 必須 | 備考 |
|---|---|---|---|
| race_id | string | レースでフィルタ | |
| status | enum | generating / draft / published / failed でフィルタ |
|
| page | number | 取得するページ番号。デフォルト: 1 |
|
| size | number | 1ページあたりの件数。デフォルト: 10 |
|
| order | enum | asc / desc(updated_at 基準)。デフォルト: desc |
レスポンス
200 OK
{
"list": [
{
"id": "string",
"race_id": "string",
"title": "string",
"body_html": "string",
"status": "draft",
"opening_at": 1234567890000,
"closing_at": null,
"accuracy_test_data": null,
"generation_error": null,
"custom_prompt": null,
"created_at": 1234567890000,
"updated_at": 1234567890000
}
],
"current_page": 1,
"total_count": 10
}
生成失敗の一覧
管理画面の「生成失敗一覧」は GET /v1/articles?status=failed で取得します。
GET /v1/articles/{article_id}
概要
指定した記事を取得します。
リクエストとレスポンス
URI
| パスパラメータ | データ型 | 備考 |
|---|---|---|
| article_id | string | _id |
レスポンス
200 OK
記事スキーマを返します。
例外処理
| 説明 | ステータスコード | ステータス名 |
|---|---|---|
| 記事が存在しない | 404 | Not Found |
| 内部サーバーエラー | 500 | Internal Server Error |
GET /v1/races/{race_id}/articles/{article_id}
概要
指定したレースに紐づく記事を取得します。
記事の race_id が race_id と一致しない場合は 404 を返します。
競技種別ごとにエンドポイントを分けていた /v1/auto_racings/... 等は、
レースの統合(/v1/races)にあわせてこのエンドポイントへ統合されました。
リクエストとレスポンス
URI
| パスパラメータ | データ型 | 備考 |
|---|---|---|
| race_id | string | レースの _id |
| article_id | string | article._id |
レスポンス
200 OK
記事スキーマを返します。
例外処理
| 説明 | ステータスコード | ステータス名 |
|---|---|---|
| 記事が存在しない | 404 | Not Found |
| 記事の race_id がパスの race_id と一致しない | 404 | Not Found |
| 内部サーバーエラー | 500 | Internal Server Error |
flowchart TD
Start([GET /v1/races/race_id/articles/article_id]) --> Fetch[DBから検索]
Fetch -->|存在しない| E404[404 Not Found]
Fetch -->|存在する| RaceCheck[race_id の一致確認]
RaceCheck -->|不一致| E404_race[404 Not Found]
RaceCheck -->|一致| Success[200 OK]
PUT /v1/articles/{article_id}
概要
指定した記事を更新します。
リクエストとレスポンス
ヘッダー
リクエストヘッダー
Content-Type: application/json
URI
| パスパラメータ | データ型 | 備考 |
|---|---|---|
| article_id | string | _id |
リクエストボディ
{
"race_id": "string",
"title": "string",
"body_html": "string",
"status": "published",
"opening_at": 1234567890000,
"closing_at": 1234567890000,
"accuracy_test_data": {}
}
| フィールド | データ型 | 必須 | 備考 |
|---|---|---|---|
| race_id | string | auto_racing / bicycle_racing / horse_racing テーブルの _id |
|
| title | string | ||
| body_html | string | HTML形式 | |
| status | enum | draft / published。generating / failed は指定できない |
|
| opening_at | number | UNIXタイムスタンプ(ミリ秒) | |
| closing_at | number | UNIXタイムスタンプ(ミリ秒) | |
| accuracy_test_data | object | 精度検証用データ。構造は article テーブル を参照 |
指定されなかった(null の)フィールドは更新しません。
body_html と accuracy_test_data は一緒に直す
本文を機械的に直したとき(「逃げ切り」→「押し切り」など)に body_html だけを更新すると、
監査(scripts/article_accuracy_test/audit.py)が読む accuracy_test_data が古いまま残り、
表示と検証結果が食い違います。両方を1回の更新で揃えられるようにしてあります。
レスポンス
200 OK
更新後の記事スキーマを返します。
例外処理
| 説明 | ステータスコード | ステータス名 |
|---|---|---|
| リクエストボディが不正 | 422 | Unprocessable Entity |
status に generating / failed を指定した |
400 | Bad Request |
| race_id 指定時、レースが存在しない | 400 | Bad Request |
| 記事が存在しない | 404 | Not Found |
| 内部サーバーエラー | 500 | Internal Server Error |
DELETE /v1/articles/{article_id}
概要
指定した記事を論理削除します。
リクエストとレスポンス
URI
レスポンス
204 No Content
例外処理
| 説明 | ステータスコード | ステータス名 |
|---|---|---|
| 記事が存在しない | 404 | Not Found |
| 内部サーバーエラー | 500 | Internal Server Error |