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
118 lines
4.6 KiB
Lean4
118 lines
4.6 KiB
Lean4
import Bookspec.Basic
|
|
|
|
/-!
|
|
# Entries: acceptance, movements, reversals
|
|
|
|
Mirrors `kernel::check_lines`, `kernel::movements`, `kernel::reversal`,
|
|
and `JournalEntry::validate`.
|
|
-/
|
|
|
|
namespace Bookspec
|
|
|
|
/-- What the engine accepts (the arithmetic of `JournalEntry::validate`,
|
|
i.e. `kernel::check_lines`): two or more lines, every amount positive,
|
|
debits equal to credits. (Range, account, and currency checks are not
|
|
arithmetic laws and are left out of the model.) -/
|
|
def Accepted (ls : List Line) : Prop :=
|
|
2 ≤ ls.length ∧ (∀ l ∈ ls, 0 < l.amount) ∧ debits ls = credits ls
|
|
|
|
/-- **Every accepted entry nets to zero.** -/
|
|
theorem Accepted.net_zero {ls : List Line} (h : Accepted ls) : net ls = 0 := by
|
|
rw [net_eq_debits_sub_credits]; have := h.2.2; omega
|
|
|
|
/-- Net movement of account `a` over some lines. -/
|
|
def movement (a : Nat) : List Line → Int
|
|
| [] => 0
|
|
| l :: ls => (if l.account = a then l.signed else 0) + movement a ls
|
|
|
|
@[simp] theorem movement_nil (a : Nat) : movement a [] = 0 := rfl
|
|
@[simp] theorem movement_cons (a : Nat) (l : Line) (ls : List Line) :
|
|
movement a (l :: ls) = (if l.account = a then l.signed else 0) + movement a ls := rfl
|
|
|
|
theorem movement_append (a : Nat) (xs ys : List Line) :
|
|
movement a (xs ++ ys) = movement a xs + movement a ys := by
|
|
induction xs with
|
|
| nil => simp
|
|
| cons x xs ih => simp only [List.cons_append, movement_cons, ih]; omega
|
|
|
|
/-- **Movements partition the net.** Summed over a chart's distinct codes
|
|
that cover every line's account, the per-account movements add up to the
|
|
net of the lines: grouping by account loses and invents nothing. -/
|
|
theorem sum_movements (as : List Nat) (hd : Distinct as) :
|
|
∀ ls : List Line, (∀ l ∈ ls, l.account ∈ as) →
|
|
sumOver (fun a => movement a ls) as = net ls := by
|
|
intro ls
|
|
induction ls with
|
|
| nil => intro _; simpa using sumOver_zero as
|
|
| cons l ls ih =>
|
|
intro hcov
|
|
simp only [movement_cons, net_cons]
|
|
rw [sumOver_add, ih (fun m hm => hcov m (List.mem_cons_of_mem _ hm)),
|
|
sumOver_indicator l.account l.signed as hd (hcov l (List.mem_cons_self ..))]
|
|
|
|
/-- So the movements of an accepted entry sum to zero. -/
|
|
theorem Accepted.movements_sum_zero {ls : List Line} (h : Accepted ls) (as : List Nat)
|
|
(hd : Distinct as) (hcov : ∀ l ∈ ls, l.account ∈ as) :
|
|
sumOver (fun a => movement a ls) as = 0 := by
|
|
rw [sum_movements as hd ls hcov, h.net_zero]
|
|
|
|
/-! ## Reversals -/
|
|
|
|
/-- A line with its side flipped. -/
|
|
def Line.reverse (l : Line) : Line := { l with side := l.side.flip }
|
|
|
|
/-- A reversing entry's lines (`kernel::reversal`). -/
|
|
def reversal (ls : List Line) : List Line := ls.map Line.reverse
|
|
|
|
theorem Line.reverse_signed (l : Line) : l.reverse.signed = -l.signed := by
|
|
simp [Line.reverse, Line.signed, signed_flip]
|
|
|
|
theorem net_reversal (ls : List Line) : net (reversal ls) = -net ls := by
|
|
induction ls with
|
|
| nil => rfl
|
|
| cons l ls ih =>
|
|
simp only [reversal, List.map_cons] at ih ⊢
|
|
simp only [net_cons, ih, Line.reverse_signed]; omega
|
|
|
|
theorem movement_reversal (a : Nat) (ls : List Line) :
|
|
movement a (reversal ls) = -movement a ls := by
|
|
induction ls with
|
|
| nil => rfl
|
|
| cons l ls ih =>
|
|
simp only [reversal, List.map_cons] at ih ⊢
|
|
simp only [movement_cons, ih, Line.reverse_signed]
|
|
by_cases h : l.account = a <;> simp [Line.reverse, h] <;> omega
|
|
|
|
/-- **An entry and its reversal net every account to zero.** -/
|
|
theorem movement_with_reversal (a : Nat) (ls : List Line) :
|
|
movement a (ls ++ reversal ls) = 0 := by
|
|
rw [movement_append, movement_reversal]; omega
|
|
|
|
theorem debits_reversal (ls : List Line) : debits (reversal ls) = credits ls := by
|
|
induction ls with
|
|
| nil => rfl
|
|
| cons l ls ih =>
|
|
simp only [reversal, List.map_cons] at ih ⊢
|
|
obtain ⟨a, s, m⟩ := l
|
|
cases s <;> simp [debits, credits, Line.reverse, Side.flip, ih]
|
|
|
|
theorem credits_reversal (ls : List Line) : credits (reversal ls) = debits ls := by
|
|
induction ls with
|
|
| nil => rfl
|
|
| cons l ls ih =>
|
|
simp only [reversal, List.map_cons] at ih ⊢
|
|
obtain ⟨a, s, m⟩ := l
|
|
cases s <;> simp [debits, credits, Line.reverse, Side.flip, ih]
|
|
|
|
/-- **A reversal is accepted exactly when the original is.** -/
|
|
theorem accepted_reversal_iff (ls : List Line) : Accepted (reversal ls) ↔ Accepted ls := by
|
|
unfold Accepted
|
|
rw [debits_reversal, credits_reversal]
|
|
have hlen : (reversal ls).length = ls.length := List.length_map ..
|
|
have hpos : (∀ l ∈ reversal ls, 0 < l.amount) ↔ (∀ l ∈ ls, 0 < l.amount) := by
|
|
simp [reversal, Line.reverse]
|
|
rw [hlen, hpos]
|
|
constructor <;> rintro ⟨a, b, c⟩ <;> exact ⟨a, b, c.symm⟩
|
|
|
|
end Bookspec
|