Skip to content

feat(customer-analytics): case 01 — actionable segmentation - #5

Merged
ronaldmego merged 1 commit into
mainfrom
feat/customer-analytics-segmentation
Jul 26, 2026
Merged

feat(customer-analytics): case 01 — actionable segmentation#5
ronaldmego merged 1 commit into
mainfrom
feat/customer-analytics-segmentation

Conversation

@ronaldmego

Copy link
Copy Markdown
Owner

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_months at 1.0000: not strongly related, the same
variable. 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_outcomes itself.

The playbook lives in playbook.csv
rather 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.
  • 106 tests pass across the track (23 new).
  • Two consecutive runs are byte-for-byte identical.
  • All four SVGs rendered and inspected; four layout defects found and fixed (a
    cropped legend, a truncated definition, a 0% that was <1%, and "a
    upgrade"). Chart height is computed from content, so none of them ship a dead
    band.
  • A real defect caught by its own test and kept as a regression: "does this play
    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

…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
@ronaldmego
ronaldmego merged commit 898403f into main Jul 26, 2026
3 checks passed
@ronaldmego
ronaldmego deleted the feat/customer-analytics-segmentation branch July 26, 2026 18:33
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