Enhance aggregation script to support annual reports
This commit is contained in:
@@ -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
|
||||
|
||||
6
output/reports/annual_report_2025.csv
Normal file
6
output/reports/annual_report_2025.csv
Normal file
@@ -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%
|
||||
|
63
output/reports/annual_transactions_2025.csv
Normal file
63
output/reports/annual_transactions_2025.csv
Normal file
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user