diff options
Diffstat (limited to 'lib/account.ml')
| -rw-r--r-- | lib/account.ml | 188 |
1 files changed, 151 insertions, 37 deletions
diff --git a/lib/account.ml b/lib/account.ml index 988b55c..3a1aff0 100644 --- a/lib/account.ml +++ b/lib/account.ml | |||
| @@ -4,45 +4,65 @@ open Prelude | |||
| 4 | path that leads to a node in the hierarchy. *) | 4 | path that leads to a node in the hierarchy. *) |
| 5 | module Type_hierarchy : sig | 5 | module Type_hierarchy : sig |
| 6 | type path | 6 | type path |
| 7 | type polarity = Increase_on_debit | Increase_on_credit | ||
| 7 | 8 | ||
| 8 | val children : path -> path list | 9 | val children : path -> path list |
| 9 | val sub : path -> string -> path option | 10 | val sub : path -> string -> path option |
| 10 | val super : path -> path option | 11 | val super : path -> path option |
| 11 | val equal_path : path -> path -> bool | 12 | val equal_path : path -> path -> bool |
| 12 | val is_prefix : path -> prefix:path -> bool | 13 | val is_prefix : path -> prefix:path -> bool |
| 13 | val root : path | 14 | val polarity : path -> polarity |
| 14 | val asset : path | 15 | val asset : path |
| 15 | val equity : path | 16 | val equity : path |
| 16 | val expense : path | 17 | val expense : path |
| 17 | val income : path | 18 | val income : path |
| 18 | val liability : path | 19 | val liability : path |
| 19 | end = struct | 20 | end = struct |
| 21 | type polarity = Increase_on_debit | Increase_on_credit | ||
| 20 | type tree = { car : tree String.Map.t } | 22 | type tree = { car : tree String.Map.t } |
| 21 | type path = Root | Sub of string * path | 23 | type trunk = { car : (polarity * tree) String.Map.t } |
| 24 | type path = Base of string | Sub of string * path | ||
| 22 | 25 | ||
| 23 | let canonical : tree = | 26 | let rec path_to_list ?(suffix = []) p = |
| 24 | let mk alist = { car = String.Map.of_alist_exn alist } in | 27 | match p with |
| 25 | mk | 28 | | Base x -> x :: suffix |
| 26 | [ | 29 | | Sub (x, p') -> path_to_list ~suffix:(x :: suffix) p' |
| 27 | ( "Asset", | ||
| 28 | mk | ||
| 29 | [ | ||
| 30 | ("Accounts_receivable", mk []); | ||
| 31 | ("Bank", mk [ ("Savings", mk []); ("Checking", mk []) ]); | ||
| 32 | ("Cash", mk []); | ||
| 33 | ("Mutual_fund", mk []); | ||
| 34 | ("Stock", mk []); | ||
| 35 | ] ); | ||
| 36 | ("Equity", mk []); | ||
| 37 | ("Expense", mk []); | ||
| 38 | ("Income", mk []); | ||
| 39 | ("Liability", mk [ ("Accounts_payable", mk []); ("Credit", mk []) ]); | ||
| 40 | ] | ||
| 41 | 30 | ||
| 42 | let rec get_node : path -> tree option = function | 31 | let canonical : trunk = |
| 43 | | Root -> Some canonical | 32 | let mk alist : tree = { car = String.Map.of_alist_exn alist } in |
| 33 | { | ||
| 34 | car = | ||
| 35 | String.Map.of_alist_exn | ||
| 36 | [ | ||
| 37 | ( "Asset", | ||
| 38 | ( Increase_on_debit, | ||
| 39 | mk | ||
| 40 | [ | ||
| 41 | ("Accounts_receivable", mk []); | ||
| 42 | ("Bank", mk [ ("Savings", mk []); ("Checking", mk []) ]); | ||
| 43 | ("Cash", mk []); | ||
| 44 | ("Mutual_fund", mk []); | ||
| 45 | ("Stock", mk []); | ||
| 46 | ] ) ); | ||
| 47 | ("Equity", (Increase_on_credit, mk [])); | ||
| 48 | ("Expense", (Increase_on_debit, mk [])); | ||
| 49 | ("Income", (Increase_on_credit, mk [])); | ||
| 50 | ( "Liability", | ||
| 51 | ( Increase_on_credit, | ||
| 52 | mk [ ("Accounts_payable", mk []); ("Credit", mk []) ] ) ); | ||
| 53 | ]; | ||
| 54 | } | ||
| 55 | |||
| 56 | (* In this module, only the following two function entertains the | ||
| 57 | option that the given path may not be valid (i.e., it does not | ||
| 58 | throw an exception for invalid paths). *) | ||
| 59 | let rec get_node : path -> tree option = | ||
| 60 | let open Option.Let_syntax in | ||
| 61 | function | ||
| 62 | | Base x -> | ||
| 63 | let%map _, t = Map.find canonical.car x in | ||
| 64 | t | ||
| 44 | | Sub (t, p) -> | 65 | | Sub (t, p) -> |
| 45 | let open Option.Let_syntax in | ||
| 46 | let%bind super = get_node p in | 66 | let%bind super = get_node p in |
| 47 | Map.find super.car t | 67 | Map.find super.car t |
| 48 | 68 | ||
| @@ -56,28 +76,33 @@ end = struct | |||
| 56 | if Map.mem node.car name then Some (Sub (name, p)) else None | 76 | if Map.mem node.car name then Some (Sub (name, p)) else None |
| 57 | 77 | ||
| 58 | let super : path -> path option = function | 78 | let super : path -> path option = function |
| 59 | | Root -> None | 79 | | Base _ -> None |
| 60 | | Sub (_, super) -> Some super | 80 | | Sub (_, super) -> Some super |
| 61 | 81 | ||
| 62 | let rec equal_path p1 p2 = | 82 | let rec equal_path p1 p2 = |
| 63 | match (p1, p2) with | 83 | match (p1, p2) with |
| 64 | | Root, Root -> true | 84 | | Base x1, Base x2 -> String.(x1 = x2) |
| 65 | | Sub (x1, p1'), Sub (x2, p2') -> String.(x1 = x2) && equal_path p1' p2' | 85 | | Sub (x1, p1'), Sub (x2, p2') -> String.(x1 = x2) && equal_path p1' p2' |
| 66 | | _, _ -> false | 86 | | _, _ -> false |
| 67 | 87 | ||
| 68 | let rec is_prefix (p : path) ~(prefix : path) : bool = | 88 | let is_prefix (p : path) ~(prefix : path) : bool = |
| 69 | match (prefix, p) with | 89 | List.is_prefix (path_to_list p) ~prefix:(path_to_list prefix) |
| 70 | | Root, Root | Root, Sub _ -> true | 90 | ~equal:String.equal |
| 71 | | Sub (x1, p'), Sub (x2, prefix') -> | 91 | |
| 72 | String.(x1 = x2) && is_prefix p' ~prefix:prefix' | 92 | let rec polarity = function |
| 73 | | _ -> false | 93 | | Base x -> |
| 94 | let pol, _ = Map.find_exn canonical.car x in | ||
| 95 | pol | ||
| 96 | | Sub (_, p') -> polarity p' | ||
| 97 | |||
| 98 | let assert_valid acc = | ||
| 99 | match get_node acc with None -> failwith "invalid base account" | _ -> acc | ||
| 74 | 100 | ||
| 75 | let root = Root | 101 | let asset = Base "Asset" |> assert_valid |
| 76 | let asset = sub root "Asset" |> Option.value_exn | 102 | let equity = Base "Equity" |> assert_valid |
| 77 | let equity = sub root "Equity" |> Option.value_exn | 103 | let expense = Base "Expense" |> assert_valid |
| 78 | let expense = sub root "Expense" |> Option.value_exn | 104 | let income = Base "Income" |> assert_valid |
| 79 | let income = sub root "Income" |> Option.value_exn | 105 | let liability = Base "Liability" |> assert_valid |
| 80 | let liability = sub root "Liability" |> Option.value_exn | ||
| 81 | end | 106 | end |
| 82 | 107 | ||
| 83 | module Type = struct | 108 | module Type = struct |
| @@ -94,3 +119,92 @@ module Type = struct | |||
| 94 | let is_strict_super a b = | 119 | let is_strict_super a b = |
| 95 | Type_hierarchy.is_prefix b ~prefix:a && not ([%equal: t] a b) | 120 | Type_hierarchy.is_prefix b ~prefix:a && not ([%equal: t] a b) |
| 96 | end | 121 | end |
| 122 | |||
| 123 | module Debit_credit = struct | ||
| 124 | type t = Debit | Credit [@@deriving string, sexp_of] | ||
| 125 | |||
| 126 | (* let opposite = function Debit -> Credit | Credit -> Debit *) | ||
| 127 | end | ||
| 128 | |||
| 129 | module Money = struct | ||
| 130 | module Amount : sig | ||
| 131 | type t | ||
| 132 | |||
| 133 | val equal : t -> t -> bool | ||
| 134 | val compare : t -> t -> int | ||
| 135 | val of_bigint : Bigint.t -> t option | ||
| 136 | val to_bigint : t -> Bigint.t | ||
| 137 | val ( + ) : t -> t -> t | ||
| 138 | val ( = ) : t -> t -> bool | ||
| 139 | val sexp_of_t : t -> Sexp.t | ||
| 140 | val zero : t | ||
| 141 | end = struct | ||
| 142 | type t = Bigint.t [@@deriving sexp_of] | ||
| 143 | |||
| 144 | let equal = Bigint.equal | ||
| 145 | let compare = Bigint.compare | ||
| 146 | let of_bigint x = if Bigint.(zero <= x) then Some x else None | ||
| 147 | let to_bigint x = x | ||
| 148 | let ( + ) x y = Bigint.(x + y) | ||
| 149 | let ( = ) = equal | ||
| 150 | let zero = Bigint.zero | ||
| 151 | end | ||
| 152 | |||
| 153 | module Diff : sig | ||
| 154 | type t | ||
| 155 | |||
| 156 | val equal : t -> t -> bool | ||
| 157 | val compare : t -> t -> int | ||
| 158 | val of_bigint : Bigint.t -> t | ||
| 159 | val to_bigint : t -> Bigint.t | ||
| 160 | val ( + ) : t -> t -> t | ||
| 161 | val ( +% ) : t -> Amount.t -> t | ||
| 162 | val ( - ) : t -> t -> t | ||
| 163 | val ( -% ) : t -> Amount.t -> t | ||
| 164 | val ( = ) : t -> t -> bool | ||
| 165 | val neg : t -> t | ||
| 166 | val ( ~$ ) : int -> t | ||
| 167 | val sexp_of_t : t -> Sexp.t | ||
| 168 | |||
| 169 | val of_amount : | ||
| 170 | Amount.t -> Debit_credit.t -> on_debit:[ `Incr | `Decr ] -> t | ||
| 171 | end = struct | ||
| 172 | type t = Bigint.t [@@deriving sexp_of] | ||
| 173 | |||
| 174 | let equal = Bigint.equal | ||
| 175 | let compare = Bigint.compare | ||
| 176 | let of_bigint x = x | ||
| 177 | let to_bigint x = x | ||
| 178 | let ( + ) x y = Bigint.(x + y) | ||
| 179 | let ( +% ) x y = x + of_bigint (Amount.to_bigint y) | ||
| 180 | let ( - ) x y = Bigint.(x - y) | ||
| 181 | let ( -% ) x y = x - of_bigint (Amount.to_bigint y) | ||
| 182 | let ( = ) = equal | ||
| 183 | let neg = Bigint.neg | ||
| 184 | let ( ~$ ) = Fn.compose of_bigint Bigint.of_int | ||
| 185 | |||
| 186 | let of_amount x (dc : Debit_credit.t) ~on_debit = | ||
| 187 | match (dc, on_debit) with | ||
| 188 | | Debit, `Incr -> of_bigint (Amount.to_bigint x) | ||
| 189 | | Credit, `Incr -> neg (of_bigint (Amount.to_bigint x)) | ||
| 190 | | Debit, `Decr -> neg (of_bigint (Amount.to_bigint x)) | ||
| 191 | | Credit, `Decr -> of_bigint (Amount.to_bigint x) | ||
| 192 | end | ||
| 193 | end | ||
| 194 | |||
| 195 | module Commodity_id = struct | ||
| 196 | type t = string [@@deriving equal, compare, sexp] | ||
| 197 | |||
| 198 | module Map = Map.Make (struct | ||
| 199 | type nonrec t = t [@@deriving equal, compare, sexp] | ||
| 200 | end) | ||
| 201 | end | ||
| 202 | |||
| 203 | module Account = struct | ||
| 204 | type t = Type.t * node | ||
| 205 | |||
| 206 | and node = | ||
| 207 | (* Balance in some commodity *) | ||
| 208 | | Leaf of Commodity_id.t * Money.Diff.t | ||
| 209 | | Subtree of node String.Map.t | ||
| 210 | end | ||