Generate Bounds for Extracted Parameters
Overview
Wraps the bounds-estimator system prompt at system-prompt.txt (next to this file) and applies it to a parameter JSON produced by extract-parameters-from-full (and ideally already passed validate-parameters). Output is a strict JSON object keyed by variable id, with low / base / high / unit / rationale / source for every variable that needs an assumption range.
Stage 4 of the pipeline described in planexe_simulator/README.md.
When to Use
- User asks to "generate bounds", "estimate ranges", "add low/base/high", or "prepare for scenarios" given an extract-parameters-from-full JSON
- User wants to fill in assumptions for
missing_values_to_estimate and uncertain key_values before running deterministic scenarios or Monte Carlo
- Pipeline step between
validate-parameters (passes clean) and generate-calculations / run-scenarios
Not for: regenerating the parameter JSON (use extract-parameters-from-full), validating the JSON (use validate-parameters), or producing Python code (use generate-calculations).
Workflow
- Get the input JSON path. If the user did not provide one, ask. Do not guess.
- Read
system-prompt.txt (sibling of this SKILL.md). Its selection rules and spread heuristics are authoritative.
- Read the parameter JSON. Assume it has already passed
validate-parameters; if it visibly hasn't, tell the user and offer to validate first.
- Produce the bounds JSON per the system prompt.
- Self-audit before output. For each bound entry, verify:
- Every numbered citation in
rationale (Risk N, Issue N, Decision N) refers to content that substantively supports the claim — not just lexically present.
- For variables feeding a declared gate threshold, the base-vs-threshold relationship is consistent with the rationale. If base implies base-case gate failure, the rationale names a report-internal anchor that justifies the shift.
source: "data" entries name an explicit anchor in the rationale.
- For asymmetric bounds, the rationale notes the asymmetry's source.
If any check fails, revise the bound; do not ship inconsistent state.
- Output destination. Default: print the JSON to chat. If the user asks for a file, write to the path they specify. Suggested default file path:
<input-basename>.bounds.json next to the input.
Selection Rules (re-stated for emphasis — see system prompt for full detail)
Generate one bounds entry for every id that meets ANY of:
- it appears in
missing_values_to_estimate
- it is a
key_value with value_type ∈ {inferred, missing_but_needed}
- it is a
key_value with value == null
- it is a
key_value with uncertainty == high
- it is a
key_value with uncertainty == medium AND modelling_priority ∈ {critical, high}
Skip: explicit/derived key_values with low uncertainty, derived_questions, recommended_first_calculations, and formula LHS-only outputs that are not declared as inputs.
Spread by Uncertainty
| Uncertainty |
Spread around base |
| low |
±10–20% |
| medium |
±25–50% |
| high |
≥±50%, up to a 2–5× factor when genuinely speculative |
Always low ≤ base ≤ high. Fractions stay in [0, 1]. Counts of discrete things (people, kits, centers, months) use integer bounds.
Output Shape
{
"<variable_id>": {
"unit": "fraction",
"low": 0.10,
"base": 0.20,
"high": 0.30,
"rationale": "Short, ≤50 words. Include required disclosures when applicable.",
"source": "data" | "assumption",
"sampling_discipline": "fraction",
"non_negative": true,
"default_pass_probability": null
}
}
Top-level is a single object keyed by variable id. Order keys roughly by importance (critical → high → medium → remaining missing values). Use "source": "data" only when the range is anchored in a citable real-world reference for similar programs; otherwise "assumption".
Sampling-discipline tag
Each entry MUST carry a sampling_discipline chosen by the LLM based on the variable's nature. Downstream consumers (run-scenarios, monte-carlo runner) read this tag directly and do not pattern-match on unit strings — so the LLM is the single authority for this classification.
| Value |
Meaning |
Downstream behavior |
"fixed" |
low == base == high; genuinely pinned |
Always return that value |
"bernoulli_gate" |
Binary pass/fail (staged funding, permit toggle, regulatory pass, conditional release). Requires default_pass_probability ∈ [0, 1] |
Bernoulli draw; low on fail, high on pass |
"integer" |
Countable units — people, households, sites, kits, days, … |
Sample continuously, round, re-clamp to [low, high] |
"fraction" |
Bounded fraction in [0, 1] |
Clamp draws to [0, 1] |
"continuous" |
Real-valued; no rounding, no extra clamping beyond [low, high] |
Plain triangular/uniform draw |
non_negative is independent of discipline: set true when the variable cannot legitimately go below zero (continuous monetary amounts, counts, capacities); set false only when negative values are physically meaningful (a delta or net change variable).
default_pass_probability is required (a number in [0, 1]) when sampling_discipline == "bernoulli_gate" and MUST be null for every other discipline.
If no variable needs bounds, return {}.
Common Mistakes
| Mistake |
Fix |
| Bounding every variable, including known explicit facts |
Skip explicit/derived key_values with low uncertainty — they are facts, not assumptions |
Returning low == base == high for variables with real uncertainty |
Give a real range; identical bounds are only for genuinely pinned KPI commitments |
Wrapping output in ```json fences |
Raw JSON only |
Including derived_questions or recommended_first_calculations ids |
Those are outputs/questions, not bounded inputs |
| Inventing ids that are not declared in the parameter JSON |
Every key must correspond to a declared id in key_values or missing_values_to_estimate |
Picking fractions outside [0, 1] (e.g. base 1.5 for a rate) |
Clamp to the unit's natural range |
Ignoring the parameter's uncertainty and giving everything the same ±20% spread |
Anchor the spread on the parameter's stated uncertainty level |
| Rationale cites a numbered artifact (Risk N, Issue N, Decision N) that does not substantively support the claim |
Re-read the cited artifact. The number must match content, not just exist in the report. If you cannot find a substantively correct citation, drop the citation and mark source: "assumption". |
Shifting actual_X base past the X_target threshold without a report-internal anchor |
Center base at the committed value unless a named Risk / Issue / Decision / premortem / expert-criticism passage forecasts a gap between commitment and reality. "Realistic execution" and "operational drift" are not anchors. |
Generating bounds for a threshold or target variable (ids ending in _threshold, _target, _ceiling, _floor, _limit) |
Thresholds enter the simulation as their stated single value. Randomising them silently changes what pass_rate measures and breaks the threshold_basis = report_explicit contract. Skip these variables. |
Triangular bounds with base = high = target on a >= target gate variable (ring-fenced reserve, segregated escrow, allocation share, KPI floor that physically cannot overshoot) |
The continuous triangular CDF has zero probability mass at the exact peak — the >= 0 margin gate fails ~100% of runs even when the commitment is intact. The Critical verdict you report is a property of the bound shape, not the plan. Switch to sampling_discipline: "bernoulli_gate" with low = shortfall floor (e.g. 0 or partial-segregation amount), high = the target, and default_pass_probability = realistic execution probability of the commitment (e.g. 0.95 for a signed escrow, 0.75 for a sponsor-committed reserve, 0.5 for a contested allocation). Bernoulli returns high on pass and low on fail, so the gate cleanly tests whether the commitment was executed. |
Reference
- System prompt (authoritative):
system-prompt.txt
- Pipeline overview and "what needs bounds" list:
../../README.md, Stage 4
- Companion skills:
../extract-parameters-from-full/SKILL.md, ../validate-parameters/SKILL.md
- Example input for testing:
/tmp/extract-params-heatwave-v8.json (passes validate-parameters with valid: true)
1---2name: generate-bounds3description: Use when the user wants to generate low/base/high assumption ranges (bounds) for missing or uncertain variables in a validated extract-parameters-from-full JSON, in preparation for deterministic scenarios or Monte Carlo4---56# Generate Bounds for Extracted Parameters78## Overview910Wraps the bounds-estimator system prompt at `system-prompt.txt` (next to this file) and applies it to a parameter JSON produced by `extract-parameters-from-full` (and ideally already passed `validate-parameters`). Output is a strict JSON object keyed by variable id, with `low / base / high / unit / rationale / source` for every variable that needs an assumption range.1112Stage 4 of the pipeline described in `planexe_simulator/README.md`.1314## When to Use1516- User asks to "generate bounds", "estimate ranges", "add low/base/high", or "prepare for scenarios" given an extract-parameters-from-full JSON17- User wants to fill in assumptions for `missing_values_to_estimate` and uncertain `key_values` before running deterministic scenarios or Monte Carlo18- Pipeline step between `validate-parameters` (passes clean) and `generate-calculations` / `run-scenarios`1920Not for: regenerating the parameter JSON (use `extract-parameters-from-full`), validating the JSON (use `validate-parameters`), or producing Python code (use `generate-calculations`).2122## Workflow23241. **Get the input JSON path.** If the user did not provide one, ask. Do not guess.252. **Read `system-prompt.txt`** (sibling of this SKILL.md). Its selection rules and spread heuristics are authoritative.263. **Read the parameter JSON.** Assume it has already passed `validate-parameters`; if it visibly hasn't, tell the user and offer to validate first.274. **Produce the bounds JSON** per the system prompt.285. **Self-audit before output.** For each bound entry, verify:29 - Every numbered citation in `rationale` (Risk N, Issue N, Decision N) refers to content that substantively supports the claim — not just lexically present.30 - For variables feeding a declared gate threshold, the base-vs-threshold relationship is consistent with the rationale. If base implies base-case gate failure, the rationale names a report-internal anchor that justifies the shift.31 - `source: "data"` entries name an explicit anchor in the rationale.32 - For asymmetric bounds, the rationale notes the asymmetry's source.33 If any check fails, revise the bound; do not ship inconsistent state.346. **Output destination.** Default: print the JSON to chat. If the user asks for a file, write to the path they specify. Suggested default file path: `<input-basename>.bounds.json` next to the input.3536## Selection Rules (re-stated for emphasis — see system prompt for full detail)3738Generate one bounds entry for every id that meets ANY of:3940- it appears in `missing_values_to_estimate`41- it is a `key_value` with `value_type ∈ {inferred, missing_but_needed}`42- it is a `key_value` with `value == null`43- it is a `key_value` with `uncertainty == high`44- it is a `key_value` with `uncertainty == medium` AND `modelling_priority ∈ {critical, high}`4546Skip: explicit/derived `key_values` with low uncertainty, `derived_questions`, `recommended_first_calculations`, and formula LHS-only outputs that are not declared as inputs.4748## Spread by Uncertainty4950| Uncertainty | Spread around base |51|---|---|52| low | ±10–20% |53| medium | ±25–50% |54| high | ≥±50%, up to a 2–5× factor when genuinely speculative |5556Always `low ≤ base ≤ high`. Fractions stay in `[0, 1]`. Counts of discrete things (people, kits, centers, months) use integer bounds.5758## Output Shape5960```json61{62 "<variable_id>": {63 "unit": "fraction",64 "low": 0.10,65 "base": 0.20,66 "high": 0.30,67 "rationale": "Short, ≤50 words. Include required disclosures when applicable.",68 "source": "data" | "assumption",69 "sampling_discipline": "fraction",70 "non_negative": true,71 "default_pass_probability": null72 }73}74```7576Top-level is a single object keyed by variable id. Order keys roughly by importance (critical → high → medium → remaining missing values). Use `"source": "data"` only when the range is anchored in a citable real-world reference for similar programs; otherwise `"assumption"`.7778### Sampling-discipline tag7980Each entry MUST carry a `sampling_discipline` chosen by the LLM based on the variable's nature. Downstream consumers (run-scenarios, monte-carlo runner) read this tag directly and do not pattern-match on unit strings — so the LLM is the single authority for this classification.8182| Value | Meaning | Downstream behavior |83|---|---|---|84| `"fixed"` | `low == base == high`; genuinely pinned | Always return that value |85| `"bernoulli_gate"` | Binary pass/fail (staged funding, permit toggle, regulatory pass, conditional release). Requires `default_pass_probability` ∈ [0, 1] | Bernoulli draw; low on fail, high on pass |86| `"integer"` | Countable units — people, households, sites, kits, days, … | Sample continuously, round, re-clamp to `[low, high]` |87| `"fraction"` | Bounded fraction in `[0, 1]` | Clamp draws to `[0, 1]` |88| `"continuous"` | Real-valued; no rounding, no extra clamping beyond `[low, high]` | Plain triangular/uniform draw |8990`non_negative` is independent of discipline: set `true` when the variable cannot legitimately go below zero (continuous monetary amounts, counts, capacities); set `false` only when negative values are physically meaningful (a delta or net change variable).9192`default_pass_probability` is required (a number in `[0, 1]`) when `sampling_discipline == "bernoulli_gate"` and MUST be `null` for every other discipline.9394If no variable needs bounds, return `{}`.9596## Common Mistakes9798| Mistake | Fix |99|---|---|100| Bounding every variable, including known explicit facts | Skip explicit/derived key_values with low uncertainty — they are facts, not assumptions |101| Returning `low == base == high` for variables with real uncertainty | Give a real range; identical bounds are only for genuinely pinned KPI commitments |102| Wrapping output in ```` ```json ```` fences | Raw JSON only |103| Including `derived_questions` or `recommended_first_calculations` ids | Those are outputs/questions, not bounded inputs |104| Inventing ids that are not declared in the parameter JSON | Every key must correspond to a declared id in `key_values` or `missing_values_to_estimate` |105| Picking fractions outside `[0, 1]` (e.g. base 1.5 for a rate) | Clamp to the unit's natural range |106| Ignoring the parameter's `uncertainty` and giving everything the same ±20% spread | Anchor the spread on the parameter's stated uncertainty level |107| Rationale cites a numbered artifact (Risk N, Issue N, Decision N) that does not substantively support the claim | Re-read the cited artifact. The number must match content, not just exist in the report. If you cannot find a substantively correct citation, drop the citation and mark `source: "assumption"`. |108| Shifting `actual_X` base past the `X_target` threshold without a report-internal anchor | Center base at the committed value unless a named Risk / Issue / Decision / premortem / expert-criticism passage forecasts a gap between commitment and reality. "Realistic execution" and "operational drift" are not anchors. |109| Generating bounds for a threshold or target variable (ids ending in `_threshold`, `_target`, `_ceiling`, `_floor`, `_limit`) | Thresholds enter the simulation as their stated single value. Randomising them silently changes what `pass_rate` measures and breaks the `threshold_basis = report_explicit` contract. Skip these variables. |110| Triangular bounds with `base = high = target` on a `>= target` gate variable (ring-fenced reserve, segregated escrow, allocation share, KPI floor that physically cannot overshoot) | The continuous triangular CDF has zero probability mass at the exact peak — the `>= 0` margin gate fails ~100% of runs even when the commitment is intact. The Critical verdict you report is a property of the bound shape, not the plan. Switch to `sampling_discipline: "bernoulli_gate"` with `low` = shortfall floor (e.g. 0 or partial-segregation amount), `high` = the target, and `default_pass_probability` = realistic execution probability of the commitment (e.g. 0.95 for a signed escrow, 0.75 for a sponsor-committed reserve, 0.5 for a contested allocation). Bernoulli returns `high` on pass and `low` on fail, so the gate cleanly tests whether the commitment was executed. |111112## Reference113114- System prompt (authoritative): `system-prompt.txt`115- Pipeline overview and "what needs bounds" list: `../../README.md`, Stage 4116- Companion skills: `../extract-parameters-from-full/SKILL.md`, `../validate-parameters/SKILL.md`117- Example input for testing: `/tmp/extract-params-heatwave-v8.json` (passes validate-parameters with `valid: true`)