Cost-of-goods + margin-driven pricing — derive menu prices from material costs #6

Open
opened 2026-05-10 14:50:23 +00:00 by padreug · 0 comments
Owner

Tracks the COGS / pricing layer of #2 (bistro and full tiers).

Background

The user's vision:

If you purchase materials etc so you could potentially automate
calculation [of] the cost of finished products by simply specifying
[a] desired profit margin.

there's an accounting module too, i really want to tie it all
together and offer it as a package to similar projects.

Restaurants today set menu prices by gut feel + competitor-watching,
or by spreadsheet labor. We have all the inputs already in the
extension (or imminent in #3): we know what each material cost, we
know what goes into each item, we know labor / overhead allocation.
Adding a margin slider gives a derived recommended price — and ties
naturally into the libra (Beancount-flavored) accounting module
the user maintains in a sibling extension.

What this issue owns

  1. Cost roll-up — for any menu item, compute the COGS by
    walking its #3 and aggregating
    material_batch.unit_cost_msat * recipe_line.qty_per_serving.
    Average over the most-recent N batches (FIFO or rolling avg —
    configurable).
  2. Margin → price helper — given a target margin %, suggest a
    menu price. Operator can:
    • accept the suggestion → menu_items.price is updated;
    • lock the price manually → suggestion is shown alongside as a
      warning when actual margin diverges;
    • leave it auto → price recalculates whenever a new
      material_batch lands.
  3. Live margin badge in the CMS — every item shows current
    COGS, current margin %, and a colored indicator when margin
    drifts under a configurable floor.
  4. Integration with libra — every paid order emits an
    accounting record (revenue line + COGS line + tax line)
    shaped for libra's ledger consumption. Either:
    • direct Python call into libra's service, or
    • emit a Nostr event of a known kind that libra subscribes to.
      Decide as part of this issue.

Behavior

  • Pricing modes per item: manual | auto | suggest.
  • Recipe yield: a recipe might produce 4 servings (a single
    pot of soup → 4 bowls). COGS divides by yield_qty.
  • Modifier costs: each modifier carries a cost_msat_delta
    derived the same way from its own mini-recipe. The customer
    pays price + sum(price_delta); the COGS stack mirrors with
    cogs_msat + sum(cost_msat_delta).
  • Currency fairness: COGS is msat-native; we don't lose
    precision when materials are priced in fiat (e.g. GTQ).
    Conversions happen at batch time using LNbits's exchange-
    rate plumbing, not at order time.

Operator UX

  • New "Pricing" tab on each item dialog (visible when mode≥bistro):
    cost breakdown, current margin, slider for target margin,
    suggested price.
  • Settings panel: default pricing mode, default target margin,
    default margin floor (warns when actual margin drops below).
  • Top-level dashboard widget: "items running below target margin"
    list — useful when material prices spike.

Tiering (per #2)

  • bar — off. Manual prices only.
  • bistro — flat-recipe COGS (one row per item, no BOM
    expansion). Margin badge shown.
  • full — full BOM-driven COGS (depends on #3 raw-material
    recipes). All three pricing modes available. libra integration
    on.

Stretch

  • Time-series margin — chart of margin per item over time as
    material costs fluctuate.
  • Substitute suggestions — "Brisket cost +25% this week — try
    carnitas, your margin would recover by 8 points."
  • Daily-special engine — auto-feature the highest-margin items
    with low aging stock.
  • Print-menu pricing snapshot — when generating the PDF in #1,
    optionally embed a footer hash of the COGS basis the prices
    were derived from. Auditable trail.

Acceptance

  • recipe_lines.cost_msat_per_unit (denormalized; recomputed
    when batches land) OR a view that computes on read.
  • menu_items.pricing_mode, .target_margin_pct,
    .computed_cogs_msat.
  • Suggest-price helper in services/pricing.py.
  • CMS pricing tab on the item dialog.
  • Margin-drift list on the dashboard.
  • libra emission stub (decision noted in docs/cogs).

See also

  • #2 — tiered operator modes (parent)
  • #3 — inventory (provides the cost basis)
  • #1 — PDF menu (should reflect current prices, optionally with
    margin trail)
  • libra extension (sibling, in ~/dev/shared/extensions/libra
    the accounting target)

References

  • Telegram conversation 2026-05-09.
Tracks the COGS / pricing layer of #2 (`bistro` and `full` tiers). ## Background The user's vision: > If you purchase materials etc so you could potentially automate > calculation [of] the cost of finished products by simply specifying > [a] desired profit margin. > there's an accounting module too, i really want to tie it all > together and offer it as a package to similar projects. Restaurants today set menu prices by gut feel + competitor-watching, or by spreadsheet labor. We have all the inputs already in the extension (or imminent in #3): we know what each material cost, we know what goes into each item, we know labor / overhead allocation. Adding a margin slider gives a derived recommended price — and ties naturally into the **libra** (Beancount-flavored) accounting module the user maintains in a sibling extension. ## What this issue owns 1. **Cost roll-up** — for any menu item, compute the COGS by walking its [[#3|recipe]] and aggregating `material_batch.unit_cost_msat * recipe_line.qty_per_serving`. Average over the most-recent N batches (FIFO or rolling avg — configurable). 2. **Margin → price helper** — given a target margin %, suggest a menu price. Operator can: - accept the suggestion → `menu_items.price` is updated; - lock the price manually → suggestion is shown alongside as a warning when actual margin diverges; - leave it auto → `price` recalculates whenever a new `material_batch` lands. 3. **Live margin badge in the CMS** — every item shows current COGS, current margin %, and a colored indicator when margin drifts under a configurable floor. 4. **Integration with libra** — every paid order emits an accounting record (revenue line + COGS line + tax line) shaped for libra's ledger consumption. Either: - direct Python call into libra's service, or - emit a Nostr event of a known kind that libra subscribes to. Decide as part of this issue. ## Behavior - **Pricing modes per item**: `manual | auto | suggest`. - **Recipe yield**: a recipe might produce 4 servings (a single pot of soup → 4 bowls). COGS divides by `yield_qty`. - **Modifier costs**: each modifier carries a `cost_msat_delta` derived the same way from its own mini-recipe. The customer pays `price + sum(price_delta)`; the COGS stack mirrors with `cogs_msat + sum(cost_msat_delta)`. - **Currency fairness**: COGS is msat-native; we don't lose precision when materials are priced in fiat (e.g. GTQ). Conversions happen at *batch* time using LNbits's exchange- rate plumbing, not at order time. ## Operator UX - New "Pricing" tab on each item dialog (visible when mode≥bistro): cost breakdown, current margin, slider for target margin, suggested price. - Settings panel: default pricing mode, default target margin, default margin floor (warns when actual margin drops below). - Top-level dashboard widget: "items running below target margin" list — useful when material prices spike. ## Tiering (per #2) - **bar** — off. Manual prices only. - **bistro** — flat-recipe COGS (one row per item, no BOM expansion). Margin badge shown. - **full** — full BOM-driven COGS (depends on #3 raw-material recipes). All three pricing modes available. libra integration on. ## Stretch - **Time-series margin** — chart of margin per item over time as material costs fluctuate. - **Substitute suggestions** — "Brisket cost +25% this week — try carnitas, your margin would recover by 8 points." - **Daily-special engine** — auto-feature the highest-margin items with low aging stock. - **Print-menu pricing snapshot** — when generating the PDF in #1, optionally embed a footer hash of the COGS basis the prices were derived from. Auditable trail. ## Acceptance - [ ] `recipe_lines.cost_msat_per_unit` (denormalized; recomputed when batches land) OR a view that computes on read. - [ ] `menu_items.pricing_mode`, `.target_margin_pct`, `.computed_cogs_msat`. - [ ] Suggest-price helper in `services/pricing.py`. - [ ] CMS pricing tab on the item dialog. - [ ] Margin-drift list on the dashboard. - [ ] libra emission stub (decision noted in [[docs/cogs]]). ## See also - #2 — tiered operator modes (parent) - #3 — inventory (provides the cost basis) - #1 — PDF menu (should reflect current prices, optionally with margin trail) - libra extension (sibling, in `~/dev/shared/extensions/libra` — the accounting target) ## References - Telegram conversation 2026-05-09.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/restaurant#6
No description provided.