aa-auto-sdr 1.21.7__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 (111) hide show
  1. aa_auto_sdr-1.21.7/.gitignore +50 -0
  2. aa_auto_sdr-1.21.7/LICENSE +21 -0
  3. aa_auto_sdr-1.21.7/PKG-INFO +541 -0
  4. aa_auto_sdr-1.21.7/README.md +505 -0
  5. aa_auto_sdr-1.21.7/pyproject.toml +128 -0
  6. aa_auto_sdr-1.21.7/sample_outputs/README.md +38 -0
  7. aa_auto_sdr-1.21.7/src/aa_auto_sdr/__init__.py +13 -0
  8. aa_auto_sdr-1.21.7/src/aa_auto_sdr/__main__.py +154 -0
  9. aa_auto_sdr-1.21.7/src/aa_auto_sdr/api/__init__.py +1 -0
  10. aa_auto_sdr-1.21.7/src/aa_auto_sdr/api/auth.py +17 -0
  11. aa_auto_sdr-1.21.7/src/aa_auto_sdr/api/cache.py +107 -0
  12. aa_auto_sdr-1.21.7/src/aa_auto_sdr/api/client.py +126 -0
  13. aa_auto_sdr-1.21.7/src/aa_auto_sdr/api/fetch.py +922 -0
  14. aa_auto_sdr-1.21.7/src/aa_auto_sdr/api/models.py +193 -0
  15. aa_auto_sdr-1.21.7/src/aa_auto_sdr/api/resilience.py +231 -0
  16. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/__init__.py +1 -0
  17. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/_filters.py +50 -0
  18. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/agent_output.py +119 -0
  19. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/__init__.py +1 -0
  20. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/_shared.py +30 -0
  21. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/batch.py +748 -0
  22. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/compare_with_prev.py +99 -0
  23. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/completion.py +94 -0
  24. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/config.py +182 -0
  25. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/diff.py +192 -0
  26. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/discovery.py +217 -0
  27. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/exit_codes.py +36 -0
  28. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/generate.py +807 -0
  29. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/inspect.py +464 -0
  30. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/interactive.py +103 -0
  31. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/inventory.py +246 -0
  32. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/notion_create.py +89 -0
  33. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/notion_prune.py +57 -0
  34. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/notion_repair.py +89 -0
  35. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/notion_schema.py +16 -0
  36. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/profiles.py +201 -0
  37. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/push_to_notion.py +214 -0
  38. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/snapshots.py +172 -0
  39. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/stats.py +186 -0
  40. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/trending.py +183 -0
  41. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/commands/watch.py +372 -0
  42. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/list_output.py +198 -0
  43. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/main.py +1216 -0
  44. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/option_resolution.py +48 -0
  45. aa_auto_sdr-1.21.7/src/aa_auto_sdr/cli/parser.py +865 -0
  46. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/__init__.py +1 -0
  47. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/_confirm.py +19 -0
  48. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/_open.py +28 -0
  49. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/colors.py +66 -0
  50. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/constants.py +5 -0
  51. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/credentials.py +244 -0
  52. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/exceptions.py +84 -0
  53. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/exit_codes.py +192 -0
  54. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/json_io.py +33 -0
  55. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/logging.py +403 -0
  56. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/profiles.py +53 -0
  57. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/run_summary.py +50 -0
  58. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/timings.py +70 -0
  59. aa_auto_sdr-1.21.7/src/aa_auto_sdr/core/version.py +3 -0
  60. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/__init__.py +1 -0
  61. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/_helpers.py +59 -0
  62. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/_template_anchors.py +71 -0
  63. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/diff_renderers/__init__.py +0 -0
  64. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/diff_renderers/_filters.py +44 -0
  65. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/diff_renderers/console.py +164 -0
  66. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/diff_renderers/json.py +29 -0
  67. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/diff_renderers/markdown.py +145 -0
  68. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/diff_renderers/pr_comment.py +161 -0
  69. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/error_envelope.py +47 -0
  70. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/notion_blocks.py +268 -0
  71. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/notion_client_guard.py +116 -0
  72. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/notion_database.py +448 -0
  73. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/notion_prune.py +75 -0
  74. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/notion_registry.py +101 -0
  75. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/protocols.py +23 -0
  76. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/registry.py +68 -0
  77. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/trending_renderers/__init__.py +1 -0
  78. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/trending_renderers/console.py +84 -0
  79. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/trending_renderers/json.py +40 -0
  80. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/trending_renderers/markdown.py +59 -0
  81. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/watch_event.py +29 -0
  82. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/writers/__init__.py +1 -0
  83. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/writers/csv.py +161 -0
  84. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/writers/excel.py +102 -0
  85. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/writers/excel_template.py +434 -0
  86. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/writers/html.py +155 -0
  87. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/writers/json.py +39 -0
  88. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/writers/markdown.py +124 -0
  89. aa_auto_sdr-1.21.7/src/aa_auto_sdr/output/writers/notion.py +217 -0
  90. aa_auto_sdr-1.21.7/src/aa_auto_sdr/pipeline/__init__.py +1 -0
  91. aa_auto_sdr-1.21.7/src/aa_auto_sdr/pipeline/batch.py +400 -0
  92. aa_auto_sdr-1.21.7/src/aa_auto_sdr/pipeline/models.py +55 -0
  93. aa_auto_sdr-1.21.7/src/aa_auto_sdr/pipeline/sampling.py +89 -0
  94. aa_auto_sdr-1.21.7/src/aa_auto_sdr/pipeline/single.py +164 -0
  95. aa_auto_sdr-1.21.7/src/aa_auto_sdr/pipeline/watch.py +520 -0
  96. aa_auto_sdr-1.21.7/src/aa_auto_sdr/pipeline/workers.py +602 -0
  97. aa_auto_sdr-1.21.7/src/aa_auto_sdr/sdr/__init__.py +1 -0
  98. aa_auto_sdr-1.21.7/src/aa_auto_sdr/sdr/builder.py +180 -0
  99. aa_auto_sdr-1.21.7/src/aa_auto_sdr/sdr/document.py +68 -0
  100. aa_auto_sdr-1.21.7/src/aa_auto_sdr/sdr/quality.py +429 -0
  101. aa_auto_sdr-1.21.7/src/aa_auto_sdr/sdr/quality_policy.py +171 -0
  102. aa_auto_sdr-1.21.7/src/aa_auto_sdr/snapshot/__init__.py +0 -0
  103. aa_auto_sdr-1.21.7/src/aa_auto_sdr/snapshot/_duration.py +35 -0
  104. aa_auto_sdr-1.21.7/src/aa_auto_sdr/snapshot/comparator.py +250 -0
  105. aa_auto_sdr-1.21.7/src/aa_auto_sdr/snapshot/git.py +450 -0
  106. aa_auto_sdr-1.21.7/src/aa_auto_sdr/snapshot/models.py +71 -0
  107. aa_auto_sdr-1.21.7/src/aa_auto_sdr/snapshot/resolver.py +164 -0
  108. aa_auto_sdr-1.21.7/src/aa_auto_sdr/snapshot/retention.py +115 -0
  109. aa_auto_sdr-1.21.7/src/aa_auto_sdr/snapshot/schema.py +170 -0
  110. aa_auto_sdr-1.21.7/src/aa_auto_sdr/snapshot/store.py +234 -0
  111. aa_auto_sdr-1.21.7/src/aa_auto_sdr/snapshot/trending.py +256 -0
