-- File: lean/PricingSpec.lean -- E-Commerce Pricing Engine Specification -- Agents migrate JavaScript source to Python. -- The 5 theorems here are property-checked against agents' code. namespace PricingSpec structure LineItem where sku : String quantity : Nat unitPrice : Int -- cents, avoid Float deriving Repr, DecidableEq structure Coupon where code : String discountPercent : Nat -- 0..100 deriving Repr, DecidableEq structure Order where items : List LineItem coupons : List Coupon regionId : String -- for tax lookup loyaltyPoints : Nat deriving Repr, DecidableEq -- Tax rates by region (basis points, e.g. 875 = 8.75%) def taxRateBps : String → Nat | "US-CA" => 875 | "US-TX" => 625 | "US-NY" => 800 | "UK" => 2000 | _ => 0 -- Subtotal before discounts def subtotal (order : Order) : Int := order.items.foldl (fun acc item => acc + item.unitPrice * item.quantity) 0 -- Coupon discount (capped at subtotal, non-stackable beyond 50% total) def couponDiscount (order : Order) : Int := let sub := subtotal order let rawDiscount := order.coupons.foldl (fun acc c => acc + (sub * c.discountPercent / 100)) 0 min rawDiscount (sub / 2) -- max 50% off via coupons -- Loyalty points: 1 point = 1 cent off, max 10% of subtotal def loyaltyDiscount (order : Order) : Int := let sub := subtotal order min (order.loyaltyPoints : Int) (sub / 10) -- Total discount def totalDiscount (order : Order) : Int := couponDiscount order + loyaltyDiscount order -- Tax applied to post-discount amount def tax (order : Order) : Int := let afterDiscount := subtotal order - totalDiscount order afterDiscount * (taxRateBps order.regionId : Int) / 10000 -- Final price def finalPrice (order : Order) : Int := subtotal order - totalDiscount order + tax order -- ── The 5 invariants agents' Python code must satisfy ────────────── -- 1. Discount never exceeds subtotal theorem discount_bounded (order : Order) (h : 0 ≤ subtotal order) : totalDiscount order ≤ subtotal order := by simp [totalDiscount, couponDiscount, loyaltyDiscount] omega -- 2. Final price is non-negative -- Strategy: afterDiscount ≥ 0 (from discount_bounded) × taxRate (Nat) ≥ 0 → tax ≥ 0 theorem no_negative_price (order : Order) (h : 0 ≤ subtotal order) : 0 ≤ finalPrice order := by have hd := discount_bounded order h have hbd : 0 ≤ subtotal order - totalDiscount order := by omega -- taxRateBps returns Nat, so (↑rate : Int) ≥ 0 — omega knows Nat casts are nonneg have hrate : (0 : Int) ≤ ↑(taxRateBps order.regionId) := by omega have hnum : 0 ≤ (subtotal order - totalDiscount order) * ↑(taxRateBps order.regionId) := Int.mul_nonneg hbd hrate simp only [finalPrice, tax] omega -- 3. Tax is non-negative theorem tax_nonneg (order : Order) (h : 0 ≤ subtotal order) : 0 ≤ tax order := by have hd := discount_bounded order h have hbd : 0 ≤ subtotal order - totalDiscount order := by omega have hrate : (0 : Int) ≤ ↑(taxRateBps order.regionId) := by omega have hnum : 0 ≤ (subtotal order - totalDiscount order) * ↑(taxRateBps order.regionId) := Int.mul_nonneg hbd hrate simp only [tax] omega -- 4. Empty order has zero final price theorem empty_order_zero : finalPrice { items := [], coupons := [], regionId := "US-CA", loyaltyPoints := 0 } = 0 := by simp [finalPrice, subtotal, totalDiscount, couponDiscount, loyaltyDiscount, tax, taxRateBps] -- 5. Adding items increases subtotal monotonically theorem add_item_increases_subtotal (order : Order) (item : LineItem) (hpos : 0 < item.unitPrice) (hqty : 0 < item.quantity) : subtotal order < subtotal { order with items := order.items ++ [item] } := by simp [subtotal, List.foldl_append] -- goal reduces to: 0 < item.unitPrice * ↑item.quantity -- omega knows 0 < item.quantity (Nat) → (0 : Int) < ↑item.quantity have hq : (0 : Int) < ↑item.quantity := by omega have hprod : 0 < item.unitPrice * ↑item.quantity := Int.mul_pos hpos hq omega end PricingSpec