💻 システム開発

SCALE_Talk_設計書

最終更新 2026年05月23日 / 31_システム開発部/SCALE_Talk/_SCALE_Talk_設計書.md

SCALE Talk 設計書

SCALE CRM から分離した「テレアポ通話録音の振り返り・成功音声共有・AI分析」専用システム。
設計の単一情報源。実装・改修時は必ずこのノートを Read してから着手する。


30秒で全体像

観点 一言
核心 CRMから音声機能を独立。自分の架電を振り返り、成功音声を聞き返す
キーワード 通話録音 / 倍速再生 / Whisper文字起こし / FBメモ / 成功音声プレイリスト / Zoom Phone同期
スタック Vanilla JS + Cloudflare Pages + D1 + Pages Functions + R2 + Workers AI
連携 SCALE CRM(メンバー同期・架電紐付け)/ Zoom Phone(録音取得)
読了目安 6分

なぜ独立させるか

大串「音声入れるとかなり重くなる。でもメンバーが自分の音声を振り返ったり、他の成功音声を聞いたりはやっぱり大事。独立した別の形で音声専用システムが欲しい」

観点 内容
「重い」の真因 旧CRMは音声メタデータを D1の架電リストキー(call_list)に内包 → 架電リスト全体が肥大化 → CRM全体が重くなった
構造的解決 独立システム化 + 音声実体をR2に隔離。D1には軽いメタデータのみ。CRMは音声を一切持たないので軽量を維持
旧機能の経緯 v9系でCRMに本格導入 → 2026-05-20 v11.5 R7d/R8 で約-1100行で全削除(重さが理由)
旧資産 lib/pages_voice.js 740行ほか。バックアップ scale-lead-pre-zoomphone-removal-2026-05-20-1129.tar.gz に完全な版が残存。移植性が高い

システム識別情報

