コンテンツにスキップ

プロジェクト構成

リポジトリ全体

jester-backend は uv のワークスペースで構成されたモノレポです。 apps/ に Lambda(および FastAPI アプリ)、packages/ に共有ロジックを置きます。

jester-backend/
├── apps/
│   ├── odds_poc_app/            # FastAPI アプリ(Backend API / App Lambda)
│   ├── article_generation/      # Lambda: レース結果10秒サマリーの生成(Python)
│   ├── article_builder/         # Lambda: 記事 HTML の構築・保存(TypeScript / Node.js)
│   ├── blog_generation/         # Lambda: レース回顧・予想ブログの書き下ろし(Python)
│   ├── blog_rewriter/           # Lambda: 過去ブログのリライト(Python)
│   ├── blog_builder/            # Lambda: ブログ HTML の構築・保存(TypeScript / Node.js)
│   └── thumbnail_generation/    # Lambda: サムネイル生成(Python)
├── packages/                    # 共有パッケージ(→ 共有パッケージのページを参照)
│   ├── models/                  # DocumentDB モデル(Beanie)
│   ├── race_result_facts/       # レース結果の決定論的な解釈・照合
│   ├── race_analysis_agent/     # レース総合解析エージェント
│   ├── media_analysis_sdk/      # Bedrock メディア解析 SDK
│   ├── article_generation_agent/
│   ├── blog_generation_agent/
│   ├── blog_parts/              # ブロック JSON・着順テーブル・レースカード
│   ├── thumbnail_generation_agent/
│   ├── ng_word_filter/
│   └── tone_of_voice/
├── bin/                         # 運用スクリプト(一括登録・アップロード等)
├── scripts/                     # スクレイピング・精度検証スクリプト
├── data/                        # ローカル作業用データ
├── api-collections/             # API クライアントのコレクション
├── .github/workflows/           # Lambda ごとのデプロイワークフロー
├── pyproject.toml               # uv ワークスペース定義
├── ruff.toml / mypy.ini         # Lint / 型チェック設定
└── mise.toml                    # ツールバージョン管理

odds_poc_app(FastAPI)

apps/odds_poc_app/
├── pyproject.toml
└── src/
    ├── app.py                   # FastAPI アプリ設定・Mangum ハンドラ
    ├── config/env.py            # pydantic-settings による設定
    ├── constants/pagination.py  # ページネーションの既定値
    ├── libs/                    # 外部サービスクライアント
    │   ├── bedrock.py           # 埋め込みベクトル生成
    │   ├── documentdb.py        # Beanie 初期化
    │   ├── s3.py                # アップロード・署名付き URL 生成
    │   └── sqs.py               # ジョブ投入
    ├── middlewares/
    ├── repositories/            # データアクセス層(コレクションごと)
    ├── routes/
    │   └── v1/                  # API v1 ルート(article / blog / category / media / ng_word / old_blog / ping / race)
    ├── schemas/                 # リクエスト・レスポンスの Pydantic モデル
    ├── services/                # ビジネスロジック
    └── utils/                   # embedding / race_result / related_info

エントリーポイントは src/app.py です。ENVIRONMENT が local 以外のとき、 Mangum が API Gateway のイベントを FastAPI へ変換します。 ルートは /v1 プレフィックスで routes/__init__.py の load_router() がまとめて登録します。


Lambda アプリ(Python)

article_generation / blog_generation / blog_rewriter / thumbnail_generation は、 HTTP ルーティングを持たないイベント駆動型の構成です。

apps/article_generation/          # 例: 記事生成 Lambda
├── pyproject.toml
├── handler.py                    # Lambda エントリーポイント
├── docker/Dockerfile
└── src/
    ├── config/env.py             # pydantic-settings による設定
    ├── schemas/event.py          # SQS イベントのペイロード型定義
    ├── services/                 # ビジネスロジック
    ├── repositories/             # データアクセス層
    └── libs/                     # 外部サービスクライアント
        ├── bedrock.py
        ├── documentdb.py
        ├── s3.py
        ├── sqs.py
        └── sqs_retry.py          # 最終試行かどうかの判定

各ファイルの役割

