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_TOKEN・AUTH_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 クエリパラメータを追加。本番デプロイ・検証済 |