summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRutger Broekhoff2025-08-26 00:35:27 +0200
committerRutger Broekhoff2025-08-26 00:35:27 +0200
commite6873458facadea0dfb228bb33291d6baf68c427 (patch)
tree9ca19e2bbb12d92447f654a92280a6048383ebba
parentb8fbaa53b912347b3b50cac3e913a142db460b0a (diff)
downloadrdcapsis-e6873458facadea0dfb228bb33291d6baf68c427.tar.gz
rdcapsis-e6873458facadea0dfb228bb33291d6baf68c427.zip
Basic import seems to be working
-rw-r--r--.gitignore1
-rw-r--r--bin/dune2
-rw-r--r--bin/main.ml40
-rw-r--r--lib/convert.ml13
-rw-r--r--lib/iban.ml1
-rw-r--r--lib/iban.mli1
-rw-r--r--lib/ingcsv.ml30
-rw-r--r--lib/ledger.ml60
8 files changed, 123 insertions, 25 deletions
diff --git a/.gitignore b/.gitignore
index c4c4112..27fffed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
3*~ 3*~
4_build/ 4_build/
5_opam/ 5_opam/
6*.csv
diff --git a/bin/dune b/bin/dune
index b82e38f..34c3866 100644
--- a/bin/dune
+++ b/bin/dune
@@ -1,4 +1,6 @@
1(executable 1(executable
2 (public_name rdcapsis) 2 (public_name rdcapsis)
3 (name main) 3 (name main)
4 (preprocess
5 (pps ppx_jane))
4 (libraries rdcapsis)) 6 (libraries rdcapsis))
diff --git a/bin/main.ml b/bin/main.ml
index 7bf6048..1c6c6c9 100644
--- a/bin/main.ml
+++ b/bin/main.ml
@@ -1 +1,39 @@
1let () = print_endline "Hello, World!" 1open Core
2module Time_ns = Time_ns_unix
3
4module List = struct
5 include List
6
7 let map_result ~(f : 'a -> ('b, 'c) result) : 'a list -> ('b list, 'c) result
8 =
9 let open Result.Let_syntax in
10 let rec go = function
11 | [] -> return []
12 | x :: xs ->
13 let%map x' = f x and xs' = go xs in
14 x' :: xs'
15 in
16 go
17end
18
19module Result = struct
20 include Result
21
22 let unwrap = function
23 | Error _ -> failwith "Result.unwrap: unexpected (Error _)"
24 | Ok v -> v
25end
26
27let () =
28 let ams_tz = Time_ns.Zone.find_exn "Europe/Amsterdam" in
29 let prim_txs =
30 In_channel.with_file ~binary:true "test.csv"
31 ~f:(Rdcapsis.Ingcsv.read_channel ~ams_tz)
32 |> Result.unwrap
33 in
34 let euc_id = "EUC" in
35 let ledger =
36 List.map_result ~f:(Rdcapsis.Convert.les_from_current_acc euc_id) prim_txs
37 |> Result.unwrap |> List.concat
38 in
39 print_endline (Sexp.to_string_hum ([%sexp_of: Rdcapsis.Ledger.t] ledger))
diff --git a/lib/convert.ml b/lib/convert.ml
index cbdb90f..fb41020 100644
--- a/lib/convert.ml
+++ b/lib/convert.ml
@@ -1,5 +1,6 @@
1open Core 1open Core
2open Ledger 2open Ledger
3open Result.Let_syntax
3 4
4let virt_checking_acc = [ "Unfiled"; "Checking" ] 5let virt_checking_acc = [ "Unfiled"; "Checking" ]
5let virt_savings_acc = [ "Unfiled"; "Savings" ] 6let virt_savings_acc = [ "Unfiled"; "Savings" ]
@@ -162,3 +163,15 @@ let tx_from_current_acc euc_id (Ingcsv.Tx (base, spec)) =
162 details.counterparty_name 163 details.counterparty_name
163 |> add (String_label Desc_tag) details.description 164 |> add (String_label Desc_tag) details.description
164 |> add (String_label Reference_tag) details.reference) 165 |> add (String_label Reference_tag) details.reference)
166
167let ba_from_current_acc (Ingcsv.Tx (base, _)) =
168 {
169 account = virt_checking_acc;
170 amount = Money.of_z base.resulting_balance;
171 labels = Labels.(empty |> add (Iban_label Account_tag) base.account);
172 }
173
174let les_from_current_acc euc_id tx =
175 let%map tx' = tx_from_current_acc euc_id tx in
176 let ba = ba_from_current_acc tx in
177 [ Bal_assert_item ba; Tx_item tx' ]
diff --git a/lib/iban.ml b/lib/iban.ml
index 6e47e9d..1db6c7b 100644
--- a/lib/iban.ml
+++ b/lib/iban.ml
@@ -84,4 +84,5 @@ let of_string s =
84 | Some iban -> iban 84 | Some iban -> iban
85 | None -> Printf.failwithf "Iban.of_string: %S" s () 85 | None -> Printf.failwithf "Iban.of_string: %S" s ()
86 86
87let sexp_of_t iban = Sexp.Atom iban
87let equal = String.equal 88let equal = String.equal
diff --git a/lib/iban.mli b/lib/iban.mli
index 944928c..c2cad9f 100644
--- a/lib/iban.mli
+++ b/lib/iban.mli
@@ -3,6 +3,7 @@ open Core
3type t 3type t
4 4
5val make : string -> t option 5val make : string -> t option
6val sexp_of_t : t -> Sexp.t
6 7
7include Stringable.S with type t := t 8include Stringable.S with type t := t
8include Equal.S with type t := t 9include Equal.S with type t := t
diff --git a/lib/ingcsv.ml b/lib/ingcsv.ml
index f3536bf..53258fe 100644
--- a/lib/ingcsv.ml
+++ b/lib/ingcsv.ml
@@ -3,14 +3,7 @@ open Result.Let_syntax
3module Time_ns = Time_ns_unix 3module Time_ns = Time_ns_unix
4 4
5module Debit_credit = struct 5module Debit_credit = struct
6 type t = Debit | Credit 6 type t = Debit | Credit [@@deriving string, sexp_of]
7
8 let of_string = function
9 | "Debit" -> Debit
10 | "Credit" -> Credit
11 | s -> Printf.failwithf "Debit_credit.of_string: %S" s ()
12
13 let to_string = function Debit -> "Debit" | Credit -> "Credit"
14end 7end
15 8
16module Cents = struct 9module Cents = struct
@@ -38,7 +31,7 @@ module Transaction_type = struct
38 | Phone_banking (* GF (telefonisch bankieren, Girofoon) *) 31 | Phone_banking (* GF (telefonisch bankieren, Girofoon) *)
39 | Transfer (* OV (overboeking); 'Transfer' *) 32 | Transfer (* OV (overboeking); 'Transfer' *)
40 | Various (* DV (diversen) *) 33 | Various (* DV (diversen) *)
41 [@@deriving equal, string] 34 [@@deriving equal, string, sexp_of]
42 35
43 let of_code = function 36 let of_code = function
44 | "AC" -> Accept_giro 37 | "AC" -> Accept_giro
@@ -209,6 +202,7 @@ type parse_err =
209 | Inconsistent_counterparty_name 202 | Inconsistent_counterparty_name
210 | Inconsistent_counterparty_iban 203 | Inconsistent_counterparty_iban
211 | Inconsistent_transaction_code 204 | Inconsistent_transaction_code
205[@@deriving string, sexp_of]
212 206
213let assert_value_date (ptx : Primitive_tx.t) d = 207let assert_value_date (ptx : Primitive_tx.t) d =
214 if Date.(d = ptx.date) then Ok () else Error Inconsistent_value_date 208 if Date.(d = ptx.date) then Ok () else Error Inconsistent_value_date
@@ -348,7 +342,7 @@ let credit_transfer_rex =
348 date: ([0-9]{2}/[0-9]{2}/[0-9]{4})$" 342 date: ([0-9]{2}/[0-9]{2}/[0-9]{4})$"
349 343
350let parse_credit_transfer_notifs notifs = 344let parse_credit_transfer_notifs notifs =
351 match Re.Pcre.extract ~rex:normal_direct_debit_rex notifs with 345 match Re.Pcre.extract ~rex:credit_transfer_rex notifs with
352 | [| _; name; desc; iban_str; ref_; val_date_str |] -> 346 | [| _; name; desc; iban_str; ref_; val_date_str |] ->
353 let%map iban = parse_iban iban_str 347 let%map iban = parse_iban iban_str
354 and val_date = parse_val_date val_date_str in 348 and val_date = parse_val_date val_date_str in
@@ -554,12 +548,18 @@ let tx_base_from_prim (ptx : Primitive_tx.t) : tx_base =
554 tag = ptx.tag; 548 tag = ptx.tag;
555 } 549 }
556 550
557let tx_from_prim ptx ~ams_tz : (tx, parse_err) result = 551type parse_err_ext = Transaction_type.t * Debit_credit.t * parse_err
552[@@deriving sexp_of]
553
554let tx_from_prim ptx ~ams_tz : (tx, parse_err_ext) result =
558 let base = tx_base_from_prim ptx in 555 let base = tx_base_from_prim ptx in
559 let%map specifics = tx_specifics_from_prim ams_tz ptx in 556 let%map specifics =
557 Result.map_error (tx_specifics_from_prim ams_tz ptx) ~f:(fun e ->
558 (ptx.type_, ptx.debit_credit, e))
559 in
560 Tx (base, specifics) 560 Tx (base, specifics)
561 561
562type csv_err = Parse_err of parse_err | Exn of exn 562type csv_err = Parse_err of parse_err_ext | Exn of exn
563 563
564module List = struct 564module List = struct
565 include List 565 include List
@@ -580,8 +580,8 @@ let read_channel (c : In_channel.t) ~ams_tz : (tx list, csv_err) result =
580 let%bind ptxs = 580 let%bind ptxs =
581 try 581 try
582 Ok 582 Ok
583 (Delimited.Read.read_lines ~header:(`Require Primitive_tx.headers) 583 (Delimited.Read.read_lines ~sep:';'
584 Primitive_tx.parse c) 584 ~header:(`Require Primitive_tx.headers) Primitive_tx.parse c)
585 with e -> Error (Exn e) 585 with e -> Error (Exn e)
586 in 586 in
587 List.map_result ptxs ~f:(tx_from_prim ~ams_tz) 587 List.map_result ptxs ~f:(tx_from_prim ~ams_tz)
diff --git a/lib/ledger.ml b/lib/ledger.ml
index 3b52bcc..ba21e49 100644
--- a/lib/ledger.ml
+++ b/lib/ledger.ml
@@ -14,10 +14,11 @@ type tx_type =
14 | Direct_debit_tx 14 | Direct_debit_tx
15 | Periodic_tx 15 | Periodic_tx
16 16
17type iban_tag = Account_tag | Counterparty_iban_tag [@@deriving compare] 17type iban_tag = Account_tag | Counterparty_iban_tag
18[@@deriving compare, sexp_of]
18 19
19type unit_tag = Filed_tag | Google_pay_tag | Auto_round_savings_tag 20type unit_tag = Filed_tag | Google_pay_tag | Auto_round_savings_tag
20[@@deriving compare] 21[@@deriving compare, sexp_of]
21 22
22type string_tag = 23type string_tag =
23 | Desc_tag 24 | Desc_tag
@@ -31,7 +32,7 @@ type string_tag =
31 | Terminal_tag 32 | Terminal_tag
32 | Card_seq_no_tag 33 | Card_seq_no_tag
33 | Savings_account_tag 34 | Savings_account_tag
34[@@deriving compare] 35[@@deriving compare, sexp_of]
35 36
36module Label = struct 37module Label = struct
37 type 'a t = 38 type 'a t =
@@ -58,7 +59,39 @@ module Label = struct
58 | Unit_label _, _ -> Gt 59 | Unit_label _, _ -> Gt
59end 60end
60 61
61module Labels = Dmap.Make (Label) 62module Labels = struct
63 include Dmap.Make (Label)
64
65 let sexp_of_t m =
66 Sexp.List
67 (bindings m
68 |> List.map ~f:(function
69 | Binding (Iban_label tag, iban) ->
70 Sexp.List
71 [
72 Sexp.Atom "Iban_label";
73 [%sexp_of: iban_tag] tag;
74 [%sexp_of: Iban.t] iban;
75 ]
76 | Binding (String_label tag, s) ->
77 Sexp.List
78 [
79 Sexp.Atom "String_label";
80 [%sexp_of: string_tag] tag;
81 Sexp.Atom s;
82 ]
83 | Binding (Timestamp_label, ts) ->
84 Sexp.List
85 [ Sexp.Atom "Timestamp_label"; [%sexp_of: Time_ns_unix.t] ts ]
86 | Binding (Unit_label tag, ()) ->
87 Sexp.List [ Sexp.Atom "Unit_label"; [%sexp_of: unit_tag] tag ]))
88end
89
90module Z = struct
91 include Z
92
93 let sexp_of_t x = Sexp.Atom (Z.to_string x)
94end
62 95
63module Money : sig 96module Money : sig
64 type t 97 type t
@@ -69,8 +102,9 @@ module Money : sig
69 val to_z : t -> Z.t 102 val to_z : t -> Z.t
70 val ( + ) : t -> t -> t 103 val ( + ) : t -> t -> t
71 val ( - ) : t -> t -> t 104 val ( - ) : t -> t -> t
105 val sexp_of_t : t -> Sexp.t
72end = struct 106end = struct
73 type t = Z.t 107 type t = Z.t [@@deriving sexp_of]
74 108
75 let equal = Z.equal 109 let equal = Z.equal
76 let compare = Z.compare 110 let compare = Z.compare
@@ -80,8 +114,11 @@ end = struct
80 let ( - ) x y = Z.(x - y) 114 let ( - ) x y = Z.(x - y)
81end 115end
82 116
83type scalar = Amount of Money.t | Rate of Z.t [@@deriving equal, compare] 117type scalar = Amount of Money.t | Rate of Z.t
84type commodity_id = string (* TODO: consider making this UUID *) 118[@@deriving equal, compare, sexp_of]
119
120type commodity_id = string
121(* TODO: consider making this UUID *) [@@deriving sexp]
85 122
86module Account_id = struct 123module Account_id = struct
87 type t = string list [@@deriving sexp, compare] 124 type t = string list [@@deriving sexp, compare]
@@ -93,12 +130,14 @@ type account = {
93 commodity_id : commodity_id; 130 commodity_id : commodity_id;
94 balance : Money.t; 131 balance : Money.t;
95} 132}
133[@@deriving sexp_of]
96 134
97type bal_assert = { 135type bal_assert = {
98 account : Account_id.t; 136 account : Account_id.t;
99 amount : Money.t; 137 amount : Money.t;
100 labels : Labels.t; 138 labels : Labels.t;
101} 139}
140[@@deriving sexp_of]
102 141
103module Account_id_map = Map.Make (Account_id) 142module Account_id_map = Map.Make (Account_id)
104 143
@@ -119,6 +158,7 @@ module Tx : sig
119 val debit : t -> scalar Account_id_map.t 158 val debit : t -> scalar Account_id_map.t
120 val credit : t -> scalar Account_id_map.t 159 val credit : t -> scalar Account_id_map.t
121 val labels : t -> Labels.t 160 val labels : t -> Labels.t
161 val sexp_of_t : t -> Sexp.t
122end = struct 162end = struct
123 (* We hide this because we only want to allow constructing balanced transactions *) 163 (* We hide this because we only want to allow constructing balanced transactions *)
124 type t = { 164 type t = {
@@ -128,7 +168,7 @@ end = struct
128 credit : scalar Account_id_map.t; 168 credit : scalar Account_id_map.t;
129 labels : Labels.t; 169 labels : Labels.t;
130 } 170 }
131 [@@deriving fields] 171 [@@deriving fields, sexp_of]
132 172
133 type error = Unbalanced 173 type error = Unbalanced
134 174
@@ -141,4 +181,6 @@ end = struct
141end 181end
142 182
143type item = Tx_item of Tx.t | Bal_assert_item of bal_assert 183type item = Tx_item of Tx.t | Bal_assert_item of bal_assert
144type ledger = Ledger of item list 184[@@deriving sexp_of]
185
186type t = item list [@@deriving sexp_of]