check / check (push) Canceled after 0s
Direct method with the operating reconciliation and noncash schedules; it foots by construction (Lean and Kani). Every non-cash account belongs to one section; reversals net; a member or lender paying for the company is cash borrowed and spent (D-42, constructive receipt and disbursement); property a member contributes stays noncash. The bookspec seed: one loader checks every example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
684 lines
30 KiB
Lean4
684 lines
30 KiB
Lean4
import Wanderbooks.Entry
|
||
|
||
/-!
|
||
# Balances and statements
|
||
|
||
Mirrors `kernel::normal_balance`, `kernel::columns`,
|
||
`Ledger::account_balance`, and `reports::trial_balance`; and the statement
|
||
of cash flows (`kernel::take`, `kernel::apply_cash`, `kernel::entry_flows`,
|
||
`kernel::constructive`, `cashflow::cash_flow`'s footing and reconciliation
|
||
checks).
|
||
-/
|
||
|
||
namespace Wanderbooks
|
||
|
||
/-- A chart of accounts: each account's code and type. -/
|
||
abbrev Chart := List (Nat × AccountType)
|
||
|
||
/-- The chart's codes. -/
|
||
def codes (c : Chart) : List Nat := c.map Prod.fst
|
||
|
||
@[simp] theorem codes_nil : codes [] = [] := rfl
|
||
@[simp] theorem codes_cons (a : Nat) (t : AccountType) (c : Chart) :
|
||
codes ((a, t) :: c) = a :: codes c := rfl
|
||
|
||
/-- An account's balance on its normal side (`kernel::normal_balance`). -/
|
||
def balance (t : AccountType) (m : Int) : Int := signed t.normal m
|
||
|
||
/-- **A normal-side line raises a balance by its amount; an opposite-side
|
||
line lowers it.** -/
|
||
theorem balance_after_line (t : AccountType) (m : Int) (s : Side) (a : Int) :
|
||
balance t (m + signed s a) = balance t m + (if s = t.normal then a else -a) := by
|
||
cases t <;> cases s <;> simp [balance, AccountType.normal, signed] <;> omega
|
||
|
||
/-- The total balance of every account of type `t`. -/
|
||
def typeTotal (t : AccountType) (ls : List Line) : Chart → Int
|
||
| [] => 0
|
||
| (a, t') :: rest => (if t' = t then balance t' (movement a ls) else 0) + typeTotal t ls rest
|
||
|
||
/-- The accounting equation's two sides differ by the net movement of the
|
||
chart's accounts. -/
|
||
theorem equation_gap (ls : List Line) (c : Chart) :
|
||
typeTotal .asset ls c + typeTotal .expense ls c
|
||
- typeTotal .liability ls c - typeTotal .equity ls c - typeTotal .revenue ls c
|
||
= sumOver (fun a => movement a ls) (codes c) := by
|
||
induction c with
|
||
| nil => rfl
|
||
| cons x c ih =>
|
||
obtain ⟨a, t⟩ := x
|
||
cases t <;> simp [typeTotal, balance, AccountType.normal, signed] <;> omega
|
||
|
||
/-- **The accounting equation.** For lines that net to zero, over a chart
|
||
with distinct codes covering every line's account:
|
||
Assets = Liabilities + Equity + (Revenue - Expenses). -/
|
||
theorem accounting_equation (ls : List Line) (c : Chart) (hd : Distinct (codes c))
|
||
(hcov : ∀ l ∈ ls, l.account ∈ codes c) (hnet : net ls = 0) :
|
||
typeTotal .asset ls c
|
||
= typeTotal .liability ls c + typeTotal .equity ls c
|
||
+ (typeTotal .revenue ls c - typeTotal .expense ls c) := by
|
||
have := equation_gap ls c
|
||
rw [sum_movements _ hd ls hcov, hnet] at this
|
||
omega
|
||
|
||
/-! ## Trial balance -/
|
||
|
||
/-- The debit column for a movement (`kernel::columns`). -/
|
||
def colDebit (m : Int) : Int := if 0 < m then m else 0
|
||
/-- The credit column for a movement (`kernel::columns`). -/
|
||
def colCredit (m : Int) : Int := if 0 < m then 0 else -m
|
||
|
||
theorem columns_spec (m : Int) :
|
||
0 ≤ colDebit m ∧ 0 ≤ colCredit m ∧ (colDebit m = 0 ∨ colCredit m = 0)
|
||
∧ colDebit m - colCredit m = m := by
|
||
unfold colDebit colCredit; split <;> omega
|
||
|
||
/-- The trial balance's debit column total. -/
|
||
def tbDebits (ls : List Line) (as : List Nat) : Int := sumOver (fun a => colDebit (movement a ls)) as
|
||
/-- The trial balance's credit column total. -/
|
||
def tbCredits (ls : List Line) (as : List Nat) : Int :=
|
||
sumOver (fun a => colCredit (movement a ls)) as
|
||
|
||
theorem tb_gap (ls : List Line) (as : List Nat) :
|
||
tbDebits ls as - tbCredits ls as = sumOver (fun a => movement a ls) as := by
|
||
induction as with
|
||
| nil => rfl
|
||
| cons a as ih =>
|
||
simp only [tbDebits, tbCredits, sumOver_cons] at ih ⊢
|
||
have := (columns_spec (movement a ls)).2.2.2
|
||
omega
|
||
|
||
/-- **The trial balance balances** for lines that net to zero. -/
|
||
theorem trial_balance_balances (ls : List Line) (as : List Nat) (hd : Distinct as)
|
||
(hcov : ∀ l ∈ ls, l.account ∈ as) (hnet : net ls = 0) :
|
||
tbDebits ls as = tbCredits ls as := by
|
||
have := tb_gap ls as
|
||
rw [sum_movements as hd ls hcov, hnet] at this
|
||
omega
|
||
|
||
/-- And conversely: a balanced trial balance means the lines net to zero.
|
||
The trial balance is a complete check of the books' arithmetic. -/
|
||
theorem trial_balance_iff (ls : List Line) (as : List Nat) (hd : Distinct as)
|
||
(hcov : ∀ l ∈ ls, l.account ∈ as) :
|
||
tbDebits ls as = tbCredits ls as ↔ net ls = 0 := by
|
||
have := tb_gap ls as
|
||
rw [sum_movements as hd ls hcov] at this
|
||
omega
|
||
|
||
/-! ## The statement of cash flows (rules.md B-14)
|
||
|
||
Mirrors `kernel::Flow`, `kernel::take`, `kernel::apply_cash` and
|
||
`kernel::entry_flows`, and the identities `cashflow::cash_flow` checks at
|
||
runtime: the sections sum to the cash accounts' change, a reversal's flows
|
||
are its original's negated, and net income reconciles to operating cash.
|
||
|
||
Lines are tagged per entry (`F : List Line → Line → Flow`), as the engine
|
||
tags them: a revenue or expense line is operating in one entry and
|
||
investing in another (rule 4), and a reversal is tagged by its chain's
|
||
root. The theorems hold for every such tagging; their hypotheses say what
|
||
the engine guarantees (the cash tag follows the account; revenue and
|
||
expense lines are operating or investing; a reversal's lines take its
|
||
original's tags). A line the engine splits between two sections (the bill
|
||
split, rule 12) is two lines here; a split keeps the entry's net. -/
|
||
|
||
/-- Where a line falls on the statement of cash flows (`kernel::Flow`). -/
|
||
inductive Flow where
|
||
| cash
|
||
| operating
|
||
| investing
|
||
| financing
|
||
deriving DecidableEq, Repr
|
||
|
||
/-- The net movement of the lines `f` tags `k`. -/
|
||
def secNet (f : Line → Flow) (k : Flow) : List Line → Int
|
||
| [] => 0
|
||
| l :: ls => (if f l = k then l.signed else 0) + secNet f k ls
|
||
|
||
@[simp] theorem secNet_nil (f : Line → Flow) (k : Flow) : secNet f k [] = 0 := rfl
|
||
@[simp] theorem secNet_cons (f : Line → Flow) (k : Flow) (l : Line) (ls : List Line) :
|
||
secNet f k (l :: ls) = (if f l = k then l.signed else 0) + secNet f k ls := rfl
|
||
|
||
/-- **Every line is in exactly one place**: an entry's net is its cash plus
|
||
its three sections' movements. -/
|
||
theorem net_splits_by_flow (f : Line → Flow) (ls : List Line) :
|
||
net ls = secNet f .cash ls + secNet f .operating ls + secNet f .investing ls
|
||
+ secNet f .financing ls := by
|
||
induction ls with
|
||
| nil => rfl
|
||
| cons l ls ih =>
|
||
simp only [net_cons, secNet_cons, ih]
|
||
cases f l <;> simp <;> omega
|
||
|
||
/-- The cash one section takes from what is left (`kernel::take`): only a
|
||
section moving against the cash, never more than its own movement. -/
|
||
def take (rem n : Int) : Int :=
|
||
if 0 < rem ∧ n < 0 then min rem (-n) else if rem < 0 ∧ 0 < n then max rem (-n) else 0
|
||
|
||
/-- `kernel::apply_cash`: the cash applied to each movement in order, and
|
||
what is left. -/
|
||
def applyCash : Int → List Int → List Int × Int
|
||
| rem, [] => ([], rem)
|
||
| rem, n :: ns => (take rem n :: (applyCash (rem - take rem n) ns).1,
|
||
(applyCash (rem - take rem n) ns).2)
|
||
|
||
/-- A sum of integers. -/
|
||
def isum : List Int → Int
|
||
| [] => 0
|
||
| x :: xs => x + isum xs
|
||
|
||
@[simp] theorem isum_nil : isum [] = 0 := rfl
|
||
@[simp] theorem isum_cons (x : Int) (xs : List Int) : isum (x :: xs) = x + isum xs := rfl
|
||
|
||
theorem isum_append (xs ys : List Int) : isum (xs ++ ys) = isum xs + isum ys := by
|
||
induction xs with
|
||
| nil => simp
|
||
| cons x xs ih => simp only [List.cons_append, isum_cons, ih]; omega
|
||
|
||
theorem isum_neg (xs : List Int) : isum (xs.map (fun x => -x)) = -isum xs := by
|
||
induction xs with
|
||
| nil => rfl
|
||
| cons x xs ih => simp only [List.map_cons, isum_cons, ih]; omega
|
||
|
||
/-- Applying cash invents and loses nothing: the parts and what is left
|
||
sum to the cash. -/
|
||
theorem applyCash_conserves (rem : Int) (ns : List Int) :
|
||
isum (applyCash rem ns).1 + (applyCash rem ns).2 = rem := by
|
||
induction ns generalizing rem with
|
||
| nil => simp [applyCash]
|
||
| cons n ns ih =>
|
||
simp only [applyCash, isum_cons]
|
||
have := ih (rem - take rem n)
|
||
omega
|
||
|
||
theorem applyCash_length (rem : Int) (ns : List Int) :
|
||
(applyCash rem ns).1.length = ns.length := by
|
||
induction ns generalizing rem with
|
||
| nil => rfl
|
||
| cons n ns ih => simp [applyCash, ih]
|
||
|
||
theorem take_zero (n : Int) : take 0 n = 0 := by
|
||
simp [take]
|
||
|
||
/-- With no cash, nothing is applied and nothing is left. -/
|
||
theorem applyCash_zero (ns : List Int) : (applyCash 0 ns).2 = 0 := by
|
||
induction ns with
|
||
| nil => rfl
|
||
| cons n ns ih => simp [applyCash, take_zero, ih]
|
||
|
||
/-- Cash in is used up when the movements against it cover it. -/
|
||
theorem applyCash_exhausts_pos (rem : Int) (ns : List Int) (h0 : 0 ≤ rem)
|
||
(h : rem + isum ns ≤ 0) : (applyCash rem ns).2 = 0 := by
|
||
induction ns generalizing rem with
|
||
| nil => simp [applyCash] at h ⊢; omega
|
||
| cons n ns ih =>
|
||
simp only [applyCash]
|
||
simp only [isum_cons] at h
|
||
by_cases hr : rem = 0
|
||
· subst hr; simp [take_zero, applyCash_zero]
|
||
· by_cases hn : n < 0
|
||
· by_cases hle : -n ≤ rem
|
||
· have ht : take rem n = -n := by
|
||
simp only [take]; rw [ite_eq_left (by omega)]; omega
|
||
rw [ht]; exact ih _ (by omega) (by omega)
|
||
· have ht : take rem n = rem := by
|
||
simp only [take]; rw [ite_eq_left (by omega)]; omega
|
||
rw [ht, Int.sub_self]; exact applyCash_zero ns
|
||
· have ht : take rem n = 0 := by
|
||
simp only [take]; rw [ite_eq_right (by omega), ite_eq_right (by omega)]
|
||
rw [ht, Int.sub_zero]; exact ih _ h0 (by omega)
|
||
|
||
/-- Cash out is used up when the movements against it cover it. -/
|
||
theorem applyCash_exhausts_neg (rem : Int) (ns : List Int) (h0 : rem ≤ 0)
|
||
(h : 0 ≤ rem + isum ns) : (applyCash rem ns).2 = 0 := by
|
||
induction ns generalizing rem with
|
||
| nil => simp [applyCash] at h ⊢; omega
|
||
| cons n ns ih =>
|
||
simp only [applyCash]
|
||
simp only [isum_cons] at h
|
||
by_cases hr : rem = 0
|
||
· subst hr; simp [take_zero, applyCash_zero]
|
||
· by_cases hn : 0 < n
|
||
· by_cases hle : rem ≤ -n
|
||
· have ht : take rem n = -n := by
|
||
simp only [take]; rw [ite_eq_right (by omega), ite_eq_left (by omega)]; omega
|
||
rw [ht]; exact ih _ (by omega) (by omega)
|
||
· have ht : take rem n = rem := by
|
||
simp only [take]; rw [ite_eq_right (by omega), ite_eq_left (by omega)]; omega
|
||
rw [ht, Int.sub_self]; exact applyCash_zero ns
|
||
· have ht : take rem n = 0 := by
|
||
simp only [take]; rw [ite_eq_right (by omega), ite_eq_right (by omega)]
|
||
rw [ht, Int.sub_zero]; exact ih _ h0 (by omega)
|
||
|
||
/-- **Balanced, the cash is used up**: when the cash and the movements sum
|
||
to zero, nothing is left over. -/
|
||
theorem applyCash_exhausts (rem : Int) (ns : List Int) (h : rem + isum ns = 0) :
|
||
(applyCash rem ns).2 = 0 := by
|
||
rcases Int.le_total 0 rem with h0 | h0
|
||
· exact applyCash_exhausts_pos rem ns h0 (by omega)
|
||
· exact applyCash_exhausts_neg rem ns h0 (by omega)
|
||
|
||
theorem take_neg (r n : Int) : take (-r) (-n) = -take r n := by
|
||
simp only [take]
|
||
by_cases h1 : 0 < r ∧ n < 0
|
||
· rw [ite_eq_right (by omega), ite_eq_left (by omega), ite_eq_left h1]; omega
|
||
· by_cases h2 : r < 0 ∧ 0 < n
|
||
· rw [ite_eq_left (by omega), ite_eq_right h1, ite_eq_left h2]; omega
|
||
· rw [ite_eq_right (by omega), ite_eq_right (by omega), ite_eq_right h1, ite_eq_right h2]; omega
|
||
|
||
/-- Applying minus the cash to minus the movements gives minus the parts:
|
||
what lets a reversal mirror its original. -/
|
||
theorem applyCash_neg (rem : Int) (ns : List Int) :
|
||
applyCash (-rem) (ns.map (fun x => -x))
|
||
= ((applyCash rem ns).1.map (fun x => -x), -(applyCash rem ns).2) := by
|
||
induction ns generalizing rem with
|
||
| nil => rfl
|
||
| cons n ns ih =>
|
||
simp only [List.map_cons, applyCash, take_neg]
|
||
have : -rem - -take rem n = -(rem - take rem n) := by omega
|
||
rw [this, ih]
|
||
|
||
/-- An entry's cash: the net of the lines tagged cash. -/
|
||
def cashOf (F : List Line → Line → Flow) (e : List Line) : Int := secNet (F e) .cash e
|
||
|
||
/-- An entry's section movements, `[operating, investing, financing]`. -/
|
||
def moves (F : List Line → Line → Flow) (e : List Line) : List Int :=
|
||
[secNet (F e) .operating e, secNet (F e) .investing e, secNet (F e) .financing e]
|
||
|
||
/-- An entry's cash flows per section (`kernel::entry_flows`). -/
|
||
def flows (F : List Line → Line → Flow) (e : List Line) : List Int :=
|
||
(applyCash (cashOf F e) (moves F e)).1
|
||
|
||
/-- **An entry's cash flows sum to its cash** (`entry_flows_sum`): for a
|
||
balanced entry, the sections' cash is exactly the cash that moved. -/
|
||
theorem entry_flows_sum (F : List Line → Line → Flow) (e : List Line) (h : net e = 0) :
|
||
isum (flows F e) = cashOf F e := by
|
||
have hb := net_splits_by_flow (F e) e
|
||
have hx : (applyCash (cashOf F e) (moves F e)).2 = 0 := by
|
||
apply applyCash_exhausts
|
||
simp only [moves, cashOf, isum_cons, isum_nil]
|
||
omega
|
||
have := applyCash_conserves (cashOf F e) (moves F e)
|
||
simp only [flows]
|
||
omega
|
||
|
||
theorem flows_length (F : List Line → Line → Flow) (e : List Line) : (flows F e).length = 3 := by
|
||
simp [flows, applyCash_length, moves]
|
||
|
||
theorem secNet_reversal (f g : Line → Flow) (k : Flow) (ls : List Line)
|
||
(h : ∀ l ∈ ls, g l.reverse = f l) : secNet g k (reversal ls) = -secNet f k ls := by
|
||
induction ls with
|
||
| nil => rfl
|
||
| cons l ls ih =>
|
||
simp only [reversal, List.map_cons] at ih ⊢
|
||
simp only [secNet_cons, h l (by simp), Line.reverse_signed,
|
||
ih (fun m hm => h m (by simp [hm]))]
|
||
split <;> omega
|
||
|
||
/-- **A reversal's flows are its original's, negated** (`flows_reversal`),
|
||
when its lines take its original's tags: what lets an entry and its
|
||
reversal drop out of the statement together (rules.md B-14 rule 6). -/
|
||
theorem flows_reversal (F : List Line → Line → Flow) (e : List Line)
|
||
(hF : ∀ l ∈ e, F (reversal e) l.reverse = F e l) :
|
||
flows F (reversal e) = (flows F e).map (fun x => -x) := by
|
||
simp only [flows, cashOf, moves]
|
||
rw [secNet_reversal (F e) (F (reversal e)) .cash e hF,
|
||
secNet_reversal (F e) (F (reversal e)) .operating e hF,
|
||
secNet_reversal (F e) (F (reversal e)) .investing e hF,
|
||
secNet_reversal (F e) (F (reversal e)) .financing e hF]
|
||
have := applyCash_neg (secNet (F e) .cash e)
|
||
[secNet (F e) .operating e, secNet (F e) .investing e, secNet (F e) .financing e]
|
||
simp only [List.map_cons, List.map_nil] at this
|
||
rw [this]
|
||
|
||
theorem isum_three (xs : List Int) (h : xs.length = 3) :
|
||
isum xs = xs.getD 0 0 + xs.getD 1 0 + xs.getD 2 0 := by
|
||
match xs, h with
|
||
| [a, b, c], _ => simp; omega
|
||
|
||
/-! ### What a member or lender pays or receives for the company (rule 11)
|
||
|
||
`kernel::constructive` (D-42): what is left unsettled of an entry's
|
||
kinds that can pay or receive for the company (`s`, a financing liability
|
||
or a member's capital) settles what is left of the kinds they pay or
|
||
receive for (`u`), as if through the cash accounts. The engine sums the
|
||
parts by section into an entry's constructive triple; the entry theorems
|
||
below take any triple that nets to zero (and, for reversals, mirrors),
|
||
which `constructive_nets` and `constructive_neg` give the kernel's. -/
|
||
|
||
/-- The credits among some movements, `Σ max(-m, 0)`: what cash in can
|
||
settle. -/
|
||
def creditTotal : List Int → Int
|
||
| [] => 0
|
||
| m :: ms => max (-m) 0 + creditTotal ms
|
||
|
||
/-- The debits among some movements, `Σ max(m, 0)`: what cash out can
|
||
settle. -/
|
||
def debitTotal : List Int → Int
|
||
| [] => 0
|
||
| m :: ms => max m 0 + debitTotal ms
|
||
|
||
theorem creditTotal_nonneg (ms : List Int) : 0 ≤ creditTotal ms := by
|
||
induction ms with
|
||
| nil => simp [creditTotal]
|
||
| cons m ms ih => simp only [creditTotal]; omega
|
||
|
||
theorem debitTotal_nonneg (ms : List Int) : 0 ≤ debitTotal ms := by
|
||
induction ms with
|
||
| nil => simp [debitTotal]
|
||
| cons m ms ih => simp only [debitTotal]; omega
|
||
|
||
theorem creditTotal_neg (ms : List Int) :
|
||
creditTotal (ms.map (fun x => -x)) = debitTotal ms := by
|
||
induction ms with
|
||
| nil => rfl
|
||
| cons m ms ih => simp only [List.map_cons, creditTotal, debitTotal, ih]; omega
|
||
|
||
theorem debitTotal_neg (ms : List Int) :
|
||
debitTotal (ms.map (fun x => -x)) = creditTotal ms := by
|
||
induction ms with
|
||
| nil => rfl
|
||
| cons m ms ih =>
|
||
simp only [List.map_cons, creditTotal, debitTotal, ih]
|
||
|
||
/-- Cash in is used up when the credits cover it (whatever the debits). -/
|
||
theorem applyCash_exhausts_in (rem : Int) (ns : List Int) (h0 : 0 ≤ rem)
|
||
(h : rem ≤ creditTotal ns) : (applyCash rem ns).2 = 0 := by
|
||
induction ns generalizing rem with
|
||
| nil => simp [applyCash, creditTotal] at h ⊢; omega
|
||
| cons n ns ih =>
|
||
simp only [applyCash]
|
||
simp only [creditTotal] at h
|
||
by_cases hr : rem = 0
|
||
· subst hr; simp [take_zero, applyCash_zero]
|
||
· by_cases hn : n < 0
|
||
· by_cases hle : -n ≤ rem
|
||
· have ht : take rem n = -n := by
|
||
simp only [take]; rw [ite_eq_left (by omega)]; omega
|
||
rw [ht]; exact ih _ (by omega) (by omega)
|
||
· have ht : take rem n = rem := by
|
||
simp only [take]; rw [ite_eq_left (by omega)]; omega
|
||
rw [ht, Int.sub_self]; exact applyCash_zero ns
|
||
· have ht : take rem n = 0 := by
|
||
simp only [take]; rw [ite_eq_right (by omega), ite_eq_right (by omega)]
|
||
rw [ht, Int.sub_zero]; exact ih _ h0 (by omega)
|
||
|
||
/-- Cash out is used up when the debits cover it (whatever the credits). -/
|
||
theorem applyCash_exhausts_out (rem : Int) (ns : List Int) (h0 : rem ≤ 0)
|
||
(h : -rem ≤ debitTotal ns) : (applyCash rem ns).2 = 0 := by
|
||
induction ns generalizing rem with
|
||
| nil => simp [applyCash, debitTotal] at h ⊢; omega
|
||
| cons n ns ih =>
|
||
simp only [applyCash]
|
||
simp only [debitTotal] at h
|
||
by_cases hr : rem = 0
|
||
· subst hr; simp [take_zero, applyCash_zero]
|
||
· by_cases hn : 0 < n
|
||
· by_cases hle : rem ≤ -n
|
||
· have ht : take rem n = -n := by
|
||
simp only [take]; rw [ite_eq_right (by omega), ite_eq_left (by omega)]; omega
|
||
rw [ht]; exact ih _ (by omega) (by omega)
|
||
· have ht : take rem n = rem := by
|
||
simp only [take]; rw [ite_eq_right (by omega), ite_eq_left (by omega)]; omega
|
||
rw [ht, Int.sub_self]; exact applyCash_zero ns
|
||
· have ht : take rem n = 0 := by
|
||
simp only [take]; rw [ite_eq_right (by omega), ite_eq_right (by omega)]
|
||
rw [ht, Int.sub_zero]; exact ih _ h0 (by omega)
|
||
|
||
/-- Which way the money went (`kernel::OnBehalf`). -/
|
||
inductive OnBehalf where
|
||
| paid
|
||
| received
|
||
deriving DecidableEq, Repr
|
||
|
||
/-- The other way: what a reversal does. -/
|
||
def OnBehalf.mirror : OnBehalf → OnBehalf
|
||
| .paid => .received
|
||
| .received => .paid
|
||
|
||
/-- The cash the sources take, + in (`kernel::constructive`): paid, the
|
||
smaller of the sources' credits and the uses' debits; received, minus the
|
||
smaller of the sources' debits and the uses' credits. -/
|
||
def onBehalfCash : OnBehalf → List Int → List Int → Int
|
||
| .paid, s, u => min (creditTotal s) (debitTotal u)
|
||
| .received, s, u => -min (debitTotal s) (creditTotal u)
|
||
|
||
/-- `kernel::constructive`: the sources' parts and the uses' parts. -/
|
||
def constructive (w : OnBehalf) (s u : List Int) : List Int × List Int :=
|
||
((applyCash (onBehalfCash w s u) s).1, (applyCash (-onBehalfCash w s u) u).1)
|
||
|
||
/-- **What a member or lender handled for the company nets to zero**
|
||
(`constructive_nets`; Kani `constructive_nets_to_zero`): the sources take
|
||
exactly what the uses give, so an entry's flows still sum to its cash. -/
|
||
theorem constructive_nets (w : OnBehalf) (s u : List Int) :
|
||
isum (constructive w s u).1 + isum (constructive w s u).2 = 0 := by
|
||
have hs := applyCash_conserves (onBehalfCash w s u) s
|
||
have hu := applyCash_conserves (-onBehalfCash w s u) u
|
||
have cs := creditTotal_nonneg s
|
||
have ds := debitTotal_nonneg s
|
||
have cu := creditTotal_nonneg u
|
||
have du := debitTotal_nonneg u
|
||
have ⟨xs, xu⟩ : (applyCash (onBehalfCash w s u) s).2 = 0 ∧
|
||
(applyCash (-onBehalfCash w s u) u).2 = 0 := by
|
||
cases w with
|
||
| paid =>
|
||
simp only [onBehalfCash]
|
||
exact ⟨applyCash_exhausts_in _ _ (by omega) (by omega),
|
||
applyCash_exhausts_out _ _ (by omega) (by omega)⟩
|
||
| received =>
|
||
simp only [onBehalfCash]
|
||
exact ⟨applyCash_exhausts_out _ _ (by omega) (by omega),
|
||
applyCash_exhausts_in _ _ (by omega) (by omega)⟩
|
||
simp only [constructive]
|
||
omega
|
||
|
||
theorem onBehalfCash_mirror (w : OnBehalf) (s u : List Int) :
|
||
onBehalfCash w.mirror (s.map (fun x => -x)) (u.map (fun x => -x))
|
||
= -onBehalfCash w s u := by
|
||
cases w <;> simp only [OnBehalf.mirror, onBehalfCash, creditTotal_neg, debitTotal_neg] <;> omega
|
||
|
||
/-- **The other way, minus the sources and uses give minus the parts**
|
||
(`constructive_neg`; Kani `constructive_mirrors`): a payment made for the
|
||
company and its reversal drop out together. -/
|
||
theorem constructive_neg (w : OnBehalf) (s u : List Int) :
|
||
constructive w.mirror (s.map (fun x => -x)) (u.map (fun x => -x))
|
||
= ((constructive w s u).1.map (fun x => -x), (constructive w s u).2.map (fun x => -x)) := by
|
||
simp only [constructive, onBehalfCash_mirror]
|
||
rw [applyCash_neg (onBehalfCash w s u) s, applyCash_neg (-onBehalfCash w s u) u]
|
||
|
||
/-- Three sections' figures, summed. -/
|
||
def sum3 (xs : List Int) : Int := xs.getD 0 0 + xs.getD 1 0 + xs.getD 2 0
|
||
|
||
@[simp] theorem sum3_cons (a b c : Int) : sum3 [a, b, c] = a + b + c := by
|
||
simp [sum3]
|
||
|
||
/-- **The kernel's parts, by section, net to zero**: split the uses into
|
||
operating ones then investing ones (`kernel::constructive` takes them in
|
||
that order), and the triple (operating, investing, financing = the
|
||
sources) the engine adds to an entry's flows sums to zero. -/
|
||
theorem constructive_sections_net (w : OnBehalf) (s uo ui : List Int) :
|
||
sum3 [isum ((constructive w s (uo ++ ui)).2.take uo.length),
|
||
isum ((constructive w s (uo ++ ui)).2.drop uo.length),
|
||
isum (constructive w s (uo ++ ui)).1] = 0 := by
|
||
have h := constructive_nets w s (uo ++ ui)
|
||
have hs := isum_append ((constructive w s (uo ++ ui)).2.take uo.length)
|
||
((constructive w s (uo ++ ui)).2.drop uo.length)
|
||
rw [List.take_append_drop] at hs
|
||
rw [sum3_cons]
|
||
omega
|
||
|
||
/-- Passes add: two triples that net to zero add to one that does. -/
|
||
theorem sum3_add (xs ys : List Int) (hx : sum3 xs = 0) (hy : sum3 ys = 0) :
|
||
sum3 [xs.getD 0 0 + ys.getD 0 0, xs.getD 1 0 + ys.getD 1 0, xs.getD 2 0 + ys.getD 2 0] = 0 := by
|
||
rw [sum3_cons]
|
||
simp only [sum3] at hx hy
|
||
omega
|
||
|
||
/-- Two triples, added section by section. -/
|
||
def add3 (xs ys : List Int) : List Int :=
|
||
[xs.getD 0 0 + ys.getD 0 0, xs.getD 1 0 + ys.getD 1 0, xs.getD 2 0 + ys.getD 2 0]
|
||
|
||
/-- An entry's flows on the statement: its cash flows, plus `C e`, what a
|
||
member or lender paid or received for it, by section (rule 11). -/
|
||
def stmtFlows (F : List Line → Line → Flow) (C : List Line → List Int) (e : List Line) :
|
||
List Int :=
|
||
add3 (flows F e) (C e)
|
||
|
||
/-- **An entry's statement flows sum to its cash** when what was handled
|
||
for it nets to zero: rule 11 moves no cash (`stmt_flows_sum`). -/
|
||
theorem stmt_flows_sum (F : List Line → Line → Flow) (C : List Line → List Int)
|
||
(e : List Line) (h : net e = 0) (hC : sum3 (C e) = 0) :
|
||
sum3 (stmtFlows F C e) = cashOf F e := by
|
||
have he := entry_flows_sum F e h
|
||
rw [isum_three _ (flows_length F e)] at he
|
||
simp only [stmtFlows, add3, sum3_cons]
|
||
simp only [sum3] at hC
|
||
omega
|
||
|
||
theorem getD_neg (xs : List Int) (k : Nat) :
|
||
(xs.map (fun x => -x)).getD k 0 = -xs.getD k 0 := by
|
||
induction xs generalizing k with
|
||
| nil => simp
|
||
| cons x xs ih => cases k with
|
||
| zero => simp
|
||
| succ k => simp only [List.map_cons, List.getD_cons_succ, ih]
|
||
|
||
/-- **A reversal's statement flows are its original's, negated**, when its
|
||
lines take the original's tags and what was handled for it mirrors
|
||
(`constructive_neg`). -/
|
||
theorem stmt_flows_reversal (F : List Line → Line → Flow) (C : List Line → List Int)
|
||
(e : List Line) (hF : ∀ l ∈ e, F (reversal e) l.reverse = F e l)
|
||
(hC : C (reversal e) = (C e).map (fun x => -x)) :
|
||
stmtFlows F C (reversal e) = (stmtFlows F C e).map (fun x => -x) := by
|
||
simp only [stmtFlows, add3, flows_reversal F e hF, hC, getD_neg, List.map_cons,
|
||
List.map_nil, List.cons.injEq, and_true]
|
||
refine ⟨?_, ?_, ?_⟩ <;> omega
|
||
|
||
/-- A section's total over some entries: the sum of each entry's cash in
|
||
it (`k` = 0 operating, 1 investing, 2 financing), what a member or lender
|
||
handled for it included. -/
|
||
def sectionTotal (F : List Line → Line → Flow) (C : List Line → List Int) (k : Nat) :
|
||
List (List Line) → Int
|
||
| [] => 0
|
||
| e :: es => (stmtFlows F C e).getD k 0 + sectionTotal F C k es
|
||
|
||
/-- The entries' cash, summed. -/
|
||
def cashTotal (F : List Line → Line → Flow) : List (List Line) → Int
|
||
| [] => 0
|
||
| e :: es => cashOf F e + cashTotal F es
|
||
|
||
/-- **The sections sum to the change in cash** (plan P3): over any balanced
|
||
entries, operating + investing + financing is the entries' cash, exactly,
|
||
whatever a member or lender handled for them as long as it nets to zero
|
||
within each entry (rule 11, `constructive_nets`). The statement's footing
|
||
(rules.md B-14 rule 9) can't fail on posted entries. -/
|
||
theorem cashflow_sections_sum_to_cash_change (F : List Line → Line → Flow)
|
||
(C : List Line → List Int) (es : List (List Line)) (h : ∀ e ∈ es, net e = 0)
|
||
(hC : ∀ e ∈ es, sum3 (C e) = 0) :
|
||
sectionTotal F C 0 es + sectionTotal F C 1 es + sectionTotal F C 2 es = cashTotal F es := by
|
||
induction es with
|
||
| nil => rfl
|
||
| cons e es ih =>
|
||
simp only [sectionTotal, cashTotal]
|
||
have he := stmt_flows_sum F C e (h e (by simp)) (hC e (by simp))
|
||
simp only [sum3] at he
|
||
have := ih (fun x hx => h x (by simp [hx])) (fun x hx => hC x (by simp [hx]))
|
||
omega
|
||
|
||
/-- When the cash tag follows the account (a line is cash exactly when
|
||
its account is a cash account), an entry's cash is the movement of the
|
||
cash accounts. -/
|
||
theorem secNet_cash_is_movement (f : Line → Flow) (cash : List Nat) (hd : Distinct cash)
|
||
(hf : ∀ l, f l = .cash ↔ l.account ∈ cash) (ls : List Line) :
|
||
secNet f .cash ls = sumOver (fun a => movement a ls) cash := by
|
||
induction ls with
|
||
| nil => simpa using (sumOver_zero cash).symm
|
||
| cons l ls ih =>
|
||
simp only [secNet_cons, movement_cons]
|
||
rw [sumOver_add, ← ih]
|
||
by_cases hc : l.account ∈ cash
|
||
· rw [ite_eq_left ((hf l).2 hc), sumOver_indicator l.account l.signed cash hd hc]
|
||
· rw [ite_eq_right (fun e => hc ((hf l).1 e)), sumOver_indicator_absent l.account l.signed cash hc]
|
||
|
||
/-! ### Net income reconciled to operating cash (rules.md B-14 rule 8) -/
|
||
|
||
/-- Net movement of the lines on revenue and expense accounts (`pl`)
|
||
tagged `k` (`sel = true`), or on balance-sheet accounts (`sel = false`). -/
|
||
def plNet (pl : Nat → Bool) (sel : Bool) (f : Line → Flow) (k : Flow) : List Line → Int
|
||
| [] => 0
|
||
| l :: ls => (if pl l.account = sel ∧ f l = k then l.signed else 0) + plNet pl sel f k ls
|
||
|
||
/-- Net movement of every revenue and expense line. -/
|
||
def plAll (pl : Nat → Bool) : List Line → Int
|
||
| [] => 0
|
||
| l :: ls => (if pl l.account = true then l.signed else 0) + plAll pl ls
|
||
|
||
/-- Net income: revenue less expenses, `-(net of the P&L lines)`. -/
|
||
def netIncome (pl : Nat → Bool) (e : List Line) : Int := -plAll pl e
|
||
|
||
/-- G: the P&L lines rule 4 puts in investing (gains and losses). -/
|
||
def gainLoss (pl : Nat → Bool) (F : List Line → Line → Flow) (e : List Line) : Int :=
|
||
plNet pl true (F e) .investing e
|
||
|
||
/-- W: minus the movement of the operating balance-sheet lines. -/
|
||
def balanceChange (pl : Nat → Bool) (F : List Line → Line → Flow) (e : List Line) : Int :=
|
||
-plNet pl false (F e) .operating e
|
||
|
||
/-- X: what no cash, real or handled for the company, settled of the
|
||
operating movement. -/
|
||
def noncashOp (F : List Line → Line → Flow) (C : List Line → List Int) (e : List Line) : Int :=
|
||
secNet (F e) .operating e + (stmtFlows F C e).getD 0 0
|
||
|
||
theorem secNet_split (pl : Nat → Bool) (f : Line → Flow) (k : Flow) (ls : List Line) :
|
||
secNet f k ls = plNet pl true f k ls + plNet pl false f k ls := by
|
||
induction ls with
|
||
| nil => rfl
|
||
| cons l ls ih =>
|
||
simp only [secNet_cons, plNet, ih]
|
||
cases pl l.account <;> by_cases h : f l = k <;> simp [h] <;> omega
|
||
|
||
theorem plAll_split (pl : Nat → Bool) (f : Line → Flow) (ls : List Line)
|
||
(h : ∀ l ∈ ls, pl l.account = true → f l = .operating ∨ f l = .investing) :
|
||
plAll pl ls = plNet pl true f .operating ls + plNet pl true f .investing ls := by
|
||
induction ls with
|
||
| nil => rfl
|
||
| cons l ls ih =>
|
||
simp only [plAll, plNet, ih (fun m hm => h m (by simp [hm]))]
|
||
cases hp : pl l.account
|
||
· simp
|
||
· rcases h l (by simp) hp with hk | hk <;> simp [hk] <;> omega
|
||
|
||
/-- **Net income reconciles to operating cash**, entry by entry:
|
||
operating cash = NI + G + X + W, when revenue and expense lines are only
|
||
ever operating or investing (rules.md B-14 rule 8; ASC 230-10-45-28 to
|
||
45-30), whatever a member or lender handled for it (rule 11). -/
|
||
theorem operating_reconciliation_ties (pl : Nat → Bool) (F : List Line → Line → Flow)
|
||
(C : List Line → List Int) (e : List Line)
|
||
(h : ∀ l ∈ e, pl l.account = true → F e l = .operating ∨ F e l = .investing) :
|
||
(stmtFlows F C e).getD 0 0
|
||
= netIncome pl e + gainLoss pl F e + noncashOp F C e + balanceChange pl F e := by
|
||
have hs := secNet_split pl (F e) .operating e
|
||
have hp := plAll_split pl (F e) e h
|
||
simp only [netIncome, gainLoss, noncashOp, balanceChange]
|
||
omega
|
||
|
||
/-- The operating section's total over some entries. -/
|
||
def opTotal (pl : Nat → Bool) (F : List Line → Line → Flow) (C : List Line → List Int) :
|
||
List (List Line) → Int
|
||
| [] => 0
|
||
| e :: es => netIncome pl e + gainLoss pl F e + noncashOp F C e + balanceChange pl F e
|
||
+ opTotal pl F C es
|
||
|
||
/-- **The reconciliation schedule ties to the operating section** over any
|
||
entries: the section's total is NI + G + X + W summed. -/
|
||
theorem operating_reconciliation_ties_all (pl : Nat → Bool) (F : List Line → Line → Flow)
|
||
(C : List Line → List Int) (es : List (List Line))
|
||
(h : ∀ e ∈ es, ∀ l ∈ e, pl l.account = true → F e l = .operating ∨ F e l = .investing) :
|
||
sectionTotal F C 0 es = opTotal pl F C es := by
|
||
induction es with
|
||
| nil => rfl
|
||
| cons e es ih =>
|
||
simp only [sectionTotal, opTotal]
|
||
rw [operating_reconciliation_ties pl F C e (h e (by simp)),
|
||
ih (fun x hx => h x (by simp [hx]))]
|
||
|
||
end Wanderbooks
|