parse 2 year's worth of transactions
This commit is contained in:
@@ -1,28 +1,484 @@
|
||||
use crate::{Account, Transaction, TransactionDetails};
|
||||
use chrono::{Datelike, NaiveDate};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Category {
|
||||
Zero815,
|
||||
Book,
|
||||
Aliexpress,
|
||||
Willhaben,
|
||||
Amazon,
|
||||
Transfer,
|
||||
Post,
|
||||
Cloudways,
|
||||
Transport(Transport),
|
||||
Donation(Donation),
|
||||
Groceries(Groceries),
|
||||
Sport(Sport),
|
||||
Fiverr,
|
||||
Accomodation,
|
||||
Rent,
|
||||
Grundbuchauszug,
|
||||
PhoneContract,
|
||||
Soda,
|
||||
Server,
|
||||
Mailgun,
|
||||
Movie,
|
||||
Restaurant(Restaurant),
|
||||
Tech(Tech),
|
||||
Membership(String),
|
||||
Oeticket,
|
||||
Household(String),
|
||||
Obi,
|
||||
XXXLutz,
|
||||
Gift,
|
||||
CoffeeToGo,
|
||||
Music,
|
||||
Form(String),
|
||||
Travel(Travel),
|
||||
Misc(String),
|
||||
Hardware(Hardware),
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum Hardware {
|
||||
Laptop,
|
||||
Remarkable,
|
||||
NUC,
|
||||
}
|
||||
|
||||
if desc == "AMAZON PAYMENTS EUROPE S.C.A." {
|
||||
impl From<Hardware> for Category {
|
||||
fn from(value: Hardware) -> Self {
|
||||
Self::Hardware(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Travel {
|
||||
eSIM,
|
||||
}
|
||||
|
||||
impl From<Travel> for Category {
|
||||
fn from(value: Travel) -> Self {
|
||||
Self::Travel(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Tech {
|
||||
LLM,
|
||||
MediaMarkt,
|
||||
Kagi,
|
||||
Grammarly,
|
||||
Komoot,
|
||||
Todoist,
|
||||
}
|
||||
|
||||
impl From<Tech> for Category {
|
||||
fn from(value: Tech) -> Self {
|
||||
Self::Tech(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Restaurant {
|
||||
Proper,
|
||||
FastFood,
|
||||
Mensa,
|
||||
Snack,
|
||||
}
|
||||
|
||||
impl From<Restaurant> for Category {
|
||||
fn from(value: Restaurant) -> Self {
|
||||
Self::Restaurant(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Sport {
|
||||
Squash,
|
||||
SportOkay,
|
||||
Hervis,
|
||||
Wings4Life,
|
||||
}
|
||||
|
||||
impl From<Sport> for Category {
|
||||
fn from(value: Sport) -> Self {
|
||||
Self::Sport(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Groceries {
|
||||
Hofer,
|
||||
Lidl,
|
||||
Billa,
|
||||
Spar,
|
||||
Penny,
|
||||
Unimarkt,
|
||||
Bakery,
|
||||
}
|
||||
|
||||
impl From<Groceries> for Category {
|
||||
fn from(value: Groceries) -> Self {
|
||||
Self::Groceries(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Transport {
|
||||
LinzLinien,
|
||||
Nextbike,
|
||||
Train,
|
||||
Flight,
|
||||
Gas,
|
||||
Bike,
|
||||
}
|
||||
|
||||
impl From<Transport> for Category {
|
||||
fn from(value: Transport) -> Self {
|
||||
Self::Transport(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Donation {
|
||||
RemarkableRCU,
|
||||
Signal,
|
||||
Wikipedia,
|
||||
}
|
||||
|
||||
impl From<Donation> for Category {
|
||||
fn from(value: Donation) -> Self {
|
||||
Self::Donation(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cat(details: &TransactionDetails) -> Category {
|
||||
if details.account == Account::N26
|
||||
&& (details.desc == "Hofer Philipp"
|
||||
|| details.desc == "Hofer Philipp"
|
||||
|| details.desc == "Philipp Hofer")
|
||||
{
|
||||
// Einzahlung auf N26
|
||||
return Category::Transfer;
|
||||
}
|
||||
if details.account == Account::N26 && details.desc == "Instant Savings" {
|
||||
return Category::Transfer;
|
||||
}
|
||||
if details.desc == "CityBikeLinz" {
|
||||
return Transport::Nextbike.into();
|
||||
}
|
||||
if details.desc == "SIGNAL FOUNDATION" {
|
||||
return Donation::Signal.into();
|
||||
}
|
||||
if details.desc.starts_with("OEBB")
|
||||
|| details.desc == "EUROSTAR INTERNATIONAL"
|
||||
|| details.desc == "westbahn.at"
|
||||
{
|
||||
return Transport::Train.into();
|
||||
}
|
||||
if details.desc == "DAVIS REMMEL" {
|
||||
return Donation::RemarkableRCU.into();
|
||||
}
|
||||
if details.desc == "Front Food" {
|
||||
return Restaurant::FastFood.into();
|
||||
}
|
||||
if details.date.year() == 2024 && details.desc == "Peek & Cloppenburg" {
|
||||
return Category::Household("Hemd".into());
|
||||
}
|
||||
if details.desc.starts_with("LIBRO FIL.") {
|
||||
return Category::Household("Libro".into());
|
||||
}
|
||||
if details.desc == "Hofer Dankt" || details.desc == "HOFER ONLINE-SHOP" {
|
||||
return Groceries::Hofer.into();
|
||||
}
|
||||
if details.desc == "Great" {
|
||||
return Restaurant::FastFood.into();
|
||||
}
|
||||
if details.desc == "LPD Wien Strafregister" {
|
||||
return Category::Form("Strafregisterauszug".into());
|
||||
}
|
||||
if details.desc.starts_with("Spar Fil. ") || details.desc.starts_with("Spar Dankt") {
|
||||
return Groceries::Spar.into();
|
||||
}
|
||||
if details.desc == "Unimarkt" {
|
||||
return Groceries::Unimarkt.into();
|
||||
}
|
||||
if details.desc == "Angkoon Thai Restauran" {
|
||||
return Restaurant::Proper.into();
|
||||
}
|
||||
if details.desc == "EVERSPORTS* F10 SPORTF" || details.desc == "EVERSPORTS* F10 SPORTS" {
|
||||
return Sport::Squash.into();
|
||||
}
|
||||
if details.desc == "SINCH MAILGUN" {
|
||||
return Category::Mailgun.into();
|
||||
}
|
||||
if details.desc == "Indigo GmbH" {
|
||||
return Restaurant::Proper.into();
|
||||
}
|
||||
if details.desc == "SP NAEVEGANSHOES" {
|
||||
return Category::Household("Schuhe".into());
|
||||
}
|
||||
if details.desc == "THE ECONOMIST NEWSPAPE" {
|
||||
return Category::Book.into();
|
||||
}
|
||||
if details.desc == "WIST OO" {
|
||||
return Category::Rent;
|
||||
}
|
||||
if details.desc == "0815 Online Handel" {
|
||||
return Category::Zero815;
|
||||
}
|
||||
if details.desc == "HOT TELEKOM" {
|
||||
return Category::PhoneContract;
|
||||
}
|
||||
if details.desc.starts_with("FSA") {
|
||||
return Transport::LinzLinien.into();
|
||||
}
|
||||
if details.desc == "bank99 AG" || details.desc.starts_with("Post ") {
|
||||
return Category::Post;
|
||||
}
|
||||
if details.desc == "COCA COLA HBC AUSTRIA" || details.desc == "Coca-Cola HBC Austria" {
|
||||
return Category::Soda;
|
||||
}
|
||||
if details.desc == "RESCH & FRISCH-EINZELH" {
|
||||
return Groceries::Bakery.into();
|
||||
}
|
||||
if details.desc == "Uni Pizza" || details.desc.starts_with("McDonalds ") {
|
||||
return Restaurant::FastFood.into();
|
||||
}
|
||||
if details.desc == "BlochbergerEisproduktG" {
|
||||
return Restaurant::Snack.into();
|
||||
}
|
||||
if details.desc == "OSTERREICHISCHER ALPENVEREIN Alpenverein Linz" {
|
||||
return Category::Membership("Alpenverein".into());
|
||||
}
|
||||
if details.desc == "OEAMTC Linz 4500" {
|
||||
return Category::Membership("ÖAMTC".into());
|
||||
}
|
||||
if details.desc == "OTT* BARKLEYMOVIE" {
|
||||
return Category::Movie;
|
||||
}
|
||||
if details.desc == "BMJ_justizonline.gv.at" {
|
||||
return Category::Grundbuchauszug;
|
||||
}
|
||||
if details.desc == "CHATGPT SUBSCRIPTION"
|
||||
|| details.desc == "OPENAI *CHATGPT SUBSCR"
|
||||
|| details.desc == "CLAUDE.AI SUBSCRIPTION"
|
||||
|| details.desc == "ANTHROPIC"
|
||||
{
|
||||
return Tech::LLM.into();
|
||||
}
|
||||
if details.desc == "Media Markt" || details.desc == "MEDIAMARKT MARKETPLACE" {
|
||||
return Tech::MediaMarkt.into();
|
||||
}
|
||||
if details.desc == "MENSA LINZ"
|
||||
|| details.desc == "KHG Mensa"
|
||||
|| details.desc == "JULIUS RAAB MENSA"
|
||||
|| details.desc == "SCIENCE CAFE LINZ"
|
||||
{
|
||||
return Restaurant::Mensa.into();
|
||||
}
|
||||
if details.desc == "BKG*HOTEL AT BOOKING.C"
|
||||
|| details.desc == "huetten-holiday.de"
|
||||
|| details.desc == "BKG*BOOKING.COM HOTEL"
|
||||
|| details.desc == "BOOKING.COM"
|
||||
|| details.desc == "Booking.com Hotel"
|
||||
|| details.desc.starts_with("Ibis Styles")
|
||||
{
|
||||
return Category::Accomodation;
|
||||
}
|
||||
if details.desc == "SIVERS.COM" {
|
||||
return Category::Book;
|
||||
}
|
||||
if details.desc == "willhaben PayLivery" {
|
||||
return Category::Willhaben;
|
||||
}
|
||||
if details.desc.starts_with("PENNY DANKT") {
|
||||
return Groceries::Penny.into();
|
||||
}
|
||||
if details.desc.starts_with("www.lampenwelt.at") {
|
||||
return Category::Household("Lampe".into());
|
||||
}
|
||||
if details.desc.starts_with("AMZN ")
|
||||
|| details.desc.starts_with("Amazon.de")
|
||||
|| details.desc.starts_with("AMAZON*")
|
||||
|| details.desc.starts_with("WWW.AMAZON.*")
|
||||
{
|
||||
return Category::Amazon;
|
||||
}
|
||||
if details.desc == "OETICKET.COM" {
|
||||
return Category::Oeticket;
|
||||
}
|
||||
if details.desc == "Sparkasse Oberösterrei" {
|
||||
return Category::Transfer;
|
||||
}
|
||||
if details.desc == "WWW.KNIFESTOCK.SK" {
|
||||
return Category::Household("Schleifstein".into());
|
||||
}
|
||||
if details.desc == "Bike24 GmbH" {
|
||||
return Transport::Bike.into();
|
||||
}
|
||||
if details.desc.starts_with("DM-Fil. ") {
|
||||
return Category::Household("DM".into());
|
||||
}
|
||||
if details.desc == "Jysk GmbH" {
|
||||
return Category::Household("Jysk".into());
|
||||
}
|
||||
if details.desc == "OBI Bau- und Heimwerke"
|
||||
|| details.desc == "OBI SAGT DANKE"
|
||||
|| details.desc == "OBI Home + Garden GmbH"
|
||||
{
|
||||
return Category::Obi;
|
||||
}
|
||||
if details.desc.starts_with("XXXLUTZ ") {
|
||||
return Category::XXXLutz;
|
||||
}
|
||||
if details.desc.starts_with("MISTER MINIT") {
|
||||
return Category::Household("Mister Minit".into());
|
||||
}
|
||||
if details.desc == "Sport-Ski Willy" {
|
||||
return Category::Household("Skiwachs-Zeug".into());
|
||||
}
|
||||
if details.desc == "BANDCAMP DEATHWISH INC" {
|
||||
return Category::Music;
|
||||
}
|
||||
if details.desc.starts_with("SHELL ")
|
||||
|| details.desc.starts_with("Eni ")
|
||||
|| details.desc.starts_with("DISKONT ")
|
||||
{
|
||||
return Transport::Gas.into();
|
||||
}
|
||||
if details.desc == "LINDE VERLAG" {
|
||||
return Category::Book;
|
||||
}
|
||||
if details.desc.starts_with("Lidl DANKT") {
|
||||
return Groceries::Lidl.into();
|
||||
}
|
||||
if details.desc.starts_with("BILLA ") {
|
||||
return Groceries::Billa.into();
|
||||
}
|
||||
if details.desc == "RCH-KAGI.COM" {
|
||||
return Tech::Kagi.into();
|
||||
}
|
||||
if details.desc == "TODOIST" {
|
||||
return Tech::Todoist.into();
|
||||
}
|
||||
if details.desc == "Semmering Hirschenkoge" && details.date.year() == 2025 {
|
||||
return Category::Gift.into();
|
||||
}
|
||||
if details.desc.starts_with("Thalia.at") {
|
||||
return Category::Book;
|
||||
}
|
||||
if details.desc == "WASHCOMPLETE.AT" || details.desc == "Miele Operations Pay" {
|
||||
return Category::Household("Waschkarte".into());
|
||||
}
|
||||
if details.desc == "ELLA BARFUSSSCHUHE" {
|
||||
return Category::Household("Schuhe".into());
|
||||
}
|
||||
if details.desc == "AIRALO" {
|
||||
return Travel::eSIM.into();
|
||||
}
|
||||
if details.desc == "Autobahnrasthaus Chiem" || details.desc == "Cafe+Co AT 311530" {
|
||||
return Category::CoffeeToGo;
|
||||
}
|
||||
if details.desc.starts_with("Bäckerei ")
|
||||
|| details.desc.starts_with("Baeckerei ")
|
||||
|| details.desc == "HONEDER NATURBACKSTU"
|
||||
|| details.desc == "ANKER HBHF SALZBURG"
|
||||
{
|
||||
return Groceries::Bakery.into();
|
||||
}
|
||||
if details.desc.starts_with("Die Obelisk") {
|
||||
return Restaurant::FastFood.into();
|
||||
}
|
||||
if details.desc.starts_with("Sport Okay") {
|
||||
return Sport::SportOkay.into();
|
||||
}
|
||||
if details.desc == "Hervis Sport und Mode" {
|
||||
return Sport::Hervis.into();
|
||||
}
|
||||
if details.desc == "Mol*Moviemento Program" {
|
||||
return Category::Movie;
|
||||
}
|
||||
if details.desc == "GRUNDSTOFF" {
|
||||
return Category::Household("Leiberl".into());
|
||||
}
|
||||
if details.desc == "Wings for Life World R" {
|
||||
return Sport::Wings4Life.into();
|
||||
}
|
||||
if details.desc.starts_with("GRAMMARLY ") {
|
||||
return Tech::Grammarly.into();
|
||||
}
|
||||
if details.desc == "KOMOOT GMBH" {
|
||||
return Tech::Komoot.into();
|
||||
}
|
||||
if details.desc == "Grüne Papaya"
|
||||
|| details.desc == "Mr. Wen - Asia Food -"
|
||||
|| details.desc == "Mr Wen"
|
||||
|| details.desc == "LOsteria Linz"
|
||||
{
|
||||
return Restaurant::Proper.into();
|
||||
}
|
||||
if details.desc == "REMARKABLE" {
|
||||
return Hardware::Remarkable.into();
|
||||
}
|
||||
if details.desc == "Bookbot" {
|
||||
return Category::Book.into();
|
||||
}
|
||||
if details.desc == "DONAU VERSICHERUNG" {
|
||||
return Category::Membership("Haftpflichtversicherung".into());
|
||||
}
|
||||
if details.desc == "IONOS SE"
|
||||
|| details.desc == "EASYNAME COM"
|
||||
|| details.desc == "DIGITALOCEAN.COM"
|
||||
|| details.desc == "PORKBUN.COM"
|
||||
{
|
||||
return Category::Server;
|
||||
}
|
||||
if details.desc == "Alois Dallmayr" || details.desc == "Automaten-Service GmbH" {
|
||||
return Category::CoffeeToGo.into();
|
||||
}
|
||||
if details.desc.starts_with("FRAMEWORK* ") {
|
||||
return Hardware::Laptop.into();
|
||||
}
|
||||
if details.desc.starts_with("RYANAIR") {
|
||||
return Transport::Flight.into();
|
||||
}
|
||||
if details.desc == "SPC*sedruck KG" || details.desc == "NYX*Kuario" {
|
||||
return Category::Misc("Druck".into());
|
||||
}
|
||||
if details.desc.starts_with("Pizzeria ") {
|
||||
return Restaurant::Proper.into();
|
||||
}
|
||||
if details.desc.starts_with("SP JUSTIN JOHNSON MU") {
|
||||
return Category::Music;
|
||||
}
|
||||
if details.date == NaiveDate::from_ymd_opt(2024, 10, 10).unwrap()
|
||||
&& details.desc == "e-tec electronic"
|
||||
{
|
||||
return Hardware::NUC.into();
|
||||
}
|
||||
if details.date.year() == 2024 && details.desc == "MyLemon" {
|
||||
return Hardware::NUC.into();
|
||||
}
|
||||
if details.desc == "ALIEXPRESS.COM" {
|
||||
return Category::Aliexpress;
|
||||
}
|
||||
if details.date.year() == 2024 && details.desc == "1ASHOP.AT" {
|
||||
return Hardware::NUC.into();
|
||||
}
|
||||
if details.date.year() == 2024 && details.desc == "HISTORIA" {
|
||||
return Category::Gift;
|
||||
}
|
||||
|
||||
todo!("Handle '{desc}'")
|
||||
todo!("Handle '{details:#?}'")
|
||||
}
|
||||
|
||||
impl From<TransactionDetails> for Transaction {
|
||||
fn from(details: TransactionDetails) -> Self {
|
||||
let cat = cat(&details);
|
||||
|
||||
Self { details, cat }
|
||||
}
|
||||
}
|
||||
|
18
src/lib.rs
18
src/lib.rs
@@ -1,11 +1,23 @@
|
||||
pub mod classifier;
|
||||
|
||||
use chrono::{DateTime, NaiveDate, Utc};
|
||||
use classifier::Category;
|
||||
use crate::classifier::Category;
|
||||
use chrono::NaiveDate;
|
||||
|
||||
pub struct Transaction {
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Account {
|
||||
N26,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TransactionDetails {
|
||||
pub date: NaiveDate,
|
||||
pub amount_in_cent: i64,
|
||||
pub desc: String,
|
||||
pub account: Account,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Transaction {
|
||||
pub details: TransactionDetails,
|
||||
pub cat: Category,
|
||||
}
|
||||
|
19
src/n26.rs
19
src/n26.rs
@@ -1,7 +1,9 @@
|
||||
use chrono::{Date, DateTime, NaiveDate};
|
||||
use chrono::NaiveDate;
|
||||
use csv::Reader;
|
||||
use fin::{Account, TransactionDetails};
|
||||
use serde::Deserialize;
|
||||
use std::fs::File;
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Transaction {
|
||||
@@ -46,25 +48,28 @@ impl From<Transaction> for fin::Transaction {
|
||||
value.payment_reference
|
||||
};
|
||||
|
||||
let cat = fin::classifier::cat(&desc);
|
||||
|
||||
Self {
|
||||
let details = TransactionDetails {
|
||||
date,
|
||||
amount_in_cent,
|
||||
desc,
|
||||
cat,
|
||||
}
|
||||
account: Account::N26,
|
||||
};
|
||||
|
||||
details.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn from_file(path: &str) -> Vec<fin::Transaction> {
|
||||
info!("Parsing {path}...");
|
||||
let mut ret = Vec::new();
|
||||
let file = File::open(path).unwrap();
|
||||
let mut rdr = Reader::from_reader(file);
|
||||
|
||||
for result in rdr.deserialize::<Transaction>() {
|
||||
for (idx, result) in rdr.deserialize::<Transaction>().enumerate() {
|
||||
println!("{idx}");
|
||||
let tx: Transaction = result.unwrap();
|
||||
println!("{tx:#?}");
|
||||
ret.push(tx.into());
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user