Repository navigation
feat(experiments): add metric breakdown injector for mean metrics - #62291
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. |
|
This comment was marked as outdated.
This comment was marked as outdated.
23e0d21 to
48d1526
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.
48d1526 to
fbd4113
Compare
fbd4113 to
7fc7e13
Compare
7fc7e13 to
0fa75b9
Compare
0fa75b9 to
68e8ee2
Compare
8fa48db to
1d465a7
Compare
ab9ccd1 to
f24fbc3
Compare
f24fbc3 to
6cfc044
Compare
a309988 to
676ed8b
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 |
676ed8b to
d5532db
Compare
d5532db to
57d2d2b
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
#61796 introduced metric-event breakdown attribution for funnel metrics. Mean metrics still attribute the breakdown from the exposure event, with the same null-bucket and consistency problems.
Changes
This PR migrates mean metrics to
MetricBreakdownInjector, still behind theexperiment-metric-event-breakdownsflag.Mean support in the injector
Mean metrics have no attribution modes, so the breakdown is always first touch:
argMinover the metric event timestamp per user. The conversion window is already enforced by theentity_metricsjoin, so no extra condition is needed. The injector's constructor now accepts all metric types; the funnel-only_attribution_stepasserts it only runs for funnels.Session-property metrics
Session-property means have an extra
metric_events_by_sessionlayer whosemetric_eventsCTE joins exposures and can't see raw event properties. The breakdown is read and deduped per session in the by-session layer (viaany()), then carried up withfirst_event_timestampso attribution uses the session's first event time.Winsorization
Winsorized means carry the breakdown through the
percentilesandwinsorized_entity_metricsCTEs, preserving per-breakdown-group thresholds. Top-N ranking is always computed fromentity_metricssince winsorization doesn't change the set of (entity, variant, breakdown) tuples;_inject_final_breakdown_columnslearned afinal_cte_nameso the outer SELECT reads from the right CTE.Wiring
Mean joins the migrated set in the runner. Both injectors expose
inject_mean_breakdown_columns, so the mean builder calls whichever it was given without type checks.products/experiments/backend/hogql_queries/metric_breakdown_injector.pyproducts/experiments/backend/hogql_queries/experiment_query_runner.pyproducts/experiments/backend/hogql_queries/experiment_mean_query_builder.pyTests
test_metric_breakdown.pystarts the flag-on runner suite (funnel and mean paths, including session properties and winsorization); injector unit tests cover the new AST shapes. The flag-off suite intest_breakdown.pystays as the old-path regression guard.products/experiments/backend/hogql_queries/test/experiment_query_runner/test_metric_breakdown.pyproducts/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:
entity_metrics.any()per session, mirroring howsession_valueis computed.