From 4933b35040c923d5b5eed03e53f83bf48e3ef566 Mon Sep 17 00:00:00 2001 From: Kevin Bataille Date: Mon, 9 Feb 2026 10:45:33 +0100 Subject: [PATCH] Enhance aggregation script to support annual reports --- docs/README.md | 6 + output/reports/annual_report_2025.csv | 6 + output/reports/annual_transactions_2025.csv | 63 ++++++++ output/reports/monthly_summary.csv | 28 +--- output/reports/yearly_summary.csv | 5 +- scripts/aggregate_by_month.py | 156 ++++++++++++++------ 6 files changed, 190 insertions(+), 74 deletions(-) create mode 100644 output/reports/annual_report_2025.csv create mode 100644 output/reports/annual_transactions_2025.csv diff --git a/docs/README.md b/docs/README.md index 4a41693..b821a40 100644 --- a/docs/README.md +++ b/docs/README.md @@ -111,6 +111,12 @@ python aggregate_by_month.py # Specify input and output directories python aggregate_by_month.py --input-dir /path/to/csv --output-dir /path/to/reports + +# Create annual reports +python aggregate_by_month.py --annual + +# Create annual report for a specific year +python aggregate_by_month.py --annual --year 2025 ``` ## Output diff --git a/output/reports/annual_report_2025.csv b/output/reports/annual_report_2025.csv new file mode 100644 index 0000000..facfb4c --- /dev/null +++ b/output/reports/annual_report_2025.csv @@ -0,0 +1,6 @@ +Category,Transaction Count,Total Amount,Percentage +Other,35,8531.95,79.42% +Internal Transfer,12,1650.0,15.36% +Loan Repayment,3,450.0,4.19% +Google Services,1,77.64,0.72% +Bank Fees,11,33.0,0.31% diff --git a/output/reports/annual_transactions_2025.csv b/output/reports/annual_transactions_2025.csv new file mode 100644 index 0000000..1cf725e --- /dev/null +++ b/output/reports/annual_transactions_2025.csv @@ -0,0 +1,63 @@ +Date,Description,Category,Amount,Institution,Source +02/01/2025,AGIOS/FRAIS,Other,18.48,Monabanq,Extrait de comptes au 2025-01-31.pdf +08/01/2025,F COTIS PRATIQ+,Bank Fees,3.0,Monabanq,Extrait de comptes au 2025-01-31.pdf +04/02/2025,PRLV SEPA URSSAF D'ILE DE FRANC,Other,35.0,Monabanq,Extrait de comptes au 2025-02-28.pdf +07/02/2025,F COTIS PRATIQ+,Bank Fees,3.0,Monabanq,Extrait de comptes au 2025-02-28.pdf +27/02/2025,VIR INST MR BATAILLE KEVIN,Other,700.0,Monabanq,Extrait de comptes au 2025-02-28.pdf +06/03/2025,F COTIS PRATIQ+,Bank Fees,3.0,Monabanq,Extrait de comptes au 2025-03-31.pdf +06/03/2025,VIR SEPA BOURSORAMA PERSO,Other,100.0,Monabanq,Extrait de comptes au 2025-03-31.pdf +06/03/2025,VIR SEPA INTERNE,Other,200.0,Monabanq,Extrait de comptes au 2025-03-31.pdf +12/03/2025,VIR INST BATAILLE KEVIN (REVOLU,Other,100.0,Monabanq,Extrait de comptes au 2025-03-31.pdf +12/03/2025,VIR INST BOURSORAMA PERSO,Other,230.0,Monabanq,Extrait de comptes au 2025-03-31.pdf +24/03/2025,GOOGLE IRELAND LTD,Google Services,77.64,Monabanq,Extrait de comptes au 2025-03-31.pdf +04/04/2025,F COTIS PRATIQ+,Bank Fees,3.0,Monabanq,Extrait de comptes au 2025-04-30.pdf +07/04/2025,VIR INST MR BATAILLE KEVIN,Other,200.0,Monabanq,Extrait de comptes au 2025-04-30.pdf +11/04/2025,VIR INST BOURSORAMA PERSO,Other,333.34,Monabanq,Extrait de comptes au 2025-04-30.pdf +29/04/2025,VIR AMAZON EUROPE CORE S.A R,Other,25.77,Monabanq,Extrait de comptes au 2025-04-30.pdf +05/05/2025,PRLV SEPA URSSAF D ILE DE FRANC,Other,18.0,Monabanq,Extrait de comptes au 2025-06-02.pdf +05/05/2025,VIR MR BATAILLE KEVIN,Internal Transfer,0.0,Monabanq,Extrait de comptes au 2025-06-02.pdf +08/05/2025,F COTIS PRATIQ+,Bank Fees,3.0,Monabanq,Extrait de comptes au 2025-06-02.pdf +04/06/2025,VIR MR BATAILLE KEVIN,Internal Transfer,150.0,Monabanq,Extrait de comptes au 2025-06-30.pdf +05/06/2025,F COTIS PRATIQ+,Bank Fees,3.0,Monabanq,Extrait de comptes au 2025-06-30.pdf +03/07/2025,VIR MR BATAILLE KEVIN,Internal Transfer,150.0,Monabanq,Extrait de comptes au 2025-07-31.pdf +07/07/2025,F COTIS PRATIQ+,Bank Fees,3.0,Monabanq,Extrait de comptes au 2025-07-31.pdf +09/07/2025,VIR MR BATAILLE KEVIN,Internal Transfer,150.0,Monabanq,Extrait de comptes au 2025-07-31.pdf +11/07/2025,VIR INST BOURSORAMA PERSO,Other,761.24,Monabanq,Extrait de comptes au 2025-07-31.pdf +11/07/2025,VIR INST KEVIN BATAILLE,Other,700.09,Monabanq,Extrait de comptes au 2025-07-31.pdf +29/07/2025,VIR INST KEVIN BATAILLE,Other,107.78,Monabanq,Extrait de comptes au 2025-07-31.pdf +29/08/2025,VIR INST BATAILLE KEVIN (REVOLU,Other,120.0,Monabanq,Extrait de comptes au 2025-09-01.pdf +29/08/2025,VIR SEPA BOURSORAMA PERSO,Other,280.0,Monabanq,Extrait de comptes au 2025-09-01.pdf +03/09/2025,VIR MR BATAILLE KEVIN,Internal Transfer,150.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +04/09/2025,F COTIS PRATIQ+,Bank Fees,3.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +07/09/2025,VIR INST BATAILLE KEVIN,Other,250.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +07/09/2025,VIR INST BOURSORAMA PERSO,Other,800.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +09/09/2025,VIR INST BATAILLE KEVIN,Other,200.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +10/09/2025,VIR INST KEVIN BATAILLE,Other,200.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +11/09/2025,VIR INST BOURSORAMA PERSO,Other,247.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +11/09/2025,VIR INST KEVIN BATAILLE,Other,200.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +11/09/2025,VIR MR BATAILLE KEVIN,Internal Transfer,150.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +14/09/2025,VIR INST BATAILLE KEVIN (REVOLU,Other,130.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +14/09/2025,VIR INST KEVIN BATAILLE,Other,270.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +21/09/2025,VIR INST BATAILLE KEVIN (REVOLU,Other,100.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +21/09/2025,VIR INST BATAILLE KEVIN (REVOLU,Other,100.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +23/09/2025,VIR INST BOURSORAMA PERSO,Other,200.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +26/09/2025,VIR INST BOURSORAMA PERSO,Other,350.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +30/09/2025,VIR INST BOURSORAMA PERSO,Other,300.0,Monabanq,Extrait de comptes au 2025-09-30.pdf +03/10/2025,VIR MR BATAILLE KEVIN,Internal Transfer,150.0,Monabanq,Extrait de comptes au 2025-10-31.pdf +04/10/2025,ECH PRET 28943002099054,Loan Repayment,150.0,Monabanq,Extrait de comptes au 2025-10-31.pdf +06/10/2025,F COTIS PRATIQ+,Bank Fees,3.0,Monabanq,Extrait de comptes au 2025-10-31.pdf +08/10/2025,VIR MR BATAILLE KEVIN,Internal Transfer,150.0,Monabanq,Extrait de comptes au 2025-10-31.pdf +12/10/2025,VIR INST BOURSORAMA PERSO,Other,700.0,Monabanq,Extrait de comptes au 2025-10-31.pdf +23/10/2025,VIR INST BOURSORAMA PERSO,Other,100.0,Monabanq,Extrait de comptes au 2025-10-31.pdf +23/10/2025,VIR INST KEVIN BATAILLE,Other,100.0,Monabanq,Extrait de comptes au 2025-10-31.pdf +29/10/2025,VIR INST BOURSORAMA PERSO,Other,90.0,Monabanq,Extrait de comptes au 2025-10-31.pdf +30/10/2025,VIR INST BOURSORAMA PERSO,Other,40.0,Monabanq,Extrait de comptes au 2025-10-31.pdf +04/11/2025,VIR MR BATAILLE KEVIN,Internal Transfer,150.0,Monabanq,Extrait de comptes au 2025-12-01.pdf +06/11/2025,ECH PRET 28943002099054,Loan Repayment,150.0,Monabanq,Extrait de comptes au 2025-12-01.pdf +07/11/2025,F COTIS PRATIQ+,Bank Fees,3.0,Monabanq,Extrait de comptes au 2025-12-01.pdf +10/11/2025,VIR MR BATAILLE KEVIN,Internal Transfer,150.0,Monabanq,Extrait de comptes au 2025-12-01.pdf +03/12/2025,VIR MR BATAILLE KEVIN,Internal Transfer,150.0,Monabanq,Extrait de comptes au 2025-12-31.pdf +04/12/2025,F COTIS PRATIQ+,Bank Fees,3.0,Monabanq,Extrait de comptes au 2025-12-31.pdf +06/12/2025,ECH PRET 28943002099054,Loan Repayment,150.0,Monabanq,Extrait de comptes au 2025-12-31.pdf +09/12/2025,VIR MR BATAILLE KEVIN,Internal Transfer,150.0,Monabanq,Extrait de comptes au 2025-12-31.pdf +25/12/2025,VIR INST MR BATAILLE KEVIN,Other,225.25,Monabanq,Extrait de comptes au 2025-12-31.pdf diff --git a/output/reports/monthly_summary.csv b/output/reports/monthly_summary.csv index 6f09271..bd69c4d 100644 --- a/output/reports/monthly_summary.csv +++ b/output/reports/monthly_summary.csv @@ -1,28 +1,2 @@ Year,Month,Total Income,Total Expenses,Net Balance,Transaction Count,Institutions -25,January,0,0,0,1,SNCF -25,February,0,0,0,1,SNCF -25,March,0,0,0,1,SNCF -25,April,0,0,0,1,SNCF -25,May,0,0,0,1,SNCF -25,June,0,0,0,1,SNCF -25,July,0,0,0,1,SNCF -25,August,0,0,0,1,SNCF -25,September,0,0,0,1,SNCF -25,October,0,0,0,1,SNCF -25,November,0,0,0,1,SNCF -25,December,0,0,0,1,SNCF -26,January,0,0,0,1,SNCF -2025,January,0,21.48,21.48,2,Monabanq -2025,February,0,738.0,738.0,3,Monabanq -2025,March,0,710.64,710.64,6,Monabanq -2025,April,0,562.11,562.11,4,Monabanq -2025,May,0,21.0,21.0,3,Monabanq -2025,June,0,153.0,153.0,2,Monabanq -2025,July,0,1872.1100000000001,1872.1100000000001,6,Monabanq -2025,August,0,400.0,400.0,2,Monabanq -2025,September,0,3650.0,3650.0,16,Monabanq -2025,October,0,1483.0,1483.0,9,Monabanq -2025,November,0,453.0,453.0,4,Monabanq -2025,December,0,678.25,678.25,5,Monabanq -2026,January,0,523.9300000000001,523.9300000000001,5,Monabanq -2026,February,0,19534.26,19534.26,319,"American Express, SNCF" +2026,February,0,19534.26,19534.26,318,American Express diff --git a/output/reports/yearly_summary.csv b/output/reports/yearly_summary.csv index 2a20a88..cbbdef7 100644 --- a/output/reports/yearly_summary.csv +++ b/output/reports/yearly_summary.csv @@ -1,5 +1,2 @@ Year,Total Income,Total Expenses,Net Balance,Transaction Count -25,0,0.0,0.0,12 -26,0,0.0,0.0,1 -2025,0,10742.59,10742.59,62 -2026,0,20058.190000000013,20058.190000000013,324 +2026,0,19534.260000000013,19534.260000000013,318 diff --git a/scripts/aggregate_by_month.py b/scripts/aggregate_by_month.py index 279f714..91e8e66 100755 --- a/scripts/aggregate_by_month.py +++ b/scripts/aggregate_by_month.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Script to aggregate all account statements by month +Script to aggregate all account statements by month or year """ import os @@ -106,21 +106,28 @@ def process_csv_file(file_path): return transactions def main(): - parser = argparse.ArgumentParser(description='Aggregate all account statements by month') + parser = argparse.ArgumentParser(description='Aggregate all account statements by month or year') parser.add_argument('--input-dir', default='output/csv', help='Directory containing CSV files to aggregate (default: output/csv)') parser.add_argument('--output-dir', default='output/reports', help='Directory to save aggregated reports (default: output/reports)') + parser.add_argument('--annual', action='store_true', + help='Create annual reports instead of monthly reports') + parser.add_argument('--year', type=int, + help='Generate reports for a specific year only') args = parser.parse_args() # Create output directory os.makedirs(args.output_dir, exist_ok=True) + report_type = "Annual" if args.annual else "Monthly" print(f"\n{'='*60}") - print(f"Monthly Aggregation of All Account Statements") + print(f"{report_type} Aggregation of All Account Statements") print(f"Input Directory: {os.path.abspath(args.input_dir)}") print(f"Output Directory: {os.path.abspath(args.output_dir)}") + if args.year: + print(f"Year Filter: {args.year}") print(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"{'='*60}") @@ -160,7 +167,7 @@ def main(): ]) # Process each month - for (year, month) in sorted(monthly_transactions.keys()): + for year, month in sorted(monthly_transactions.keys()): transactions = monthly_transactions[(year, month)] month_name = calendar.month_name[month] @@ -180,34 +187,6 @@ def main(): transaction_count, institutions_str ]) - # Create detailed monthly transactions file for each month - for (year, month) in sorted(monthly_transactions.keys()): - month_name = calendar.month_name[month].lower() - transactions = monthly_transactions[(year, month)] - - # Create filename - detail_file = os.path.join(args.output_dir, f'transactions_{year}_{month_name}.csv') - - with open(detail_file, 'w', newline='', encoding='utf-8') as f: - writer = csv.DictWriter(f, fieldnames=[ - 'Date', 'Description', 'Category', 'Amount', - 'Institution', 'Source' - ]) - writer.writeheader() - - # Sort transactions by date - sorted_transactions = sorted(transactions, key=lambda x: (x['day'], x['description'])) - - for transaction in sorted_transactions: - writer.writerow({ - 'Date': transaction['date_str'], - 'Description': transaction['description'], - 'Category': transaction['category'], - 'Amount': transaction['amount'], - 'Institution': transaction['institution'], - 'Source': transaction['source'] - }) - # Create yearly summary yearly_summary = defaultdict(lambda: {'income': 0, 'expenses': 0, 'count': 0}) for transaction in all_transactions: @@ -231,22 +210,113 @@ def main(): year, data['income'], data['expenses'], net_balance, data['count'] ]) - # Print summary statistics - print(f"\n{'='*60}") - print(f"Aggregation Complete") - print(f"Total Transactions: {len(all_transactions)}") - print(f"Months with Data: {len(monthly_transactions)}") - print(f"{'='*60}") - - # List generated files + # Create annual reports if requested generated_files = [ os.path.basename(summary_file), os.path.basename(yearly_file) ] - for (year, month) in sorted(monthly_transactions.keys()): - month_name = calendar.month_name[month].lower() - generated_files.append(f'transactions_{year}_{month_name}.csv') + if args.annual: + # Create annual reports + for year in sorted(yearly_summary.keys()): + if args.year and year != args.year: + continue # Skip years not matching filter + + print(f"\nCreating annual report for {year}...") + + # Get all transactions for the year + year_transactions = [t for t in all_transactions if t['year'] == year] + + # Group by category for the annual report + categories = defaultdict(lambda: {'count': 0, 'total': 0}) + for transaction in year_transactions: + category = transaction['category'] + amount = transaction['amount'] + categories[category]['count'] += 1 + categories[category]['total'] += amount + + # Create annual detailed report + annual_file = os.path.join(args.output_dir, f'annual_report_{year}.csv') + with open(annual_file, 'w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + writer.writerow(['Category', 'Transaction Count', 'Total Amount', 'Percentage']) + + year_total = sum(c['total'] for c in categories.values()) + + # Sort categories by total amount + sorted_categories = sorted(categories.items(), key=lambda x: x[1]['total'], reverse=True) + + for category, data in sorted_categories: + percentage = (data['total'] / year_total) * 100 if year_total != 0 else 0 + writer.writerow([category, data['count'], data['total'], f"{percentage:.2f}%"]) + + # Create annual transactions file + annual_transactions_file = os.path.join(args.output_dir, f'annual_transactions_{year}.csv') + with open(annual_transactions_file, 'w', newline='', encoding='utf-8') as f: + writer = csv.DictWriter(f, fieldnames=[ + 'Date', 'Description', 'Category', 'Amount', + 'Institution', 'Source' + ]) + writer.writeheader() + + # Sort transactions by date + sorted_transactions = sorted(year_transactions, key=lambda x: (x['month'], x['day'], x['description'])) + + for transaction in sorted_transactions: + writer.writerow({ + 'Date': transaction['date_str'], + 'Description': transaction['description'], + 'Category': transaction['category'], + 'Amount': transaction['amount'], + 'Institution': transaction['institution'], + 'Source': transaction['source'] + }) + + generated_files.append(os.path.basename(annual_file)) + generated_files.append(os.path.basename(annual_transactions_file)) + + print(f" Created {os.path.basename(annual_file)} and {os.path.basename(annual_transactions_file)}") + else: + # Create monthly reports (existing functionality) + for year, month in sorted(monthly_transactions.keys()): + month_name = calendar.month_name[month].lower() + transactions = monthly_transactions[(year, month)] + + # Create filename + detail_file = os.path.join(args.output_dir, f'transactions_{year}_{month_name}.csv') + + with open(detail_file, 'w', newline='', encoding='utf-8') as f: + writer = csv.DictWriter(f, fieldnames=[ + 'Date', 'Description', 'Category', 'Amount', + 'Institution', 'Source' + ]) + writer.writeheader() + + # Sort transactions by date + sorted_transactions = sorted(transactions, key=lambda x: (x['day'], x['description'])) + + for transaction in sorted_transactions: + writer.writerow({ + 'Date': transaction['date_str'], + 'Description': transaction['description'], + 'Category': transaction['category'], + 'Amount': transaction['amount'], + 'Institution': transaction['institution'], + 'Source': transaction['source'] + }) + + generated_files.append(f'transactions_{year}_{month_name}.csv') + + # Print summary statistics + print(f"\n{'='*60}") + print(f"Aggregation Complete") + print(f"Total Transactions: {len(all_transactions)}") + print(f"Years with Data: {len(yearly_summary)}") + if not args.annual: + print(f"Months with Data: {len(monthly_transactions)}") + print(f"{'='*60}") + + # List generated files print("\nGenerated Files:") for file in generated_files: file_path = os.path.join(args.output_dir, file)