P3: party totals by what moved the money, once (defects 14, 11, 24)

Each party's part of an entry is signed as its own account would show it,
with a direction and its cash and in-kind amounts; a reversal is its
original negated. Totals on a cash or all basis: for a member, all equals
the members' equity statement and cash the K-1's cash. Money is cash,
cards and the financing accounts, and a liability credit never pays its
own creditor (creditor_of), so accruals and declared distributions count
once. tin_last4 from the TIN or W-9 facts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-21 12:12:26 -06:00
1 parent 9ab003d0d1
commit c2ceabbae9
52 files changed
+7535 -560

No files matched your search

+4
View File
@@ -67,3 +67,7 @@ open Wanderbooks
#print axioms cashflow_reversal_nets
#print axioms Reachable.cashflow_foots
#print axioms Reachable.cashflow_is_change_in_cash
#print axioms reverseTarget_involutive
#print axioms capitalTarget_odd
#print axioms partyTarget_odd
#print axioms sumOver_movement_reversal
+1
View File
@@ -4,3 +4,4 @@ import Wanderbooks.Statements
import Wanderbooks.Closing
import Wanderbooks.Ledger
import Wanderbooks.Apportion
import Wanderbooks.Parties
+215
View File
@@ -0,0 +1,215 @@
import Wanderbooks.Entry
/-!
# A party's part of an entry (rules.md B-35)
Mirrors `kernel::party_target` and `kernel::reverse_target`, and proves
what makes a reversal the exact opposite of what it reverses: the target
is odd (but for the debits fallback, where a sign means nothing, and a
member's entry whose cash isn't its money: an expense booked straight to
capital, a debt converted, a distribution still owed), reversing a target
twice gives it back, and a reversal's movement over any set of accounts
(a member's equity, say) is the original's negated.
-/
namespace Wanderbooks
/-- Which way an entry moved value with its parties (`kernel::Direction`). -/
inductive Direction where
| paid
| received
| neither
deriving DecidableEq, Repr
/-- Paid and received swap; neither stays neither. -/
def Direction.flip : Direction → Direction
| .paid => .received
| .received => .paid
| .neither => .neither
@[simp] theorem Direction.flip_flip (d : Direction) : d.flip.flip = d := by
cases d <;> rfl
/-- The direction of a signed amount (`Direction::of`). -/
def Direction.of (a : Int) : Direction :=
if 0 < a then .paid else if a < 0 then .received else .neither
theorem Direction.of_neg (a : Int) : Direction.of (-a) = (Direction.of a).flip := by
unfold Direction.of
by_cases h1 : 0 < a
· rw [ite_eq_right (show ¬ (0 < -a) by omega), ite_eq_left (show -a < 0 by omega), ite_eq_left h1]; rfl
· by_cases h2 : a < 0
· rw [ite_eq_left (show 0 < -a by omega), ite_eq_right h1, ite_eq_left h2]; rfl
· rw [ite_eq_right (show ¬ (0 < -a) by omega), ite_eq_right (show ¬ (-a < 0) by omega), ite_eq_right h1,
ite_eq_right h2]; rfl
/-- `kernel::PartyTarget`. -/
structure PartyTarget where
amount : Int
direction : Direction
cash : Int
inKind : Int
deriving DecidableEq, Repr
/-- `kernel::reverse_target`: every amount negated, paid and received
swapped. -/
def reverseTarget (t : PartyTarget) : PartyTarget :=
⟨-t.amount, t.direction.flip, -t.cash, -t.inKind⟩
/-- **Reversing a reversal gives the original back** (B-9). -/
theorem reverseTarget_involutive (t : PartyTarget) :
reverseTarget (reverseTarget t) = t := by
cases t; simp [reverseTarget]
/-- A magnitude. -/
def magnitude (e : Int) : Int := if e < 0 then -e else e
theorem magnitude_neg (e : Int) : magnitude (-e) = magnitude e := by
unfold magnitude
by_cases h : e < 0
· rw [ite_eq_right (show ¬ (-e < 0) by omega), ite_eq_left h]
· by_cases h' : -e < 0
· rw [ite_eq_left h', ite_eq_right h]; omega
· rw [ite_eq_right h', ite_eq_right h]; omega
/-- The magnitude `m` with the sign of `like`. -/
def withSign (like m : Int) : Int := if like < 0 then -m else m
theorem withSign_neg (e m : Int) (he : e ≠ 0) : withSign (-e) m = -withSign e m := by
unfold withSign
by_cases h : e < 0
· rw [ite_eq_right (show ¬ (-e < 0) by omega), ite_eq_left h]; omega
· rw [ite_eq_left (show -e < 0 by omega), ite_eq_right h]
/-- What of a member's capital movement `e` counts, as (cash, in kind)
(`kernel::party_target` INVARIANTS 1-2): of a contribution (`e < 0`), the
property in it, at most `|e|`, in kind and the rest cash; of a
distribution, the money it paid (`net`, at most `e`) as cash, then the
property it gave, at most what is left, in kind. The rest of a
distribution is still owed and counts as nothing. -/
def capitalParts (e property net : Int) : Int × Int :=
if e < 0 then
(magnitude e - min (max property 0) (magnitude e), min (max property 0) (magnitude e))
else
(min (max net 0) (magnitude e),
min (max property 0) (magnitude e - min (max net 0) (magnitude e)))
/-- The capital case of `kernel::party_target` (a member's entry moving
equity `e ≠ 0`), signed as `e`; neither when nothing counts. -/
def capitalTarget (e property net : Int) : PartyTarget :=
⟨e, if (capitalParts e property net).1 + (capitalParts e property net).2 = 0 then .neither
else Direction.of e,
withSign e (capitalParts e property net).1, withSign e (capitalParts e property net).2⟩
/-- The rest of `kernel::party_target`: money moved, else the parties'
own-account movement, else the debits; neither moves nothing. -/
def moneyTarget (net own debits : Int) : PartyTarget :=
if net ≠ 0 then ⟨net, Direction.of net, net, 0⟩
else ⟨if own ≠ 0 then own else debits, .neither, 0, 0⟩
/-- `kernel::party_target`. -/
def partyTarget (capital : Option (Int × Int)) (net own debits : Int) : PartyTarget :=
match capital with
| some (e, p) => if e ≠ 0 then capitalTarget e p net else moneyTarget net own debits
| none => moneyTarget net own debits
/-- A member's entry whose cash is exactly its money: the money moved the
member's way (paid out on a distribution, received on a contribution) is
what isn't property. Not so for an expense booked straight to capital, a
debt converted, or a distribution still owed: the reversal arm, never the
lines, gives their reversals a target. -/
def cashIsMoney (e property net : Int) : Prop :=
min (max (if 0 < e then net else -net) 0) (magnitude e) =
magnitude e - min (max property 0) (magnitude e)
/-- Not vacuous: a cash contribution, a property contribution, and a
distribution of money and property each have their cash as their money. -/
example : cashIsMoney (-100) 0 (-100) ∧ cashIsMoney (-100) 100 0 ∧ cashIsMoney 100 40 60 := by
unfold cashIsMoney magnitude
decide
/-- Negating a member's entry whose cash is its money keeps what counts. -/
theorem capitalParts_neg (e p net : Int) (he : e ≠ 0) (h : cashIsMoney e p net) :
capitalParts (-e) p (-net) = capitalParts e p net := by
unfold cashIsMoney at h
unfold capitalParts
rw [magnitude_neg]
by_cases hn : e < 0
· rw [ite_eq_right (show ¬ (0 < e) by omega)] at h
rw [ite_eq_right (show ¬ (-e < 0) by omega), ite_eq_left hn, h]
congr 1
omega
· rw [ite_eq_left (show 0 < e by omega)] at h
rw [ite_eq_left (show -e < 0 by omega), ite_eq_right hn, h]
congr 1
omega
/-- **A member's capital target is odd** whenever its cash is its money. -/
theorem capitalTarget_odd (e p net : Int) (he : e ≠ 0) (h : cashIsMoney e p net) :
capitalTarget (-e) p (-net) = reverseTarget (capitalTarget e p net) := by
unfold capitalTarget reverseTarget
rw [capitalParts_neg e p net he h, withSign_neg _ _ he, withSign_neg _ _ he]
congr 1
by_cases hc : (capitalParts e p net).1 + (capitalParts e p net).2 = 0
· simp [hc, Direction.flip]
· simp [hc, Direction.of_neg]
/-- With money moved, or the parties' own accounts moved, the rest of the
target is odd too. -/
theorem moneyTarget_odd (net own debits : Int) (h : net ≠ 0 ∨ own ≠ 0) :
moneyTarget (-net) (-own) debits = reverseTarget (moneyTarget net own debits) := by
unfold moneyTarget reverseTarget
by_cases hn : net = 0
· have ho : own ≠ 0 := by omega
have : ¬ (-own = 0) := by omega
simp [hn, ho, this, Direction.flip]
· have : ¬ (-net = 0) := by omega
simp [hn, this, Direction.of_neg]
/-- **The target is odd** (rules.md B-35 item 3, B-9): negating an entry's
lines negates its capital movement, its money net and its own-account
movement, while the property beside a capital movement comes from the
other side of the lines and keeps its size; the result is the original's
`reverseTarget`, but for the debits fallback (nothing moved: a sign means
nothing there) and a member's entry whose cash isn't its money. The
engine's reversal arm, not the lines, gives every reversal its target. -/
theorem partyTarget_odd (capital : Option (Int × Int)) (net own debits : Int)
(h : (∃ e p, capital = some (e, p) ∧ e ≠ 0 ∧ cashIsMoney e p net) ∨
((∀ e p, capital = some (e, p) → e = 0) ∧ (net ≠ 0 ∨ own ≠ 0))) :
partyTarget (capital.map fun c => (-c.1, c.2)) (-net) (-own) debits =
reverseTarget (partyTarget capital net own debits) := by
cases capital with
| none =>
simp only [Option.map, partyTarget]
apply moneyTarget_odd
rcases h with ⟨_, _, hc, _⟩ | ⟨_, h⟩
· cases hc
· exact h
| some c =>
obtain ⟨e, p⟩ := c
simp only [Option.map, partyTarget]
by_cases he : e = 0
· have : ¬ (-e ≠ 0) := by omega
simp only [he, ne_eq, not_true_eq_false, ite_false, Int.neg_zero]
apply moneyTarget_odd
rcases h with ⟨e', p', hc, he', _⟩ | ⟨_, h⟩
· simp at hc; omega
· exact h
· have : -e ≠ 0 := by omega
simp only [ne_eq, he, this, not_false_eq_true, ite_true]
rcases h with ⟨e', p', hc, _, hm⟩ | ⟨hz, _⟩
· simp only [Option.some.injEq, Prod.mk.injEq] at hc
obtain ⟨rfl, rfl⟩ := hc
exact capitalTarget_odd e p net he hm
· exact absurd (hz e p rfl) he
/-- **A reversal's movement over any accounts is the original's negated**
(so the equity movement a member's target reads flips with it): summed
over a set of accounts, `movement_reversal` account by account. -/
theorem sumOver_movement_reversal (as : List Nat) (ls : List Line) :
sumOver (fun a => movement a (reversal ls)) as = -sumOver (fun a => movement a ls) as := by
have : (fun a => movement a (reversal ls)) = (fun a => -movement a ls) := by
funext a; exact movement_reversal a ls
rw [this, sumOver_neg]
end Wanderbooks