• #todo
  • #development
  • #chart
  • #fastapi
  • #hono
  • #htmx
未分類

開発TODO

Sankey Diagram(サンキーダイアグラム)の作成

財務データの流れを可視化するSankey Diagramを作成する。 alt text

参考イメージ

  • 左側:収益の内訳(Subscription, Product, Services & other)
  • 中央:Total Revenue → Gross Profit → Operating Income
  • 右側:費用の内訳(Cost of revenue, S&M, R&D, G&A, Taxes, Interest expense)と Net Income

表示する指標

項目
Total revenue$6.0B, 9% Y/Y
Gross profit$5.4B, 10% Y/Y
Operating income$2.3B, 5% Y/Y
Net income$1.8B, 6% Y/Y
Operating expenses$3.2B, 10% Y/Y

実装候補ライブラリ

  • D3.js d3-sankey - 最も柔軟、カスタマイズ性高い
  • Apache ECharts - sankey chart対応、設定が簡単
  • Plotly.js - インタラクティブなsankey対応

タスク

  • ライブラリ選定
  • サンプルデータ構造の設計
  • Vue コンポーネント作成
  • 実際の財務データで表示

actual-consensus ページへのデータテーブル機能追加

既存の actual-consensus ページに nvidia-financial-chart のようなデータテーブル+チャート連動機能を追加する。

📎 対象ページ: https://log.eurekapu.com/financial-quiz/actual-consensus 📎 参考実装: https://log.eurekapu.com/financial-data/nvidia-financial-chart

実装する機能

  1. データテーブル表示
    • チャート下部に全指標のデータテーブルを表示
    • 四半期ごとの時系列データ
  2. 指標のチェックボックス選択
    • チェックを付けた指標のみグラフに表示
    • Metricsセクションで表示/非表示を切り替え
  3. 左軸・右軸の切り替え機能(重要)
    • 左軸: 金額ベースの指標(売上、利益など)
    • 右軸: マージン率・成長率
    • 「L/R」ボタンで軸の割り当てを操作

タスク

  • nvidia-financial-chart の実装を参考に設計
  • データテーブルコンポーネント作成
  • 指標選択UI(チェックボックス)実装
  • 左軸/右軸切り替え機能実装
  • actual-consensus ページに統合

FastAPI + htmx プロトタイプ開発(別プロジェクト)

AIスタートアップの初期フェーズ向けモック開発スタック。

📎 参考: https://zenn.dev/livetoon/articles/04dccf642d324c

なぜ FastAPI + htmx?

  • Python統一: AI/LLM開発でPython必須の環境に最適
  • セットアップ最小限: pip install fastapi uvicorn jinja2 のみ
  • 単一サーバー: CORS設定不要、管理が楽
  • htmx: HTML属性だけでJS不要の非同期通信

技術スタック

カテゴリ技術
BackendFastAPI
Frontendhtmx + Jinja2
CSSTailwind CSS + DaisyUI
ORMSQLModel

