feat(analytics): DEEP scope drilldown + TypedDicts for row shapes (#120 follow-up)#141
Merged
Merged
Conversation
… 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
This was referenced May 23, 2026
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two #120 follow-ups, both additive and ABI non-breaking:
1. DEEP scope for
diagnose_performancePerformanceDiagnosis.per_campaign_metrics(default()— non-breaking) carries one entry per campaign as(campaign_id, ((metric_name, value), ...))when the diagnosis runs atPerformanceScope.DEEP. Both built-in adapters emit per-campaign findings (one human-readable line per campaign with spend / CV / CPA) and populateper_campaign_metricssorted by spend descending so the highest-impact campaigns surface first.Rows missing
campaign_idstay in aggregate totals but are dropped from the drilldown — no synthetic""key — so a workflow that doesdict(per_campaign_metrics)cannot collide two anonymous rows.Why: workflow skills like
rescueneed to drill into specific campaigns ("camp_X spend is way up with poor CPA") rather than aggregate-only summaries. Before this change,DEEPscope 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.pydocuments the platform-row shapes the built-in adapters consume:GoogleLivePerformanceRow/GoogleByodPerformanceRow/GoogleMetricsDict/GooglePerformanceRowMetaLivePerformanceRow/MetaByodPerformanceRow/MetaActionEntry/MetaPerformanceRowGoogleAdRow/MetaAdRowAll
total=False. Re-exported frommureo.analyticsso plugin authors can type their own analytics modules against the same shapes.Helper signatures (
google_row_metrics,meta_row_conversions) keepdict[str, Any]parameter types — the TypedDicts cannot freely acceptdict[str, Any]at the call site without acast, so propagating them fully would cascadecast()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
mainRefs #120