項目
名称 SCALE Talk
本番URL https://talk.scale-group.co.jp/(初期は https://scale-talk.pages.dev/)
Cloudflareプロジェクト scale-talk
ローカルパス /Users/oogushiyuuki/株式会社SCALE/scale-talk/
D1データベース scale-talk-prod(binding名 DB
R2バケット scale-talk-recordings(binding名 BUCKET
Workers AI binding名 AI(Whisper文字起こし)
バックアップ ~/scale-talk-backups/(tar.gz・pre/post-deploy)
デプロイ cd /Users/oogushiyuuki/株式会社SCALE/scale-talk && bash deploy.sh

アーキテクチャ

[ブラウザ: SCALE Talk UI]
   |  SSO cookie(.scale-group.co.jp 共有)
   v
[Cloudflare Pages: scale-talk]
   ├─ Pages Functions /api/*
   ├─ D1: scale-talk-prod      … メタデータ(録音情報・FB・プレイリスト・メンバーキャッシュ)
   ├─ R2: scale-talk-recordings … 音声ファイル実体
   └─ Workers AI: Whisper       … 文字起こし
        |
        |  連携
        ├─→ [SCALE CRM: scale-lead] /api/talk/* … メンバー同期・架電リスト取得(トークン認証)
        └─→ [Zoom Phone] Server-to-Server OAuth → Call Recording API … 録音の自動取得

D1スキーマ(scale-talk-prod)

テーブル 役割
recordings 録音1件=1行。CRM架電に call_id/project_id で紐付け
recording_feedback 録音へのタグ付きFBメモ(良い/改善/共有候補/気付き)
playlists 成功音声プレイリスト(PMが共有)
playlist_items プレイリスト所属の録音
members_cache CRMから同期したメンバー(別登録不要にするため)
zoom_sync_log Zoom Phone同期の実行ログ
app_data 汎用KV(設定・Zoomトークンキャッシュ等)
CREATE TABLE IF NOT EXISTS recordings (
  recording_id    TEXT PRIMARY KEY,
  project_id      TEXT,            -- CRM案件ID
  call_id         TEXT,            -- CRM架電ID(任意)
  member_id       TEXT,            -- 発信者(CRMメンバーID)
  company         TEXT,            -- 会社名(表示用キャッシュ)
  phone           TEXT,            -- 通話相手の電話番号
  direction       TEXT,            -- outbound / inbound
  recorded_at     TEXT,            -- 通話日時(ISO)
  duration_sec    INTEGER,
  r2_key          TEXT NOT NULL,   -- R2上の音声ファイルキー
  source          TEXT,            -- zoomphone / manual_upload
  matched_by      TEXT,            -- zoom_extension / three_point / manual
  zoom_call_id    TEXT,            -- Zoom側の録音ID(重複防止)
  transcript      TEXT,            -- Whisper文字起こし結果
  transcribed_at  TEXT,
  transcript_lang TEXT,
  created_at      TEXT NOT NULL DEFAULT (datetime('now')),
  updated_at      TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_rec_member   ON recordings(member_id);
CREATE INDEX IF NOT EXISTS idx_rec_project  ON recordings(project_id);
CREATE INDEX IF NOT EXISTS idx_rec_recorded ON recordings(recorded_at);
CREATE UNIQUE INDEX IF NOT EXISTS idx_rec_zoom ON recordings(zoom_call_id);

CREATE TABLE IF NOT EXISTS recording_feedback (
  fb_id        TEXT PRIMARY KEY,
  recording_id TEXT NOT NULL,
  author_id    TEXT,
  author_name  TEXT,
  text         TEXT NOT NULL,
  tag          TEXT,               -- good / improve / share / insight
  created_at   TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_fb_rec ON recording_feedback(recording_id);

CREATE TABLE IF NOT EXISTS playlists (
  playlist_id  TEXT PRIMARY KEY,
  name         TEXT NOT NULL,
  description  TEXT,
  kind         TEXT,               -- success / improvement / general
  created_by   TEXT,
  shared_scope TEXT,               -- JSON {type:'all'|'project'|'members', ids:[]}
  created_at   TEXT NOT NULL DEFAULT (datetime('now')),
  updated_at   TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS playlist_items (
  playlist_id  TEXT NOT NULL,
  recording_id TEXT NOT NULL,
  note         TEXT,
  sort_order   INTEGER DEFAULT 0,
  added_by     TEXT,
  added_at     TEXT NOT NULL DEFAULT (datetime('now')),
  PRIMARY KEY (playlist_id, recording_id)
);

CREATE TABLE IF NOT EXISTS members_cache (
  member_id  TEXT PRIMARY KEY,
  name       TEXT,
  email      TEXT,
  role       TEXT,                 -- admin / null
  type       TEXT,                 -- pm / partner_teleapo / partner_linkedin
  projects   TEXT,                 -- JSON配列
  zoom_ext   TEXT,                 -- Zoom Phone 内線番号
  data       TEXT,                 -- 生JSON全体
  synced_at  TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS zoom_sync_log (
  sync_id     TEXT PRIMARY KEY,
  started_at  TEXT,
  finished_at TEXT,
  range_from  TEXT,
  range_to    TEXT,
  fetched     INTEGER DEFAULT 0,
  saved       INTEGER DEFAULT 0,
  skipped     INTEGER DEFAULT 0,
  status      TEXT,                -- running / done / error
  error       TEXT
);

CREATE TABLE IF NOT EXISTS app_data (
  key        TEXT PRIMARY KEY,
  value      TEXT,
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

R2構成(scale-talk-recordings)

項目
バケット名 scale-talk-recordings
キー構造 recordings/{project_id}/{call_id}/{recording_id}.{ext}
公開設定 非公開。音声は機密。直接URLでは配信しない
配信経路 /api/recordings/:id/audio で認証後にストリーム配信(Range対応で倍速・シーク可)

API構成(functions/api/)

エンドポイント メソッド 役割
/api/health GET ヘルスチェック
/api/auth/me GET SSOセッション検証・ログインユーザー返却
/api/recordings GET/POST/PATCH/DELETE 録音メタCRUD(GETはフィルタ・検索)
/api/recordings/:id/audio GET R2から音声ストリーム配信(Range対応)
/api/recordings/:id/transcribe POST Whisper文字起こし実行
/api/feedback GET/POST/DELETE 録音へのFBメモCRUD
/api/playlists GET/POST/PATCH/DELETE プレイリストCRUD
/api/playlists/:id/items POST/DELETE プレイリストへの録音追加・削除
/api/members/sync POST CRMからメンバー一括同期 → members_cache
/api/members GET members_cache 読み出し
/api/calls/proxy GET CRMの架電リストを取得(紐付けUI用)
/api/upload POST 手動音声アップロード → R2保存 + recordings 登録
/api/zoom/sync POST Zoom Phone録音を取得 → R2保存 + recordings 登録
/api/zoom/status GET Zoom同期ログ(zoom_sync_log

認証: functions/_middleware.ts で全 *.js / /api/* を SSO cookie 検証でガード(/api/health のみ素通り)。


認証 / SSO

大串の理想「2つのシステムでそれぞれ登録不要・同期できたら理想」を満たす設計。

項目 内容
CRMの認証 cookie scl_session(HMAC-SHA256署名・HttpOnly・TTL14日・PBKDF2でパスワード照合)
SSO方式 CRMの cookie Domain を .scale-group.co.jp に拡張 → talk.scale-group.co.jp でも同cookieが届く
SCALE Talk側 CRMと同じ AUTH_SECRET で HMAC 検証 → ログイン画面なしで素通り(CRMログイン済みなら即利用可)
CRM側の小改修 cookie発行時の Domain 属性を .scale-group.co.jp に変更(既存機能は不変・既存ユーザーは次回ログインで共有cookie化)
未ログイン時 cookie無効なら CRMログイン画面(crm.scale-group.co.jp)へ誘導。SCALE Talkは認証情報・パスワードを一切自前で持たず、CRMを認証の単一情報源とする(二重管理ゼロ=大串の理想形)

SCALE CRM 連携

CRM(scale-lead)は好評・安定稼働中。連携追加は 最小・既存機能を一切変えない

CRM側に追加するもの(最小)

追加 内容
functions/api/talk/members.ts Bearer token認証でメンバー一覧(member_rows)を返す
functions/api/talk/calls.ts Bearer token認証で架電リスト(call_rows)を返す
環境変数 TALK_SYNC_TOKEN SCALE Talk ↔ CRM のサーバ間共有秘密
cookie Domain 変更 SSO用(.scale-group.co.jp

CRMの既存API(/api/members /api/calls)はcookie認証のみで、Bearer token非対応・外部から叩けない。そのため /api/talk/* を新設してトークン認証で開ける。functions/_middleware.ts/api/talk/* をcookieガード対象から除外(トークン認証へ委譲)。

メンバー同期フロー

CRMでメンバー追加(member_rows に1行)
   → SCALE Talk が /api/talk/members を pull(起動時 + 定期)
   → members_cache を upsert
   → SCALE Talk 側で別登録は不要(大串の理想形を実現)

架電リスト連携

録音は call_id / project_id でCRM架電に紐付け。録音一覧で「どの会社への架電か」を表示。紐付けUIで CRM /api/talk/calls?project= の架電メタを参照。


Zoom Phone 連携(旧Phase2を完成させる)

旧CRMの音声機能はクライアントUIだけ完成し、Zoom Phoneのサーバー連携は一度も稼働しなかった(外部worker scale-ai-proxy にエンドポイント未実装のまま)。SCALE Talk で今度こそ完成させる。

項目 内容
認証方式 Server-to-Server OAuth(Zoom App Marketplace でアプリ作成)
必要な値 Account ID / Client ID / Client Secret(大串が発行 → 環境変数に格納)
scope phone:read:admin / phone_recording:read:admin / phone_call_log:read:admin
トークン取得 POST https://zoom.us/oauth/token?grant_type=account_credentials&account_id=X(Basic認証)→ access_token
録音取得 GET /phone/recordings?from=&to= → 一覧 → 各 download_url から音声DL → R2保存
個人特定 通話ログAPI(電話番号・時刻・内線)+ 三点マッチ(共有内線でも「電話番号+時刻±3分」で発信者を特定。旧ロジック移植)
自動同期 毎朝1回(Cloudflare Cron Triggers。不可なら Mac crontab で外部cron)
冪等性 recordings.zoom_call_id のUNIQUE制約で重複登録を防止

文字起こし(Whisper)

Cloudflare Workers AI @cf/openai/whisper。Pages Functions が R2 の音声を取得 → env.AI.run('@cf/openai/whisper', {audio})transcript を D1 に保存。録音長の1/3〜1/5で処理。言語自動判定。


UI(ページ構成)

# ページ 内容
1 ダッシュボード 統計(総録音 / 今週 / 文字起こし済 / FB数)
2 録音一覧(音声分析) フィルタ8種・企業名検索・倍速再生(0.5〜2x)・文字起こし表示・FBメモ
3 マイ録音 ログインユーザー自身の架電録音を振り返る
4 成功音声プレイリスト PMが良い音声/改善音声をまとめて共有、メンバーが視聴
5 メンバー別 メンバーごとの録音一覧
6 Zoom同期 同期設定・実行ログ・手動同期ボタン
7 アップロード 手動で音声ファイルを登録(R2へ)

旧資産の扱い

旧(CRM内蔵) 新(SCALE Talk)
lib/pages_voice.js 740行 録音一覧・再生・文字起こし・FBメモUIへ移植
lib/pages_extras.js プレイリスト10関数 成功音声プレイリストへ移植
三点マッチ・倍速プレイヤー ロジック移植
音声メタを架電キーに内包 廃止 → 独立D1 + R2隔離
Zoom proxy URL参照のみ(実体なし) R2に実体保存
Zoom Phoneサーバー連携 未完 完成させる

実装フェーズ

Phase 内容 状態
1 プロジェクト雛形・D1スキーマ・R2・SSO認証・録音一覧UI(手動アップロードで動作確認) 完了
2 CRM連携(/api/talk/* 追加・メンバー同期・架電紐付け) 完了
3 Zoom Phone連携(OAuth・録音取得・自動同期・三点マッチ) コード完了・本番デプロイ済(実同期は ZOOM_CLIENT_SECRET 登録後)
4 Whisper文字起こし・成功音声プレイリスト・FBメモ 完了・本番デプロイ済
5 デプロイ・独自ドメイン・本番連携テスト 一部(デプロイ済/独自ドメイン・ブラウザ実機検証は保留)

Phase 3 の発信者特定は内線番号マッチで実装(電話番号+時刻の三点マッチは将来拡張)。Zoom 実同期のみ ZOOM_CLIENT_SECRET の secret 登録が前提。


人間(大串)依存タスク

# タスク 理由 状態
1 ZOOM_CLIENT_SECRET を scale-talk の secret に登録(npx wrangler pages secret put ZOOM_CLIENT_SECRET ●●●●●●=scale-talk Zoom実同期に必須。Account ID / Client ID は登録済 未。Phase 3コードは本番稼働中
2 Zoom Phone の録音機能が有効か確認 録音が無いと取得不可 確認済
3 talk.scale-group.co.jp の DNS CNAME レコード作成(talk → scale-talk.pages.dev・プロキシ) SSO は .scale-group.co.jp cookie 依存。Pagesプロジェクトへのドメイン追加はAPI実施済、DNSレコードのみ未 大串保留中(「あとでやる」)

D1 / R2 / Pagesプロジェクト作成は wrangler CLI で Claude が実施。


セキュリティ

項目 方針
録音音声 機密。R2は非公開。/api/recordings/:id/audio で認証後ストリーム配信のみ
SSO 全ページ・全API(health除く)で scl_session 検証必須
秘密情報 Zoom認証情報・TALK_SYNC_TOKENAUTH_SECRET は環境変数。コードに書かない
CRM連携 /api/talk/*TALK_SYNC_TOKEN のBearer認証。漏洩時はトークン差し替えで失効

関連ノート


更新履歴

日付 内容
2026-05-22 初版作成。SCALE CRMから音声機能を独立させる設計を確定(名称SCALE Talk・talk.scale-group.co.jp)
2026-05-22 Phase 2 実装完了。CRM v11.5.45 で /api/talk/members・/api/talk/calls 新設+middleware除外。SCALE Talk に /api/members/sync・/api/members・/api/calls/proxy +メンバー連携UI。両本番デプロイ済。実機検証は talk.scale-group.co.jp ドメイン設定後
2026-05-22 Phase 3/4 実装完了。Zoom連携(zoom/sync・zoom/status・_lib/zoom)/Whisper文字起こし(recordings/:id/transcribe)/FBメモ(feedback)/プレイリスト(playlists・playlists/:id・playlists/:id/items)の8 Functions+フロントUI(プレイリスト/Zoom同期ページ・録音カード詳細)を新設。本番デプロイ・検証済。Zoom実同期は ZOOM_CLIENT_SECRET 登録後
2026-05-23 設計書の残UI実装(v=202605230433):メンバー別ドリルダウン(UI #5)/ダッシュボードに FB数 スタット追加/録音フィルタ拡充(source・direction・transcribed)。recordings/index.ts に direction・transcribed クエリパラメータを追加。本番デプロイ・検証済