Files
wanderbooks/verify/lean/Wanderbooks/Statements.lean
T
swaitsandClaude Opus 5.5 eb4329bb2a
check / check (push) Canceled after 0s
P3: the cash flow statement by ASC 230 (defect 1, D-35, D-42)
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
2026-10-21 09:35:32 -06:00

684 lines
30 KiB
Lean4
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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