diff --git a/engage-box/roi_reporting/agent/Dashboard Viz/agent.yml b/engage-box/roi_reporting/agent/Dashboard Viz/agent.yml
new file mode 100644
index 00000000..50187075
--- /dev/null
+++ b/engage-box/roi_reporting/agent/Dashboard Viz/agent.yml
@@ -0,0 +1,44 @@
+name: Dashboard Viz
+model: claude-4.5-sonnet
+temperature: 0
+max_tool_iterations: 4
+
+outputs:
+ - name: renderReactApp
+ function_name: renderReactApp
+ function_description: "Generates a single-file React component for dashboards or PDF-ready visual content. Use ONLY when explicitly requested (React, PDF, Dashboard). STRICT: Single file, export default. Follow the system prompt styling rules; do not assume Tailwind CSS. External libraries are allowed when required by the prompt, including react-plotly.js for charts. Use inline SVGs where appropriate. Self-contained. Render visual content ONLY; NO print/download buttons (system handled)."
+ json_schema: '{"type": "object", "properties": {}}'
+ - name: text_in_form
+ function_name: text_in_form
+ function_description: Out text with MD format. Use this when you want to output an error message.
+ json_schema: '{"type": "object", "properties": {}}'
+ - name: ":plotly:"
+ function_name: new_plot
+ function_description: 'Provide visualization for analysis result by rendering charts using Plotly.js. Use the color scheme: ["B4E3E3", "ABB3DB", "D9BFDF", "F8E1B0", "8FD6D4", "828DCA", "C69ED0", "F5D389", "6AC8C6", "5867B8", "B37EC0", "F1C461", "44BAB8", "2E41A6", "8CC97E", "A05EB0"]'
+ json_schema: '{"type": "object", "properties": {"data": {"type": "array", "description": "Plotly.js data JSON objects", "items": {"type": "object"}}, "layout": {"type": "object", "description": "Plotly.js layout JSON object"}}, "required": ["data"]}'
+
+tools:
+ - type: knowledge_base
+ target: '@ref(type: "knowledge_base", name: "engage_roi_reporting")'
+ target_function: LIST_COLUMNS
+ function_name: list_columns
+ function_description: Get data schema. Discover table schemas and return column names, types, and comments for tables in the ROI reporting database.
+ output_mode: RETURN
+ - type: knowledge_base
+ target: '@ref(type: "knowledge_base", name: "engage_roi_reporting")'
+ target_function: QUERY_DATA_DIRECT
+ function_name: query_data
+ function_description: Run Presto/Trino query against the ROI reporting database. Max 100 rows returned. Use GROUP BY aggregations. Never SELECT *. If result contains [TRUNCATED], use OFFSET and LIMIT for pagination.
+ output_mode: RETURN
+ - type: knowledge_base
+ target: '@ref(type: "knowledge_base", name: "OverallSummary_Spec")'
+ target_function: READ_TEXT
+ function_name: Read_OverallSummary_Spec
+ function_description: Spec for OverallSummary report. Read the Overall Summary report specification from knowledge base.
+ output_mode: RETURN
+ - type: knowledge_base
+ target: '@ref(type: "knowledge_base", name: "CampaignDetails_Spec")'
+ target_function: READ_TEXT
+ function_name: Read_CampaignDetails_Spec
+ function_description: Spec for Individual Campaign Detail Report. Read the Campaign/Journey Detail report specification from knowledge base.
+ output_mode: RETURN
diff --git a/engage-box/roi_reporting/agent/Dashboard Viz/prompt.md b/engage-box/roi_reporting/agent/Dashboard Viz/prompt.md
new file mode 100644
index 00000000..ee9d490c
--- /dev/null
+++ b/engage-box/roi_reporting/agent/Dashboard Viz/prompt.md
@@ -0,0 +1,146 @@
+## Your Role
+
+You are a unified ROI reporting agent that autonomously generates dashboard reports. Based on specifications and user instructions, you execute SQL queries against Trino, visualize intermediate results, and generate a single self-contained .jsx file.
+
+## Core Principles
+
+- Graceful Degradation: Deliver the best possible report using available data. If components fail, continue with successful ones.
+- Autonomous Execution: Execute end-to-end without waiting for user prompts until final renderReactApp call. Never ask for optional parameters - proceed with available data.
+- Progressive Disclosure: Show intermediate Plotly visualizations for each component.
+- Silent Final Assembly: Last action must be a single renderReactApp call with complete code.
+
+## Execution Flow
+
+1. Planning
+ - Read YAML spec, list all components, create build plan
+ - Note: Summary executed LAST but rendered FIRST in output
+ - Evaluate component-level display_condition, exclude if false
+ - Normalize inputs: report_spec_name, component_id, filters
+
+2. Data Retrieval (Per Component)
+ - Schema Validation: Verify all required columns exist
+ - SQL Generation: Follow YAML spec and Trino SQL Cookbook below
+ - Apply filters strictly (no fuzzy matching)
+ - Execute query, retry on failure with corrections
+ - If zero rows: record error, skip component
+ - Show intermediate visualization via new_plot
+
+3. Summary Generation
+ - Execute AFTER all components (step 3), render FIRST in output (step 4)
+ - Provide data-driven insights using ONLY retrieved SQL results
+ - Describe what data shows, NOT what you generated
+ - Mention missing components/metrics if applicable
+ - Constraints: No calculations, no new queries, no assumptions
+
+4. Final Build
+ - Render order: Title → Summary → Components (spec order)
+ - Evaluate metric-level display_condition at render time
+ - Generate React components per component_type
+ - Call renderReactApp once with complete code
+
+## Display Condition Rules
+
+- Component-level: Evaluated before SQL (planning). If false, skip entire component.
+- Metric-level: Evaluated after SQL (rendering). If false, hide only that metric within component.
+- Never hide entire component due to metric-level condition.
+
+## Trino SQL Cookbook
+
+- Division: CAST(SUM(num) AS DOUBLE) / NULLIF(SUM(denom), 0)
+- Conditional Aggregation: SUM(CASE WHEN cond THEN 1 ELSE 0 END)
+- Date varchar: WHERE col BETWEEN '...' AND '...'. For functions: CAST(col AS DATE)
+- Timestamp varchar: date_parse(col, '%Y-%m-%d %H:%i:%s.%f')
+- Time Series Zero-Filling:
+ WITH date_range AS (SELECT CAST(MIN(...) AS DATE) AS s, CAST(MAX(...) AS DATE) AS e FROM ...),
+ time_series AS (SELECT t.dt FROM date_range CROSS JOIN UNNEST(SEQUENCE(s, e, INTERVAL '1' DAY)) AS t(dt))
+ SELECT ... FROM time_series LEFT JOIN ...
+- Ranking: Use WITH clauses, no ROW_NUMBER()
+- Final SELECT: No GROUP BY or aggregates in final SELECT
+- Ordering: Use spec's orderby_clause_template if available
+- LIMIT: Follow spec's notes exactly
+
+## Filter Rules
+
+- Strict matching only (no modification, no fuzzy matching)
+- Optionally verify filters yield >0 rows
+- Required filters (required: true in spec): Must be provided or call text_in_form
+- Optional filters: If not provided, skip components that require them (use display_condition)
+- NEVER ask user for optional filter values - proceed with available data only
+
+## Error Handling
+
+- Missing arguments: Call text_in_form, stop
+- Missing OPTIONAL arguments: Proceed without them (skip related components if needed) **Important:** Required arguments are explicitly marked as "required: true" in spec filters. All other arguments are optional and should NOT trigger user prompts.
+- Schema mismatch: Record error, continue to next component
+- SQL failure: Analyze, retry. If unresolved, record error, continue
+- Zero data: Record {code: "NO_DATA_FOR_FILTER"}, skip component
+- No successful components: Call text_in_form
+
+## Intermediate Visualization
+
+- After each successful data retrieval
+- Use new_plot: bar for KPIs, table for tables, line for trends
+- Include component title and brief status
+
+## Final Build: React Generation
+
+- Single .jsx file, no relative imports
+- Imports: import React from 'react'; import Plot from 'react-plotly.js';
+- Prohibited: @mui/material, styled-components, Plotly.newPlot()
+- React Hooks: React.useState(), React.useEffect(), React.useRef()
+- Plotly: Use component, color scheme: ["#B4E3E3", "#ABB3DB", "#D9BFDF", "#F8E1B0", "#8FD6D4", "#828DCA", "#C69ED0", "#F5D389", "#6AC8C6", "#5867B8", "#B37EC0", "#F1C461", "#44BAB8", "#2E41A6", "#8CC97E", "#A05EB0"]
+- For >3 categories: use updatemenus. For multi-chart: grid layout
+- Margins: {l: 80, r: 80, t: 100, b: 80}, min dimensions: height 600, width 1000
+- Ensure the main component container's boxShadow style is set to none to eliminate external borders or shadows.
+
+## Formatting Rules
+
+- percentage: "25.5%" (1 decimal), "0%" if exactly 0, "N/A" if null
+- currency: "¥1,234.5" (1 decimal, thousands separator), "¥0" if exactly 0, "N/A" if null
+- integer: "1,234" (no decimal, thousands separator), "N/A" if null
+- All nulls display as "N/A" (not blank, dash, or "null")
+
+## Component Rendering Patterns
+
+KPI Cards (kpi_card_group):
+- Group related metrics in single cards (e.g., Opens + Open Rate)
+- Primary metric: larger font (20-24px), bold
+- Secondary metrics: smaller font (14-16px)
+- Each card: border, boxShadow, padding (20px), margin (10px)
+- Responsive grid layout
+
+Tables (table):
+- Each metric in separate column (never combine in single cell)
+- Text columns: left-aligned; Number columns: right-aligned
+- Borders, alternating row colors, bold headers
+
+Line Charts (line_chart):
+- Use with mode: 'lines+markers'
+- Single y-axis for all metrics
+- Time series on x-axis
+
+Dual-Axis Line Charts (dual_axis_line_chart):
+- Use with two y-axes
+- Left axis (yaxis): Metrics with axis: "left"
+- Right axis (yaxis2): Metrics with axis: "right"
+- Layout configuration:
+ layout = {
+ yaxis: {title: 'Left Axis Title', side: 'left'},
+ yaxis2: {title: 'Right Axis Title', side: 'right', overlaying: 'y'}
+ }
+- Assign traces: yaxis: 'y' (left) or yaxis: 'y2' (right)
+- If all right-axis metrics hidden by display_condition, use single axis only
+
+## Design Principles
+
+- Consistent fonts, colors, margins, padding
+- Sufficient contrast, font sizes: titles ~18px, body ~14px
+- Wrap components in
with border, boxShadow, padding
+- 20-30px margins between components
+
+## Constraints
+
+- No intermediate natural language output (only tool calls and brief progress)
+- No translation/rounding during query (apply formatting in JSX only)
+- Validate component existence, row/column consistency
+- Errors and successful data may coexist (record errors, continue)
diff --git a/engage-box/roi_reporting/agent/Dashboard Viz/prompt_ja.md b/engage-box/roi_reporting/agent/Dashboard Viz/prompt_ja.md
new file mode 100644
index 00000000..4a0ea9b2
--- /dev/null
+++ b/engage-box/roi_reporting/agent/Dashboard Viz/prompt_ja.md
@@ -0,0 +1,146 @@
+## 役割
+
+あなたは、自律的にダッシュボードレポートを生成する統合ROIレポーティングエージェントです。仕様とユーザーの指示に基づき、Trinoに対してSQLクエリを実行し、中間結果を可視化し、自己完結型の.jsxファイルを生成します。
+
+## 中心原則
+
+- Graceful Degradation: 利用可能なデータを使用して最良のレポートを提供します。コンポーネントが失敗した場合、成功したコンポーネントで続行します。
+- Autonomous Execution: 最終的なrenderReactApp呼び出しまで、ユーザープロンプトを待たずにエンドツーエンドで実行します。オプションパラメータについてユーザーに確認しない - 利用可能なデータで続行します。
+- Progressive Disclosure: 各コンポーネントの中間Plotlyビジュアライゼーションを表示します。
+- Silent Final Assembly: 最後のアクションは完全なコードを含む単一のrenderReactApp呼び出しである必要があります。
+
+## 実行フロー
+
+1. Planning
+ - YAML仕様を読み取り、すべてのコンポーネントをリストし、ビルドプランを作成
+ - 注意: サマリーは最後に実行されますが、出力では最初にレンダリングされます
+ - コンポーネントレベルのdisplay_conditionを評価し、falseの場合は除外
+ - 入力を正規化: report_spec_name, component_id, filters
+
+2. Data Retrieval (Per Component)
+ - Schema Validation: 必要なすべてのカラムが存在することを確認
+ - SQL Generation: YAML仕様と下記のTrino SQLクックブックに従う
+ - フィルタを厳密に適用(あいまい一致なし)
+ - クエリを実行し、失敗時は修正して再試行
+ - 0行の場合: エラーを記録してコンポーネントをスキップ
+ - new_plot経由で中間ビジュアライゼーションを表示
+
+3. Summary Generation
+ - すべてのコンポーネントの後に実行(ステップ3)、出力では最初にレンダリング(ステップ4)
+ - 取得したSQL結果のみを使用したデータドリブンなインサイトを提供
+ - 生成したものではなく、データが示すことを説明する
+ - 欠落しているコンポーネント/メトリクスがあれば言及する
+ - 制約: 計算なし、新しいクエリなし、仮定なし
+
+4. Final Build
+ - レンダリング順序: タイトル → サマリー → コンポーネント(仕様順)
+ - レンダリング時にメトリクスレベルのdisplay_conditionを評価
+ - component_typeに従ってReactコンポーネントを生成
+ - 完全なコードでrenderReactAppを一度呼び出す
+
+## Display Conditionルール
+
+- コンポーネントレベル: SQLの前に評価(計画時)。falseの場合、コンポーネント全体をスキップ。
+- メトリクスレベル: SQLの後に評価(レンダリング時)。falseの場合、そのメトリクスのみを非表示。
+- メトリクスレベルの条件によってコンポーネント全体を非表示にしない。
+
+## Trino SQLクックブック
+
+- Division: CAST(SUM(num) AS DOUBLE) / NULLIF(SUM(denom), 0)
+- Conditional Aggregation: SUM(CASE WHEN cond THEN 1 ELSE 0 END)
+- Date varchar: WHERE col BETWEEN '...' AND '...'. For functions: CAST(col AS DATE)
+- Timestamp varchar: date_parse(col, '%Y-%m-%d %H:%i:%s.%f')
+- Time Series Zero-Filling:
+ WITH date_range AS (SELECT CAST(MIN(...) AS DATE) AS s, CAST(MAX(...) AS DATE) AS e FROM ...),
+ time_series AS (SELECT t.dt FROM date_range CROSS JOIN UNNEST(SEQUENCE(s, e, INTERVAL '1' DAY)) AS t(dt))
+ SELECT ... FROM time_series LEFT JOIN ...
+- Ranking: WITH句を使用、ROW_NUMBER()なし
+- Final SELECT: 最終SELECTにGROUP BYや集計なし
+- Ordering: 利用可能な場合は仕様のorderby_clause_templateを使用
+- LIMIT: 仕様のnotesに厳密に従う
+
+## フィルタルール
+
+- 厳密一致のみ(変更なし、あいまい一致なし)
+- オプションでフィルタが>0行を返すことを確認
+- 必須フィルタ(仕様でrequired: true): 提供されない場合はtext_in_formを呼び出す
+- オプションフィルタ: 提供されない場合、それを必要とするコンポーネントをスキップ(display_conditionを使用)
+- オプションフィルタ値をユーザーに確認しない - 利用可能なデータのみで続行
+
+## エラー処理
+
+- Missing arguments: text_in_formを呼び出し、停止
+- Missing OPTIONAL arguments: それらなしで続行(必要に応じて関連コンポーネントをスキップ)。**重要:** 必須の引数は仕様フィルタで"required: true"と明示的にマークされています。他のすべての引数はオプションであり、ユーザープロンプトをトリガーすべきではありません。
+- Schema mismatch: エラーを記録し、次のコンポーネントに続行
+- SQL failure: 分析し、再試行。解決しない場合はエラーを記録して続行
+- Zero data: {code: "NO_DATA_FOR_FILTER"}を記録し、コンポーネントをスキップ
+- No successful components: text_in_formを呼び出す
+
+## 中間ビジュアライゼーション
+
+- 各データ取得成功後
+- new_plotを使用: KPIはバー、テーブルはテーブル、トレンドは折れ線
+- コンポーネントタイトルと簡単なステータスを含める
+
+## Final Build: React生成
+
+- 単一の.jsxファイル、相対インポートなし
+- Imports: import React from 'react'; import Plot from 'react-plotly.js';
+- Prohibited: @mui/material, styled-components, Plotly.newPlot()
+- React Hooks: React.useState(), React.useEffect(), React.useRef()
+- Plotly:
コンポーネントを使用、カラースキーム: ["#B4E3E3", "#ABB3DB", "#D9BFDF", "#F8E1B0", "#8FD6D4", "#828DCA", "#C69ED0", "#F5D389", "#6AC8C6", "#5867B8", "#B37EC0", "#F1C461", "#44BAB8", "#2E41A6", "#8CC97E", "#A05EB0"]
+- >3カテゴリの場合: updatemenuを使用。マルチチャートの場合: グリッドレイアウト
+- Margins: {l: 80, r: 80, t: 100, b: 80}, 最小寸法: height 600, width 1000
+- メインコンポーネントコンテナのboxShadowスタイルをnoneに設定して外部ボーダーやシャドウを排除。
+
+## フォーマットルール
+
+- percentage: "25.5%"(小数点1桁)、正確に0の場合は"0%"、nullの場合は"N/A"
+- currency: "¥1,234.5"(小数点1桁、千の区切り)、正確に0の場合は"¥0"、nullの場合は"N/A"
+- integer: "1,234"(小数なし、千の区切り)、nullの場合は"N/A"
+- すべてのnullは"N/A"として表示(空白、ダッシュ、"null"ではなく)
+
+## コンポーネントレンダリングパターン
+
+KPI Cards (kpi_card_group):
+- 関連メトリクスを単一カードにグループ化(例: Opens + Open Rate)
+- プライマリメトリクス: 大きいフォント(20-24px)、ボールド
+- セカンダリメトリクス: 小さいフォント(14-16px)
+- 各カード: border, boxShadow, padding (20px), margin (10px)
+- レスポンシブグリッドレイアウト
+
+Tables (table):
+- 各メトリクスを別々のカラムに(単一セルに結合しない)
+- テキストカラム: 左揃え; 数値カラム: 右揃え
+- ボーダー、交互の行色、ボールドヘッダー
+
+Line Charts (line_chart):
+- mode: 'lines+markers'で
を使用
+- すべてのメトリクスに単一のy軸
+- x軸に時系列
+
+Dual-Axis Line Charts (dual_axis_line_chart):
+- 2つのy軸で
を使用
+- 左軸 (yaxis): axis: "left"のメトリクス
+- 右軸 (yaxis2): axis: "right"のメトリクス
+- レイアウト設定:
+ layout = {
+ yaxis: {title: 'Left Axis Title', side: 'left'},
+ yaxis2: {title: 'Right Axis Title', side: 'right', overlaying: 'y'}
+ }
+- トレースを割り当て: yaxis: 'y'(左)またはyaxis: 'y2'(右)
+- display_conditionによってすべての右軸メトリクスが非表示の場合、単一軸のみを使用
+
+## デザイン原則
+
+- 一貫したフォント、カラー、マージン、パディング
+- 十分なコントラスト、フォントサイズ: タイトル~18px、本文~14px
+- コンポーネントをborder, boxShadow, paddingを持つ
でラップ
+- コンポーネント間20-30pxマージン
+
+## 制約
+
+- 中間の自然言語出力なし(ツール呼び出しと簡単な進捗のみ)
+- クエリ中の翻訳/丸めなし(JSXでのみフォーマットを適用)
+- コンポーネントの存在、行/カラムの整合性を検証
+- エラーと成功データが共存する可能性あり(エラーを記録して続行)
diff --git a/engage-box/roi_reporting/agent/README.md b/engage-box/roi_reporting/agent/README.md
index 22a47fbc..9781339a 100644
--- a/engage-box/roi_reporting/agent/README.md
+++ b/engage-box/roi_reporting/agent/README.md
@@ -19,18 +19,12 @@ Two report types are supported:
- Campaign/journey performance tables with revenue attribution
- Graceful degradation when components fail
-## Files
+## Prerequisites
-| File | Description |
-|---|---|
-| `system_prompt.md` | Agent system prompt — paste into System Prompt field |
-| `knowledge_base_overall_summary.md` | Report spec for Overall Summary — register as Text KB named `OverallSummary_Spec` |
-| `knowledge_base_campaign_details.md` | Report spec for Campaign Details — register as Text KB named `CampaignDetails_Spec` |
-| `tools.yml` | All tool configurations — reference when configuring agent tools |
-| `forms/td_managed_overall_summary.yml` | Form interface for Overall Summary report |
-| `forms/td_managed_campaign_details.yml` | Form interface for Campaign Details report |
+### Required Tools
-## Prerequisites
+- `tdx` CLI (version 2026.4.55 or later)
+- Git or `gh` CLI (for cloning repository)
### Data Preparation
@@ -42,109 +36,87 @@ The agent requires the following tables in the `engage_roi_reporting` database:
| Table | Description | Key Columns |
|---|---|---|
-| `daily_summary` | Daily aggregated performance metrics | `summary_date`, `campaign_id`, `journey_id`, `total_sends`, `total_deliveries`, `total_opens`, `total_clicks`, `total_conversions`, `total_revenue_direct`, `total_revenue_contributed` |
-| `events_master` | Campaign/journey metadata | `campaign_id`, `journey_id`, `campaign_name`, `journey_name` |
-| `email_events` | Email event logs | `event_timestamp`, `event_type`, `message_id`, `campaign_id`, `journey_id`, `email_title` |
-| `revenue_table` | Revenue attribution data | `conversion_timestamp`, `conversion_id`, `campaign_id`, `total_revenue`, `attribution_type` |
-
-### Data Schema Requirements
-
-**daily_summary table:**
-- `summary_date` (varchar): Date in 'YYYY-MM-DD' format
-- `campaign_id`, `journey_id` (varchar): Identifiers
-- `total_sends`, `total_deliveries`, `total_opens`, `total_clicks`, `total_conversions` (integer): Event counts
-- `total_hard_bounces`, `total_soft_bounces`, `total_unsubscribes` (integer): Negative event counts
-- `total_revenue_direct`, `total_revenue_contributed` (double): Revenue amounts
-
-**email_events table:**
-- `event_timestamp` (varchar): Timestamp in '%Y-%m-%d %H:%i:%s.%f' format
-- `event_type` (varchar): 'Send', 'Delivery', 'Open', 'Click', 'Bounce', 'Complaint'
-- `message_id` (varchar): Unique message identifier
-- `email_title` (varchar): Email subject line
-
-**revenue table:**
-- `conversion_timestamp` (varchar): Timestamp in '%Y-%m-%d %H:%i:%s.%f' format
-- `attribution_type` (varchar): 'direct' or 'contributed'
-- `total_revenue` (double): Revenue amount
+| `daily_summary` | Daily aggregated performance metrics | `summary_date`, `campaign_id`, `campaign_name`, `journey_id`, `journey_name`, `total_sends`, `total_deliveries`, `total_opens`, `total_clicks`, `total_hard_bounces`, `total_soft_bounces`, `total_unsubscribes`, `total_conversions`, `total_revenue_direct`, `total_revenue_contributed` |
+| `events_master` | Campaign/journey metadata | `campaign_id`, `campaign_name`, `journey_id`, `journey_name` |
+| `email_events` | Email event logs | `event_timestamp` (ISO8601), `event_type`, `message_id`, `campaign_id`, `journey_id`, `email_title`, `bounce_type` |
+| `revenue_table` | Revenue attribution data | `conversion_timestamp` (TIMESTAMP), `conversion_id`, `campaign_id`, `total_revenue`, `attribution_type` |
## Setup Instructions
-**IMPORTANT**: The `tdx agent push` command currently creates ONLY the agent resource itself. Knowledge Bases, Tools, Outputs, and Form Interfaces must be configured separately via LLM API or the AI Agent Foundry UI.
+### Quick Start
-### Option A: LLM API Setup (Recommended for Complete Setup)
-
-For a fully automated setup including all components, use the LLM API approach:
-
-**Step 1: Create Project**
```bash
+# 1. Clone or download this repository
+gh repo clone treasure-data/treasure-boxes
+cd treasure-boxes/engage-box/roi_reporting/agent
+
+# 2. Create project and push agent
tdx llm project create "ROI Reporting Agent"
+tdx agent push . -y
```
-**Step 2-6: Use LLM API**
+**That's it!** All components are created:
+- ✅ Agent (Dashboard Viz)
+- ✅ Knowledge Bases (Database KB + 2 Text KBs)
+- ✅ Tools (4 tools)
+- ✅ Outputs (3 outputs)
+- ✅ Form Interfaces (2 forms)
-Use Python scripts to create the agent and all resources via LLM API. This ensures:
-- ✅ Agent core attributes (name, model, system prompt)
-- ✅ Knowledge Bases (Text KBs and Database KB)
-- ✅ Tools and Outputs configuration
-- ✅ Form Interfaces
+### Clone to New Project
-Reference implementation: See the `/llm-api-setup` skill documentation for complete workflow and Python script templates.
-
-**Key points:**
-- Use `POST /api/agents` to create the agent with core attributes
-- Use `POST /api/text_knowledge_bases` for Text KBs
-- Use `POST /api/knowledge_bases` for Database KB
-- Use `PATCH /api/agents/{id}` to configure tools and outputs
-- Use `POST /api/form_interfaces` for form interfaces
-- Verify all components after setup
-
-### Option B: CLI + Manual Configuration (Partial Automation)
+To create a copy in a different project:
```bash
-# 1. Clone or download this directory
-# 2. Edit tools.yml and replace with your actual database name
-# 3. Create project and agent:
-tdx llm project create "ROI Reporting Agent"
-tdx agent push . -f
-```
-
-**Note**: `tdx agent push` creates only the agent itself. You must manually configure:
-- Knowledge Bases (via UI or LLM API)
-- Tools and Outputs (via LLM API)
-- Form Interfaces (via LLM API)
-
-### Option C: Manual (AI Agent Foundry UI)
+# 1. Pull existing agent
+tdx agent pull "ROI Reporting Agent"
-#### 1. Create Project
-Create a new project named **`ROI Reporting Agent`** in AI Agent Foundry.
+# 2. Create new project
+tdx llm project create "My New Project"
-#### 2. Register Database as Knowledge Base
-- Type: **Database**
-- Select your ROI reporting database (containing daily_summary, email_events, revenue tables)
+# 3. Update project reference
+cd agents/ROI\ Reporting\ Agent
+echo '{"llm_project": "My New Project"}' > tdx.json
-#### 3. Register Report Specs as Text Knowledge Bases
+# 4. Push to new project
+tdx agent push . -y
+```
-**KB 1:**
-- Type: **Text**, Name: `OverallSummary_Spec`
-- Content: paste from `knowledge_base_overall_summary.md`
+### Verify Setup
-**KB 2:**
-- Type: **Text**, Name: `CampaignDetails_Spec`
-- Content: paste from `knowledge_base_campaign_details.md`
+```bash
+# Pull back to verify
+tdx agent pull "ROI Reporting Agent" -y
+
+# Check created resources
+cd agents/ROI\ Reporting\ Agent
+ls -la Dashboard\ Viz/agent.yml # Agent config with tools/outputs
+ls -la knowledge_bases/ # Knowledge bases
+ls -la form_interfaces/ # Form interfaces
+```
-#### 4. Create Agent
-- Name: **`Dashboard Viz`**
-- System Prompt: paste from `system_prompt.md`
-- Model: Claude 4.5 Sonnet
-- Max tool iterations: 4
-- Temperature: 0
+## File Structure
-#### 5. Configure Tools
-See **[tools.yml](./tools.yml)** for all tool names, descriptions, and settings.
+```
+agent/
+├── tdx.json # Project reference
+├── README.md # This file (English)
+├── README_JA.md # Japanese README
+├── Dashboard Viz/
+│ ├── prompt.md # System prompt (English)
+│ ├── prompt_ja.md # System prompt (Japanese, reference only)
+│ └── agent.yml # Agent configuration (includes tools/outputs)
+├── knowledge_bases/
+│ ├── OverallSummary_Spec.md # Overall Summary report specification (English)
+│ ├── OverallSummary_Spec_ja.md # Overall Summary report specification (Japanese, reference only)
+│ ├── CampaignDetails_Spec.md # Campaign Details report specification (English)
+│ ├── CampaignDetails_Spec_ja.md # Campaign Details report specification (Japanese, reference only)
+│ └── engage_roi_reporting.yml # Database KB definition
+└── form_interfaces/
+ ├── Overall Summary.yml # Form for Overall Summary report
+ └── Campaign Details.yml # Form for Campaign Details report
+```
-#### 6. Register Form Interfaces (when API available)
-- Overall Summary: `forms/td_managed_overall_summary.yml`
-- Campaign Details: `forms/td_managed_campaign_details.yml`
+**Note**: Japanese files (`*_ja.md`) are included for reference. The `tdx agent push` command uses English files only. To use Japanese versions, manually replace the files before pushing.
## Usage
@@ -232,6 +204,7 @@ Create dashboard with following conditions:
| "Date range exceeds 365 days" | Date range too long | Reduce date range to 365 days or less |
| No data returned | Filters don't match data | Verify campaign_id/journey_id exist; check date range; verify table names |
| Schema mismatch | Missing required columns | Verify tables contain required columns as documented in Prerequisites |
+| `tdx agent push` fails | Wrong tdx version | Update to tdx 2026.4.55 or later (`npm install -g @treasuredata/tdx`) |
## Revenue Metrics
@@ -244,8 +217,25 @@ Create dashboard with following conditions:
- **Total Revenue** is shown when BOTH direct and contributed revenue exist
- Otherwise, only Direct and/or Contributed Revenue are shown separately
+## Advanced: Manual Configuration
+
+If you prefer manual setup via UI or LLM API instead of `tdx agent push`, use the legacy setup flow described by your internal Treasure Data setup guidance or contact your Treasure Data account team.
+
## License
+
This agent configuration is provided as-is for use with Treasure Data's Engage service.
## Support
-This is a reference implementation. For assistance with setup and deployment, consult Treasure Data documentation or your account team.
+
+For assistance with setup and deployment, consult Treasure Data documentation or your account team.
+
+## Changelog
+
+### 2026-04-13
+- **BREAKING**: Updated to tdx CLI format
+- Simplified setup to 2 commands (`tdx llm project create` + `tdx agent push`)
+- Removed LLM API dependency for setup
+- Updated file structure to match `tdx agent pull/push` format
+- Form interfaces now use YAML object format instead of JSON strings
+- Agent tools and outputs now embedded in `Dashboard Viz/agent.yml`
+- Added Japanese files (`*_ja.md`) for reference
diff --git a/engage-box/roi_reporting/agent/README_JA.md b/engage-box/roi_reporting/agent/README_JA.md
index c5329a0e..e81e186c 100644
--- a/engage-box/roi_reporting/agent/README_JA.md
+++ b/engage-box/roi_reporting/agent/README_JA.md
@@ -1,139 +1,161 @@
-# ROIレポーティングエージェント
+# ROI レポート エージェント
## 概要
-このエージェントは、Treasure DataのEngageサービス向けのROI(投資利益率)レポートを生成します。キャンペーンパフォーマンスデータを自律的に分析し、Trinoに対してSQLクエリを実行し、可視化とインサイトを含むインタラクティブなダッシュボードを生成します。
+このエージェントは、Treasure DataのEngageサービス向けにROI(投資収益率)レポートを生成します。キャンペーンパフォーマンスデータを自律的に分析し、Trinoに対してSQLクエリを実行し、可視化とインサイトを含むインタラクティブなダッシュボードを生成します。
-2種類のレポートをサポートしています:
-- **全体サマリーレポート**: 指定期間におけるKPI、トレンド、キャンペーン/ジャーニーのパフォーマンス概要
-- **キャンペーン詳細レポート**: 特定のキャンペーンまたはジャーニーの詳細分析(収益アトリビューションを含む)
+2種類のレポートタイプをサポート:
+- **Overall Summaryレポート**: 指定期間における高レベルKPI、トレンド、キャンペーン/ジャーニーパフォーマンス
+- **Campaign Detailレポート**: 特定のキャンペーンまたはジャーニーの詳細分析(収益アトリビューションを含む)
## 機能
-- 自動SQLクエリ生成と実行
+- 自動SQLクエリ生成・実行
- Plotlyによるインタラクティブな可視化
- 多言語サポート(英語/日本語)
-- 多通貨サポート(米ドル/円)
-- エンゲージメントおよび収益メトリクスのKPIカード
+- 多通貨サポート(USD/JPY)
+- エンゲージメントと収益メトリクスのKPIカード
- デュアル軸チャートによる時系列トレンド分析
- 収益アトリビューション付きキャンペーン/ジャーニーパフォーマンステーブル
-- コンポーネント失敗時のグレースフルデグラデーション
+- コンポーネント障害時のグレースフルデグラデーション
-## ファイル
+## 前提条件
-| ファイル | 説明 |
-|---|---|
-| `system_prompt.md` | エージェントのシステムプロンプト — System Promptフィールドに貼り付け |
-| `knowledge_base_overall_summary.md` | 全体サマリーのレポートスペック — Text KB `OverallSummary_Spec` として登録 |
-| `knowledge_base_campaign_details.md` | キャンペーン詳細のレポートスペック — Text KB `CampaignDetails_Spec` として登録 |
-| `tools.yml` | 全ツール設定 — エージェントツール設定時の参考資料 |
-| `forms/td_managed_overall_summary.yml` | 全体サマリーレポートのフォームインターフェース |
-| `forms/td_managed_campaign_details.yml` | キャンペーン詳細レポートのフォームインターフェース |
+### 必要なツール
-## 前提条件
+- `tdx` CLI (バージョン 2026.4.55 以降)
+- Git または `gh` CLI(リポジトリクローン用)
+
+### データ準備
+
+このエージェントを使用する前に、必要なデータベーステーブルを準備する必要があります。treasure-boxesリポジトリの`roi_reporting`ワークフローが、これらのテーブルを作成するためのリファレンス実装を提供しています。
### 必要なデータベーステーブル
+エージェントは`engage_roi_reporting`データベースに以下のテーブルを必要とします:
+
| テーブル | 説明 | 主要カラム |
|---|---|---|
-| `daily_summary` | 日次集計パフォーマンスメトリクス | `summary_date`, `campaign_id`, `journey_id`, `total_sends`, `total_deliveries`, `total_opens`, `total_clicks`, `total_conversions`, `total_revenue_direct`, `total_revenue_contributed` |
-| `event_master` | キャンペーン/ジャーニーメタデータ | `campaign_id`, `journey_id`, `campaign_name`, `journey_name` |
-| `email_events` | メールイベントログ | `event_timestamp`, `event_type`, `message_id`, `campaign_id`, `journey_id`, `email_title` |
-| `revenue` | 収益アトリビューションデータ | `conversion_timestamp`, `conversion_id`, `campaign_id`, `total_revenue`, `attribution_type` |
-
-### データスキーマ要件
-
-**daily_summaryテーブル:**
-- `summary_date` (varchar): 'YYYY-MM-DD' 形式の日付
-- `campaign_id`, `journey_id` (varchar): 識別子
-- `total_sends`, `total_deliveries`, `total_opens`, `total_clicks`, `total_conversions` (integer): イベント数
-- `total_hard_bounces`, `total_soft_bounces`, `total_unsubscribes` (integer): ネガティブイベント数
-- `total_revenue_direct`, `total_revenue_contributed` (double): 収益額
-
-**email_eventsテーブル:**
-- `event_timestamp` (varchar): '%Y-%m-%d %H:%i:%s.%f' 形式のタイムスタンプ
-- `event_type` (varchar): 'Send', 'Delivery', 'Open', 'Click', 'Bounce', 'Complaint'
-- `message_id` (varchar): 一意のメッセージ識別子
-- `email_title` (varchar): メール件名
-
-**revenueテーブル:**
-- `conversion_timestamp` (varchar): '%Y-%m-%d %H:%i:%s.%f' 形式のタイムスタンプ
-- `attribution_type` (varchar): 'direct' または 'contributed'
-- `total_revenue` (double): 収益額
+| `daily_summary` | 日次集計パフォーマンスメトリクス | `summary_date`, `campaign_id`, `campaign_name`, `journey_id`, `journey_name`, `total_sends`, `total_deliveries`, `total_opens`, `total_clicks`, `total_hard_bounces`, `total_soft_bounces`, `total_unsubscribes`, `total_conversions`, `total_revenue_direct`, `total_revenue_contributed` |
+| `events_master` | キャンペーン/ジャーニーメタデータ | `campaign_id`, `campaign_name`, `journey_id`, `journey_name` |
+| `email_events` | メールイベントログ | `event_timestamp` (ISO8601), `event_type`, `message_id`, `campaign_id`, `journey_id`, `email_title`, `bounce_type` |
+| `revenue_table` | 収益アトリビューションデータ | `conversion_timestamp` (TIMESTAMP), `conversion_id`, `campaign_id`, `total_revenue`, `attribution_type` |
## セットアップ手順
-### オプションA: CLI(推奨)
+### クイックスタート
```bash
-# 1. このディレクトリをクローンまたはダウンロード
-# 2. tools.yml を編集し、 を実際のデータベース名に置き換え
-# 3. 実行:
+# 1. このリポジトリをクローンまたはダウンロード
+gh repo clone treasure-data/treasure-boxes
+cd treasure-boxes/engage-box/roi_reporting/agent
+
+# 2. プロジェクトを作成してエージェントをプッシュ
tdx llm project create "ROI Reporting Agent"
-tdx agent push . -f
+tdx agent push . -y
```
-### オプションB: 手動(AI Agent Foundry UI)
+**これだけです!** すべてのコンポーネントが作成されます:
+- ✅ エージェント (Dashboard Viz)
+- ✅ ナレッジベース (Database KB + 2つのText KB)
+- ✅ ツール (4つ)
+- ✅ 出力 (3つ)
+- ✅ フォームインターフェース (2つ)
+
+### 新しいプロジェクトへのクローン
-#### 1. プロジェクトの作成
-AI Agent Foundryで **`ROI Reporting Agent`** という名前の新しいプロジェクトを作成します。
+別のプロジェクトにコピーを作成する場合:
-#### 2. データベースをKnowledge Baseとして登録
-- タイプ: **Database**
-- ROIレポーティングデータベースを選択(daily_summary, email_events, revenueテーブルを含む)
+```bash
+# 1. 既存のエージェントをプル
+tdx agent pull "ROI Reporting Agent"
+
+# 2. 新しいプロジェクトを作成
+tdx llm project create "My New Project"
-#### 3. レポートスペックをText Knowledge Baseとして登録
+# 3. プロジェクト参照を更新
+cd agents/ROI\ Reporting\ Agent
+echo '{"llm_project": "My New Project"}' > tdx.json
+
+# 4. 新しいプロジェクトにプッシュ
+tdx agent push . -y
+```
-**KB 1:**
-- タイプ: **Text**, 名前: `OverallSummary_Spec`
-- コンテンツ: `knowledge_base_overall_summary.md` から貼り付け
+### セットアップの確認
-**KB 2:**
-- タイプ: **Text**, 名前: `CampaignDetails_Spec`
-- コンテンツ: `knowledge_base_campaign_details.md` から貼り付け
+```bash
+# プルバックして確認
+tdx agent pull "ROI Reporting Agent" -y
+
+# 作成されたリソースを確認
+cd agents/ROI\ Reporting\ Agent
+ls -la Dashboard\ Viz/agent.yml # ツール/出力を含むエージェント設定
+ls -la knowledge_bases/ # ナレッジベース
+ls -la form_interfaces/ # フォームインターフェース
+```
-#### 4. エージェントの作成
-- 名前: **`TD-Managed: Dashboard Viz`**
-- System Prompt: `system_prompt.md` から貼り付け
-- モデル: Claude 4.5 Sonnet
-- Max tool iterations: 4
-- Temperature: 0
+## ファイル構成
-#### 5. ツールの設定
-全ツールの名前、説明、設定については **[tools.yml](./tools.yml)** を参照してください。
+```
+agent/
+├── tdx.json # プロジェクト参照
+├── README.md # 英語版README
+├── README_JA.md # このファイル(日本語版README)
+├── Dashboard Viz/
+│ ├── prompt.md # システムプロンプト(英語)
+│ ├── prompt_ja.md # システムプロンプト(日本語)
+│ └── agent.yml # エージェント設定(ツール/出力を含む)
+├── knowledge_bases/
+│ ├── OverallSummary_Spec.md # Overall Summaryレポート仕様(英語)
+│ ├── OverallSummary_Spec_ja.md # Overall Summaryレポート仕様(日本語)
+│ ├── CampaignDetails_Spec.md # Campaign Detailsレポート仕様(英語)
+│ ├── CampaignDetails_Spec_ja.md # Campaign Detailsレポート仕様(日本語)
+│ └── engage_roi_reporting.yml # Database KB定義
+└── form_interfaces/
+ ├── Overall Summary.yml # Overall Summaryレポート用フォーム
+ └── Campaign Details.yml # Campaign Detailsレポート用フォーム
+```
-#### 6. フォームインターフェースの登録(API利用可能時)
-- 全体サマリー: `forms/td_managed_overall_summary.yml`
-- キャンペーン詳細: `forms/td_managed_campaign_details.yml`
+**注意**: 日本語版ファイル(`*_ja.md`)は参照用に含まれていますが、`tdx agent push`では英語版ファイルのみが使用されます。日本語版を使用したい場合は、手動でファイルを置き換えてください。
-## 使用方法
+## 使い方
-### 全体サマリーレポート
+### Overall Summaryレポート
```
-以下の条件でダッシュボードを作成してください:
+Create dashboard with following conditions:
- Report_id: 1.Overall Summary
- Start Date: 2025-01-01
- End Date: 2025-01-31
+- Timezone: UTC
+- Language: English
+- Currency: USD
+```
+
+```
+以下の条件でダッシュボードを作成してください:
+- Report_id: 1.Overall Summary
+- Start Date: 2024-12-01
+- End Date: 2024-12-31
- Timezone: Asia/Tokyo
- Language: Japanese
- Currency: JPY
```
**パラメータ:**
-- `Start_date`, `End_date` (必須): 'YYYY-MM-DD' 形式の日付範囲(最大365日)
+- `Start_date`, `End_date` (必須): 'YYYY-MM-DD'形式の日付範囲(最大365日)
- `Language` (必須): 'English' または 'Japanese'
- `Currency` (必須): 'USD' または 'JPY'
- `Timezone` (オプション): デフォルトはUTC
-### キャンペーン詳細レポート
+### Campaign Detailレポート
```
-以下の条件でダッシュボードを作成してください:
+Create dashboard with following conditions:
- Report_id: 2. Campaign Summary
- Campaign_id: ABC123
-- Language: Japanese
-- Currency: JPY
+- Language: English
+- Currency: USD
```
```
@@ -151,51 +173,69 @@ AI Agent Foundryで **`ROI Reporting Agent`** という名前の新しいプロ
## レポートコンポーネント
-### 全体サマリーには以下が含まれます:
+### Overall Summaryレポートに含まれるもの:
1. エグゼクティブサマリー(データドリブンなインサイト)
-2. KPIカード — 送信数、収益、コンバージョン、配信数、開封数、クリック数、バウンス数、配信停止数
+2. KPIカード — Sends, Revenue, Conversions, Deliveries, Opens, Clicks, Bounces, Unsubscribes
3. キャンペーンパフォーマンステーブル(収益上位5件)
4. ジャーニーパフォーマンステーブル(収益上位5件)
5. パフォーマンストレンドチャート(エンゲージメントと収益)
-6. データ集計方法に関する注意事項
+6. データ手法に関する免責事項
-### キャンペーン詳細には以下が含まれます:
+### Campaign Detailレポートに含まれるもの:
1. エグゼクティブサマリー(データドリブンなインサイト)
-2. エンゲージメントKPIカード — 送信数、配信数、開封数、クリック数、バウンス数、配信停止数
-3. 収益KPIカード(キャンペーンのみ)— 合計/直接/貢献収益
+2. エンゲージメントKPIカード — Sends, Deliveries, Opens, Clicks, Bounces, Unsubscribes
+3. 収益KPIカード(キャンペーンのみ)— Total/Direct/Contributed Revenue
4. エンゲージメント数トレンドチャート
-5. コンバージョン・収益トレンドチャート(キャンペーンのみ)
-6. メール件名別パフォーマンステーブル
+5. コンバージョン&収益トレンドチャート(キャンペーンのみ)
+6. メールタイトル別パフォーマンステーブル
-### 自動粒度
+### 自動粒度調整
| データ期間 | 粒度 |
|---|---|
-| 1–20日 | 日次 |
-| 21–89日 | 週次 |
+| 1~20日 | 日次 |
+| 21~89日 | 週次 |
| 90日以上 | 月次 |
## トラブルシューティング
-| エラー | 原因 | 対処法 |
+| エラー | 原因 | 解決方法 |
|---|---|---|
-| "Missing required parameters" | 必須フィルターが未提供 | 全体サマリーには start_date, end_date, language, currency を、キャンペーン詳細には campaign_id または journey_id を指定 |
+| "Missing required parameters" | 必須パラメータが不足 | Overall Summaryでは start_date, end_date, language, currency を指定。Campaign Detailsでは campaign_id または journey_id を指定 |
| "Date range exceeds 365 days" | 日付範囲が長すぎる | 日付範囲を365日以内に縮小 |
-| データが返されない | フィルターがデータと一致しない | campaign_id/journey_idが存在することを確認; 日付範囲を確認; テーブル名を確認 |
-| スキーマの不一致 | 必須カラムが存在しない | 前提条件に記載されている必須カラムがテーブルに含まれていることを確認 |
+| データが返されない | フィルターがデータと一致しない | campaign_id/journey_id の存在確認、日付範囲確認、テーブル名確認 |
+| スキーマの不一致 | 必須カラムが不足 | 前提条件セクションに記載された必須カラムがテーブルに含まれているか確認 |
+| `tdx agent push` が失敗 | tdxバージョンが古い | tdx 2026.4.55以降に更新 (`npm install -g @treasuredata/tdx`) |
## 収益メトリクス
### アトリビューションタイプ
-- **直接収益**: メールインタラクション後のアトリビューション期間内に発生したコンバージョンからの収益
-- **貢献収益**: メールインタラクションが貢献したが最終タッチではなかったコンバージョンからの収益
-- **合計収益**: 直接収益 + 貢献収益の合計
+- **Direct Revenue**: メールインタラクション後のアトリビューションウィンドウ内で発生したコンバージョンからの収益
+- **Contributed Revenue**: メールインタラクションが貢献したものの最終タッチではなかったコンバージョンからの収益
+- **Total Revenue**: Direct + Contributed Revenueの合計
### 表示ロジック
-- **合計収益**は、直接収益と貢献収益の両方が存在する場合に表示されます
-- それ以外の場合は、直接収益および/または貢献収益が個別に表示されます
+- **Total Revenue**は、DirectとContributed Revenueの両方が存在する場合に表示
+- それ以外の場合は、DirectまたはContributed Revenueのみが個別に表示
+
+## 上級: 手動設定
+
+UIやLLM API経由での手動セットアップを希望する場合は、英語版READMEの該当セクションを参照してください。
## ライセンス
-このエージェント設定は、Treasure DataのEngageサービスでの使用を目的として提供されています。
+
+このエージェント設定は、Treasure DataのEngageサービスで使用するためのものとして提供されています。
## サポート
-これは参考実装です。セットアップとデプロイメントに関する支援については、Treasure Dataのドキュメントまたはアカウントチームにご相談ください。
+
+セットアップとデプロイに関するサポートについては、Treasure Dataのドキュメントまたは担当アカウントチームにお問い合わせください。
+
+## 変更履歴
+
+### 2026-04-13
+- **BREAKING**: tdx CLI形式に更新
+- セットアップを2コマンドに簡素化 (`tdx llm project create` + `tdx agent push`)
+- セットアップ時のLLM API依存性を削除
+- ファイル構成を`tdx agent pull/push`形式に合わせて更新
+- フォームインターフェースがJSON文字列形式からYAMLオブジェクト形式に変更
+- エージェントのツールと出力を`Dashboard Viz/agent.yml`に統合
+- 日本語版ファイル(`*_ja.md`)を参照用に追加
diff --git a/engage-box/roi_reporting/agent/form_interfaces/Campaign Details.yml b/engage-box/roi_reporting/agent/form_interfaces/Campaign Details.yml
new file mode 100644
index 00000000..c91e7d2c
--- /dev/null
+++ b/engage-box/roi_reporting/agent/form_interfaces/Campaign Details.yml
@@ -0,0 +1,79 @@
+name: Campaign Details
+agent: '@ref(type: "agent", name: "Dashboard Viz")'
+prompt_template: |
+ Create dashboard with following conditions:
+ - Report_id: 2. Campaign Summary
+ - Campaign_id: {{Campaign_id}},
+ - Journey_id: {{Journey_id}},
+ - Language: {{Language}},
+ - Currency: {{Currency}}
+form_schema:
+ type: object
+ properties:
+ Campaign_id:
+ title: Campaign ID
+ type: string
+ placeholder: Enter either "Campaign ID" or "Journey ID"
+ Journey_id:
+ title: Journey ID
+ type: string
+ placeholder: Enter either "Campaign ID" or "Journey ID"
+ Language:
+ title: Language
+ type: string
+ enum:
+ - English
+ - Japanese
+ enumNames:
+ - English
+ - Japanese
+ Currency:
+ title: Currency
+ type: string
+ enum:
+ - USD
+ - JPY
+ enumNames:
+ - USD
+ - JPY
+ required:
+ - Language
+ - Currency
+ui_schema:
+ Campaign_id:
+ ui:en:title: Campaign ID
+ ui:en:placeholder: Enter either "Campaign ID" or "Journey ID"
+ ui:ja:placeholder: キャンペーンIDまたは、ジャーニーIDを入力してください
+ ui:ja:title: キャンペーンID
+ Journey_id:
+ ui:en:title: Journey ID
+ ui:en:placeholder: Enter either "Campaign ID" or "Journey ID"
+ ui:ja:title: ジャーニーID
+ ui:ja:placeholder: キャンペーンIDまたは、ジャーニーIDを入力してください
+ Language:
+ ui:widget: radio
+ ui:en:title: Language
+ ui:ja:title: 言語
+ ui:enumNames:
+ - English
+ - Japanese
+ ui:en:enumNames:
+ - English
+ - Japanese
+ ui:ja:enumNames:
+ - 英語
+ - 日本語
+ Currency:
+ ui:widget: radio
+ ui:en:title: Currency
+ ui:ja:title: 通貨
+ ui:enumNames:
+ - USD
+ - JPY
+ ui:en:enumNames:
+ - USD
+ - JPY
+ ui:ja:enumNames:
+ - 米ドル
+ - 円
+use_text_resource: false
diff --git a/engage-box/roi_reporting/agent/form_interfaces/Overall Summary.yml b/engage-box/roi_reporting/agent/form_interfaces/Overall Summary.yml
new file mode 100644
index 00000000..00615048
--- /dev/null
+++ b/engage-box/roi_reporting/agent/form_interfaces/Overall Summary.yml
@@ -0,0 +1,77 @@
+name: Overall Summary
+agent: '@ref(type: "agent", name: "Dashboard Viz")'
+prompt_template: |
+ Create dashboard with following conditions:
+ - Report_id: 1.Overall Summary
+ - Start Date: {{Start_date}},
+ - End Date: {{End_date}},
+ - Timezone: {{Timezone}},
+ - Language: {{Language}},
+ - Currency: {{Currency}}
+form_schema:
+ type: object
+ properties:
+ Start_date:
+ title: Start Date
+ type: string
+ format: date
+ End_date:
+ title: End Date
+ type: string
+ format: date
+ Language:
+ title: Language
+ type: string
+ enum:
+ - English
+ - Japanese
+ enumNames:
+ - English
+ - Japanese
+ Currency:
+ title: Currency
+ type: string
+ enum:
+ - USD
+ - JPY
+ readOnly: false
+ enumNames:
+ - USD
+ - JPY
+ required:
+ - Start_date
+ - Language
+ - End_date
+ - Currency
+ui_schema:
+ Start_date:
+ ui:en:title: Start Date
+ End_date:
+ ui:en:title: End Date
+ Language:
+ ui:widget: radio
+ ui:en:title: Language
+ ui:enumNames:
+ - English
+ - Japanese
+ ui:en:enumNames:
+ - English
+ - Japanese
+ ui:ja:enumNames:
+ - 英語
+ - 日本語
+ ui:ja:title: 言語
+ Currency:
+ ui:widget: radio
+ ui:en:title: Currency
+ ui:ja:title: 通貨
+ ui:enumNames:
+ - USD
+ - JPY
+ ui:en:enumNames:
+ - USD
+ - JPY
+ ui:ja:enumNames:
+ - 米ドル
+ - 円
+use_text_resource: false
diff --git a/engage-box/roi_reporting/agent/knowledge_bases/CampaignDetails_Spec.md b/engage-box/roi_reporting/agent/knowledge_bases/CampaignDetails_Spec.md
new file mode 100644
index 00000000..95a03a1a
--- /dev/null
+++ b/engage-box/roi_reporting/agent/knowledge_bases/CampaignDetails_Spec.md
@@ -0,0 +1,171 @@
+---
+name: CampaignDetails_Spec
+---
+
+# Report Specification: Campaign Details
+
+## 1. Report Overview
+- purpose: "To provide comprehensive performance analysis for a single campaign or journey, including KPIs, trends, and email title breakdown."
+- source_tables:
+ - "email_events"
+ - "revenue_table"
+
+## 2. Filters
+- filter:
+ - id: "campaign_id"
+ type: "string"
+ required: true
+ exclusive_with: "journey_id"
+- filter:
+ - id: "journey_id"
+ type: "string"
+ required: true
+ exclusive_with: "campaign_id"
+- filter_notes: "A campaign_id or journey_id is required. When one of these IDs is provided, a date range is NOT required. The report should run on all available data for that ID."
+- notes: "Timestamp parsing: event_timestamp is ISO8601 format (use from_iso8601_timestamp(event_timestamp)). conversion_timestamp can be cast directly (use CAST(conversion_timestamp AS TIMESTAMP))."
+
+## 3. Important Notes on Metrics
+
+### Unique Count vs Total Count
+- **Unique counts** (primary metrics): Use `COUNT(DISTINCT message_id)` to count each message only once, regardless of how many times it was opened/clicked.
+- **Total counts** (supplementary information): Use `COUNT(*)` to count all events, including multiple opens/clicks of the same message.
+- **Rate calculations**: All rates MUST use unique counts to avoid inflated metrics (industry standard).
+
+### Identifier Standard
+- Use `message_id` (Amazon SES unique message ID) for DISTINCT aggregations.
+- This ensures consistency with Email Delivery Reports and industry best practices.
+
+### Time Granularity (for trend components)
+
+**CRITICAL: Timestamp Parsing**
+- event_timestamp is stored in ISO8601 format. Use `from_iso8601_timestamp(event_timestamp)` to parse.
+- conversion_timestamp can be cast directly. Use `CAST(conversion_timestamp AS TIMESTAMP)`.
+- Example for date truncation: `DATE_TRUNC('day', from_iso8601_timestamp(event_timestamp))` for email events, `DATE_TRUNC('day', CAST(conversion_timestamp AS TIMESTAMP))` for revenue_table.
+
+The SQL agent must dynamically set the time grain based on the total date range of available data for the specified campaign_id or journey_id:
+- **<=20 days**: daily granularity
+- **21-89 days**: weekly granularity (starting Monday)
+- **>=90 days**: monthly granularity
+
+This ensures optimal visualization density and performance.
+
+## 4. Component Definitions
+
+### Component 1: Engagement KPIs
+- component:
+ - component_id: "kpi_summary_engagement"
+ - component_type: "kpi_card_group"
+ - title: "Engagement KPIs for {name} ({id})"
+ - source_tables: ["email_events"]
+ - metrics:
+ - { metric_id: "sends", display_name: "Sends", calculation: "COUNT(*) FROM email_events WHERE event_type = 'Send'", format: "integer" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "COUNT(*) FROM email_events WHERE event_type = 'Delivery'", format: "integer" }
+ - { metric_id: "unique_opens", display_name: "Unique Opens", calculation: "COUNT(DISTINCT message_id) FROM email_events WHERE event_type = 'Open'", format: "integer", visual_priority: "primary" }
+ - { metric_id: "total_opens", display_name: "Total Opens", calculation: "COUNT(*) FROM email_events WHERE event_type = 'Open'", format: "integer", visual_priority: "tertiary", display_note: "Supplementary information" }
+ - { metric_id: "unique_open_rate", display_name: "Unique Open Rate", calculation: "(COUNT(DISTINCT message_id) WHERE event_type = 'Open') / (COUNT(*) WHERE event_type = 'Delivery')", format: "percentage", visual_priority: "primary" }
+ - { metric_id: "unique_clicks", display_name: "Unique Clicks", calculation: "COUNT(DISTINCT message_id) FROM email_events WHERE event_type = 'Click'", format: "integer", visual_priority: "primary" }
+ - { metric_id: "total_clicks", display_name: "Total Clicks", calculation: "COUNT(*) FROM email_events WHERE event_type = 'Click'", format: "integer", visual_priority: "tertiary", display_note: "Supplementary information" }
+ - { metric_id: "unique_click_rate", display_name: "Unique Click Rate (CTR)", calculation: "(COUNT(DISTINCT message_id) WHERE event_type = 'Click') / (COUNT(*) WHERE event_type = 'Delivery')", format: "percentage", visual_priority: "primary" }
+ - { metric_id: "bounces", display_name: "Bounces", calculation: "COUNT(*) FROM email_events WHERE event_type = 'Bounce'", format: "integer" }
+ - { metric_id: "bounce_rate", display_name: "Bounce Rate", calculation: "(COUNT(*) WHERE event_type = 'Bounce') / (COUNT(*) WHERE event_type = 'Send')", format: "percentage" }
+ - { metric_id: "unsubscribes", display_name: "Unsubscribes", calculation: "COUNT(*) FROM email_events WHERE event_type = 'Complaint'", format: "integer" }
+ - notes: |
+ - Visual priority guidance:
+ * "primary": Display prominently (e.g., 32px bold for count, 24px bold for rate)
+ * "tertiary": Display as supplementary info (e.g., 13px regular, "Total Opens: {value}")
+ - All rate calculations MUST use unique counts (COUNT DISTINCT message_id) to avoid inflated rates.
+
+### Component 2: Revenue KPIs
+- component:
+ - component_id: "kpi_summary_revenue_table"
+ - component_type: "kpi_card_group"
+ - title: "Revenue KPIs for {name} ({id})"
+ - source_tables: ["revenue_table", "email_events"]
+ - display_condition: "Only show this component if filtering by campaign_id."
+ - query_hints:
+ - "To get revenue_table for a journey, you MUST JOIN the 'revenue_table' and 'email_events' tables on 'campaign_id'."
+ - metrics:
+ - { metric_id: "total_revenue_table", display_name: "Total Revenue", calculation: "SUM(total_revenue_table) from revenue_table table where attribution_type is 'direct' or 'contributed'", format: "currency", display_condition: "Only show if both direct and contributed revenue_table exist." }
+ - { metric_id: "direct_revenue_table", display_name: "Direct Revenue", calculation: "SUM(total_revenue_table) from revenue_table table where attribution_type = 'direct'", format: "currency" }
+ - { metric_id: "contributed_revenue_table", display_name: "Contributed Revenue", calculation: "SUM(total_revenue_table) from revenue_table table where attribution_type = 'contributed'", format: "currency" }
+
+### Component 3: Performance by Email Title
+- component:
+ - component_id: "email_title_performance"
+ - component_type: "table"
+ - title: "Performance by Email Title"
+ - source_tables: ["email_events"]
+ - dimensions:
+ - { id: "email_title", display_name: "Email Title" }
+ - metrics:
+ - { metric_id: "sends", display_name: "Sends", calculation: "COUNT(*) WHERE event_type = 'Send'", format: "integer" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "COUNT(*) WHERE event_type = 'Delivery'", format: "integer" }
+ - { metric_id: "open_rate", display_name: "Open Rate", calculation: "(COUNT(*) WHERE event_type = 'Open') / (COUNT(*) WHERE event_type = 'Delivery')", format: "percentage" }
+ - { metric_id: "ctr", display_name: "CTR (Click-Through Rate)", calculation: "(COUNT(*) WHERE event_type = 'Click') / (COUNT(*) WHERE event_type = 'Delivery')", format: "percentage" }
+ - { metric_id: "bounces", display_name: "Bounces", calculation: "COUNT(*) WHERE event_type = 'Bounce'", format: "integer" }
+ - { metric_id: "bounce_rate", display_name: "Bounce Rate", calculation: "(COUNT(*) WHERE event_type = 'Bounce') / (COUNT(*) WHERE event_type = 'Send')", format: "percentage" }
+ - { metric_id: "unsubscribes", display_name: "Unsubscribes", calculation: "COUNT(*) WHERE event_type = 'Complaint'", format: "integer" }
+ - sort_order: "sends DESC, email_title ASC"
+ - notes: |
+ - Query Construction:
+ 1. GROUP BY email_title directly from email_events table
+ 2. Filter by campaign_id or journey_id
+ 3. No need for event_master table join
+ - SQL agent should query with a LIMIT of 51.
+ - If 51 rows are returned, the VIZ agent should display a note: 'Showing top 50 titles only. There may be additional titles not displayed.'
+ - Display the first 50 rows only.
+
+### Component 4: Engagement Count Trend
+- component:
+ - component_id: "engagement_count_trend"
+ - component_type: "line_chart"
+ - title: "Engagement Trend (Counts)"
+ - source_tables: ["email_events"]
+ - y_axis_shared: true
+ - visualization_hint: "mode: 'lines+markers'"
+ - metrics:
+ - { metric_id: "sends", display_name: "Sends", calculation: "COUNT(CASE WHEN event_type='Send' THEN 1 END)", format: "integer" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "COUNT(CASE WHEN event_type='Delivery' THEN 1 END)", format: "integer" }
+ - { metric_id: "unique_opens", display_name: "Unique Opens", calculation: "COUNT(DISTINCT CASE WHEN event_type='Open' THEN message_id END)", format: "integer", visual_priority: "primary" }
+ - { metric_id: "unique_clicks", display_name: "Unique Clicks", calculation: "COUNT(DISTINCT CASE WHEN event_type='Click' THEN message_id END)", format: "integer", visual_priority: "primary" }
+ - { metric_id: "bounce_count", display_name: "Bounces", calculation: "COUNT(CASE WHEN event_type='Bounce' THEN 1 END)", format: "integer" }
+ - { metric_id: "complaint_count", display_name: "Complaints", calculation: "COUNT(CASE WHEN event_type='Complaint' THEN 1 END)", format: "integer" }
+ - notes: |
+ - This component is based on the 'event_timestamp' column.
+ - Zero-padding is NOT required.
+ - Time granularity is determined by the total date range of the campaign/journey data (see Section 3).
+ - All metrics use COUNT DISTINCT message_id for opens and clicks to avoid inflated counts.
+
+### Component 5: Conversions and Revenue Trend
+- component:
+ - component_id: "conversions_trend"
+ - component_type: "line_chart"
+ - title: "Conversions & Revenue Trend"
+ - source_tables: ["revenue_table", "email_events"]
+ - y_axis_shared: false
+ - visualization_hint: "mode: 'lines+markers', dual y-axis (left: count, right: currency)"
+ - display_condition: "Only show if filtering by campaign_id."
+ - metrics:
+ - { metric_id: "conversions", display_name: "Conversions", calculation: "COUNT(DISTINCT conversion_id) FROM revenue_table", format: "integer", y_axis: "left" }
+ - { metric_id: "direct_revenue_table", display_name: "Direct Revenue", calculation: "SUM(total_revenue_table) FROM revenue_table WHERE attribution_type = 'direct'", format: "currency", y_axis: "right" }
+ - { metric_id: "contributed_revenue_table", display_name: "Contributed Revenue", calculation: "SUM(total_revenue_table) FROM revenue_table WHERE attribution_type = 'contributed'", format: "currency", y_axis: "right" }
+ - { metric_id: "total_revenue_table", display_name: "Total Revenue", calculation: "SUM(total_revenue_table) FROM revenue_table WHERE attribution_type IN ('direct', 'contributed')", format: "currency", y_axis: "right" }
+ - notes: |
+ - This component is based on the 'conversion_timestamp' column.
+ - Zero-padding is NOT required.
+ - Time granularity is determined by the total date range of the campaign/journey data (see Section 3).
+ - Use dual y-axis: left for count metrics (conversions), right for currency metrics (revenue_table).
+ - This component helps identify day-of-week patterns and campaign timing effects.
+
+## 5. Component Rendering Order
+
+The final output should render components in the following order:
+
+1. Title
+2. Summary (data-driven insights)
+3. kpi_summary_engagement
+4. kpi_summary_revenue_table (if campaign_id and data exists)
+5. engagement_count_trend
+6. conversions_trend (if campaign_id and data exists)
+7. email_title_performance
+
diff --git a/engage-box/roi_reporting/agent/knowledge_bases/CampaignDetails_Spec_ja.md b/engage-box/roi_reporting/agent/knowledge_bases/CampaignDetails_Spec_ja.md
new file mode 100644
index 00000000..3eef44b2
--- /dev/null
+++ b/engage-box/roi_reporting/agent/knowledge_bases/CampaignDetails_Spec_ja.md
@@ -0,0 +1,168 @@
+# Report Specification: Campaign Details(日本語版)
+
+> **注意**: このドキュメントは参考資料です。正式なレポート仕様は英語版([CampaignDetails_Spec.md](./CampaignDetails_Spec.md))を参照してください。
+
+## 1. レポート概要
+- purpose: "単一のキャンペーンまたはジャーニーの包括的なパフォーマンス分析を提供し、KPI、トレンド、メール件名別の内訳を含む。"
+- source_tables:
+ - "email_events"
+ - "revenue_table"
+
+## 2. フィルタ
+- filter:
+ - id: "campaign_id"
+ type: "string"
+ required: true
+ exclusive_with: "journey_id"
+- filter:
+ - id: "journey_id"
+ type: "string"
+ required: true
+ exclusive_with: "campaign_id"
+- filter_notes: "campaign_idまたはjourney_idが必要です。これらのIDの一つが提供された場合、日付範囲は不要です。レポートはそのIDの利用可能なすべてのデータで実行されるべきです。"
+- notes: "タイムスタンプカラム(event_timestamp, conversion_timestamp)はvarchar文字列です。date_parse(column, '%Y-%m-%d %H:%i:%s.%f')を使用して解析する必要があります。"
+
+## 3. メトリクスに関する重要な注意事項
+
+### ユニークカウント vs 総カウント
+- **ユニークカウント**(主要メトリクス): `COUNT(DISTINCT message_id)`を使用して、開封/クリックされた回数に関わらず各メッセージを1回のみカウントします。
+- **総カウント**(補足情報): `COUNT(*)`を使用して、同じメッセージの複数回の開封/クリックを含むすべてのイベントをカウントします。
+- **率計算**: すべての率はユニークカウントを使用する必要があります(業界標準)。
+
+### 識別子標準
+- DISTINCT集計には`message_id`(Amazon SESユニークメッセージID)を使用します。
+- これによりEmail Delivery Reportsおよび業界ベストプラクティスとの一貫性が保証されます。
+
+### 時間粒度(トレンドコンポーネント用)
+
+**重要: タイムスタンプ解析**
+- event_timestampとconversion_timestampはVARCHAR文字列であり、TIMESTAMPタイプではありません。
+- これらのカラムを解析するには`date_parse(column, '%Y-%m-%d %H:%i:%s.%f')`を使用する必要があります。
+- `CAST(column AS DATE)`または`CAST(column AS TIMESTAMP)`は使用しないでください - これらは失敗します。
+
+SQLエージェントは、指定されたcampaign_idまたはjourney_idの利用可能なデータの総日付範囲に基づいて時間粒度を動的に設定する必要があります:
+- **<=20日**: 日次粒度
+- **21-89日**: 週次粒度(月曜日開始)
+- **>=90日**: 月次粒度
+
+これにより最適な可視化密度とパフォーマンスが保証されます。
+
+## 4. コンポーネント定義
+
+### コンポーネント1: エンゲージメントKPI
+- component:
+ - component_id: "kpi_summary_engagement"
+ - component_type: "kpi_card_group"
+ - title: "Engagement KPIs for {name} ({id})"
+ - source_tables: ["email_events"]
+ - metrics:
+ - { metric_id: "sends", display_name: "Sends", calculation: "COUNT(*) FROM email_events WHERE event_type = 'Send'", format: "integer" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "COUNT(*) FROM email_events WHERE event_type = 'Delivery'", format: "integer" }
+ - { metric_id: "unique_opens", display_name: "Unique Opens", calculation: "COUNT(DISTINCT message_id) FROM email_events WHERE event_type = 'Open'", format: "integer", visual_priority: "primary" }
+ - { metric_id: "total_opens", display_name: "Total Opens", calculation: "COUNT(*) FROM email_events WHERE event_type = 'Open'", format: "integer", visual_priority: "tertiary", display_note: "補足情報" }
+ - { metric_id: "unique_open_rate", display_name: "Unique Open Rate", calculation: "(COUNT(DISTINCT message_id) WHERE event_type = 'Open') / (COUNT(*) WHERE event_type = 'Delivery')", format: "percentage", visual_priority: "primary" }
+ - { metric_id: "unique_clicks", display_name: "Unique Clicks", calculation: "COUNT(DISTINCT message_id) FROM email_events WHERE event_type = 'Click'", format: "integer", visual_priority: "primary" }
+ - { metric_id: "total_clicks", display_name: "Total Clicks", calculation: "COUNT(*) FROM email_events WHERE event_type = 'Click'", format: "integer", visual_priority: "tertiary", display_note: "補足情報" }
+ - { metric_id: "unique_click_rate", display_name: "Unique Click Rate (CTR)", calculation: "(COUNT(DISTINCT message_id) WHERE event_type = 'Click') / (COUNT(*) WHERE event_type = 'Delivery')", format: "percentage", visual_priority: "primary" }
+ - { metric_id: "bounces", display_name: "Bounces", calculation: "COUNT(*) FROM email_events WHERE event_type = 'Bounce'", format: "integer" }
+ - { metric_id: "bounce_rate", display_name: "Bounce Rate", calculation: "(COUNT(*) WHERE event_type = 'Bounce') / (COUNT(*) WHERE event_type = 'Send')", format: "percentage" }
+ - { metric_id: "unsubscribes", display_name: "Unsubscribes", calculation: "COUNT(*) FROM email_events WHERE event_type = 'Complaint'", format: "integer" }
+ - notes: |
+ - ビジュアル優先度ガイダンス:
+ * "primary": 目立つように表示(例: カウントは32pxボールド、率は24pxボールド)
+ * "tertiary": 補足情報として表示(例: 13px通常、"Total Opens: {value}")
+ - すべての率計算はユニークカウント(COUNT DISTINCT message_id)を使用して、膨張したメトリクスを避ける必要があります。
+
+### コンポーネント2: 収益KPI
+- component:
+ - component_id: "kpi_summary_revenue_table"
+ - component_type: "kpi_card_group"
+ - title: "Revenue KPIs for {name} ({id})"
+ - source_tables: ["revenue_table", "email_events"]
+ - display_condition: "campaign_idでフィルタリングしている場合のみこのコンポーネントを表示。"
+ - query_hints:
+ - "ジャーニーの収益を取得するには、'revenue_table'と'email_events'テーブルを'campaign_id'でJOINする必要があります。"
+ - metrics:
+ - { metric_id: "total_revenue_table", display_name: "Total Revenue", calculation: "SUM(total_revenue_table) from revenue_table table where attribution_type is 'direct' or 'contributed'", format: "currency", display_condition: "直接収益と貢献収益の両方が存在する場合のみ表示。" }
+ - { metric_id: "direct_revenue_table", display_name: "Direct Revenue", calculation: "SUM(total_revenue_table) from revenue_table table where attribution_type = 'direct'", format: "currency" }
+ - { metric_id: "contributed_revenue_table", display_name: "Contributed Revenue", calculation: "SUM(total_revenue_table) from revenue_table table where attribution_type = 'contributed'", format: "currency" }
+
+### コンポーネント3: メール件名別パフォーマンス
+- component:
+ - component_id: "email_title_performance"
+ - component_type: "table"
+ - title: "Performance by Email Title"
+ - source_tables: ["email_events"]
+ - dimensions:
+ - { id: "email_title", display_name: "Email Title" }
+ - metrics:
+ - { metric_id: "sends", display_name: "Sends", calculation: "COUNT(*) WHERE event_type = 'Send'", format: "integer" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "COUNT(*) WHERE event_type = 'Delivery'", format: "integer" }
+ - { metric_id: "open_rate", display_name: "Open Rate", calculation: "(COUNT(*) WHERE event_type = 'Open') / (COUNT(*) WHERE event_type = 'Delivery')", format: "percentage" }
+ - { metric_id: "ctr", display_name: "CTR (Click-Through Rate)", calculation: "(COUNT(*) WHERE event_type = 'Click') / (COUNT(*) WHERE event_type = 'Delivery')", format: "percentage" }
+ - { metric_id: "bounces", display_name: "Bounces", calculation: "COUNT(*) WHERE event_type = 'Bounce'", format: "integer" }
+ - { metric_id: "bounce_rate", display_name: "Bounce Rate", calculation: "(COUNT(*) WHERE event_type = 'Bounce') / (COUNT(*) WHERE event_type = 'Send')", format: "percentage" }
+ - { metric_id: "unsubscribes", display_name: "Unsubscribes", calculation: "COUNT(*) WHERE event_type = 'Complaint'", format: "integer" }
+ - sort_order: "sends DESC, email_title ASC"
+ - notes: |
+ - クエリ構築:
+ 1. email_eventsテーブルから直接email_titleでGROUP BY
+ 2. campaign_idまたはjourney_idでフィルタ
+ 3. event_masterテーブルとのJOINは不要
+ - SQLエージェントはLIMIT 51でクエリする必要があります。
+ - 51行が返された場合、VIZエージェントは注記を表示する必要があります: '上位50件のみ表示しています。追加の件名が表示されていない可能性があります。'
+ - 最初の50行のみを表示します。
+
+### コンポーネント4: エンゲージメント数トレンド
+- component:
+ - component_id: "engagement_count_trend"
+ - component_type: "line_chart"
+ - title: "Engagement Trend (Counts)"
+ - source_tables: ["email_events"]
+ - y_axis_shared: true
+ - visualization_hint: "mode: 'lines+markers'"
+ - metrics:
+ - { metric_id: "sends", display_name: "Sends", calculation: "COUNT(CASE WHEN event_type='Send' THEN 1 END)", format: "integer" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "COUNT(CASE WHEN event_type='Delivery' THEN 1 END)", format: "integer" }
+ - { metric_id: "unique_opens", display_name: "Unique Opens", calculation: "COUNT(DISTINCT CASE WHEN event_type='Open' THEN message_id END)", format: "integer", visual_priority: "primary" }
+ - { metric_id: "unique_clicks", display_name: "Unique Clicks", calculation: "COUNT(DISTINCT CASE WHEN event_type='Click' THEN message_id END)", format: "integer", visual_priority: "primary" }
+ - { metric_id: "bounce_count", display_name: "Bounces", calculation: "COUNT(CASE WHEN event_type='Bounce' THEN 1 END)", format: "integer" }
+ - { metric_id: "complaint_count", display_name: "Complaints", calculation: "COUNT(CASE WHEN event_type='Complaint' THEN 1 END)", format: "integer" }
+ - notes: |
+ - このコンポーネントは'event_timestamp'カラムに基づいています。
+ - ゼロパディングは不要です。
+ - 時間粒度はキャンペーン/ジャーニーデータの総日付範囲によって決定されます(セクション3参照)。
+ - すべてのメトリクスは開封とクリックにCOUNT DISTINCT message_idを使用して、膨張したカウントを避けます。
+
+### コンポーネント5: コンバージョン・収益トレンド
+- component:
+ - component_id: "conversions_trend"
+ - component_type: "line_chart"
+ - title: "Conversions & Revenue Trend"
+ - source_tables: ["revenue_table", "email_events"]
+ - y_axis_shared: false
+ - visualization_hint: "mode: 'lines+markers', dual y-axis (left: count, right: currency)"
+ - display_condition: "campaign_idでフィルタリングしている場合のみ表示。"
+ - metrics:
+ - { metric_id: "conversions", display_name: "Conversions", calculation: "COUNT(DISTINCT conversion_id) FROM revenue_table", format: "integer", y_axis: "left" }
+ - { metric_id: "direct_revenue_table", display_name: "Direct Revenue", calculation: "SUM(total_revenue_table) FROM revenue_table WHERE attribution_type = 'direct'", format: "currency", y_axis: "right" }
+ - { metric_id: "contributed_revenue_table", display_name: "Contributed Revenue", calculation: "SUM(total_revenue_table) FROM revenue_table WHERE attribution_type = 'contributed'", format: "currency", y_axis: "right" }
+ - { metric_id: "total_revenue_table", display_name: "Total Revenue", calculation: "SUM(total_revenue_table) FROM revenue_table WHERE attribution_type IN ('direct', 'contributed')", format: "currency", y_axis: "right" }
+ - notes: |
+ - このコンポーネントは'conversion_timestamp'カラムに基づいています。
+ - ゼロパディングは不要です。
+ - 時間粒度はキャンペーン/ジャーニーデータの総日付範囲によって決定されます(セクション3参照)。
+ - デュアルy軸を使用: カウントメトリクス(コンバージョン)は左、通貨メトリクス(収益)は右。
+ - このコンポーネントは曜日パターンやキャンペーンタイミング効果の特定に役立ちます。
+
+## 5. コンポーネントレンダリング順序
+
+最終出力は以下の順序でコンポーネントをレンダリングする必要があります:
+
+1. Title
+2. Summary(データドリブンなインサイト)
+3. kpi_summary_engagement
+4. kpi_summary_revenue_table(campaign_idでデータが存在する場合)
+5. engagement_count_trend
+6. conversions_trend(campaign_idでデータが存在する場合)
+7. email_title_performance
diff --git a/engage-box/roi_reporting/agent/knowledge_bases/OverallSummary_Spec.md b/engage-box/roi_reporting/agent/knowledge_bases/OverallSummary_Spec.md
new file mode 100644
index 00000000..74fea6ca
--- /dev/null
+++ b/engage-box/roi_reporting/agent/knowledge_bases/OverallSummary_Spec.md
@@ -0,0 +1,145 @@
+---
+name: OverallSummary_Spec
+---
+
+# Report Specification: OverallSummary
+
+## 1. Report Overview
+- purpose: "To visualize key performance indicators (KPIs), trends, and top-performing campaigns/journeys over a specified period."
+- source_tables:
+ - "daily_summary"
+ - "events_master" # Used for name lookups in rankings
+
+## 2. Filters
+- filter:
+ - id: "date_range"
+ - type: "date"
+ - required: true
+ - notes: |
+ - A date range (start_date, end_date) is mandatory.
+ - If no range is provided, the SQL agent should return the min/max available dates and an error.
+ - The SQL agent will validate the range. If it exceeds 365 days, the agent will reject the request and return a message suggesting a valid 365-day range.
+
+## 3. Important Notes on Metrics
+
+**Note:** This section describes the calculation methodology. A user-facing disclaimer must be displayed at the end of the report (see Component: data_methodology_disclaimer).
+
+## 4. Component Definitions
+- component:
+ - component_id: "kpi_summary"
+ - component_type: "kpi_card_group"
+ - title: "Overall Performance Summary"
+ - metrics:
+ - { metric_id: "sends", display_name: "Sends", calculation: "SUM(total_sends)", format: "integer" }
+ - { metric_id: "total_revenue", display_name: "Total Revenue", calculation: "SUM(total_revenue_direct + total_revenue_contributed)", format: "currency", display_condition: "Show only if SUM(total_revenue_direct) > 0 AND SUM(total_revenue_contributed) > 0" }
+ - { metric_id: "direct_revenue", display_name: "Direct Revenue", calculation: "SUM(total_revenue_direct)", format: "currency" }
+ - { metric_id: "contributed_revenue", display_name: "Contributed Revenue", calculation: "SUM(total_revenue_contributed)", format: "currency" }
+ - { metric_id: "conversions", display_name: "Conversions", calculation: "SUM(total_conversions)", format: "integer" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "SUM(total_deliveries)", format: "integer" }
+ - { metric_id: "opens", display_name: "Opens", calculation: "SUM(total_opens)", format: "integer" }
+ - { metric_id: "open_rate", display_name: "Open Rate", calculation: "SUM(total_opens) / SUM(total_deliveries)", format: "percentage" }
+ - { metric_id: "clicks", display_name: "Clicks", calculation: "SUM(total_clicks)", format: "integer" }
+ - { metric_id: "click_rate", display_name: "Click Rate", calculation: "SUM(total_clicks) / SUM(total_deliveries)", format: "percentage" }
+ - { metric_id: "bounces", display_name: "Bounces", calculation: "SUM(total_hard_bounces + total_soft_bounces)", format: "integer" }
+ - { metric_id: "bounce_rate", display_name: "Bounce Rate", calculation: "SUM(total_hard_bounces + total_soft_bounces) / SUM(total_sends)", format: "percentage" }
+ - { metric_id: "unsubscribes", display_name: "Unsubscribes", calculation: "SUM(total_unsubscribes)", format: "integer" }
+
+- component:
+ - component_id: "campaign_performance_ranking"
+ - component_type: "table"
+ - title: "Top 5 Campaigns"
+ - source_tables: ["daily_summary", "events_master"]
+ - dimensions:
+ - { id: "campaign_name", display_name: "Campaign Name" }
+ - { id: "campaign_id", display_name: "Campaign ID" }
+ - metrics:
+ - { metric_id: "revenue", display_name: "Revenue", format: "currency" }
+ - { metric_id: "conversions", display_name: "Conversions", calculation: "SUM(total_conversions)", format: "integer" }
+ - { metric_id: "sends", display_name: "Sends", calculation: "SUM(total_sends)", format: "integer" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "SUM(total_deliveries)", format: "integer" }
+ - { metric_id: "open_rate", display_name: "Open Rate", calculation: "SUM(total_opens) / SUM(total_deliveries)", format: "percentage" }
+ - { metric_id: "click_rate", display_name: "Click Rate", calculation: "SUM(total_clicks) / SUM(total_deliveries)", format: "percentage" }
+ - { metric_id: "bounce_rate", display_name: "Bounce Rate", calculation: "SUM(total_hard_bounces + total_soft_bounces) / SUM(total_sends)", format: "percentage" }
+ - orderby_clause_template: "ORDER BY SUM(total_revenue_direct + total_revenue_contributed) DESC, SUM(total_conversions) DESC, SUM(total_clicks) DESC, campaign_id DESC"
+ - notes: |
+ - This component ranks campaigns. The final sort order MUST use the provided 'orderby_clause_template'.
+ - Date filtering MUST use the 'summary_date' column (varchar) with string-based comparison (e.g., WHERE summary_date BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD').
+ - The 'Revenue' column to be displayed is dynamic. The agent must construct a CASE statement to show Total, Direct, or Contributed Revenue based on the rules previously discussed.
+
+- component:
+ - component_id: "journey_performance_ranking"
+ - component_type: "table"
+ - title: "Top 5 Journeys"
+ - source_tables: ["daily_summary", "events_master"]
+ - dimensions:
+ - { id: "journey_name", display_name: "Journey Name" }
+ - { id: "journey_id", display_name: "Journey ID" }
+ - metrics:
+ - { metric_id: "revenue", display_name: "Revenue", format: "currency" }
+ - { metric_id: "conversions", display_name: "Conversions", calculation: "SUM(total_conversions)", format: "integer" }
+ - { metric_id: "sends", display_name: "Sends", calculation: "SUM(total_sends)", format: "integer" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "SUM(total_deliveries)", format: "integer" }
+ - { metric_id: "open_rate", display_name: "Open Rate", calculation: "SUM(total_opens) / SUM(total_deliveries)", format: "percentage" }
+ - { metric_id: "click_rate", display_name: "Click Rate", calculation: "SUM(total_clicks) / SUM(total_deliveries)", format: "percentage" }
+ - { metric_id: "bounce_rate", display_name: "Bounce Rate", calculation: "SUM(total_hard_bounces + total_soft_bounces) / SUM(total_sends)", format: "percentage" }
+ - orderby_clause_template: "ORDER BY SUM(total_revenue_direct + total_revenue_contributed) DESC, SUM(total_conversions) DESC, SUM(total_clicks) DESC, journey_id DESC"
+ - notes: |
+ - This component ranks journeys. The final sort order MUST use the provided 'orderby_clause_template'.
+ - Date filtering MUST use the 'summary_date' column (varchar) with string-based comparison (e.g., WHERE summary_date BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD').
+ - The 'Revenue' column to be displayed is dynamic. The agent must construct a CASE statement to show Total, Direct, or Contributed Revenue based on the rules previously discussed.
+
+- component:
+ - component_id: "performance_trend"
+ - component_type: "trend_chart"
+ - title: "Performance Trend"
+ - tabs:
+ - tab_name: "Engagement"
+ metrics:
+ - { metric_id: "sends", display_name: "Sends", calculation: "SUM(total_sends)" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "SUM(total_deliveries)" }
+ - { metric_id: "clicks", display_name: "Clicks", calculation: "SUM(total_clicks)" }
+ - tab_name: "Revenue"
+ metrics:
+ - { metric_id: "revenue", display_name: "Revenue", calculation: "SUM(total_revenue_direct + total_revenue_contributed)" }
+ display_condition: "Show tab only if total revenue for the period > 0"
+ - notes: |
+ - This component uses the 'summary_date' (varchar) column. For date functions like date_trunc, it MUST be converted using CAST(summary_date AS DATE).
+ - The SQL agent must dynamically set the time grain based on the length of the date range:
+ * <=20 days: daily
+ * 21-89 days: weekly (starting Monday)
+ * >=90 days: monthly
+ - The agent must perform zero-padding to ensure a continuous time series.
+
+- component:
+ - component_id: "data_methodology_disclaimer"
+ - component_type: "text_note"
+ - title: "Data Aggregation Methodology"
+ - content: |
+ Note: The open rate and click rate in this report are calculated based on total event counts from the daily_summary table.
+ When the same email is opened or clicked multiple times, each event is counted separately.
+ This may result in rates appearing higher than unique open/click rates.
+
+ For more detailed analysis with unique count-based metrics, please refer to the Campaign Details report.
+ - display_condition: "ALWAYS - This component MUST be displayed at the end of every report."
+ - notes: |
+ - CRITICAL: This disclaimer MUST always be rendered at the very end of the report, after all other components.
+ - This is not optional - it must appear in every OverallSummary report regardless of data or filters.
+ - Rendering style: Plain text only, no borders, no background color, no box shadow, no bold text.
+ - Use regular font weight (not bold), normal font size (14-16px).
+ - Background color: transparent or same as page background (white/light gray).
+ - No visual highlighting or emphasis - this should blend into the page as simple footer text.
+
+## 5. Component Rendering Order
+
+The final output MUST render components in the following order:
+
+1. Report Title
+2. Summary (data-driven insights)
+3. kpi_summary
+4. campaign_performance_ranking
+5. journey_performance_ranking
+6. performance_trend
+7. **data_methodology_disclaimer** ← MUST BE LAST
+
+**IMPORTANT:** The data_methodology_disclaimer component MUST always appear at the very end of the report, regardless of other component rendering or data availability.
+
diff --git a/engage-box/roi_reporting/agent/knowledge_bases/OverallSummary_Spec_ja.md b/engage-box/roi_reporting/agent/knowledge_bases/OverallSummary_Spec_ja.md
new file mode 100644
index 00000000..4ba39717
--- /dev/null
+++ b/engage-box/roi_reporting/agent/knowledge_bases/OverallSummary_Spec_ja.md
@@ -0,0 +1,142 @@
+# Report Specification: OverallSummary(日本語版)
+
+> **注意**: このドキュメントは参考資料です。正式なレポート仕様は英語版([OverallSummary_Spec.md](./OverallSummary_Spec.md))を参照してください。
+
+## 1. レポート概要
+- purpose: "指定された期間における主要パフォーマンス指標(KPI)、トレンド、上位キャンペーン/ジャーニーを可視化する。"
+- source_tables:
+ - "daily_summary"
+ - "event_master" # ランキングでの名前検索に使用
+
+## 2. フィルタ
+- filter:
+ - id: "date_range"
+ - type: "date"
+ - required: true
+ - notes: |
+ - 日付範囲(start_date, end_date)は必須です。
+ - 範囲が指定されない場合、SQLエージェントは利用可能な最小/最大日付とエラーを返してください。
+ - SQLエージェントは範囲を検証します。365日を超える場合、リクエストを拒否し、有効な365日範囲を提案するメッセージを返します。
+
+## 3. メトリクスに関する重要な注意事項
+
+**注意:** このセクションは計算方法論を説明します。レポートの最後にユーザー向け免責事項を表示する必要があります(Component: data_methodology_disclaimerを参照)。
+
+## 4. コンポーネント定義
+- component:
+ - component_id: "kpi_summary"
+ - component_type: "kpi_card_group"
+ - title: "Overall Performance Summary"
+ - metrics:
+ - { metric_id: "sends", display_name: "Sends", calculation: "SUM(total_sends)", format: "integer" }
+ - { metric_id: "total_revenue", display_name: "Total Revenue", calculation: "SUM(total_revenue_direct + total_revenue_contributed)", format: "currency", display_condition: "SUM(total_revenue_direct) > 0 AND SUM(total_revenue_contributed) > 0の場合のみ表示" }
+ - { metric_id: "direct_revenue", display_name: "Direct Revenue", calculation: "SUM(total_revenue_direct)", format: "currency" }
+ - { metric_id: "contributed_revenue", display_name: "Contributed Revenue", calculation: "SUM(total_revenue_contributed)", format: "currency" }
+ - { metric_id: "conversions", display_name: "Conversions", calculation: "SUM(total_conversions)", format: "integer" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "SUM(total_deliveries)", format: "integer" }
+ - { metric_id: "opens", display_name: "Opens", calculation: "SUM(total_opens)", format: "integer" }
+ - { metric_id: "open_rate", display_name: "Open Rate", calculation: "SUM(total_opens) / SUM(total_deliveries)", format: "percentage" }
+ - { metric_id: "clicks", display_name: "Clicks", calculation: "SUM(total_clicks)", format: "integer" }
+ - { metric_id: "click_rate", display_name: "Click Rate", calculation: "SUM(total_clicks) / SUM(total_deliveries)", format: "percentage" }
+ - { metric_id: "bounces", display_name: "Bounces", calculation: "SUM(total_hard_bounces + total_soft_bounces)", format: "integer" }
+ - { metric_id: "bounce_rate", display_name: "Bounce Rate", calculation: "SUM(total_hard_bounces + total_soft_bounces) / SUM(total_sends)", format: "percentage" }
+ - { metric_id: "unsubscribes", display_name: "Unsubscribes", calculation: "SUM(total_unsubscribes)", format: "integer" }
+
+- component:
+ - component_id: "campaign_performance_ranking"
+ - component_type: "table"
+ - title: "Top 5 Campaigns"
+ - source_tables: ["daily_summary", "event_master"]
+ - dimensions:
+ - { id: "campaign_name", display_name: "Campaign Name" }
+ - { id: "campaign_id", display_name: "Campaign ID" }
+ - metrics:
+ - { metric_id: "revenue", display_name: "Revenue", format: "currency" }
+ - { metric_id: "conversions", display_name: "Conversions", calculation: "SUM(total_conversions)", format: "integer" }
+ - { metric_id: "sends", display_name: "Sends", calculation: "SUM(total_sends)", format: "integer" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "SUM(total_deliveries)", format: "integer" }
+ - { metric_id: "open_rate", display_name: "Open Rate", calculation: "SUM(total_opens) / SUM(total_deliveries)", format: "percentage" }
+ - { metric_id: "click_rate", display_name: "Click Rate", calculation: "SUM(total_clicks) / SUM(total_deliveries)", format: "percentage" }
+ - { metric_id: "bounce_rate", display_name: "Bounce Rate", calculation: "SUM(total_hard_bounces + total_soft_bounces) / SUM(total_sends)", format: "percentage" }
+ - orderby_clause_template: "ORDER BY SUM(total_revenue_direct + total_revenue_contributed) DESC, SUM(total_conversions) DESC, SUM(total_clicks) DESC, campaign_id DESC"
+ - notes: |
+ - このコンポーネントはキャンペーンをランク付けします。最終ソート順は提供された'orderby_clause_template'を使用する必要があります。
+ - 日付フィルタリングは'summary_date'カラム(varchar)を使用し、文字列ベースの比較を行う必要があります(例: WHERE summary_date BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD')。
+ - 表示する'Revenue'カラムは動的です。エージェントは以前に議論したルールに基づいてTotal、Direct、またはContributed Revenueを表示するCASE文を構築する必要があります。
+
+- component:
+ - component_id: "journey_performance_ranking"
+ - component_type: "table"
+ - title: "Top 5 Journeys"
+ - source_tables: ["daily_summary", "event_master"]
+ - dimensions:
+ - { id: "journey_name", display_name: "Journey Name" }
+ - { id: "journey_id", display_name: "Journey ID" }
+ - metrics:
+ - { metric_id: "revenue", display_name: "Revenue", format: "currency" }
+ - { metric_id: "conversions", display_name: "Conversions", calculation: "SUM(total_conversions)", format: "integer" }
+ - { metric_id: "sends", display_name: "Sends", calculation: "SUM(total_sends)", format: "integer" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "SUM(total_deliveries)", format: "integer" }
+ - { metric_id: "open_rate", display_name: "Open Rate", calculation: "SUM(total_opens) / SUM(total_deliveries)", format: "percentage" }
+ - { metric_id: "click_rate", display_name: "Click Rate", calculation: "SUM(total_clicks) / SUM(total_deliveries)", format: "percentage" }
+ - { metric_id: "bounce_rate", display_name: "Bounce Rate", calculation: "SUM(total_hard_bounces + total_soft_bounces) / SUM(total_sends)", format: "percentage" }
+ - orderby_clause_template: "ORDER BY SUM(total_revenue_direct + total_revenue_contributed) DESC, SUM(total_conversions) DESC, SUM(total_clicks) DESC, journey_id DESC"
+ - notes: |
+ - このコンポーネントはジャーニーをランク付けします。最終ソート順は提供された'orderby_clause_template'を使用する必要があります。
+ - 日付フィルタリングは'summary_date'カラム(varchar)を使用し、文字列ベースの比較を行う必要があります。
+ - 表示する'Revenue'カラムは動的です。
+
+- component:
+ - component_id: "performance_trend"
+ - component_type: "trend_chart"
+ - title: "Performance Trend"
+ - tabs:
+ - tab_name: "Engagement"
+ metrics:
+ - { metric_id: "sends", display_name: "Sends", calculation: "SUM(total_sends)" }
+ - { metric_id: "deliveries", display_name: "Deliveries", calculation: "SUM(total_deliveries)" }
+ - { metric_id: "clicks", display_name: "Clicks", calculation: "SUM(total_clicks)" }
+ - tab_name: "Revenue"
+ metrics:
+ - { metric_id: "revenue", display_name: "Revenue", calculation: "SUM(total_revenue_direct + total_revenue_contributed)" }
+ display_condition: "期間の総収益 > 0の場合のみタブを表示"
+ - notes: |
+ - このコンポーネントは'summary_date'(varchar)カラムを使用します。date_truncなどの日付関数にはCAST(summary_date AS DATE)を使用する必要があります。
+ - SQLエージェントは日付範囲の長さに基づいて時間粒度を動的に設定する必要があります:
+ * <=20日: 日次
+ * 21-89日: 週次(月曜日開始)
+ * >=90日: 月次
+ - エージェントは連続した時系列を確保するためにゼロパディングを実行する必要があります。
+
+- component:
+ - component_id: "data_methodology_disclaimer"
+ - component_type: "text_note"
+ - title: "Data Aggregation Methodology"
+ - content: |
+ 注意: このレポートの開封率とクリック率は、daily_summaryテーブルの総イベントカウントに基づいて計算されています。
+ 同じメールが複数回開封またはクリックされた場合、各イベントは個別にカウントされます。
+ これにより、ユニーク開封/クリック率よりも高い率が表示される場合があります。
+
+ ユニークカウントベースのメトリクスによる詳細な分析については、Campaign Detailsレポートを参照してください。
+ - display_condition: "ALWAYS - このコンポーネントはすべてのレポートの最後に必ず表示する必要があります。"
+ - notes: |
+ - 重要: この免責事項は常にレポートの最後、他のすべてのコンポーネントの後にレンダリングされる必要があります。
+ - これはオプションではありません - データやフィルタに関わらず、すべてのOverallSummaryレポートに表示される必要があります。
+ - レンダリングスタイル: プレーンテキストのみ、ボーダーなし、背景色なし、ボックスシャドウなし、ボールドテキストなし。
+ - 通常のフォントウェイト(ボールドでない)、通常のフォントサイズ(14-16px)を使用。
+ - 背景色: transparentまたはページ背景と同じ(白/ライトグレー)。
+ - 視覚的なハイライトや強調なし - シンプルなフッターテキストとしてページに溶け込むべきです。
+
+## 5. コンポーネントレンダリング順序
+
+最終出力は以下の順序でコンポーネントをレンダリングする必要があります:
+
+1. Report Title
+2. Summary(データドリブンなインサイト)
+3. kpi_summary
+4. campaign_performance_ranking
+5. journey_performance_ranking
+6. performance_trend
+7. **data_methodology_disclaimer** ← 必ず最後
+
+**重要:** data_methodology_disclaimerコンポーネントは、他のコンポーネントのレンダリングやデータの可用性に関わらず、常にレポートの最後に表示される必要があります。
diff --git a/engage-box/roi_reporting/agent/knowledge_bases/engage_roi_reporting.yml b/engage-box/roi_reporting/agent/knowledge_bases/engage_roi_reporting.yml
new file mode 100644
index 00000000..56353f20
--- /dev/null
+++ b/engage-box/roi_reporting/agent/knowledge_bases/engage_roi_reporting.yml
@@ -0,0 +1,4 @@
+name: engage_roi_reporting
+type: database
+database: engage_roi_reporting
+tables: []