背景・目的
前回の記事で、Google Cloud が公開した OKF(Open Knowledge Format)の仕様を整理しました。
OKF は Markdown + YAML frontmatter で知識を構造化するフォーマットですが、仕様を読んだだけでは「で、何が嬉しいの?」が見えにくいと感じました。
データ基盤のプロジェクトでは「このカラムどこから来た?」「テーブル間の関係は?」「このカラム変えたらどこに影響する?」が日常的に発生します。これらの疑問に対して、OKF で構造化した情報を AI に読ませれば自動的に答えが得られるのではないか、という仮説を検証してみます。
本記事では、架空の EC サイトを題材に OKF バンドルを作成し、AI にデータリネージュ図・ER 図を自動生成させます。
まとめ
下記に特徴をまとめます。
| 特徴 | 内容 |
|---|---|
| やったこと | OKF バンドルを作り、AI にリネージュ図・ER 図・テスト計画書を生成させた |
| 必要な仕組み | Schema テーブルの Source 列 + cross-link(標準 Markdown リンク)+ SQL |
| 生成できたもの | カラムレベルリネージュ(Mermaid)、ER 図(Mermaid)、影響分析、テスト計画書 |
| 所感 | 構造化さえしておけば AI は図もテストケースも導出できる。OKF の「独自キー自由拡張」が効いている |
概要
前回のおさらい
OKF の核心を3点で振り返ります。
- 1ファイル = 1コンセプト(テーブル、ダッシュボード等)
- frontmatter(YAML)で
typeを宣言し、独自キーを自由に追加できる - cross-link(標準 Markdown リンク)で Concept 間の関係を表現する
今回のゴール
上記の仕組みを使って、AI に以下を自動生成させます。
- データリネージュ図(カラムレベル)
- ER 図(テーブル間リレーション)
- 影響分析(変更波及先の特定)
- 試験計画書
実践
前提
架空の EC サイトを題材にします。構成は以下の通りです。
ECサイト (PostgreSQL) Spark ETL S3 (Parquet) Athena → QuickSight
┌──────────────────┐ ┌────────────┐ ┌──────────────────────┐
│ orders │ │ │ │ Bronze(そのまま) │
│ order_items │───→│ 抽出 │───→│ Silver(クレンジング) │───→ Athena ───→ QS
│ customers │ │ 変換 │ │ Gold(集計) │
│ products │ │ 集計 │ └──────────────────────┘
└──────────────────┘ └────────────┘
Step 1: OKF バンドルを作る
steering/skillの作成
まずは、下記のようにsteeringとskillを用意します。
ec-knowledge/
├── .kiro/
│ ├── steering/
│ │ └── okf-knowledge.md ← AI に「OKF を必ず参照」を強制
│ └── skills/
│ ├── generate-lineage.md ← リネージュ図を生成するワークフロー
│ ├── generate-er-diagram.md ← E-R図を生成するワークフロー
│ ├── impact-analysis.md ← 影響分析するワークフロー
│ └── generate-test-plan.md ← テスト計画書を生成するワークフロー
└── .quick-workspace/
└── RULES.md ← Quick 向けの同等ルール(簡略版)
バンドル構成
下記のようなバンドル構成としました。
ec-knowledge/
├── index.md
├── tables/
│ ├── source/ ← EC サイト PostgreSQL
│ │ ├── orders.md
│ │ ├── order_items.md
│ │ ├── customers.md
│ │ └── products.md
│ ├── bronze/ ← 抽出したまま(Parquet 化のみ)
│ │ ├── orders.md
│ │ ├── order_items.md
│ │ ├── customers.md
│ │ └── products.md
│ ├── silver/ ← クレンジング・結合・正規化
│ │ ├── orders.md
│ │ ├── customers.md
│ │ └── products.md
│ └── gold/ ← ビジネスロジック集計
│ ├── daily_sales.md
│ ├── customer_purchase_summary.md
│ ├── category_trends.md
│ └── rfm_scores.md
├── flows/
│ ├── etl_bronze.md
│ ├── etl_silver_orders.md
│ └── etl_gold_daily_sales.md (他 Gold も同構造)
└── dashboards/
├── sales_dashboard.md
├── customer_dashboard.md
└── product_dashboard.md
以降、各レイヤの代表的なファイルを掲載します。
Source層: orders.md
---
type: PostgreSQL Table
title: orders
description: 注文ヘッダ。1行=1注文。
resource: postgresql://XXXXX.example.com:5432/ecdb/public.orders
tags: [EC, 注文, Source]
timestamp: 2026-06-01T00:00:00Z
---
# Schema
| Column | Type | Description | Source |
|--------|------|-------------|--------|
| order_id | UUID | 注文ID(PK) | アプリケーション発番 |
| customer_id | UUID | 顧客ID(FK → [customers](./customers.md)) | アプリケーション |
| order_date | DATE | 注文日 | アプリケーション |
| status | VARCHAR(20) | ステータス(confirmed/cancelled/returned) | アプリケーション |
Source 層: order_items.md
---
type: PostgreSQL Table
title: order_items
description: 注文明細。1行=1注文×1商品。
resource: postgresql://XXXXX.example.com:5432/ecdb/public.order_items
tags: [EC, 注文明細, Source]
timestamp: 2026-06-01T00:00:00Z
---
# Schema
| Column | Type | Description | Source |
|--------|------|-------------|--------|
| order_item_id | UUID | 明細ID(PK) | アプリケーション発番 |
| order_id | UUID | 注文ID(FK → [orders](./orders.md)) | アプリケーション |
| product_id | UUID | 商品ID(FK → [products](./products.md)) | アプリケーション |
| quantity | INTEGER | 数量 | ユーザー入力 |
| unit_price | NUMERIC(10,2) | 単価(税抜、注文時点) | 商品マスタから転記 |
Source 層: customers.md / products.md
(省略。customers は customer_id, customer_name, email, prefecture, registered_at。products は product_id, product_name, category, sub_category, list_price。)
Bronze 層: orders.md(代表例)
---
type: Bronze Table
title: bronze_orders
description: Source.orders をそのまま Parquet 化。スキーマ変更なし。
resource: s3://XXXXX/bronze/orders/
tags: [Bronze, 注文]
timestamp: 2026-06-01T00:00:00Z
---
# Schema
| Column | Type | Description | Source |
|--------|------|-------------|--------|
| order_id | STRING | 注文ID | [source.orders](../source/orders.md).order_id |
| customer_id | STRING | 顧客ID | [source.orders](../source/orders.md).customer_id |
| order_date | DATE | 注文日 | [source.orders](../source/orders.md).order_date |
| status | STRING | ステータス | [source.orders](../source/orders.md).status |
| _ingested_at | TIMESTAMP | 取込日時 | ETL付与 |
(bronze/order_items.md, bronze/customers.md, bronze/products.md も同構造。省略。)
Silver 層: orders.md
---
type: Silver Table
title: silver_orders
description: Bronze.orders + Bronze.order_items を結合・フラット化。cancelled 除外済み。
resource: s3://XXXXX/silver/orders/
tags: [Silver, 注文]
timestamp: 2026-06-01T00:00:00Z
---
# Schema
| Column | Type | Description | Source |
|--------|------|-------------|--------|
| order_id | STRING | 注文ID | [bronze.orders](../bronze/orders.md).order_id |
| order_date | DATE | 注文日 | [bronze.orders](../bronze/orders.md).order_date |
| customer_id | STRING | 顧客ID | [bronze.orders](../bronze/orders.md).customer_id |
| product_id | STRING | 商品ID | [bronze.order_items](../bronze/order_items.md).product_id |
| quantity | INTEGER | 数量 | [bronze.order_items](../bronze/order_items.md).quantity |
| unit_price | DOUBLE | 単価 | [bronze.order_items](../bronze/order_items.md).unit_price |
| line_amount | DOUBLE | 明細金額 | quantity * unit_price(ETL算出) |
# 変換ルール
- [bronze.orders](../bronze/orders.md) と [bronze.order_items](../bronze/order_items.md) を order_id で JOIN
- status = 'cancelled' の注文を除外
- line_amount = quantity * unit_price を算出
# 生成元
[etl_silver_orders](../../flows/etl_silver_orders.md)
Silver 層: customers.md
---
type: Silver Table
title: silver_customers
description: 重複排除・NULL補完済みの顧客マスタ。
resource: s3://XXXXX/silver/customers/
tags: [Silver, 顧客]
timestamp: 2026-06-01T00:00:00Z
---
# Schema
| Column | Type | Description | Source |
|--------|------|-------------|--------|
| customer_id | STRING | 顧客ID(PK) | [bronze.customers](../bronze/customers.md).customer_id |
| customer_name | STRING | 顧客名 | [bronze.customers](../bronze/customers.md).customer_name |
| email | STRING | メールアドレス | [bronze.customers](../bronze/customers.md).email |
| prefecture | STRING | 都道府県(NULL→'不明') | [bronze.customers](../bronze/customers.md).prefecture |
| registered_at | TIMESTAMP | 登録日時 | [bronze.customers](../bronze/customers.md).registered_at |
# 変換ルール
- customer_id で重複排除(最新レコードを採用)
- prefecture が NULL の場合 '不明' で補完
Silver 層: products.md
---
type: Silver Table
title: silver_products
description: カテゴリ名正規化済みの商品マスタ。
resource: s3://XXXXX/silver/products/
tags: [Silver, 商品]
timestamp: 2026-06-01T00:00:00Z
---
# Schema
| Column | Type | Description | Source |
|--------|------|-------------|--------|
| product_id | STRING | 商品ID(PK) | [bronze.products](../bronze/products.md).product_id |
| product_name | STRING | 商品名 | [bronze.products](../bronze/products.md).product_name |
| category | STRING | カテゴリ(正規化済み) | [bronze.products](../bronze/products.md).category |
| sub_category | STRING | サブカテゴリ | [bronze.products](../bronze/products.md).sub_category |
| list_price | DOUBLE | 定価 | [bronze.products](../bronze/products.md).list_price |
# 変換ルール
- category の表記ゆれを統一(例: '家電' / '家電製品' → '家電')
Gold 層: daily_sales.md
---
type: Gold Table
title: daily_sales
description: 日別×商品別の売上サマリ。
resource: s3://XXXXX/gold/daily_sales/
tags: [Gold, 売上, 日次]
timestamp: 2026-06-01T00:00:00Z
---
# Schema
| Column | Type | Description | Source |
|--------|------|-------------|--------|
| sale_date | DATE | 売上日 | [silver.orders](../silver/orders.md).order_date |
| product_id | STRING | 商品ID | [silver.orders](../silver/orders.md).product_id |
| product_name | STRING | 商品名 | [silver.products](../silver/products.md).product_name |
| category | STRING | カテゴリ | [silver.products](../silver/products.md).category |
| total_quantity | BIGINT | 合計数量 | SUM([silver.orders](../silver/orders.md).quantity) |
| total_amount | DOUBLE | 合計金額 | SUM([silver.orders](../silver/orders.md).line_amount) |
# 生成元
[etl_gold_daily_sales](../../flows/etl_gold_daily_sales.md)
Gold 層: customer_purchase_summary.md
---
type: Gold Table
title: customer_purchase_summary
description: 顧客別の購買サマリ(累積)。
resource: s3://XXXXX/gold/customer_purchase_summary/
tags: [Gold, 顧客, 購買分析]
timestamp: 2026-06-01T00:00:00Z
---
# Schema
| Column | Type | Description | Source |
|--------|------|-------------|--------|
| customer_id | STRING | 顧客ID | [silver.orders](../silver/orders.md).customer_id |
| customer_name | STRING | 顧客名 | [silver.customers](../silver/customers.md).customer_name |
| prefecture | STRING | 都道府県 | [silver.customers](../silver/customers.md).prefecture |
| total_orders | BIGINT | 累計注文数 | COUNT(DISTINCT [silver.orders](../silver/orders.md).order_id) |
| total_amount | DOUBLE | 累計購入金額 | SUM([silver.orders](../silver/orders.md).line_amount) |
| first_order_date | DATE | 初回注文日 | MIN([silver.orders](../silver/orders.md).order_date) |
| last_order_date | DATE | 最終注文日 | MAX([silver.orders](../silver/orders.md).order_date) |
# 生成元
[etl_gold_customer_purchase_summary](../../flows/etl_gold_customer_purchase_summary.md)
Gold 層: category_trends.md
---
type: Gold Table
title: category_trends
description: カテゴリ別の月次売上トレンド。
resource: s3://XXXXX/gold/category_trends/
tags: [Gold, カテゴリ, 月次]
timestamp: 2026-06-01T00:00:00Z
---
# Schema
| Column | Type | Description | Source |
|--------|------|-------------|--------|
| year_month | STRING | 年月(YYYY-MM) | DATE_FORMAT([silver.orders](../silver/orders.md).order_date, 'yyyy-MM') |
| category | STRING | カテゴリ | [silver.products](../silver/products.md).category |
| sub_category | STRING | サブカテゴリ | [silver.products](../silver/products.md).sub_category |
| order_count | BIGINT | 注文件数 | COUNT(DISTINCT [silver.orders](../silver/orders.md).order_id) |
| total_quantity | BIGINT | 合計数量 | SUM([silver.orders](../silver/orders.md).quantity) |
| total_amount | DOUBLE | 合計金額 | SUM([silver.orders](../silver/orders.md).line_amount) |
# 生成元
[etl_gold_category_trends](../../flows/etl_gold_category_trends.md)
Gold 層: rfm_scores.md
---
type: Gold Table
title: rfm_scores
description: 顧客別 RFM スコア(Recency / Frequency / Monetary)。
resource: s3://XXXXX/gold/rfm_scores/
tags: [Gold, 顧客, RFM]
timestamp: 2026-06-01T00:00:00Z
---
# Schema
| Column | Type | Description | Source |
|--------|------|-------------|--------|
| customer_id | STRING | 顧客ID | [silver.orders](../silver/orders.md).customer_id |
| customer_name | STRING | 顧客名 | [silver.customers](../silver/customers.md).customer_name |
| recency_days | INTEGER | 最終購入からの経過日数 | DATEDIFF(CURRENT_DATE, MAX([silver.orders](../silver/orders.md).order_date)) |
| frequency | BIGINT | 購入回数 | COUNT(DISTINCT [silver.orders](../silver/orders.md).order_id) |
| monetary | DOUBLE | 累計購入金額 | SUM([silver.orders](../silver/orders.md).line_amount) |
| r_score | INTEGER | Recency スコア(1-5) | NTILE(5) |
| f_score | INTEGER | Frequency スコア(1-5) | NTILE(5) |
| m_score | INTEGER | Monetary スコア(1-5) | NTILE(5) |
| rfm_segment | STRING | セグメント名 | ルールベース判定 |
# 生成元
[etl_gold_rfm_scores](../../flows/etl_gold_rfm_scores.md)
ダッシュボード: sales_dashboard.md
---
type: QuickSight Dashboard
title: 売上ダッシュボード
description: 日別・カテゴリ別の売上推移。
tags: [QuickSight, 売上]
timestamp: 2026-06-01T00:00:00Z
---
# データソース
Athena 経由で以下の Gold テーブルを参照:
- [daily_sales](../tables/gold/daily_sales.md)
- [category_trends](../tables/gold/category_trends.md)
# ビジュアル
| No | タイトル | 種類 | 使用カラム | テーブル |
|----|---------|------|-----------|---------|
| 1 | 日別売上推移 | 折れ線グラフ | sale_date, total_amount | daily_sales |
| 2 | カテゴリ別売上構成 | 円グラフ | category, total_amount | daily_sales |
| 3 | 商品別売上ランキング | テーブル | product_name, total_quantity, total_amount | daily_sales |
| 4 | カテゴリ月次トレンド | 積み上げ棒グラフ | year_month, category, total_amount | category_trends |
ダッシュボード: customer_dashboard.md
---
type: QuickSight Dashboard
title: 顧客分析ダッシュボード
description: 顧客別の購買傾向と RFM セグメント。
tags: [QuickSight, 顧客分析]
timestamp: 2026-06-01T00:00:00Z
---
# データソース
Athena 経由で以下の Gold テーブルを参照:
- [customer_purchase_summary](../tables/gold/customer_purchase_summary.md)
- [rfm_scores](../tables/gold/rfm_scores.md)
# ビジュアル
| No | タイトル | 種類 | 使用カラム | テーブル |
|----|---------|------|-----------|---------|
| 1 | 都道府県別顧客数 | 地図 | prefecture, COUNT(customer_id) | customer_purchase_summary |
| 2 | 購入金額分布 | ヒストグラム | total_amount | customer_purchase_summary |
| 3 | RFM セグメント構成 | ツリーマップ | rfm_segment, COUNT(customer_id) | rfm_scores |
| 4 | 優良顧客リスト | テーブル | customer_name, frequency, monetary, rfm_segment | rfm_scores |
ダッシュボード: product_dashboard.md
---
type: QuickSight Dashboard
title: 商品分析ダッシュボード
description: カテゴリ・サブカテゴリ別のトレンドと商品パフォーマンス。
tags: [QuickSight, 商品分析]
timestamp: 2026-06-01T00:00:00Z
---
# データソース
Athena 経由で以下の Gold テーブルを参照:
- [daily_sales](../tables/gold/daily_sales.md)
- [category_trends](../tables/gold/category_trends.md)
# ビジュアル
| No | タイトル | 種類 | 使用カラム | テーブル |
|----|---------|------|-----------|---------|
| 1 | サブカテゴリ別売上 | 横棒グラフ | sub_category, total_amount | category_trends |
| 2 | カテゴリ別月次推移 | 折れ線グラフ | year_month, category, total_amount | category_trends |
| 3 | 商品別数量ランキング | テーブル | product_name, total_quantity | daily_sales |
Step 2: AI にデータリネージュを生成させる
上記のStep 1でOKFによりバンドルを構築しました。次はプロンプトを実行します。
-
下記のようなプロンプトを実行させます
10% λ > 以下の OKF バンドルを読んで、daily_sales_dm のカラムレベルデータリネージュを Mermaid フローチャートで出力してください。 Source 列のリンクを辿って、各カラムがどのテーブルのどのカラムから来ているかを示してください。 @generate-lineageを使用すること -
実行が始まります
> まず daily_sales_dm のファイルを探し、スキーマを読みます。 Searching for files: **/*daily_sales* (using tool: glob) ✓ Successfully found 2 files under current directory - Completed in 0.6s > daily_sales_dm ではなく daily_sales がGoldレイヤにあります。これを読みます。 Reading file: /XXXXX/ec-knowledge/tables/gold/daily_sales.md, all lines (using tool: read) ✓ Successfully read 908 bytes from /XXXXX/ec-knowledge/tables/gold/daily_sales.md - Completed in 0.0s ・・・・・・ 補足: - silver_orders.line_amount は quantity * unit_price のETL算出カラム(Bronze層の2カラムから派生) - total_quantity / total_amount は SUM 集計であることをエッジラベルで明示 - daily_sales_dm という名前のテーブルはバンドル内に存在しなかったため、Gold層の daily_sales をリネージュ対象としました -
出力されたリネージュを開きます。カラムレベルのリネージュができました
Step 3: AI に ER 図を生成させる
-
プロンプトを実行します
@generate-er-diagramを使用して 以下の OKF バンドルの Schema セクションを読んで、テーブル間のリレーションを Mermaid ER 図で出力してください。 FK の cross-link(Markdownリンク)からリレーションを判定してください。 -
実行されます
> まずスキルの定義を確認します。 ・・・ - Summary: 6 operations processed, 6 successful, 0 failed - Completed in 0.5s > 全テーブルの情報が揃いました。レイヤごとにER図を生成してファイルに出力します。
Step 4: 影響分析
-
プロンプトを実行します
OKF バンドル内で、orders.customer_id を削除した場合の影響範囲を教えてください。 cross-link を逆引きして、影響を受けるファイルとカラムを列挙してください。また、QuickSightへの影響も考慮してください。 -
実行されます
まず impact-analysis スキルを確認します ・・・ > スキルの手順に従い、source/orders.customer_id から下流を再帰的に辿ります。既に読み込み済みの全テーブル情報で逆引きできます。 -
分析結果を確認します。それっぽい結果が出てきました
## ■ 影響分析: source.orders.customer_id の削除 ### 直接影響 | ファイル | カラム | 影響 | |----------|--------|------| | tables/bronze/orders.md | customer_id | ソース消失 → 取得不可 | ### 間接影響(Level 1: Bronze → Silver) | ファイル | カラム | 影響 | |----------|--------|------| | tables/silver/orders.md | customer_id | 上流Bronze消失 → 取得不可 | ### 間接影響(Level 2: Silver → Gold) | ファイル | カラム | 影響 | |----------|--------|------| | tables/gold/customer_purchase_summary.md | customer_id | 集計キー消失 → テーブル全体が生成不可 | | 同上 | customer_name, prefecture, total_orders, total_amount, first_order_date, last_order_date | 顧客別GROUP BY不可 | | tables/gold/rfm_scores.md | customer_id | 集計キー消失 → テーブル全体が生成不可 | | 同上 | recency_days, frequency, monetary, r_score, f_score, m_score, rfm_segment | 顧客別算出不可 | ### 間接影響(Level 3: Gold → QuickSight) | ダッシュボード | ビジュアル | 影響 | |---------------|-----------|------| | 顧客分析ダッシュボード | No.1 都道府県別顧客数 | customer_purchase_summary 消失 → 表示不可 | | 同上 | No.2 購入金額分布 | 同上 | | 同上 | No.3 RFMセグメント構成 | rfm_scores 消失 → 表示不可 | | 同上 | No.4 優良顧客リスト | 同上 | ### 影響を受けないテーブル - tables/gold/daily_sales.md — customer_id を使用していない - tables/gold/category_trends.md — 同上 - 売上ダッシュボード / 商品分析ダッシュボード — 同上 ### サマリ | 指標 | 件数 | |------|------| | 影響ファイル数 | 7件(Bronze 1 + Silver 1 + Gold 2 + Dashboard 1 + ETLフロー2) | | 影響カラム数 | 15件 | | QuickSight影響ビジュアル数 | 4件(顧客分析ダッシュボード全滅) | orders.customer_id を削除すると、**顧客軸の分析基盤が全壊**します。売上・カテゴリ系は無事です。
Step 5: AI にテスト計画書を生成させる
今度は、テスト計画書を生成させます
-
プロンプトを実行します
以下の OKF バンドルを読んで、daily_sales_dm のテスト計画書を作成してください。 Schema(型・NULL可否)、Source列(出自)、SQL(変換ロジック)、cross-link(参照関係) の全てを根拠にテストケースを導出してください。 -
実行が始まります
> まずスキルの定義とETLフローを確認します。 Batch fs_read operation with 2 operations (using tool: read) ↱ Operation 1: Reading file: /XXXXX/ec-knowledge/.kiro/skills/generate-test-plan.md, all lines ↱ Operation 2: Reading file: /XXXXX/ec-knowledge/flows/etl_gold_daily_sales.md, all lines ✓ Successfully read 1106 bytes from /XXXXX/ec-knowledge/.kiro/skills/generate-test-plan.md ✓ Successfully read 1326 bytes from /XXXXX/ec-knowledge/flows/etl_gold_daily_sales.md ⋮ - Summary: 2 operations processed, 2 successful, 0 failed - Completed in 0.2s > 全情報が揃いました。テスト計画書を生成します。 -
確認します。テスト計画書にはホワイトボックステストや境界値分析の観点を含めていなかったので単体テストとしては、今後精緻化できそうです




考察
今回 OKF を試してみて、OKF の本質は人間のドキュメントを AI のインターフェースにしたことだとわかりました。今後は、「設計書 → テスト計画生成 → 実装 → 検証」のサイクルを回し続けたいと思います。
以下は、本件で感じたことです。
- Source 列がないと AI はリネージュを生成できない。「どこから来たか」が構造化されていることが前提
- cross-link(Markdown リンク)がないと ER 図の FK 関係を判定できない。Description に文字で書いてあるだけでは精度が落ちる
- OKF の「独自キー自由拡張」(§4.1 Extensions)が Source 列の追加を可能にしている。仕様が厳格だったらこの応用はできない
- Excel 設計書でも同じ情報は書けるが、AI に直接読ませるにはひと手間必要かつ精度も落ちる。OKF(Markdown)であることが自動生成の前提条件
- 逆に言えば、OKF でなくても「Markdown + Source列 + リンク」があれば同じことは可能。OKF の価値は、この構造を仕様として標準化した点にある
参考