@@ -0,0 +1,50 @@
1
+ # Local-only working areas
2
+ .claude/
3
+ .worktrees/
4
+ docs/superpowers/
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+ *.so
11
+ .Python
12
+ build/
13
+ dist/
14
+ *.egg-info/
15
+ .eggs/
16
+ *.egg
17
+
18
+ # Virtual envs
19
+ .venv/
20
+ venv/
21
+ env/
22
+
23
+ # Test / coverage
24
+ .pytest_cache/
25
+ .coverage
26
+ .coverage.*
27
+ htmlcov/
28
+ .tox/
29
+
30
+ # Editor
31
+ .vscode/
32
+ .idea/
33
+ *.swp
34
+ .DS_Store
35
+
36
+ # Project secrets — never commit
37
+ .env*
38
+ !.env.example
39
+ config.json
40
+
41
+ # Build artifacts
42
+ *.xlsx
43
+ *.csv
44
+ !tests/fixtures/**/*.csv
45
+ !tests/fixtures/**/*.xlsx
46
+ !sample_outputs/**/*.csv
47
+ !sample_outputs/**/*.xlsx
48
+
49
+ # v1.3.0 — per-run log files written by core/logging.py
50
+ logs/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brian Au
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,541 @@
1
+ Metadata-Version: 2.4
2
+ Name: aa-auto-sdr
3
+ Version: 1.21.7
4
+ Summary: Adobe Analytics SDR generator with snapshot/diff. Read-only. API 2.0 only.
5
+ Project-URL: Homepage, https://github.com/brian-a-au/aa_auto_sdr
6
+ Project-URL: Repository, https://github.com/brian-a-au/aa_auto_sdr
7
+ Project-URL: Issues, https://github.com/brian-a-au/aa_auto_sdr/issues
8
+ Project-URL: Changelog, https://github.com/brian-a-au/aa_auto_sdr/blob/main/CHANGELOG.md
9
+ Author: Brian Au
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: adobe-analytics,cli,diff,documentation,sdr,snapshot,solution-design-reference
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Information Technology
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Office/Business
22
+ Classifier: Topic :: Software Development :: Documentation
23
+ Requires-Python: >=3.14
24
+ Requires-Dist: aanalytics2>=0.4.0
25
+ Requires-Dist: openpyxl>=3.1.0
26
+ Requires-Dist: pandas<3,>=2.3.3
27
+ Requires-Dist: requests>=2.0
28
+ Requires-Dist: xlsxwriter>=3.2.9
29
+ Provides-Extra: completion
30
+ Requires-Dist: argcomplete>=3.0.0; extra == 'completion'
31
+ Provides-Extra: env
32
+ Requires-Dist: python-dotenv>=1.0.0; extra == 'env'
33
+ Provides-Extra: notion
34
+ Requires-Dist: notion-client>=3.0.0; extra == 'notion'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # Adobe Analytics Solution Design Reference Generator
38
+
39
+ <img width="2750" height="1536" alt="Gemini_Generated_Image_fmlfuefmlfuefmlf" src="https://github.com/user-attachments/assets/28bea7c7-918b-4402-802b-b4a34f4cd77f" />
40
+
41
+ [![Tests](https://github.com/brian-a-au/aa_auto_sdr/actions/workflows/tests.yml/badge.svg)](https://github.com/brian-a-au/aa_auto_sdr/actions/workflows/tests.yml)
42
+ [![Lint](https://github.com/brian-a-au/aa_auto_sdr/actions/workflows/lint.yml/badge.svg)](https://github.com/brian-a-au/aa_auto_sdr/actions/workflows/lint.yml)
43
+ [![Version Sync](https://github.com/brian-a-au/aa_auto_sdr/actions/workflows/version-sync.yml/badge.svg)](https://github.com/brian-a-au/aa_auto_sdr/actions/workflows/version-sync.yml)
44
+ [![Python 3.14+](https://img.shields.io/badge/python-3.14%2B-blue.svg)](https://www.python.org/downloads/)
45
+ [![Coverage](https://img.shields.io/badge/coverage-99%25-brightgreen.svg)](tests/)
46
+ [![Tests](https://img.shields.io/badge/tests-2331-brightgreen.svg)](tests/)
47
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
48
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
49
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
50
+
51
+ A production-ready Python CLI that automates the creation of Solution Design Reference (SDR) documentation from your Adobe Analytics implementation. Read-only against Adobe Analytics. API 2.0 only.
52
+
53
+ Counterpart to [`cja_auto_sdr`](https://github.com/brian-a-au/cja_auto_sdr); shares UX conventions, does not share code.
54
+
55
+ ## What It Is
56
+
57
+ A **Solution Design Reference** is the documentation that bridges your business requirements and your analytics implementation. It catalogs every dimension, metric, segment, calculated metric, virtual report suite, and classification dataset in your Adobe Analytics report suite — the single source of truth for what you collect and how it's configured.
58
+
59
+ **The Problem:** Manual SDR documentation is time-consuming, error-prone, and quickly outdated. Teams export data, format spreadsheets, and cross-reference configurations only to repeat the entire process when components change.
60
+
61
+ **The Solution:** This tool connects to the Adobe Analytics 2.0 API, extracts every component of a report suite, and renders the result in five formats (Excel, CSV, JSON, HTML, Markdown). It also persists snapshots of the normalized model and produces structured diffs between any two snapshots — version control of SDR.
62
+
63
+ ### How It Works
64
+
65
+ 1. **Authenticates** via Adobe OAuth Server-to-Server (env vars, named profile, `.env`, or `config.json`).
66
+ 2. **Fetches** every component from your report suite via the Adobe Analytics 2.0 API. Read-only — no writes ever.
67
+ 3. **Builds** an `SdrDocument` — the normalized, SDK-agnostic boundary type that all renderers and snapshots consume.
68
+ 4. **Renders** to your chosen format(s) and optionally **persists a snapshot** under `~/.aa/orgs/<profile>/snapshots/` for later diffing.
69
+
70
+ ### Key Features
71
+
72
+ | Category | Feature |
73
+ |----------|---------|
74
+ | **Generation** | Single-RSID generation by ID or name (case-insensitive exact match) |
75
+ | | **Template-fill Excel** — point `--template aa_en_BRD_SDR_template.xlsx` at Adobe's BRD/SDR template (or your own customized copy) to fill component data into the official styled workbook while preserving formulas, styles, and untouched cells. See the [Template-Fill Workflow guide](docs/TEMPLATE_WORKFLOW.md) for first-run + batch + troubleshooting walkthroughs. Direct download: [`aa_en_BRD_SDR_template.xlsx`](https://cdn.experienceleague.adobe.com/assets/Adobe-Enterprise-Docs/analytics-learn.en/main/help/implementation/implementation-basics/assets/aa_en_BRD_SDR_template.xlsx). |
76
+ | | Auto-batch when 2+ identifiers are given on the command line; `--batch` flag still supported |
77
+ | | RSIDs and names may be mixed freely in one invocation |
78
+ | | `--metrics-only` / `--dimensions-only` slim the SDR; skip API calls for excluded types |
79
+ | | `--dry-run` previews would-be output paths without writing (auth still validates) |
80
+ | | `--open` opens generated output in OS default app after writing |
81
+ | | `--show-timings` prints per-stage timings to stderr at end of run |
82
+ | | `--run-summary-json PATH` emits a structured JSON run summary to a file or stdout |
83
+ | | Continue-on-error across N report suites with summary banner |
84
+ | | Five output formats: Excel, CSV, JSON, HTML, Markdown |
85
+ | | Four format aliases: `all`, `reports` (excel + markdown), `data` (csv + json), `ci` (json + markdown) |
86
+ | | Multi-match name fan-out: a name matching N suites generates N SDRs |
87
+ | **Discovery & Inspection** | `--list-reportsuites`, `--list-virtual-reportsuites` |
88
+ | | `--describe-reportsuite <RSID>` — metadata + per-component counts |
89
+ | | `--list-{metrics,dimensions,segments,calculated-metrics,classification-datasets} <RSID>` |
90
+ | | `--stats [<RSID>...]` — quick component counts per RSID without full SDR build |
91
+ | | `--interactive` — pick an RSID interactively; emits to stdout for shell composition |
92
+ | | `--filter`, `--exclude`, `--sort`, `--limit` on every list command |
93
+ | **Snapshot & Diff** | `--snapshot` opt-in persist alongside generation |
94
+ | | `--auto-snapshot` saves a snapshot per RSID on every `<RSID>` / `--batch` run |
95
+ | | `--auto-prune` + `--keep-last N` / `--keep-since 30d` retention policy |
96
+ | | `--list-snapshots [<RSID>]` action — table or json view |
97
+ | | `--prune-snapshots [<RSID>] --dry-run` — apply retention policy with optional preview |
98
+ | | `--prune-snapshots` confirms before deleting; `--yes` skips the prompt |
99
+ | | `--diff <a> <b>` between any two snapshots |
100
+ | | Token grammar: bare path / `<rsid>@<ts>` / `<rsid>@latest` / `<rsid>@previous` / `git:<ref>:<path>` |
101
+ | | Four diff renderers: console (ANSI-colored), JSON, Markdown, **`pr-comment`** (compact GFM with collapsible `<details>` for GitHub PRs) |
102
+ | | Diff modifiers: `--side-by-side`, `--summary`, `--ignore-fields description,tags` |
103
+ | | Diff polish: `--quiet-diff`, `--diff-labels A=… B=…`, `--reverse-diff`, `--changes-only`, `--show-only TYPES`, `--max-issues N` |
104
+ | | `--warn-threshold N` exits 3 when total changes ≥ N (CI signal) |
105
+ | | `$GITHUB_STEP_SUMMARY` auto-append for `--diff` when env var is set |
106
+ | | Identity by component ID, never by name (a name change is *modification*) |
107
+ | | Value normalization (whitespace, NaN/None/`""`, order-insensitive `tags`/`categories`) suppresses false-positive diffs |
108
+ | **Authentication** | OAuth Server-to-Server (env vars / profile / `.env` / `config.json`) |
109
+ | | Named profiles for multi-org users (`~/.aa/orgs/<name>/`) |
110
+ | | `--show-config` reports which credential source resolved |
111
+ | | `--config-status` / `--validate-config` / `--sample-config` for credential introspection |
112
+ | | `--profile-add <name>` interactive credential capture |
113
+ | | `--profile-list` / `--profile-show NAME` / `--profile-test NAME` / `--profile-import NAME FILE` |
114
+ | | `--profile-import` requires `--profile-overwrite` to replace an existing profile |
115
+ | **Output** | `--output -` stdout pipe for json (single-RSID generation) and json/markdown (diff) |
116
+ | | Machine-readable JSON error envelope on stderr for pipe-path failures |
117
+ | | Atomic file writes (temp + rename) for every output format |
118
+ | | One-command registry setup: `--notion-create-database` builds the SDR Registry database with the full schema under your Notion parent page. |
119
+ | **Reliability** | **Read-only against Adobe Analytics, forever** — CI-enforced via meta-test scanning `src/aa_auto_sdr/api/` for any write-shape SDK call |
120
+ | | **API 2.0 only, no 1.4 paths** — CI-enforced via meta-test |
121
+ | | 90% coverage gate on the unit slice |
122
+ | | CI matrix on Linux + macOS + Windows |
123
+ | | Atomic snapshot writes; sorted-key JSON for git-friendly diffs |
124
+ | **Developer UX** | `--exit-codes` lists every code; `--explain-exit-code <CODE>` prints meaning + remediation |
125
+ | | `--completion {bash,zsh,fish}` emits a static shell-completion script |
126
+ | | Sub-100ms fast-path for `-V`/`--version`/`-h`/`--help`/`--exit-codes`/`--explain-exit-code`/`--completion` |
127
+ | | `--help` covers every flag |
128
+
129
+ ### Who It's For
130
+
131
+ - **Adobe Analytics implementers** documenting report suite state for stakeholders or audits
132
+ - **Analytics teams** capturing SDR snapshots in git for change tracking
133
+ - **Consultants** managing multiple client implementations across orgs (multi-profile)
134
+ - **Data Governance** teams needing a structured artifact of every RS configuration over time
135
+ - **DevOps Engineers** automating SDR drift detection in CI/CD pipelines
136
+
137
+ ## Quick Start
138
+
139
+ > **macOS/Linux:** prefix commands with `uv run` (e.g. `uv run aa_auto_sdr --list-reportsuites`).
140
+ > **Windows:** activate the venv first (`.venv\Scripts\activate`), then run commands directly.
141
+
142
+ ### 1. Clone the Repository
143
+
144
+ ```bash
145
+ git clone https://github.com/brian-a-au/aa_auto_sdr
146
+ cd aa_auto_sdr
147
+ ```
148
+
149
+ ### 2. Install Dependencies
150
+
151
+ Install [`uv`](https://docs.astral.sh/uv/) — pick whichever path is convenient:
152
+
153
+ ```bash
154
+ # macOS / Linux (recommended)
155
+ curl -LsSf https://astral.sh/uv/install.sh | sh
156
+
157
+ # Or via pip on any platform
158
+ pip install uv
159
+ ```
160
+
161
+ Sync the project (creates `.venv/` and installs all dependencies):
162
+
163
+ ```bash
164
+ uv sync --all-extras
165
+ ```
166
+
167
+ **Windows:** activate the virtual environment so subsequent commands work without the `uv run` prefix:
168
+
169
+ ```powershell
170
+ .venv\Scripts\activate
171
+ ```
172
+
173
+ ### 3. Configure Credentials (Adobe Analytics API 2.0, OAuth Server-to-Server)
174
+
175
+ The Adobe Analytics 2.0 API uses **OAuth Server-to-Server** authentication.
176
+
177
+ #### a. Create an Adobe Developer Console project
178
+
179
+ 1. Visit https://developer.adobe.com/console
180
+ 2. Create a new project (or open an existing one)
181
+ 3. Add the **Adobe Analytics API**
182
+ 4. Choose **OAuth Server-to-Server** as the authentication method
183
+ 5. The console generates: **Org ID**, **Client ID**, **Client Secret**
184
+
185
+ #### b. Required scopes
186
+
187
+ The `SCOPES` value must include these **three** scopes (verified minimum for the read surface this tool exercises):
188
+
189
+ ```text
190
+ openid
191
+ AdobeID
192
+ additional_info.projectedProductContext
193
+ ```
194
+
195
+ These two scopes are **recommended** for fuller endpoint coverage and broader org configurations:
196
+
197
+ ```text
198
+ read_organizations
199
+ additional_info.job_function
200
+ ```
201
+
202
+ If your org's IMS rules require either of the recommended scopes for the endpoints this tool calls, you'll see a 403 on `--list-reportsuites` or empty `/dimensions` / `/metrics` responses despite a successful auth handshake. Add them to your `SCOPES` value if that happens.
203
+
204
+ #### c. Add the integration to a Product Profile
205
+
206
+ In the **Adobe Admin Console**, add the integration to an Adobe Analytics **Product Profile**. Without this, authentication can succeed while no Analytics companies or report suites are visible. In the underlying SDK flow, `Login().getCompanyId()` may return no companies and subsequent `Analytics()` calls may return empty data. `aa_auto_sdr --show-config` cannot detect this — the first generation attempt is what surfaces the problem.
207
+
208
+ > RSID is not the same as the Analytics global company ID. The CLI is RSID-first for the user, but Adobe Analytics 2.0 API requests are made in the context of an Analytics **global company ID** (sent as `x-proxy-global-company-id`); the tool resolves it from your Product Profile context internally. See [`docs/CONFIGURATION.md`](docs/CONFIGURATION.md#analytics-company-context-rsid-vs-global-company-id) for the full explanation.
209
+
210
+ #### d. Save credentials
211
+
212
+ Pick **one** of the four sources (resolution precedence: `--profile` → env vars → `.env` → `config.json`):
213
+
214
+ **Option 1 — Named profile (recommended for daily use):**
215
+
216
+ ```bash
217
+ uv run aa_auto_sdr --profile-add prod
218
+ ```
219
+
220
+ Walks through prompts for ORG_ID / CLIENT_ID / SECRET / SCOPES and writes `~/.aa/orgs/prod/config.json`. Use with `--profile prod` on subsequent commands.
221
+
222
+ **Option 2 — Environment variables:**
223
+
224
+ macOS / Linux:
225
+
226
+ ```text
227
+ export ORG_ID="YOUR_ORG_ID@AdobeOrg"
228
+ export CLIENT_ID="YOUR_CLIENT_ID"
229
+ export SECRET="YOUR_CLIENT_SECRET"
230
+ export SCOPES="openid,AdobeID,additional_info.projectedProductContext"
231
+ ```
232
+
233
+ Windows cmd:
234
+
235
+ ```text
236
+ setx ORG_ID "YOUR_ORG_ID@AdobeOrg"
237
+ setx CLIENT_ID "YOUR_CLIENT_ID"
238
+ setx SECRET "YOUR_CLIENT_SECRET"
239
+ setx SCOPES "openid,AdobeID,additional_info.projectedProductContext"
240
+ ```
241
+
242
+ PowerShell:
243
+
244
+ ```text
245
+ $Env:ORG_ID = "YOUR_ORG_ID@AdobeOrg"
246
+ $Env:CLIENT_ID = "YOUR_CLIENT_ID"
247
+ $Env:SECRET = "YOUR_CLIENT_SECRET"
248
+ $Env:SCOPES = "openid,AdobeID,additional_info.projectedProductContext"
249
+ ```
250
+
251
+ **Option 3 — `config.json` in repo root:**
252
+
253
+ ```bash
254
+ cp config.json.example config.json
255
+ # Edit config.json — already gitignored
256
+ ```
257
+
258
+ **Option 4 — `.env` file** (requires `python-dotenv`, an optional extra): same fields as env vars, in a `.env` file in the working directory.
259
+
260
+ ### 4. Verify Setup & Run
261
+
262
+ Confirm credentials and connectivity, then generate your first SDR:
263
+
264
+ ```text
265
+ $ uv run aa_auto_sdr --show-config # which credential source resolved
266
+ $ uv run aa_auto_sdr --list-reportsuites # confirms auth + scope; lists visible RSes
267
+ $ uv run aa_auto_sdr <RSID> # default Excel; <RSID> from the list above
268
+ ```
269
+
270
+ **Troubleshooting:**
271
+
272
+ - If `--show-config` succeeds but `--list-reportsuites` returns empty → the integration isn't on a Product Profile (step c).
273
+ - If `--list-reportsuites` returns a 403 or `/dimensions` / `/metrics` come back empty despite a successful auth → try adding the recommended scopes (`read_organizations`, `additional_info.job_function`) per step b.
274
+ - For full code-by-code remediation: `uv run aa_auto_sdr --explain-exit-code <CODE>`.
275
+
276
+ ### 5. Review Output
277
+
278
+ Default format produces `<RSID>.xlsx` in the working directory. Use `--format` for alternates:
279
+
280
+ ```text
281
+ $ uv run aa_auto_sdr <RSID> --format json # single JSON file
282
+ $ uv run aa_auto_sdr <RSID> --format all # all five formats at once
283
+ $ uv run aa_auto_sdr <RSID> --output-dir /tmp/sdr # custom directory
284
+ ```
285
+
286
+ Browse [`sample_outputs/`](sample_outputs/) in this repo to see what each format looks like before running anything.
287
+
288
+ ## Common Use Cases
289
+
290
+ > Commands below omit the `uv run` prefix for brevity (macOS/Linux: prepend `uv run`; Windows: activate venv first).
291
+
292
+ | Task | Command |
293
+ |------|---------|
294
+ | **Getting Started** | |
295
+ | List visible report suites | `aa_auto_sdr --list-reportsuites` |
296
+ | Show resolved credentials source | `aa_auto_sdr --show-config` |
297
+ | Print help | `aa_auto_sdr --help` |
298
+ | **SDR Generation** | |
299
+ | Single RS by RSID | `aa_auto_sdr dgeo1xxpnwcidadobestore` |
300
+ | Single RS by name | `aa_auto_sdr "Adobe Store"` |
301
+ | Custom output directory | `aa_auto_sdr <RSID> --output-dir /tmp/sdr` |
302
+ | Auto-batch — multiple positional identifiers | `aa_auto_sdr rs1 rs2 rs3` |
303
+ | Auto-batch with mixed RSIDs and names | `aa_auto_sdr dgeo1xxpnwcidadobestore "Adobe Store" demo.prod` |
304
+ | Batch via explicit flag | `aa_auto_sdr --batch RS1 RS2 RS3` |
305
+ | Parallel batch | `aa_auto_sdr --batch RS1 RS2 RS3 --workers 4` |
306
+ | Parallel + fail-fast | `aa_auto_sdr --batch RS1 RS2 --workers 2 --fail-fast` |
307
+ | Naming audit | `aa_auto_sdr RS1 --audit-naming` |
308
+ | Flag stale components | `aa_auto_sdr RS1 --flag-stale` |
309
+ | Resolve by name | `aa_auto_sdr "Production RS" --name-match fuzzy` |
310
+ | Extended-field diff | `aa_auto_sdr --diff snap_a snap_b --extended-fields` |
311
+ | Sample batch | `aa_auto_sdr --batch RS1 RS2 RS3 RS4 RS5 --sample 2` |
312
+ | Sample reproducibly | `aa_auto_sdr --batch RS1 RS2 RS3 --sample 2 --sample-seed 42` |
313
+ | Stratified sample by prefix | `aa_auto_sdr --batch prod.us prod.eu dev.us --sample 2 --sample-stratified` |
314
+ | Quality report | `aa_auto_sdr <RSID> --audit-naming --quality-report json` |
315
+ | Fail CI on HIGH or worse | `aa_auto_sdr <RSID> --fail-on-quality HIGH` |
316
+ | Quality policy from file | `aa_auto_sdr <RSID> --quality-policy ./policy.json` |
317
+ | Use a named profile | `aa_auto_sdr <RSID> --profile prod` |
318
+ | **Output Formats** | |
319
+ | Excel (default) | `aa_auto_sdr <RSID>` |
320
+ | JSON | `aa_auto_sdr <RSID> --format json` |
321
+ | All five formats | `aa_auto_sdr <RSID> --format all` |
322
+ | Pipe JSON to jq | `aa_auto_sdr <RSID> --format json --output - \| jq '.report_suite'` |
323
+ | Aliases (excel + markdown) | `aa_auto_sdr <RSID> --format reports` |
324
+ | Template-fill Excel ([download `.xlsx`](https://cdn.experienceleague.adobe.com/assets/Adobe-Enterprise-Docs/analytics-learn.en/main/help/implementation/implementation-basics/assets/aa_en_BRD_SDR_template.xlsx) · [tutorial](https://experienceleague.adobe.com/en/docs/analytics-learn/tutorials/implementation/implementation-basics/creating-and-maintaining-an-sdr)) | `aa_auto_sdr <RSID> --template ~/aa_en_BRD_SDR_template.xlsx` |
325
+ | Notion publishing + optional registry database + maintenance modes ([setup guide](docs/NOTION_SETUP.md) · [command reference](docs/CLI_REFERENCE.md#notion-integration)) | `aa_auto_sdr <RSID> --format notion` |
326
+ | **Discovery & Inspection** | |
327
+ | List metrics for one RS | `aa_auto_sdr --list-metrics <RSID>` |
328
+ | Filter + sort + limit | `aa_auto_sdr --list-metrics <RSID> --filter page --sort name --limit 10` |
329
+ | Describe (counts only) | `aa_auto_sdr --describe-reportsuite <RSID>` |
330
+ | List as JSON for scripting | `aa_auto_sdr --list-reportsuites --format json --output -` |
331
+ | Org-wide inventory rollup | `aa_auto_sdr --inventory-summary` |
332
+ | Inventory across selected RSes as CSV | `aa_auto_sdr rs1 rs2 rs3 --inventory-summary --format csv` |
333
+ | Inventory as machine-readable JSON | `aa_auto_sdr --inventory-summary --format json` |
334
+ | **Snapshot** | |
335
+ | Capture snapshot alongside generation | `aa_auto_sdr <RSID> --snapshot --profile prod` |
336
+ | Capture snapshots for multiple RSes (auto-batch) | `aa_auto_sdr RS1 RS2 --snapshot --profile prod` |
337
+ | Auto-snapshot every run | `aa_auto_sdr <RSID> --auto-snapshot --profile prod` |
338
+ | **Diff** | |
339
+ | Diff two snapshot files | `aa_auto_sdr --diff a.json b.json` |
340
+ | Diff `@latest` vs `@previous` | `aa_auto_sdr --diff <RSID>@latest <RSID>@previous --profile prod` |
341
+ | Diff at a specific timestamp | `aa_auto_sdr --diff <RSID>@2026-04-26T17-29-01+00-00 <RSID>@latest --profile prod` |
342
+ | Diff at a git ref | `aa_auto_sdr --diff git:HEAD~1:snapshots/x.json git:HEAD:snapshots/x.json` |
343
+ | Diff to JSON pipe | `aa_auto_sdr --diff a.json b.json --format json --output -` |
344
+ | Diff to Markdown file | `aa_auto_sdr --diff a.json b.json --format markdown --output diff.md` |
345
+ | Drift over 30 days | `aa_auto_sdr <RSID> --trending-window 30d --profile prod` |
346
+ | Drift as JSON for dashboards | `aa_auto_sdr rs1 rs2 rs3 --trending-window 30d --format json` |
347
+ | Latest vs previous snapshot | `aa_auto_sdr <RSID> --compare-with-prev --profile prod` |
348
+ | Watch for changes | `aa_auto_sdr rs_prod_us --watch --interval 1h --agent-mode \| jq -c .` |
349
+ | Watch multiple RSIDs every 6 h | `aa_auto_sdr rs1 rs2 --watch --interval 6h --watch-threshold 5` |
350
+ | **Profile / Config** | |
351
+ | Create profile interactively | `aa_auto_sdr --profile-add prod` |
352
+ | Verify resolved source | `aa_auto_sdr --show-config` |
353
+ | **Fast-path / Help** | |
354
+ | Print version | `aa_auto_sdr -V` |
355
+ | List exit codes | `aa_auto_sdr --exit-codes` |
356
+ | Explain one exit code | `aa_auto_sdr --explain-exit-code 11` |
357
+ | Generate completion script | `aa_auto_sdr --completion zsh > ~/.zsh/completions/_aa_auto_sdr` |
358
+
359
+ ### Watch mode
360
+
361
+ Watch mode enters a foreground monitoring loop that fetches, snapshots, and diffs a report suite on a repeating interval, emitting structured NDJSON events on stdout. Pair it with `--agent-mode` and pipe to `jq` to react to changes in real time:
362
+
363
+ ```bash
364
+ # Watch a report suite for changes — emits NDJSON events on stdout (Ctrl+C to stop)
365
+ uv run aa_auto_sdr rs_prod_us --watch --interval 1h --agent-mode | jq -c .
366
+
367
+ # Watch and publish to Notion on every change
368
+ uv run aa_auto_sdr rs_prod_us --watch --interval 1h --format notion
369
+ ```
370
+
371
+ Three event types: `baseline` (first cycle, always emitted), `change` (when `total_changes >= --watch-threshold`), and `error` (per-RSID fetch failure — loop continues). SIGINT exits 0. With `--format notion`, Notion publishes on the baseline cycle and on `change` events; zero-change and error cycles are skipped.
372
+
373
+ ### Git-versioned snapshot audit trail
374
+
375
+ The snapshot directory is the git repo. The first `--git-commit` initializes it automatically — no separate setup step.
376
+
377
+ ```bash
378
+ # Commit each snapshot to a git-versioned audit trail.
379
+ uv run aa_auto_sdr rs_prod_us --git-commit
380
+
381
+ # In a watch loop — each baseline/change cycle commits automatically.
382
+ uv run aa_auto_sdr rs_prod_us --watch --interval 1h --git-commit --git-push
383
+ ```
384
+
385
+ ### Retry tuning
386
+
387
+ For flaky AA orgs or noisy CI environments, tune retry behavior:
388
+
389
+ ```bash
390
+ aa_auto_sdr RSID --max-retries 6 --retry-base-delay 1.0 --retry-max-delay 30.0
391
+ ```
392
+
393
+ Defaults are conservative (3 retries, 0.5s base, 10s cap). Retries fire only
394
+ on transient failures; permanent errors (auth, validation, unknown RSID)
395
+ surface immediately.
396
+
397
+ ### Snapshot envelope
398
+
399
+ Current schema: `aa-sdr-snapshot/v4`. Snapshot files carry two top-level fetch-quality keys:
400
+
401
+ - `degraded_components: list[str]` — component types whose fetch returned no
402
+ data (e.g., when Adobe's VRS endpoint flapped). Empty by default.
403
+ - `partial_components: dict[str, str]` — component types whose fetch fell
404
+ back to a reduced expansion level. Empty by default; current releases no
405
+ longer produce partial fetches (the key is kept for snapshots written by
406
+ earlier releases and remains honored by `--diff`).
407
+
408
+ …plus a `quality` block (`issues`, `summary`) populated when the quality engine runs.
409
+
410
+ When `--diff` compares two snapshots and either side has a degraded or
411
+ partial-with-mismatched-level fetch for a component type, that section's
412
+ diff is suppressed with a single annotation rather than rendering false
413
+ "modified" rows. Older majors (`v1`–`v3`) load forward-compat with missing
414
+ fields defaulted. See [`docs/SNAPSHOT_DIFF.md`](docs/SNAPSHOT_DIFF.md) for the full schema and `CHANGELOG.md` for version history.
415
+
416
+ ### Fetch-quality signal
417
+
418
+ When Adobe's VRS or classifications endpoint flaps,
419
+ `--describe-reportsuite` and `--stats` annotate the affected count cell
420
+ with a trailing `*` and emit a footer line per non-healthy
421
+ `(rsid, component_type)` pair. The example below shows `--stats` output
422
+ (8 columns); `--describe-reportsuite` adds metadata columns (timezone,
423
+ currency, parent_rsid) but uses the same `*` marker + footer convention:
424
+
425
+ ```
426
+ RSID NAME DIM MET SEG CALC VRS CLS
427
+ demo.prod Demo Production 331 122 45 12 0 * 5
428
+
429
+ * demo.prod virtual_report_suites: fetch degraded
430
+ * (counts marked with * may be inaccurate; see logs/SDR_*.log)
431
+ ```
432
+
433
+ For JSON output (`--format json`), the equivalent signal is a per-record
434
+ `fetch_status` field. Where CSV is available (`--describe-reportsuite`), it
435
+ omits both signals — use JSON when machine-parseable fetch quality is needed.
436
+ (`--stats` itself supports only `table` and `json`.)
437
+
438
+ `--list-classification-datasets` emits a stderr banner above the list
439
+ when the fetch degrades; exit code stays 0 to preserve pipeline behavior.
440
+
441
+ ## Documentation
442
+
443
+ | Guide | Description |
444
+ |-------|-------------|
445
+ | [Quick Reference](docs/QUICK_REFERENCE.md) | Single-page command cheat sheet, grouped by mode |
446
+ | [Quickstart](docs/QUICKSTART.md) | Step-by-step walkthrough from clone to first SDR |
447
+ | [Installation](docs/INSTALLATION.md) | Platform setup, install methods, optional extras, dependency reference |
448
+ | [Configuration](docs/CONFIGURATION.md) | Credential sources, OAuth scopes, profile management, diagnostics, troubleshooting |
449
+ | [Notion Setup](docs/NOTION_SETUP.md) | Step-by-step Notion publishing and SDR Registry setup |
450
+ | [CLI Reference](docs/CLI_REFERENCE.md) | Every flag organized by capability, exit codes, machine-readable error envelope |
451
+ | [Use Cases & Best Practices](docs/USE_CASES.md) | Scenario playbooks, automation, scheduling, CI/CD |
452
+ | [Snapshot & Diff](docs/SNAPSHOT_DIFF.md) | Snapshot file format, resolver token grammar, diff semantics, trending, watch, common workflows |
453
+ | [Output Formats](docs/OUTPUT_FORMATS.md) | Five formats + four aliases, when to use each, file layouts |
454
+ | [Template-Fill Workflow](docs/TEMPLATE_WORKFLOW.md) | Hands-on guide for `--template` — getting Adobe's template, first run, batch, composition with snapshot/git, coverage map, troubleshooting |
455
+ | [Logging](docs/LOGGING.md) | Log flags, file naming, redaction, canonical events |
456
+ | [Logging Style Guide](docs/LOGGING_STYLE.md) | Internal logger-call contract — canonical vocabulary, required extras (binds `tests/core/test_logging_vocabulary.py`) |
457
+ | [Sample Outputs](sample_outputs/) | Browse representative outputs without installing |
458
+ | [`AGENTS.md`](AGENTS.md) | Machine-readable contract for unattended / agent-driven runs (`--agent-mode`) |
459
+
460
+ ## Requirements
461
+
462
+ - Python 3.14+
463
+ - Adobe Developer Console project with **Adobe Analytics API** access (OAuth Server-to-Server)
464
+ - Integration added to an Adobe Analytics **Product Profile** in Admin Console
465
+ - Network connectivity to Adobe APIs
466
+
467
+ ## Project Structure
468
+
469
+ High-level layout (representative, not exhaustive):
470
+
471
+ ```
472
+ aa_auto_sdr/
473
+ ├── .github/
474
+ │ └── workflows/ # tests, lint, version-sync, release-gate
475
+ ├── src/
476
+ │ └── aa_auto_sdr/ # main package (src layout)
477
+ │ ├── __init__.py
478
+ │ ├── __main__.py # fast-path entry (sub-100ms for --version/--help/--exit-codes/--completion)
479
+ │ ├── api/ # aanalytics2 wrapper, auth, fetchers, normalized models
480
+ │ │ ├── client.py # only file (besides auth/fetch) that imports aanalytics2
481
+ │ │ ├── auth.py
482
+ │ │ ├── fetch.py # per-component fetchers; coerces SDK shapes
483
+ │ │ └── models.py # normalized dataclasses (boundary types)
484
+ │ ├── cli/ # argparse + dispatch
485
+ │ │ ├── parser.py
486
+ │ │ ├── main.py
487
+ │ │ ├── list_output.py # table/json/csv rendering for list/inspect
488
+ │ │ └── commands/ # one module per command
489
+ │ ├── core/ # cross-cutting utilities
490
+ │ │ ├── version.py # canonical __version__
491
+ │ │ ├── exceptions.py # typed exception hierarchy
492
+ │ │ ├── exit_codes.py # central ExitCode enum + ROWS + EXPLANATIONS
493
+ │ │ ├── colors.py # ANSI helpers (auto-disabled for non-TTY / NO_COLOR)
494
+ │ │ ├── credentials.py # OAuth credential resolution + precedence
495
+ │ │ ├── profiles.py # ~/.aa/orgs/<name>/ profile CRUD
496
+ │ │ ├── constants.py # BANNER_WIDTH, etc.
497
+ │ │ └── json_io.py # atomic JSON read/write
498
+ │ ├── output/ # format writers + diff renderers + error envelope
499
+ │ │ ├── protocols.py
500
+ │ │ ├── registry.py
501
+ │ │ ├── error_envelope.py # JSON envelope on stderr for pipe-path failures
502
+ │ │ ├── _helpers.py
503
+ │ │ ├── writers/ # excel.py, excel_template.py, csv.py, json.py, html.py, markdown.py, notion.py
504
+ │ │ └── diff_renderers/ # console.py, json.py, markdown.py, pr_comment.py, _filters.py
505
+ │ ├── pipeline/ # run coordination
506
+ │ │ ├── single.py # single-RSID
507
+ │ │ ├── batch.py # multi-RSID, sequential, continue-on-error
508
+ │ │ └── models.py # RunResult, BatchResult, BatchFailure
509
+ │ ├── sdr/ # SDR assembly
510
+ │ │ ├── builder.py # pure: AaClient + RSID -> SdrDocument
511
+ │ │ └── document.py # SdrDocument boundary type
512
+ │ └── snapshot/ # version control of SDR
513
+ │ ├── store.py # save/load + path convention
514
+ │ ├── schema.py # aa-sdr-snapshot envelope + validator
515
+ │ ├── resolver.py # token grammar dispatcher
516
+ │ ├── git.py # git show wrapper
517
+ │ ├── comparator.py # diff algorithm + value normalization
518
+ │ ├── retention.py # --keep-last / --keep-since policy
519
+ │ └── models.py # DiffReport, ComponentDiff, FieldDelta
520
+ ├── tests/ # pytest suite — unit / integration / meta
521
+ │ └── meta/ # CI-enforced architectural invariants
522
+ ├── scripts/
523
+ │ ├── check_version_sync.py # version drift gate
524
+ │ └── build_sample_outputs.py # deterministic sample generator
525
+ ├── docs/ # user-facing markdown (gitignored docs/superpowers/ excluded)
526
+ ├── sample_outputs/ # representative outputs, generated from fixture
527
+ ├── pyproject.toml
528
+ ├── uv.lock
529
+ ├── README.md # this file
530
+ ├── CHANGELOG.md
531
+ ├── LICENSE
532
+ ├── config.json.example
533
+ └── .env.example
534
+ ```
535
+
536
+ ## Additional Resources
537
+
538
+ - [Adobe Analytics 2.0 API documentation](https://developer.adobe.com/analytics-apis/docs/2.0/)
539
+ - [`aanalytics2` Python wrapper](https://github.com/pitchmuc/adobe-analytics-api-2.0) — the underlying SDK
540
+ - [`uv` package manager](https://github.com/astral-sh/uv)
541
+ - [Sister project: `cja_auto_sdr`](https://github.com/brian-a-au/cja_auto_sdr) — Customer Journey Analytics equivalent