はじめに
筆者は専業のエンジニアではない。それでも、AI コパイロットと既存 SaaS の API を組み合わせれば、社内の集計ダッシュボード程度なら自分で作れる、というのが本稿の主張である。題材は Jira の課題データから「チーム別リードタイム」を可視化する最小実装。業界事例として、ある IT 系大手の非エンジニア寄り PM が 9 日間で同種のツールを完走した実装記が公開されており、同じ流れは中小〜中堅企業の業務改善担当者にも応用可能だと判断した。
DORA 指標 (デプロイ頻度・変更リードタイム等) を扱う汎用ボードは便利だが、「うちのチームの、この時間帯の、このプロセス段階の詰まり」を見たいときには粒度が粗い。本稿では Jira REST API を叩き、pandas で正規化し、Streamlit で簡易ダッシュボードに落とすところまでを段階的に示す。
前提・環境
Python 3.11
requests / pandas / streamlit
Jira Cloud (REST API v3) — オンプレ Server 版でもパスを少し直せば流用可
AI コパイロット: Cursor + Claude 系 LLM (補助役)
実装ステップ
1. Jira REST API から課題データを取得する
認証は API トークン + Basic Auth。ページング (startAt / maxResults) は明示制御する。
import os
import requests
from requests.auth import HTTPBasicAuth
JIRA_BASE = os.environ["JIRA_BASE_URL"] # 例: https://your-domain.atlassian.net
JIRA_USER = os.environ["JIRA_USER"]
JIRA_TOKEN = os.environ["JIRA_API_TOKEN"] # YOUR_API_TOKEN
def fetch_issues(jql: str, fields: list[str], page_size: int = 100) -> list[dict]:
url = f"{JIRA_BASE}/rest/api/3/search"
start_at, issues = 0, []
while True:
resp = requests.get(
url,
params={
"jql": jql,
"fields": ",".join(fields),
"startAt": start_at,
"maxResults": page_size,
},
auth=HTTPBasicAuth(JIRA_USER, JIRA_TOKEN),
timeout=30,
)
resp.raise_for_status()
data = resp.json()
issues.extend(data["issues"])
if start_at + page_size >= data["total"]:
break
start_at += page_size
return issues
2. データを正規化する
ネストされた JSON のままでは扱いにくい。必要な列だけフラットな DataFrame に落とす。
import pandas as pd
def to_dataframe(issues: list[dict]) -> pd.DataFrame:
rows = []
for it in issues:
f = it["fields"]
assignee = f.get("assignee") or {}
rows.append({
"key": it["key"],
"status": f["status"]["name"],
"assignee": assignee.get("displayName") or "未割当",
"created": f["created"],
"resolved": f.get("resolutiondate"),
})
df = pd.DataFrame(rows)
df["created"] = pd.to_datetime(df["created"], utc=True)
df["resolved"] = pd.to_datetime(df["resolved"], utc=True)
df["lead_time_h"] = (
(df["resolved"] - df["created"]).dt.total_seconds() / 3600
)
return df
3. Streamlit で最小ダッシュボード化
import streamlit as st
st.set_page_config(page_title="チーム別リードタイム", layout="wide")
st.title("チーム別リードタイム可視化")
jql = st.text_input(
"JQL",
value='project = "ABC" AND resolved >= -30d',
)
if st.button("取得"):
issues = fetch_issues(
jql,
fields=["status", "assignee", "created", "resolutiondate"],
)
df = to_dataframe(issues)
st.metric("対象チケット数", len(df))
st.dataframe(df.head(50))
median_by_user = (
df.dropna(subset=["lead_time_h"])
.groupby("assignee")["lead_time_h"]
.median()
.sort_values()
)
st.bar_chart(median_by_user)
streamlit run app.py で起動すれば、JQL を入力 → 取得 → 担当者別の中央値リードタイムが棒グラフで描画されるところまで到達する。
動作確認
JIRA_BASE_URL/JIRA_USER/JIRA_API_TOKENを環境変数に設定直近 30 日分のクローズ済みチケットで担当者別 median が描画されること
件数が 1 万件を超える場合は新しい
/rest/api/3/search/jqlエンドポイントへの移行を検討
ハマりポイント
権限: API トークンは「閲覧したいプロジェクトにアクセス権がある人」のもので発行する。管理者トークンの流用は社内ポリシー上 NG なケースが多い。
レート制限: Jira Cloud は概ね秒間 10 リクエスト程度が安全圏。一括取得時はバックオフ (リトライ + sleep) を入れる。
データ正規化: assignee が未割当 / resolutiondate が NULL / カスタムフィールドが
customfield_10031のような ID で返る の 3 点は最初に必ず引っかかる。タイムゾーン:
created/resolvedは UTC で返る。社内基準のタイムゾーンへ明示的に変換しないと「金曜夕方の山」が「土曜午前」に見えてしまう。AI コパイロットへの聞き方: 「Jira API でリードタイム出して」とだけ書くと回答がブレる。「
issues[].fields.createdとfields.resolutiondateの差を時間単位で」と粒度を下げて指示すると、出力が一貫しやすい。
応用・発展
営業 CRM (Salesforce / HubSpot) のリードを段階別歩留まりとして同様に可視化
製造業: 生産管理 SaaS の API から日次の作業完了件数を集計
物流: 受発注 API から「受注 → 出荷」までの内部リードタイム
小売: POS / EC バックエンドの API から「カテゴリ別 売上中央値」
採用: ATS の API で「応募 → 内定」までの段階別所要日数
データソースが API で取れる業務であれば、ほぼ同じ構造で展開できる。
まとめ
9 日というスパンは「コードを書く」時間ではなく、「課題を分解して、AI に聞く粒度を見つける」時間に使う、というのが本稿の肝である。非エンジニアであっても、既存 SaaS の API + pandas + Streamlit + AI コパイロット という最小スタックは現実的な選択肢になりつつある。
筆者の所属する 5years+ では、韓国・日本の中小〜中堅企業向けに、こうした「既存業務データの API 接続 → 小さく可視化 → 段階的に自動化」という流れの AI/業務自動化導入を支援している。社内に専任エンジニアがいない場合でも、月 100 万円規模の小さな PoC から始める案件が多い。気軽な実装相談は 5ya.io から可能である。