summaryrefslogtreecommitdiffstats
path: root/lib/account.ml
blob: 3a1aff0a51c67913d99761c6de06313929dc3a8f (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
open Prelude

(** The 'kernel' of account types: a hierarchy of valid types. A valid type is a
    path that leads to a node in the hierarchy. *)
module Type_hierarchy : sig
  type path
  type polarity = Increase_on_debit | Increase_on_credit

  val children : path -> path list
  val sub : path -> string -> path option
  val super : path -> path option
  val equal_path : path -> path -> bool
  val is_prefix : path -> prefix:path -> bool
  val polarity : path -> polarity
  val asset : path
  val equity : path
  val expense : path
  val income : path
  val liability : path
end = struct
  type polarity = Increase_on_debit | Increase_on_credit
  type tree = { car : tree String.Map.t }
  type trunk = { car : (polarity * tree) String.Map.t }
  type path = Base of string | Sub of string * path

  let rec path_to_list ?(suffix = []) p =
    match p with
    | Base x -> x :: suffix
    | Sub (x, p') -> path_to_list ~suffix:(x :: suffix) p'

  let canonical : trunk =
    let mk alist : tree = { car = String.Map.of_alist_exn alist } in
    {
      car =
        String.Map.of_alist_exn
          [
            ( "Asset",
              ( Increase_on_debit,
                mk
                  [
                    ("Accounts_receivable", mk []);
                    ("Bank", mk [ ("Savings", mk []); ("Checking", mk []) ]);
                    ("Cash", mk []);
                    ("Mutual_fund", mk []);
                    ("Stock", mk []);
                  ] ) );
            ("Equity", (Increase_on_credit, mk []));
            ("Expense", (Increase_on_debit, mk []));
            ("Income", (Increase_on_credit, mk []));
            ( "Liability",
              ( Increase_on_credit,
                mk [ ("Accounts_payable", mk []); ("Credit", mk []) ] ) );
          ];
    }

  (* In this module, only the following two function entertains the
     option that the given path may not be valid (i.e., it does not
     throw an exception for invalid paths). *)
  let rec get_node : path -> tree option =
    let open Option.Let_syntax in
    function
    | Base x ->
        let%map _, t = Map.find canonical.car x in
        t
    | Sub (t, p) ->
        let%bind super = get_node p in
        Map.find super.car t

  (** Always gives [Some] under valid paths, giving a list of valid paths *)
  let children (p : path) : path list =
    let node = Option.value_exn (get_node p) in
    List.map (Map.keys node.car) ~f:(fun sub -> Sub (sub, p))

  let sub (p : path) (name : string) : path option =
    let node = Option.value_exn (get_node p) in
    if Map.mem node.car name then Some (Sub (name, p)) else None

  let super : path -> path option = function
    | Base _ -> None
    | Sub (_, super) -> Some super

  let rec equal_path p1 p2 =
    match (p1, p2) with
    | Base x1, Base x2 -> String.(x1 = x2)
    | Sub (x1, p1'), Sub (x2, p2') -> String.(x1 = x2) && equal_path p1' p2'
    | _, _ -> false

  let is_prefix (p : path) ~(prefix : path) : bool =
    List.is_prefix (path_to_list p) ~prefix:(path_to_list prefix)
      ~equal:String.equal

  let rec polarity = function
    | Base x ->
        let pol, _ = Map.find_exn canonical.car x in
        pol
    | Sub (_, p') -> polarity p'

  let assert_valid acc =
    match get_node acc with None -> failwith "invalid base account" | _ -> acc

  let asset = Base "Asset" |> assert_valid
  let equity = Base "Equity" |> assert_valid
  let expense = Base "Expense" |> assert_valid
  let income = Base "Income" |> assert_valid
  let liability = Base "Liability" |> assert_valid
end

module Type = struct
  type t = Type_hierarchy.path [@@deriving equal]

  let rec base (t : t) : t option =
    match Type_hierarchy.super t with
    | None -> (* [t] is the root type *) None
    | Some t' ->
        (* [t] is a base type if its supertype is the root type *)
        Some (Option.value (base t') ~default:t)

  (** [a] is a strict supertype of [b] *)
  let is_strict_super a b =
    Type_hierarchy.is_prefix b ~prefix:a && not ([%equal: t] a b)
end

module Debit_credit = struct
  type t = Debit | Credit [@@deriving string, sexp_of]

  (*  let opposite = function Debit -> Credit | Credit -> Debit *)
end

module Money = struct
  module Amount : sig
    type t

    val equal : t -> t -> bool
    val compare : t -> t -> int
    val of_bigint : Bigint.t -> t option
    val to_bigint : t -> Bigint.t
    val ( + ) : t -> t -> t
    val ( = ) : t -> t -> bool
    val sexp_of_t : t -> Sexp.t
    val zero : t
  end = struct
    type t = Bigint.t [@@deriving sexp_of]

    let equal = Bigint.equal
    let compare = Bigint.compare
    let of_bigint x = if Bigint.(zero <= x) then Some x else None
    let to_bigint x = x
    let ( + ) x y = Bigint.(x + y)
    let ( = ) = equal
    let zero = Bigint.zero
  end

  module Diff : 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 -> Amount.t -> t
    val ( - ) : t -> t -> t
    val ( -% ) : t -> Amount.t -> t
    val ( = ) : t -> t -> bool
    val neg : t -> t
    val ( ~$ ) : int -> t
    val sexp_of_t : t -> Sexp.t

    val of_amount :
      Amount.t -> Debit_credit.t -> on_debit:[ `Incr | `Decr ] -> t
  end = struct
    type t = Bigint.t [@@deriving sexp_of]

    let equal = Bigint.equal
    let compare = Bigint.compare
    let of_bigint x = x
    let to_bigint x = x
    let ( + ) x y = Bigint.(x + y)
    let ( +% ) x y = x + of_bigint (Amount.to_bigint y)
    let ( - ) x y = Bigint.(x - y)
    let ( -% ) x y = x - of_bigint (Amount.to_bigint y)
    let ( = ) = equal
    let neg = Bigint.neg
    let ( ~$ ) = Fn.compose of_bigint Bigint.of_int

    let of_amount x (dc : Debit_credit.t) ~on_debit =
      match (dc, on_debit) with
      | Debit, `Incr -> of_bigint (Amount.to_bigint x)
      | Credit, `Incr -> neg (of_bigint (Amount.to_bigint x))
      | Debit, `Decr -> neg (of_bigint (Amount.to_bigint x))
      | Credit, `Decr -> of_bigint (Amount.to_bigint x)
  end
end

module Commodity_id = struct
  type t = string [@@deriving equal, compare, sexp]

  module Map = Map.Make (struct
    type nonrec t = t [@@deriving equal, compare, sexp]
  end)
end

module Account = struct
  type t = Type.t * node

  and node =
    (* Balance in some commodity *)
    | Leaf of Commodity_id.t * Money.Diff.t
    | Subtree of node String.Map.t
end