From 88de2fa12a1c7fa817b1cf97c990d0ea285492e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=91=D0=B0?= =?UTF-8?q?=D0=B3=D0=B8=D0=BD?= Date: Thu, 2 Apr 2026 17:26:09 +0000 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?/=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- finance_manager.py | 263 +++++++++++++++++++++++++++++++++++++++++++++ transactions.txt | 16 +++ 2 files changed, 279 insertions(+) create mode 100644 finance_manager.py create mode 100644 transactions.txt diff --git a/finance_manager.py b/finance_manager.py new file mode 100644 index 0000000..b7b162d --- /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..9116bc7 --- /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,Подарки,День рождения друга