1. 概要
MNP獲得実績を可視化する自社ダッシュボード。Tableauの代替として、Google Sheetsの入力フォーム(成果報告・出発報告)を5分ごとに取得・集計し、FastAPI + Chart.js で表示する。
| 項目 | 内容 |
| 本番URL | https://castboard-data-reader.web.app |
廃止URL(*.run.app 直アクセスは web.app へ 301 リダイレクト) | https://castbox-dashboard-80063870039.asia-northeast1.run.app / https://castbox-dashboard-3x2gddnwgq-an.a.run.app |
| GCPプロジェクト | castboard-data-reader |
| リージョン | asia-northeast1 |
| サービス名 | castbox-dashboard |
| 最新リビジョン | castbox-dashboard-00078-2zj |
| スプレッドシートID | 1KdORhEshilEWEESRySSc9qATGRj6m-HdEiLO9RN6kcI |
2. システム構成
[ Google Sheets (入力フォーム) ]
│ gspread (Service Account認証, readonly)
▼
[ core/pipeline.py ] 取得・名寄せ・結合・集計
│
▼
[ api/cache.py ] インメモリ DataFrame + SQLite永続化 + 5分バッチ更新
│
▼
[ api/auth.py ] Firebase Auth ミドルウェア(JWT検証)
│
▼
[ api/main.py ] FastAPI REST + StaticFiles(ui/ をルートにマウント)
│
▼
[ ui/ ] vanilla JS + Chart.js(外部CSS/JS分割済 T-19)
index.html ← スプラッシュ + ダッシュボード + マイページオーバーレイ
login.html ← Firebase Auth ログイン画面
account.html ← アカウント設定(スタンドアロン)
2.1 実行環境
- コンテナ:
python:3.12-slim / Cloud Run
- 公開ポート:
8080
- 起動コマンド:
uvicorn api.main:app --host 0.0.0.0 --port 8080
- 認証: Service Account(Sheets APIのみ読み取り)
- 認証鍵:
secrets/castboard-data-reader-81f1ba38fac7.json
U-02 移行予定
2.2 依存ライブラリ
| パッケージ | バージョン |
| fastapi | >=0.111.0 |
| uvicorn[standard] | >=0.29.0 |
| gspread | >=6.1.0 |
| google-auth | >=2.29.0 |
| pandas | >=2.2.0 |
| firebase-admin | >=6.5.0 |
3. データソース
3.1 シート一覧
| シート名 | 用途 | 主要列 |
成果報告_V3 | 退勤時の獲得件数 | A:timestamp / B:email / E:client / F〜N:キャリア別件数 / U:備考 |
出発報告_V2 | 出勤時の稼働場所申告 | A:timestamp / B:email / G:クライアント / H〜N:稼働場所 / R:氏名 |
氏名マスタ | メール→氏名(低優先) | A:email / B:氏名 |
ユーザー登録_V1 | メール→氏名(高優先) | B:email / C:氏名 |
3.2 集計ロジック
件数パース(_parse_count)
"3件" / "3" → 数値
- 空文字・
テスト|test|見込み|練習|確認 を含む値 → 除外
稼働場所算出(_calc_location)
- 候補列(H〜N)のいずれかに
この中に当てはまるものがない が入っていれば列N(自由記入)を採用
- そうでなければ H〜M の最初の非空値
- すべて空なら N
キャリア名寄せ
| 入力 | 正規化後 |
| Softbank / Ymobile / SB | Softbank |
| docomo / ドコモ | docomo |
| KDDI / au / UQ | KDDI |
| その他非空 | その他 |
結合
- 出発報告は
(email, dep_date) で dedup(最小タイムスタンプ採用)
- 成果報告 × 出発報告(dedup済)を
(email, date) LEFT JOIN
- 氏名解決優先順位: ユーザー登録_V1 → 氏名マスタ → 出発報告の氏名 → emailローカル部
3.3 キャリア列マッピング(成果報告_V3)
| 列index | キャリア | 列index | キャリア |
| 5 | SB | 10 | POVO |
| 6 | YM | 11 | UQ |
| 7 | docomo | 12 | 楽天 |
| 8 | au | 13 | その他 |
| 9 | ahamo | | |
4. キャッシュ設計
| 項目 | 仕様 |
| 保持形式 | pandas DataFrame(インメモリ)+ SQLite永続化 |
| SQLiteパス | cache.db(プロセス直下) |
| 更新間隔 | 300秒(5分) |
| 起動フロー | SQLite復元 → 即サーブ → バックグラウンドでSheets再取得 |
| 手動更新 | POST /api/refresh |
| データ準備中 | 503 Service Unavailable |
5. API仕様
5.1 エンドポイント一覧
| メソッド | パス | 概要 |
| GET | /api/summary | KPI + 案件別 + 人別 + 日次 + コメント |
| GET | /api/by_location | 案件別ランキング |
| GET | /api/by_carrier | キャリア別件数(ドーナツ用) |
| GET | /api/calendar | 日次カレンダー |
| GET | /api/filters | フィルター選択肢 |
| GET | /api/health | ヘルスチェック |
| POST | /api/refresh | 手動データ更新 |
5.2 共通クエリパラメータ
| パラメータ | 例 | 説明 |
month | 2026-04 | 年月(YYYY-MM) |
name | 山田太郎 | 氏名完全一致 |
location | 渋谷店 | 稼働場所完全一致 |
career | Softbank | クライアント(Softbank / docomo / KDDI) |
date | 2026-04-12 | 単日絞り込み(summary/by_carrierのみ) |
5.3 レスポンス例
GET /api/summary
{
"total_count": 309,
"total_working": 268,
"avg_per_work": 1.15,
"by_location": [{"name": "渋谷店", "件数": 42, "稼働数": 28}, ...],
"by_person": [{"name": "山田太郎", "件数": 15, "稼働数": 10}, ...],
"daily": [{"date": "2026-04-01", "件数": 12, "稼働数": 5}, ...],
"comments": [{"date": "2026-04-21", "name": "山田太郎", "comment": "..."}],
"updated_at": "2026-04-21T06:00:00+00:00"
}
GET /api/by_carrier
{
"carriers": {"SB": 80, "YM": 12, "docomo": 60, "au": 50, "ahamo": 8,
"POVO": 5, "UQ": 10, "楽天": 4, "その他": 2},
"updated_at": "2026-04-21T06:00:00+00:00"
}
GET /api/filters
{
"names": ["山田太郎", "佐藤花子", ...],
"locations": ["渋谷店", "新宿店", ...],
"months": ["2026-04", "2026-03", ...],
"careers": ["Softbank", "docomo", "KDDI"]
}
names は month / career 適用後の 稼働数(出勤日数)降順
- 同数時は氏名昇順で安定ソート
6. フロントエンド仕様
6.1 技術スタック
- vanilla JS(フレームワーク不使用)ES Module形式
- Chart.js 4.4.1 / chartjs-plugin-datalabels 2.2.0 / chartjs-chart-sankey 0.12.1 / html2canvas 1.4.1
- Firebase JS SDK v10(CDN / モジュール形式): Auth + Firestore
- Material Symbols Outlined
- Comfortaa Variable Font(ロゴ文字専用)/ BIZ UDPGothic(本文フォールバック)
6.2 外部化ファイル構成(T-19 / 2026-04-23)
| ファイル | 役割 |
styles/base.css | 変数・フォント・ヘッダー静的スタイル・btn-menu共通 |
styles/splash.css | 🔒 Splash演出(仕様クリティカル) |
styles/dashboard.css | PCレイアウト・フィルタバー・オーバーレイ |
styles/mobile.css | @media(〜900/640/380px) |
styles/account.css | マイページ専用スタイル |
scripts/auth.js | 認証ガード・グローバル変数 |
scripts/splash.js | 🔒 Splash関数(SVG ID占有) |
scripts/dashboard.js | ダッシュボード本体・オーバーレイ開閉 |
scripts/main.js | updateDashboard / init / manualRefresh |
scripts/pull-refresh.js | モバイルPull-to-refresh |
scripts/account.js | Firestore読み書き・プロフィール編集(ES Module) |
6.3 主要画面構成
- ヘッダー(static ロゴ・フィルタトグル・ハンバーガーメニュー)
- フィルタバー(クライアント / 対象月 / 氏名 / 稼働場所 / 更新・Download)
- KPI 3枚(件数 / 総稼働数 / 生産)
- カレンダー(件数・生産切替) + 稼働場所ランキング + Sankey(獲得経路)
- 人別ランキング(Lv.バッジ・稼働比例バー・稼働/件数/生産切替)+ ドーナツ(件/% 切替)
- コメント一覧
- マイページオーバーレイ(ハンバーガー押下で固定表示 / ダッシュボード上に重なる)
6.4 マイページ(account overlay)
- Firestore
users/{uid}: displayName / companyName / email / updatedAt
- Firestore
users/{uid}/login_history: signinTime / device(直近10件)
- Google連携/解除(
linkWithPopup / unlinkProvider)
- account.js は
IS_OVERLAY フラグで index.html / account.html 両モードを兼用
- データは dashboard 初回ロード時に先行取得 → オーバーレイ開時は即表示
6.3 Splash演出
- 初回ロードと更新ボタン押下時のみ
runSplashSequence() 経由で発火
phase-loading → phase-settle → phase-exit の3段
- 最低表示時間 5秒保証
- フォント: Comfortaa Variable(
ui/assets/fonts/Comfortaa-VariableFont_wght.ttf)
6.4 禁止事項(先祖返り防止)
setInterval(updateDashboard, ...) 自動更新の再導入禁止
- 初回ロード・手動更新で
updateDashboard() を直接呼ばず runSplashSequence() 経由
- 最低5秒表示の短縮禁止
- ヘッダーロゴのアニメ化禁止(splash側がSVG IDを占有)
phase-settle → phase-exit の切替順は remove('phase-settle') → add('phase-exit')
- CSS記述順は
.phase-exit .char を .phase-settle .char より後ろ
6.5 モバイル対応
- Pull-to-refresh(最上部で下フリック →
manualRefresh / オーバーレイ開時は無効)
- フィルターバー開閉アニメ + 欄外タップで閉じる + 遮光
- タップハイライト無効化
- PWA対応: apple-touch-icon 180px / manifest.webmanifest / theme-color #37359A(T-23)
- ヘッダー高さ変数
--header-h: デスクトップ52px / モバイル60px+safe-area
7. デプロイ
gcloud run deploy castbox-dashboard \
--source . \
--region asia-northeast1 \
--allow-unauthenticated \
--set-secrets "GOOGLE_SA_KEY_JSON=castbox-sa-key:latest" \
--quiet
--allow-unauthenticated: 認証なし公開(Firebase Auth はフロント側で実施)
- SA鍵は
castbox-sa-key(Secret Manager)経由で注入。Dockerイメージには含まれない(T-21完了)
- UI変更も本コマンドのみで反映(firebase deploy 不要)
8. セキュリティ
8.1 SA鍵管理(T-21完了 2026-04-24)
- SA鍵は
castbox-sa-key(Secret Manager)に登録済み
secrets/ は .dockerignore 除外済み → Dockerイメージには含まれない
- ローカル開発は
secrets/ 配下の鍵ファイルをフォールバックで使用
8.2 Firebase認証
- フロント: Firebase SDK v10 / ID トークンを
castbox_id_token (localStorage) に保存
- バックエンド:
api/auth.py が firebase-admin でトークン検証
- DEV:
DEV_SKIP_AUTH=1 環境変数でバイパス(本番未設定)
- Firestore規則:
users/{uid}/{document=**} → 本人のみ読み書き可
8.3 グローバル運用規則
.env / .env.local は読み取り禁止・内容出力禁止
- 本番DBへの直接操作禁止
git push --force 禁止
9. タスク状況
完了
| # | タスク | 完了日 |
| T-01 | Sheets API 接続 | 2026-04-19 |
| T-02 | location計算式 Python再現(100%一致) | 2026-04-19 |
| T-03 | 「X件」→数値変換 | 2026-04-19 |
| T-04 | 成果×出発 結合ロジック | 2026-04-19 |
| T-05 | Tableau突き合わせ(件数309 vs 311、稼働268完全一致) | 2026-04-19 |
| T-06 | FastAPI + UIフルスタック構築 | 2026-04-20 |
| T-07 | UI Tableau配色刷新(白背景・オレンジアクセント) | 2026-04-20 |
| T-08 | Cloud Run本番デプロイ(5分自動更新) | 2026-04-20 |
| T-09 | 即時更新ボタン(POST /api/refresh) | 2026-04-21 |
| T-10 | UI Ver.2刷新(ロゴ・2段フィルター・レイアウト再構成) | 2026-04-21 |
| T-11 | 自動更新停止 + ヘッダーロゴ静的化 | 2026-04-21 |
| T-12 | Splashアニメーション実装(phase 3段・5秒保証) | 2026-04-21 |
| T-13 | Comfortaa Variable 適用(ヘッダー+splash) | 2026-04-21 |
| T-14 | ローカル開発環境整備(scripts/dev.cmd / DEV_SKIP_AUTH) | 2026-04-22 |
| T-15 | UI を Cloud Run 経由配信に統一 | 2026-04-22 |
| T-16 | 合計行バーをレベル別人数スタック化 + 折れ線ラベル期間集計 + 期間トグル改称 | 2026-04-22 |
| T-17 | Phase 2: 上位N%レベル化(Lv.4段階)+ 凡例固定 + ファネル + スマホ列幅最適化 | 2026-04-23 |
| T-18 | 人リスト1列化(Lv.mini+レベル着色バー)+ 比較母数3か月固定 + グラフ切替2段トグル | 2026-04-23 |
| T-19 | UI外部化: CSS 5ファイル + JS 6ファイル分割(index.html 2780→242行) | 2026-04-23 |
| T-20 | ファネル母数3か月集計プール統一 + バー幅人数比例化 | 2026-04-23 |
| T-21 | SA鍵を Secret Manager へ移行(castbox-sa-key) | 2026-04-24 |
| T-22 | アカウントページ新規作成(プロフィール編集・Google連携・Firestore読み書き) | 2026-04-24 |
| T-23 | PWAアイコン統一(apple-touch-icon 180px / manifest / theme-color #37359A) | 2026-04-24 |
| T-24 | マイページをページ遷移からオーバーレイ方式に変更(即時表示・ハンバーガー→X) | 2026-04-25 |
| T-25 | フィルタボタン・Pull-to-refresh の overlay 連動 + ヘッダー高さ変数統一 --header-h | 2026-04-25 |
バックログ
| # | タスク | 優先度 |
| U-01 | UIデザイン細部修正(Tableau差異詰め) | 中 |
| U-03 | カスタムドメイン移行(独自ドメイン) | 低 |
| U-04 | 折れ線グラフ・ドーナツのツールチップ実装 | 中 |