marketing-data-hub 0.2.0__tar.gz

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 (68) hide show
  1. marketing_data_hub-0.2.0/.claude/settings.local.json +112 -0
  2. marketing_data_hub-0.2.0/.env.example +1 -0
  3. marketing_data_hub-0.2.0/.gitignore +22 -0
  4. marketing_data_hub-0.2.0/LICENSE +21 -0
  5. marketing_data_hub-0.2.0/PKG-INFO +196 -0
  6. marketing_data_hub-0.2.0/README.md +137 -0
  7. marketing_data_hub-0.2.0/SETUP.md +222 -0
  8. marketing_data_hub-0.2.0/config.yaml.example +46 -0
  9. marketing_data_hub-0.2.0/docs/superpowers/plans/2026-07-04-marketing-data-hub.md +2862 -0
  10. marketing_data_hub-0.2.0/docs/superpowers/specs/2026-07-04-marketing-data-hub-design.md +231 -0
  11. marketing_data_hub-0.2.0/pyproject.toml +54 -0
  12. marketing_data_hub-0.2.0/scripts/sync_daily.bat +17 -0
  13. marketing_data_hub-0.2.0/scripts/sync_daily.sh +17 -0
  14. marketing_data_hub-0.2.0/src/hub/__init__.py +0 -0
  15. marketing_data_hub-0.2.0/src/hub/api/__init__.py +0 -0
  16. marketing_data_hub-0.2.0/src/hub/api/app.py +124 -0
  17. marketing_data_hub-0.2.0/src/hub/cli.py +298 -0
  18. marketing_data_hub-0.2.0/src/hub/connectors/__init__.py +0 -0
  19. marketing_data_hub-0.2.0/src/hub/connectors/base.py +123 -0
  20. marketing_data_hub-0.2.0/src/hub/connectors/catalog.py +49 -0
  21. marketing_data_hub-0.2.0/src/hub/connectors/ga4.py +193 -0
  22. marketing_data_hub-0.2.0/src/hub/connectors/google_ads.py +113 -0
  23. marketing_data_hub-0.2.0/src/hub/connectors/google_auth.py +100 -0
  24. marketing_data_hub-0.2.0/src/hub/connectors/gsc.py +137 -0
  25. marketing_data_hub-0.2.0/src/hub/connectors/meta_ads.py +82 -0
  26. marketing_data_hub-0.2.0/src/hub/connectors/youtube.py +51 -0
  27. marketing_data_hub-0.2.0/src/hub/core/__init__.py +0 -0
  28. marketing_data_hub-0.2.0/src/hub/core/accounts.py +163 -0
  29. marketing_data_hub-0.2.0/src/hub/core/config.py +55 -0
  30. marketing_data_hub-0.2.0/src/hub/core/models.py +44 -0
  31. marketing_data_hub-0.2.0/src/hub/core/normalizer.py +23 -0
  32. marketing_data_hub-0.2.0/src/hub/core/presets.py +62 -0
  33. marketing_data_hub-0.2.0/src/hub/core/status.py +22 -0
  34. marketing_data_hub-0.2.0/src/hub/core/storage.py +223 -0
  35. marketing_data_hub-0.2.0/src/hub/core/sync.py +74 -0
  36. marketing_data_hub-0.2.0/src/hub/destinations/__init__.py +0 -0
  37. marketing_data_hub-0.2.0/src/hub/destinations/csv_export.py +26 -0
  38. marketing_data_hub-0.2.0/src/hub/mcp/__init__.py +0 -0
  39. marketing_data_hub-0.2.0/src/hub/mcp/server.py +541 -0
  40. marketing_data_hub-0.2.0/src/hub/scheduler/__init__.py +0 -0
  41. marketing_data_hub-0.2.0/src/hub/scheduler/runner.py +37 -0
  42. marketing_data_hub-0.2.0/src/hub/setup_wizard/__init__.py +9 -0
  43. marketing_data_hub-0.2.0/src/hub/setup_wizard/app.py +217 -0
  44. marketing_data_hub-0.2.0/src/hub/setup_wizard/page.py +261 -0
  45. marketing_data_hub-0.2.0/tests/__init__.py +0 -0
  46. marketing_data_hub-0.2.0/tests/test_accounts.py +90 -0
  47. marketing_data_hub-0.2.0/tests/test_api.py +120 -0
  48. marketing_data_hub-0.2.0/tests/test_base_connector.py +37 -0
  49. marketing_data_hub-0.2.0/tests/test_catalog.py +26 -0
  50. marketing_data_hub-0.2.0/tests/test_cli.py +56 -0
  51. marketing_data_hub-0.2.0/tests/test_config.py +79 -0
  52. marketing_data_hub-0.2.0/tests/test_csv_export.py +32 -0
  53. marketing_data_hub-0.2.0/tests/test_ga4.py +100 -0
  54. marketing_data_hub-0.2.0/tests/test_google_ads.py +57 -0
  55. marketing_data_hub-0.2.0/tests/test_google_auth.py +44 -0
  56. marketing_data_hub-0.2.0/tests/test_gsc.py +33 -0
  57. marketing_data_hub-0.2.0/tests/test_identities.py +110 -0
  58. marketing_data_hub-0.2.0/tests/test_mcp.py +255 -0
  59. marketing_data_hub-0.2.0/tests/test_meta_ads.py +52 -0
  60. marketing_data_hub-0.2.0/tests/test_normalizer.py +46 -0
  61. marketing_data_hub-0.2.0/tests/test_presets.py +81 -0
  62. marketing_data_hub-0.2.0/tests/test_reports.py +269 -0
  63. marketing_data_hub-0.2.0/tests/test_scaffold.py +3 -0
  64. marketing_data_hub-0.2.0/tests/test_scheduler.py +22 -0
  65. marketing_data_hub-0.2.0/tests/test_setup_wizard.py +120 -0
  66. marketing_data_hub-0.2.0/tests/test_storage.py +114 -0
  67. marketing_data_hub-0.2.0/tests/test_sync.py +93 -0
  68. marketing_data_hub-0.2.0/tests/test_youtube.py +31 -0
