Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Agent Guidelines

This file contains instructions and preferences for AI agents working on this repository.

## Environment and Package Management

- **Tool**: Use `uv` for all environment and package management tasks.
- **Running Scripts**: Use `uv run <script>` (e.g., `uv run python main.py`) to ensure the correct environment is used.
- **Adding Dependencies**: Use `uv add <package>` to add new dependencies to `pyproject.toml`.
- **Installing Dependencies**: Use `uv sync` to ensure the environment is up to date.
7 changes: 6 additions & 1 deletion api/config_files/verity_schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ tables:
is_pk: False
datatype: TEXT
nullable: False
- column_name: balance
is_pk: False
is_fk: False
datatype: INTEGER
nullable: False

- table_name: party
table_columns:
Expand Down Expand Up @@ -128,4 +133,4 @@ tables:
- column_name: datetime
is_pk: False
datatype: TEXT
nullable: False
nullable: False
109 changes: 109 additions & 0 deletions api/src/account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"Account Module"

import logging

from api.src.data_handler import Database

logger = logging.getLogger(__name__)


class Account:
"Main account class for any 'real' storage of currency"

def __init__(self, database: Database, name: str = "", id: int = 0, user_id: int = 0):
self.database = database
self.name = name
self.id = id
self.user_id = user_id
self.type = ""
self.balance = 0
self.type_id = 0
logger.info(f"Account {self.name} | {self.id} initialised")

def __repr__(self):
return f"""Account:(
Name: {self.name}
Id: {self.id}
Type: {self.type}
Balance: {self.balance}
)
"""

def __str__(self):
return f"Account: {self.name} of type: {self.type}"

def add(self):
"add account to database"
logger.info(f"Adding {self.name} to {self.user_id}")
if self.user_id == 0:
logger.error("Account not attached to a user, cannot continue")
return 0
if self.type_id == 0:
logger.error("Cannot add a non-typed account, please use Children of Account")
return 0
sql_statement = """
INSERT INTO account (user_id, type_id, name, balance)
VALUES (?, ?, ?, ?)
"""
params = (self.user_id, self.type_id, self.name, self.balance)
success, self.id = self.database.execute(sql_statement, params, return_id=True)
if not success:
logger.error(f"Failed to add Account {self.name}, Check the logs")
return self.id

def get_name(self):
"get name with id"
pass

def get_id(self):
"get id with name"
pass

def get(self):
"get account details from database"
pass


class currentAccount(Account):
"Current Bank Account for every day banking"

def __init__(self, database, name, id, user_id=0):
super(currentAccount, self).__init__(database, name, id, user_id)
self.type = "Current"
self.type_id = 1


class cashAccount(Account):
"Cash Account for tracking your real money"

def __init__(self, database, name, id, user_id=0):
super(cashAccount, self).__init__(database, name, id, user_id)
self.type = "Cash"
self.type_id = 2


class savingAccount(Account):
"Saving Account for tracking your savings"

def __init__(self, database, name, id, user_id=0):
super(savingAccount, self).__init__(database, name, id, user_id)
self.type = "Saving"
self.type_id = 3


class creditAccount(Account):
"Credit Acount, for tracking your debts like credit cards"

def __init__(self, database, name, id, user_id=0):
super(creditAccount, self).__init__(database, name, id, user_id)
self.type = "Credit"
self.type_id = 4


class untrackedAccount(Account):
"for money you still want to see, but dont use as part of your budget."

def __init__(self, database, name, id, user_id=0):
super(untrackedAccount, self).__init__(database, name, id, user_id)
self.type = "Untracked"
self.type_id = 5
2 changes: 1 addition & 1 deletion api/src/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


class Category:
"Main category class, for anything related to the category"
"Main category class, for any theoretical storage of currency"

