Beyond Food¶
wright is built on domain-agnostic primitives: Material, Component,
ProductionItem, and PurchasedItem. This guide shows how to use them
for construction, brewing, event planning, and manufacturing.
Construction¶
Build a deck BOM, scale it, and calculate material costs:
from decimal import Decimal
from wright import Material, Component, ProductionItem, ProductionRun
from wright import generate_shopping_list, calculate_item_costs
# Define a deck as a set of components
framing = Component(
name="Deck Framing",
materials=[
Material(
name="2x6 Pressure-Treated",
quantity=48,
unit="ft",
require_tags=["#2", "ground-contact"],
),
Material(name="2x4 Joist Hangers", quantity=12, unit="each"),
Material(name='3" Deck Screws', quantity=400, unit="each"),
],
)
footings = Component(
name="Concrete Footings",
materials=[
Material(
name="Concrete Mix",
quantity=8,
unit="bag",
equivalent_quantity=60,
equivalent_unit="lb",
),
Material(name="Post Anchor", quantity=6, unit="each"),
],
)
# Purchase price data (any PurchasedItem protocol works)
hardware_prices = [
Purchase(
name="2x6 Pressure-Treated",
quantity=8,
unit="ft",
price=Decimal("12.97"),
store="Home Depot",
tags="ground-contact,#2",
),
Purchase(
name='3" Deck Screws',
quantity=100,
unit="each",
price=Decimal("5.97"),
store="Home Depot",
),
Purchase(
name="Concrete Mix",
quantity=1,
unit="bag",
price=Decimal("4.98"),
store="Lowe's",
),
]
# Cost individual materials
materials_costed = calculate_item_costs(framing.materials, hardware_prices)
for mc in materials_costed:
print(f" {mc.name}: ${mc.total_cost} ({mc.store})")
# Use construction-specific categorization
from wright import CategoryRule, categorize_item
lumberyard_rules = [
CategoryRule(
category="Lumber",
priority=0,
keywords=["lumber", "plywood", "2x", "stud", "beam"],
),
CategoryRule(
category="Hardware",
priority=1,
keywords=["screw", "nail", "bolt", "anchor", "hanger"],
),
CategoryRule(
category="Concrete",
priority=2,
keywords=["concrete", "cement", "mortar", "post"],
),
]
Subclass for construction metadata¶
from wright import Material
class Lumber(Material):
grade: str | None = None
waste_factor: float = 0.10
species: str | None = None
stud = Lumber(
name="2x4 Stud",
quantity=24,
unit="ft",
grade="#2",
species="Douglas Fir",
waste_factor=0.05,
)
Brewing¶
A beer recipe as a grain bill with hops and yeast:
from wright import Material, Component
grain_bill = Component(
name="Mash",
materials=[
Material(name="Pilsner Malt", quantity=5, unit="kg"),
Material(name="Munich Malt", quantity=1, unit="kg"),
Material(name="Carapils", quantity=0.5, unit="kg"),
],
)
hop_schedule = Component(
name="Boil Hops",
materials=[
Material(name="Hallertau", quantity=50, unit="g", require_tags=["4.5% AA"]),
Material(name="Saaz", quantity=25, unit="g", require_tags=["3.2% AA"]),
],
)
# Scale to a standard batch
five_gallon_grain = grain_bill * 1.0 # 1× = ~6.5 kg grain bill
# Determine cost per batch
from decimal import Decimal
from wright import Purchase, calculate_item_costs, cost_by_component, Assembly
supplier_prices = [
Purchase(name="Pilsner Malt", quantity=25, unit="kg", price=Decimal("42.00")),
Purchase(name="Munich Malt", quantity=25, unit="kg", price=Decimal("45.00")),
Purchase(name="Carapils", quantity=25, unit="kg", price=Decimal("44.00")),
Purchase(name="Hallertau", quantity=25, unit="g", price=Decimal("1.20")),
Purchase(name="Saaz", quantity=25, unit="g", price=Decimal("1.10")),
]
# Per-batch ingredient cost
batch_cost = calculate_item_costs(
grain_bill.materials + hop_schedule.materials,
supplier_prices,
)
for mc in batch_cost:
print(f" {mc.name}: ${mc.total_cost:.2f}")
# → Pilsner Malt: $8.40
# → Munich Malt: $1.80
# → Carapils: $0.88
# → Hallertau: $2.40
# → Saaz: $1.10
# Per-12oz cost (pint), proportionally scaled
per_pint = calculate_item_costs(
grain_bill.materials + hop_schedule.materials,
supplier_prices,
per_unit=(12, "floz"),
)
for mc in per_pint:
print(f" {mc.name}: ${mc.total_cost:.2f} per 12 floz")
# Cost breakdown by component
recipe = Assembly(
name="Pilsner",
components=[grain_bill, hop_schedule],
)
breakdown = cost_by_component(recipe, supplier_prices)
for comp, total in breakdown.items():
print(f" {comp}: ${total:.2f}")
# → Mash: $11.08
# → Boil Hops: $3.50
Event planning¶
Aggregate supplies for a 50-person event:
seating = Component(
name="Seating",
materials=[
Material(name="Folding Chairs", quantity=50, unit="each"),
Material(
name="Round Tables", quantity=5, unit="each", require_tags=["60-inch"]
),
],
)
decor = Component(
name="Decor",
materials=[
Material(name="Table Linens", quantity=5, unit="each", require_tags=["white"]),
Material(name="Centerpieces", quantity=5, unit="each"),
],
)
# Shopping list aggregation works the same way
all_supplies = seating.materials + decor.materials
Manufacturing¶
An assembly line bill of materials:
chassis = Component(
name="Chassis Assembly",
materials=[
Material(name="Steel Frame", quantity=1, unit="each"),
Material(name="M8 Bolts", quantity=16, unit="each", require_tags=["stainless"]),
Material(name="Rubber Feet", quantity=4, unit="each"),
],
)
electronics = Component(
name="Control Board",
materials=[
Material(name="PCB v3", quantity=1, unit="each", product_ref="pcb-subassembly"),
Material(name="Wiring Harness", quantity=1, unit="each"),
],
)
# product_ref handles recursive sub-assembly expansion
Supply tracking across domains¶
Inventory management works the same regardless of domain:
from wright import Stock, SupplyItem
# Track lumber inventory
stock = Stock([
SupplyItem(name="2x6 Pressure-Treated", quantity=200, unit="ft"),
SupplyItem(name='3" Deck Screws', quantity=1000, unit="each"),
])
# Deduct what a deck project consumes
stock, remaining = stock.use(framing.materials)
# remaining → list[SupplyItem] with deficit (empty if stock covers it)
See also¶
- Models — defining assemblies and subclassing for your domain
- Matching & Planning — shopping lists, categorization, and cost enrichment
- Customization — inject custom matchers, converters, and callbacks