feat(customer-analytics): case 01 — actionable segmentation - #5
Merged
Conversation
…aldmego/site-ronaldmego#64) Builds the risk-by-value grid the issue asked for — every cell carrying behaviour, need, risk, an eligible offer, consent and an action — and then attacks it four ways, because the grid alone is a description. RFM is computed as prescribed and then measured: on a subscription, recency has no variance at all (one distinct value across the base) and frequency correlates with tenure at 1.0000 because it is tenure. Two of three letters are reading the company's own billing schedule. Findings, all reproducible from the seed: - 3 of 9 cells clear the cost of a contact; the deliverable is mostly a list of people not to contact. - 45% of the base changes cell in six months, 41.3% of it on the risk axis against 6.0% on value, while segment sizes move 2.3% — the aggregate view cannot show it. Measured cross-fitted; the in-sample version reports 40.2%, so the comfortable method understates. - Two of the playbook's own plays are refused by the catalogue rather than by policy: one contradicts the definition of the cell it was written for. - Priced against case 02's continuous ranking on the same budget, the grid loses 9.1%. Rank to choose who, segment to choose what. Withdrawn rather than shipped: an earlier draft claimed reachability falls monotonically with risk. A test contradicted it on both seeds tried, so the claim was cut back to the gap between the extreme thirds, whose direction holds across seeds while its size does not. The playbook lives in playbook.csv, not in the scoring code, for the reason case 03 moved the contact policy into a table. Imports case 02's model, feature builder and profit accounting, case 03's permission layer, and case 05's measured save rate and quarantined answer key — no case reimplements a neighbour's definition. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ANTzZtAz4hMbK9oBv2X9sC
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.
Refs ronaldmego/site-ronaldmego#64
Fourth of the five cases on the shared telco data model, and the one that judges
the others' output rather than extending it.
What it builds
The grid the issue asked for — every cell carrying behaviour, need, risk, an
eligible offer, consent and an action — and then four attacks on it, because a
grid on its own is a description.
RFM is computed as prescribed, then measured. On a subscription, recency has
no variance at all — one distinct value across the base, because everybody was
invoiced last month, because that is what a billing run does — and frequency
correlates with
tenure_monthsat 1.0000: not strongly related, the samevariable. Two of the three letters read the company's own schedule. The repair
is a different event, not a different formula: recency rebuilt on app logins
separates churn three times as well as the best of the original three.
The grid: 3 of 9 cells clear the cost of contacting their average member.
The deliverable is mostly a list of people not to contact. Segment profiles are
computed from the features, never written — what comes back is the data model's
own causal design, recovered rather than asserted.
45% of the base changes cell in six months, 41.3% of it on the risk axis
against 6.0% on value: the grid crosses a photograph with a film. Meanwhile the
largest change in any segment's size is 2.3%, so a segment-size dashboard is
flat while half the people inside have swapped places. Measured cross-fitted —
the in-sample version reports 40.2%, so the comfortable method understates, which
is the direction that matters.
Two of the playbook's own plays are refused by the catalogue rather than by
policy. Grow is told to offer the next plan up and 294 of its 494 customers
are already on it or better — the cell is defined as the top third by value,
which is mechanically the customers at the top of the plan ladder. Both are left
in and reported rather than quietly fixed: nothing errors when a play does not
apply, the campaign just under-delivers.
Priced against case 02's continuous ranking on the same budget, the grid loses
9.1% at 75.7% overlap. Not an argument against segmenting — an argument
against segmenting for this. Rank to choose who, segment to choose what.
What was withdrawn
An earlier draft claimed reachability falls monotonically with risk. A test
contradicted it — on the 800-customer seed and on the 5,000-customer default —
so the claim was cut back to the gap between the extreme thirds (78.5% vs
64.4%), whose direction holds across three seeds while its size does not. The
report now says which of the two it is licensed to assert, and a test asserts the
flag that licenses it is derived rather than hardcoded.
Reuse, so the cases cannot drift
Case 02's model, feature builder, population definition and profit accounting;
case 03's permission layer; case 05's measured save rate (12.4%) and quarantined
answer key. No case reimplements a neighbour's definition, and this one never
opens
churn_potential_outcomesitself.The playbook lives in
playbook.csvrather than in the scoring code, for the reason case 03 moved the contact policy
into a table.
Verification
ruff check .clean at monorepo and track level.cropped legend, a truncated definition, a
0%that was<1%, and "aupgrade"). Chart height is computed from content, so none of them ship a dead
band.
contact anyone" was inferred from the action text, and a play reading "no
contact this wave" contains the word. It is a declared column now.
🤖 Generated with Claude Code
https://claude.ai/code/session_01ANTzZtAz4hMbK9oBv2X9sC