def __init__(
self,
Expand Down
37 changes: 36 additions & 1 deletion api/src/data_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ def _build_column(column: dict) -> str:

def _add_table_to_db(self, table: dict) -> bool:
"Creates the table in the verity database, based on the schema yaml"
sql = f"""CREATE TABLE IF NOT EXISTS {table["table_name"]} (
table_name = table["table_name"]
sql = f"""CREATE TABLE IF NOT EXISTS {table_name} (
"""
columns = []
for column in table["table_columns"]:
Expand All @@ -101,6 +102,23 @@ def _add_table_to_db(self, table: dict) -> bool:
connection = sqlite3.connect(self.database)
cursor = connection.cursor()
cursor.execute(sql)

# Check for missing columns in existing table
cursor.execute(f"PRAGMA table_info({table_name})")
existing_columns = [row[1] for row in cursor.fetchall()]

for column in table["table_columns"]:
col_name = column["column_name"]
if col_name not in existing_columns:
logger.info(f"Adding missing column {col_name} to table {table_name}")
col_def = self._build_column(column)
alter_sql = f"ALTER TABLE {table_name} ADD COLUMN {col_def}"
try:
cursor.execute(alter_sql)
logger.info(f"Added column {col_name}")
except sqlite3.OperationalError as e:
logger.error(f"Failed to add column {col_name}: {e}")

connection.commit()
success_status = True
except sqlite3.ProgrammingError as pe:
Expand All @@ -116,11 +134,28 @@ def _add_table_to_db(self, table: dict) -> bool:
logger.error(e)
return success_status

def seed_static_data(self):
"""Populates static data tables like account_type"""
account_types = [
(1, "Current"),
(2, "Cash"),
(3, "Saving"),
(4, "Credit"),
(5, "Untracked"),
]
for type_id, name in account_types:
# Check if exists first to avoid unique constraint errors if re-running
check_sql = "SELECT 1 FROM account_type WHERE id = ?"
if not self.read(check_sql, (type_id,)):
logger.info(f"Seeding account_type {name}")
self.execute("INSERT INTO account_type (id, name) VALUES (?, ?)", (type_id, name))

def build_database(self):
for table in self.schema["tables"]:
logger.info(f"Checking {table['table_name']}")
# Add true/false handling here to gracefully handle errors
self._add_table_to_db(table)
self.seed_static_data()

def print_table_schema(self, table_name):
"""
Expand Down
35 changes: 34 additions & 1 deletion api/src/user.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import logging
from typing import List

from api.src.account import (
Account,
cashAccount,
creditAccount,
currentAccount,
savingAccount,
untrackedAccount,
)
from api.src.category import Category
from api.src.data_handler import Database

Expand All @@ -14,8 +22,9 @@ def __init__(self, database: Database, user_name: str = "", id: int = 0):
self.database: Database = database
self.name: str = user_name
self.id: int = id
self.categories: List = []
self.categories: List[Category] = []
self.internal_category_id = 0
self.accounts: list[Account] = []
logger.info(f"{self.name} initialised.")

def __str__(self):
Expand Down Expand Up @@ -98,3 +107,27 @@ def get_internal_master_category(self):
except Exception:
master_id = 0
self.internal_category_id = master_id

def get_accounts(self):
logger.info(f"Getting Accounts for {self.name}")
sql = "SELECT id, name, type_id, balance FROM account WHERE user_id = ?"
params = (self.id,)
accounts_data = self.database.read(sql, params)
self.accounts = []
for acc_data in accounts_data:
acc_id, name, type_id, balance = acc_data
if type_id == 1:
acc = currentAccount(self.database, name, acc_id, self.id)
elif type_id == 2:
acc = cashAccount(self.database, name, acc_id, self.id)
elif type_id == 3:
acc = savingAccount(self.database, name, acc_id, self.id)
elif type_id == 4:
acc = creditAccount(self.database, name, acc_id, self.id)
elif type_id == 5:
acc = untrackedAccount(self.database, name, acc_id, self.id)
else:
continue
acc.balance = balance
self.accounts.append(acc)
return self.accounts
44 changes: 43 additions & 1 deletion docs/src/components/accounts.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,45 @@
# Accounts

## coming soon


The Attributes of the accounts class are:

- database (Database Object)
* passed in so we can test with a theoretical database

- name (String)
* The name of the account as stored in the database

- id (int)
* the id of the account as stored in the database

- Type (String)
* The type of account (see children below)

- balance (int)
* The amount of currency the account holds

The Methods of the account class are:

- add
* Adds the account to the database

- get_name
* Gets the name of the account, sets it in the name attribute

- get_id
* gets the id od the account, sets it in the id attribute

- get
* gets all sored information about the account
* _Probably_ returns the results, so we can build an object with the data.

The Attribute class is a parent class to the following children:
The use case of these will allow for more fexible handling of specific tasks that each account type
will be able to do.

- CurrentAccount
- cashAccount
- savingAccount
- creditAccount
- untrackedAccount
54 changes: 54 additions & 0 deletions front/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

from flask import Blueprint, flash, redirect, render_template, request, session, url_for

from api.src.account import (
cashAccount,
creditAccount,
currentAccount,
savingAccount,
untrackedAccount,
)
from api.src.category import Category
from api.src.config import VerityConfig
from api.src.currency_handler import CurrencyBrain
Expand Down Expand Up @@ -31,14 +38,17 @@ def home_page():
logger.info(f"user: {verity_user}")
if not verity_user:
categories = []
accounts = []
else:
categories = verity_user.get_categories()
accounts = verity_user.get_accounts()
for category in categories:
category.get_children()
return render_template(
"home.html",
users=users,
categories=categories,
accounts=accounts,
selected_user_id=user_id,
selected_user_name=selected_user_name,
)
Expand Down Expand Up @@ -160,6 +170,50 @@ def submit_category():
return redirect(url_for("home.home_page"))


@home_bp.route("/submit_account", methods=["POST"])
def submit_account():
user_id = session.get("user_id")
if not user_id:
flash("No user selected!", "danger")
return redirect(url_for("home.home_page"))

account_name = request.form.get("accountName")
account_type = request.form.get("accountType")
initial_balance = request.form.get("initialBalance", "0")

try:
balance = CurrencyBrain.convert_to_universal_currency(initial_balance)
except Exception:
balance = 0

database = Database(VerityConfig())

if account_type == "1":
new_account = currentAccount(database, account_name, 0, user_id)
elif account_type == "2":
new_account = cashAccount(database, account_name, 0, user_id)
elif account_type == "3":
new_account = savingAccount(database, account_name, 0, user_id)
elif account_type == "4":
new_account = creditAccount(database, account_name, 0, user_id)
elif account_type == "5":
new_account = untrackedAccount(database, account_name, 0, user_id)
else:
flash("Invalid account type", "danger")
return redirect(url_for("home.home_page"))

new_account.balance = balance
account_id = new_account.add()
logger.info(f"Account add returned id: {account_id}")

if account_id > 0:
flash("Account added successfully!", "success")
else:
flash("Failed to add account.", "danger")

return redirect(url_for("home.home_page"))


def stop_mdbook():
try:
process = subprocess.run(["pgrep", "mdbook"], capture_output=True)
Expand Down
Loading