導入(問題提起)
「毎週の数値が担当者のExcelでしか見られない」「同じKPIが部署ごとに違う数字を示している」——こうした現場の混乱は、データの一元管理と可視化がないことが原因です。Excelでの手作業集計はスピードも正確性も限界があります。PythonでシンプルなWebシステム開発を行い、社内用のダッシュボード(業務システムの一部)として公開すれば、正しい数値を正しいタイミングでチーム全員に届けられます。まずは小さく作り、使いながら改善することが成功の近道です。
本記事では、Excel Web化の延長として「Pythonで業務ダッシュボードを最小構成で作る」現実解を、設計・実装・運用の観点で解説します。対象は小規模〜中規模のチーム。データはSQLiteやMySQL/PostgreSQLに置き、内製で回せる仕組みにします。
課題の詳細説明
Excel中心の集計・レポート運用には、以下の構造的な課題があります。
- データソースが散在している
- 集計方法が人に依存している
- 更新頻度と鮮度が低い
- 数値の「定義」が統一されていない
- アクセス管理・監査が弱い
- 売上は会計SaaS、見込みはスプレッドシート、在庫はExcel、サイトはGA…とバラバラ - 集約のためのコピペや人手ETLが常態化し、更新漏れ・二重管理が起きる
- ピボットの条件やフィルタが担当者の頭の中にあり、属人化 - 退職・異動のたびにレポートが止まる
- 週次・月次の締め作業でしか数値が揃わないため、意思決定が後手 - 誤りの発見が遅れ、手戻りコストが高い
- 受注、出荷、売上、ARPU、LTVなどの定義が部署で微妙に異なる - 「どの数字が正?」の議論に時間を取られる
- 閲覧権限・エクスポート権限の区別ができない - 不正ダウンロードや誤配布のリスクがある
これらは「単一の真実の源泉(Single Source of Truth)」を設け、集計ロジックをコード化・共有化し、Web化することで解決できます。Pythonは学習コストが低く、軽量なWebフレームワークや可視化ライブラリが豊富なため、内製の第一歩として適しています。
解決方法
解決の鍵は「最小セットで始め、継続的に拡張する」ことです。推奨する進め方は次のとおりです。
- 単一の“真実の源泉”(DB)を決める
- 集計処理とKPI定義をコード化し、バージョン管理
- ダッシュボードUIは「KPIカード+推移グラフ+明細」の3点セット
- 権限分離(閲覧のみ/ダウンロード可/管理者)
- 自動更新と監視
- まずはSQLite(単一ファイル・バックアップ容易)でPoCし、必要に応じてMySQL/PostgreSQLへ移行 - テーブル定義と数値の定義(仕様)をリポジトリに記述
- PythonのETL(抽出・変換・ロード)スクリプトを作成 - 集計SQLやWindow関数はGitで共有しレビュー可能に
- まずは5〜8枚のKPIカード(今日/今週/今月) - 折れ線や棒グラフで主要KPIの推移を表示 - 詳細確認のための明細テーブル(CSVエクスポート可、権限制御)
- RBAC(Role-Based Access Control)でコントロールし、監査ログを残す
- バッチでETLをスケジュールし、失敗時は通知(メール/Slack) - メトリクス(ETL遅延・処理件数・エラー数)を可視化
具体例
以下は、小規模企業で実装しやすい「最小セット」の事例です。
- 事例A:受注/出荷/売上の3段階KPI
- 事例B:Web問い合わせのリード管理
- 事例C:在庫回転と欠品アラート
- 事例D:SLA/サポート指標
- KPIカード:受注件数、出荷件数、売上金額(当月/前月比/前年比) - 推移:週次の受注件数、品目別売上 - 明細:当月の受注一覧(検索・CSV出力)
- KPIカード:新規リード数、商談化率、成約率 - 推移:チャネル別の月次推移(広告/自然検索/紹介 等) - 明細:最新のリード(担当・ステータス・次アクション)
- KPIカード:在庫健全率、欠品SKU数、滞留在庫 - 推移:SKU別の在庫回転日数 - 明細:欠品・滞留リスト(補充推奨数)
- KPIカード:初回応答時間中央値、24h解決率、未対応チケット - 推移:曜日別の問合せ量と応答時間 - 明細:SLA違反リスト(担当・影響度)
これらはすべて、Pythonと軽量なフレームワーク(Bottle/FastAPI)+SQLite/MySQLで数日〜数週間で構築可能です。まずは最優先の3〜5指標から着手し、活用が進むにつれて追加します。
技術的な解説
最小構成のアーキテクチャと実装断片を示します。目的は「読みやすく・テストしやすく・移行しやすい」構成にすることです。
- 推奨構成
- Web: Python(BottleまたはFastAPI) - DB: SQLite(PoC)→ MySQL/PostgreSQL(成長に合わせて) - 集計: SQL(View/CTE/Window関数)+Python(ETL) - 認可: RBAC(role_table, user_role_table) - 監査: 監査テーブル(だれが/いつ/何を見たか/出力したか)
-- KPIの元となる受注テーブル例
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
order_date DATE NOT NULL,
customer_id INTEGER NOT NULL,
amount DECIMAL(12,2) NOT NULL,
channel TEXT,
status TEXT CHECK(status IN ('new','shipped','canceled'))
);
-- 月次売上のCTE集計
WITH m AS (
SELECT strftime('%Y-%m', order_date) AS ym,
SUM(CASE WHEN status='shipped' THEN amount ELSE 0 END) AS shipped_amount
FROM orders
GROUP BY ym
)
SELECT ym, shipped_amount,
LAG(shipped_amount) OVER (ORDER BY ym) AS prev,
ROUND((shipped_amount - LAG(shipped_amount) OVER (ORDER BY ym))
/ NULLIF(LAG(shipped_amount) OVER (ORDER BY ym),0) * 100, 2) AS mom
FROM m
ORDER BY ym DESC
LIMIT 12;
# Bottleでの最小ルーティング例(KPIカード/推移/明細)
from bottle import Bottle, request, response
import sqlite3, json
app = Bottle()
def db():
return sqlite3.connect('app.db')
@app.get('/api/kpi')
def kpi():
q = """
SELECT COUNT(*) AS orders,
SUM(CASE WHEN status='shipped' THEN amount ELSE 0 END) AS sales
FROM orders WHERE date(order_date) >= date('now','start of month')
"""
con = db(); cur = con.cursor(); cur.execute(q)
row = cur.fetchone(); con.close()
return {'orders': row[0], 'sales': float(row[1] or 0)}
@app.get('/api/sales/trend')
def trend():
q = """
WITH m AS (
SELECT strftime('%Y-%m', order_date) AS ym,
SUM(CASE WHEN status='shipped' THEN amount ELSE 0 END) AS sales
FROM orders GROUP BY ym
) SELECT ym, sales FROM m ORDER BY ym
"""
con = db(); cur = con.cursor(); cur.execute(q)
data = [{'ym': ym, 'sales': float(s or 0)} for ym, s in cur.fetchall()]
con.close(); return data
@app.get('/api/orders')
def orders():
kw = request.query.getunicode('q') or ''
con = db(); cur = con.cursor()
cur.execute("SELECT id, order_date, customer_id, amount, status FROM orders WHERE CAST(id AS TEXT) LIKE ? ORDER BY order_date DESC LIMIT 200", (f'%{kw}%',))
rows = cur.fetchall(); con.close()
return [{'id': r[0], 'date': r[1], 'customer': r[2], 'amount': float(r[3]), 'status': r[4]} for r in rows]
# RBACと監査の最小実装断片
def can_export(user):
return 'exporter' in user.roles or 'admin' in user.roles
def audit(user_id, action, meta=None):
con = db(); cur = con.cursor()
cur.execute("INSERT INTO audit_logs(user_id, action, meta, created_at) VALUES (?, ?, ?, datetime('now'))",
(user_id, action, json.dumps(meta or {})))
con.commit(); con.close()
可視化は、Chart.jsやEChartsを使ってフロントで描画するのが最小コストです。APIの戻り値は単純なJSONにし、フロントで汎用コンポーネント化すると拡張が容易です。
導入の流れ
短期間で立ち上げるための段階的な進め方を提示します。
- 1週目:要件定義ミニワークショップ(半日×2)
- 2週目:データ基盤の最小構築
- 3週目:ダッシュボードMVP
- 4週目:運用化と拡張
- 重要KPIを5〜8個に絞る(定義を文章化) - データの所在と取得方法を洗い出す(Excel Web化も検討)
- SQLiteでDB作成、テーブル定義、サンプルデータ投入 - Python ETLスクリプトでデータ取り込み(SaaS API/CSV)
- KPIカードと主要推移グラフを実装、明細テーブルを追加 - RBAC(閲覧/エクスポート/管理)と監査ログを実装
- スケジューラでETL自動化、失敗通知を設定 - フィードバック反映、KPI追加、チューニング
この流れなら、1か月で「意思決定に使える最小ダッシュボード」を社内公開できます。成長とともにMySQL/PostgreSQLへ移行し、KPI・画面・APIを拡張すれば十分に長持ちします。
まとめ
Excel中心の集計から卒業し、Pythonによる業務システムとしてのダッシュボードを立ち上げると、数字の鮮度と信頼性が向上します。単一DB・コード化された集計・3点セットのUI・RBAC/監査・自動更新という基本を小さく作り、継続的に磨き上げていきましょう。Webシステム開発の初期投資を抑えつつ、意思決定の質を段階的に引き上げられます。
ダッシュボード設計・実装やExcel Web化、既存業務システムとの連携に関するご相談は以下よりお寄せください。
Webシステム開発のご相談は monou まで ※お問い合わせはこちら