プロジェクト管理 2026年10月15日

WBSを切った日は17本だった。4日後には33本になり、チケットの消化率は6割まで上がったが、残った13本はすべて人の作業か外部の確認待ちだった

XECIN WBSPMBOK技術検証

親チケットの進捗バーが 70% を指していました。WBS を切って起票してから、4日目の夜のことです。

数字だけ見れば、もう終盤です。ところが、リリースまでに残っている作業を1本ずつなぞってみると、専門家への確認の依頼、外部の審査、本番での受け入れ確認と、どれも手つかずのまま並んでいました。進捗率は上がっているのに、リリースの日付は1日も近づいていない。

正直なところ、この食い違いにはしばらく気づきませんでした。親チケットの進捗率は Redmine が子チケットから自動で出してくれる値で、私はそれを毎朝なんとなく眺めていただけだったんですよね。

気になったので、起票からの4日間を数え直しました。題材は、自社サービスの集客施策(広告の検証配信)の準備です。

初日の WBS は17本だった

初日の午前に、設計書と起票一覧を依頼側に承認してもらい、作業チケットを17本起票しました。「計測と着地ページを整えてリリースする」の枠に10本、「広告アカウントを開設して検証配信を行う」の枠に7本です。

このとき1本だけ、意図的に中身を切らなかったチケットがあります。「無料の上限に当たった利用者への案内を調べて設計する」という調査のチケットです。

PMBOK の WBS には「100%ルール」があり、プロジェクトの作業は漏れなく WBS に含める、というのが原則です。理屈はそうなんですが、調査の結果が出る前に実装のチケットまで書こうとすると、当て推量で書いたチケットの多くを、結果を見てから書き直すか捨てることになります。現場では、その書き直しのほうが高くつきがちです。そこで今回は調査を「箱」として1本で置き、中身は調査が終わってから切ることにしました。いわゆるローリングウェーブ計画です。

ここまでは計画どおりでした。

4日後に数えたら33本

4日目の夜の時点で、作業チケットは33本になっていました。16本増えています。足した日時はチケットの作成日時に残っているので、日ごとに数え直せます。

日付(その日の終わり)その日に足した本数チケット全体で見た消化初日の17本で見た消化
1日目(10-11)24 / 19(21%)4 / 17(24%)
2日目(10-12)04 / 19(21%)4 / 17(24%)
3日目(10-13)85 / 27(19%)5 / 17(29%)
4日目(10-14)619 / 33(58%)7 / 17(41%)
5日目(10-15)020 / 33(61%)7 / 17(41%)

想定外だったのは3日目です。この日は1本閉じたのに、全体の消化率は21%から19%に下がりました。調査の箱から実装が出てきて、その日の深夜に8本足したからです。

逆に4日目は14本閉じて、58%まで一気に上がりました。ただ、初日の17本で見ると、4日目に閉じたのは2本だけです。

足した16本を理由で分けた

最初は、増えた16本を「スコープが膨らんだ」と1つにまとめて報告しかけました。そう書くと、依頼側があとから注文を足したように読めます。念のためチケットの説明を1本ずつ読み、理由で分けてみると、印象はかなり違いました。

足した理由本数中身
調査の結果として出た実装6調査の箱から出た、画面の改修4本・ヘルプの更新1本・検証とリリース1本
実装や確認の途中で見つかった作業6計測タグの境界の場合(読み込み前に画面を離れたときなど)、登録ボタンの二度押し、着地ページに残っていた表現の直し、レビューの指摘から出たヘルプの直し
依頼側の方針変更2計測で送る項目を増やす判断と、それに合わせたプライバシーポリシーの追記
取りこぼし・分割2アカウント開設の終了条件の外に残った設定、外部の確認を待たずに進めるための前段の切り出し

依頼側の方針変更は2本だけでした。残りの14本は、こちらが計画の時点では書けなかった作業です。しかも6本は、初日に「中身は後で切る」と決めた箱から出てきたもので、いわば予定どおりの増加なんですよね。

報告を「スコープクリープ」の一言で済ませていたら、依頼側との間に要らない緊張を作るところでした。理由を分けておくと、次の打ち合わせで話すことも変わります。方針変更の2本は、何が後ろにずれるかを話す。調査から出た6本は、箱の大きさの読みが合っていたかを話す。途中で見つかった6本は、レビューで拾えた品質の話として扱う。この3つは、同じ「増えた」でも打ち手がまったく違います。

チケットの数で進捗を見ると何が起きるか

親チケットの進捗率は、子チケットに予定工数が入っていないと、子チケット1本を同じ重さとして平均します。閉じた子チケットは100%扱いです。「計測と着地ページ」の枠の70%は、17本中12本が閉じているという意味でした。

問題は、足したチケットの性質です。足した16本のうち13本はもう閉じていて、起票から閉じるまでの中央値は4時間39分でした。小さく切った実装のチケットは開発側が並行で片付けるので、早いものは1時間ちょっとで閉じます。

一方、残っている13本の内訳はこうです。

  • 人が行う作業、または人と開発が一緒に行う作業: 9本(専門家への確認の依頼、外部の審査が絡む設定、撮影、入稿、本番の受け入れ確認など)
  • 開発側の作業だが、外部の確認を待っているもの: 4本(3本は作業を終えて確認の結果待ち、1本は確認の結果を見てから着手)