@@ -0,0 +1,112 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebSearch",
5
+ "PowerShell(git init)",
6
+ "PowerShell(git add docs)",
7
+ "PowerShell($scratch = \"C:\\\\Users\\\\LAPTOP~1\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\C--Users-Laptop-577-Documents-data-collection\\\\d49c770f-659c-44f3-8386-f8316346d030\\\\scratchpad\"; $plan = \"docs\\\\superpowers\\\\plans\\\\2026-07-04-marketing-data-hub.md\"; Add-Content -Path $plan -Value \\(Get-Content \"$scratch\\\\plan_part2.md\" -Raw\\) -Encoding utf8; Add-Content -Path $plan -Value \\(Get-Content \"$scratch\\\\plan_part3.md\" -Raw\\) -Encoding utf8; Add-Content -Path $plan -Value \\(Get-Content \"$scratch\\\\plan_part4.md\" -Raw\\) -Encoding utf8; \\(Get-Content $plan | Measure-Object -Line\\).Lines)",
8
+ "PowerShell(git checkout -b feature/marketing-data-hub)",
9
+ "Bash(python -m pip install -e \".[dev]\")",
10
+ "Bash(python -m pip show uvicorn pyyaml typer httpx google-api-python-client google-auth-oauthlib)",
11
+ "Bash(python -m pytest tests/test_scaffold.py -v)",
12
+ "Bash(git add *)",
13
+ "Bash(git commit *)",
14
+ "Bash(python -m pip show marketing-data-hub)",
15
+ "Bash(git ls-tree *)",
16
+ "Bash(python -c \"import sys; print\\(sys.version\\)\")",
17
+ "Bash(\"/c/Users/Laptop-577/AppData/Local/Temp/hub_scaffold_venv/Scripts/python.exe\" -m pip install -e \".[dev]\" --quiet)",
18
+ "Bash(ls -la \"/c/Users/Laptop-577/AppData/Local/Temp/hub_scaffold_venv/Lib/site-packages/\" 2>&1 | grep -iE \"hub|duckdb|fastapi\" | head -20)",
19
+ "Bash(\"/c/Users/Laptop-577/AppData/Local/Temp/hub_scaffold_venv/Scripts/python.exe\" -m pip list)",
20
+ "Bash(\"/c/Users/Laptop-577/AppData/Local/Temp/hub_scaffold_venv/Scripts/python.exe\" -m pytest tests/test_scaffold.py -v)",
21
+ "Bash(\"/c/Users/Laptop-577/AppData/Local/Temp/hub_scaffold_venv/Scripts/hub.exe\")",
22
+ "Bash(\"/c/Users/Laptop-577/AppData/Local/Temp/hub_scaffold_venv/Scripts/python.exe\" -m pip install -e \".[dev]\" -v)",
23
+ "Bash(\"/c/Users/Laptop-577/AppData/Local/Temp/hub_scaffold_venv/Scripts/python.exe\" -c \"import hub; print\\(hub.__file__\\)\")",
24
+ "Bash(xxd .gitignore)",
25
+ "Bash(xxd pyproject.toml)",
26
+ "Bash(python -m pytest tests/test_normalizer.py -v)",
27
+ "Bash(python -m pytest -v)",
28
+ "Bash(python -c \"import pydantic; print\\(pydantic.VERSION\\)\")",
29
+ "Bash(python -c ' *)",
30
+ "Bash(find src *)",
31
+ "Bash(ls src/hub)",
32
+ "Bash(ls src/hub/core)",
33
+ "Bash(python -m pytest tests/test_config.py -v)",
34
+ "Bash(python -m pytest -q)",
35
+ "Bash(python -c \"import duckdb; print\\(duckdb.__version__\\)\")",
36
+ "Bash(python -m pytest tests/test_storage.py -v)",
37
+ "Bash(ls docs *)",
38
+ "Read(//tmp/**)",
39
+ "Bash(pip show *)",
40
+ "Bash(pip index *)",
41
+ "Bash(python -m pytest tests/test_base_connector.py -v)",
42
+ "Bash(git show *)",
43
+ "Bash(python -c \"import google.auth, google.oauth2.credentials, google_auth_oauthlib.flow; print\\('ok'\\)\")",
44
+ "Bash(python -m pytest tests/test_google_auth.py -v)",
45
+ "Bash(python -c \"import google.auth; print\\(google.auth.__version__\\)\")",
46
+ "Bash(python -m pytest tests/test_ga4.py -v)",
47
+ "Bash(python -c \"import google.analytics.data_v1beta\")",
48
+ "Bash(python -m pip show google-analytics-data)",
49
+ "Bash(python -m pytest tests/test_gsc.py -v)",
50
+ "Bash(python -m pytest tests/test_youtube.py -v)",
51
+ "Skill(update-config)",
52
+ "Skill(update-config:*)",
53
+ "Bash(git *)",
54
+ "Bash(python *)",
55
+ "Bash(pytest *)",
56
+ "Bash(pip *)",
57
+ "Bash(hub *)",
58
+ "Bash(ls *)",
59
+ "Bash(find *)",
60
+ "Bash(cat *)",
61
+ "Bash(head *)",
62
+ "Bash(tail *)",
63
+ "PowerShell(git *)",
64
+ "PowerShell(python *)",
65
+ "PowerShell(pytest *)",
66
+ "PowerShell(pip *)",
67
+ "PowerShell(hub *)",
68
+ "PowerShell(try { $j = Get-Content .claude/settings.local.json -Raw | ConvertFrom-Json; echo \\(\"JSON OK - allow rules: \" + $j.permissions.allow.Count\\) } catch { echo \\(\"JSON INVALID: \" + $_.Exception.Message\\) })",
69
+ "Bash(python -m pytest tests/test_catalog.py -v)",
70
+ "Bash(python -c \"import hub.connectors.catalog; print\\('catalog import OK'\\)\")",
71
+ "Bash(python -c \"from hub.connectors.catalog import connector_class; connector_class\\('google_ads'\\)\")",
72
+ "Bash(python -m pytest tests/test_sync.py -v)",
73
+ "Skill(code-review)",
74
+ "Skill(code-review:*)",
75
+ "Bash(timeout 60 bash -c 'while true; do sleep 5; done')",
76
+ "Bash(find . -iname \"*cli*\" -not -path \"*/node_modules/*\" -not -path \"*/.git/*\")",
77
+ "Bash(timeout 45 bash -c 'while true; do sleep 5; done')",
78
+ "Bash(python3 -c ' *)",
79
+ "Bash(git commit -m ' *)",
80
+ "Bash(python -c \"import fastapi, httpx; print\\(fastapi.__version__, httpx.__version__\\)\")",
81
+ "Bash(python -m pytest tests/test_api.py -v)",
82
+ "Bash(python \"C:\\\\Users\\\\LAPTOP~1\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\C--Users-Laptop-577-Documents-data-collection\\\\d49c770f-659c-44f3-8386-f8316346d030\\\\scratchpad\\\\probe_api.py\")",
83
+ "Bash(mkdir -p \"/c/Users/LAPTOP~1/AppData/Local/Temp/claude/C--Users-Laptop-577-Documents-data-collection/d49c770f-659c-44f3-8386-f8316346d030/scratchpad\")",
84
+ "Bash(python \"C:\\\\Users\\\\LAPTOP~1\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\C--Users-Laptop-577-Documents-data-collection\\\\d49c770f-659c-44f3-8386-f8316346d030\\\\scratchpad\\\\stress_storage.py\")",
85
+ "Bash(python -m pytest tests/test_csv_export.py -v)",
86
+ "Bash(python -c \"import typer; print\\(typer.__version__\\)\")",
87
+ "Bash(python -m pytest tests/test_cli.py -v)",
88
+ "Bash(python -c \"from hub.cli import app; import typer; print\\('ok, app is', type\\(app\\)\\)\")",
89
+ "Bash(python -m hub.cli --help)",
90
+ "Bash(chcp)",
91
+ "Bash(python -X utf8 -m hub.cli --help)",
92
+ "Bash(PYTHONIOENCODING=utf-8 python -m hub.cli --help)",
93
+ "Bash(xargs grep -l \"class.*Connector\")",
94
+ "Bash(python -m pytest tests/test_scheduler.py -v)",
95
+ "Bash(python -c \"import fastmcp; print\\(fastmcp.__version__\\)\")",
96
+ "Bash(python -m pytest tests/test_mcp.py -v)",
97
+ "Bash(python -m pytest tests/test_google_ads.py -v)",
98
+ "Bash(python -c \"from hub.connectors.catalog import connector_class; print\\(connector_class\\('google_ads'\\)\\)\")",
99
+ "Bash(python -c \"import google.ads\")",
100
+ "Bash(python -m pytest tests/test_meta_ads.py -v)",
101
+ "Bash(ls *.md)",
102
+ "Bash(find . -maxdepth 2 -iname \"*plan*\")",
103
+ "Bash(python -m pytest --collect-only -q)",
104
+ "Bash(git stash *)",
105
+ "PowerShell($dir = \"C:\\\\Users\\\\Laptop-577\\\\Documents\\\\data collection\\\\secrets\"; if \\(-not \\(Test-Path $dir\\)\\) { New-Item -ItemType Directory -Path $dir | Out-Null; echo \"created $dir\" } else { echo \"$dir already exists\" })",
106
+ "Bash(python \"/c/Users/LAPTOP~1/AppData/Local/Temp/claude/C--Users-Laptop-577-Documents-data-collection/d49c770f-659c-44f3-8386-f8316346d030/scratchpad/discover_accounts.py\")",
107
+ "Bash(kill %1)",
108
+ "Bash(pkill -f discover_accounts.py)",
109
+ "Bash(cp \"/c/Users/Laptop-577/Downloads/client_secret_178030722856-4mvmk7q3i6u5r438iijstdv8c40mhsp4.apps.googleusercontent.com.json\" \"/c/Users/Laptop-577/Documents/data collection/secrets/google_client.json\" *)"
110
+ ]
111
+ }
112
+ }
@@ -0,0 +1 @@
1
+ HUB_API_KEY=change-me-to-a-long-random-string
@@ -0,0 +1,22 @@
1
+ # --- secrets & credentials (NEVER commit or share) ---
2
+ .env
3
+ secrets/
4
+ *.json.key
5
+ google_token.json
6
+ google_client.json
7
+
8
+ # --- personal config & data (each user has their own) ---
9
+ config.yaml
10
+ data/
11
+ exports/
12
+ logs/
13
+ *.duckdb
14
+ *.duckdb.wal
15
+
16
+ # --- python ---
17
+ __pycache__/
18
+ *.egg-info/
19
+ .venv/
20
+ venv/
21
+ .pytest_cache/
22
+ dist/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bhargava
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.
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: marketing-data-hub
3
+ Version: 0.2.0
4
+ Summary: Open-source Windsor.ai alternative: GA4, Search Console, Google Ads & Meta Ads into one local database - queryable by API, CSV, and AI assistants (MCP)
5
+ Project-URL: Homepage, https://github.com/rallabandibhargava-dev/marketing-data-hub
6
+ Project-URL: Issues, https://github.com/rallabandibhargava-dev/marketing-data-hub/issues
7
+ Author: Bhargava
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Bhargava
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: analytics,duckdb,ga4,marketing,mcp,search-console,windsor
31
+ Classifier: Development Status :: 4 - Beta
32
+ Classifier: Intended Audience :: Other Audience
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3.11
35
+ Classifier: Programming Language :: Python :: 3.12
36
+ Classifier: Topic :: Office/Business
37
+ Requires-Python: >=3.11
38
+ Requires-Dist: apscheduler<4,>=3.10
39
+ Requires-Dist: duckdb>=1.0
40
+ Requires-Dist: fastapi>=0.110
41
+ Requires-Dist: fastmcp>=2.0
42
+ Requires-Dist: google-analytics-data>=0.18
43
+ Requires-Dist: google-api-python-client>=2.120
44
+ Requires-Dist: google-auth-oauthlib>=1.2
45
+ Requires-Dist: pydantic>=2.6
46
+ Requires-Dist: python-dotenv>=1.0
47
+ Requires-Dist: pyyaml>=6.0
48
+ Requires-Dist: ruamel-yaml>=0.18
49
+ Requires-Dist: typer>=0.12
50
+ Requires-Dist: uvicorn>=0.29
51
+ Provides-Extra: dev
52
+ Requires-Dist: httpx>=0.27; extra == 'dev'
53
+ Requires-Dist: pytest>=8.0; extra == 'dev'
54
+ Provides-Extra: googleads
55
+ Requires-Dist: google-ads>=24.0; extra == 'googleads'
56
+ Provides-Extra: meta
57
+ Requires-Dist: facebook-business>=19.0; extra == 'meta'
58
+ Description-Content-Type: text/markdown
59
+
60
+ # Marketing Data Hub
61
+
62
+ **Your marketing data, on your machine, free.** An open-source Windsor.ai
63
+ alternative: pulls Google Analytics 4, Search Console (plus Google Ads, Meta
64
+ Ads, YouTube) into one local database — queryable via a REST API, scheduled
65
+ CSV exports, and by AI assistants like Claude (MCP). No hosted service, no
66
+ subscription, your tokens never leave your computer.
67
+
68
+ ## Quick start (no config editing needed)
69
+
70
+ ```bash
71
+ git clone https://github.com/rallabandibhargava-dev/marketing-data-hub
72
+ cd marketing-data-hub
73
+ python -m pip install -e .
74
+ hub setup
75
+ ```
76
+
77
+ `hub setup` opens a page in your browser where you:
78
+
79
+ 1. **Connect Google** — sign in, done (multiple Google accounts supported)
80
+ 2. **Tick the GA4 properties / Search Console sites** you want
81
+ 3. Optionally paste Google Ads / Meta Ads tokens
82
+ 4. **Run the first sync** and watch it load
83
+ 5. **Copy the Claude snippet** to ask questions in plain English
84
+
85
+ One prerequisite: a Google OAuth client file at `secrets/google_client.json`
86
+ (one-time, ~5 minutes — see [SETUP.md](SETUP.md) step 2; teams share one file).
87
+
88
+ Then ask Claude things like *"How did organic traffic do in June vs May?"* or
89
+ *"Top non-branded search queries this month?"* — or automate a daily 6am sync
90
+ (SETUP.md, step 8).
91
+
92
+ ## Reports (analysis shapes)
93
+
94
+ Each source syncs several named *reports* — different dimensional shapes of the
95
+ same data, stored side by side and never mixed (mixing granularities would
96
+ double-count):
97
+
98
+ | Source | Report | Answers |
99
+ |---|---|---|
100
+ | ga4 | `core` | daily campaign totals (sessions, users, conversions, revenue) |
101
+ | ga4 | `channels` | traffic mix: organic vs paid vs direct, engagement, pageviews |
102
+ | ga4 | `landing_pages` | entry-page performance per channel |
103
+ | ga4 | `pages` | page behaviour: views, engagement time, events per path |
104
+ | ga4 | `audience` | device × country segmentation |
105
+ | ga4 | `visitors` | new vs returning (cohort-lite) |
106
+ | gsc | `core` | exact daily search totals per site |
107
+ | gsc | `queries` | per-query performance (branded split = string-match) |
108
+ | gsc | `pages` | per-URL search performance |
109
+ | gsc | `devices` / `countries` | mobile/desktop and geo splits |
110
+ | ga4 | `events` | per-event counts by name (brand-specific: form_submit, call_click...) |
111
+
112
+ Pass `report=<name>` to the API/MCP `query_metrics`; default is `core`.
113
+ MCP `query_metrics` also supports `compare=` (prev_period / prev_day / prev_week /
114
+ prev_month / prev_year — returns value, previous, and %-change per metric for any
115
+ date range) and `filters=` (exact match on any dimension incl. report extras,
116
+ e.g. `{"event": "form_submit"}` or `{"device": "MOBILE"}`).
117
+ Rates are computed, not stored: engagement rate = engaged_sessions/sessions,
118
+ ctr = clicks/impressions, avg engagement time = engagement_seconds/pageviews.
119
+ GSC breakdown reports undercount totals slightly (Google anonymises rare
120
+ queries) — use `core` for toplines. True user-level cohorts need the GA4
121
+ BigQuery export; `visitors` + the live tools cover cohort-lite analysis.
122
+
123
+ For anything the synced reports don't cover, the MCP tools `query_ga4_live`
124
+ and `query_gsc_live` pass arbitrary dimension/metric combinations straight to
125
+ the APIs on demand.
126
+
127
+ ## Setup
128
+
129
+ **New here / installing on another machine? Follow [SETUP.md](SETUP.md)** — a
130
+ step-by-step guide including the Google Cloud OAuth setup. Quick version:
131
+
132
+ 1. `python -m pip install -e ".[dev]"`
133
+ 2. Copy `config.yaml.example` → `config.yaml`; fill in your GA4 `property_id`
134
+ and Search Console `site_url`. Have multiple GA4 properties or Search Console
135
+ sites under the same Google login? Use `property_ids: [...]` / `site_urls: [...]`
136
+ instead — all of them sync, and every row is tagged with its own `account_id`
137
+ so they stay distinguishable downstream.
138
+ 3. Copy `.env.example` → `.env`; set a random `HUB_API_KEY`.
139
+ 4. Google Cloud Console → create a project → enable **Google Analytics Data API**,
140
+ **Google Analytics Admin API**, **Search Console API**, **YouTube Analytics
141
+ API** → create an **OAuth client (Desktop app)** → download JSON to
142
+ `secrets/google_client.json`. (See [SETUP.md](SETUP.md) for the OAuth
143
+ consent-screen steps and the 7-day token-expiry gotcha.)
144
+ 5. `hub doctor` — first run opens a browser to authorize; then all checks go green.
145
+ 6. `hub accounts --add` — pick which GA4 properties / GSC sites to sync from
146
+ everything your Google login can see.
147
+
148
+ ## Daily use
149
+
150
+ | Command | What it does |
151
+ |---|---|
152
+ | `hub sync all` | sync every configured source (rolling 30-day window) |
153
+ | `hub backfill ga4 --from 2024-01-01` | load history in 90-day chunks |
154
+ | `hub status` | row counts + last sync per source |
155
+ | `hub serve` | query API on 127.0.0.1:8000 + cron scheduler |
156
+ | `hub export all` | write configured CSVs to exports/ |
157
+ | `hub mcp` | MCP server (stdio) for Claude |
158
+
159
+ ## Query API
160
+
161
+ ```
162
+ GET /connectors/all/data?fields=date,source,clicks,spend&date_preset=last_30d
163
+ X-API-Key: <HUB_API_KEY>
164
+ ```
165
+ `format=csv` for CSV, `report=<name>` for a breakdown report. `/connectors`
166
+ lists sources; `/connectors/{source}/reports` lists report shapes;
167
+ `/connectors/{source}/fields?report=<name>` lists fields.
168
+
169
+ ## Claude MCP
170
+
171
+ `claude mcp add marketing-hub -- python -m hub.cli mcp --config <absolute-path>/config.yaml`
172
+ Then ask Claude: "How did my campaigns do last week?"
173
+
174
+ Note: use an absolute path for --config; the MCP process may be launched from a
175
+ different working directory.
176
+
177
+ `trigger_sync` starts the sync in the background and returns immediately
178
+ (output goes to `logs/mcp_sync.log`); poll `sync_status` to see when it
179
+ finishes. While a sync holds the write lock, query tools return a readable
180
+ "database is busy" error instead of hanging.
181
+
182
+ ## Activating the ad connectors
183
+
184
+ - **Google Ads:** apply for a developer token (API Center), then uncomment
185
+ `google_ads` in config.yaml and fill options.
186
+ - **Meta Ads:** create a Meta app, generate a long-lived token with `ads_read`,
187
+ uncomment `meta_ads` and fill options.
188
+
189
+ ## Known limitations
190
+
191
+ - DuckDB allows one writer: run `hub mcp` OR `hub serve`, not both at once
192
+ (trigger_sync from MCP spawns the CLI, which needs the write lock free).
193
+ While any sync runs, MCP query tools report "database is busy" until it
194
+ finishes (~3 min for `sync all`).
195
+ - Extras fields (e.g. position, ctr, views) are returned as strings by the query
196
+ API — cast numerically as needed.
@@ -0,0 +1,137 @@
1
+ # Marketing Data Hub
2
+
3
+ **Your marketing data, on your machine, free.** An open-source Windsor.ai
4
+ alternative: pulls Google Analytics 4, Search Console (plus Google Ads, Meta
5
+ Ads, YouTube) into one local database — queryable via a REST API, scheduled
6
+ CSV exports, and by AI assistants like Claude (MCP). No hosted service, no
7
+ subscription, your tokens never leave your computer.
8
+
9
+ ## Quick start (no config editing needed)
10
+
11
+ ```bash
12
+ git clone https://github.com/rallabandibhargava-dev/marketing-data-hub
13
+ cd marketing-data-hub
14
+ python -m pip install -e .
15
+ hub setup
16
+ ```
17
+
18
+ `hub setup` opens a page in your browser where you:
19
+
20
+ 1. **Connect Google** — sign in, done (multiple Google accounts supported)
21
+ 2. **Tick the GA4 properties / Search Console sites** you want
22
+ 3. Optionally paste Google Ads / Meta Ads tokens
23
+ 4. **Run the first sync** and watch it load
24
+ 5. **Copy the Claude snippet** to ask questions in plain English
25
+
26
+ One prerequisite: a Google OAuth client file at `secrets/google_client.json`
27
+ (one-time, ~5 minutes — see [SETUP.md](SETUP.md) step 2; teams share one file).
28
+
29
+ Then ask Claude things like *"How did organic traffic do in June vs May?"* or
30
+ *"Top non-branded search queries this month?"* — or automate a daily 6am sync
31
+ (SETUP.md, step 8).
32
+
33
+ ## Reports (analysis shapes)
34
+
35
+ Each source syncs several named *reports* — different dimensional shapes of the
36
+ same data, stored side by side and never mixed (mixing granularities would
37
+ double-count):
38
+
39
+ | Source | Report | Answers |
40
+ |---|---|---|
41
+ | ga4 | `core` | daily campaign totals (sessions, users, conversions, revenue) |
42
+ | ga4 | `channels` | traffic mix: organic vs paid vs direct, engagement, pageviews |
43
+ | ga4 | `landing_pages` | entry-page performance per channel |
44
+ | ga4 | `pages` | page behaviour: views, engagement time, events per path |
45
+ | ga4 | `audience` | device × country segmentation |
46
+ | ga4 | `visitors` | new vs returning (cohort-lite) |
47
+ | gsc | `core` | exact daily search totals per site |
48
+ | gsc | `queries` | per-query performance (branded split = string-match) |
49
+ | gsc | `pages` | per-URL search performance |
50
+ | gsc | `devices` / `countries` | mobile/desktop and geo splits |
51
+ | ga4 | `events` | per-event counts by name (brand-specific: form_submit, call_click...) |
52
+
53
+ Pass `report=<name>` to the API/MCP `query_metrics`; default is `core`.
54
+ MCP `query_metrics` also supports `compare=` (prev_period / prev_day / prev_week /
55
+ prev_month / prev_year — returns value, previous, and %-change per metric for any
56
+ date range) and `filters=` (exact match on any dimension incl. report extras,
57
+ e.g. `{"event": "form_submit"}` or `{"device": "MOBILE"}`).
58
+ Rates are computed, not stored: engagement rate = engaged_sessions/sessions,
59
+ ctr = clicks/impressions, avg engagement time = engagement_seconds/pageviews.
60
+ GSC breakdown reports undercount totals slightly (Google anonymises rare
61
+ queries) — use `core` for toplines. True user-level cohorts need the GA4
62
+ BigQuery export; `visitors` + the live tools cover cohort-lite analysis.
63
+
64
+ For anything the synced reports don't cover, the MCP tools `query_ga4_live`
65
+ and `query_gsc_live` pass arbitrary dimension/metric combinations straight to
66
+ the APIs on demand.
67
+
68
+ ## Setup
69
+
70
+ **New here / installing on another machine? Follow [SETUP.md](SETUP.md)** — a
71
+ step-by-step guide including the Google Cloud OAuth setup. Quick version:
72
+
73
+ 1. `python -m pip install -e ".[dev]"`
74
+ 2. Copy `config.yaml.example` → `config.yaml`; fill in your GA4 `property_id`
75
+ and Search Console `site_url`. Have multiple GA4 properties or Search Console
76
+ sites under the same Google login? Use `property_ids: [...]` / `site_urls: [...]`
77
+ instead — all of them sync, and every row is tagged with its own `account_id`
78
+ so they stay distinguishable downstream.
79
+ 3. Copy `.env.example` → `.env`; set a random `HUB_API_KEY`.
80
+ 4. Google Cloud Console → create a project → enable **Google Analytics Data API**,
81
+ **Google Analytics Admin API**, **Search Console API**, **YouTube Analytics
82
+ API** → create an **OAuth client (Desktop app)** → download JSON to
83
+ `secrets/google_client.json`. (See [SETUP.md](SETUP.md) for the OAuth
84
+ consent-screen steps and the 7-day token-expiry gotcha.)
85
+ 5. `hub doctor` — first run opens a browser to authorize; then all checks go green.
86
+ 6. `hub accounts --add` — pick which GA4 properties / GSC sites to sync from
87
+ everything your Google login can see.
88
+
89
+ ## Daily use
90
+
91
+ | Command | What it does |
92
+ |---|---|
93
+ | `hub sync all` | sync every configured source (rolling 30-day window) |
94
+ | `hub backfill ga4 --from 2024-01-01` | load history in 90-day chunks |
95
+ | `hub status` | row counts + last sync per source |
96
+ | `hub serve` | query API on 127.0.0.1:8000 + cron scheduler |
97
+ | `hub export all` | write configured CSVs to exports/ |
98
+ | `hub mcp` | MCP server (stdio) for Claude |
99
+
100
+ ## Query API
101
+
102
+ ```
103
+ GET /connectors/all/data?fields=date,source,clicks,spend&date_preset=last_30d
104
+ X-API-Key: <HUB_API_KEY>
105
+ ```
106
+ `format=csv` for CSV, `report=<name>` for a breakdown report. `/connectors`
107
+ lists sources; `/connectors/{source}/reports` lists report shapes;
108
+ `/connectors/{source}/fields?report=<name>` lists fields.
109
+
110
+ ## Claude MCP
111
+
112
+ `claude mcp add marketing-hub -- python -m hub.cli mcp --config <absolute-path>/config.yaml`
113
+ Then ask Claude: "How did my campaigns do last week?"
114
+
115
+ Note: use an absolute path for --config; the MCP process may be launched from a
116
+ different working directory.
117
+
118
+ `trigger_sync` starts the sync in the background and returns immediately
119
+ (output goes to `logs/mcp_sync.log`); poll `sync_status` to see when it
120
+ finishes. While a sync holds the write lock, query tools return a readable
121
+ "database is busy" error instead of hanging.
122
+
123
+ ## Activating the ad connectors
124
+
125
+ - **Google Ads:** apply for a developer token (API Center), then uncomment
126
+ `google_ads` in config.yaml and fill options.
127
+ - **Meta Ads:** create a Meta app, generate a long-lived token with `ads_read`,
128
+ uncomment `meta_ads` and fill options.
129
+
130
+ ## Known limitations
131
+
132
+ - DuckDB allows one writer: run `hub mcp` OR `hub serve`, not both at once
133
+ (trigger_sync from MCP spawns the CLI, which needs the write lock free).
134
+ While any sync runs, MCP query tools report "database is busy" until it
135
+ finishes (~3 min for `sync all`).
136
+ - Extras fields (e.g. position, ctr, views) are returned as strings by the query
137
+ API — cast numerically as needed.