代理店向け事業戦略(鰻人モデルケース)とUX分析を前提に、素材投入→自動編集→多言語化→エンドユーザー承認→投稿を一連で完結させる制作エンジンを、エンジニアが実装できる粒度で定義する。操作者はA(PC・制作)とB(スマホ・確認)に分離し、承認者はエンドユーザー。統制原則は「計算=script/判断=AI/実行=人の承認後」。
目的:代理店が外注している「制作の一段」を、品質×速度のAI制作エンジンで白label置換する。MVPは鰻人(うなぎ専門店)を証明台に、一連の流れを実際に完結させる。素材はクライアントのテスト素材が主(我々のデフォルト素材から生成・整形はしない)。勝ち筋はリサーチ→分析でAIが仮説する(v0.2で詳細化)。
| 用語 | 意味 |
|---|---|
| エンドクライアント | 飲食店(鰻人・ジンミダン)。証明の舞台。素材(mov)の供給元 |
| 代理店 | 主顧客。白labelで制作エンジンを使う(MVPでは未接続、データモデルには用意) |
| 勝ち型テンプレ | 実績で勝った投稿の型(フック・3幕構成・shot-list・字幕・音) |
| 特徴DB(StoreFeature) | 店舗ごとの独自要素。パクリ9割に対し独自1割の源泉 |
| 操作者A(作る人) | 代理店の運用担当(または社内)。PCで制作・比較・確認依頼を行う |
| 操作者B(確かめる人) | 店舗オーナー。スマホで数秒見て2択で承認/指摘する |
| 承認ゲート | 投稿など後戻り不可の行為の前に必須で挟む承認。承認者はエンドユーザー(A または B) |
| 確認依頼 | AがBへ送るリンク1つの依頼。開封状況が送り手に見える |
| 司令塔 | オーケストレーター。現状Claude Code、製品化はClaude Agent SDK |
各要件に受け入れ基準を付す。MVPは FR-01〜FR-10 を必須、FR-11/12 は最小実装。
| ID | 要件 | 詳細 | 受け入れ基準 |
|---|---|---|---|
| FR-01 | 依頼受付 | クライアント・対象言語・本数・テンプレを指定してジョブ生成 | ジョブが status=received で作成され、必須項目が検証される |
| FR-02 | 素材取込 | mov/画像をアップロードしAssetとして登録(ストレージ格納) | 公開URLが払い出され、Jobに紐付く |
| FR-03 | 素材解析・QC | Visionでシーン分類・内容タグ(例:unagi/蒲焼/炭火)・品質チェック・使用可能尺の抽出 | 各Assetにvision_tags・shot_role・qc_statusが付与される |
| FR-04 | 戦略・型選択 | 業種と特徴DBから勝ち型テンプレを選択し、独自要素を1割抽出(9:1) | compose_brief(template_id+applied_original_elements)が生成される |
| FR-05 | 自動編集(複数案) | clips+仮説→9:16リール/4:5ポストを複数案生成(1案提示にしない) | 1ジョブにつき2案以上のRenderが生成され、差分が比較できる |
| FR-06 | 多言語化 | 日/英/韓/繁中の字幕・オーバーレイ・TTSナレーションを差し替え | 4言語分の ReelRender が揃う(文字化けなし) |
| FR-07 | キャプション生成 | 型+言語+独自要素からキャプション・ハッシュタグを生成 | 言語別キャプションが Post ドラフトに保存される |
| FR-08 | 承認(エンドユーザー) | プレビュー→承認/差戻し。承認ゲート。承認者はA または B | 承認なしに公開へ進めない。誰がいつ承認したかがApprovalに残り画面に表示される |
| FR-13 | 確認依頼フロー | AがBへ確認リンクを送信。開封/視聴状況をAが確認。催促は自動 | リンク1つでBが開けて即再生。開封状況がAの画面に反映される |
| FR-14 | 選択肢式の指摘 | 差戻し理由を候補から選択(映り/情報が違う/表現が強い等)。自由記述は任意 | Bが記述なしで差戻しでき、理由が構造化データで残る |
| FR-15 | 対訳の確認 | 原文(日本語)と訳を対応表示し、確認済みの印と確認者を記録 | 言語ごとに対訳が並び、確認済み状態が可視化される |
| FR-16 | 成果の言い換え表示 | 専門用語を使わない表現へ翻訳し、前回との比較を添える | B向け画面にreach等の語が出ず、前回比が表示される |
| FR-17 | スマホからの素材投入 | Bがその場で撮影/選択して送信。形式を問わず受ける | スマホから送った素材がAssetとしてジョブに紐づく |
| FR-18 | 契約前トライアル入口 | 登録・契約を挟まず素材を渡し、試作を見られる導線 | 未登録状態で素材投入→試作閲覧まで到達できる |
| FR-19 | 次の一手の提示 | 実績を次の候補生成に反映し「次はこれ」を提示 | 実績反映後、次案の推奨がAの画面に表示される |
| FR-20 | 店舗コンテキストの明示 | 常にどの店舗の作業中かを表示し、素材・成果物を店舗単位で隔離 | 画面上で店舗が常時視認でき、他店舗の素材が混ざらない |
| FR-09 | 投稿・予約 | Meta Graph APIでリール公開/予約(承認後のみ) | ig_media_id・permalinkが保存され status=published |
| FR-10 | 分析取得 | Insights取得(reach/plays/保存/プロフ訪問/海外リーチ比) | Post に日次でInsightが記録される |
| FR-11 | ナレッジ更新 | 実績→勝ち型テンプレのスコア更新・店舗別ルール更新(当初手動トリガ) | performance_score/KnowledgeRuleが更新できる |
| FR-12 | オペログ | 各ステージの時刻・担当・処理・コストを記録(監査) | 1ジョブの全ステージが時系列で追える |
| ID | 分類 | 要件 |
|---|---|---|
| NFR-01 | 統制 | 実行(投稿/送信)は承認ゲート通過後のみ。計算はscript・判断はAIに分離 |
| NFR-02 | 安全 | API はGET中心、書込は承認後。エージェントにターン上限・スペンド上限・最小権限 |
| NFR-03 | 信頼性 | 各ステージは冪等(idempotency key)+リトライ可能。長時間処理は非同期ジョブ |
| NFR-04 | 監査 | 全ステージの入出力・ツール・コストをOperationLogに保存 |
| NFR-05 | 拡張性 | 各ステージ・各モデル/ツールはアダプタで疎結合。差し替え・追加が容易 |
| NFR-06 | スケール | 将来のマルチテナント(代理店/店舗単位のデータ分離)を壊さないスキーマ設計 |
| NFR-07 | デプロイ | Git push→自動デプロイ(Cloudflare)。CI/CDでテスト→本番。IaCで再現可能 |
| NFR-08 | レート/コスト | Meta投稿レート(~100/24h/アカ)を管理。生成AIコストを可観測化 |
| NFR-10 | UI原則 | 説明せず見せる/作らせず選ばせる/言語化を強いない/勝手に進まない/1画面1判断(特にB)。詳細編集は初期画面に出さず、要求時に展開 |
| NFR-11 | 画面分離 | A=PC(情報量多・制御)、B=スマホ(情報量最小・2択)。同じ機能を両方に載せない |
| NFR-12 | 内部機能の露出範囲 | エージェント名編集・API選択・ワークフロー設定は社内/A限定。Bには出さない |
| NFR-09 | 権利・規約 | 素材/肖像/音楽ライセンス、景表法・薬機法、Metaポリシー遵守のチェック項目 |
レイヤードに分離。司令塔(オーケストレーター)がジョブを制御し、各処理はアダプタ経由で外部AI/ツール/APIを呼ぶ。
# レイヤー構成 [UI層] 承認UI / 依頼UI / ジョブ実行ビュー / オペログ (Cloudflare Pages) | REST/WebSocket [API層] Job API / Approval API / Webhook受信 (Workers / Container) | [オーケストレーター] 司令塔 = Claude Code(現) -> Agent SDK(製品) | ステージ実行・状態遷移・承認待ち・リトライ |--- サブエージェント(部署): 解析 / 制作 / 多言語 / 分析 [アダプタ層] モデル/ツール/APIを抽象化(差し替え可能) |--- Vision(Claude/Gemini) --- 動画編集(Shotstack/Creatomate) |--- LLM(Claude) --- 画像(GPT Image2/Nano Banana/Seedream) |--- TTS(ElevenLabs) --- Meta Graph/Insights/Messaging API [データ層] Postgres(業務データ) / R2(メディア) / Queue(非同期) / KVS(状態)
PostgreSQL想定。将来のマルチテナントに備え agency_id / client_id を保持(MVPでは agency_id は null 可)。
agency( -- 代理店(白label顧客・将来) id uuid pk, name text, brand_logo_url text, contact jsonb, created_at timestamptz) client( -- エンドクライアント(店舗) id uuid pk, agency_id uuid null fk->agency, name text, cuisine_type text, -- 業種: 'unagi' google_place_id text, target_languages text[], -- ['ja','en','ko','zh-Hant'] ig_account_id text, -- 投稿先IGアカウント(誰名義かは未決) created_at timestamptz) brand_profile( -- ブランドの声・世界観 id uuid pk, client_id uuid fk->client, brand_voice text, world_view text, signature_menus jsonb, -- 蒲焼/白焼/ひつまぶし… ng_list jsonb, updated_at timestamptz) store_feature( -- 特徴DB = 独自1割の源泉 id uuid pk, client_id uuid fk->client, feature_tags jsonb, -- ['三度の食べ方','炭火','老舗'] strengths jsonb, unique_elements jsonb, -- 独自要素の候補 discovery_gap text, updated_at timestamptz) template( -- 勝ち型テンプレ / WinningPattern id uuid pk, name text, vertical text, -- 'unagi'/'yakiniku'… structure jsonb, -- 3幕: hook/guide/info shot_list jsonb, -- 必要ショット定義 caption_template text, hashtag_set jsonb, subtitle_style jsonb, music_style text, performance_score numeric, scope text, -- global/vertical/client client_id uuid null, updated_at timestamptz) asset( -- 素材(clip/photo) id uuid pk, client_id uuid fk->client, type text, -- 'video'/'photo' storage_url text, public_url text, duration_ms int, vision_tags jsonb, -- {scene, foods:['unagi'], ...} shot_role text, quality_score numeric, qc_status text, -- pending/ok/rejected created_at timestamptz) job( -- 依頼(1リクエスト) id uuid pk, client_id uuid fk->client, requested_by text, target_languages text[], count int, template_id uuid null fk->template, compose_brief jsonb, -- 型+独自要素(9:1) status text, -- enum(下記) priority text, created_at, updated_at timestamptz) -- status enum: received|setup|analyzing|strategizing|producing| -- localizing|awaiting_qa|approved|publishing|published|analyzing_insights|failed job_stage( -- ステージ実行ログ(冪等/リトライ) id uuid pk, job_id uuid fk->job, stage text, status text, -- pending/running/done/failed/waiting_approval idempotency_key text, input jsonb, output jsonb, tool text, cost numeric, error text, started_at, finished_at timestamptz) reel_render( -- 言語別の完成リール id uuid pk, job_id uuid fk->job, language text, video_url text, duration_ms int, thumbnail_url text, subtitle_track jsonb, voiceover_url text, spec jsonb, -- {ratio:'9:16',codec:'h264'} status text, created_at timestamptz) approval( -- 承認ゲートの記録(承認者=エンドユーザー) id uuid pk, job_id uuid, reel_render_id uuid, reviewer text, reviewer_role text, -- 'agency_staff'(A) / 'store_owner'(B) decision text, -- approve/reject/revise issue_codes text[], -- 選択肢式の指摘: looks/info_wrong/tone_strong… comment text, -- 自由記述は任意 target_stage text, created_at timestamptz) review_request( -- 確認依頼(A -> B、リンク1つ) id uuid pk, job_id uuid, candidate_id uuid, sent_to text, link_token text, sent_at timestamptz, opened_at timestamptz, viewed_at timestamptz, reminder_count int, status text) -- sent/opened/answered candidate( -- 複数案(1案提示にしない) id uuid pk, job_id uuid, reel_render_id uuid, variant_label text, diff_meta jsonb, -- 案ごとの差分(フック/尺/構成) selected boolean, created_at timestamptz) translation_pair( -- 原文↔訳の対応(多言語の確認) id uuid pk, reel_render_id uuid, language text, source_text text, translated_text text, verified boolean, verified_by text, verified_at timestamptz) post( -- 公開投稿 id uuid pk, job_id uuid, reel_render_id uuid, ig_account_id text, ig_media_id text, permalink text, caption text, scheduled_at, published_at timestamptz, status text) insight( -- 投稿実績(日次) id uuid pk, post_id uuid fk->post, captured_at timestamptz, reach int, plays int, saves int, avg_watch_ms int, profile_visits int, overseas_reach_ratio numeric, dm_count int) knowledge_rule( -- 学習ルール(勝ち型の根拠) id uuid pk, scope text, vertical text, client_id uuid null, language text, rule jsonb, evidence_post_ids uuid[], confidence numeric, updated_at timestamptz) operation_log( -- 監査ログ id uuid pk, job_id uuid, actor text, action text, tool text, payload jsonb, ts timestamptz)
司令塔がステージを順に実行。種別:計算/判断/実行。★=承認ゲート。各ステージは job_stage に記録し、失敗時は当該ステージから再開。
| # | ステージ | 種別 | 入力 → 出力 | 担当/ツール | ★ |
|---|---|---|---|---|---|
| 1 | received | 判断 | 依頼 → Job(検証済) | API+司令塔 | — |
| 2 | setup | 計算 | Job → 作業フォルダ・client-profile初期化 | case-setup script | — |
| 3 | analyzing | 判断 | Asset → vision_tags・shot_role・qc | Vision(Claude/Gemini) | — |
| 4 | strategizing | 判断 | 業種+特徴DB → compose_brief(型9:独自1) | Claude+template+store_feature | — |
| 5 | producing | 判断 | clips+brief → 9:16動画(基準言語) | 動画編集API | — |
| 6 | localizing | 判断 | 動画+言語 → 4言語 reel_render+caption | Claude+字幕+TTS | — |
| 7 | awaiting_qa | 実行前 | プレビュー → approve/revise/reject | 承認UI | ★ |
| 8 | publishing | 実行 | 承認済 → IG公開/予約 | Meta Graph API | ★ |
| 9 | analyzing_insights | 計算 | media_id → Insight(日次) | Insights API+集計 | — |
| 10 | knowledge_update | 判断 | 実績 → score/rule更新 | Claude+DB(手動トリガ) | — |
"生成"でなく実素材のテンプレ自動編集。鰻人の勝ち型=「無音で伝わる寄り(シズル)」→「食べ方ガイド(三度の食べ方)」→「予約・立地」の3幕。
# R1 shot-list(templateから): 必要ショットと役割 required_shots = [exterior, dish_closeup, sizzle, pour/plating, reaction] # R2 clip解析: asset.vision_tags と shot_role をマッチング(不足は警告) # R3 自動編集(動画API): テンプレJSONにclipを流し込む timeline = { ratio:'9:16', fps:30, scenes:[ {role:'sizzle', clip:asset_a, dur:1800, text_overlay:hook}, {role:'guide', clip:asset_b, dur:6000, captions:lang}, {role:'info', clip:asset_c, dur:3000, text:reserve+map} ], music: template.music_style, brand: client.logo } render(timeline) -> mp4(9:16, h264/aac, 5-90s) # R4 多言語: 字幕トラック+TTSを言語ごとに差し替えて再レンダ for lang in ['ja','en','ko','zh-Hant']: subtitles = translate(script, lang) # Claude(文化適応) voiceover = tts(script, lang) # ElevenLabs(任意) reel_render[lang] = render(timeline + subtitles + voiceover) # R5 人QA -> 承認後に確定・書き出し
再現性(勝てる型)と独自性(その店らしさ)を両立。ステージ4で compose_brief を作る際のロジック。
# 入力: client, vertical, store_feature, 勝ちtemplate候補 def build_compose_brief(client, vertical): tmpl = pick_template(vertical, scope in [client, vertical, global]) # 9割=型 # 独自1割: 特徴DBから、その店ならではの要素を1-2個レコメンド/抽出 originals = recommend_originals( # Claude store_feature.unique_elements + store_feature.feature_tags, constraint = "テンプレの3幕を壊さず、hook/captionに1割だけ差し込む") return {template_id: tmpl.id, base_structure: tmpl.structure, # 90%そのまま applied_originals: originals, # 10%差し込み ratio: "9:1"} # 学習ループ: 投稿実績 -> スコア更新 -> 店舗専用の勝ち型が育つ on insight_captured(post): update template.performance_score (by reach/save/来店反応) upsert knowledge_rule(scope='client', client_id, rule=勝ち要因) # 次回 pick_template は client scope を優先 = その店専用に賢くなる
リールは公式 Content Publishing で投稿可能。動画は公開URLでホストし、コンテナ生成→処理完了→publishの3手。
# 投稿(Reels) 1) POST /{ig_user_id}/media { media_type:'REELS', video_url:, caption, share_to_feed:true } -> { id: creation_id } 2) poll GET /{creation_id}?fields=status_code # 指数バックオフ, FINISHEDまで 3) POST /{ig_user_id}/media_publish { creation_id } -> { id: media_id } # 承認後のみ 4) GET /{media_id}/insights?metric=reach,plays,saved,total_interactions,... # 制約 - 動画: 9:16 / 5-90秒 / H.264+AAC / 公開URL必須 - レート: ~100投稿/24h/アカウント(新規は低め)→ 1日1-3本推奨 - 要件: Business/Creator + FBページ + アプリ審査(instagram_content_publish) - DM(後): 24h枠 / ~200件/時 / Webhookはレート非消費
UI↔バックエンドの契約。REST想定。ステージ進行はイベント/ポーリングで反映。
POST /jobs # 依頼作成 req: {client_id, target_languages[], count, template_id?} res: {job_id, status:'received'} POST /jobs/{id}/assets # 素材添付(署名付きURLでアップロード) GET /jobs/{id} # 状態+stages+reel_renders res: {status, stages:[{stage,status,...}], renders:[{language,video_url}]} POST /jobs/{id}/approve # 承認ゲート(承認者=A または B) req: {candidate_id, decision:'approve|revise|reject', issue_codes[], comment?, target_stage?} POST /jobs/{id}/review-requests # A -> B へ確認依頼(リンク1つ) req: {candidate_id, send_to} res: {review_request_id, link_url} GET /review/{token} # B用・未ログインで即再生できる確認画面 res: {video_url, store_name, is_prepublish:true, issue_options[]} POST /review/{token}/answer # B の2択回答 req: {decision:'ok|issue', issue_codes[], comment?} GET /jobs/{id}/candidates # 複数案と差分 GET /jobs/{id}/translations # 原文↔訳の対応・確認済み状態 POST /trials # 契約前トライアル(登録を挟まない入口) POST /jobs/{id}/publish # 承認済のみ許可。予約可 req: {reel_render_ids[], scheduled_at?} GET /jobs/{id}/insights # 実績 POST /webhooks/meta # コメント/DM受信(後) POST /webhooks/render # 動画レンダ完了通知
現状のGit→Cloudflare運用を踏襲し、スケール前提・自動デプロイ。
| 要素 | 採用(候補) | 備考 |
|---|---|---|
| UI/承認画面 | Cloudflare Pages | Git push で自動デプロイ |
| API/オーケストレーター | Cloudflare Workers / 重処理はコンテナ基盤 | Agent SDK常駐・動画レンダは実行時間制約に注意→ワーカー/コンテナ |
| メディア | Cloudflare R2 | Meta要件の公開URLを払い出し |
| キュー | Cloudflare Queues | 非同期ジョブ・リトライ |
| DB | マネージドPostgres(Neon / Supabase) | 業務データ |
| CI/CD | Git連携(テスト→デプロイ) | ステージング→本番、IaCで再現 |