1+ import * as fs from 'fs' ;
2+ import csv from 'csv-parser' ;
3+ import moment from 'moment' ;
4+ import { parse } from 'path' ;
5+ import { createObjectCsvWriter as createCsvWriter } from 'csv-writer' ;
6+
7+ interface RawTransaction {
8+ date : string ;
9+ description : string ;
10+ amount : number ;
11+ balance : number ;
12+ }
13+
14+ interface ProcessedTransaction {
15+ date : string ;
16+ description : string ;
17+ amount : string ;
18+ balance : string ;
19+ }
20+
21+ let rawTransactions : RawTransaction [ ] = [ ] ;
22+ let balance = 0 ;
23+
24+ // Get the filename from the command line arguments
25+ const filename = process . argv [ 2 ] ;
26+ if ( ! filename ) {
27+ console . error ( 'Please provide a filename as a command line argument.' ) ;
28+ process . exit ( 1 ) ;
29+ }
30+
31+ fs . createReadStream ( filename )
32+ . pipe ( csv ( ) )
33+ . on ( 'data' , ( row ) => {
34+ let amount = parseFloat ( row [ 'Value' ] . replace ( '€' , '' ) ) ;
35+ balance += amount ;
36+
37+ rawTransactions . push ( {
38+ date : moment ( row [ 'Date (UTC)' ] , 'MMM D, YYYY' ) . format ( 'DD/MM/YYYY' ) ,
39+ description : row [ 'Description' ] ,
40+ amount : amount ,
41+ balance : balance
42+ } ) ;
43+ } )
44+ . on ( 'end' , ( ) => {
45+ let combinedTransactions : ProcessedTransaction [ ] = [ ] ;
46+
47+ let combinedAmount = 0 ;
48+ let previousTransaction : RawTransaction | undefined ;
49+
50+ for ( let transaction of rawTransactions ) {
51+ console . log ( 'Processing transaction:' , transaction ) ;
52+ let { description, amount, balance, date } = transaction ;
53+
54+ // Don't record interest/fee transactions until the last one:
55+ if ( description . startsWith ( 'Interest PAID' ) || description . startsWith ( 'Service Fee Charged' ) ) {
56+ console . log ( 'Keeping transaction pending...' ) ;
57+ combinedAmount += amount ;
58+ previousTransaction = transaction ;
59+ continue ;
60+ }
61+
62+ if ( combinedAmount ) {
63+ // We've reached the last interest/fee transaction, so record the combined transaction:
64+ combinedTransactions . push ( {
65+ date : previousTransaction ! . date ,
66+ description : 'Interest after fees' ,
67+ amount : combinedAmount . toFixed ( 2 ) ,
68+ balance : previousTransaction ! . balance . toFixed ( 2 )
69+ } ) ;
70+
71+ combinedAmount = 0 ;
72+ }
73+
74+ // Record the current (non-interest/fee) transaction:
75+ combinedTransactions . push ( {
76+ date : date ,
77+ description : description ,
78+ amount : amount . toFixed ( 2 ) ,
79+ balance : balance . toFixed ( 2 )
80+ } ) ;
81+ }
82+
83+ if ( combinedAmount ) {
84+ // We've reached the last interest/fee transaction, so record the combined transaction:
85+ combinedTransactions . push ( {
86+ date : previousTransaction ! . date ,
87+ description : 'Interest after fees' ,
88+ amount : combinedAmount . toFixed ( 2 ) ,
89+ balance : previousTransaction ! . balance . toFixed ( 2 )
90+ } ) ;
91+ }
92+
93+ // Sort from most recent to oldest
94+ combinedTransactions
95+ . reverse ( ) // Ensure same-date order is flipped
96+ . sort ( ( a , b ) => moment ( b . date , 'DD/MM/YYYY' ) . valueOf ( ) - moment ( a . date , 'DD/MM/YYYY' ) . valueOf ( ) ) ;
97+
98+ // Write the output to a file
99+ const { dir, name } = parse ( filename ) ;
100+ const outputFilename = `${ dir } /${ name } -processed.csv` ;
101+ const csvWriter = createCsvWriter ( {
102+ path : outputFilename ,
103+ header : [
104+ { id : 'date' , title : 'DATE' } ,
105+ { id : 'description' , title : 'DESCRIPTION' } ,
106+ { id : 'amount' , title : 'AMOUNT' } ,
107+ { id : 'balance' , title : 'BALANCE' } ,
108+ ]
109+ } ) ;
110+ csvWriter . writeRecords ( combinedTransactions ) ;
111+ } ) ;
0 commit comments