Skip to content

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.

json
{ "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) }}"
  } }
bash
phony generate --use @demo/weighted:plan --package ./weighted --seed 3 -n 6 -f jsonl
json
{"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 on x. It errors if nothing matches and there's no default, so always give one.
json
{ "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')) }}"
  } }
bash
phony generate --use @demo/weighted:status --package ./weighted --seed 42 -n 5 -f jsonl
json
{"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:

json
{ "name": "@demo/weighted:sku",
  "variants": [
    { "pattern": "SKU-{{ pattern:#### }}",   "weight": 8 },
    { "pattern": "PROMO-{{ pattern:@@@ }}",  "weight": 2 },
    { "pattern": "LEGACY-{{ pattern:### }}", "weight": 0 }
  ] }
bash
phony generate --use @demo/weighted:sku --package ./weighted --seed 42 -n 6 -f jsonl
json
"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}]:

json
{ "name": "@demo/weighted:role",
  "output": {
    "role": "{{ pick_weighted([{value: 'admin', weight: 1}, {value: 'member', weight: 9}]) }}"
  } }
bash
phony generate --use @demo/weighted:role --package ./weighted --seed 5 -n 6 -f jsonl
json
{"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 ​

  • switch needs a default (unless you can guarantee a match) — a no-match, no-default switch is a generation error, not a silent null.
  • switch uses key: value pairs, not comma-separated pairs: switch(plan, 'free': 1, default: 1).
  • Weight 0 means never, not "rarely." To make something rare, give it a small positive weight.
  • == is type-aware. 1 == '1' is true (both numeric), but true == 'true' is false. See the coercion rules in Expressions (PEL).

See also ​

Phony Cloud — Documentation & Specification