From 4d8ab562c21db0613424b62ca9f007345d54d0ec Mon Sep 17 00:00:00 2001 From: Lasse Wiedemann Date: Sun, 30 Mar 2025 22:16:44 +0200 Subject: [PATCH] Initial commit --- .gitignore | 3 +++ README.md | 53 +++++++++++++++++++++++++++++++++++++++++ config/settings.yaml | 9 +++++++ requirements.txt | 2 ++ src/config.py | 8 +++++++ src/converter.py | 15 ++++++++++++ src/converter_new.py | 43 +++++++++++++++++++++++++++++++++ src/main.py | 22 +++++++++++++++++ src/utils/file_utils.py | 8 +++++++ 9 files changed, 163 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config/settings.yaml create mode 100644 requirements.txt create mode 100644 src/config.py create mode 100644 src/converter.py create mode 100644 src/converter_new.py create mode 100644 src/main.py create mode 100644 src/utils/file_utils.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77f80cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +data/ +src/rules.yaml +venv/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..755c11c --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# CSV to Beancount Converter + +This project provides a simple utility to convert CSV files into Beancount format. It is designed to facilitate the management of financial data by transforming structured CSV data into a format compatible with Beancount accounting software. + +## Project Structure + +``` +csv-to-beancount +├── src +│ ├── main.py # Entry point for the application +│ ├── converter.py # Logic for converting CSV to Beancount +│ ├── config.py # Configuration settings loader +│ └── utils +│ └── file_utils.py # Utility functions for file operations +├── config +│ └── settings.yaml # Configuration file with fixed values +├── data +│ └── example.csv # Sample CSV file for conversion +├── requirements.txt # Project dependencies +└── README.md # Project documentation +``` + +## Installation + +To set up the project, clone the repository and install the required dependencies: + +```bash +git clone +cd csv-to-beancount +pip install -r requirements.txt +``` + +## Usage + +To convert a CSV file to Beancount format, run the following command: + +```bash +python src/main.py data/example.csv +``` + +This command will read the specified CSV file, convert its contents into Beancount format using the defined mappings in the configuration file, and save the output to a Beancount file. + +## Configuration + +The configuration settings are stored in `config/settings.yaml`. You can modify this file to adjust account mappings and other constants used during the conversion process. + +## Contributing + +Contributions are welcome! Please feel free to submit a pull request or open an issue for any enhancements or bug fixes. + +## License + +This project is licensed under the MIT License. See the LICENSE file for more details. \ No newline at end of file diff --git a/config/settings.yaml b/config/settings.yaml new file mode 100644 index 0000000..e613932 --- /dev/null +++ b/config/settings.yaml @@ -0,0 +1,9 @@ +account_mappings: + giro_account: "Assets:Bank:Giro" + +fixed_values: + currency: "EUR" + default_transaction_type: "expense" + +file_mappings: + example.csv: "Assets:Bank:Giro" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e97cfb2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +PyYAML +pandas \ No newline at end of file diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..abd693f --- /dev/null +++ b/src/config.py @@ -0,0 +1,8 @@ +from pathlib import Path +import yaml + +def load_config(): + config_path = Path(__file__).parent.parent / 'config' / 'settings.yaml' + with open(config_path, 'r') as file: + config = yaml.safe_load(file) + return config \ No newline at end of file diff --git a/src/converter.py b/src/converter.py new file mode 100644 index 0000000..5622504 --- /dev/null +++ b/src/converter.py @@ -0,0 +1,15 @@ +def convert_csv_to_beancount(csv_data, config): + beancount_entries = [] + + for row in csv_data: + date = row['date'] + amount = row['amount'] + description = row['description'] + + # Map the account based on the configuration + account = config['account_mappings'].get(row['filename'], 'Assets:Bank:Giro') + + entry = f"{date} * \"{description}\"\n {account} {amount}\n" + beancount_entries.append(entry) + + return ''.join(beancount_entries) \ No newline at end of file diff --git a/src/converter_new.py b/src/converter_new.py new file mode 100644 index 0000000..5f17b8f --- /dev/null +++ b/src/converter_new.py @@ -0,0 +1,43 @@ +import yaml + +class BeancountConverter: + def __init__(self, rules_file): + with open(rules_file, 'r') as f: + self.rule_definitions = yaml.safe_load(f) + + # Compile rules + self.rules = self._compile_rules(self.rule_definitions) + + def _compile_rules(self, rule_defs): + compiled_rules = [] + + for rule in rule_defs: + conditions = [] + + # Build conditions from rule definition + if 'description_contains' in rule: + terms = rule['description_contains'] + conditions.append( + lambda t, terms=terms: any(term.upper() in t.get('description', '').upper() for term in terms) + ) + + if 'amount_range' in rule: + min_val = rule['amount_range'].get('min', float('-inf')) + max_val = rule['amount_range'].get('max', float('inf')) + conditions.append( + lambda t, min_val=min_val, max_val=max_val: + min_val <= float(t.get('amount', 0)) <= max_val + ) + + # Combine all conditions + compiled_rule = ( + lambda t, conditions=conditions: all(cond(t) for cond in conditions), + rule['target_account'] + ) + + compiled_rules.append(compiled_rule) + + # Add default rule + compiled_rules.append((lambda t: True, 'Expenses:Uncategorized')) + + return compiled_rules \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..2571e5f --- /dev/null +++ b/src/main.py @@ -0,0 +1,22 @@ +import pandas as pd +from src.config import load_config +from src.converter import convert_csv_to_beancount +from src.utils.file_utils import write_beancount + +def main(): + # Load configuration settings + config = load_config() + + # Read the CSV file + csv_file_path = 'data/example.csv' + csv_data = pd.read_csv(csv_file_path) + + # Convert CSV data to Beancount format + beancount_entries = convert_csv_to_beancount(csv_data, config) + + # Write the Beancount entries to a file + output_file_path = 'data/output.beancount' + write_beancount(output_file_path, beancount_entries) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/utils/file_utils.py b/src/utils/file_utils.py new file mode 100644 index 0000000..de11081 --- /dev/null +++ b/src/utils/file_utils.py @@ -0,0 +1,8 @@ +def read_csv(filepath): + import pandas as pd + return pd.read_csv(filepath) + +def write_beancount(filepath, beancount_entries): + with open(filepath, 'w') as f: + for entry in beancount_entries: + f.write(entry + '\n') \ No newline at end of file