feat(bookspec): the five laws as Lean theorems (P13 step 4)
The first edition's five laws (debits equal credits; the accounting equation; the trial balance balances; net income articulates to equity; the cash-flow sections sum to the change in cash) are theorems in bookspec/lean, ported from verify/lean (Basic, Entry, Statements, Closing, Ledger under namespace Bookspec) and restated by name in Laws.lean, each a statement about every reachable state of the books. Audit.lean prints their axioms; the checker's lean.rs test fails on a sorry, a law not audited, or any axiom beyond propext, Quot.sound and Classical.choice. Money is Int, the exact count of the smallest unit: every law is a law of addition, so the model is faithful to exact decimals. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
1 parent
435e9704b8
commit
f034b0d45b
13 files changed
+2162
No files matched your search
@@ -0,0 +1,165 @@
|
||||
/-!
|
||||
# Lines, sides, and nets
|
||||
|
||||
The vocabulary of double entry. Money is `Int`: an exact count of the
|
||||
smallest unit the books use. Any finite set of exact decimals (what the
|
||||
engine stores) scales to integers without loss, and every law here is a
|
||||
law of addition, so `Int` is a faithful model.
|
||||
|
||||
Mirrors `crates/ledger-core/src/kernel.rs` (`flip`, `signed`, `net`,
|
||||
`totals`) and `account.rs` (`Side`, `AccountType`, `normal_side`).
|
||||
-/
|
||||
|
||||
namespace Bookspec
|
||||
|
||||
/-- Debit or credit. -/
|
||||
inductive Side where
|
||||
| debit
|
||||
| credit
|
||||
deriving DecidableEq, Repr
|
||||
|
||||
/-- The other side (`kernel::flip`). -/
|
||||
def Side.flip : Side → Side
|
||||
| .debit => .credit
|
||||
| .credit => .debit
|
||||
|
||||
@[simp] theorem Side.flip_flip (s : Side) : s.flip.flip = s := by
|
||||
cases s <;> rfl
|
||||
|
||||
theorem Side.flip_ne (s : Side) : s.flip ≠ s := by
|
||||
cases s <;> simp [Side.flip]
|
||||
|
||||
/-- The five account types. -/
|
||||
inductive AccountType where
|
||||
| asset
|
||||
| liability
|
||||
| equity
|
||||
| revenue
|
||||
| expense
|
||||
deriving DecidableEq, Repr
|
||||
|
||||
/-- The side that increases an account (`AccountType::normal_side`). -/
|
||||
def AccountType.normal : AccountType → Side
|
||||
| .asset | .expense => .debit
|
||||
| .liability | .equity | .revenue => .credit
|
||||
|
||||
/-- A line's signed amount: `+a` for a debit, `-a` for a credit
|
||||
(`kernel::signed`). -/
|
||||
def signed : Side → Int → Int
|
||||
| .debit, a => a
|
||||
| .credit, a => -a
|
||||
|
||||
@[simp] theorem signed_debit (a : Int) : signed .debit a = a := rfl
|
||||
@[simp] theorem signed_credit (a : Int) : signed .credit a = -a := rfl
|
||||
|
||||
theorem signed_flip (s : Side) (a : Int) : signed s.flip a = -signed s a := by
|
||||
cases s <;> simp [Side.flip]
|
||||
|
||||
/-- One journal line. -/
|
||||
structure Line where
|
||||
account : Nat
|
||||
side : Side
|
||||
amount : Int
|
||||
deriving DecidableEq, Repr
|
||||
|
||||
/-- A line's signed amount. -/
|
||||
def Line.signed (l : Line) : Int := Bookspec.signed l.side l.amount
|
||||
|
||||
/-- The net of some lines: debits minus credits (`kernel::net`). -/
|
||||
def net : List Line → Int
|
||||
| [] => 0
|
||||
| l :: ls => l.signed + net ls
|
||||
|
||||
/-- Total debits. -/
|
||||
def debits : List Line → Int
|
||||
| [] => 0
|
||||
| l :: ls => (if l.side = .debit then l.amount else 0) + debits ls
|
||||
|
||||
/-- Total credits. -/
|
||||
def credits : List Line → Int
|
||||
| [] => 0
|
||||
| l :: ls => (if l.side = .credit then l.amount else 0) + credits ls
|
||||
|
||||
@[simp] theorem net_nil : net [] = 0 := rfl
|
||||
@[simp] theorem net_cons (l : Line) (ls : List Line) : net (l :: ls) = l.signed + net ls := rfl
|
||||
|
||||
theorem net_append (xs ys : List Line) : net (xs ++ ys) = net xs + net ys := by
|
||||
induction xs with
|
||||
| nil => simp
|
||||
| cons x xs ih => simp only [List.cons_append, net_cons, ih]; omega
|
||||
|
||||
/-- The net is debits minus credits. -/
|
||||
theorem net_eq_debits_sub_credits (ls : List Line) : net ls = debits ls - credits ls := by
|
||||
induction ls with
|
||||
| nil => rfl
|
||||
| cons l ls ih =>
|
||||
obtain ⟨a, s, m⟩ := l
|
||||
cases s <;> simp [net, debits, credits, Line.signed, signed, ih] <;> omega
|
||||
|
||||
/-- A sum over a list of account codes. -/
|
||||
def sumOver (f : Nat → Int) : List Nat → Int
|
||||
| [] => 0
|
||||
| a :: as => f a + sumOver f as
|
||||
|
||||
@[simp] theorem sumOver_nil (f : Nat → Int) : sumOver f [] = 0 := rfl
|
||||
@[simp] theorem sumOver_cons (f : Nat → Int) (a : Nat) (as : List Nat) :
|
||||
sumOver f (a :: as) = f a + sumOver f as := rfl
|
||||
|
||||
theorem sumOver_add (f g : Nat → Int) (as : List Nat) :
|
||||
sumOver (fun a => f a + g a) as = sumOver f as + sumOver g as := by
|
||||
induction as with
|
||||
| nil => rfl
|
||||
| cons a as ih => simp only [sumOver_cons, ih]; omega
|
||||
|
||||
theorem sumOver_neg (f : Nat → Int) (as : List Nat) :
|
||||
sumOver (fun a => -f a) as = -sumOver f as := by
|
||||
induction as with
|
||||
| nil => rfl
|
||||
| cons a as ih => simp only [sumOver_cons, ih]; omega
|
||||
|
||||
theorem sumOver_zero (as : List Nat) : sumOver (fun _ => 0) as = 0 := by
|
||||
induction as with
|
||||
| nil => rfl
|
||||
| cons a as ih => simp [ih]
|
||||
|
||||
theorem sumOver_congr (f g : Nat → Int) (as : List Nat) (h : ∀ a ∈ as, f a = g a) :
|
||||
sumOver f as = sumOver g as := by
|
||||
induction as with
|
||||
| nil => rfl
|
||||
| cons a as ih =>
|
||||
simp only [sumOver_cons]
|
||||
rw [h a (by simp), ih (fun b hb => h b (by simp [hb]))]
|
||||
|
||||
/-- No code repeats (a chart's codes are unique). -/
|
||||
def Distinct : List Nat → Prop
|
||||
| [] => True
|
||||
| a :: as => a ∉ as ∧ Distinct as
|
||||
|
||||
/-- Summing an indicator over codes that do not include `k` gives zero. -/
|
||||
theorem sumOver_indicator_absent (k : Nat) (x : Int) (as : List Nat) (h : k ∉ as) :
|
||||
sumOver (fun a => if k = a then x else 0) as = 0 := by
|
||||
induction as with
|
||||
| nil => rfl
|
||||
| cons a as ih =>
|
||||
have hka : k ≠ a := fun e => h (e ▸ List.mem_cons_self ..)
|
||||
have hk : k ∉ as := fun hm => h (List.mem_cons_of_mem _ hm)
|
||||
simp [hka, ih hk]
|
||||
|
||||
/-- Summing an indicator over distinct codes that include `k` picks out
|
||||
exactly one term. -/
|
||||
theorem sumOver_indicator (k : Nat) (x : Int) (as : List Nat) (hd : Distinct as)
|
||||
(hk : k ∈ as) : sumOver (fun a => if k = a then x else 0) as = x := by
|
||||
induction as with
|
||||
| nil => simp at hk
|
||||
| cons a as ih =>
|
||||
obtain ⟨ha, hd⟩ := hd
|
||||
by_cases e : k = a
|
||||
· subst e
|
||||
simp [sumOver_indicator_absent k x as ha]
|
||||
· have : k ∈ as := by
|
||||
cases hk with
|
||||
| head => exact absurd rfl e
|
||||
| tail _ h => exact h
|
||||
simp [e, ih hd this]
|
||||
|
||||
end Bookspec
|
||||
Reference in new issue
Block a user