aads-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +159 -0
  3. package/README.md +159 -0
  4. package/data/campaign-layer-policy.json +68 -0
  5. package/dist/analysis/anomaly-detection.d.ts +16 -0
  6. package/dist/analysis/anomaly-detection.d.ts.map +1 -0
  7. package/dist/analysis/anomaly-detection.js +55 -0
  8. package/dist/analysis/anomaly-detection.js.map +1 -0
  9. package/dist/analysis/auto-to-manual.d.ts +11 -0
  10. package/dist/analysis/auto-to-manual.d.ts.map +1 -0
  11. package/dist/analysis/auto-to-manual.js +57 -0
  12. package/dist/analysis/auto-to-manual.js.map +1 -0
  13. package/dist/analysis/campaign-layer-classifier.d.ts +10 -0
  14. package/dist/analysis/campaign-layer-classifier.d.ts.map +1 -0
  15. package/dist/analysis/campaign-layer-classifier.js +50 -0
  16. package/dist/analysis/campaign-layer-classifier.js.map +1 -0
  17. package/dist/analysis/campaign-structure.d.ts +3 -0
  18. package/dist/analysis/campaign-structure.d.ts.map +1 -0
  19. package/dist/analysis/campaign-structure.js +51 -0
  20. package/dist/analysis/campaign-structure.js.map +1 -0
  21. package/dist/analysis/cpc-optimizer.d.ts +11 -0
  22. package/dist/analysis/cpc-optimizer.d.ts.map +1 -0
  23. package/dist/analysis/cpc-optimizer.js +46 -0
  24. package/dist/analysis/cpc-optimizer.js.map +1 -0
  25. package/dist/analysis/performance-analyzer.d.ts +3 -0
  26. package/dist/analysis/performance-analyzer.d.ts.map +1 -0
  27. package/dist/analysis/performance-analyzer.js +58 -0
  28. package/dist/analysis/performance-analyzer.js.map +1 -0
  29. package/dist/analysis/sku-classifier.d.ts +3 -0
  30. package/dist/analysis/sku-classifier.d.ts.map +1 -0
  31. package/dist/analysis/sku-classifier.js +64 -0
  32. package/dist/analysis/sku-classifier.js.map +1 -0
  33. package/dist/cli.d.ts +3 -0
  34. package/dist/cli.d.ts.map +1 -0
  35. package/dist/cli.js +351 -0
  36. package/dist/cli.js.map +1 -0
  37. package/dist/config/campaign-layer-policy.d.ts +28 -0
  38. package/dist/config/campaign-layer-policy.d.ts.map +1 -0
  39. package/dist/config/campaign-layer-policy.js +56 -0
  40. package/dist/config/campaign-layer-policy.js.map +1 -0
  41. package/dist/config/constants.d.ts +74 -0
  42. package/dist/config/constants.d.ts.map +1 -0
  43. package/dist/config/constants.js +61 -0
  44. package/dist/config/constants.js.map +1 -0
  45. package/dist/config/optimisation-config.d.ts +24 -0
  46. package/dist/config/optimisation-config.d.ts.map +1 -0
  47. package/dist/config/optimisation-config.js +41 -0
  48. package/dist/config/optimisation-config.js.map +1 -0
  49. package/dist/core/header-mapper.d.ts +6 -0
  50. package/dist/core/header-mapper.d.ts.map +1 -0
  51. package/dist/core/header-mapper.js +34 -0
  52. package/dist/core/header-mapper.js.map +1 -0
  53. package/dist/core/normalizer.d.ts +9 -0
  54. package/dist/core/normalizer.d.ts.map +1 -0
  55. package/dist/core/normalizer.js +76 -0
  56. package/dist/core/normalizer.js.map +1 -0
  57. package/dist/io/csv-reader.d.ts +3 -0
  58. package/dist/io/csv-reader.d.ts.map +1 -0
  59. package/dist/io/csv-reader.js +52 -0
  60. package/dist/io/csv-reader.js.map +1 -0
  61. package/dist/io/excel-reader.d.ts +4 -0
  62. package/dist/io/excel-reader.d.ts.map +1 -0
  63. package/dist/io/excel-reader.js +83 -0
  64. package/dist/io/excel-reader.js.map +1 -0
  65. package/dist/io/excel-writer.d.ts +7 -0
  66. package/dist/io/excel-writer.d.ts.map +1 -0
  67. package/dist/io/excel-writer.js +42 -0
  68. package/dist/io/excel-writer.js.map +1 -0
  69. package/dist/pipeline/analyze-pipeline.d.ts +8 -0
  70. package/dist/pipeline/analyze-pipeline.d.ts.map +1 -0
  71. package/dist/pipeline/analyze-pipeline.js +145 -0
  72. package/dist/pipeline/analyze-pipeline.js.map +1 -0
  73. package/dist/pipeline/types.d.ts +142 -0
  74. package/dist/pipeline/types.d.ts.map +1 -0
  75. package/dist/pipeline/types.js +2 -0
  76. package/dist/pipeline/types.js.map +1 -0
  77. package/dist/ranking/keyword-matcher.d.ts +10 -0
  78. package/dist/ranking/keyword-matcher.d.ts.map +1 -0
  79. package/dist/ranking/keyword-matcher.js +62 -0
  80. package/dist/ranking/keyword-matcher.js.map +1 -0
  81. package/dist/ranking/ranking-db.d.ts +14 -0
  82. package/dist/ranking/ranking-db.d.ts.map +1 -0
  83. package/dist/ranking/ranking-db.js +97 -0
  84. package/dist/ranking/ranking-db.js.map +1 -0
  85. package/dist/ranking/seo-factor.d.ts +5 -0
  86. package/dist/ranking/seo-factor.d.ts.map +1 -0
  87. package/dist/ranking/seo-factor.js +57 -0
  88. package/dist/ranking/seo-factor.js.map +1 -0
  89. package/dist/ranking/types.d.ts +25 -0
  90. package/dist/ranking/types.d.ts.map +1 -0
  91. package/dist/ranking/types.js +2 -0
  92. package/dist/ranking/types.js.map +1 -0
  93. package/dist/schemas/types.d.ts +32 -0
  94. package/dist/schemas/types.d.ts.map +1 -0
  95. package/dist/schemas/types.js +2 -0
  96. package/dist/schemas/types.js.map +1 -0
  97. package/dist/utils/date-utils.d.ts +17 -0
  98. package/dist/utils/date-utils.d.ts.map +1 -0
  99. package/dist/utils/date-utils.js +56 -0
  100. package/dist/utils/date-utils.js.map +1 -0
  101. package/dist/utils/logger.d.ts +12 -0
  102. package/dist/utils/logger.d.ts.map +1 -0
  103. package/dist/utils/logger.js +45 -0
  104. package/dist/utils/logger.js.map +1 -0
  105. package/package.json +65 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 yuuki-courage
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ja.md ADDED
@@ -0,0 +1,159 @@
1
+ # aads
2
+
3
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org/)
5
+
6
+ Amazon Ads スポンサープロダクト広告のパフォーマンス解析 CLI ツール。
7
+
8
+ Amazon Ads のバルクシートエクスポート(Excel/CSV)を読み込み、CPC入札最適化レポート、Auto→Manual昇格候補、ネガティブキーワード提案、SEOランキング連携レポートを生成します。
9
+
10
+ ## 機能
11
+
12
+ | コマンド | 説明 |
13
+ |---------|------|
14
+ | `analyze` | バルクシートからKPI(CTR, CVR, ACOS, ROAS)を集計 |
15
+ | `summary` | キャンペーン構造サマリー + レイヤー別集計 |
16
+ | `cpc-report` | CPC入札最適化レポート(SEOランキング連携対応) |
17
+ | `promotion-report` | Auto→Manual昇格候補 + ネガティブキーワード提案 |
18
+ | `seo-report` | SEOオーガニック順位 × 広告キーワード統合レポート |
19
+
20
+ ## インストール
21
+
22
+ ```bash
23
+ npm install -g aads-cli
24
+ ```
25
+
26
+ インストールせずに使う場合:
27
+
28
+ ```bash
29
+ npx aads --help
30
+ ```
31
+
32
+ ## クイックスタート
33
+
34
+ ```bash
35
+ # バルクシートを解析
36
+ aads analyze --input "bulk-*.xlsx"
37
+
38
+ # キャンペーン構造サマリーを表示
39
+ aads summary --input "bulk-*.xlsx"
40
+
41
+ # CPC最適化レポートを生成
42
+ aads cpc-report --input "bulk-*.xlsx" --output cpc-report.xlsx
43
+
44
+ # 昇格レポートを生成
45
+ aads promotion-report --input "bulk-*.xlsx" --output promotion.xlsx
46
+
47
+ # SEO統合レポートを生成
48
+ aads seo-report --input "bulk-*.xlsx" --ranking-db ranking.db
49
+ ```
50
+
51
+ ## コマンド
52
+
53
+ ### `analyze`
54
+
55
+ Amazon Ads バルクシートからキャンペーンKPIを集計します。
56
+
57
+ ```bash
58
+ aads analyze --input <pattern> [--layer-policy <file>]
59
+ ```
60
+
61
+ | オプション | 説明 |
62
+ |-----------|------|
63
+ | `--input <pattern>` | 入力Excel/CSVパスまたはワイルドカードパターン(必須) |
64
+ | `--layer-policy <file>` | キャンペーンレイヤーポリシーJSONパス |
65
+
66
+ ### `summary`
67
+
68
+ キャンペーン構造と広告グループの詳細、レイヤー分類を表示します。
69
+
70
+ ```bash
71
+ aads summary --input <pattern> [--layer-policy <file>]
72
+ ```
73
+
74
+ ### `cpc-report`
75
+
76
+ CPC入札最適化の推奨値をExcelレポートとして出力します。
77
+
78
+ ```bash
79
+ aads cpc-report --input <pattern> --output <file> [--ranking-db <path>]
80
+ ```
81
+
82
+ | オプション | 説明 |
83
+ |-----------|------|
84
+ | `--input <pattern>` | 入力Excel/CSVパスまたはワイルドカードパターン(必須) |
85
+ | `--output <file>` | 出力xlsxパス(必須) |
86
+ | `--ranking-db <path>` | SEO順位調整用のA_rank SQLite DBパス |
87
+
88
+ ### `promotion-report`
89
+
90
+ Auto キャンペーンの検索語から Manual キャンペーンへの昇格候補を特定し、無駄な検索語にはネガティブキーワードを提案します。
91
+
92
+ ```bash
93
+ aads promotion-report --input <pattern> --output <file>
94
+ ```
95
+
96
+ ### `seo-report`
97
+
98
+ 広告キーワードとオーガニックSEOランキングデータを照合し、オーガニック順位が強いキーワードの入札削減機会を特定します。
99
+
100
+ ```bash
101
+ aads seo-report --input <pattern> --ranking-db <path> [--output <file>] [--format <type>]
102
+ ```
103
+
104
+ | オプション | 説明 |
105
+ |-----------|------|
106
+ | `--input <pattern>` | 入力Excel/CSVパスまたはワイルドカードパターン(必須) |
107
+ | `--ranking-db <path>` | A_rank SQLite DBパス(必須) |
108
+ | `--output <file>` | 出力ファイルパス |
109
+ | `--format <type>` | `console` \| `json` \| `xlsx`(デフォルト: `console`) |
110
+
111
+ ## 設定
112
+
113
+ 環境変数(または `.env` ファイル)で設定します:
114
+
115
+ | 変数 | デフォルト | 説明 |
116
+ |------|---------|------|
117
+ | `TARGET_ACOS` | `0.25` | CPC最適化の目標ACOS |
118
+ | `MIN_CLICKS_CPC` | `5` | CPC推奨の最小クリック数 |
119
+ | `MIN_CLICKS_PROMOTION` | `5` | 昇格候補の最小クリック数 |
120
+ | `MIN_CVR_PROMOTION` | `0.03` | 昇格候補の最小CVR |
121
+ | `NEGATIVE_ACOS_THRESHOLD` | `0.4` | ネガティブキーワード提案のACOS閾値 |
122
+ | `SEO_ENABLED` | `true` | SEOランキング連携を有効化 |
123
+ | `SEO_CPC_CEILING` | `0` | CPC上限(0 = 自動) |
124
+ | `RANKING_DB_PATH` | - | A_rank SQLite DBのデフォルトパス |
125
+ | `LOG_LEVEL` | `info` | ログレベル(`debug` \| `info`) |
126
+
127
+ テンプレートは [`.env.example`](.env.example) を参照してください。
128
+
129
+ ## SEO連携
130
+
131
+ `cpc-report` と `seo-report` コマンドは、SQLiteデータベースに格納されたSEOランキングデータと連携できます(例: [A_rank](https://github.com/yuuki-courage/A_rank))。
132
+
133
+ 広告キーワードのオーガニック順位が強い場合(1-4位)、推奨CPC入札額を自動的に削減します:
134
+
135
+ | オーガニック順位 | SEO係数 | 入札削減率 |
136
+ |---------------|---------|----------|
137
+ | 1位 | 0.50 | -50% |
138
+ | 2位 | 0.60 | -40% |
139
+ | 3位 | 0.70 | -30% |
140
+ | 4位 | 0.80 | -20% |
141
+ | 5位以降 | 1.00 | 変更なし |
142
+
143
+ ## キャンペーンレイヤーポリシー
144
+
145
+ `analyze` と `summary` コマンドは、JSONポリシーファイルによるキャンペーンレイヤー分類をサポートします:
146
+
147
+ ```bash
148
+ aads summary --input "bulk-*.xlsx" --layer-policy data/campaign-layer-policy.json
149
+ ```
150
+
151
+ デフォルトのポリシー構造は [`data/campaign-layer-policy.json`](data/campaign-layer-policy.json) を参照してください。
152
+
153
+ ## コントリビュート
154
+
155
+ 開発環境のセットアップとコントリビューションガイドラインは [CONTRIBUTING.md](CONTRIBUTING.md) を参照してください。
156
+
157
+ ## ライセンス
158
+
159
+ [MIT](LICENSE)
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # aads
2
+
3
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org/)
5
+
6
+ CLI tool for analyzing Amazon Ads Sponsored Products campaign performance.
7
+
8
+ Reads Amazon Ads bulk sheet exports (Excel/CSV) and generates actionable optimization reports — CPC bid recommendations, auto-to-manual promotion candidates, negative keyword suggestions, and SEO ranking integration.
9
+
10
+ ## Features
11
+
12
+ | Command | Description |
13
+ |---------|-------------|
14
+ | `analyze` | Aggregate KPIs (CTR, CVR, ACOS, ROAS) from bulk sheet data |
15
+ | `summary` | Campaign structure overview with layer-level aggregation |
16
+ | `cpc-report` | CPC bid optimization report with optional SEO ranking integration |
17
+ | `promotion-report` | Auto-to-Manual promotion candidates + negative keyword suggestions |
18
+ | `seo-report` | SEO organic ranking vs ad keyword integrated report |
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install -g aads-cli
24
+ ```
25
+
26
+ Or use without installing:
27
+
28
+ ```bash
29
+ npx aads --help
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ```bash
35
+ # Analyze a bulk sheet export
36
+ aads analyze --input "bulk-*.xlsx"
37
+
38
+ # Generate campaign structure summary
39
+ aads summary --input "bulk-*.xlsx"
40
+
41
+ # Generate CPC optimization report
42
+ aads cpc-report --input "bulk-*.xlsx" --output cpc-report.xlsx
43
+
44
+ # Generate promotion report
45
+ aads promotion-report --input "bulk-*.xlsx" --output promotion.xlsx
46
+
47
+ # Generate SEO-integrated report
48
+ aads seo-report --input "bulk-*.xlsx" --ranking-db ranking.db
49
+ ```
50
+
51
+ ## Commands
52
+
53
+ ### `analyze`
54
+
55
+ Aggregates campaign KPIs from Amazon Ads bulk sheet exports.
56
+
57
+ ```bash
58
+ aads analyze --input <pattern> [--layer-policy <file>]
59
+ ```
60
+
61
+ | Option | Description |
62
+ |--------|-------------|
63
+ | `--input <pattern>` | Input Excel/CSV path or wildcard pattern (required) |
64
+ | `--layer-policy <file>` | Campaign layer policy JSON path |
65
+
66
+ ### `summary`
67
+
68
+ Displays campaign structure with ad group details and layer classification.
69
+
70
+ ```bash
71
+ aads summary --input <pattern> [--layer-policy <file>]
72
+ ```
73
+
74
+ ### `cpc-report`
75
+
76
+ Generates CPC bid optimization recommendations as an Excel report.
77
+
78
+ ```bash
79
+ aads cpc-report --input <pattern> --output <file> [--ranking-db <path>]
80
+ ```
81
+
82
+ | Option | Description |
83
+ |--------|-------------|
84
+ | `--input <pattern>` | Input Excel/CSV path or wildcard pattern (required) |
85
+ | `--output <file>` | Output xlsx path (required) |
86
+ | `--ranking-db <path>` | A_rank SQLite DB path for SEO-based CPC adjustment |
87
+
88
+ ### `promotion-report`
89
+
90
+ Identifies auto campaign search terms ready for manual campaign promotion and suggests negative keywords for wasteful terms.
91
+
92
+ ```bash
93
+ aads promotion-report --input <pattern> --output <file>
94
+ ```
95
+
96
+ ### `seo-report`
97
+
98
+ Cross-references ad keywords with organic SEO ranking data to identify opportunities for bid reduction where organic rankings are strong.
99
+
100
+ ```bash
101
+ aads seo-report --input <pattern> --ranking-db <path> [--output <file>] [--format <type>]
102
+ ```
103
+
104
+ | Option | Description |
105
+ |--------|-------------|
106
+ | `--input <pattern>` | Input Excel/CSV path or wildcard pattern (required) |
107
+ | `--ranking-db <path>` | A_rank SQLite DB path (required) |
108
+ | `--output <file>` | Output file path |
109
+ | `--format <type>` | `console` \| `json` \| `xlsx` (default: `console`) |
110
+
111
+ ## Configuration
112
+
113
+ Configuration is done via environment variables (or a `.env` file):
114
+
115
+ | Variable | Default | Description |
116
+ |----------|---------|-------------|
117
+ | `TARGET_ACOS` | `0.25` | Target ACOS for CPC optimization |
118
+ | `MIN_CLICKS_CPC` | `5` | Minimum clicks for CPC recommendation |
119
+ | `MIN_CLICKS_PROMOTION` | `5` | Minimum clicks for promotion candidate |
120
+ | `MIN_CVR_PROMOTION` | `0.03` | Minimum CVR for promotion candidate |
121
+ | `NEGATIVE_ACOS_THRESHOLD` | `0.4` | ACOS threshold for negative keyword suggestion |
122
+ | `SEO_ENABLED` | `true` | Enable SEO ranking integration |
123
+ | `SEO_CPC_CEILING` | `0` | CPC ceiling (0 = auto) |
124
+ | `RANKING_DB_PATH` | - | Default path to A_rank SQLite DB |
125
+ | `LOG_LEVEL` | `info` | Log level (`debug` \| `info`) |
126
+
127
+ See [`.env.example`](.env.example) for a complete template.
128
+
129
+ ## SEO Integration
130
+
131
+ The `cpc-report` and `seo-report` commands can integrate with SEO ranking data stored in a SQLite database (e.g., from [A_rank](https://github.com/yuuki-courage/A_rank)).
132
+
133
+ When an ad keyword has strong organic ranking (positions 1-4), the tool automatically reduces the recommended CPC bid:
134
+
135
+ | Organic Position | SEO Factor | Bid Reduction |
136
+ |-----------------|------------|---------------|
137
+ | #1 | 0.50 | -50% |
138
+ | #2 | 0.60 | -40% |
139
+ | #3 | 0.70 | -30% |
140
+ | #4 | 0.80 | -20% |
141
+ | #5+ | 1.00 | No change |
142
+
143
+ ## Campaign Layer Policy
144
+
145
+ The `analyze` and `summary` commands support campaign layer classification using a JSON policy file:
146
+
147
+ ```bash
148
+ aads summary --input "bulk-*.xlsx" --layer-policy data/campaign-layer-policy.json
149
+ ```
150
+
151
+ See [`data/campaign-layer-policy.json`](data/campaign-layer-policy.json) for the default policy structure.
152
+
153
+ ## Contributing
154
+
155
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and contribution guidelines.
156
+
157
+ ## License
158
+
159
+ [MIT](LICENSE)
@@ -0,0 +1,68 @@
1
+ {
2
+ "version": "1.0",
3
+ "layers": {
4
+ "L0": {
5
+ "name": "DEF",
6
+ "nameJa": "防衛",
7
+ "purpose": "指名流入の取りこぼし防止",
8
+ "alwaysOn": true,
9
+ "budgetSharePct": 0.05,
10
+ "campaignPrefix": "SP_DEF_",
11
+ "targetAcos": 0.10
12
+ },
13
+ "L1": {
14
+ "name": "CORE",
15
+ "nameJa": "恒常主力",
16
+ "purpose": "安定売上・事業の軸",
17
+ "alwaysOn": true,
18
+ "budgetSharePct": 0.55,
19
+ "campaignPrefix": "SP_CORE_",
20
+ "targetAcos": 0.15
21
+ },
22
+ "L2": {
23
+ "name": "EXP",
24
+ "nameJa": "探索",
25
+ "purpose": "育成・昇格候補の蓄積",
26
+ "alwaysOn": true,
27
+ "budgetSharePct": 0.25,
28
+ "campaignPrefix": "SP_EXP_",
29
+ "targetAcos": 0.25
30
+ },
31
+ "L3": {
32
+ "name": "EVT",
33
+ "nameJa": "セール専用",
34
+ "purpose": "セール時の最大化",
35
+ "alwaysOn": false,
36
+ "budgetSharePct": 0.10,
37
+ "campaignPrefix": "SP_EVT_",
38
+ "targetAcos": 0.20
39
+ },
40
+ "L4": {
41
+ "name": "AUTO",
42
+ "nameJa": "オート",
43
+ "purpose": "新規発見+既存性能維持",
44
+ "alwaysOn": true,
45
+ "budgetSharePct": 0.05,
46
+ "campaignPrefix": "SP_AUTO_",
47
+ "targetAcos": 0.30
48
+ }
49
+ },
50
+ "namingPatterns": [
51
+ { "pattern": "^SP_DEF_", "layer": "L0" },
52
+ { "pattern": "^SP_CORE_", "layer": "L1" },
53
+ { "pattern": "^SP_EXP_", "layer": "L2" },
54
+ { "pattern": "^SP_EVT_", "layer": "L3" },
55
+ { "pattern": "^SP_AUTO_", "layer": "L4" }
56
+ ],
57
+ "fallbackClassification": {
58
+ "auto": "L4",
59
+ "manual-exact": "L1",
60
+ "manual-phrase": "L2",
61
+ "manual-broad": "L2"
62
+ },
63
+ "promotionRules": [
64
+ { "from": "L4", "to": "L2", "description": "Auto→EXP: clicks>=5 AND orders>=1" },
65
+ { "from": "L2", "to": "L1", "description": "EXP→CORE: (CVR>=20% OR orders>=2) AND ACOS<=15%" },
66
+ { "from": "L2", "to": "L3", "description": "EXP→EVT: セール期間中にCVR改善" }
67
+ ]
68
+ }
@@ -0,0 +1,16 @@
1
+ import type { NormalizedRecord } from "../pipeline/types.js";
2
+ export interface AnomalyThresholds {
3
+ impressionThreshold: number;
4
+ spendThreshold: number;
5
+ cpcThreshold: number;
6
+ }
7
+ export interface CampaignAnomaly {
8
+ campaignId: string;
9
+ campaignName: string;
10
+ anomalyTypes: string[];
11
+ impressionChangePct: number;
12
+ spendChangePct: number;
13
+ cpcChangePct: number;
14
+ }
15
+ export declare const detectAnomalies: (current: NormalizedRecord[], baseline: NormalizedRecord[], thresholds: AnomalyThresholds) => CampaignAnomaly[];
16
+ //# sourceMappingURL=anomaly-detection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anomaly-detection.d.ts","sourceRoot":"","sources":["../../src/analysis/anomaly-detection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,MAAM,WAAW,iBAAiB;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;AA8BD,eAAO,MAAM,eAAe,GAC1B,SAAS,gBAAgB,EAAE,EAC3B,UAAU,gBAAgB,EAAE,EAC5B,YAAY,iBAAiB,KAC5B,eAAe,EAkCjB,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { safeDivide } from "../core/normalizer.js";
2
+ const aggregateByCampaign = (records) => {
3
+ const map = new Map();
4
+ for (const record of records) {
5
+ const key = record.campaignId || record.campaignName;
6
+ if (!key)
7
+ continue;
8
+ const agg = map.get(key) ?? {
9
+ campaignId: record.campaignId,
10
+ campaignName: record.campaignName,
11
+ impressions: 0,
12
+ clicks: 0,
13
+ spend: 0,
14
+ };
15
+ agg.impressions += record.impressions;
16
+ agg.clicks += record.clicks;
17
+ agg.spend += record.spend;
18
+ map.set(key, agg);
19
+ }
20
+ return map;
21
+ };
22
+ export const detectAnomalies = (current, baseline, thresholds) => {
23
+ const currentMap = aggregateByCampaign(current);
24
+ const baselineMap = aggregateByCampaign(baseline);
25
+ const anomalies = [];
26
+ for (const [key, currentAgg] of currentMap.entries()) {
27
+ const base = baselineMap.get(key);
28
+ if (!base)
29
+ continue;
30
+ const currentCpc = safeDivide(currentAgg.spend, currentAgg.clicks);
31
+ const baselineCpc = safeDivide(base.spend, base.clicks);
32
+ const impressionChange = safeDivide(currentAgg.impressions - base.impressions, Math.max(base.impressions, 1));
33
+ const spendChange = safeDivide(currentAgg.spend - base.spend, Math.max(base.spend, 1));
34
+ const cpcChange = safeDivide(currentCpc - baselineCpc, Math.max(baselineCpc, 1e-9));
35
+ const types = [];
36
+ if (impressionChange > thresholds.impressionThreshold)
37
+ types.push(`impressions+${Math.round(impressionChange * 100)}%`);
38
+ if (spendChange > thresholds.spendThreshold)
39
+ types.push(`spend+${Math.round(spendChange * 100)}%`);
40
+ if (cpcChange > thresholds.cpcThreshold)
41
+ types.push(`cpc+${Math.round(cpcChange * 100)}%`);
42
+ if (types.length > 0) {
43
+ anomalies.push({
44
+ campaignId: currentAgg.campaignId,
45
+ campaignName: currentAgg.campaignName,
46
+ anomalyTypes: types,
47
+ impressionChangePct: impressionChange * 100,
48
+ spendChangePct: spendChange * 100,
49
+ cpcChangePct: cpcChange * 100,
50
+ });
51
+ }
52
+ }
53
+ return anomalies.sort((a, b) => b.spendChangePct - a.spendChangePct);
54
+ };
55
+ //# sourceMappingURL=anomaly-detection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anomaly-detection.js","sourceRoot":"","sources":["../../src/analysis/anomaly-detection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { NegativeCandidate, NormalizedRecord, PromotionCandidate } from "../pipeline/types.js";
2
+ export interface AutoToManualOptions {
3
+ minClicks: number;
4
+ minCvr: number;
5
+ negativeAcosThreshold: number;
6
+ }
7
+ export declare const analyzeAutoToManual: (records: NormalizedRecord[], options: AutoToManualOptions) => {
8
+ promotionCandidates: PromotionCandidate[];
9
+ negativeCandidates: NegativeCandidate[];
10
+ };
11
+ //# sourceMappingURL=auto-to-manual.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-to-manual.d.ts","sourceRoot":"","sources":["../../src/analysis/auto-to-manual.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAGpG,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAQD,eAAO,MAAM,mBAAmB,GAC9B,SAAS,gBAAgB,EAAE,EAC3B,SAAS,mBAAmB,KAC3B;IAAE,mBAAmB,EAAE,kBAAkB,EAAE,CAAC;IAAC,kBAAkB,EAAE,iBAAiB,EAAE,CAAA;CAqDtF,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { normalizeMatchType, safeDivide } from "../core/normalizer.js";
2
+ const buildRecommendedAdGroupName = (adGroupName, matchType) => {
3
+ const suffix = normalizeMatchType(matchType).includes("exact") ? "manual-exact" : "manual-phrase";
4
+ const base = adGroupName || "adgroup";
5
+ return `${base}-${suffix}`.replace(/\s+/g, "-");
6
+ };
7
+ export const analyzeAutoToManual = (records, options) => {
8
+ const promotionCandidates = [];
9
+ const negativeCandidates = [];
10
+ for (const record of records) {
11
+ const isAuto = /auto/i.test(record.targetingType || "");
12
+ if (!isAuto)
13
+ continue;
14
+ // Auto: use customerSearchTerm (カスタマー検索用語), Manual: use keywordText (キーワードテキスト)
15
+ const term = isAuto ? record.customerSearchTerm : record.keywordText;
16
+ if (!term)
17
+ continue;
18
+ const cvr = safeDivide(record.orders, record.clicks);
19
+ const acos = safeDivide(record.spend, record.sales);
20
+ const avgCpc = safeDivide(record.spend, record.clicks);
21
+ const recommendedBid = Math.max(1, Math.round((record.bid === "" ? avgCpc : Number(record.bid)) * 1.05));
22
+ if (record.clicks >= options.minClicks && (record.orders > 0 || cvr >= options.minCvr)) {
23
+ promotionCandidates.push({
24
+ campaignId: record.campaignId,
25
+ campaignName: record.campaignName,
26
+ adGroupId: record.adGroupId,
27
+ adGroupName: record.adGroupName,
28
+ keywordText: term,
29
+ matchType: normalizeMatchType(record.matchType || "exact"),
30
+ sku: record.sku,
31
+ clicks: record.clicks,
32
+ spend: record.spend,
33
+ orders: record.orders,
34
+ cvr,
35
+ avgCpc,
36
+ recommendedBid,
37
+ recommendedAdGroupName: buildRecommendedAdGroupName(record.adGroupName, record.matchType),
38
+ });
39
+ }
40
+ if (record.clicks >= options.minClicks && record.orders === 0 && acos >= options.negativeAcosThreshold) {
41
+ negativeCandidates.push({
42
+ campaignId: record.campaignId,
43
+ campaignName: record.campaignName,
44
+ adGroupId: record.adGroupId,
45
+ adGroupName: record.adGroupName,
46
+ keywordText: term,
47
+ matchType: "negative phrase",
48
+ reason: `high_acos(${acos.toFixed(2)})`,
49
+ });
50
+ }
51
+ }
52
+ return {
53
+ promotionCandidates: promotionCandidates.sort((a, b) => b.clicks - a.clicks),
54
+ negativeCandidates: negativeCandidates.sort((a, b) => b.keywordText.localeCompare(a.keywordText)),
55
+ };
56
+ };
57
+ //# sourceMappingURL=auto-to-manual.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-to-manual.js","sourceRoot":"","sources":["../../src/analysis/auto-to-manual.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { CampaignLayerId, CampaignLayerPolicy } from "../config/campaign-layer-policy.js";
2
+ import type { CampaignMetrics } from "../pipeline/types.js";
3
+ export interface CampaignLayerClassification {
4
+ campaignName: string;
5
+ layer: CampaignLayerId;
6
+ confidence: "high" | "medium" | "low";
7
+ matchedBy: "naming" | "targeting" | "fallback";
8
+ }
9
+ export declare const classifyCampaignLayers: (metrics: CampaignMetrics[], policy: CampaignLayerPolicy) => CampaignLayerClassification[];
10
+ //# sourceMappingURL=campaign-layer-classifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"campaign-layer-classifier.d.ts","sourceRoot":"","sources":["../../src/analysis/campaign-layer-classifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC/F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,eAAe,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,SAAS,EAAE,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;CAChD;AAID,eAAO,MAAM,sBAAsB,GACjC,SAAS,eAAe,EAAE,EAC1B,QAAQ,mBAAmB,KAC1B,2BAA2B,EAkD7B,CAAC"}
@@ -0,0 +1,50 @@
1
+ const DEFAULT_FALLBACK_LAYER = "L2";
2
+ export const classifyCampaignLayers = (metrics, policy) => {
3
+ const compiledPatterns = policy.namingPatterns.map((np) => ({
4
+ regex: new RegExp(np.pattern),
5
+ layer: np.layer,
6
+ }));
7
+ return metrics.map((campaign) => {
8
+ // 1. Match by campaign name prefix
9
+ for (const { regex, layer } of compiledPatterns) {
10
+ if (regex.test(campaign.campaignName)) {
11
+ return {
12
+ campaignName: campaign.campaignName,
13
+ layer,
14
+ confidence: "high",
15
+ matchedBy: "naming",
16
+ };
17
+ }
18
+ }
19
+ // 2. Fallback by targeting type
20
+ const targetingType = campaign.targetingType.toLowerCase();
21
+ if (targetingType === "auto" && policy.fallbackClassification.auto) {
22
+ return {
23
+ campaignName: campaign.campaignName,
24
+ layer: policy.fallbackClassification.auto,
25
+ confidence: "medium",
26
+ matchedBy: "targeting",
27
+ };
28
+ }
29
+ if (targetingType === "manual") {
30
+ // Cannot determine exact/phrase/broad at campaign level, use generic manual fallback
31
+ const fallbackKey = "manual-exact";
32
+ if (policy.fallbackClassification[fallbackKey]) {
33
+ return {
34
+ campaignName: campaign.campaignName,
35
+ layer: policy.fallbackClassification[fallbackKey],
36
+ confidence: "medium",
37
+ matchedBy: "targeting",
38
+ };
39
+ }
40
+ }
41
+ // 3. Default fallback
42
+ return {
43
+ campaignName: campaign.campaignName,
44
+ layer: DEFAULT_FALLBACK_LAYER,
45
+ confidence: "low",
46
+ matchedBy: "fallback",
47
+ };
48
+ });
49
+ };
50
+ //# sourceMappingURL=campaign-layer-classifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"campaign-layer-classifier.js","sourceRoot":"","sources":["../../src/analysis/campaign-layer-classifier.ts"],"names":[],"mappings":"AAUA,MAAM,sBAAsB,GAAoB,IAAI,CAAC;AAErD,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,OAA0B,EAC1B,MAA2B,EACI,EAAE;IACjC,MAAM,gBAAgB,GAAG,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAC1D,KAAK,EAAE,IAAI,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC;QAC7B,KAAK,EAAE,EAAE,CAAC,KAAK;KAChB,CAAC,CAAC,CAAC;IAEJ,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC9B,mCAAmC;QACnC,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,gBAAgB,EAAE,CAAC;YAChD,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;gBACtC,OAAO;oBACL,YAAY,EAAE,QAAQ,CAAC,YAAY;oBACnC,KAAK;oBACL,UAAU,EAAE,MAAe;oBAC3B,SAAS,EAAE,QAAiB;iBAC7B,CAAC;YACJ,CAAC;QACH,CAAC;QAED,gCAAgC;QAChC,MAAM,aAAa,GAAG,QAAQ,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC;QAC3D,IAAI,aAAa,KAAK,MAAM,IAAI,MAAM,CAAC,sBAAsB,CAAC,IAAI,EAAE,CAAC;YACnE,OAAO;gBACL,YAAY,EAAE,QAAQ,CAAC,YAAY;gBACnC,KAAK,EAAE,MAAM,CAAC,sBAAsB,CAAC,IAAI;gBACzC,UAAU,EAAE,QAAiB;gBAC7B,SAAS,EAAE,WAAoB;aAChC,CAAC;QACJ,CAAC;QACD,IAAI,aAAa,KAAK,QAAQ,EAAE,CAAC;YAC/B,qFAAqF;YACrF,MAAM,WAAW,GAAG,cAAc,CAAC;YACnC,IAAI,MAAM,CAAC,sBAAsB,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC/C,OAAO;oBACL,YAAY,EAAE,QAAQ,CAAC,YAAY;oBACnC,KAAK,EAAE,MAAM,CAAC,sBAAsB,CAAC,WAAW,CAAC;oBACjD,UAAU,EAAE,QAAiB;oBAC7B,SAAS,EAAE,WAAoB;iBAChC,CAAC;YACJ,CAAC;QACH,CAAC;QAED,sBAAsB;QACtB,OAAO;YACL,YAAY,EAAE,QAAQ,CAAC,YAAY;YACnC,KAAK,EAAE,sBAAsB;YAC7B,UAAU,EAAE,KAAc;YAC1B,SAAS,EAAE,UAAmB;SAC/B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { CampaignStructureNode, NormalizedRecord } from "../pipeline/types.js";
2
+ export declare const buildCampaignStructure: (records: NormalizedRecord[]) => CampaignStructureNode[];
3
+ //# sourceMappingURL=campaign-structure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"campaign-structure.d.ts","sourceRoot":"","sources":["../../src/analysis/campaign-structure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAkBpF,eAAO,MAAM,sBAAsB,GAAI,SAAS,gBAAgB,EAAE,KAAG,qBAAqB,EAqDzF,CAAC"}