How to weight choices and branch on values
Real data isn't uniform: most accounts are on the free plan, most orders complete, a few do neither. And downstream fields usually depend on those choices — a plan implies a seat count, a score implies a grade. This recipe covers both: weighting a draw and branching on the result.
Weight a categorical draw
A list (or statistical) source whose values are { "value", "weight" } objects draws by weight. Higher weight = more frequent.
{ "name": "@demo/weighted:plan",
"let": {
"plan": { "type": "list", "source": "inline", "values": [
{ "value": "free", "weight": 70 },
{ "value": "pro", "weight": 25 },
{ "value": "enterprise", "weight": 5 }
] }
},
"output": {
"plan": "{{ plan }}",
"seats": "{{ switch(plan, 'free': 1, 'pro': 10, 'enterprise': 100, default: 1) }}",
"trial": "{{ if(plan == 'free', true, false) }}"
} }phony generate --use @demo/weighted:plan --package ./weighted --seed 3 -n 6 -f jsonl{"plan":"free","seats":1,"trial":true}
{"plan":"free","seats":1,"trial":true}
{"plan":"free","seats":1,"trial":true}
{"plan":"enterprise","seats":100,"trial":false}
{"plan":"free","seats":1,"trial":true}
{"plan":"free","seats":1,"trial":true}free dominates, enterprise is rare — and seats/trial are derived from whatever plan was drawn, so they always agree with it.
Weights are relative, not percentages — 70/25/5 and 14/5/1 behave the same.
Branch with if and switch
Both are PEL functions usable anywhere an expression is (a computed let, an output leaf, a transform).
if(cond, then, else)— a two-way branch; nest them for more arms.switch(x, k1: v1, k2: v2, …, default: v)— a multi-way match onx. It errors if nothing matches and there's nodefault, so always give one.
{ "name": "@demo/weighted:status",
"let": {
"score": { "type": "logic", "algorithm": "int_between", "params": { "min": 0, "max": 100 } }
},
"output": {
"score": "{{ score }}",
"grade": "{{ if(score >= 90, 'A', if(score >= 70, 'B', 'C')) }}"
} }phony generate --use @demo/weighted:status --package ./weighted --seed 42 -n 5 -f jsonl{"grade":"C","score":55}
{"grade":"C","score":9}
{"grade":"C","score":16}
{"grade":"C","score":45}
{"grade":"C","score":43}Weighted variants — a choice of whole templates
When the shape of the value differs (not just a field), use weighted variants: the composition picks one body template by weight. A variant with weight 0 is never selected — handy for retiring a format while keeping it documented:
{ "name": "@demo/weighted:sku",
"variants": [
{ "pattern": "SKU-{{ pattern:#### }}", "weight": 8 },
{ "pattern": "PROMO-{{ pattern:@@@ }}", "weight": 2 },
{ "pattern": "LEGACY-{{ pattern:### }}", "weight": 0 }
] }phony generate --use @demo/weighted:sku --package ./weighted --seed 42 -n 6 -f jsonl"SKU-5614"
"SKU-3479"
"SKU-3497"
"SKU-0481"
"PROMO-uhl"
"SKU-1279"Roughly 80% SKU, 20% PROMO, and never a LEGACY (its weight is 0). If every variant's weight were 0, the package would fail to load.
pick_weighted — weight inside an expression
To weight a choice inline in a PEL expression (rather than as a list source), use pick_weighted over [{value, weight}]:
{ "name": "@demo/weighted:role",
"output": {
"role": "{{ pick_weighted([{value: 'admin', weight: 1}, {value: 'member', weight: 9}]) }}"
} }phony generate --use @demo/weighted:role --package ./weighted --seed 5 -n 6 -f jsonl{"role":"member"}
{"role":"member"}
{"role":"admin"}
{"role":"member"}
{"role":"member"}
{"role":"member"}As with variants, a weight of 0 is never selected and all-zero weights are an error.
Pitfalls
switchneeds adefault(unless you can guarantee a match) — a no-match, no-defaultswitchis a generation error, not a silentnull.switchuseskey: valuepairs, not comma-separated pairs:switch(plan, 'free': 1, default: 1).- Weight
0means never, not "rarely." To make something rare, give it a small positive weight. ==is type-aware.1 == '1'istrue(both numeric), buttrue == 'true'isfalse. See the coercion rules in Expressions (PEL).