Skip to content

feat(analytics): DEEP scope drilldown + TypedDicts for row shapes (#120 follow-up)#141

Merged
hyoshi merged 1 commit into
mainfrom
feat/analytics-deep-scope-typeddict
May 23, 2026
Merged

feat(analytics): DEEP scope drilldown + TypedDicts for row shapes (#120 follow-up)#141
hyoshi merged 1 commit into
mainfrom
feat/analytics-deep-scope-typeddict

Conversation

@hyoshi
Copy link
Copy Markdown
Collaborator

@hyoshi hyoshi commented May 23, 2026

Summary

Two #120 follow-ups, both additive and ABI non-breaking:

1. DEEP scope for diagnose_performance

PerformanceDiagnosis.per_campaign_metrics (default () — non-breaking) carries one entry per campaign as (campaign_id, ((metric_name, value), ...)) when the diagnosis runs at PerformanceScope.DEEP. Both built-in adapters emit per-campaign findings (one human-readable line per campaign with spend / CV / CPA) and populate per_campaign_metrics sorted by spend descending so the highest-impact campaigns surface first.

Rows missing campaign_id stay in aggregate totals but are dropped from the drilldown — no synthetic "" key — so a workflow that does dict(per_campaign_metrics) cannot collide two anonymous rows.

Why: workflow skills like rescue need to drill into specific campaigns ("camp_X spend is way up with poor CPA") rather than aggregate-only summaries. Before this change, DEEP scope was a stub.

Validated against the local BYOD bundle: 4 campaigns reported with spend 1.4M–7.7M, CPAs from 2,187 to 27,500, correctly sorted into the structured payload.

2. TypedDicts for API row shapes

New mureo/analytics/builtin/_row_types.py documents the platform-row shapes the built-in adapters consume:

  • GoogleLivePerformanceRow / GoogleByodPerformanceRow / GoogleMetricsDict / GooglePerformanceRow
  • MetaLivePerformanceRow / MetaByodPerformanceRow / MetaActionEntry / MetaPerformanceRow
  • GoogleAdRow / MetaAdRow

All total=False. Re-exported from mureo.analytics so plugin authors can type their own analytics modules against the same shapes.

Helper signatures (google_row_metrics, meta_row_conversions) keep dict[str, Any] parameter types — the TypedDicts cannot freely accept dict[str, Any] at the call site without a cast, so propagating them fully would cascade cast() calls through 10+ files for marginal additional safety. The TypedDicts serve as documentation + plugin-side typing aid; ABI doc §4a pins the field-set contract.

Test plan

  • +6 tests (133 → 139); 0 regressions vs main
  • Lint (ruff) + format (black) + mypy clean for the analytics package
  • Live BYOD validation: DEEP scope produced correct per-campaign drilldown for all 4 Google Ads campaigns
  • Code-review verdict: Approve, no CRITICAL/HIGH/MEDIUM blockers

Refs #120

… follow-up)

Two follow-ups to the #120 series.

1. DEEP scope for diagnose_performance

   PerformanceDiagnosis.per_campaign_metrics (default empty tuple —
   ABI non-breaking) carries one entry per campaign as
   (campaign_id, ((metric_name, value), ...)) when the diagnosis runs
   at PerformanceScope.DEEP. Both built-in adapters now emit
   per-campaign findings (one human-readable line per campaign with
   spend / CV / CPA) and populate per_campaign_metrics in spend-
   descending order so the highest-impact campaigns surface first.

   Rows missing campaign_id are kept in the aggregate totals but
   dropped from the drilldown — no synthetic "" key — so a workflow
   that does dict(per_campaign_metrics) cannot silently collide two
   anonymous rows.

   Validated against the local BYOD bundle: 4 campaigns reported with
   spend 1.4M-7.7M and CPAs ranging 2,187 to 27,500, sorted correctly
   and packed into structured per_campaign_metrics for skill use.

2. TypedDicts for API row shapes

   New mureo/analytics/builtin/_row_types.py documents the platform-
   row shapes the built-in adapters consume:

   - GoogleLivePerformanceRow / GoogleByodPerformanceRow / GoogleMetricsDict
   - GooglePerformanceRow (union)
   - MetaLivePerformanceRow / MetaByodPerformanceRow / MetaActionEntry
   - MetaPerformanceRow (union)
   - GoogleAdRow / MetaAdRow

   All total=False — mureo promises the field set, not that any field
   is always present. Re-exported from mureo.analytics so plugin
   authors can type their own analytics modules against the same
   shapes. ABI doc section 4a documents the contract.

   google_row_metrics / meta_row_conversions helpers keep dict[str, Any]
   parameter types (TypedDicts cannot freely accept dict[str, Any] at
   the call site without a cast); the TypedDicts serve as documentation
   + plugin-side typing aid rather than internal-strictness
   enforcement, with the docstrings pointing at the expected shape.

Tests: +6 (133 -> 139), 0 regressions. Lint + mypy clean.
Code-review verdict: Approve, no blockers.

Refs #120
@hyoshi hyoshi merged commit d595145 into main May 23, 2026
9 checks passed
@hyoshi hyoshi deleted the feat/analytics-deep-scope-typeddict branch May 23, 2026 07:53
hyoshi added a commit that referenced this pull request May 23, 2026
…atforms (#120) (#142)

Bump version across pyproject.toml / mureo/__init__.py / .claude-plugin/plugin.json
and add the 0.9.9 CHANGELOG entry summarising the five PRs that shipped
the analytics-module work (#137, #138, #139, #140, #141).

Headline:

- opt-in `AnalyticsModule` Protocol + registry + entry-point group
  `mureo.analytics`, so external-integration platforms (official MCPs
  and third-party plugins) can plug into mureo's deep analytics on
  the same terms as the built-in adapters
- built-in google_ads / meta_ads adapters wired against live + BYOD
  clients with sentinel handling for missing credentials
- per-campaign fan-out for `detect_anomalies`, full implementations
  of `audit_creative` and `analyze_budget_efficiency`, and DEEP scope
  drilldown for `diagnose_performance`
- 10 row-shape TypedDicts re-exported from `mureo.analytics` for
  plugin-side typing
- skill prompts (daily-check, rescue, _mureo-shared) consult the new
  `mureo_analytics_modules_list` MCP tool and report
  `analytics_not_available_for_<platform>` honestly when a module is
  absent

Refs #120
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant