Repository navigation
feat(experiments): add metric breakdown injector for funnels - #61796
Conversation
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
|
Size Change: 0 B Total Size: 81.9 MB ℹ️ View Unchanged
|
13098b4 to
1722625
Compare
This comment was marked as duplicate.
This comment was marked as duplicate.
1722625 to
960ad5a
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
960ad5a to
852b301
Compare
05eb6f8 to
e0bec4f
Compare
181e3e1 to
89009de
Compare
89009de to
53662f4
Compare
|
This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, please remove the |
|
This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, please remove the |
53662f4 to
e0c8973
Compare
…isfy tuple-return rule
e0c8973 to
a646e8a
Compare
|
This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, please remove the |

Problem
Experiment breakdowns attribute the breakdown value from the exposure event (
BreakdownInjector). Insights funnels instead read the value off the funnel events, with a user-selectable attribution mode. The mismatch means an experiment funnel and the equivalent funnel insight bucket users differently, and exposure events often lack the property entirely, inflating the null bucket.This PR starts the migration to metric-event attribution with funnel metrics, the only type where the user picks an attribution mode. The frontend controls are in #61788; mean, retention, and ratio follow in #62291, #62292, #62293.
Changes
This PR adds a new breakdown injector that reads the breakdown off the metric events for funnel metrics, gated behind the
experiment-metric-event-breakdownsflag.Metric-event breakdown injector
MetricBreakdownInjectoris a standalone class, deliberately not a subclass ofBreakdownInjector; the old injector gets deleted once every metric type migrates. It reads each breakdown property off the metric event and attributes per user withargMinIf/argMaxIfover the funnel step columns. In experiment funnelsstep_0is the exposure event and the metric series maps tostep_1..step_N, so attribution targets the metric steps and never the exposure. First touch and all events resolve to the first step, last touch to the last, and step attribution validatesbreakdownAttributionValueagainst the series length. Both the legacy 3-CTE and the optimized 2-CTE funnel shapes are supported.products/experiments/backend/hogql_queries/metric_breakdown_injector.pyTop-N limit and the Other bucket
Breakdown tuples are ranked by number of experiment units per bucket, pooled across variants, and capped at
breakdownFilter.breakdown_limit(defaultBREAKDOWN_VALUES_LIMIT). Tuples beyond the cap are relabeled to the standard Other label before the final GROUP BY, collapsing the whole tuple together so output cardinality stays bounded.Wiring and gating
The runner builds the injector only when the flag is on, the metric is a funnel, and breakdowns exist; everything else falls back to the old
BreakdownInjector. The flag is evaluated locally only (no network on the query hot path) and fails closed. The injector exposesattributes_from_exposure()soExposureQueryBuilderkeeps the exposure query breakdown-free on the new path, and the mean, ratio, and retention builders guard withisinstanceso they stay on the old injector for now.products/experiments/backend/hogql_queries/experiment_query_runner.pyproducts/experiments/backend/hogql_queries/experiment_query_builder.pyproducts/experiments/backend/hogql_queries/experiment_exposure_query_builder.pyproducts/experiments/backend/hogql_queries/breakdown_injector.pyproducts/experiments/backend/hogql_queries/experiment_mean_query_builder.pyproducts/experiments/backend/hogql_queries/experiment_ratio_query_builder.pyproducts/experiments/backend/hogql_queries/experiment_retention_query_builder.pyTests
Flag-on runner tests cover attribution modes, the limit, and the Other bucket; unit tests cover the injector's AST output. The existing flag-off breakdown suite is untouched, which is the regression guarantee that the old path is unchanged.
products/experiments/backend/hogql_queries/test/experiment_query_runner/test_breakdown.pyproducts/experiments/backend/hogql_queries/test/test_metric_breakdown_injector.pyHow did you test this code?
👉 Stay up-to-date with PostHog coding conventions for a smoother review.
Automatic notifications
Docs update
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Model: Opus 4.7 (implementation), Fable 5 (restack and PR description)
Manually refactored: yes
Skills used:
Relevant decisions:
BreakdownInjector: the two attribution models share almost nothing, and a clean class makes the eventual deletion of the old injector a pure removal.only_evaluate_locally=True, failing closed to the old behavior when flag definitions aren't cached.