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.
- package/LICENSE +21 -0
- package/README.ja.md +159 -0
- package/README.md +159 -0
- package/data/campaign-layer-policy.json +68 -0
- package/dist/analysis/anomaly-detection.d.ts +16 -0
- package/dist/analysis/anomaly-detection.d.ts.map +1 -0
- package/dist/analysis/anomaly-detection.js +55 -0
- package/dist/analysis/anomaly-detection.js.map +1 -0
- package/dist/analysis/auto-to-manual.d.ts +11 -0
- package/dist/analysis/auto-to-manual.d.ts.map +1 -0
- package/dist/analysis/auto-to-manual.js +57 -0
- package/dist/analysis/auto-to-manual.js.map +1 -0
- package/dist/analysis/campaign-layer-classifier.d.ts +10 -0
- package/dist/analysis/campaign-layer-classifier.d.ts.map +1 -0
- package/dist/analysis/campaign-layer-classifier.js +50 -0
- package/dist/analysis/campaign-layer-classifier.js.map +1 -0
- package/dist/analysis/campaign-structure.d.ts +3 -0
- package/dist/analysis/campaign-structure.d.ts.map +1 -0
- package/dist/analysis/campaign-structure.js +51 -0
- package/dist/analysis/campaign-structure.js.map +1 -0
- package/dist/analysis/cpc-optimizer.d.ts +11 -0
- package/dist/analysis/cpc-optimizer.d.ts.map +1 -0
- package/dist/analysis/cpc-optimizer.js +46 -0
- package/dist/analysis/cpc-optimizer.js.map +1 -0
- package/dist/analysis/performance-analyzer.d.ts +3 -0
- package/dist/analysis/performance-analyzer.d.ts.map +1 -0
- package/dist/analysis/performance-analyzer.js +58 -0
- package/dist/analysis/performance-analyzer.js.map +1 -0
- package/dist/analysis/sku-classifier.d.ts +3 -0
- package/dist/analysis/sku-classifier.d.ts.map +1 -0
- package/dist/analysis/sku-classifier.js +64 -0
- package/dist/analysis/sku-classifier.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +351 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/campaign-layer-policy.d.ts +28 -0
- package/dist/config/campaign-layer-policy.d.ts.map +1 -0
- package/dist/config/campaign-layer-policy.js +56 -0
- package/dist/config/campaign-layer-policy.js.map +1 -0
- package/dist/config/constants.d.ts +74 -0
- package/dist/config/constants.d.ts.map +1 -0
- package/dist/config/constants.js +61 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/optimisation-config.d.ts +24 -0
- package/dist/config/optimisation-config.d.ts.map +1 -0
- package/dist/config/optimisation-config.js +41 -0
- package/dist/config/optimisation-config.js.map +1 -0
- package/dist/core/header-mapper.d.ts +6 -0
- package/dist/core/header-mapper.d.ts.map +1 -0
- package/dist/core/header-mapper.js +34 -0
- package/dist/core/header-mapper.js.map +1 -0
- package/dist/core/normalizer.d.ts +9 -0
- package/dist/core/normalizer.d.ts.map +1 -0
- package/dist/core/normalizer.js +76 -0
- package/dist/core/normalizer.js.map +1 -0
- package/dist/io/csv-reader.d.ts +3 -0
- package/dist/io/csv-reader.d.ts.map +1 -0
- package/dist/io/csv-reader.js +52 -0
- package/dist/io/csv-reader.js.map +1 -0
- package/dist/io/excel-reader.d.ts +4 -0
- package/dist/io/excel-reader.d.ts.map +1 -0
- package/dist/io/excel-reader.js +83 -0
- package/dist/io/excel-reader.js.map +1 -0
- package/dist/io/excel-writer.d.ts +7 -0
- package/dist/io/excel-writer.d.ts.map +1 -0
- package/dist/io/excel-writer.js +42 -0
- package/dist/io/excel-writer.js.map +1 -0
- package/dist/pipeline/analyze-pipeline.d.ts +8 -0
- package/dist/pipeline/analyze-pipeline.d.ts.map +1 -0
- package/dist/pipeline/analyze-pipeline.js +145 -0
- package/dist/pipeline/analyze-pipeline.js.map +1 -0
- package/dist/pipeline/types.d.ts +142 -0
- package/dist/pipeline/types.d.ts.map +1 -0
- package/dist/pipeline/types.js +2 -0
- package/dist/pipeline/types.js.map +1 -0
- package/dist/ranking/keyword-matcher.d.ts +10 -0
- package/dist/ranking/keyword-matcher.d.ts.map +1 -0
- package/dist/ranking/keyword-matcher.js +62 -0
- package/dist/ranking/keyword-matcher.js.map +1 -0
- package/dist/ranking/ranking-db.d.ts +14 -0
- package/dist/ranking/ranking-db.d.ts.map +1 -0
- package/dist/ranking/ranking-db.js +97 -0
- package/dist/ranking/ranking-db.js.map +1 -0
- package/dist/ranking/seo-factor.d.ts +5 -0
- package/dist/ranking/seo-factor.d.ts.map +1 -0
- package/dist/ranking/seo-factor.js +57 -0
- package/dist/ranking/seo-factor.js.map +1 -0
- package/dist/ranking/types.d.ts +25 -0
- package/dist/ranking/types.d.ts.map +1 -0
- package/dist/ranking/types.js +2 -0
- package/dist/ranking/types.js.map +1 -0
- package/dist/schemas/types.d.ts +32 -0
- package/dist/schemas/types.d.ts.map +1 -0
- package/dist/schemas/types.js +2 -0
- package/dist/schemas/types.js.map +1 -0
- package/dist/utils/date-utils.d.ts +17 -0
- package/dist/utils/date-utils.d.ts.map +1 -0
- package/dist/utils/date-utils.js +56 -0
- package/dist/utils/date-utils.js.map +1 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +45 -0
- package/dist/utils/logger.js.map +1 -0
- 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
|
+
[](LICENSE)
|
|
4
|
+
[](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
|
+
[](LICENSE)
|
|
4
|
+
[](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 @@
|
|
|
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"}
|