ファイル 役割
handler.py lambda_handler(event, context) を定義する。SQS レコードのパースと services の呼び出しのみを行う
src/config/env.py 環境変数を pydantic-settings で管理する
src/schemas/event.py SQS イベントのペイロードを Pydantic モデルで型定義・バリデーションする
src/services/ ビジネスロジック。handler.py から呼ばれ、repositories と libs を使う
src/repositories/ DocumentDB へのデータアクセス。services からのみ呼ばれる
src/libs/ Bedrock・S3・SQS・DocumentDB のクライアント初期化とラッパー
src/libs/sqs_retry.py ApproximateReceiveCount から「この配信が最終試行か」を判定する

handler.py の構造

import asyncio
from typing import Any

from apps.article_generation.src.libs import sqs_retry
from apps.article_generation.src.schemas.event import ArticleGenerationPayload
from apps.article_generation.src.services import article as article_service


def lambda_handler(event: dict[str, Any], context: object) -> None:
    for record in event["Records"]:
        payload = ArticleGenerationPayload.model_validate_json(record["body"])
        asyncio.run(
            article_service.generate(
                payload,
                discard_on_error=sqs_retry.is_final_attempt(record),
            )
        )

discard_on_error は SQS の最終試行のときだけ True になります。 最終試行でなければ失敗しても何もせず、SQS の再配信による自己復旧に委ねます。

層間の呼び出し規則

handler.py
  └─► services/        (ビジネスロジック)
        ├─► repositories/  (DB アクセス)
        ├─► libs/          (外部サービス: Bedrock / S3 / SQS)
        └─► packages/      (共有ロジック: エージェント・事実照合など)
  • handler.py は services のみを呼び出す
  • services は repositories・libs・packages を呼び出す
  • repositories は libs を呼び出してよいが、services は呼び出してはいけない
  • packages/models/ の DocumentDB モデルは repositories から参照する

Lambda アプリ(TypeScript)

article_builder / blog_builder は React コンポーネントで HTML を構築するため、 TypeScript(Node.js 22)で実装します。

apps/blog_builder/
├── package.json
├── tsconfig.json
├── handler.ts                    # Lambda エントリーポイント
├── docker/Dockerfile
├── scripts/
│   ├── esbuild-options.mjs       # バンドル設定(SCSS・アイコン allowlist 等)
│   └── sync-oddspark.mjs         # oddspark-static-pages からの同期
├── vendor/oddspark/              # 同期したコンポーネント・SCSS・アセット(コミット対象)
└── src/
    ├── article/                  # 記事テンプレートとブロック描画
    ├── config/env.ts
    ├── libs/
    │   ├── documentdb.ts         # mongodb クライアント(再接続対応)
    │   └── sqs-retry.ts          # 最終試行かどうかの判定
    ├── repositories/
    ├── schemas/event.ts          # zod による SQS ペイロード検証
    └── services/

article_builder も同じ構成ですが、vendor/ を持たず、 src/components/race-summary/ に自前のサマリーコンポーネントを置きます。


odds_poc_app と Lambda アプリの違い

項目 odds_poc_app(FastAPI) Lambda アプリ
エントリーポイント src/app.py(FastAPI + Mangum) handler.py / handler.ts
ルーティング routes/ 層 なし(ハンドラが直接受け取る)
イベント型 HTTP Request(API Gateway) SQS メッセージ
レスポンス HTTP Response なし(次の SQS へ送信、または DB に保存)
失敗時 HTTP エラーを返す 最終試行かどうかで挙動を分ける(再送出 or 破棄・失敗記録)

テスト

テストは対象パッケージ直下に test_*.py として置き、uv run python <path> で実行します。

ファイル 対象
apps/odds_poc_app/test_related_info.py 関連情報セクションの組み立て
packages/article_generation_agent/test_placeholders.py プレースホルダー差し込み
packages/blog_generation_agent/test_main.py ブログ執筆の回帰テスト
packages/race_result_facts/test_checks.py 事実照合
packages/race_result_facts/test_prediction.py 予想データの扱い
packages/race_result_facts/test_source_mix.py 記事の由来(レース結果 / 動画)の測定

TypeScript 側(article_builder / blog_builder)は npm run typecheck / npm run lint と ビルド成功・描画確認で担保します。