fix(data): decorrelate the 30-case demo volume generator - #1671
Merged
Conversation
The generator read ONE counter for every axis it varies: `crm_account` and `status` both took `i % 5`, and `priority`, `type` and `origin` all took `i % 4`. Equal-length lists walked by the same index advance in lockstep, so account and status came out perfectly correlated — 5 of the 25 cross-tab cells occupied — and priority, type and origin were a three-way lockstep holding 4 of 16 cells apiece. A generator whose whole purpose is volume WITH variety was producing volume with none: filtering the case list by account returned a single status every time. Each list now advances one step per row PLUS a rotation, so no two axes stay in step. `crm_account` is the anchor; `status` rotates one step per pass through the accounts, which makes account × status complete by construction rather than by luck — account `a` takes rows a, a+5 … a+25, so its six statuses are `(a + b) % 5` over b = 0…5, all five of them. The `priority`, `type` and `origin` rotations are tuned, not derived: they were chosen by computing all ten pairwise cross-tabs and keeping the assignment that left the fewest empty cells. The block comment records that, and says to re-measure if a list's length changes. Measured over the shipped lists: pairwise cell occupancy 137/193 before, 188/193 after; no cell holds more than 3 of the 30 rows; marginals stay flat at 6/6/6/6/6 by account and by status and 8/7/7/8 by priority, type and origin. Every account now shows all five statuses. The eight hand-authored cases are untouched, as are the seeded accounts, events and tasks. The #638 territory partition and the #671 activity bands are unmoved. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018xtjdpZFjgWh4Ad9Wcx68J
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
os-steve
marked this pull request as ready for review
September 6, 2026 08:53
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.
Description
The 30-case demo volume generator in
src/data/service.seed.tsread onecounter for every axis it varies.
crm_accountandstatusboth tooki % 5,and
priority,typeandoriginall tooki % 4. Equal-length lists walkedby the same index advance in lockstep, so each group came out perfectly
correlated. A generator whose declared purpose is volume with variety was
producing volume with none.
This decorrelates every axis onto a stated distribution, and reports the
measurement rather than an impression.
Type of Change
Related Issues
Closes #1659
The measurement — account x status cross-tab
Computed from the real seed by importing
cases.recordsand cross-tabbing the30 generated rows, before and after.
Before — 5 of 25 cells occupied, a perfect one-to-one map:
After — 25 of 25 cells occupied, every account showing all five statuses:
The other axes — status was not the only correlated pair
priority,typeandoriginall readi % 4, so they were a three-waylockstep, not merely correlated with each other in passing: every
lowcase wasa
questionraised byemail, everymediumabugbyphone, everyhighaproblemfromweb, everycriticalafeature_requestfromchat. Thegenerated subjects said so out loud —
Demo case 02 — medium bugrepeated everyfour rows.
All ten pairwise cross-tabs, cells occupied:
created_datewas checked too.ageDays = 1 + (i % 30)over 30 rows makes theday a bijection with the row index, so every axis is trivially a function of it;
the artifact that matters is a short fixed period. Before, all five axes
walked the calendar on a constant stride (accounts and statuses every 5th day,
priorities/types/origins every 4th). After, only
crm_accountdoes — see thetrade-off below.
Changes Made
status,priority,typeandorigineach advance one step per row plusa rotation, so no two axes stay in step.
crm_accountremains the anchor.account x statusis complete by construction, not by luck: accountatakes rows
a, a+5 … a+25, whose block numbers run0…5, so its six statusesare
(a + b) % 5overb = 0…5— all five. The 30 rows walk the whole 5 x 5grid, 25 cells once and five of them twice.
priority/type/originrotations are tuned, not derived. Theywere chosen by computing all ten cross-tabs and keeping the assignment with
the fewest empty cells. The block comment says so, and says to re-measure if
any list's length changes — there is no gate holding this (AGENTS.md:431).
(
subject,crm_account,type,origin,sla_due_dateall read them).Why this is not just another coincidence
Two schemes that read as more principled than they measure were tried and
rejected on the numbers, and are recorded in the block comment so they are not
re-derived:
lengths are coprime so they cannot resonate." Measured: it empties one
status completely (marginals
8,8,7,7,0) and dropsstatus x priorityto4 of 20 cells — worse than the defect.
crm_accounttoo, so it stops landing on every fifth day."Measured: costs twelve further empty cells (5 → 17 missing) across the other
pairs. The every-fifth-day beat is the deliberate price of anchoring — with
one row per day, some axis has to be the fixed one the other four are measured
against. Stated beside the code per the deliberate-deviation rule.
Marginals are as flat as 30 rows allow and, for the two 5-element axes,
unchanged from before: 6/6/6/6/6 by account and by status, 8/7/7/8 by priority,
type and origin.
Second-order effect the card called out
Acme's related list held 8 open cases against a hand-written description
speaking of one worked ticket plus one billing dispute. Verified: 8 Acme rows, 2
hand-authored + 6 generated, and all 6 generated were
newby construction.After, Acme has 7 open cases spanning five different statuses; one generated row
is now legitimately
closed. The hand-authored description is not touched —it is true of the hand-authored set, and PR #1657 deliberately kept it.
Testing
pnpm verifygreen end to endpnpm verify=validate && typecheck && lint && lint:i18n-gate && hygiene && hygiene:tokens && build && test, all eight stages green:Partitions re-measured after the change, as the card requires:
No partition moved. The named assertions pass by name —
partitions the seeded accounts across NA, EU and neither,populates each of the 30 / 60 / 90-day bands,stratifies the three churn tiles instead of repeating one number. Thestronger statement: the diff touches
src/data/service.seed.tsand nothingelse, and inside it only the generated-case block, so the accounts, events and
tasks those partitions read are byte-identical to
main. That also covers thecsm_flagged_accountsgrouping PR #1657 just changed — its input,src/data/sales.seed.ts, is untouched.Additional Notes
Generated demo-case subjects change, because they name the row's priority and
type and those now vary independently (
Demo case 06 — high feature_requestwhere it read
medium bug). Subject is the upsert identity for these rows, so ademo database seeded before this change keeps the old rows alongside the new
ones until
pnpm demo:reset. Called out in the changeset.is_sla_violatedmoves from 1 generated row to 2. The rule is unchanged; thecount moves because which rows are settled moved.
seed-consistency's breachassertion — flagged only when open and genuinely past due — still passes.
Checklist
.changeset/decorrelate-the-case-volume-generator.md🤖 Generated with Claude Code
https://claude.ai/code/session_018xtjdpZFjgWh4Ad9Wcx68J
Generated by Claude Code