Files
aurum/backend/app/db/seed.py
ZProger fe9b079c12 Initial public release
Aurum — a self-hosted personal finance operating system: cash flow,
net worth tracking, budgets, goals, recurring payments, reports,
proactive alerts, and a compound-interest returns calculator, all
bilingual (RU/EN) and mobile-first. Ships as a three-container Docker
Compose stack (Postgres + FastAPI + nginx-served React frontend).

See README.md for setup and CONTRIBUTING.md for development.
2026-08-16 18:39:52 +03:00

87 lines
3.2 KiB
Python

"""Seeds the default category set on first boot.
The expense categories are assigned hues from the dataviz skill's validated
8-slot categorical palette, in the palette's fixed slot order (never
reordered/cycled) so the dashboard donut chart is colorblind-safe out of the
box. See CLAUDE.md-adjacent design notes in UPDATES.md for the source.
"""
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import get_settings
from app.models.account import Account
from app.models.category import Category
from app.models.enums import AccountType, CategoryKind
from app.models.settings import AppSettings
# (name, icon, color) — order doubles as sort_order / palette slot index.
DEFAULT_EXPENSE_CATEGORIES = [
("Housing & Utilities", "home", "#2a78d6"), # slot 1 blue
("Groceries", "shopping-basket", "#1baf7a"), # slot 3 aqua
("Dining Out", "utensils", "#eb6834"), # slot 2 orange
("Transportation", "car", "#4a3aa7"), # slot 7 violet
("Health & Fitness", "heart-pulse", "#e34948"), # slot 8 red
("Shopping", "shopping-bag", "#eda100"), # slot 4 yellow
("Entertainment", "clapperboard", "#e87ba4"), # slot 5 magenta
("Subscriptions", "repeat", "#008300"), # slot 6 green
]
DEFAULT_INCOME_CATEGORIES = [
("Salary", "banknote", "#2a78d6"),
("Freelance", "briefcase", "#1baf7a"),
("Investments", "trending-up", "#4a3aa7"),
("Gifts", "gift", "#e87ba4"),
("Other Income", "plus-circle", "#898781"),
]
async def seed_default_categories(session: AsyncSession) -> None:
existing = await session.execute(select(Category.id).limit(1))
if existing.first() is not None:
return
order = 0
for name, icon, color in DEFAULT_EXPENSE_CATEGORIES:
session.add(
Category(name=name, kind=CategoryKind.EXPENSE, icon=icon, color=color, sort_order=order, is_default=True)
)
order += 1
for name, icon, color in DEFAULT_INCOME_CATEGORIES:
session.add(
Category(name=name, kind=CategoryKind.INCOME, icon=icon, color=color, sort_order=order, is_default=True)
)
order += 1
await session.commit()
async def seed_default_account(session: AsyncSession) -> None:
"""Creates one starter account so the Transactions form always has a
destination to post to, even before the (future) accounts management UI
ships."""
existing = await session.execute(select(Account.id).limit(1))
if existing.first() is not None:
return
session.add(
Account(
name="Main Account",
type=AccountType.CHECKING,
currency=get_settings().default_currency,
color="#2a78d6",
)
)
await session.commit()
async def seed_default_app_settings(session: AsyncSession) -> None:
"""Ensures the singleton app_settings row (id=1) exists — the migration
already inserts it, but this makes app boot self-healing if that row is
ever missing (e.g. a DB restored from a pre-currency-setting backup)."""
existing = await session.get(AppSettings, 1)
if existing is not None:
return
session.add(AppSettings(id=1, currency=get_settings().default_currency))
await session.commit()