testing new features

This commit is contained in:
2026-07-22 12:12:09 +03:00
parent 3cc8e94863
commit 9ed71cbc58
15 changed files with 1201 additions and 40 deletions
+419
View File
@@ -0,0 +1,419 @@
from pathlib import Path
import typer
from rich.console import Console
from rich.table import Table
from cashflow_model import (
Account,
Asset,
ExchangeRate,
FinancialModel,
Liability,
RecurringCashflow,
Transaction,
)
app = typer.Typer(name="config", help="Управление моделью (счета, транзакции, валюты...)")
console = Console()
DATA_DIR = Path("data")
MODEL_PATH = DATA_DIR / "model.json"
def _load_model() -> FinancialModel:
if MODEL_PATH.exists():
return FinancialModel.load(MODEL_PATH)
return FinancialModel()
def _save_model(model: FinancialModel) -> None:
model.save(MODEL_PATH)
# ── base-currency ──────────────────────────────────────────────
@app.command()
def base_currency(
currency: str = typer.Argument(None, help="Код валюты (RUB, USD, EUR...)"),
) -> None:
"""Показать или установить базовую валюту модели"""
model = _load_model()
if currency:
model.base_currency = currency.upper()
_save_model(model)
console.print(f"[green]OK[/green] Базовая валюта установлена: {model.base_currency}")
else:
console.print(f"Базовая валюта: [bold]{model.base_currency}[/bold]")
# ── rate ───────────────────────────────────────────────────────
@app.command()
def rate_set(
from_currency: str = typer.Argument(..., help="Из валюты"),
to_currency: str = typer.Argument(..., help="В валюту"),
rate: float = typer.Argument(..., help="Курс"),
) -> None:
"""Добавить или обновить курс валюты"""
model = _load_model()
fc, tc = from_currency.upper(), to_currency.upper()
existing = [r for r in model.exchange_rates if r.from_currency == fc and r.to_currency == tc]
if existing:
existing[0].rate = rate
else:
model.exchange_rates.append(ExchangeRate(
from_currency=from_currency.upper(),
to_currency=to_currency.upper(),
rate=rate,
))
_save_model(model)
fc, tc = from_currency.upper(), to_currency.upper()
console.print(f"[green]OK[/green] Курс {fc}{tc} = {rate}")
@app.command(name="rate-list")
def rate_list() -> None:
"""Список курсов валют"""
model = _load_model()
if not model.exchange_rates:
console.print("[yellow]Курсы не заданы[/yellow]")
return
table = Table(title="Курсы валют")
table.add_column("Из", style="cyan")
table.add_column("В", style="cyan")
table.add_column("Курс", justify="right")
for r in model.exchange_rates:
table.add_row(r.from_currency, r.to_currency, str(r.rate))
console.print(table)
@app.command(name="rate-remove")
def rate_remove(
from_currency: str = typer.Argument(..., help="Из валюты"),
to_currency: str = typer.Argument(..., help="В валюту"),
) -> None:
"""Удалить курс валюты"""
model = _load_model()
fc, tc = from_currency.upper(), to_currency.upper()
initial = len(model.exchange_rates)
model.exchange_rates = [
r for r in model.exchange_rates
if not (r.from_currency == fc and r.to_currency == tc)
]
if len(model.exchange_rates) < initial:
_save_model(model)
console.print(f"[green]OK[/green] Курс {fc}{tc} удалён")
else:
console.print(f"[red]Курс {fc}{tc} не найден[/red]")
raise typer.Exit(1)
# ── account ────────────────────────────────────────────────────
@app.command()
def account_add(
name: str = typer.Option(..., "--name", "-n", help="Название счёта"),
balance: float = typer.Option(0.0, "--balance", "-b", help="Начальный баланс"),
currency: str = typer.Option("", "--currency", "-c", help="Валюта счёта"),
) -> None:
"""Добавить счёт"""
model = _load_model()
curr = currency.upper() if currency else model.base_currency
account = Account(name=name, balance=balance, currency=curr)
model.accounts.append(account)
_save_model(model)
console.print(f"[green]OK[/green] Счёт '{name}' добавлен (баланс: {balance}, валюта: {curr})")
@app.command(name="account-list")
def account_list() -> None:
"""Список счетов"""
model = _load_model()
if not model.accounts:
console.print("[yellow]Счета не добавлены[/yellow]")
return
table = Table(title="Счета")
table.add_column("ID", style="dim")
table.add_column("Название", style="cyan")
table.add_column("Валюта")
table.add_column("Баланс", justify="right")
for a in model.accounts:
table.add_row(str(a.id)[:8], a.name, a.currency, f"{a.balance:,.2f}")
console.print(table)
@app.command(name="account-remove")
def account_remove(
identifier: str = typer.Argument(..., help="ID или название счёта"),
) -> None:
"""Удалить счёт по ID или названию"""
model = _load_model()
initial = len(model.accounts)
model.accounts = [
a for a in model.accounts
if str(a.id) != identifier and a.name != identifier
]
if len(model.accounts) < initial:
_save_model(model)
console.print(f"[green]OK[/green] Счёт '{identifier}' удалён")
else:
console.print(f"[red]Счёт '{identifier}' не найден[/red]")
raise typer.Exit(1)
# ── transaction ────────────────────────────────────────────────
@app.command()
def transaction_add(
account: str = typer.Option(..., "--account", "-a", help="ID или название счёта"),
amount: float = typer.Option(..., "--amount", "-m", help="Сумма (доход/расход)"),
category: str = typer.Option("", "--category", "-c", help="Категория"),
description: str = typer.Option("", "--description", "-d", help="Описание"),
date: str = typer.Option("", "--date", "-D", help="Дата (YYYY-MM-DD)"),
) -> None:
"""Добавить транзакцию"""
model = _load_model()
account_id = _resolve_account_id(model, account)
if not account_id:
console.print(f"[red]Счёт '{account}' не найден[/red]")
raise typer.Exit(1)
txn = Transaction(
date=date or "",
account=account_id,
category=category,
amount=amount,
description=description,
)
model.transactions.append(txn)
_save_model(model)
kind = "доход" if amount >= 0 else "расход"
console.print(f"[green]OK[/green] Транзакция '{kind}' на {abs(amount):.2f} (счёт: {account})")
@app.command(name="transaction-list")
def transaction_list() -> None:
"""Список транзакций"""
model = _load_model()
if not model.transactions:
console.print("[yellow]Транзакции не добавлены[/yellow]")
return
table = Table(title="Транзакции")
table.add_column("ID", style="dim")
table.add_column("Дата")
table.add_column("Счёт")
table.add_column("Категория")
table.add_column("Сумма", justify="right")
table.add_column("Описание")
for t in model.transactions:
account_name = _account_name(model, t.account)
table.add_row(
str(t.id)[:8], t.date, account_name, t.category,
f"{t.amount:,.2f}", t.description,
)
console.print(table)
@app.command(name="transaction-remove")
def transaction_remove(
identifier: str = typer.Argument(..., help="ID транзакции"),
) -> None:
"""Удалить транзакцию по ID"""
model = _load_model()
initial = len(model.transactions)
model.transactions = [t for t in model.transactions if str(t.id) != identifier]
if len(model.transactions) < initial:
_save_model(model)
console.print(f"[green]OK[/green] Транзакция '{identifier}' удалена")
else:
console.print(f"[red]Транзакция '{identifier}' не найдена[/red]")
raise typer.Exit(1)
# ── recurring ──────────────────────────────────────────────────
@app.command()
def recurring_add(
amount: float = typer.Option(..., "--amount", "-m", help="Сумма"),
category: str = typer.Option("", "--category", "-c", help="Категория"),
frequency: str = typer.Option("monthly", "--frequency", "-f", help="Периодичность"),
start_date: str = typer.Option("", "--start", "-s", help="Дата начала (YYYY-MM-DD)"),
end_date: str = typer.Option("", "--end", "-e", help="Дата окончания (YYYY-MM-DD)"),
) -> None:
"""Добавить регулярный платёж"""
model = _load_model()
rc = RecurringCashflow(
start_date=start_date or "", end_date=end_date or "",
frequency=frequency, amount=amount, category=category,
)
model.recurring.append(rc)
_save_model(model)
console.print(f"[green]OK[/green] Регулярный платёж на {abs(amount):.2f} ({frequency})")
@app.command(name="recurring-list")
def recurring_list() -> None:
"""Список регулярных платежей"""
model = _load_model()
if not model.recurring:
console.print("[yellow]Регулярные платежи не добавлены[/yellow]")
return
table = Table(title="Регулярные платежи")
table.add_column("ID", style="dim")
table.add_column("Начало")
table.add_column("Конец")
table.add_column("Период")
table.add_column("Сумма", justify="right")
table.add_column("Категория")
for r in model.recurring:
row = (str(r.id)[:8], r.start_date, r.end_date, r.frequency, f"{r.amount:,.2f}", r.category)
table.add_row(*row)
console.print(table)
@app.command(name="recurring-remove")
def recurring_remove(
identifier: str = typer.Argument(..., help="ID регулярного платежа"),
) -> None:
"""Удалить регулярный платёж по ID"""
model = _load_model()
initial = len(model.recurring)
model.recurring = [r for r in model.recurring if str(r.id) != identifier]
if len(model.recurring) < initial:
_save_model(model)
console.print(f"[green]OK[/green] Регулярный платёж '{identifier}' удалён")
else:
console.print(f"[red]Регулярный платёж '{identifier}' не найден[/red]")
raise typer.Exit(1)
# ── asset ──────────────────────────────────────────────────────
@app.command()
def asset_add(
name: str = typer.Option(..., "--name", "-n", help="Название актива"),
value: float = typer.Option(0.0, "--value", "-v", help="Стоимость"),
growth_rate: float = typer.Option(0.0, "--growth", "-g", help="Годовой рост (%)"),
) -> None:
"""Добавить актив"""
model = _load_model()
asset = Asset(name=name, value=value, growth_rate=growth_rate)
model.assets.append(asset)
_save_model(model)
console.print(f"[green]OK[/green] Актив '{name}' добавлен (стоимость: {value})")
@app.command(name="asset-list")
def asset_list() -> None:
"""Список активов"""
model = _load_model()
if not model.assets:
console.print("[yellow]Активы не добавлены[/yellow]")
return
table = Table(title="Активы")
table.add_column("ID", style="dim")
table.add_column("Название", style="cyan")
table.add_column("Стоимость", justify="right")
table.add_column("Рост (%)", justify="right")
for a in model.assets:
table.add_row(str(a.id)[:8], a.name, f"{a.value:,.2f}", f"{a.growth_rate:.1f}%")
console.print(table)
@app.command(name="asset-remove")
def asset_remove(
identifier: str = typer.Argument(..., help="ID или название актива"),
) -> None:
"""Удалить актив по ID или названию"""
model = _load_model()
initial = len(model.assets)
model.assets = [
a for a in model.assets
if str(a.id) != identifier and a.name != identifier
]
if len(model.assets) < initial:
_save_model(model)
console.print(f"[green]OK[/green] Актив '{identifier}' удалён")
else:
console.print(f"[red]Актив '{identifier}' не найден[/red]")
raise typer.Exit(1)
# ── liability ──────────────────────────────────────────────────
@app.command()
def liability_add(
name: str = typer.Option(..., "--name", "-n", help="Название"),
balance: float = typer.Option(0.0, "--balance", "-b", help="Остаток долга"),
interest: float = typer.Option(0.0, "--interest", "-i", help="Процентная ставка (%)"),
payment: float = typer.Option(0.0, "--payment", "-p", help="Ежемесячный платёж"),
) -> None:
"""Добавить обязательство"""
model = _load_model()
liability = Liability(name=name, balance=balance, interest=interest, payment=payment)
model.liabilities.append(liability)
_save_model(model)
console.print(f"[green]OK[/green] Обязательство '{name}' добавлено (долг: {balance})")
@app.command(name="liability-list")
def liability_list() -> None:
"""Список обязательств"""
model = _load_model()
if not model.liabilities:
console.print("[yellow]Обязательства не добавлены[/yellow]")
return
table = Table(title="Обязательства")
table.add_column("ID", style="dim")
table.add_column("Название", style="cyan")
table.add_column("Долг", justify="right")
table.add_column("Ставка", justify="right")
table.add_column("Платёж", justify="right")
for li in model.liabilities:
b, i, p = f"{li.balance:,.2f}", f"{li.interest:.1f}%", f"{li.payment:,.2f}"
row = (str(li.id)[:8], li.name, b, i, p)
table.add_row(*row)
console.print(table)
@app.command(name="liability-remove")
def liability_remove(
identifier: str = typer.Argument(..., help="ID или название обязательства"),
) -> None:
"""Удалить обязательство по ID или названию"""
model = _load_model()
initial = len(model.liabilities)
model.liabilities = [
li for li in model.liabilities
if str(li.id) != identifier and li.name != identifier
]
if len(model.liabilities) < initial:
_save_model(model)
console.print(f"[green]OK[/green] Обязательство '{identifier}' удалено")
else:
console.print(f"[red]Обязательство '{identifier}' не найдено[/red]")
raise typer.Exit(1)
# ── helpers ────────────────────────────────────────────────────
def _resolve_account_id(model: FinancialModel, identifier: str) -> str | None:
for a in model.accounts:
if str(a.id) == identifier or a.name == identifier:
return str(a.id)
return None
def _account_name(model: FinancialModel, account_id: str) -> str:
for a in model.accounts:
if str(a.id) == account_id:
return a.name
return account_id[:8]
+155 -27
View File
@@ -6,7 +6,8 @@ from rich.console import Console
from rich.table import Table
from ai.assistant import AssistantService
from cashflow_model import FinancialModel
from cashflow_model import CurrencyConverter, FinancialModel
from cli.config import app as config_app
from engine.forecast import ForecastService
from engine.scenarios import DEFAULT_SCENARIOS, ScenarioService
from sync.excel_sync import ExcelSync
@@ -22,6 +23,9 @@ console = Console()
DATA_DIR = Path("data")
MODEL_PATH = DATA_DIR / "model.json"
CURRENCY_HELP = "Валюта отображения (по умолчанию — базовая валюта модели)"
CURRENCY_OPTION = typer.Option(None, "--currency", "-c", help=CURRENCY_HELP)
def _load_model() -> FinancialModel:
if MODEL_PATH.exists():
@@ -33,26 +37,59 @@ def _save_model(model: FinancialModel) -> None:
model.save(MODEL_PATH)
def _get_converter(model: FinancialModel) -> CurrencyConverter:
return CurrencyConverter(model.exchange_rates)
def _fmt(amount: float, currency: str, symbol: str) -> str:
return f"{symbol}{amount:,.2f}"
def _resolve_currency(model: FinancialModel, currency: str | None) -> tuple[str, str]:
target = currency or model.base_currency
converter = _get_converter(model)
symbol = converter.get_symbol(target)
return target, symbol
def _convert_value(
converter: CurrencyConverter, amount: float, from_curr: str, to_curr: str
) -> float:
if to_curr == from_curr:
return amount
return converter.convert(amount, from_curr, to_curr)
@app.command()
def init() -> None:
"""Создать пустую финансовую модель"""
model = FinancialModel()
_save_model(model)
console.print("[green]OK[/green] Пустая модель создана в data/model.json")
console.print(f"[green]OK[/green] Пустая модель создана в {MODEL_PATH}")
@app.command()
def forecast(
months: int = typer.Option(12, "--months", "-m", help="Количество месяцев прогноза"),
currency: str | None = CURRENCY_OPTION,
) -> None:
"""Запустить прогноз денежных потоков"""
model = _load_model()
target_curr, symbol = _resolve_currency(model, currency)
converter = _get_converter(model)
base_curr = model.base_currency
service = ForecastService(model)
results = service.forecast_cashflow(months)
summary = service.summary(months)
def _cv(val):
return _convert_value(converter, val, base_curr, target_curr)
def _f(val):
return _fmt(val, target_curr, symbol)
if results:
table = Table(title=f"Прогноз на {months} мес.")
table = Table(title=f"Прогноз на {months} мес. ({target_curr})")
table.add_column("Счёт", style="cyan")
table.add_column("Месяц", style="white")
table.add_column("Баланс", justify="right", style="green")
@@ -62,24 +99,29 @@ def forecast(
for r in results:
table.add_row(
r["account"], str(r["month"]),
f"${r['balance']:.2f}",
f"${r['income']:.2f}",
f"${r['expenses']:.2f}",
_f(_cv(r["balance"])),
_f(_cv(r["income"])),
_f(_cv(r["expenses"])),
)
console.print(table)
console.print(f"\n[bold]Итог:[/bold] Баланс: ${summary['total_balance']:.2f} | "
f"Доход: ${summary['total_income']:.2f} | "
f"Расход: ${summary['total_expenses']:.2f}")
cb = _cv(summary["total_balance"])
ci = _cv(summary["total_income"])
ce = _cv(summary["total_expenses"])
console.print(f"\n[bold]Итог:[/bold] Баланс: {_f(cb)} | Доход: {_f(ci)} | Расход: {_f(ce)}")
@app.command()
def scenario(
name: str = typer.Argument("baseline", help="Имя сценария: baseline, optimistic, pessimistic"),
months: int = typer.Option(12, "--months", "-m", help="Количество месяцев"),
currency: str | None = CURRENCY_OPTION,
) -> None:
"""Применить сценарий и показать прогноз"""
model = _load_model()
target_curr, symbol = _resolve_currency(model, currency)
converter = _get_converter(model)
base_curr = model.base_currency
service = ScenarioService(model)
if name in DEFAULT_SCENARIOS:
@@ -90,11 +132,14 @@ def scenario(
raise typer.Exit(1)
result = service.apply(scenario_obj, months)
balance = _convert_value(converter, result["total_balance"], base_curr, target_curr)
income = _convert_value(converter, result["total_income"], base_curr, target_curr)
expenses = _convert_value(converter, result["total_expenses"], base_curr, target_curr)
console.print(f"[bold]Сценарий:[/bold] {result['scenario']}")
console.print(f"[dim]{result['scenario_description']}[/dim]")
console.print(f"Баланс: ${result['total_balance']:.2f}")
console.print(f"Доход: ${result['total_income']:.2f}")
console.print(f"Расход: ${result['total_expenses']:.2f}")
console.print(f"Баланс: {_fmt(balance, target_curr, symbol)}")
console.print(f"Доход: {_fmt(income, target_curr, symbol)}")
console.print(f"Расход: {_fmt(expenses, target_curr, symbol)}")
@app.command()
@@ -103,41 +148,53 @@ def whatif(
expense_mult: float = typer.Option(1.0, "--expense", "-e", help="Множитель расхода"),
growth_mult: float = typer.Option(1.0, "--growth", "-g", help="Множитель роста активов"),
months: int = typer.Option(12, "--months", "-m", help="Количество месяцев"),
currency: str | None = CURRENCY_OPTION,
) -> None:
"""What-if анализ с произвольными множителями"""
model = _load_model()
target_curr, symbol = _resolve_currency(model, currency)
converter = _get_converter(model)
base_curr = model.base_currency
service = ScenarioService(model)
result = service.what_if(income_mult, expense_mult, growth_mult, months)
balance = _convert_value(converter, result["total_balance"], base_curr, target_curr)
income = _convert_value(converter, result["total_income"], base_curr, target_curr)
expenses = _convert_value(converter, result["total_expenses"], base_curr, target_curr)
console.print("[bold]What-if анализ[/bold]")
console.print(f"Доход x{income_mult} | Расход x{expense_mult} | Рост x{growth_mult}")
console.print(f"Баланс: ${result['total_balance']:.2f}")
console.print(f"Доход: ${result['total_income']:.2f}")
console.print(f"Расход: ${result['total_expenses']:.2f}")
console.print(f"Баланс: {_fmt(balance, target_curr, symbol)}")
console.print(f"Доход: {_fmt(income, target_curr, symbol)}")
console.print(f"Расход: {_fmt(expenses, target_curr, symbol)}")
@app.command()
def compare(
months: int = typer.Option(12, "--months", "-m", help="Количество месяцев"),
currency: str | None = CURRENCY_OPTION,
) -> None:
"""Сравнить все сценарии"""
model = _load_model()
target_curr, symbol = _resolve_currency(model, currency)
converter = _get_converter(model)
base_curr = model.base_currency
service = ScenarioService(model)
results = service.compare(months)
table = Table(title="Сравнение сценариев")
table = Table(title=f"Сравнение сценариев ({target_curr})")
table.add_column("Сценарий", style="cyan")
table.add_column("Баланс", justify="right")
table.add_column("Доход", justify="right")
table.add_column("Расход", justify="right")
for name, r in results.items():
table.add_row(
name,
f"${r['total_balance']:.2f}",
f"${r['total_income']:.2f}",
f"${r['total_expenses']:.2f}",
)
b = _convert_value(converter, r["total_balance"], base_curr, target_curr)
i = _convert_value(converter, r["total_income"], base_curr, target_curr)
e = _convert_value(converter, r["total_expenses"], base_curr, target_curr)
fb = _fmt(b, target_curr, symbol)
fi = _fmt(i, target_curr, symbol)
fe = _fmt(e, target_curr, symbol)
table.add_row(name, fb, fi, fe)
console.print(table)
@@ -155,6 +212,7 @@ def import_xlsx(
f"{len(model.recurring)} регулярных платежей, "
f"{len(model.assets)} активов, "
f"{len(model.liabilities)} обязательств")
console.print(f"[dim]Базовая валюта: {model.base_currency}[/dim]")
except Exception as e:
console.print(f"[red]Ошибка импорта: {e}[/red]")
raise typer.Exit(1)
@@ -178,24 +236,94 @@ def export_xlsx(
@app.command()
def analyze(
months: int = typer.Option(12, "--months", "-m", help="Количество месяцев для анализа"),
currency: str | None = CURRENCY_OPTION,
) -> None:
"""AI-анализ финансовой модели"""
model = _load_model()
assistant = AssistantService(model)
target_curr, _ = _resolve_currency(model, currency)
converter = _get_converter(model)
assistant = AssistantService(model, converter=converter, display_currency=target_curr)
result = assistant.analyze(months)
console.print("[bold]Промпт для AI:[/bold]")
console.print(result["prompt"][:500] + "...\n")
console.print("[bold]Сводка:[/bold]")
s = result["summary"]
console.print(f"Баланс: ${s['total_balance']:.2f}")
console.print(f"Доход: ${s['total_income']:.2f}")
console.print(f"Расход: ${s['total_expenses']:.2f}")
console.print(f"Базовая валюта: {model.base_currency}")
console.print(
"\n[yellow]AI-ответ: заглушка. Подключите реальный API в ai/assistant.py[/yellow]"
)
@app.command()
def info() -> None:
"""Сводка всей финансовой модели"""
model = _load_model()
converter = _get_converter(model)
symbol = converter.get_symbol(model.base_currency)
console.print("[bold]Метаданные[/bold]")
console.print(f" Базовая валюта: {model.base_currency} ({symbol})")
console.print(f" Счетов: {len(model.accounts)}")
console.print(f" Транзакций: {len(model.transactions)}")
console.print(f" Регулярных платежей: {len(model.recurring)}")
console.print(f" Активов: {len(model.assets)}")
console.print(f" Обязательств: {len(model.liabilities)}")
console.print(f" Курсов валют: {len(model.exchange_rates)}")
if model.accounts:
t = Table(title="Счета")
t.add_column("Название", style="cyan")
t.add_column("Валюта")
t.add_column("Баланс", justify="right")
for a in model.accounts:
t.add_row(a.name, a.currency, f"{a.balance:,.2f}")
console.print(t)
if model.transactions:
t = Table(title="Транзакции")
t.add_column("Дата")
t.add_column("Категория")
t.add_column("Сумма", justify="right")
t.add_column("Описание")
for tx in model.transactions[:10]:
t.add_row(tx.date, tx.category, f"{tx.amount:,.2f}", tx.description)
if len(model.transactions) > 10:
console.print(t)
console.print(f"[dim]... и ещё {len(model.transactions) - 10} транзакций[/dim]")
else:
console.print(t)
if model.recurring:
t = Table(title="Регулярные платежи")
t.add_column("Период")
t.add_column("Сумма", justify="right")
t.add_column("Категория")
for r in model.recurring:
t.add_row(r.frequency, f"{r.amount:,.2f}", r.category)
console.print(t)
if model.assets:
t = Table(title="Активы")
t.add_column("Название", style="cyan")
t.add_column("Стоимость", justify="right")
t.add_column("Рост", justify="right")
for a in model.assets:
t.add_row(a.name, f"{a.value:,.2f}", f"{a.growth_rate:.1f}%")
console.print(t)
if model.liabilities:
t = Table(title="Обязательства")
t.add_column("Название", style="cyan")
t.add_column("Долг", justify="right")
t.add_column("Ставка", justify="right")
t.add_column("Платёж", justify="right")
for li in model.liabilities:
t.add_row(li.name, f"{li.balance:,.2f}", f"{li.interest:.1f}%", f"{li.payment:,.2f}")
console.print(t)
app.add_typer(config_app)
if __name__ == "__main__":
app()