diff options
| author | Rutger Broekhoff | 2026-02-22 00:57:41 +0100 |
|---|---|---|
| committer | Rutger Broekhoff | 2026-02-23 21:12:22 +0100 |
| commit | 8bcc0a7c0dfae57824cb1e80db2835f652d3b4ab (patch) | |
| tree | 7d15d67f51b35bbabd28b9c0d1d10e780ecbfb82 /lib/account.ml | |
| parent | 2367d2caa83831992392069c21bd96cb91e113f0 (diff) | |
| download | rdcapsis-8bcc0a7c0dfae57824cb1e80db2835f652d3b4ab.tar.gz rdcapsis-8bcc0a7c0dfae57824cb1e80db2835f652d3b4ab.zip | |
helo
Diffstat (limited to 'lib/account.ml')
| -rw-r--r-- | lib/account.ml | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/lib/account.ml b/lib/account.ml new file mode 100644 index 0000000..988b55c --- /dev/null +++ b/lib/account.ml | |||
| @@ -0,0 +1,96 @@ | |||
| 1 | open Prelude | ||
| 2 | |||
| 3 | (** The 'kernel' of account types: a hierarchy of valid types. A valid type is a | ||
| 4 | path that leads to a node in the hierarchy. *) | ||
| 5 | module Type_hierarchy : sig | ||
| 6 | type path | ||
| 7 | |||
| 8 | val children : path -> path list | ||
| 9 | val sub : path -> string -> path option | ||
| 10 | val super : path -> path option | ||
| 11 | val equal_path : path -> path -> bool | ||
| 12 | val is_prefix : path -> prefix:path -> bool | ||
| 13 | val root : path | ||
| 14 | val asset : path | ||
| 15 | val equity : path | ||
| 16 | val expense : path | ||
| 17 | val income : path | ||
| 18 | val liability : path | ||
| 19 | end = struct | ||
| 20 | type tree = { car : tree String.Map.t } | ||
| 21 | type path = Root | Sub of string * path | ||
| 22 | |||
| 23 | let canonical : tree = | ||
| 24 | let mk alist = { car = String.Map.of_alist_exn alist } in | ||
| 25 | mk | ||
| 26 | [ | ||
| 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 | |||
| 42 | let rec get_node : path -> tree option = function | ||
| 43 | | Root -> Some canonical | ||
| 44 | | Sub (t, p) -> | ||
| 45 | let open Option.Let_syntax in | ||
| 46 | let%bind super = get_node p in | ||
| 47 | Map.find super.car t | ||
| 48 | |||
| 49 | (** Always gives [Some] under valid paths, giving a list of valid paths *) | ||
| 50 | let children (p : path) : path list = | ||
| 51 | let node = Option.value_exn (get_node p) in | ||
| 52 | List.map (Map.keys node.car) ~f:(fun sub -> Sub (sub, p)) | ||
| 53 | |||
| 54 | let sub (p : path) (name : string) : path option = | ||
| 55 | let node = Option.value_exn (get_node p) in | ||
| 56 | if Map.mem node.car name then Some (Sub (name, p)) else None | ||
| 57 | |||
| 58 | let super : path -> path option = function | ||
| 59 | | Root -> None | ||
| 60 | | Sub (_, super) -> Some super | ||
| 61 | |||
| 62 | let rec equal_path p1 p2 = | ||
| 63 | match (p1, p2) with | ||
| 64 | | Root, Root -> true | ||
| 65 | | Sub (x1, p1'), Sub (x2, p2') -> String.(x1 = x2) && equal_path p1' p2' | ||
| 66 | | _, _ -> false | ||
| 67 | |||
| 68 | let rec is_prefix (p : path) ~(prefix : path) : bool = | ||
| 69 | match (prefix, p) with | ||
| 70 | | Root, Root | Root, Sub _ -> true | ||
| 71 | | Sub (x1, p'), Sub (x2, prefix') -> | ||
| 72 | String.(x1 = x2) && is_prefix p' ~prefix:prefix' | ||
| 73 | | _ -> false | ||
| 74 | |||
| 75 | let root = Root | ||
| 76 | let asset = sub root "Asset" |> Option.value_exn | ||
| 77 | let equity = sub root "Equity" |> Option.value_exn | ||
| 78 | let expense = sub root "Expense" |> Option.value_exn | ||
| 79 | let income = sub root "Income" |> Option.value_exn | ||
| 80 | let liability = sub root "Liability" |> Option.value_exn | ||
| 81 | end | ||
| 82 | |||
| 83 | module Type = struct | ||
| 84 | type t = Type_hierarchy.path [@@deriving equal] | ||
| 85 | |||
| 86 | let rec base (t : t) : t option = | ||
| 87 | match Type_hierarchy.super t with | ||
| 88 | | None -> (* [t] is the root type *) None | ||
| 89 | | Some t' -> | ||
| 90 | (* [t] is a base type if its supertype is the root type *) | ||
| 91 | Some (Option.value (base t') ~default:t) | ||
| 92 | |||
| 93 | (** [a] is a strict supertype of [b] *) | ||
| 94 | let is_strict_super a b = | ||
| 95 | Type_hierarchy.is_prefix b ~prefix:a && not ([%equal: t] a b) | ||
| 96 | end | ||