4日目に閉じた14本は、すべて開発側のチケットでした。人と外部の作業は、この日1本も減っていません。閉じやすいチケットを足して閉じるたびに進捗率は上がるのに、リリースを決めている作業はそのままだったわけです。ドキュメントには「タスクの数ではなく成果物で進捗を見る」と書いてあることが多いですが、ツールの既定の進捗率はタスクの数そのものなので、放っておくとこうなります。

数え直しに使ったもの

Redmine からチケットを CSV で書き出し、追加の理由(reason)と担い手(owner)の2列だけ手で足しました。

id,created,closed,reason,owner
9011,2026-10-11 09:56,2026-10-11 12:55,plan,dev
9021,2026-10-11 10:05,,plan,people
9056,2026-10-13 23:56,2026-10-14 05:13,investigation,dev
9068,2026-10-14 19:01,2026-10-14 20:58,found,dev

created が計画の締め(起票し終えた時刻)以前なら計画分、それより後なら追加分です。日ごとに、その日の終わりまでに作られたチケットと閉じたチケットを数えます。

import csv
from collections import Counter
from datetime import date, datetime, timedelta

BASELINE_AT = datetime(2026, 10, 11, 10, 10)  # WBS を承認して起票し終えた時刻
FIRST_DAY, LAST_DAY = date(2026, 10, 11), date(2026, 10, 15)

def at(s):
    return datetime.strptime(s, "%Y-%m-%d %H:%M") if s else None

tickets = [
    {**r, "created": at(r["created"]), "closed": at(r["closed"])}
    for r in csv.DictReader(open("tickets.csv", encoding="utf-8"))
]
baseline = [t for t in tickets if t["created"] <= BASELINE_AT]

day = FIRST_DAY
while day <= LAST_DAY:
    end = datetime.combine(day, datetime.max.time())
    scope = [t for t in tickets if t["created"] <= end]
    done = [t for t in scope if t["closed"] and t["closed"] <= end]
    base_done = [t for t in baseline if t["closed"] and t["closed"] <= end]
    print(f"{day:%m-%d} 全体 {len(done):2}/{len(scope):2} ({len(done) / len(scope):4.0%})"
          f"  計画分 {len(base_done):2}/{len(baseline)} ({len(base_done) / len(baseline):4.0%})")
    day += timedelta(days=1)

added = [t for t in tickets if t["created"] > BASELINE_AT]
print("追加の理由:", dict(Counter(t["reason"] for t in added).most_common()))
left = [t for t in tickets if not t["closed"]]
print("残りの担い手:", dict(Counter(t["owner"] for t in left).most_common()))

# 実行結果:
# 10-11 全体  4/19 ( 21%)  計画分  4/17 ( 24%)
# 10-12 全体  4/19 ( 21%)  計画分  4/17 ( 24%)
# 10-13 全体  5/27 ( 19%)  計画分  5/17 ( 29%)
# 10-14 全体 19/33 ( 58%)  計画分  7/17 ( 41%)
# 10-15 全体 20/33 ( 61%)  計画分  7/17 ( 41%)
# 追加の理由: {'investigation': 6, 'found': 6, 'policy': 2, 'leftover': 2}
# 残りの担い手: {'people': 9, 'review': 4}

このスクリプトには改善の余地があります。チケット1本を同じ重さで数えているので、1時間で閉じる実装と、何営業日もかかる外部の審査が同じ1本です。作業を終えて外部の確認を待っているだけのチケットも、閉じていない以上は0として数えています。それでも、2つの消化率を並べるだけで、少なくとも「6割終わりました」と言わずに済みました。

追加するときに1行だけ残す

数え直しで一番手間がかかったのは、16本の理由を説明文から読み取る作業でした。次からは、チケットを足すときに説明の先頭へ次の2行を書くことにしています。

追加の理由: 調査の結果 / 途中で見つかった / 方針変更 / 取りこぼし・分割 のどれか1つ
元のチケット: #(この追加のきっかけになったチケットの番号)

4つに絞ったのは、迷わず選べる数にしたかったからです。PMBOK の変更管理にならって、追加のたびに影響(期間・コスト・品質)まで書く案も考えましたが、3日目のように一晩で8本足す日には回りません。影響まで書くのは「方針変更」のときだけにしました。フレームワークは目的ではなく道具なので、重いところは削り、後から数えられる1行だけを残します。

あわせて、枠のチケットの説明には初日の子チケットの表を残し、あとから足したものは「追加」と書いた行で下に足しています。これが無いと、どれが初日の計画だったのかを、作成日時から逆算することになります。今回の数え直しがまさにそれでした。PMBOK でいうスコープ・ベースライン(承認した時点の WBS)を、ツールが覚えていてくれないなら自分で残しておく、ということです。

おわりに

4日間を数え直して分かったのは、WBS の本数が増えること自体は悪くない、ということでした。増えた理由の内訳があって、初日の計画分とその後の分を分けて数えられれば、進捗の報告はむしろ正直になります。

次の案件では、承認した時点の WBS を計画分として固定し、週次の報告に「チケット全体の消化」「計画分の消化」「残りのうち人と外部の作業の本数」の3つを並べてみるつもりです。調査の箱についても、そこから何本くらい出てきそうかを初日に幅で書いておき、あとで答え合わせをしたいと思っています。今回の箱から出てきたのは、6本でした。