diff options
author | Rutger Broekhoff | 2025-08-26 00:35:27 +0200 |
---|---|---|
committer | Rutger Broekhoff | 2025-08-26 00:35:27 +0200 |
commit | e6873458facadea0dfb228bb33291d6baf68c427 (patch) | |
tree | 9ca19e2bbb12d92447f654a92280a6048383ebba | |
parent | b8fbaa53b912347b3b50cac3e913a142db460b0a (diff) | |
download | rdcapsis-e6873458facadea0dfb228bb33291d6baf68c427.tar.gz rdcapsis-e6873458facadea0dfb228bb33291d6baf68c427.zip |
Basic import seems to be working
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | bin/dune | 2 | ||||
-rw-r--r-- | bin/main.ml | 40 | ||||
-rw-r--r-- | lib/convert.ml | 13 | ||||
-rw-r--r-- | lib/iban.ml | 1 | ||||
-rw-r--r-- | lib/iban.mli | 1 | ||||
-rw-r--r-- | lib/ingcsv.ml | 30 | ||||
-rw-r--r-- | lib/ledger.ml | 60 |
8 files changed, 123 insertions, 25 deletions
@@ -3,3 +3,4 @@ | |||
3 | *~ | 3 | *~ |
4 | _build/ | 4 | _build/ |
5 | _opam/ | 5 | _opam/ |
6 | *.csv | ||
@@ -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 @@ | |||
1 | let () = print_endline "Hello, World!" | 1 | open Core |
2 | module Time_ns = Time_ns_unix | ||
3 | |||
4 | module 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 | ||
17 | end | ||
18 | |||
19 | module Result = struct | ||
20 | include Result | ||
21 | |||
22 | let unwrap = function | ||
23 | | Error _ -> failwith "Result.unwrap: unexpected (Error _)" | ||
24 | | Ok v -> v | ||
25 | end | ||
26 | |||
27 | let () = | ||
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 @@ | |||
1 | open Core | 1 | open Core |
2 | open Ledger | 2 | open Ledger |
3 | open Result.Let_syntax | ||
3 | 4 | ||
4 | let virt_checking_acc = [ "Unfiled"; "Checking" ] | 5 | let virt_checking_acc = [ "Unfiled"; "Checking" ] |
5 | let virt_savings_acc = [ "Unfiled"; "Savings" ] | 6 | let 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 | |||
167 | let 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 | |||
174 | let 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 | ||
87 | let sexp_of_t iban = Sexp.Atom iban | ||
87 | let equal = String.equal | 88 | let 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 | |||
3 | type t | 3 | type t |
4 | 4 | ||
5 | val make : string -> t option | 5 | val make : string -> t option |
6 | val sexp_of_t : t -> Sexp.t | ||
6 | 7 | ||
7 | include Stringable.S with type t := t | 8 | include Stringable.S with type t := t |
8 | include Equal.S with type t := t | 9 | include 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 | |||
3 | module Time_ns = Time_ns_unix | 3 | module Time_ns = Time_ns_unix |
4 | 4 | ||
5 | module Debit_credit = struct | 5 | module 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" | ||
14 | end | 7 | end |
15 | 8 | ||
16 | module Cents = struct | 9 | module 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 | ||
213 | let assert_value_date (ptx : Primitive_tx.t) d = | 207 | let 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 | ||
350 | let parse_credit_transfer_notifs notifs = | 344 | let 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 | ||
557 | let tx_from_prim ptx ~ams_tz : (tx, parse_err) result = | 551 | type parse_err_ext = Transaction_type.t * Debit_credit.t * parse_err |
552 | [@@deriving sexp_of] | ||
553 | |||
554 | let 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 | ||
562 | type csv_err = Parse_err of parse_err | Exn of exn | 562 | type csv_err = Parse_err of parse_err_ext | Exn of exn |
563 | 563 | ||
564 | module List = struct | 564 | module 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 | ||
17 | type iban_tag = Account_tag | Counterparty_iban_tag [@@deriving compare] | 17 | type iban_tag = Account_tag | Counterparty_iban_tag |
18 | [@@deriving compare, sexp_of] | ||
18 | 19 | ||
19 | type unit_tag = Filed_tag | Google_pay_tag | Auto_round_savings_tag | 20 | type unit_tag = Filed_tag | Google_pay_tag | Auto_round_savings_tag |
20 | [@@deriving compare] | 21 | [@@deriving compare, sexp_of] |
21 | 22 | ||
22 | type string_tag = | 23 | type 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 | ||
36 | module Label = struct | 37 | module 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 |
59 | end | 60 | end |
60 | 61 | ||
61 | module Labels = Dmap.Make (Label) | 62 | module 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 ])) | ||
88 | end | ||
89 | |||
90 | module Z = struct | ||
91 | include Z | ||
92 | |||
93 | let sexp_of_t x = Sexp.Atom (Z.to_string x) | ||
94 | end | ||
62 | 95 | ||
63 | module Money : sig | 96 | module 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 | ||
72 | end = struct | 106 | end = 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) |
81 | end | 115 | end |
82 | 116 | ||
83 | type scalar = Amount of Money.t | Rate of Z.t [@@deriving equal, compare] | 117 | type scalar = Amount of Money.t | Rate of Z.t |
84 | type commodity_id = string (* TODO: consider making this UUID *) | 118 | [@@deriving equal, compare, sexp_of] |
119 | |||
120 | type commodity_id = string | ||
121 | (* TODO: consider making this UUID *) [@@deriving sexp] | ||
85 | 122 | ||
86 | module Account_id = struct | 123 | module 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 | ||
97 | type bal_assert = { | 135 | type 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 | ||
103 | module Account_id_map = Map.Make (Account_id) | 142 | module 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 | ||
122 | end = struct | 162 | end = 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 | |||
141 | end | 181 | end |
142 | 182 | ||
143 | type item = Tx_item of Tx.t | Bal_assert_item of bal_assert | 183 | type item = Tx_item of Tx.t | Bal_assert_item of bal_assert |
144 | type ledger = Ledger of item list | 184 | [@@deriving sexp_of] |
185 | |||
186 | type t = item list [@@deriving sexp_of] | ||