diff options
Diffstat (limited to 'lib/account/type_hierarchy.ml')
| -rw-r--r-- | lib/account/type_hierarchy.ml | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/lib/account/type_hierarchy.ml b/lib/account/type_hierarchy.ml new file mode 100644 index 0000000..7f27830 --- /dev/null +++ b/lib/account/type_hierarchy.ml | |||
| @@ -0,0 +1,83 @@ | |||
| 1 | open Prelude | ||
| 2 | |||
| 3 | type tree = { car : tree String.Map.t } | ||
| 4 | type trunk = (Money.polarity * tree) String.Map.t | ||
| 5 | type path = Base of string | Sub of string * path | ||
| 6 | |||
| 7 | let rec path_to_list ?(suffix = []) p = | ||
| 8 | match p with | ||
| 9 | | Base x -> x :: suffix | ||
| 10 | | Sub (x, p') -> path_to_list ~suffix:(x :: suffix) p' | ||
| 11 | |||
| 12 | let canonical : trunk = | ||
| 13 | let make alist : tree = { car = String.Map.of_alist_exn alist } in | ||
| 14 | String.Map.of_alist_exn | ||
| 15 | [ | ||
| 16 | ( "Asset", | ||
| 17 | ( Money.Increase_on_debit, | ||
| 18 | make | ||
| 19 | [ | ||
| 20 | ("Accounts_receivable", make []); | ||
| 21 | ("Bank", make [ ("Savings", make []); ("Checking", make []) ]); | ||
| 22 | ("Cash", make []); | ||
| 23 | ("Mutual_fund", make []); | ||
| 24 | ("Stock", make []); | ||
| 25 | ] ) ); | ||
| 26 | ("Equity", (Money.Increase_on_credit, make [])); | ||
| 27 | ("Expense", (Money.Increase_on_debit, make [])); | ||
| 28 | ("Income", (Money.Increase_on_credit, make [])); | ||
| 29 | ( "Liability", | ||
| 30 | ( Money.Increase_on_credit, | ||
| 31 | make [ ("Accounts_payable", make []); ("Credit", make []) ] ) ); | ||
| 32 | ] | ||
| 33 | |||
| 34 | (* In this module, only the following two function entertains the | ||
| 35 | option that the given path may not be valid (i.e., it does not | ||
| 36 | throw an exception for invalid paths). *) | ||
| 37 | let rec get_node : path -> tree option = | ||
| 38 | let open Option.Let_syntax in | ||
| 39 | function | ||
| 40 | | Base x -> | ||
| 41 | let%map _, t = Map.find canonical x in | ||
| 42 | t | ||
| 43 | | Sub (t, p) -> | ||
| 44 | let%bind super = get_node p in | ||
| 45 | Map.find super.car t | ||
| 46 | |||
| 47 | (** Always gives [Some] under valid paths, giving a list of valid paths *) | ||
| 48 | let children (p : path) : path list = | ||
| 49 | let node = Option.value_exn (get_node p) in | ||
| 50 | List.map (Map.keys node.car) ~f:(fun sub -> Sub (sub, p)) | ||
| 51 | |||
| 52 | let sub (p : path) (name : string) : path option = | ||
| 53 | let node = Option.value_exn (get_node p) in | ||
| 54 | if Map.mem node.car name then Some (Sub (name, p)) else None | ||
| 55 | |||
| 56 | let super : path -> path option = function | ||
| 57 | | Base _ -> None | ||
| 58 | | Sub (_, super) -> Some super | ||
| 59 | |||
| 60 | let rec equal_path p1 p2 = | ||
| 61 | match (p1, p2) with | ||
| 62 | | Base x1, Base x2 -> String.(x1 = x2) | ||
| 63 | | Sub (x1, p1'), Sub (x2, p2') -> String.(x1 = x2) && equal_path p1' p2' | ||
| 64 | | _, _ -> false | ||
| 65 | |||
| 66 | let is_prefix (p : path) ~(prefix : path) : bool = | ||
| 67 | List.is_prefix (path_to_list p) ~prefix:(path_to_list prefix) | ||
| 68 | ~equal:String.equal | ||
| 69 | |||
| 70 | let rec polarity = function | ||
| 71 | | Base x -> | ||
| 72 | let pol, _ = Map.find_exn canonical x in | ||
| 73 | pol | ||
| 74 | | Sub (_, p') -> polarity p' | ||
| 75 | |||
| 76 | let assert_valid acc = | ||
| 77 | match get_node acc with None -> failwith "invalid base account" | _ -> acc | ||
| 78 | |||
| 79 | let asset = Base "Asset" |> assert_valid | ||
| 80 | let equity = Base "Equity" |> assert_valid | ||
| 81 | let expense = Base "Expense" |> assert_valid | ||
| 82 | let income = Base "Income" |> assert_valid | ||
| 83 | let liability = Base "Liability" |> assert_valid | ||