diff --git a/engage-box/email-delivery-reporter/Email Delivery Dashboard/agent.yml b/engage-box/email-delivery-reporter/Email Delivery Dashboard/agent.yml new file mode 100644 index 00000000..9ad76d84 --- /dev/null +++ b/engage-box/email-delivery-reporter/Email Delivery Dashboard/agent.yml @@ -0,0 +1,45 @@ +name: Email Delivery Dashboard +model: claude-4.5-sonnet +temperature: 0 +max_tool_iterations: 4 +prompt_file: prompt.md + +outputs: + - name: renderReactApp + function_name: renderReactApp + function_description: "Generates React components with Tailwind CSS. ENVIRONMENT CONSTRAINTS: 1. Charts: react-plotly.js is the ONLY installed library. recharts is NOT installed. 2. Icons: lucide-react is NOT installed; use inline tags only. 3. UI: Static view only. NO
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) + +## Available Tools + +### Data Access +- **List_columns**: Discover table schemas. +- **Query_data_directly**: Execute SQL against PlazmaDB. Max 100 rows. Use GROUP BY. Never SELECT *. Use OFFSET/LIMIT if [TRUNCATED]. +- **read_overall_summary_spec**: Read the Overall Summary report specification. +- **read_campaign_summary_spec**: Read the Campaign/Journey Detail report specification. + +### Output +- **new_plot**: Intermediate visualizations. +- **renderReactApp**: Final React dashboard. Single file, export default. +- **text_in_form**: Error messages only. diff --git a/engage-box/email-delivery-reporter/README.md b/engage-box/email-delivery-reporter/README.md index 549d2afb..b7cb055d 100644 --- a/engage-box/email-delivery-reporter/README.md +++ b/engage-box/email-delivery-reporter/README.md @@ -1,4 +1,6 @@ -# Email Delivery Reporter Agent +# Email Delivery Reporter + +An AI agent that automatically analyzes Treasure Data Engage email delivery logs from PlazmaDB and generates interactive React + Plotly dashboards. Supports English and Japanese, USD and JPY. ## Overview @@ -11,31 +13,46 @@ Two report types are supported: ## Features - Automated SQL query generation and execution -- Interactive visualizations +- Interactive visualizations with Plotly - Multilingual support (English/Japanese) +- Multi-currency support (USD/JPY) - KPI cards with engagement and quality metrics - Time-series trend analysis with dual-axis charts - Campaigns/journeys/subjects performance tables - Graceful degradation when components fail -## Files +## Repository Structure -| 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 `DeliveryOverallSummary_Spec` | -| `knowledge_base_campaign_summary.md` | Report spec for Campaign Detail — register as Text KB named `DeliveryCampaignSummary_Spec` | -| `tools.yml` | All tool configurations — reference when configuring agent tools | +``` +email-delivery-reporter/ +├── tdx.json # Project manifest (no changes needed) +├── README.md # This file +├── Email Delivery Dashboard/ +│ ├── agent.yml # ← EDIT: replace DOMAIN (2 places) +│ └── prompt.md # System prompt (no changes needed) +├── knowledge_bases/ +│ ├── delivery_email_DOMAIN.yml # ← EDIT: replace DOMAIN (filename + 2 places inside) +│ ├── OverallSummary_Spec.md # Report spec (no changes needed) +│ └── CampaignSummary_Spec.md # Report spec (no changes needed) +├── form_interfaces/ +│ ├── Overall Summary.yml # Form UI (no changes needed) +│ └── Campaign Details.yml # Form UI (no changes needed) +└── docs/japanese/ # Japanese documentation (reference only, not deployed) +``` ## Prerequisites +- `tdx` CLI (v2026.4.55+), authenticated (`tdx auth setup`) +- Treasure Data account with Engage enabled +- PlazmaDB database `delivery_email_` must exist + ### Required PlazmaDB Database The database name follows this pattern: ``` delivery_email_ ``` -Where `` is your email domain with dots replaced by underscores. +Where `` is your email domain with dots and hyphens replaced by underscores. **Examples:** - `example.com` → `delivery_email_example_com` @@ -49,84 +66,144 @@ Where `` is your email domain with dots replaced by underscores. | `error_events` | Pre-send failures | `timestamp`, `error_type`, `error_message`, `custom_event_id` | | `subscription_events` | Opt-out events | `profile_identifier_value`, `campaign_id`, `action`, `received_time`, `time` | -event_type values: `Send`, `Delivery`, `Open`, `Click`, `Bounce`, `Complaint`, `DeliveryDelay` +Event types: `Send`, `Delivery`, `Open`, `Click`, `Bounce`, `Complaint`, `DeliveryDelay` -## Setup Instructions +## Quick Start -### Option A: CLI (Recommended) +### Step 1: Clone ```bash -# 1. Clone or download this directory -# 2. Edit knowledge_bases/.yml with your actual database name -# 3. Run: -tdx llm project create "Email Delivery Reporter" -tdx agent push . -f +git clone --depth 1 --filter=blob:none --sparse \ + https://github.com/treasure-data/treasure-boxes.git +cd treasure-boxes +git sparse-checkout set engage-box/email-delivery-reporter +cd engage-box/email-delivery-reporter ``` -### Option B: Manual (AI Agent Foundry UI) +### Step 2: Determine your domain slug -#### 1. Create Project -Create a new project named **`Email Delivery Reporter`** in AI Agent Foundry. +Derive the database name from your Engage sending email domain: +- Replace dots and hyphens with underscores +- Example: `example.com` → `example_com`, `my-company.co.jp` → `my_company_co_jp` -#### 2. Register PlazmaDB as Knowledge Base -- Type: **Database** -- Select: `delivery_email_` +Verify the database exists: -#### 3. Register Report Specs as Text Knowledge Bases +```bash +tdx databases | grep delivery_email +``` -**KB 1:** -- Type: **Text**, Name: `DeliveryOverallSummary_Spec` -- Content: paste from `knowledge_base_overall_summary.md` +### Step 3: Replace DOMAIN (5 replacements across 2 files + 1 rename) -**KB 2:** -- Type: **Text**, Name: `DeliveryCampaignSummary_Spec` -- Content: paste from `knowledge_base_campaign_summary.md` +The following example uses `example_com`. Replace with your actual domain slug. -#### 4. Create Agent -- Name: **`Email Delivery Reporter`** -- System Prompt: paste from `system_prompt.md` +| # | File | Location | Before → After | +|---|------|----------|----------------| +| 1 | `knowledge_bases/delivery_email_DOMAIN.yml` | **Filename** | → `delivery_email_example_com.yml` | +| 2 | Same file | `name:` (line 1) | `delivery_email_DOMAIN` → `delivery_email_example_com` | +| 3 | Same file | `database:` (line 3) | `delivery_email_DOMAIN` → `delivery_email_example_com` | +| 4 | `Email Delivery Dashboard/agent.yml` | 1st `@ref` | `"delivery_email_DOMAIN"` → `"delivery_email_example_com"` | +| 5 | Same file | 2nd `@ref` | `"delivery_email_DOMAIN"` → `"delivery_email_example_com"` | -#### 5. Configure Tools -See **[tools.yml](./tools.yml)** for all tool names, descriptions, and settings. +Commands (replace `example_com` with your slug): + +```bash +# Rename the knowledge base file +mv knowledge_bases/delivery_email_DOMAIN.yml \ + knowledge_bases/delivery_email_example_com.yml + +# Update file contents (both files at once) +# macOS / BSD: +sed -i '' 's/delivery_email_DOMAIN/delivery_email_example_com/g' \ + knowledge_bases/delivery_email_example_com.yml \ + "Email Delivery Dashboard/agent.yml" + +# Linux (GNU sed): +sed -i 's/delivery_email_DOMAIN/delivery_email_example_com/g' \ + knowledge_bases/delivery_email_example_com.yml \ + "Email Delivery Dashboard/agent.yml" +``` + +### Step 4: Deploy + +```bash +tdx llm project create "Email Delivery Reporter" +tdx agent push . -f +``` + +Expected output: +``` +Push summary for 'Email Delivery Reporter': + + 6 new + Agents: 1 created + Knowledge Bases: 1 created + Text Knowledge Bases: 2 created + Form Interfaces: 2 created + +✔ Pushed 6 resources to 'Email Delivery Reporter' +``` + +### Step 5: Verify + +```bash +tdx agent list +``` + +Or open: AI Agent Foundry > Email Delivery Reporter > Email Delivery Dashboard ## Usage ### Overall Summary Report +**Example (English):** ``` -Generate an overall email delivery report for January 2025 in English. -date_range: { start_date: '2025-01-01', end_date: '2025-01-31' } -language: 'en' +Generate an overall email delivery report with following conditions: +- Report_id: 1. Overall Summary +- Start Date: 2025-01-01 +- End Date: 2025-01-31 +- Language: English +- Currency: USD ``` +**Example (Japanese):** ``` -2024年12月のメール配信レポートを日本語で作成してください。 -date_range: { start_date: '2024-12-01', end_date: '2024-12-31' } -language: 'ja' +以下の条件でメール配信の全体サマリーレポートを作成してください: +- Report_id: 1. Overall Summary +- Start Date: 2025-01-01 +- End Date: 2025-01-31 +- Language: Japanese +- Currency: JPY ``` -**Parameters:** -- `date_range` (optional): defaults to full data range -- `language` (optional): `'en'` or `'ja'`, defaults to `'en'` -- `campaign_id`, `journey_id`, `subject` (optional): additional filters +**Parameters:** +- `Start_date`, `End_date` (required, max 365 days apart) +- `Language` (`English` or `Japanese`) +- `Currency` (`USD` or `JPY`) ### Campaign Detail Report +**Example (English):** ``` -Create a detailed report for campaign XYZ789. -campaign_id: 'XYZ789' -language: 'en' +Generate a detailed email delivery report with following conditions: +- Report_id: 2. Campaign Summary +- Campaign_id: ABC123 +- Language: English +- Currency: USD ``` +**Example (Japanese):** ``` -ジャーニーID "welcome-series" の詳細レポートを日本語で作成してください。 -journey_id: 'welcome-series' -language: 'ja' +以下の条件でキャンペーン詳細レポートを作成してください: +- Report_id: 2. Campaign Summary +- Campaign_id: ABC123 +- Language: Japanese +- Currency: JPY ``` **Parameters:** -- At least one of `campaign_id`, `journey_id`, or `subject` is required -- `date_range`, `language` optional +- `Campaign_id` or `Journey_id` (at least one required) +- `Language` (`English` or `Japanese`) +- `Currency` (`USD` or `JPY`) +- Optional: `date_range`, `subject` filter ## Report Components @@ -139,7 +216,7 @@ language: 'ja' ### Campaign Detail includes: 1. Executive Summary -2. KPI Cards (filtered) +2. KPI Cards (filtered by campaign/journey) 3. Performance Trend Chart 4. Email Subject Performance Table (top 100 by sends) @@ -154,12 +231,15 @@ language: 'ja' | Error | Cause | Fix | |---|---|---| -| "No email delivery database found" | DB not registered or wrong name | Verify `delivery_email_` is registered as KB | -| "Missing required parameters" | Campaign Detail needs at least one identifier | Provide campaign_id, journey_id, or subject | -| No data returned | Filters don't match data | Verify IDs exist; check date range; broaden filters | +| Knowledge base not found | `name` in `.yml` doesn't match `@ref` in `agent.yml` | Check all 5 DOMAIN replacements above | +| Database not found | `database:` field doesn't match existing TD database | Run `tdx databases \| grep delivery_email` to verify | +| `tdx agent push` structure error | `knowledge_bases/` not at project root | Ensure `knowledge_bases/` is at same level as `tdx.json`, not nested in `agent/` | +| LLM_PROJECT_NOT_FOUND | Project not created | Run `tdx llm project create "Email Delivery Reporter"` first | +| No data returned | Filter doesn't match data or date range too narrow | Verify campaign_id/journey_id exists; widen date range; check filters | +| "Missing required parameters" | Campaign Detail needs at least one identifier | Provide campaign_id or journey_id | ## License This agent configuration is provided as-is for use with Treasure Data's Engage service. ## Support -This is a reference implementation. No support is provided. +For questions or issues, please contact your Treasure Data support team. diff --git a/engage-box/email-delivery-reporter/docs/japanese/CampaignSummary_Spec_ja.md b/engage-box/email-delivery-reporter/docs/japanese/CampaignSummary_Spec_ja.md new file mode 100644 index 00000000..565273b5 --- /dev/null +++ b/engage-box/email-delivery-reporter/docs/japanese/CampaignSummary_Spec_ja.md @@ -0,0 +1,166 @@ +# Report Specification: Campaign Details(日本語版) + +> **注意**: このドキュメントは参考資料です。正式なレポート仕様は英語版([CampaignSummary_Spec.md](../../knowledge_bases/CampaignSummary_Spec.md))を参照してください。 + +## 1. レポート概要 +- purpose: "単一のキャンペーンまたはジャーニーの包括的なパフォーマンス分析を提供する(KPI、トレンド、メールタイトル別内訳を含む)。" +- source_tables: + - "email_events" + - "revenue" + +## 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 は TIMESTAMP 型ではなく VARCHAR 文字列です。 +- これらのカラムを解析するには `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. コンポーネント定義 + +### 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: "補足情報" } + - { 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: | + - Visual priority ガイダンス: + * "primary": 目立つように表示(例: カウントは 32px ボールド、率は 24px ボールド) + * "tertiary": 補足情報として表示(例: 13px レギュラー、"Total Opens: {value}") + - すべての率計算は膨張した率を避けるためにユニークカウント(COUNT DISTINCT message_id)を使用する必要があります。 + +### Component 2: Revenue KPIs +- component: + - component_id: "kpi_summary_revenue" + - component_type: "kpi_card_group" + - title: "Revenue KPIs for {name} ({id})" + - source_tables: ["revenue", "email_events"] + - display_condition: "campaign_id でフィルタリングしている場合のみこのコンポーネントを表示。" + - query_hints: + - "ジャーニーの収益を取得するには、'revenue' と 'email_events' テーブルを 'campaign_id' で JOIN する必要があります。" + - metrics: + - { metric_id: "total_revenue", display_name: "Total Revenue", calculation: "revenue テーブルの attribution_type が 'direct' または 'contributed' の total_revenue の SUM", format: "currency", display_condition: "direct と contributed の両方の収益が存在する場合のみ表示。" } + - { metric_id: "direct_revenue", display_name: "Direct Revenue", calculation: "revenue テーブルの attribution_type = 'direct' の total_revenue の SUM", format: "currency" } + - { metric_id: "contributed_revenue", display_name: "Contributed Revenue", calculation: "revenue テーブルの attribution_type = 'contributed' の total_revenue の SUM", 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: | + - クエリ構築: + 1. email_events テーブルから email_title で直接 GROUP BY + 2. campaign_id または journey_id でフィルタ + 3. event_master テーブルの JOIN は不要 + - SQL エージェントは LIMIT 51 でクエリを実行してください。 + - 51行が返された場合、VIZ エージェントは次のノートを表示: 'Showing top 50 titles only. There may be additional titles not displayed.' + - 最初の50行のみを表示。 + +### 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: | + - このコンポーネントは 'event_timestamp' カラムに基づいています。 + - ゼロパディングは不要です。 + - 時間粒度はキャンペーン/ジャーニーデータの総日付範囲によって決定されます(セクション3を参照)。 + - すべてのメトリクスは膨張したカウントを避けるため、開封とクリックに COUNT DISTINCT message_id を使用します。 + +### Component 5: Conversions and Revenue Trend +- component: + - component_id: "conversions_trend" + - component_type: "line_chart" + - title: "Conversions & Revenue Trend" + - source_tables: ["revenue", "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", format: "integer", y_axis: "left" } + - { metric_id: "direct_revenue", display_name: "Direct Revenue", calculation: "SUM(total_revenue) FROM revenue WHERE attribution_type = 'direct'", format: "currency", y_axis: "right" } + - { metric_id: "contributed_revenue", display_name: "Contributed Revenue", calculation: "SUM(total_revenue) FROM revenue WHERE attribution_type = 'contributed'", format: "currency", y_axis: "right" } + - { metric_id: "total_revenue", display_name: "Total Revenue", calculation: "SUM(total_revenue) FROM revenue 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(campaign_id とデータが存在する場合) +5. engagement_count_trend +6. conversions_trend(campaign_id とデータが存在する場合) +7. email_title_performance diff --git a/engage-box/email-delivery-reporter/docs/japanese/OverallSummary_Spec_ja.md b/engage-box/email-delivery-reporter/docs/japanese/OverallSummary_Spec_ja.md new file mode 100644 index 00000000..a1142640 --- /dev/null +++ b/engage-box/email-delivery-reporter/docs/japanese/OverallSummary_Spec_ja.md @@ -0,0 +1,142 @@ +# Report Specification: OverallSummary(日本語版) + +> **注意**: このドキュメントは参考資料です。正式なレポート仕様は英語版([OverallSummary_Spec.md](../../knowledge_bases/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/email-delivery-reporter/docs/japanese/README_JA.md b/engage-box/email-delivery-reporter/docs/japanese/README_JA.md new file mode 100644 index 00000000..a9b4391a --- /dev/null +++ b/engage-box/email-delivery-reporter/docs/japanese/README_JA.md @@ -0,0 +1,160 @@ +# Email Delivery Reporter Agent + +## 概要 + +このエージェントは、Treasure Data の Engage サービス向けにメール配信レポートを生成します。PlazmaDB のメール配信ログを自動的に分析し、Trino に対して SQL クエリを実行し、ビジュアライゼーションとインサイトを含むインタラクティブなダッシュボードを生成します。 + +2種類のレポートをサポートします: +- **全体サマリーレポート**: 期間全体の高レベル KPI、トレンド、キャンペーン/ジャーニーのパフォーマンス +- **キャンペーン詳細レポート**: 特定のキャンペーンまたはジャーニーの詳細分析 + +## 機能 + +- SQL クエリの自動生成と実行 +- インタラクティブなビジュアライゼーション +- 多言語サポート(英語/日本語) +- 多通貨サポート(USD/JPY) +- エンゲージメントと品質メトリクスを含む KPI カード +- 時系列トレンド分析 +- キャンペーン/ジャーニーのパフォーマンステーブル +- コンポーネント失敗時の優雅な劣化 + +## ファイル構成 + +``` +email-delivery-reporter/ +├── tdx.json # プロジェクトマニフェスト(変更不要) +├── Email Delivery Dashboard/ +│ ├── agent.yml # ← 編集: DOMAIN を置換(2箇所) +│ └── prompt.md # システムプロンプト(変更不要) +├── knowledge_bases/ +│ ├── delivery_email_DOMAIN.yml # ← 編集: DOMAIN を置換(ファイル名 + 内容2箇所) +│ ├── OverallSummary_Spec.md # レポート仕様(変更不要) +│ └── CampaignSummary_Spec.md # レポート仕様(変更不要) +├── form_interfaces/ +│ ├── Overall Summary.yml # フォーム UI(変更不要) +│ └── Campaign Details.yml # フォーム UI(変更不要) +└── docs/japanese/ # 参考資料のみ(デプロイ対象外) +``` + +## 前提条件 + +- `tdx` CLI (v2026.4.55+)、認証済み (`tdx auth setup`) +- Engage が有効な Treasure Data アカウント +- PlazmaDB データベース `delivery_email_<ドメイン>` が存在すること + +## セットアップ手順 + +### ステップ 1: クローン + +```bash +git clone --depth 1 --filter=blob:none --sparse \ + https://github.com/treasure-data/treasure-boxes.git +cd treasure-boxes +git sparse-checkout set engage-box/email-delivery-reporter +cd engage-box/email-delivery-reporter +``` + +### ステップ 2: ドメインスラッグの特定 + +Engage の送信元メールドメインからデータベース名を導出します: +- ドットとハイフンをアンダースコアに置換 +- 例: `example.com` → `example_com`, `my-company.co.jp` → `my_company_co_jp` + +データベースが存在するか確認: + +```bash +tdx databases | grep delivery_email +``` + +### ステップ 3: DOMAIN の置換(2ファイル + 1リネーム、計5箇所) + +以下の例では `example_com` を使用しています。ご自身のドメインスラッグに置き換えてください。 + +| # | ファイル | 場所 | 変更前 → 変更後 | +|---|---------|------|----------------| +| 1 | `knowledge_bases/delivery_email_DOMAIN.yml` | **ファイル名** | → `delivery_email_example_com.yml` | +| 2 | 同上 | `name:` (1行目) | `delivery_email_DOMAIN` → `delivery_email_example_com` | +| 3 | 同上 | `database:` (3行目) | `delivery_email_DOMAIN` → `delivery_email_example_com` | +| 4 | `Email Delivery Dashboard/agent.yml` | 1つ目の `@ref` | `"delivery_email_DOMAIN"` → `"delivery_email_example_com"` | +| 5 | 同上 | 2つ目の `@ref` | `"delivery_email_DOMAIN"` → `"delivery_email_example_com"` | + +コマンド(`example_com` をご自身のスラッグに置き換えてください): + +```bash +# ナレッジベースファイルのリネーム +mv knowledge_bases/delivery_email_DOMAIN.yml \ + knowledge_bases/delivery_email_example_com.yml + +# ファイル内容の一括置換 +# macOS / BSD: +sed -i '' 's/delivery_email_DOMAIN/delivery_email_example_com/g' \ + knowledge_bases/delivery_email_example_com.yml \ + "Email Delivery Dashboard/agent.yml" + +# Linux (GNU sed): +sed -i 's/delivery_email_DOMAIN/delivery_email_example_com/g' \ + knowledge_bases/delivery_email_example_com.yml \ + "Email Delivery Dashboard/agent.yml" +``` + +### ステップ 4: デプロイ + +```bash +tdx llm project create "Email Delivery Reporter" +tdx agent push . -f +``` + +### ステップ 5: 確認 + +```bash +tdx agent list +``` + +または: AI Agent Foundry > Email Delivery Reporter > Email Delivery Dashboard + +## 使用方法 + +### 全体サマリーレポート + +``` +Generate an overall email delivery report with following conditions: +- Report_id: 1. Overall Summary +- Start Date: 2025-01-01 +- End Date: 2025-01-31 +- Language: English +- Currency: USD +``` + +``` +以下の条件でメール配信レポートを作成してください: +- Report_id: 1. Overall Summary +- Start Date: 2024-12-01 +- End Date: 2024-12-31 +- Language: Japanese +- Currency: JPY +``` + +パラメータ: `Start_date`, `End_date`(必須、最大365日)、`Language`(`English`/`Japanese`)、`Currency`(`USD`/`JPY`) + +### キャンペーン詳細レポート + +``` +Generate a detailed email delivery report with following conditions: +- Report_id: 2. Campaign Summary +- Campaign_id: ABC123 +- Language: English +- Currency: USD +``` + +パラメータ: `Campaign_id` または `Journey_id`(いずれか必須)、`Language`、`Currency` + +## トラブルシューティング + +| エラー | 原因 | 解決策 | +|---|---|---| +| Knowledge base not found | `.yml` の `name` が `agent.yml` の `@ref` と不一致 | 上記5箇所の DOMAIN 置換を確認 | +| Database not found | `database:` フィールドが既存の TD データベースと不一致 | `tdx databases \| grep delivery_email` で確認 | +| `tdx agent push` 構造エラー | `knowledge_bases/` がプロジェクトルートにない | `knowledge_bases/` が `tdx.json` と同じ階層にあることを確認 | +| LLM_PROJECT_NOT_FOUND | プロジェクト未作成 | `tdx llm project create "Email Delivery Reporter"` を先に実行 | +| データが返されない | フィルタがデータにマッチしない | campaign_id/journey_id の存在確認、日付範囲の拡大 | diff --git a/engage-box/email-delivery-reporter/docs/japanese/prompt_ja.md b/engage-box/email-delivery-reporter/docs/japanese/prompt_ja.md new file mode 100644 index 00000000..f29c8127 --- /dev/null +++ b/engage-box/email-delivery-reporter/docs/japanese/prompt_ja.md @@ -0,0 +1,152 @@ +## 役割 + +あなたは、自律的にダッシュボードレポートを生成する Email Reporting Agent です。仕様とユーザーの指示に基づき、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 カテゴリの場合: updatemenus を使用。マルチチャートの場合: グリッドレイアウト +- 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" のメトリクス +- レイアウト設定: 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 でのみフォーマットを適用) +- コンポーネントの存在、行/カラムの整合性を検証 +- エラーと成功データが共存する可能性あり(エラーを記録して続行) + +## 利用可能なツール + +### データアクセス +- **List_columns**: テーブルスキーマを検出します。 +- **Query_data_directly**: PlazmaDB に対して SQL を実行します。最大100行。GROUP BY を使用。SELECT * は使用しない。[TRUNCATED] の場合は OFFSET/LIMIT を使用。 +- **read_overall_summary_spec**: Overall Summary レポート仕様を読み取ります。 +- **read_campaign_summary_spec**: Campaign/Journey Detail レポート仕様を読み取ります。 + +### 出力 +- **new_plot**: 中間ビジュアライゼーション。 +- **renderReactApp**: 最終 React ダッシュボード。単一ファイル、export default。 +- **text_in_form**: エラーメッセージのみ。 diff --git a/engage-box/email-delivery-reporter/form_interfaces/Campaign Details.yml b/engage-box/email-delivery-reporter/form_interfaces/Campaign Details.yml new file mode 100644 index 00000000..d47f853a --- /dev/null +++ b/engage-box/email-delivery-reporter/form_interfaces/Campaign Details.yml @@ -0,0 +1,73 @@ +name: Campaign Details +agent: '@ref(type: "agent", name: "Email Delivery Dashboard")' +prompt_template: | + Generate a detailed email delivery report 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 Campaign ID + Journey_id: + title: Journey ID + type: string + placeholder: Enter 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:ja:title: キャンペーンID + ui:en:placeholder: Enter Campaign ID + ui:ja:placeholder: キャンペーンIDを入力してください + Journey_id: + ui:en:title: Journey ID + ui:ja:title: ジャーニーID + ui:en:placeholder: Enter Journey ID + ui:ja:placeholder: ジャーニーIDを入力してください + Language: + ui:widget: radio + ui:en:title: Language + ui:ja:title: 言語 + ui:en:enumNames: + - English + - Japanese + ui:ja:enumNames: + - 英語 + - 日本語 + Currency: + ui:widget: radio + ui:en:title: Currency + ui:ja:title: 通貨 + ui:en:enumNames: + - USD + - JPY + ui:ja:enumNames: + - 米ドル + - 円 +use_text_resource: false diff --git a/engage-box/email-delivery-reporter/form_interfaces/Overall Summary.yml b/engage-box/email-delivery-reporter/form_interfaces/Overall Summary.yml new file mode 100644 index 00000000..98200b03 --- /dev/null +++ b/engage-box/email-delivery-reporter/form_interfaces/Overall Summary.yml @@ -0,0 +1,71 @@ +name: Overall Summary +agent: '@ref(type: "agent", name: "Email Delivery Dashboard")' +prompt_template: | + Generate an overall email delivery report with following conditions: + - Report_id: 1. Overall Summary + - Start Date: {{Start_date}} + - End Date: {{End_date}} + - 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 + enumNames: + - USD + - JPY + required: + - Start_date + - End_date + - Language + - Currency +ui_schema: + Start_date: + ui:en:title: Start Date + ui:ja:title: 開始日 + End_date: + ui:en:title: End Date + ui:ja:title: 終了日 + Language: + ui:widget: radio + ui:en:title: Language + ui:ja:title: 言語 + ui:en:enumNames: + - English + - Japanese + ui:ja:enumNames: + - 英語 + - 日本語 + Currency: + ui:widget: radio + ui:en:title: Currency + ui:ja:title: 通貨 + ui:en:enumNames: + - USD + - JPY + ui:ja:enumNames: + - 米ドル + - 円 +use_text_resource: false diff --git a/engage-box/email-delivery-reporter/knowledge_bases/CampaignSummary_Spec.md b/engage-box/email-delivery-reporter/knowledge_bases/CampaignSummary_Spec.md new file mode 100644 index 00000000..5aed268a --- /dev/null +++ b/engage-box/email-delivery-reporter/knowledge_bases/CampaignSummary_Spec.md @@ -0,0 +1,133 @@ +--- +name: CampaignSummary_Spec +type: text +--- + +# 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" + +## 2. Filters +- filter: + - id: "campaign_id" + type: "string" + required: false + exclusive_with: "journey_id" +- filter: + - id: "journey_id" + type: "string" + required: false + exclusive_with: "campaign_id" +- filter_notes: "Exactly one of campaign_id or journey_id must be provided. Do not provide both. 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 column (event_timestamp) is a varchar string. It MUST be parsed using date_parse(column, '%Y-%m-%d %H:%i:%s.%f')." + +## 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 a VARCHAR string, NOT a TIMESTAMP type. +- You MUST use `date_parse(column, '%Y-%m-%d %H:%i:%s.%f')` to parse this column. +- DO NOT use `CAST(column AS DATE)` or `CAST(column AS TIMESTAMP)` - these will fail. + +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: 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 3: 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. + +## 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. email_title_performance +5. engagement_count_trend diff --git a/engage-box/email-delivery-reporter/knowledge_bases/OverallSummary_Spec.md b/engage-box/email-delivery-reporter/knowledge_bases/OverallSummary_Spec.md new file mode 100644 index 00000000..b1cf4c17 --- /dev/null +++ b/engage-box/email-delivery-reporter/knowledge_bases/OverallSummary_Spec.md @@ -0,0 +1,131 @@ +--- +name: OverallSummary_Spec +type: text +--- + +# 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" + - "event_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: "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: "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_clicks) DESC, SUM(total_deliveries) 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'). + +- 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: "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_clicks) DESC, SUM(total_deliveries) 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'). + +- 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)" } + - 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/email-delivery-reporter/knowledge_bases/delivery_email_DOMAIN.yml b/engage-box/email-delivery-reporter/knowledge_bases/delivery_email_DOMAIN.yml new file mode 100644 index 00000000..84501a1e --- /dev/null +++ b/engage-box/email-delivery-reporter/knowledge_bases/delivery_email_DOMAIN.yml @@ -0,0 +1,28 @@ +name: delivery_email_DOMAIN +type: database +database: delivery_email_DOMAIN +tables: + - name: events + td_query: SELECT * FROM events + enable_data: true + enable_data_index: true + - name: error_events + td_query: SELECT * FROM error_events + enable_data: true + enable_data_index: true + - name: subscription_events + td_query: SELECT * FROM subscription_events + enable_data: true + enable_data_index: true + - name: daily_summary + td_query: SELECT * FROM daily_summary + enable_data: true + enable_data_index: true + - name: event_master + td_query: SELECT * FROM event_master + enable_data: true + enable_data_index: true + - name: email_events + td_query: SELECT * FROM email_events + enable_data: true + enable_data_index: true diff --git a/engage-box/email-delivery-reporter/tdx.json b/engage-box/email-delivery-reporter/tdx.json new file mode 100644 index 00000000..e1646b5e --- /dev/null +++ b/engage-box/email-delivery-reporter/tdx.json @@ -0,0 +1,4 @@ +{ + "llm_project": "Email Delivery Reporter", + "version": "1.0" +}