From b4bc6aecbfc4dd78409085221a8b88ee4129b171 Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Thu, 8 Jan 2026 22:57:34 +0100 Subject: Pre-destruction commit #2 --- lib/ledger.ml | 256 ++++++++++++++++++++++++++++++++++------------------- lib/ledger.mli | 133 ---------------------------- lib/ledger.mli.bak | 133 ++++++++++++++++++++++++++++ 3 files changed, 297 insertions(+), 225 deletions(-) delete mode 100644 lib/ledger.mli create mode 100644 lib/ledger.mli.bak (limited to 'lib') diff --git a/lib/ledger.ml b/lib/ledger.ml index 54a030e..7de131f 100644 --- a/lib/ledger.ml +++ b/lib/ledger.ml @@ -187,28 +187,34 @@ module Gh = struct type global (* The five top-level categories *) - type asset - type equity - type expense - type income - type liability + type asset [@@deriving sexp_of] + type equity [@@deriving sexp_of] + type expense [@@deriving sexp_of] + type income [@@deriving sexp_of] + type liability [@@deriving sexp_of] + + (* Subcategories of assets *) + type bank [@@deriving sexp_of] (* No subcategories *) - type final + type final [@@deriving sexp_of] end module Account_structure0 (F : sig - type 'a t + type 'a t [@@deriving sexp_of] end) = struct type 'a f = | Accounts_payable : Gh.final f F.t -> Gh.liability f | Accounts_receivable : Gh.final f F.t -> Gh.asset f - | Bank : Gh.final f F.t -> Gh.asset f + | Bank : Gh.bank f F.t -> Gh.asset f | Cash : Gh.final f F.t -> Gh.asset f | Credit : Gh.final f F.t -> Gh.liability f | Mutual_fund : Gh.final f F.t -> Gh.asset f | Stock : Gh.final f F.t -> Gh.asset f + | Savings : Gh.final f F.t -> Gh.bank f + | Checking : Gh.final f F.t -> Gh.bank f + [@@deriving sexp_of] type t0 = | Asset of Gh.asset f F.t @@ -216,18 +222,19 @@ struct | Expense of Gh.expense f F.t | Income of Gh.income f F.t | Liability of Gh.liability f F.t + [@@deriving sexp_of] end module Account_type = struct - type 'a elem = Leaf | Node of 'a + type 'a elem = Leaf | Node of 'a [@@deriving sexp_of] include Account_structure0 (struct - type 'a t = 'a elem + type 'a t = 'a elem [@@deriving sexp_of] end) end module Account_structure (F : sig - type 'a t + type 'a t [@@deriving sexp_of] end) = struct include Account_structure0 (F) @@ -272,14 +279,36 @@ struct | Stock v -> let%map c, v' = f.car v (fun el -> mkt (Account_type.Stock el)) in (c, Stock v') + | Savings v -> + let%map c, v' = f.car v (fun el -> mkt (Account_type.Savings el)) in + (c, Savings v') + | Checking v -> + let%map c, v' = f.car v (fun el -> mkt (Account_type.Checking el)) in + (c, Checking v') + end + + module Folder = struct + type nonrec 'b t = { car : 'a. 'a f F.t -> 'b } + + let fold (type b c) (f : c t) : b f -> c = function + | Accounts_payable v -> f.car v + | Accounts_receivable v -> f.car v + | Bank v -> f.car v + | Cash v -> f.car v + | Credit v -> f.car v + | Mutual_fund v -> f.car v + | Stock v -> f.car v + | Savings v -> f.car v + | Checking v -> f.car v end end module Typed_account_path = struct type 'a elem = Leaf | Node of string * 'a | Ind of string * 'a elem + [@@deriving sexp_of] include Account_structure (struct - type 'a t = 'a elem + type 'a t = 'a elem [@@deriving sexp_of] end) end @@ -300,78 +329,158 @@ module Account_hierarchy = struct | Ind of 'a account String.Map.t (* Has no subaccounts, has a balance in a certain commodity *) | Leaf of Commodity_id.t * Money.Diff.t + [@@deriving sexp_of] - and extra = { description : String.t } + and extra = { description : String.t } [@@deriving sexp_of] and 'a account = extra * 'a core include Account_structure (struct - type 'a t = 'a account + type 'a t = 'a account [@@deriving sexp_of] end) (* All accounts *) type world = t0 String.Map.t - let rec alter_aux (subaid : Account_path.t) + let rec unsafe_alter_aux (subaid : Account_path.t) (f : - Account_type.t0 -> Commodity_id.t -> Money.Diff.t -> 'a * Money.Diff.t) - : 'a Mapper.t = + Account_type.t0 -> + extra -> + Commodity_id.t -> + Money.Diff.t -> + 'a * extra * Money.Diff.t) : 'a Mapper.t = { car = (fun in_acc mkt -> let open Option.Let_syntax in match (subaid, in_acc) with | [], (extra, Leaf (acc_comm, acc_bal)) -> - let x, acc_bal' = f (mkt Account_type.Leaf) acc_comm acc_bal in - Some (x, (extra, Leaf (acc_comm, acc_bal'))) + let x, extra', acc_bal' = + f (mkt Account_type.Leaf) extra acc_comm acc_bal + in + Some (x, (extra', Leaf (acc_comm, acc_bal'))) | [], _ -> None | subaid0 :: subaid, (extra, Node subaccs) -> - let open Option.Let_syntax in let%bind subacc = Map.find subaccs subaid0 in let%map x, subacc' = - Mapper.map (alter_aux subaid f) (fun k -> mkt (Node k)) subacc + Mapper.map + (unsafe_alter_aux subaid f) + (fun k -> mkt (Node k)) + subacc in (x, (extra, Node (Map.set subaccs ~key:subaid0 ~data:subacc'))) | subaid0 :: subaid, (extra, Ind subaccs) -> - let open Option.Let_syntax in let%bind subacc = Map.find subaccs subaid0 in - let%map x, subacc' = (alter_aux subaid f).car subacc mkt in + let%map x, subacc' = (unsafe_alter_aux subaid f).car subacc mkt in (x, (extra, Ind (Map.set subaccs ~key:subaid0 ~data:subacc'))) | _ :: _, (_, Leaf _) -> None); } - let alter (aid : Account_path.t) + let unsafe_alter (aid : Account_path.t) (f : - Account_type.t0 -> Commodity_id.t -> Money.Diff.t -> 'a * Money.Diff.t) - (w : world) : ('a * world) option = + Account_type.t0 -> + extra -> + Commodity_id.t -> + Money.Diff.t -> + 'a * extra * Money.Diff.t) (w : world) : ('a * world) option = match aid with | [] -> None | aid0 :: subaid -> ( let open Option.Let_syntax in match%bind Map.find w aid0 with | Asset acc -> - let%map x, acc' = (alter_aux subaid f).car acc (fun k -> Asset k) in + let%map x, acc' = + (unsafe_alter_aux subaid f).car acc (fun k -> Asset k) + in (x, Map.set w ~key:aid0 ~data:(Asset acc')) | Expense acc -> let%map x, acc' = - (alter_aux subaid f).car acc (fun k -> Expense k) + (unsafe_alter_aux subaid f).car acc (fun k -> Expense k) in (x, Map.set w ~key:aid0 ~data:(Expense acc')) | Income acc -> let%map x, acc' = - (alter_aux subaid f).car acc (fun k -> Income k) + (unsafe_alter_aux subaid f).car acc (fun k -> Income k) in (x, Map.set w ~key:aid0 ~data:(Income acc')) | Liability acc -> let%map x, acc' = - (alter_aux subaid f).car acc (fun k -> Liability k) + (unsafe_alter_aux subaid f).car acc (fun k -> Liability k) in (x, Map.set w ~key:aid0 ~data:(Liability acc')) | Equity acc -> let%map x, acc' = - (alter_aux subaid f).car acc (fun k -> Equity k) + (unsafe_alter_aux subaid f).car acc (fun k -> Equity k) in (x, Map.set w ~key:aid0 ~data:(Equity acc'))) + (** Update the balance (debit/credit ([dc])) of account [aid] [by_amount] + (commodity: [in_comm]) in [world], giving the updated world and the pre + and post balances for [aid] iff the account exists in [world]. *) + let update_bal aid dc by_amount in_comm (w : world) : + (Money.Diff.t * Money.Diff.t * world) option = + let open Option.Let_syntax in + let%bind mres, w' = + unsafe_alter aid + (fun acc_type acc_extra acc_comm acc_bal -> + if not ([%equal: Commodity_id.t] acc_comm in_comm) then + (None, acc_extra, acc_bal) + else + let on_debit = + match acc_type with + | Asset _ -> `Incr + | Expense _ -> `Incr + | Income _ -> `Decr + | Liability _ -> `Decr + | Equity _ -> `Decr + in + let acc_bal' = + Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit) + in + (Some (acc_bal, acc_bal'), acc_extra, acc_bal')) + w + in + let%map pre_bal, post_bal = mres in + (pre_bal, post_bal, w') + + let get_bal aid (w : world) : (Commodity_id.t * Money.Diff.t) option = + let open Option.Let_syntax in + let%map cb, _world' = + unsafe_alter aid + (fun _acc_type acc_extra acc_comm acc_bal -> + ((acc_comm, acc_bal), acc_extra, acc_bal)) + w + in + cb + + let add_balance_maps m1 m2 : Money.Diff.t Commodity_id.Map.t = + Map.merge m1 m2 ~f:(fun ~key:_comm -> function + | `Both (b1, b2) -> Some Money.Diff.(b1 + b2) + | `Left b | `Right b -> Some b) + + let rec collect_balances : type a. + a f account -> Money.Diff.t Commodity_id.Map.t = function + | _extra, Leaf (acc_comm, acc_bal) -> + Commodity_id.Map.singleton acc_comm acc_bal + | _extra, Ind subaccs -> + Map.fold subaccs ~init:Commodity_id.Map.empty + ~f:(fun ~key:_ ~data:subacc comm_bal_sums -> + add_balance_maps comm_bal_sums (collect_balances subacc)) + | _extra, Node subaccs -> + Map.fold subaccs ~init:Commodity_id.Map.empty + ~f:(fun ~key:_ ~data:subacc comm_bal_sums -> + add_balance_maps comm_bal_sums + (Folder.fold { car = collect_balances } subacc)) + + type delete_error = Not_found | Nonzero_balance + + (* + let rec delete_aux : type a. (Account_path.t * a f account) -> (a f account, delete_error) result = function + | [], (extra, Leaf (_acc_comm, acc_bal)) -> + if Money.Diff.(acc_bal = ~$0) then + + let delete (aid : Account_path.t) (w : world) = + *) + let world_inst : world = String.Map.of_alist_exn [ @@ -406,12 +515,21 @@ module Account_hierarchy = struct ] end -type bal_assert = { - account : Account_path.t; - labels : Labels.t; - bal : Money.Diff.t; -} -[@@deriving sexp_of] +module Bal_assert = struct + type t = { account : Account_path.t; labels : Labels.t; bal : Money.Diff.t } + [@@deriving sexp_of] +end + +module Account_decl = struct + type t = { + type_ : Account_type.t0; + parent : Account_path.t; + name : string; + commodity : Commodity_id.t; + extra : Account_hierarchy.extra; + } + [@@deriving sexp_of] +end module Tx : sig type entry = { @@ -472,7 +590,8 @@ end type item = | Tx_item of Tx.t - | Bal_assert_item of bal_assert (*| Account_decl_item of account_decl*) + | Bal_assert_item of Bal_assert.t + | Account_decl_item of Account_decl.t [@@deriving sexp_of] type t = item list [@@deriving sexp_of] @@ -482,77 +601,30 @@ module World = struct let empty : t = String.Map.empty - (** Update the balance (debit/credit ([dc])) of account [aid] [by_amount] - (commodity: [in_comm]) in [world], giving the updated world and the pre - and post balances for [aid] iff the account exists in [world]. *) - let update_bal aid dc by_amount in_comm (world : t) : - (Money.Diff.t * Money.Diff.t * t) option = - let open Option.Let_syntax in - let%bind mres, world' = - Account_hierarchy.alter aid - (fun acc_type acc_comm acc_bal -> - if not ([%equal: Commodity_id.t] acc_comm in_comm) then (None, acc_bal) - else - match acc_type with - | Asset _ -> - let acc_bal' = - Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit:`Incr) - in - (Some (acc_bal, acc_bal'), acc_bal') - | Expense _ -> - let acc_bal' = - Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit:`Incr) - in - (Some (acc_bal, acc_bal'), acc_bal') - | Income _ -> - let acc_bal' = - Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit:`Decr) - in - (Some (acc_bal, acc_bal'), acc_bal') - | Liability _ -> - let acc_bal' = - Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit:`Decr) - in - (Some (acc_bal, acc_bal'), acc_bal') - | Equity _ -> - let acc_bal' = - Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit:`Decr) - in - (Some (acc_bal, acc_bal'), acc_bal')) - world - in - let%map pre_bal, post_bal = mres in - (pre_bal, post_bal, world') - - let get_bal aid (world : t) : (Commodity_id.t * Money.Diff.t) option = - let open Option.Let_syntax in - let%map cb, _world' = - Account_hierarchy.alter aid - (fun _acc_type acc_comm acc_bal -> ((acc_comm, acc_bal), acc_bal)) - world - in - cb - let apply_tx (tx : Tx.t) world : t option = Map.fold_option tx.entries ~init:world ~f:(fun ~key:aid ~(data : Tx.entry) world -> let open Option.Let_syntax in let%bind _old_bal, new_bal, world = - update_bal aid data.dc data.amount data.commodity world + Account_hierarchy.update_bal aid data.dc data.amount data.commodity + world in match data.assertion with | None -> Some world | Some bal_ass -> if Money.Diff.(bal_ass = new_bal) then Some world else None) - let apply_ba (ba : bal_assert) world : t option = + let apply_ba (ba : Bal_assert.t) world : t option = let open Option.Let_syntax in - let%bind _comm, bal = get_bal ba.account world in + let%bind _comm, bal = Account_hierarchy.get_bal ba.account world in if not Money.Diff.(bal = ba.bal) then None else Some world + let apply_ad (_ad : Account_decl.t) _world : t option = None + let apply : item -> t -> t option = function | Tx_item tx -> apply_tx tx | Bal_assert_item ba -> apply_ba ba + | Account_decl_item ad -> apply_ad ad end module Ctxd_item = struct end diff --git a/lib/ledger.mli b/lib/ledger.mli deleted file mode 100644 index 0b8e383..0000000 --- a/lib/ledger.mli +++ /dev/null @@ -1,133 +0,0 @@ -open Prelude - -(* -type account_type = Asset | Equity | Liability | Expense | Income -[@@deriving compare, sexp]*) - -type tx_type = - | Interest_tx - | Online_banking_tx - | Recurrent_direct_tx - | Payment_terminal_tx - | Cash_payment_tx - | Atm_tx - | Auto_save_rounding_tx - | Batch_tx - | Direct_debit_tx - | Periodic_tx - -type iban_tag = Account_tag | Counterparty_iban_tag [@@deriving compare, sexp] - -type unit_tag = Filed_tag | Google_pay_tag | Auto_round_savings_tag -[@@deriving compare, sexp] - -type string_tag = - | Desc_tag - | User_tag - | Counterparty_name_tag - | Reference_tag - | Mandate_id_tag - | Creditor_id_tag - | Other_party_tag - | Transaction_tag - | Terminal_tag - | Card_seq_no_tag - | Savings_account_tag -[@@deriving compare, sexp] - -module Label : sig - type 'a t = - | Iban_label : iban_tag -> Iban.t t - | String_label : string_tag -> string t - | Timestamp_label : Time_ns.t t - | Unit_label : unit_tag -> unit t - - val int_to_cmp : int -> ('a, 'a) Dmap.cmp - val compare : 'a1 'a2. 'a1 t -> 'a2 t -> ('a1, 'a2) Dmap.cmp -end - -module Labels : sig - include Dmap.S with type 'a key = 'a Label.t - - val sexp_of_binding : binding -> Sexp.t - val binding_of_sexp : Sexp.t -> binding - - include Sexpable.S with type t := t -end - -module Money : sig - type t - - val equal : t -> t -> bool - val compare : t -> t -> int - val of_bigint : Bigint.t -> t - val to_bigint : t -> Bigint.t - val ( + ) : t -> t -> t - val ( - ) : t -> t -> t - val ( = ) : t -> t -> bool - val ( ~$ ) : int -> t - val sexp_of_t : t -> Sexp.t -end - -type commodity_id = string -(* TODO: consider making this UUID *) [@@deriving equal, compare, sexp] - -type scalar = - | Amount of Money.t - | Rate of { in_primary_commodity : Money.t; rate : Bigdecimal.t } -[@@deriving equal, compare, sexp_of] - -module Account_id : sig - type t = string list [@@deriving sexp, compare] -end - -type account = { - id : Account_id.t; - description : string list; - commodity_id : commodity_id; - balance : Money.t; -} -[@@deriving sexp_of] - -type bal_assert = { - account : Account_id.t; - amount : Money.t; - labels : Labels.t; -} -[@@deriving sexp_of] - -module Account_id_map : Map.S with type Key.t = Account_id.t - -module Debit_credit : sig - type t = Debit | Credit [@@deriving string, sexp_of] - - val opposite : t -> t -end - -module Tx : sig - (* Private because we only want to allow constructing balanced transactions. *) - type t = private { - cleared : Date.t option; - commodity_id : commodity_id; - entries : (Debit_credit.t * scalar * Money.t option) Account_id_map.t; - labels : Labels.t; - } - - type error = Unbalanced - - val make : - cleared:Date.t option -> - commodity_id:commodity_id -> - entries:(Debit_credit.t * scalar * Money.t option) Account_id_map.t -> - labels:Labels.t -> - (t, error) result - - val sexp_of_t : t -> Sexp.t -end - -type item = Tx_item of Tx.t | Bal_assert_item of bal_assert -[@@deriving sexp_of] - -type t [@@deriving sexp_of] - -val make : item list -> t diff --git a/lib/ledger.mli.bak b/lib/ledger.mli.bak new file mode 100644 index 0000000..0b8e383 --- /dev/null +++ b/lib/ledger.mli.bak @@ -0,0 +1,133 @@ +open Prelude + +(* +type account_type = Asset | Equity | Liability | Expense | Income +[@@deriving compare, sexp]*) + +type tx_type = + | Interest_tx + | Online_banking_tx + | Recurrent_direct_tx + | Payment_terminal_tx + | Cash_payment_tx + | Atm_tx + | Auto_save_rounding_tx + | Batch_tx + | Direct_debit_tx + | Periodic_tx + +type iban_tag = Account_tag | Counterparty_iban_tag [@@deriving compare, sexp] + +type unit_tag = Filed_tag | Google_pay_tag | Auto_round_savings_tag +[@@deriving compare, sexp] + +type string_tag = + | Desc_tag + | User_tag + | Counterparty_name_tag + | Reference_tag + | Mandate_id_tag + | Creditor_id_tag + | Other_party_tag + | Transaction_tag + | Terminal_tag + | Card_seq_no_tag + | Savings_account_tag +[@@deriving compare, sexp] + +module Label : sig + type 'a t = + | Iban_label : iban_tag -> Iban.t t + | String_label : string_tag -> string t + | Timestamp_label : Time_ns.t t + | Unit_label : unit_tag -> unit t + + val int_to_cmp : int -> ('a, 'a) Dmap.cmp + val compare : 'a1 'a2. 'a1 t -> 'a2 t -> ('a1, 'a2) Dmap.cmp +end + +module Labels : sig + include Dmap.S with type 'a key = 'a Label.t + + val sexp_of_binding : binding -> Sexp.t + val binding_of_sexp : Sexp.t -> binding + + include Sexpable.S with type t := t +end + +module Money : sig + type t + + val equal : t -> t -> bool + val compare : t -> t -> int + val of_bigint : Bigint.t -> t + val to_bigint : t -> Bigint.t + val ( + ) : t -> t -> t + val ( - ) : t -> t -> t + val ( = ) : t -> t -> bool + val ( ~$ ) : int -> t + val sexp_of_t : t -> Sexp.t +end + +type commodity_id = string +(* TODO: consider making this UUID *) [@@deriving equal, compare, sexp] + +type scalar = + | Amount of Money.t + | Rate of { in_primary_commodity : Money.t; rate : Bigdecimal.t } +[@@deriving equal, compare, sexp_of] + +module Account_id : sig + type t = string list [@@deriving sexp, compare] +end + +type account = { + id : Account_id.t; + description : string list; + commodity_id : commodity_id; + balance : Money.t; +} +[@@deriving sexp_of] + +type bal_assert = { + account : Account_id.t; + amount : Money.t; + labels : Labels.t; +} +[@@deriving sexp_of] + +module Account_id_map : Map.S with type Key.t = Account_id.t + +module Debit_credit : sig + type t = Debit | Credit [@@deriving string, sexp_of] + + val opposite : t -> t +end + +module Tx : sig + (* Private because we only want to allow constructing balanced transactions. *) + type t = private { + cleared : Date.t option; + commodity_id : commodity_id; + entries : (Debit_credit.t * scalar * Money.t option) Account_id_map.t; + labels : Labels.t; + } + + type error = Unbalanced + + val make : + cleared:Date.t option -> + commodity_id:commodity_id -> + entries:(Debit_credit.t * scalar * Money.t option) Account_id_map.t -> + labels:Labels.t -> + (t, error) result + + val sexp_of_t : t -> Sexp.t +end + +type item = Tx_item of Tx.t | Bal_assert_item of bal_assert +[@@deriving sexp_of] + +type t [@@deriving sexp_of] + +val make : item list -> t -- cgit v1.2.3