Notion のリンクを投げたらバリューストリームマップを作成してくれる GPTs を作ってみた

# バリューストリームマップとは
- 製品やサービスが初期段階から最終的な顧客に届くまでの全プロセスをマッピングしたもの
- マッピングした各プロセスにどのぐらい時間を要したか可視化したもの

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457899/5c9f7a5b-2470-3ef0-793e-2fb556645b6a.png", width=500>
https://dora.dev/capabilities/work-visibility-in-value-stream/

# 目的
- 開発フローを透明化してメンバー全員が把握できるようにする
- 各フローでどのぐらい時間がかかっているかを計測できるようにする
- どのフローが改善できるか、もっとよくできるか、を特定して今後のアクションに繋げる

# 完成物
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457899/0e9628fc-0a4c-d048-012d-853994d737f6.png", width=500>

```markdown
graph LR
    linkStyle default interpolate basis
    A[仕様検討] -->|PT: 0h<br>LT: 16h| B[開発]
    B -->|PT: 0h<br>LT: 24h| C[QA]
    C -->|PT: 0h<br>LT: 95h| D[完了]
```

```mermaid
graph LR
    linkStyle default interpolate basis
    A[仕様検討] -->|PT: 0h<br>LT: 16h| B[開発]
    B -->|PT: 0h<br>LT: 24h| C[QA]
    C -->|PT: 0h<br>LT: 95h| D[完了]
```

# GPTs 概要

## 前提
- Notion でタスクを管理していること
- タスクステータス変更履歴が Slack にあること
  - https://www.notion.so/ja-jp/help/slack
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457899/86a50ef4-9994-a45f-9777-15c35cb8a25a.png", width=500>

## 使用技術
- Notion API
- Slack API
- GPTs
- Cloudflare

## 流れ
1. GPTs に Notion のタスクカードのリンクを投げる
2. GPTs が Notion のタスクカードのリンクからページIDを取得して、Cloudflare 側にリクエストを投げる
3. Cloudflare 側のコードで以下処理を行う
    - Notion API を用いて該当のタスクカード名を取得する
    - Slack API を用いて該当のタスクカード名で検索を行い、変更履歴を取得する
4. 取得した変更履歴を良い感じに整形して GPTs 側に返す
5. GPTs の(良い感じ)のプロンプトによって変更履歴の情報からバリューストリームマップを作成する

### 各流れの実装(抜粋)
> ②：GPTs が Notion のタスクカードのリンクからページIDを取得

