| 1 |
# mnw-assumptions |
| 2 |
|
| 3 |
MNW's business numbers as a typed, validated, substitutable source of truth. |
| 4 |
|
| 5 |
Loads `assumptions.toml`, computes derived values (Stripe fee math, ARPU, break-even, |
| 6 |
annual prices, per-creator marginal cost), checks the whole thing for internal |
| 7 |
consistency, and substitutes `{{ dotted.path | filter }}` markers in markdown. |
| 8 |
|
| 9 |
Split out of [docengine](../docengine) on 2026-07-25, along with the generic engine it |
| 10 |
sits on, [subst](../subst). Nothing here is reusable outside MNW; that was the point of |
| 11 |
the split. |
| 12 |
|
| 13 |
```rust |
| 14 |
use mnw_assumptions::Assumptions; |
| 15 |
|
| 16 |
let a = Assumptions::load("docs/business/assumptions.toml")?; |
| 17 |
a.validate()?; |
| 18 |
let resolved = a.substitute(&markdown)?; |
| 19 |
let html = docengine::render_permissive(&resolved); |
| 20 |
``` |
| 21 |
|
| 22 |
Canonical file: `MNW/server/docs/business/assumptions.toml`. The copy under |
| 23 |
`tests/fixtures/` is a vendored snapshot so the crate's own tests don't reach across the |
| 24 |
repo; `server/tests/assumptions.rs` is what tests the live one. |
| 25 |
|
| 26 |
## What it produces |
| 27 |
|
| 28 |
Three views of the same toml: |
| 29 |
|
| 30 |
- A **typed mirror** (serde structs) for the fields validation and derived math need. |
| 31 |
- A **derived registry** under the `derived.` prefix -- `R_cap`, `ARPU_founding`, |
| 32 |
`ARPU_standard`, `stripe_fee_*`, `break_even_*`, `annual_<class>_<tier>`, |
| 33 |
`marginal_*`, the discount percentages. |
| 34 |
- A **flat lookup table** of every key in the file, dotted-path keyed, which is what |
| 35 |
substitution reads. Unknown sections are walked into it too, so a doc author can add an |
| 36 |
ad-hoc key to the toml and use it in markdown without touching Rust. |
| 37 |
|
| 38 |
## Validation |
| 39 |
|
| 40 |
`validate()` collects every failure rather than stopping at the first, and returns them |
| 41 |
as one `AssumptionsError::Validation(Vec<String>)`. The rules: |
| 42 |
|
| 43 |
- `expenses.F_monthly` inside (100, 10000) -- a typo guard on the burn rate |
| 44 |
- `tier_mix.assumed.*` sums to 1.0 |
| 45 |
- `reserve.surplus_split_{reserve,earnback}` sums to 1.0 |
| 46 |
- `reserve.rho_annual`, `reserve.rho_incident` inside (0, 1], and incident <= annual |
| 47 |
- every founding tier price <= the matching standard price |
| 48 |
- `cohort.cap_count > 0`, `cohort.cap_months > 0` |
| 49 |
- each `tier_bytes.<k>` parses back to the display string in `tier_limits.<k>` |
| 50 |
|
| 51 |
That last one is the shape to copy when a number has both a machine form and a |
| 52 |
human-facing form. The server adds one more of its own in `tests/assumptions.rs`: the |
| 53 |
`[synckit]` block must match the Rust constants in `src/synckit_billing.rs`, which stay |
| 54 |
authoritative. |
| 55 |
|
| 56 |
## Filters |
| 57 |
|
| 58 |
Everything `subst` ships (`int`, `ceil`, `floor`, `round`, `money`, `percent`, `upper`, |
| 59 |
`lower`), registered by default. Add MNW-specific ones with `with_filter(name, f)` before |
| 60 |
substituting. `LookupValue` is a re-export of `subst::Value` under its historical name. |
| 61 |
|
| 62 |
## substitute_dir |
| 63 |
|
| 64 |
``` |
| 65 |
substitute_dir <assumptions.toml> <docs_root> [--check] |
| 66 |
``` |
| 67 |
|
| 68 |
Walks a markdown tree and substitutes in place. `--check` writes nothing and exits |
| 69 |
non-zero if any file would change or any marker failed to resolve. Built for the |
| 70 |
`_private/docs/` mirror, which cites business numbers but does not go through the boot-time |
| 71 |
pipeline the public site-docs corpus does. |
| 72 |
|
| 73 |
## License |
| 74 |
|
| 75 |
MIT. |
| 76 |
|