first draft

This commit is contained in:
Philipp Hofer
2025-10-16 15:59:26 +02:00
commit 900c9872e1
8 changed files with 2212 additions and 0 deletions

28
src/classifier.rs Normal file
View File

@@ -0,0 +1,28 @@
pub enum Category {
Amazon,
Transfer,
}
pub fn cat(desc: &str) -> Category {
if desc.starts_with("From") && desc.contains(" to ") {
return Category::Transfer;
}
if desc == "Investing" {
return Category::Transfer;
}
if desc == "Main Account" {
return Category::Transfer;
}
if desc == "Backup" {
return Category::Transfer;
}
if desc == "N26 Migration" {
return Category::Transfer;
}
if desc == "AMAZON PAYMENTS EUROPE S.C.A." {
return Category::Amazon;
}
todo!("Handle '{desc}'")
}

11
src/lib.rs Normal file
View File

@@ -0,0 +1,11 @@
pub mod classifier;
use chrono::{DateTime, NaiveDate, Utc};
use classifier::Category;
pub struct Transaction {
pub date: NaiveDate,
pub amount_in_cent: i64,
pub desc: String,
pub cat: Category,
}

5
src/main.rs Normal file
View File

@@ -0,0 +1,5 @@
mod n26;
fn main() {
n26::Transaction::from_file("./data/n26-2025-10-16.csv");
}

73
src/n26.rs Normal file
View File

@@ -0,0 +1,73 @@
use chrono::{Date, DateTime, NaiveDate};
use csv::Reader;
use serde::Deserialize;
use std::fs::File;
#[derive(Debug, Deserialize)]
pub struct Transaction {
#[serde(rename = "Booking Date")]
booking_date: String,
#[serde(rename = "Value Date")]
value_date: Option<String>,
#[serde(rename = "Partner Name")]
partner_name: Option<String>,
#[serde(rename = "Partner Iban")]
partner_iban: Option<String>,
#[serde(rename = "Type")]
type_: String,
#[serde(rename = "Payment Reference")]
payment_reference: String,
#[serde(rename = "Account Name")]
account_name: String,
#[serde(rename = "Amount (EUR)")]
amount_eur: f64,
#[serde(rename = "Original Amount")]
original_amount: Option<f64>,
#[serde(rename = "Original Currency")]
original_currency: Option<String>,
#[serde(rename = "Exchange Rate")]
exchange_rate: Option<f64>,
}
impl From<Transaction> for fin::Transaction {
fn from(value: Transaction) -> Self {
let date = if let Some(date) = value.value_date {
// If value_date exist, use this as it's earlier and closer to real date...
date
} else {
// ... otherwise use booking date
value.booking_date
};
let date = NaiveDate::parse_from_str(&date, "%Y-%m-%d").unwrap();
let amount_in_cent = (value.amount_eur * 100.).round() as i64;
let desc = if let Some(desc) = value.partner_name {
desc.clone()
} else {
value.payment_reference
};
let cat = fin::classifier::cat(&desc);
Self {
date,
amount_in_cent,
desc,
cat,
}
}
}
impl Transaction {
pub fn from_file(path: &str) -> Vec<fin::Transaction> {
let mut ret = Vec::new();
let file = File::open(path).unwrap();
let mut rdr = Reader::from_reader(file);
for result in rdr.deserialize::<Transaction>() {
let tx: Transaction = result.unwrap();
ret.push(tx.into());
}
ret
}
}