```:プロンプト
# 要件
1. Notion からデータを抽出
  - 提供された Notion リンクから pageId を取得する。 // 実際はここまででOK(以下は後続の指示)
  - pageId を使用して getTaskCardInfo を実行する。
  - 重要: getTaskCardInfo を実行し、もしエラーが出た場合はその内容や原因を詳細に出力してください。
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457899/774cd229-65f1-011e-ab1f-69bd27970e9e.png", width=500>

---

> ②：Cloudflare 側にリクエストを投げる

Action を定義する
- 認証タイプ： APIキー
- APIキー： Notion インテグレーションで作成したシークレットキー
- 認証タイプ： Bearer
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457899/a5baaee5-6254-cd0e-a8c8-2653a2189199.png", width=500>

<details><summary>Schema を定義する</summary>

```yaml
{
  "openapi": "3.1.0",
  "info": {
    "title": "Notion Task Card Info API",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "hogefuga.workers.dev" // cloudflare のURL に変更する
    }
  ],
  "paths": {
    "/": {
      "get": {
        "operationId": "getTaskCardInfo",
        "tags": ["Task Card"],
        "summary": "Retrieve information about a specific task card in Notion",
        "parameters": [
          {
            "name": "notion_page_id",
            "in": "query",
            "required": true,
            "description": "The ID of the task card (Notion page ID)",
            "schema": {
              "type": "string"
            }
          }
        ],
        ...略
```

</details>

---

> ③：Cloudflare 側のコードで以下処理を行う
```javascript: Notionのタスクカード名を取得
async function fetchNotionTitle(pageId) {
  const NOTION_API_URL = "https://api.notion.com/v1/pages/";
  const NOTION_API_KEY = NOTION_SECRET;
  const notionResponse = await fetch(`${NOTION_API_URL}${pageId}`, {
    headers: {
      "Authorization": `Bearer ${NOTION_API_KEY}`,
      "Notion-Version": "2022-06-28"
    }
  });
  const notionPageData = await notionResponse.json();
  const notionTitle = notionPageData.properties["名前"].title[0].plain_text;
  return notionTitle;
}
```
```javascript: タスクカードの変更履歴を Slack の検索で取得
async function fetchSlackData(taskTitle, searchWord) {
  const SLACK_API_URL = "https://slack.com/api/search.messages";
  const SLACK_API_KEY = SLACK_USER_OAUTH_TOKEN;
  const query = encodeURIComponent(`${taskTitle} & "${searchWord}" & "→" in:${SLACK_CHANNEL_NAME}`); // "→"を含めているのはステータス変更以外で検索にヒットする内容を除外するため
  const slackResponse = await fetch(`${SLACK_API_URL}?query=${query}`, {
    headers: {
      "Authorization": `Bearer ${SLACK_API_KEY}`,
      "Content-Type": "application/json"
    }
  });
  if (!slackResponse.ok) {
    throw new Error("Failed to fetch data from Slack");
  }
  return slackResponse.json();
}
```
Slack の検索はタスクカード名と変更を監視したいプロパティ名を「&」で繋げて検索する事で欲しい情報が取得できる。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457899/7ec4114b-98d5-603e-854e-78453b7106da.png", width=500>

> ④：取得した変更履歴を良い感じに整形して GPTs 側に返す

この取得できた変更履歴を良い感じに整形してレスポンスとして GPTs 側に返す。

```json: レスポンス例

  "notion_title": "課金画面の実装",
  "notion_histories": [
    {
      "status": "*ステータス*\n~要求・要件整理~ → デザイン",
      "date": "2024/7/10 17:14:05",
      "elapsed_time_hour": 0
    },
    {
      "status": "*ステータス*\n~デザイン~ → 仕様検討",
      "date": "2024/7/10 17:34:43",
      "elapsed_time_hour": 0
    },
    {
      "status": "*ステータス*\n~仕様検討~ → 開発",
      "date": "2024/7/11 9:58:10",
      "elapsed_time_hour": 16
    },
    {
      "status": "*ステータス*\n~開発~ → QA",
      "date": "2024/7/12 10:41:06",
      "elapsed_time_hour": 24
    },
    {
      "status": "*ステータス*\n~QA~ → 完了",
      "date": "2024/7/16 10:23:47",
      "elapsed_time_hour": 95
    }
  ]
}
```
※経過時間(h)を Cloudflare 側で計算して返却しているのは GPTs 側で date から経過時間を計算させた時に正しい結果が得られない事があったため。(GPTs のプロンプト次第では GPTs 側でも計算可能だと思います)

> ⑤：GPTs の(良い感じ)のプロンプトによって変更履歴の情報からバリューストリームマップを作成する

バリューストリームマップを Mermeid 記法で出力してもらうようプロンプトを追記

```:プロンプト
...略

2. レスポンスの処理
  - Notion の変更履歴から以下の情報を抽出する
    - Notion ページのタイトル (notion_title)
    - 変更履歴の詳細（notion_histories)
3. リードタイム(LT)を取得
  - 下記の変更履歴の "elapsed_time_hour" を取得しリードタイム（LT）として Value Stream Map の作成に使用する。
    - notion_histories
4. Value Stream Map の作成
  - 各ステータス毎に以下の形式で Mermeid 形式の図表を出力する。ただし "elapsed_time_hour" が 0 のステータスについては Value Stream Map には含めないで下さい。"elapsed_time_hour" が 0 以外のものは漏れなく Value Stream Map に含めて下さい
    - 「ステータス」に対する Value Stream Map を Mermeid 形式の図表を出力して下さい。

graph LR
    linkStyle default interpolate basis
    A[ステータス 1] -->|PT: 0h<br>LT: Xh| B[ステータス 2]
    B -->|PT: 0h<br>LT: Xh| C[ステータス 3]
    C -->|PT: 0h<br>LT: Xh| D[ステータス 4]

# 例
graph LR
    linkStyle default interpolate basis
    A[仕様検討中] -->|PT: 0h<br>LT: 5h| B[見積もり]
    B -->|PT: 0h<br>LT: 8h| C[Ready]
    C -->|PT: 0h<br>LT: 3h| D[開発中]

- ステータス 1、ステータス 2 などは、Notion データからの実際のステータス名に置き換えてください。
- PT（処理時間）は 0h に設定してください。
```

# 問題・課題
- そもそもタスクボードを正しく管理・更新しないと LT がデタラメな数値になってしまう
  - デイリースクラム等の毎日のイベントを通して、タスクボードを適切なタイミングで更新する仕組みを設ける必要がある(難しい)
- PT(プロセスタイム) や %C/A(非手戻り率) の算出には未対応(難しい)

# まとめ
色々問題や課題はあるものの GPTs 等のツールを用いてバリューストリームマップを作成する事はできた。
GPTs で作成したこのバリューストリームマップをたたき台として、改めてチーム全員でバリューストリームマッピングを実施していくのが、良いのではないかと思います。
その第一歩としてこのツールを今後活用して行きたいなと思います。

# 参考記事
https://www.notion.so/ja/help/create-integrations-with-the-notion-api

https://zenn.dev/yutakobayashi/articles/gpts-notion-api

https://note.com/toki_mwc/n/n2d899d0dd049