ROI model
Most architecture ROI sections are a single confident number with no source.
gate.economics exists because a number nobody can trace back to a price list
is not an estimate — it's a guess wearing a decimal point. This skill produces
the opposite: every price sourced, the drivers that actually swing the answer
stress-tested, and the output expressed with the honesty a range gives that a
point estimate doesn't.
How this skill is used
The economics-analyst agent runs this skill during cost to write 04-roi.md
and prices.json. Both draw on 03-experience-blueprint.md (what the system
actually does, stage by stage — that's where the unit counts come from) and on
this repo's prices/prices.json as a starting reference, refreshed against
live sources rather than copied blind.
The method
- List the cost drivers from the blueprint. Every AI/Agents cell in
03-experience-blueprint.mdthat calls a model, a vector store, a function, storage, or a workflow runtime is a line item. Don't invent categories the blueprint doesn't contain. - Price each driver with a sourced row. One row per priced item in
prices.json— see the contract below. No unsourced numbers anywhere in04-roi.md; if a price can't be found live, the line is marked[unknown]in the narrative, not filled with a plausible-sounding guess. - Estimate volume from the discovery and flow artifacts, not from a round number picked for convenience. If discovery didn't establish volume, say so and mark the model's confidence accordingly — a volume estimate with no source is exactly the kind of unsourced number this gate exists to catch.
- Compute cost per run and cost at the stated volume. Show the arithmetic inline — a reader should be able to re-derive the total from the unit prices and the volume without guessing an intermediate step.
- Run the sensitivity band (below) on the three drivers with the largest effect on the total.
- State payback as a range, not a point (below), and never as an IRR (below).
The prices.json contract
{
"schema": "ai-architect.prices.v1",
"rows": [
{
"item": "Model tokens — <provider-neutral name>, input",
"unit": "per 1M input tokens",
"unit_price": 0.0,
"currency": "USD",
"source_url": "https://...",
"retrieved_at": "2026-08-22",
"notes": ""
}
]
}
Rules gate.economics enforces:
- Every row needs
source_urlandretrieved_at, andretrieved_atmust be ≤ 90 days old at the time04-roi.mdis written. A price from a stale row gets re-fetched before it's used, not carried forward on the assumption nothing changed — pricing pages move without notice. - If a live source can't be fetched, omit the row. A missing row is visible
and honest; a guessed row is neither.
04-roi.mdshould say plainly which cost category has no sourced price yet, rather than filling the gap. - Use capability nouns in
item, not vendor SKUs, per the plugin's vendor-neutral rule — "model tokens," "managed vector store," "serverless function time," "object storage," "durable workflow runtime." A specific vendor may appear innotesas a worked example of where the number came from, never as the only name for the category. - Reuse
this repo'sprices/prices.jsonas a starting point, not a cache. Re-verify any row you rely on against itssource_urlbefore using it in a customer's04-roi.md— the reference file has its own retrieval date, and that date is not the customer engagement's retrieval date.
Sensitivity method
Pick the three drivers with the largest effect on total cost — usually volume, the dominant model's price, and one system-shaped cost (a vector store, a workflow runtime, a serverless compute line). For each, show the total at a low, base, and high case:
| Driver | Low | Base | High | Effect on annual total |
|---|---|---|---|---|
| Monthly ticket volume | 2,000 | 5,000 | 12,000 | $X → $Y → $Z |
| Dominant model output price ($/1M tok) | 8 (batch rate) | 10 | 15 (peak or fallback tier) | $X → $Y → $Z |
| Vector store read units (1M/mo) | 1 | 4 | 10 | $X → $Y → $Z |
Pick low/high bounds from real alternatives — a batch discount, a documented retry rate, a plausible volume range from the discovery interview — not from an arbitrary ±20%. A sensitivity band built on invented bounds is exactly as unsourced as a single guessed number, just with three of them.
The no-IRR rule
Never write the string IRR. Internal rate of return implies a cash-flow
model with a discount rate and a defined investment horizon that a
docs/architecture/ artifact almost never has — the "investment" is usually a
mix of engineering time, ongoing token spend, and avoided cost that don't net
into a single rate without assumptions nobody stated. Reporting an IRR anyway
manufactures false precision: a rate to two decimal places built on a discount
rate someone picked without saying so. Report cost per unit of work, total at
volume, and payback range instead — each of those is checkable against the
sourced prices; an IRR is not.
Expressing payback as a range
State payback the same way the sensitivity band states cost: as a range tied to the low/base/high volume or price cases, not as a single date.
Payback: 3-7 months at 5,000 tickets/month (base case), assuming the avoided
cost is [sourced comparison, e.g. "the fully-loaded cost of the manual triage
step this replaces, per discovery §2"]. Fast end (3 months) assumes volume at
the sensitivity table's high case; slow end (7 months) assumes base-case
volume and standard (non-batch) token pricing.
If the avoided cost the payback depends on isn't sourced from discovery or a verifiable figure, say the payback range can't be computed yet and name what's missing — a wide guessed range is no more honest than a wrong point estimate.
Close 04-roi.md with the stamp line: Generated by AI Architect · https://www.frankx.ai/ai-architect