htmx 実装パターン例

  • 定期自動更新(hx-trigger="every 5s"
  • フィルター検索
  • SSEストリーミング対応
  • 無限スクロール

SQLModel - DBモデルとAPIスキーマの統一

📎 参考: https://zenn.dev/livetoon/articles/9923c448c2734c

FastAPI作者が開発したORM。Pydantic + SQLAlchemy の融合。

革新的ポイント:

  • 二重管理不要: 1つのクラスでDBモデルとAPIスキーマを兼用
  • table=True でDBモデル、外せばPydanticモデル
  • 型ヒントがそのままDBの型に変換
  • Relationshipで親子関係を直感的に定義
  • 非同期処理対応(aiosqlite)
from sqlmodel import SQLModel, Field

class User(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    email: str = Field(index=True)

タスク

  • 新規プロジェクト作成(別リポジトリ)
  • FastAPI + htmx 環境セットアップ
  • SQLModelでモデル定義
  • 基本的なCRUDサンプル作成
  • htmxパターン(自動更新、検索、SSE)を試す

Hono + htmx + Cloudflare スタック(別プロジェクト候補)

Hono作者 yusukebe 提唱のシンプル&高速スタック。

📎 参考: https://zenn.dev/yusukebe/articles/e8ff26c8507799

技術スタック

カテゴリ技術
BackendHono + JSX(サーバーサイド)
Frontendhtmx
HostingCloudflare Workers(エッジ)
DatabaseCloudflare D1(SQLite)
ValidationZod
CSSTailwind CSS

なぜ Hono + htmx?

  • JSXをサーバーサイドテンプレートとして使用: ハイドレーション不要で高速
  • エッジ実行: Cloudflare Workersで低レイテンシ
  • 超軽量: TODOアプリが100行程度、Gzip時22KB
  • PHP/Rails的アプローチの現代版: シンプルなSSR + htmxでSPA不要

FastAPI vs Hono 比較

観点FastAPI + htmxHono + htmx
言語PythonTypeScript
用途AI/LLM開発向けエッジ・軽量アプリ向け
デプロイ任意のPythonホストCloudflare Workers
テンプレートJinja2JSX

タスク

  • Hono + htmx のサンプル作成を検討
  • D1との連携を試す

アニメーション動画のMP4変換バックエンド

現在のWebM出力をMP4に変換するバックエンド機能の検討。

現状の課題

  • ブラウザ側でWebM生成 → MP4への変換が必要
  • MP4の方が互換性が高い(iOS Safari、SNS共有など)

技術選定: FastAPI + FFmpeg

観点Python (FastAPI)Hono (Cloudflare Workers)
FFmpeg実行✅ 簡単(subprocess)❌ Workers不可
処理場所サーバー/VPSエッジでは無理
実装の楽さ✅ 楽△ 外部サービス必要

結論: MP4変換には FastAPI + FFmpeg が最適

Cloudflare Workers はサーバーレス/エッジの制約でFFmpegを直接実行できない。 動画変換は重い処理のため、サーバーフル環境が必要。

推奨アーキテクチャ

┌─────────────────────────────────────────────────────────┐
│  フロントエンド: Nuxt (SSR/SSG)                          │
│  - SEO対応(企業ページは静的生成)                        │
│  - 財務データ閲覧は誰でも可能                             │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│  動画ダウンロード機能(会員限定)                         │
│  - 会員登録 → ログイン必須                               │
│  - WebM生成(ブラウザ)→ FastAPI に送信                  │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│  バックエンド: FastAPI + FFmpeg                          │
│  - WebM → MP4 変換                                      │
│  - ホスト: Railway / Render / EC2 など                   │
└─────────────────────────────────────────────────────────┘

ビジネスモデル

機能アクセス
財務データ閲覧無料(SEO集客)
アニメーション再生無料
MP4動画ダウンロード会員登録必須

FastAPI 実装例

from fastapi import FastAPI, UploadFile
import subprocess
import tempfile

app = FastAPI()

@app.post("/api/convert-to-mp4")
async def convert_to_mp4(file: UploadFile):
    with tempfile.NamedTemporaryFile(suffix=".webm") as input_file:
        input_file.write(await file.read())
        output_path = input_file.name.replace(".webm", ".mp4")

        subprocess.run([
            "ffmpeg", "-i", input_file.name,
            "-c:v", "libx264", "-preset", "fast",
            "-c:a", "aac",
            output_path
        ])

        return FileResponse(output_path, filename="animation.mp4")

重要: 静的MP4 vs 動的生成の判断

まず考えるべきこと: 本当にバックエンドが必要か?

パターン A: 静的MP4配信(バックエンド不要)⭐推奨

適用条件:

  • データが固定(決算期ごとの更新のみ)
  • ユーザーがカスタマイズしない
  • 全ユーザーに同じ動画を提供

現在の財務データページ(QQQ構成銘柄等)はこれに該当!

❌ 非効率(毎回サーバーで生成)
ユーザーがボタン押す → WebM生成 → サーバーでMP4変換 → ダウンロード
(毎回同じ動画を生成...コストと時間の無駄!)

✅ 効率的(事前生成して静的配信)
事前にローカルでMP4生成 → /public/videos/ に配置 → ボタンでダウンロード

メリット:

  • サーバーコスト $0(Cloudflare Pagesの静的配信のみ)
  • ダウンロード 爆速(生成待ち時間なし)
  • バックエンド 不要

運用フロー:

決算データ更新時(四半期ごと)
    ↓
ローカルでMP4生成(100社分をバッチ処理)
    ↓
/public/videos/GOOGL-2024.mp4 等に配置
    ↓
git push → Cloudflare Pagesにデプロイ

パターン B: 動的生成(バックエンド必要)

適用条件:

  • ユーザーが期間を自由に選べる
  • ユーザーが表示指標をカスタマイズできる
  • リアルタイムデータを使用する
  • ユーザー固有のデータで動画生成

この場合のみ Railway 等のバックエンドが必要

┌────────────────────────────────────────┐
│  Cloudflare Pages (Nuxt)               │
│  - フロントエンド                       │
└────────────────────────────────────────┘
                  │
                  ▼ API呼び出し
┌────────────────────────────────────────┐
│  Railway ($5/月)                        │
│  - FastAPI + FFmpeg                    │
│  - MP4変換処理                          │
└────────────────────────────────────────┘

バックエンドホスティングの詳細: バックエンドホスティングサービス比較


現プロジェクトの結論

QQQ構成銘柄ページ → パターン A(静的MP4)を採用

  • データは固定(決算期更新のみ)
  • カスタマイズ機能なし
  • バックエンド不要、コスト $0

プロジェクト構成(モノレポ)

フロントエンドとバックエンドを1つのリポジトリで管理し、それぞれ別のサービスにデプロイ。


パターン A: 新規プロジェクトの場合

financial-app/
├── README.md
├── .gitignore
│
├── frontend/                  # Nuxt アプリ → Cloudflare Pages
│   ├── nuxt.config.ts
│   ├── package.json
│   ├── .env.example
│   ├── app/
│   │   ├── pages/
│   │   ├── components/
│   │   └── composables/
│   ├── content/               # MDXコンテンツ
│   └── public/
│
├── backend/                   # FastAPI → Render / Railway
│   ├── main.py                # FastAPIエントリポイント
│   ├── requirements.txt       # Python依存関係
│   ├── Dockerfile             # コンテナ化(任意)
│   ├── .env.example
│   ├── app/
│   │   ├── __init__.py
│   │   ├── api/
│   │   │   ├── __init__.py
│   │   │   ├── routes/
│   │   │   │   ├── convert.py     # 動画変換API
│   │   │   │   └── auth.py        # 認証API
│   │   │   └── deps.py            # 依存性注入
│   │   ├── core/
│   │   │   ├── config.py          # 設定
│   │   │   └── security.py        # JWT等
│   │   └── services/
│   │       └── ffmpeg.py          # FFmpeg処理
│   └── tests/
│
└── docs/                      # 共有ドキュメント(任意)

パターン B: 現在のプロジェクト(mdx-playground)の場合

すでにモノレポ構成が整っている!

mdx-playground/
├── apps/
│   ├── web/                   # フロントエンド(Nuxt)→ Cloudflare Pages
│   │   ├── nuxt.config.ts
│   │   ├── package.json
│   │   ├── app/
│   │   │   ├── pages/
│   │   │   ├── components/
│   │   │   └── composables/
│   │   └── content/
│   │
│   └── api/                   # バックエンド(FastAPI)→ Render / Railway
│       ├── main.py            # ← 追加
│       ├── requirements.txt   # ← 追加
│       ├── app/               # ← 追加
│       │   └── services/
│       │       └── ffmpeg.py
│       └── README.md          # 既存(空)
│
├── packages/                  # 共有パッケージ(必要に応じて)
└── ...

対応表:

新規プロジェクト現在のプロジェクト役割
frontend/apps/web/Nuxt フロントエンド
backend/apps/api/FastAPI バックエンド

メリット: 別プロジェクト作成不要!

apps/api/ ディレクトリは現在ほぼ空(README.mdのみ)なので、 ここにFastAPIをセットアップすれば今すぐバックエンド追加可能


現プロジェクトでのデプロイ設定

Cloudflare Pages(apps/web) - 既存設定そのまま

Render(apps/api) - 新規追加

Root Directory: apps/api
Build Command: pip install -r requirements.txt
Start Command: uvicorn main:app --host 0.0.0.0 --port $PORT

デプロイフロー

┌─────────────────────────────────────────────────────────┐
│                   GitHub リポジトリ                      │
│                   financial-app/                        │
└─────────────────────────────────────────────────────────┘
                │                       │
                ▼                       ▼
┌──────────────────────┐    ┌──────────────────────┐
│   Cloudflare Pages   │    │   Render / Railway   │
│   (frontend/)        │    │   (backend/)         │
│                      │    │                      │
│   Root: /frontend    │    │   Root: /backend     │
│   Build: pnpm build  │    │   Build: pip install │
│   Output: .output    │    │   Start: uvicorn     │
└──────────────────────┘    └──────────────────────┘
         │                            │
         │      API呼び出し            │
         └────────────────────────────┘

各サービスの設定

Cloudflare Pages(frontend)

Build command: cd frontend && pnpm install && pnpm build
Build output directory: frontend/.output/public
Root directory: /

または Root directory を frontend に設定:

Root directory: frontend
Build command: pnpm install && pnpm build
Build output directory: .output/public

Render(backend)

# render.yaml(任意)
services:
  - type: web
    name: financial-api
    env: python
    rootDir: backend
    buildCommand: pip install -r requirements.txt
    startCommand: uvicorn main:app --host 0.0.0.0 --port $PORT

または手動設定:

Root Directory: backend
Build Command: pip install -r requirements.txt
Start Command: uvicorn main:app --host 0.0.0.0 --port $PORT

環境変数の繋ぎ込み

frontend/.env

# 開発時(ローカル)
NUXT_PUBLIC_API_URL=http://localhost:8000

# 本番(Render)
NUXT_PUBLIC_API_URL=https://financial-api.onrender.com

# 本番(Railway移行後)
NUXT_PUBLIC_API_URL=https://financial-api.up.railway.app

backend/.env

# CORS許可するフロントエンドのURL
FRONTEND_URL=https://your-app.pages.dev

# JWT認証用シークレット
SECRET_KEY=your-secret-key

# その他
DEBUG=false

backend/main.py(最小構成)

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import os

app = FastAPI()

# CORS設定(フロントエンドからのアクセスを許可)
app.add_middleware(
    CORSMiddleware,
    allow_origins=[os.getenv("FRONTEND_URL", "http://localhost:3000")],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/health")
def health_check():
    return {"status": "ok"}

@app.post("/api/convert-to-mp4")
async def convert_to_mp4(file: UploadFile):
    # FFmpeg変換処理
    pass

frontend での API 呼び出し

// frontend/app/composables/useApi.ts
export const useApi = () => {
  const config = useRuntimeConfig()
  const apiUrl = config.public.apiUrl  // NUXT_PUBLIC_API_URL

  const convertToMp4 = async (webmBlob: Blob) => {
    const formData = new FormData()
    formData.append('file', webmBlob, 'animation.webm')

    const response = await fetch(`${apiUrl}/api/convert-to-mp4`, {
      method: 'POST',
      body: formData,
      credentials: 'include',  // 認証Cookie送信
    })

    return response.blob()
  }

  return { convertToMp4 }
}

タスク

  • 新規リポジトリ作成(financial-app)
  • frontend/ ディレクトリにNuxtセットアップ
  • backend/ ディレクトリにFastAPIセットアップ
  • FFmpeg 変換エンドポイント実装
  • CORS設定
  • 認証機能(会員登録/ログイン)
  • Nuxt側の会員限定UI実装
  • Render にバックエンドをデプロイ
  • Cloudflare Pages にフロントエンドをデプロイ
  • 環境変数で繋ぎ込み
  • ユーザー増加時に Railway へ移行