Bottom-up cost roll-up engine (recipe BOM -> cost/margin) - follow-up C from #98 [depends on A] #102

Closed
opened 2026-07-07 06:54:04 +00:00 by coilysiren · 2 comments
Owner

The bottom-up cost roll-up engine from the #98 design: the piece that turns recipe definitions into a per-item cost, so margin and "is this worth crafting" become answerable. This is follow-up C.

Depends on #98-A (the recipe BOM data). Do not start until A has landed.

Scope

  • A pure-Python engine that, given a recipe id, recursively rolls up cost over the bill-of-materials: ingredient cost (resolving each leaf ingredient's price from market.py median) + labor (calories) + calorie cost + time, following the ingredient edges A exposes. Handles recipe variants and category/tag inputs, guards against cycles, and degrades gracefully when a leaf has no market price (surface it as "unpriced input", do not silently zero it).
  • Expose the roll-up where the consumers need it (a cost.py module + whatever /preview/*.json field the recipes page and pricing page read).
  • This powers margin everywhere: the value-per-profession page (D) upgrades from revenue-ranking to true margin, and the "how to price X" page (E) gets its cost-breakdown + margin panels.

Done

Unit-tested under tests/ (fixture recipes with known BOMs, including a multi-level tree and an unpriced-leaf case). Green on ward exec test + lint. Update docs/FEATURES.md if it adds a surface. Full design context and the "valuable" definition it feeds on #98.

The bottom-up cost roll-up engine from the #98 design: the piece that turns recipe definitions into a per-item cost, so margin and "is this worth crafting" become answerable. This is follow-up **C**. **Depends on #98-A (the recipe BOM data).** Do not start until A has landed. ## Scope - A pure-Python engine that, given a recipe id, recursively rolls up cost over the bill-of-materials: ingredient cost (resolving each leaf ingredient's price from `market.py` median) + labor (calories) + calorie cost + time, following the ingredient edges A exposes. Handles recipe variants and category/tag inputs, guards against cycles, and degrades gracefully when a leaf has no market price (surface it as "unpriced input", do not silently zero it). - Expose the roll-up where the consumers need it (a `cost.py` module + whatever `/preview/*.json` field the recipes page and pricing page read). - This powers margin everywhere: the value-per-profession page (D) upgrades from revenue-ranking to true margin, and the "how to price X" page (E) gets its cost-breakdown + margin panels. ## Done Unit-tested under `tests/` (fixture recipes with known BOMs, including a multi-level tree and an unpriced-leaf case). Green on `ward exec` test + lint. Update `docs/FEATURES.md` if it adds a surface. Full design context and the "valuable" definition it feeds on #98.
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-eco-app-102 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-07T07:17:19Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

run seed context — what this run is carrying (ward#609)
  • Resolved: coilyco-gaming/eco-app#102 · branch issue-102 · driver claude · workflow direct-main
  • Run: engineer-claude-eco-app-102 · ward v0.419.0 · dispatched 2026-07-07T07:17:19Z
  • Comment thread: 0 included in the pre-flight read, 0 stripped (ward's own automated comments).

Issue body as seeded:

The bottom-up cost roll-up engine from the #98 design: the piece that turns recipe definitions into a per-item cost, so margin and "is this worth crafting" become answerable. This is follow-up **C**.

**Depends on #98-A (the recipe BOM data).** Do not start until A has landed.

## Scope

- A pure-Python engine that, given a recipe id, recursively rolls up cost over the bill-of-materials: ingredient cost (resolving each leaf ingredient's price from `market.py` median) + labor (calories) + calorie cost + time, following the ingredient edges A exposes. Handles recipe variants and category/tag inputs, guards against cycles, and degrades gracefully when a leaf has no market price (surface it as "unpriced input", do not silently zero it).
- Expose the roll-up where the consumers need it (a `cost.py` module + whatever `/preview/*.json` field the recipes page and pricing page read).
- This powers margin everywhere: the value-per-profession page (D) upgrades from revenue-ranking to true margin, and the "how to price X" page (E) gets its cost-breakdown + margin panels.

## Done

Unit-tested under `tests/` (fixture recipes with known BOMs, including a multi-level tree and an unpriced-leaf case). Green on `ward exec` test + lint. Update `docs/FEATURES.md` if it adds a surface. Full design context and the "valuable" definition it feeds on #98.

Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.419.0).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-eco-app-102` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-07T07:17:19Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <details><summary>run seed context — what this run is carrying (ward#609)</summary> - **Resolved:** `coilyco-gaming/eco-app#102` · branch `issue-102` · driver `claude` · workflow `direct-main` - **Run:** `engineer-claude-eco-app-102` · ward `v0.419.0` · dispatched `2026-07-07T07:17:19Z` - **Comment thread:** 0 included in the pre-flight read, 0 stripped (ward's own automated comments). **Issue body as seeded:** ``` The bottom-up cost roll-up engine from the #98 design: the piece that turns recipe definitions into a per-item cost, so margin and "is this worth crafting" become answerable. This is follow-up **C**. **Depends on #98-A (the recipe BOM data).** Do not start until A has landed. ## Scope - A pure-Python engine that, given a recipe id, recursively rolls up cost over the bill-of-materials: ingredient cost (resolving each leaf ingredient's price from `market.py` median) + labor (calories) + calorie cost + time, following the ingredient edges A exposes. Handles recipe variants and category/tag inputs, guards against cycles, and degrades gracefully when a leaf has no market price (surface it as "unpriced input", do not silently zero it). - Expose the roll-up where the consumers need it (a `cost.py` module + whatever `/preview/*.json` field the recipes page and pricing page read). - This powers margin everywhere: the value-per-profession page (D) upgrades from revenue-ranking to true margin, and the "how to price X" page (E) gets its cost-breakdown + margin panels. ## Done Unit-tested under `tests/` (fixture recipes with known BOMs, including a multi-level tree and an unpriced-leaf case). Green on `ward exec` test + lint. Update `docs/FEATURES.md` if it adds a surface. Full design context and the "valuable" definition it feeds on #98. ``` Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.419.0). </details> <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - cost.py roll-up engine + /preview/recipes.json?cost=1, merged to main (c1d504a)

This one was mostly a design problem wearing an implementation problem's clothes. The recursion, cycle guard, and tag/variant handling were straightforward once the make-or-buy framing clicked. What actually fought back was the cost decomposition: I went around twice on whether labor/calories should roll up full-tree or per-recipe, because the money and the reported calorie figure kept ending up at different scopes. Landed on: money is fully recursive (a crafted sub-ingredient's whole cost, labor included, is inside its make-or-buy price), while the top card's laborCost/laborCalories report just that recipe's marginal step. Clean and consistent, but it took a rewrite to see it.

The #98 design deliberately left caloriePrice/minutePrice unquantified, so I made them caller-supplied and defaulting to 0 rather than inventing a number - the raw calorie/minute totals always ride the payload, money only monetizes when a rate is given. That felt like the honest call but it means margin-by-default is ingredient-only until D/E pass a rate.

Confidence is high on the engine: it's pure, and the route test annotates all 1,453 real recipes without hanging, which is the real cycle/scale proof. Hit a merge conflict in FEATURES.md against #101 (the recipes browse page, B) - both wanted the same slot; kept both, ordered A->B->C.

Rough edges worth filing: byproducts aren't credited against primary cost yet (conservative over-estimate), and there's no frontend surface here - the pricing page (E) and value-per-profession (D) are where this actually gets seen. tableTierRequired is still null; C was expected to derive it but that's a separate concern from cost roll-up and I left it untouched.

WARD-OUTCOME: done - cost.py roll-up engine + /preview/recipes.json?cost=1, merged to main (c1d504a) This one was mostly a design problem wearing an implementation problem's clothes. The recursion, cycle guard, and tag/variant handling were straightforward once the make-or-buy framing clicked. What actually fought back was the cost *decomposition*: I went around twice on whether labor/calories should roll up full-tree or per-recipe, because the money and the reported calorie figure kept ending up at different scopes. Landed on: money is fully recursive (a crafted sub-ingredient's whole cost, labor included, is inside its make-or-buy price), while the top card's laborCost/laborCalories report just that recipe's marginal step. Clean and consistent, but it took a rewrite to see it. The #98 design deliberately left caloriePrice/minutePrice unquantified, so I made them caller-supplied and defaulting to 0 rather than inventing a number - the raw calorie/minute totals always ride the payload, money only monetizes when a rate is given. That felt like the honest call but it means margin-by-default is ingredient-only until D/E pass a rate. Confidence is high on the engine: it's pure, and the route test annotates all 1,453 real recipes without hanging, which is the real cycle/scale proof. Hit a merge conflict in FEATURES.md against #101 (the recipes browse page, B) - both wanted the same slot; kept both, ordered A->B->C. Rough edges worth filing: byproducts aren't credited against primary cost yet (conservative over-estimate), and there's no frontend surface here - the pricing page (E) and value-per-profession (D) are where this actually gets seen. tableTierRequired is still null; C was expected to derive it but that's a separate concern from cost roll-up and I left it untouched.
Sign in to join this conversation.
No description provided.