summaryrefslogtreecommitdiffstats
path: root/lib/account/type_hierarchy.ml
diff options
context:
space:
mode:
authorRutger Broekhoff2026-03-09 22:29:18 +0100
committerRutger Broekhoff2026-03-09 22:29:18 +0100
commitc90ff5253efd858a2bf0c20eaa2ee9763a402783 (patch)
treed1c5ab837ece7034d882368f1beeeb56b934ac4d /lib/account/type_hierarchy.ml
parent2f94997e2befc70ada84bd04a56831efe2747220 (diff)
downloadrdcapsis-c90ff5253efd858a2bf0c20eaa2ee9763a402783.tar.gz
rdcapsis-c90ff5253efd858a2bf0c20eaa2ee9763a402783.zip
oha!
Diffstat (limited to 'lib/account/type_hierarchy.ml')
-rw-r--r--lib/account/type_hierarchy.ml83
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 @@
1open Prelude
2
3type tree = { car : tree String.Map.t }
4type trunk = (Money.polarity * tree) String.Map.t
5type path = Base of string | Sub of string * path
6
7let 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
12let 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). *)
37let 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 *)
48let 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
52let 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
56let super : path -> path option = function
57 | Base _ -> None
58 | Sub (_, super) -> Some super
59
60let 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
66let 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
70let rec polarity = function
71 | Base x ->
72 let pol, _ = Map.find_exn canonical x in
73 pol
74 | Sub (_, p') -> polarity p'
75
76let assert_valid acc =
77 match get_node acc with None -> failwith "invalid base account" | _ -> acc
78
79let asset = Base "Asset" |> assert_valid
80let equity = Base "Equity" |> assert_valid
81let expense = Base "Expense" |> assert_valid
82let income = Base "Income" |> assert_valid
83let liability = Base "Liability" |> assert_valid