From 4634a36dd5006151f49fed7e14a04f9e25891033 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 2 Apr 2026 21:52:02 +0300 Subject: [PATCH] initial commit --- .vs/ProjectSettings.json | 3 + .vs/VSWorkspaceState.json | 7 + ...425f4c6b-a216-46eb-8728-ca96c3237550.vsidx | Bin 0 -> 14418 bytes .vs/project/v17/.wsuo | Bin 0 -> 14336 bytes .vs/project/v17/DocumentLayout.json | 23 ++ .vs/slnx.sqlite | Bin 0 -> 90112 bytes finance_manager.py | 263 ++++++++++++++++++ transactions.txt | 16 ++ 8 files changed, 312 insertions(+) create mode 100644 .vs/ProjectSettings.json create mode 100644 .vs/VSWorkspaceState.json create mode 100644 .vs/project/FileContentIndex/425f4c6b-a216-46eb-8728-ca96c3237550.vsidx create mode 100644 .vs/project/v17/.wsuo create mode 100644 .vs/project/v17/DocumentLayout.json create mode 100644 .vs/slnx.sqlite create mode 100644 finance_manager.py create mode 100644 transactions.txt diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 0000000..f8b4888 --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..62797f8 --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,7 @@ +{ + "ExpandedNodes": [ + "" + ], + "SelectedNode": "\\finance_manager.py", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/project/FileContentIndex/425f4c6b-a216-46eb-8728-ca96c3237550.vsidx b/.vs/project/FileContentIndex/425f4c6b-a216-46eb-8728-ca96c3237550.vsidx new file mode 100644 index 0000000000000000000000000000000000000000..f815d35c3e0e8833db0d15ba86b2f9e1e1f4a835 GIT binary patch literal 14418 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 literal 0 HcmV?d00001 diff --git a/.vs/project/v17/.wsuo b/.vs/project/v17/.wsuo new file mode 100644 index 0000000000000000000000000000000000000000..1e0fc8b6ff76a9eeca382d91952aba02f5c3616f GIT binary patch literal 14336 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 VKmY_l00ck)1V8`;K;VTE_+JWp`O*LY literal 0 HcmV?d00001 diff --git a/finance_manager.py b/finance_manager.py new file mode 100644 index 0000000..68d96a9 --- /dev/null +++ b/finance_manager.py @@ -0,0 +1,263 @@ +import csv +from datetime import datetime, timedelta + +def load_transactions(filepath: str) -> list[dict]: + """Загружает список транзакций из CSV-файла.""" + transactions = [] + try: + with open(filepath, 'r', encoding='utf-8') as file: + reader = csv.DictReader(file) + for row in reader: + try: + transaction = { + 'id': int(row['id']), + 'date': row['date'], + 'amount': float(row['amount']), + 'category': row['category'], + 'description': row['description'] + } + transactions.append(transaction) + except (ValueError, KeyError): + # Пропускаем строки с некорректным форматом + continue + print(f"Загружено транзакций: {len(transactions)}") + return transactions + except FileNotFoundError: + print(f"Ошибка: Файл {filepath} не найден") + return [] + except Exception as e: + print(f"Ошибка при чтении файла: {e}") + return [] + +def filter_by_date_range(transactions: list[dict], start_date: str, end_date: str) -> list[dict]: + """Фильтрует транзакции по диапазону дат.""" + return [t for t in transactions if start_date <= t['date'] <= end_date] + +def filter_by_category(transactions: list[dict], categories: list[str], exclude: bool = False) -> list[dict]: + """ + Фильтрует транзакции по категориям. + + Параметры: + - transactions: список транзакций. + - categories:список категорий для фильтрации + - exclude: если True - исключает указанные категории, если False - оставляет только их + + Возвращает: отфильтрованый список транзакций + """ + categories_lower = [c.lower() for c in categories] + if exclude: + return [t for t in transactions if t['category'].lower() not in categories_lower] + else: + return [t for t in transactions if t['category'].lower() in categories_lower] + +def filter_by_amount_threshold(transactions: list[dict], min_amount: float = None, max_amount: float = None) -> list[dict]: + """Фильтрует транзакции по диапазону сумм""" + result = transactions + if min_amount is not None: + result = [t for t in result if t['amount'] >= min_amount] + if max_amount is not None: + result = [t for t in result if t['amount'] <= max_amount] + return result + +def calculate_balance(transactions: list[dict]) -> float: + """Вычисляет общий баланс""" + balance = sum(t['amount'] for t in transactions) + return round(balance, 2) + +def group_by_category(transactions: list[dict]) -> dict[str, float]: + """Группирует транзакции по категориям""" + groups = {} + for t in transactions: + category = t['category'] + groups[category] = groups.get(category, 0) + t['amount'] + return {cat: round(amount, 2) for cat, amount in groups.items()} + +def get_top_expenses(transactions: list[dict], n: int) -> list[dict]: + """Возвращает n транзакций с наибольшими расходами.""" + expenses = [t for t in transactions if t['amount'] < 0] + expenses.sort(key=lambda x: x['amount']) # Сортировка от наиболее отрицательных + return expenses[:n] + +def get_average_daily_expenses(transactions: list[dict], days: int = 30) -> float: + """Вычисляет среднюю сумму расходов за последние days дней.""" + if not transactions: + return 0.0 + + # Находим самую позднюю дату + latest_date = max(t['date'] for t in transactions) + latest_date_obj = datetime.strptime(latest_date, "%Y-%m-%d") + start_date_obj = latest_date_obj - timedelta(days=days - 1) + start_date = start_date_obj.strftime("%Y-%m-%d") + + # Фильтруем расходы за указанный период + expenses_in_period = [ + t for t in transactions + if t['amount'] < 0 and start_date <= t['date'] <= latest_date + ] + + if not expenses_in_period: + return 0.0 + + total_expenses = abs(sum(t['amount'] for t in expenses_in_period)) + return round(total_expenses / days, 2) + +def find_transactions_by_text(transactions: list[dict], keyword: str) -> list[dict]: + """находит транзакции по ключевому слову в описании.""" + keyword_lower = keyword.lower() + return [t for t in transactions if keyword_lower in t['description'].lower()] + +def generate_summary_report(transactions: list[dict]) -> str: + """генерирует текстовый отчет по транзакциям.""" + if not transactions: + return "Нет данных для формирования отчета" + + total_count = len(transactions) + incomes = [t for t in transactions if t['amount'] > 0] + expenses = [t for t in transactions if t['amount'] < 0] + + total_income = sum(t['amount'] for t in incomes) + total_expense = abs(sum(t['amount'] for t in expenses)) + balance = total_income - total_expense + + # Топ-3 категории по расходам + expenses_by_category = {} + for t in expenses: + expenses_by_category[t['category']] = expenses_by_category.get(t['category'], 0) + abs(t['amount']) + top_categories = sorted(expenses_by_category.items(), key=lambda x: x[1], reverse=True)[:3] + + # Самая крупная расходная транзакция + largest_expense = min(expenses, key=lambda x: x['amount']) if expenses else None + + # Формируем отчёт + report = [] + report.append("=" * 50) + report.append("ФИНАНСОВЫЙ ОТЧЕТ") + report.append("=" * 50) + report.append(f"Всего транзакций: {total_count}") + report.append(f"Общий доход: {total_income:.2f} руб.") + report.append(f"Общий расход: {total_expense:.2f} руб.") + report.append(f"Итоговый баланс: {balance:.2f} руб.") + report.append("-" * 50) + report.append("ТОП-3 КАТЕГОРИИ ПО РАСХОДАМ:") + for i, (cat, amount) in enumerate(top_categories, 1): + report.append(f" {i}. {cat}: {amount:.2f} руб.") + + if largest_expense: + report.append("-" * 50) + report.append("САМАЯ КРУПНАЯ РАСХОДНАЯ ТРАНЗАКЦИЯ:") + report.append(f" Дата: {largest_expense['date']}") + report.append(f" Категория: {largest_expense['category']}") + report.append(f" Сумма: {largest_expense['amount']:.2f} руб.") + report.append(f" Описание: {largest_expense['description']}") + report.append("=" * 50) + + return "\n".join(report) + +def main(): + """Главная функция, демонстрирующая работу всех функций.""" + print("=" * 60) + print("СИСТЕМА УПРАВЛЕНИЯ ЛИЧНЫМИ ФИНАНСАМИ") + print("=" * 60) + + # 1.Загрузка данных + print("\n1. ЗАГРУЗКА ДАННЫХ") + print("-" * 40) + transactions = load_transactions('data/transactions.txt') + + if not transactions: + print("Нет данных для обработки. Завершение работы.") + return + + # 2. Фильтрация по дате (последние 90 дней) + print("\n2. ФИЛЬТРАЦИЯ ПО ДАТЕ (последние 90 дней)") + print("-" * 40) + + # Находим самую позднюю и самую раннюю дату + latest_date = max(t['date'] for t in transactions) + earliest_date = min(t['date'] for t in transactions) + latest_date_obj = datetime.strptime(latest_date, "%Y-%m-%d") + + # Вычисляем дату 90 дней назад + start_date_obj = latest_date_obj - timedelta(days=89) + start_date = start_date_obj.strftime("%Y-%m-%d") + end_date = latest_date + + # Если start_date раньше earliest_date, то используем earliest_date + if start_date < earliest_date: + start_date = earliest_date + print(f"Примечание: Данных за последние 90 дней недостаточно") + print(f"Используем все доступные данные с {start_date} по {end_date}") + else: + print(f"Период: с {start_date} по {end_date}") + + filtered_by_date = filter_by_date_range(transactions, start_date, end_date) + print(f"Транзакций после фильтрации по дате: {len(filtered_by_date)}") + + # 3. Фильтрация по категориям (исключаем "Перевод" и "Инвестиции") + print("\n3. ФИЛЬТРАЦИЯ ПО КАТЕГОРИЯМ (исключаем 'Перевод' и 'Инвестиции')") + print("-" * 40) + + excluded_categories = ["Перевод", "Инвестиции"] + filtered_by_category_data = filter_by_category(filtered_by_date, excluded_categories, exclude=True) + print(f"Транзакций после фильтрации по категориям: {len(filtered_by_category_data)}") + + # 4. Расчёт баланса + print("\n4. РАСЧЁТ БАЛАНСА") + print("-" * 40) + balance = calculate_balance(filtered_by_category_data) + print(f"Баланс за период: {balance:.2f} руб.") + + # 5. Топ-5 расходов + print("\n5. ТОП-5 РАСХОДОВ") + print("-" * 40) + top_expenses = get_top_expenses(filtered_by_category_data, 5) + if top_expenses: + for i, expense in enumerate(top_expenses, 1): + print(f"{i}. {expense['date']} | {expense['category']} | {expense['amount']:.2f} руб. | {expense['description']}") + else: + print("Нет расходов за указанный период") + + # 6. Поиск транзакций по ключевому слову "кафе" + print("\n6. ПОИСК ПО КЛЮЧЕВОМУ СЛОВУ 'кафе'") + print("-" * 40) + found_transactions = find_transactions_by_text(filtered_by_category_data, "кафе") + if found_transactions: + print(f"Найдено транзакций: {len(found_transactions)}") + for t in found_transactions: + print(f" {t['date']} | {t['category']} | {t['amount']:.2f} руб. | {t['description']}") + else: + print("Транзакции с ключевым словом 'кафе' не найдены") + + # 7. Среднедневные расходы за последние 30 дней + print("\n7. СРЕДНЕДНЕВНЫЕ РАСХОДЫ (последние 30 дней)") + print("-" * 40) + avg_expenses = get_average_daily_expenses(filtered_by_category_data, 30) + print(f"Среднедневные расходы: {avg_expenses:.2f} руб.") + + # 8. Группировка по категориям + print("\n8. ГРУППИРОВКА ПО КАТЕГОРИЯМ") + print("-" * 40) + grouped = group_by_category(filtered_by_category_data) + for category, amount in sorted(grouped.items(), key=lambda x: x[1], reverse=True): + print(f" {category}: {amount:.2f} руб.") + + # 9. Итоговый отчет + print("\n9. ИТОГОВЫЙ ОТЧЕТ") + print("-" * 40) + report = generate_summary_report(filtered_by_category_data) + print(report) + + # 10. Демонстрация фильтрации по сумме + print("\n10. ДЕМОНСТРАЦИЯ ФИЛЬТРАЦИИ ПО СУММЕ") + print("-" * 40) + large_expenses = filter_by_amount_threshold(filtered_by_category_data, max_amount=-500) + print(f"Расходы более 500 руб. (сумма < -500): {len(large_expenses)} транзакций") + for expense in large_expenses[:3]: # Показываем первые 3 + print(f" {expense['date']} | {expense['category']} | {expense['amount']:.2f} руб.") + + print("\n" + "=" * 60) + print("ОБРАБОТКА ЗАВЕРШЕНА") + print("=" * 60) + +if __name__ == "__main__": + main() diff --git a/transactions.txt b/transactions.txt new file mode 100644 index 0000000..9da2621 --- /dev/null +++ b/transactions.txt @@ -0,0 +1,16 @@ +id,date,amount,category,description +1,2025-03-01,50000.00,Зарплата,Оплата за март +2,2025-03-02,-350.00,Еда,Продукты в супермаркете +3,2025-03-03,-13000.00,Транспорт,Покупка авиабилетов +4,2025-03-04,-2500.00,Аренда,Аренда квартиры +5,2025-03-05,15000.00,Фриланс,Разработка сайта +6,2025-03-06,-800.00,Ресторан,Ужин с друзьями +7,2025-03-07,-450.00,Еда,Доставка продуктов +8,2025-03-08,-10000.00,Инвестиции,Покупка акций +9,2025-03-09,-500.00,Транспорт,Заправка автомобиля +10,2025-03-10,-950.00,Кафе,Обед в кафе +11,2025-03-11,45000.00,Зарплата,Оплата за март (аванс) +12,2025-03-12,-320.00,Еда,Супермаркет +13,2025-03-13,-2100.00,Коммунальные,Квартплата +14,2025-03-14,-750.00,Столовая,Завтрак +15,2025-03-15,-1500.00,Подарки,День рождения друга