From b8fbaa53b912347b3b50cac3e913a142db460b0a Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Mon, 25 Aug 2025 23:39:51 +0200 Subject: Conversion --- lib/ingcsv.ml | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 8 deletions(-) (limited to 'lib/ingcsv.ml') diff --git a/lib/ingcsv.ml b/lib/ingcsv.ml index 203a353..f3536bf 100644 --- a/lib/ingcsv.ml +++ b/lib/ingcsv.ml @@ -68,6 +68,8 @@ module Transaction_type = struct end module Primitive_tx = struct + exception Inconsistent_transaction_code + type t = { date : Date.t; description : string; @@ -85,6 +87,21 @@ module Primitive_tx = struct let opt_field (f : string -> 'a) (v : string) : 'a option = if String.is_empty (String.strip v) then None else Some (f v) + let headers = + [ + "Date"; + "Name / Description"; + "Account"; + "Counterparty"; + "Code"; + "Debit/credit"; + "Amount (EUR)"; + "Transaction type"; + "Notifications"; + "Resulting balance"; + "Tag"; + ] + let parse : t Delimited.Read.t = let open Delimited.Read.Let_syntax in let%map_open date = at_header "Date" ~f:Date.of_string @@ -99,12 +116,7 @@ module Primitive_tx = struct and resulting_balance = at_header "Resulting balance" ~f:Cents.of_string and tag = at_header "Tag" ~f:Fn.id in if not ([%equal: Transaction_type.t] code type_) then - Printf.failwithf - "Primitive_tx.parse: parsed transaction code (%S) and type (%S) do not \ - match" - (Transaction_type.to_string code) - (Transaction_type.to_string type_) - (); + raise Inconsistent_transaction_code; { date; description; @@ -123,7 +135,7 @@ type tx_base = { date : Date.t; account : Iban.t; amount : Cents.t; - res_bal : Cents.t; + resulting_balance : Cents.t; tag : string; } @@ -196,6 +208,7 @@ type parse_err = | Inconsistent_value_date | Inconsistent_counterparty_name | Inconsistent_counterparty_iban + | Inconsistent_transaction_code let assert_value_date (ptx : Primitive_tx.t) d = if Date.(d = ptx.date) then Ok () else Error Inconsistent_value_date @@ -383,7 +396,7 @@ let parse_batch_payment_credit_notifs notifs = (name, desc, iban, ref_, val_date) | _ | (exception _) -> Error No_notifs_match -let specifics_from_prim_exn (ams_tz : Time_ns.Zone.t) : +let tx_specifics_from_prim (ams_tz : Time_ns.Zone.t) : Primitive_tx.t -> (tx_specifics, parse_err) result = function | { type_ = Payment_terminal; debit_credit = Debit; _ } as ptx -> let%bind @@ -531,3 +544,45 @@ let specifics_from_prim_exn (ams_tz : Time_ns.Zone.t) : reference = ref_; } | _ -> Error Unknown_type_combination + +let tx_base_from_prim (ptx : Primitive_tx.t) : tx_base = + { + date = ptx.date; + account = ptx.account; + amount = ptx.amount; + resulting_balance = ptx.resulting_balance; + tag = ptx.tag; + } + +let tx_from_prim ptx ~ams_tz : (tx, parse_err) result = + let base = tx_base_from_prim ptx in + let%map specifics = tx_specifics_from_prim ams_tz ptx in + Tx (base, specifics) + +type csv_err = Parse_err of parse_err | Exn of exn + +module List = struct + include List + + let map_result ~(f : 'a -> ('b, 'c) result) : 'a list -> ('b list, 'c) result + = + let open Result.Let_syntax in + let rec go = function + | [] -> return [] + | x :: xs -> + let%map x' = f x and xs' = go xs in + x' :: xs' + in + go +end + +let read_channel (c : In_channel.t) ~ams_tz : (tx list, csv_err) result = + let%bind ptxs = + try + Ok + (Delimited.Read.read_lines ~header:(`Require Primitive_tx.headers) + Primitive_tx.parse c) + with e -> Error (Exn e) + in + List.map_result ptxs ~f:(tx_from_prim ~ams_tz) + |> Result.map_error ~f:(fun e -> Parse_err e) -- cgit v1.2.3