diff --git a/.flake8 b/.flake8
index 68847e2..3bcf684 100644
--- a/.flake8
+++ b/.flake8
@@ -1,3 +1,4 @@
[flake8]
max-line-length = 119
-exclude = .git, __pycache__, venv
\ No newline at end of file
+exclude = .git,__pycache__,venv,.venv
+ignore = E203,W503
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 71c7e50..e0b3257 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,216 @@
+# Byte-compiled / optimized / DLL files
__pycache__/
-*.pyc
+*.py[codz]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py.cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+# Pipfile.lock
+
+# UV
+# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# uv.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+# poetry.lock
+# poetry.toml
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
+# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
+# pdm.lock
+# pdm.toml
+.pdm-python
+.pdm-build/
+
+# pixi
+# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
+# pixi.lock
+# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
+# in the .venv directory. It is recommended not to include this directory in version control.
+.pixi
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# Redis
+*.rdb
+*.aof
+*.pid
+
+# RabbitMQ
+mnesia/
+rabbitmq/
+rabbitmq-data/
+
+# ActiveMQ
+activemq-data/
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
.env
+.envrc
.venv
-poetry.lock
-*.xlsx
-*.json
-.DS_Store
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+# .idea/
+
+# Abstra
+# Abstra is an AI-powered process automation framework.
+# Ignore directories containing user credentials, local state, and settings.
+# Learn more at https://abstra.io/docs
+.abstra/
+
+# Visual Studio Code
+# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
+# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
+# and can be added to the global gitignore or merged into this file. However, if you prefer,
+# you could uncomment the following to ignore the entire vscode folder
+# .vscode/
+
+# Ruff stuff:
+.ruff_cache/
+
+# PyPI configuration file
+.pypirc
+
+# Marimo
+marimo/_static/
+marimo/_lsp/
+__marimo__/
+
+# Streamlit
+.streamlit/secrets.toml
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/PythonProject9.iml b/.idea/PythonProject9.iml
deleted file mode 100644
index 0b63526..0000000
--- a/.idea/PythonProject9.iml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2d..0000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index c110604..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- Markdown
-
-
- Python
-
-
-
-
- UvPackageVersionsInspection
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 1570b0d..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..80e16ba
--- /dev/null
+++ b/README.md
@@ -0,0 +1,187 @@
+# Transaction Analyzer
+
+Приложение для анализа банковских транзакций с поддержкой веб-интерфейса, отчетов и аналитики.
+
+## Функциональность
+
+- 📊 Анализ транзакций из Excel-файлов
+- 🌐 Генерация JSON данных для веб-страниц
+- 📈 Отчеты и аналитика
+- 💰 Расчет кешбэка и инвесткопилки
+- 🔍 Поиск и фильтрация транзакций
+- 💹 Интеграция с API курсов валют и акций
+
+# Установка
+## 1. Клонирование и настройка
+### Клонируйте репозиторий:
+```bash
+git clone
+cd transaction-analyzer
+```
+
+### Установите зависимости:
+```bash
+poetry install
+```
+
+### Активируйте виртуальное окружение
+```bash
+poetry shell
+```
+
+## 2. Подготовка данных
+
+### Вариант A: Используйте тестовые данные
+
+Сгенерируйте тестовые данные
+```bash
+python data/sample_data.py
+```
+### Вариант B: Загрузите свои данные
+1. Поместите ваш файл с транзакциями в папку data/
+
+2. Переименуйте его в operations.xlsx
+
+## 3. Запуск приложения
+
+Запуск в режиме веб-страниц (генерирует JSON для фронтенда)
+```bash
+python -m src.main --command web
+```
+
+Запуск в режиме отчетов (генерирует отчеты в папку reports/)
+```bash
+python -m src.main --command report
+```
+
+Запуск в режиме анализа (кешбэк и инвесткопилка)
+```bash
+python -m src.main --command analyze
+```
+
+Простой тест функциональности
+```bash
+python -m src.main --command test
+```
+
+## Настройки приложения
+
+### Файл user_settings.json содержит пользовательские настройки:
+
+Файл user_settings.json содержит пользовательские настройки:
+
+```json
+{
+ "user_currencies": ["USD", "EUR", "GBP"],
+ "user_stocks": ["AAPL", "AMZN", "GOOGL", "MSFT", "TSLA"],
+ "cashback_rules": {
+ "Супермаркеты": 0.05,
+ "Фастфуд": 0.03,
+ "Транспорт": 0.02,
+ "default": 0.01
+ }
+}
+```
+
+### Параметры настроек:
+
+* user_currencies - список валют для отслеживания курсов
+
+* user_stocks - список акций для отслеживания цен
+
+* cashback_rules - правила расчета кешбэка по категориям
+
+# Веб-страницы API
+## Главная страница
+```python
+# Генерирует JSON для главной страницы
+from src.views import main_page
+
+data = main_page("2023-12-20 15:30:00")
+```
+
+### Формат ответа:
+```json
+{
+ "greeting": "Добрый день",
+ "cards": [
+ {
+ "last_digits": "5814",
+ "total_spent": 1262.00,
+ "cashback": 12.62
+ }
+ ],
+ "top_transactions": [...],
+ "currency_rates": [...],
+ "stock_prices": [...]
+}
+```
+## Страница событий
+```python
+# Генерирует JSON для страницы событий
+from src.views import events_page
+
+data = events_page("2023-12-20", "M") # M - месяц, W - неделя, Y - год, ALL - все данные
+```
+
+# Сервисы поиска
+
+## Простой поиск
+```python
+from src.services import simple_search
+
+# Поиск по описанию и категории
+results = simple_search(transactions, "магазин")
+```
+
+## Поиск по телефонным номерам
+```python
+from src.services import search_by_phone
+
+# Поиск транзакций с номерами телефонов
+results = search_by_phone(transactions)
+```
+## Поиск переводов физлицам
+```python
+from src.services import search_by_person_transfers
+
+# Поиск переводов физическим лицам
+results = search_by_person_transfers(transactions)
+```
+
+# Генерация отчетов
+
+## Траты по категории
+```python
+from src.reports import spending_by_category
+
+# Траты по категории за последние 3 месяца
+report = spending_by_category(transactions, "Супермаркеты", "2023-12-20")
+```
+## Траты по дням недели
+```python
+from src.reports import spending_by_weekday
+
+# Средние траты по дням недели
+report = spending_by_weekday(transactions)
+```
+## Все отчеты через главный модуль
+```bash
+python -m src.main --command report
+```
+#### Отчеты сохраняются в папку reports/ в формате JSON.
+
+# Тестирование
+
+```bash
+# Запуск всех тестов
+pytest tests/ -v
+```
+```bash
+# Запуск конкретного модуля тестов
+pytest tests/test_views.py -v
+```
+```bash
+# Запуск с покрытием кода
+pytest tests/ --cov=src --cov-report=html
+```
diff --git a/data/operations.xlsx b/data/operations.xlsx
new file mode 100644
index 0000000..b65d76a
Binary files /dev/null and b/data/operations.xlsx differ
diff --git a/data/sample_data.py b/data/sample_data.py
new file mode 100644
index 0000000..5eee45a
--- /dev/null
+++ b/data/sample_data.py
@@ -0,0 +1,72 @@
+"""
+Скрипт для генерации тестовых данных
+"""
+
+import random
+from datetime import datetime, timedelta
+
+import pandas as pd
+
+
+def generate_sample_data(num_records: int = 1000) -> pd.DataFrame:
+ """Генерация тестовых данных транзакций"""
+
+ # Категории транзакций
+ categories = [
+ 'Супермаркеты', 'Фастфуд', 'Транспорт', 'Развлечения',
+ 'Одежда', 'Электроника', 'Здоровье', 'Образование',
+ 'Переводы', 'Наличные', 'Зарплата', 'Инвестиции'
+ ]
+
+ # Статусы операций
+ statuses = ['OK', 'FAILED', 'PENDING']
+
+ # Номера карт
+ cards = ['1234567812345814', '1234567812347512', '1234567812340923']
+
+ # Генерация дат
+ start_date = datetime(2023, 1, 1)
+ end_date = datetime(2023, 12, 31)
+ date_range = (end_date - start_date).days
+
+ data = []
+ for i in range(num_records):
+ # Случайная дата в 2023 году
+ random_days = random.randint(0, date_range)
+ operation_date = start_date + timedelta(days=random_days)
+ payment_date = operation_date + timedelta(days=random.randint(0, 30))
+
+ # Случайная сумма (большинство - расходы, некоторые - доходы)
+ is_income = random.random() < 0.2 # 20% доходы
+ amount = round(random.uniform(100, 50000), 2)
+ if is_income:
+ amount = -amount
+
+ transaction = {
+ 'Дата операции': operation_date.strftime('%d.%m.%Y'),
+ 'Дата платежа': payment_date.strftime('%d.%m.%Y'),
+ 'Номер карты': random.choice(cards),
+ 'Статус': random.choices(statuses, weights=[0.85, 0.1, 0.05])[0],
+ 'Сумма операции': amount,
+ 'Валюта операции': 'RUB',
+ 'Сумма платежа': amount,
+ 'Валюта платежа': 'RUB',
+ 'Кешбэк': round(abs(amount) * 0.01, 2) if amount > 0 and random.random() < 0.7 else 0,
+ 'Категория': random.choice(categories),
+ 'MCC': random.randint(1000, 9999),
+ 'Описание': f'Транзакция #{i + 1}',
+ 'Бонусы (включая кешбэк)': round(abs(amount) * 0.005, 2) if amount > 0 else 0,
+ 'Округление на «Инвесткопилку»': random.randint(0, 50),
+ 'Сумма операции с округлением': round(amount / 50) * 50 if amount > 0 else amount
+ }
+
+ data.append(transaction)
+
+ return pd.DataFrame(data)
+
+
+if __name__ == "__main__":
+ # Генерация тестовых данных
+ df = generate_sample_data(1000)
+ df.to_excel('data/operations.xlsx', index=False)
+ print("Тестовые данные сохранены в data/operations.xlsx")
diff --git a/env.example b/env.example
new file mode 100644
index 0000000..11f8414
--- /dev/null
+++ b/env.example
@@ -0,0 +1,3 @@
+ALPHA_VANTAGE_API_KEY=your_alpha_vantage_key_here
+EXCHANGERATE_API_KEY=your_exchangerate_api_key_here
+CURRENCY_API_KEY=your_currency_api_key_here
diff --git a/logs/transaction_analyzer.log b/logs/transaction_analyzer.log
new file mode 100644
index 0000000..d75d15f
--- /dev/null
+++ b/logs/transaction_analyzer.log
@@ -0,0 +1,2130 @@
+2025-09-27 23:14:32,365 - __main__ - INFO - ...
+2025-09-27 23:14:32,366 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:14:32,622 - src.utils - INFO - 1000
+2025-09-27 23:14:32,623 - __main__ - INFO -
+2025-09-27 23:14:32,623 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:14:32,747 - src.utils - INFO - 1000
+2025-09-27 23:14:32,748 - src.utils - INFO - 0 2025-09-01 - 2025-09-27
+2025-09-27 23:14:34,849 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:14:34,978 - src.utils - INFO - 1000
+2025-09-27 23:14:34,979 - src.utils - INFO - 0 2025-09-01 - 2025-09-27
+2025-09-27 23:14:34,980 - src.api_client - INFO - Using cached currency rates
+2025-09-27 23:14:34,981 - src.api_client - INFO - Using cached stock prices
+2025-09-27 23:23:25,440 - __main__ - INFO - ...
+2025-09-27 23:23:25,440 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:23:25,684 - src.utils - INFO - 1000
+2025-09-27 23:23:25,685 - __main__ - INFO -
+2025-09-27 23:23:25,685 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:23:25,812 - src.utils - INFO - 1000
+2025-09-27 23:23:25,813 - src.utils - INFO - 0 2025-09-01 - 2025-09-27
+2025-09-27 23:23:28,055 - src.api_client - INFO - Using fallback stock prices
+2025-09-27 23:23:28,057 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:23:28,182 - src.utils - INFO - 1000
+2025-09-27 23:23:28,183 - src.utils - INFO - 0 2025-09-01 - 2025-09-27
+2025-09-27 23:23:28,184 - src.api_client - INFO - Using cached currency rates
+2025-09-27 23:23:29,450 - src.api_client - INFO - Using fallback stock prices
+2025-09-27 23:30:07,421 - __main__ - INFO - ...
+2025-09-27 23:30:07,422 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:30:07,670 - src.utils - INFO - 1000
+2025-09-27 23:30:07,671 - __main__ - INFO -
+2025-09-27 23:30:07,671 - __main__ - ERROR - : name 'ReportGenerator' is not defined
+2025-09-27 23:38:33,025 - __main__ - INFO - ...
+2025-09-27 23:38:33,026 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:38:33,269 - src.utils - INFO - 1000
+2025-09-27 23:38:33,270 - __main__ - INFO -
+2025-09-27 23:38:33,493 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:38:33,620 - src.utils - INFO - 1000
+2025-09-27 23:38:33,622 - src.utils - INFO - 0 2025-09-01 - 2025-09-27
+2025-09-27 23:38:37,571 - src.api_client - INFO - Using fallback stock prices
+2025-09-27 23:38:37,573 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:38:37,700 - src.utils - INFO - 1000
+2025-09-27 23:38:37,701 - src.utils - INFO - 0 2025-09-01 - 2025-09-27
+2025-09-27 23:38:37,703 - src.api_client - INFO - Using cached currency rates
+2025-09-27 23:38:41,146 - src.api_client - INFO - Using fallback stock prices
+2025-09-27 23:39:37,249 - __main__ - INFO - ...
+2025-09-27 23:39:37,249 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:39:37,518 - src.utils - INFO - 1000
+2025-09-27 23:39:37,518 - __main__ - INFO -
+2025-09-27 23:39:37,741 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:39:37,869 - src.utils - INFO - 1000
+2025-09-27 23:39:37,871 - src.utils - INFO - 0 2025-09-01 - 2025-09-27
+2025-09-27 23:39:42,148 - src.api_client - INFO - Using fallback stock prices
+2025-09-27 23:39:42,150 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:39:42,279 - src.utils - INFO - 1000
+2025-09-27 23:39:42,280 - src.utils - INFO - 0 2025-09-01 - 2025-09-27
+2025-09-27 23:39:42,282 - src.api_client - INFO - Using cached currency rates
+2025-09-27 23:39:46,372 - src.api_client - INFO - Using fallback stock prices
+2025-09-27 23:39:57,888 - __main__ - INFO - ...
+2025-09-27 23:39:57,889 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:39:58,139 - src.utils - INFO - 1000
+2025-09-27 23:39:58,140 - __main__ - INFO -
+2025-09-27 23:39:58,145 - src.reports - INFO - reports/spending_by_category_20250927_233958.json
+2025-09-27 23:39:58,148 - src.reports - INFO - reports/spending_by_weekday_20250927_233958.json
+2025-09-27 23:39:58,150 - src.reports - INFO - reports/spending_by_workday_20250927_233958.json
+2025-09-27 23:39:58,151 - src.reports - INFO - reports/monthly_summary_20250927_233958.json
+2025-09-27 23:40:18,779 - __main__ - INFO - ...
+2025-09-27 23:40:18,779 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:40:19,028 - src.utils - INFO - 1000
+2025-09-27 23:40:19,028 - __main__ - INFO -
+2025-09-27 23:40:19,029 - src.services - INFO - profitable_cashback_categories
+2025-09-27 23:40:19,031 - src.services - WARNING - 9/2025
+2025-09-27 23:40:19,031 - src.services - INFO - profitable_cashback_categories
+2025-09-27 23:40:19,038 - src.services - INFO - investment_bank
+2025-09-27 23:40:19,038 - src.services - WARNING -
+2025-09-27 23:40:19,038 - src.services - WARNING -
+2025-09-27 23:40:19,038 - src.services - WARNING -
+2025-09-27 23:40:19,038 - src.services - WARNING -
+2025-09-27 23:40:19,038 - src.services - WARNING -
+2025-09-27 23:40:19,038 - src.services - WARNING -
+2025-09-27 23:40:19,038 - src.services - WARNING -
+2025-09-27 23:40:19,038 - src.services - WARNING -
+2025-09-27 23:40:19,039 - src.services - WARNING -
+2025-09-27 23:40:19,039 - src.services - WARNING -
+2025-09-27 23:40:19,039 - src.services - WARNING -
+2025-09-27 23:40:19,039 - src.services - WARNING -
+2025-09-27 23:40:19,039 - src.services - WARNING -
+2025-09-27 23:40:19,039 - src.services - WARNING -
+2025-09-27 23:40:19,039 - src.services - WARNING -
+2025-09-27 23:40:19,039 - src.services - WARNING -
+2025-09-27 23:40:19,039 - src.services - WARNING -
+2025-09-27 23:40:19,039 - src.services - WARNING -
+2025-09-27 23:40:19,039 - src.services - WARNING -
+2025-09-27 23:40:19,039 - src.services - WARNING -
+2025-09-27 23:40:19,039 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,040 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,041 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,042 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,043 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,044 - src.services - WARNING -
+2025-09-27 23:40:19,045 - src.services - WARNING -
+2025-09-27 23:40:19,045 - src.services - WARNING -
+2025-09-27 23:40:19,045 - src.services - WARNING -
+2025-09-27 23:40:19,045 - src.services - WARNING -
+2025-09-27 23:40:19,045 - src.services - WARNING -
+2025-09-27 23:40:19,045 - src.services - WARNING -
+2025-09-27 23:40:19,045 - src.services - WARNING -
+2025-09-27 23:40:19,045 - src.services - WARNING -
+2025-09-27 23:40:19,045 - src.services - WARNING -
+2025-09-27 23:40:19,045 - src.services - WARNING -
+2025-09-27 23:40:19,045 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,046 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,047 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,048 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,049 - src.services - WARNING -
+2025-09-27 23:40:19,050 - src.services - WARNING -
+2025-09-27 23:40:19,050 - src.services - WARNING -
+2025-09-27 23:40:19,050 - src.services - WARNING -
+2025-09-27 23:40:19,050 - src.services - WARNING -
+2025-09-27 23:40:19,050 - src.services - WARNING -
+2025-09-27 23:40:19,050 - src.services - WARNING -
+2025-09-27 23:40:19,050 - src.services - WARNING -
+2025-09-27 23:40:19,050 - src.services - WARNING -
+2025-09-27 23:40:19,050 - src.services - WARNING -
+2025-09-27 23:40:19,050 - src.services - WARNING -
+2025-09-27 23:40:19,050 - src.services - WARNING -
+2025-09-27 23:40:19,050 - src.services - WARNING -
+2025-09-27 23:40:19,050 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,051 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,052 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,053 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,054 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,055 - src.services - WARNING -
+2025-09-27 23:40:19,056 - src.services - WARNING -
+2025-09-27 23:40:19,056 - src.services - WARNING -
+2025-09-27 23:40:19,056 - src.services - WARNING -
+2025-09-27 23:40:19,056 - src.services - WARNING -
+2025-09-27 23:40:19,056 - src.services - WARNING -
+2025-09-27 23:40:19,056 - src.services - WARNING -
+2025-09-27 23:40:19,056 - src.services - WARNING -
+2025-09-27 23:40:19,056 - src.services - WARNING -
+2025-09-27 23:40:19,056 - src.services - WARNING -
+2025-09-27 23:40:19,056 - src.services - WARNING -
+2025-09-27 23:40:19,056 - src.services - WARNING -
+2025-09-27 23:40:19,056 - src.services - WARNING -
+2025-09-27 23:40:19,056 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,057 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,058 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,059 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,060 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,061 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,062 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,063 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,064 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,065 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,066 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,067 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,068 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,069 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,070 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,071 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,072 - src.services - WARNING -
+2025-09-27 23:40:19,073 - src.services - WARNING -
+2025-09-27 23:40:19,073 - src.services - WARNING -
+2025-09-27 23:40:19,073 - src.services - WARNING -
+2025-09-27 23:40:19,073 - src.services - WARNING -
+2025-09-27 23:40:19,073 - src.services - WARNING -
+2025-09-27 23:40:19,073 - src.services - WARNING -
+2025-09-27 23:40:19,073 - src.services - WARNING -
+2025-09-27 23:40:19,073 - src.services - WARNING -
+2025-09-27 23:40:19,073 - src.services - WARNING -
+2025-09-27 23:40:19,073 - src.services - WARNING -
+2025-09-27 23:40:19,073 - src.services - WARNING -
+2025-09-27 23:40:19,073 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,074 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,075 - src.services - WARNING -
+2025-09-27 23:40:19,076 - src.services - WARNING -
+2025-09-27 23:40:19,076 - src.services - WARNING -
+2025-09-27 23:40:19,076 - src.services - WARNING -
+2025-09-27 23:40:19,076 - src.services - WARNING -
+2025-09-27 23:40:19,076 - src.services - WARNING -
+2025-09-27 23:40:19,076 - src.services - WARNING -
+2025-09-27 23:40:19,076 - src.services - WARNING -
+2025-09-27 23:40:19,076 - src.services - WARNING -
+2025-09-27 23:40:19,076 - src.services - WARNING -
+2025-09-27 23:40:19,076 - src.services - WARNING -
+2025-09-27 23:40:19,076 - src.services - WARNING -
+2025-09-27 23:40:19,076 - src.services - WARNING -
+2025-09-27 23:40:19,076 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,077 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,078 - src.services - WARNING -
+2025-09-27 23:40:19,079 - src.services - WARNING -
+2025-09-27 23:40:19,079 - src.services - WARNING -
+2025-09-27 23:40:19,079 - src.services - WARNING -
+2025-09-27 23:40:19,079 - src.services - WARNING -
+2025-09-27 23:40:19,079 - src.services - WARNING -
+2025-09-27 23:40:19,079 - src.services - WARNING -
+2025-09-27 23:40:19,079 - src.services - WARNING -
+2025-09-27 23:40:19,079 - src.services - WARNING -
+2025-09-27 23:40:19,079 - src.services - WARNING -
+2025-09-27 23:40:19,079 - src.services - WARNING -
+2025-09-27 23:40:19,079 - src.services - WARNING -
+2025-09-27 23:40:19,079 - src.services - WARNING -
+2025-09-27 23:40:19,079 - src.services - WARNING -
+2025-09-27 23:40:19,080 - src.services - WARNING -
+2025-09-27 23:40:19,080 - src.services - WARNING -
+2025-09-27 23:40:19,080 - src.services - WARNING -
+2025-09-27 23:40:19,080 - src.services - WARNING -
+2025-09-27 23:40:19,080 - src.services - WARNING -
+2025-09-27 23:40:19,080 - src.services - WARNING -
+2025-09-27 23:40:19,080 - src.services - WARNING -
+2025-09-27 23:40:19,080 - src.services - WARNING -
+2025-09-27 23:40:19,080 - src.services - WARNING -
+2025-09-27 23:40:19,080 - src.services - WARNING -
+2025-09-27 23:40:19,080 - src.services - WARNING -
+2025-09-27 23:40:19,080 - src.services - WARNING -
+2025-09-27 23:40:19,080 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,081 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,082 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,083 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,084 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,085 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,086 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,087 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,088 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,089 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,090 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,091 - src.services - WARNING -
+2025-09-27 23:40:19,092 - src.services - WARNING -
+2025-09-27 23:40:19,092 - src.services - WARNING -
+2025-09-27 23:40:19,092 - src.services - WARNING -
+2025-09-27 23:40:19,092 - src.services - WARNING -
+2025-09-27 23:40:19,092 - src.services - WARNING -
+2025-09-27 23:40:19,092 - src.services - WARNING -
+2025-09-27 23:40:19,092 - src.services - WARNING -
+2025-09-27 23:40:19,092 - src.services - WARNING -
+2025-09-27 23:40:19,092 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,093 - src.services - WARNING -
+2025-09-27 23:40:19,094 - src.services - WARNING -
+2025-09-27 23:40:19,094 - src.services - WARNING -
+2025-09-27 23:40:19,094 - src.services - WARNING -
+2025-09-27 23:40:19,094 - src.services - WARNING -
+2025-09-27 23:40:19,094 - src.services - WARNING -
+2025-09-27 23:40:19,094 - src.services - WARNING -
+2025-09-27 23:40:19,094 - src.services - WARNING -
+2025-09-27 23:40:19,094 - src.services - WARNING -
+2025-09-27 23:40:19,094 - src.services - WARNING -
+2025-09-27 23:40:19,094 - src.services - WARNING -
+2025-09-27 23:40:19,094 - src.services - WARNING -
+2025-09-27 23:40:19,094 - src.services - WARNING -
+2025-09-27 23:40:19,094 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,095 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,096 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,097 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,098 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,099 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,100 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,101 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,102 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,103 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,104 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,105 - src.services - WARNING -
+2025-09-27 23:40:19,106 - src.services - WARNING -
+2025-09-27 23:40:19,106 - src.services - WARNING -
+2025-09-27 23:40:19,106 - src.services - WARNING -
+2025-09-27 23:40:19,106 - src.services - WARNING -
+2025-09-27 23:40:19,106 - src.services - WARNING -
+2025-09-27 23:40:19,106 - src.services - WARNING -
+2025-09-27 23:40:19,106 - src.services - WARNING -
+2025-09-27 23:40:19,106 - src.services - WARNING -
+2025-09-27 23:40:19,106 - src.services - WARNING -
+2025-09-27 23:40:19,106 - src.services - WARNING -
+2025-09-27 23:40:19,106 - src.services - WARNING -
+2025-09-27 23:40:19,106 - src.services - WARNING -
+2025-09-27 23:40:19,106 - src.services - INFO - investment_bank
+2025-09-27 23:45:29,262 - __main__ - INFO - ...
+2025-09-27 23:45:29,262 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:45:29,513 - src.utils - INFO - 1000
+2025-09-27 23:45:29,514 - __main__ - INFO -
+2025-09-27 23:45:29,741 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:45:29,871 - src.utils - INFO - 1000
+2025-09-27 23:45:29,873 - src.utils - INFO - 0 2025-09-01 - 2025-09-27
+2025-09-27 23:45:31,924 - src.api_client - INFO - Using fallback stock prices
+2025-09-27 23:45:31,926 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:45:32,054 - src.utils - INFO - 1000
+2025-09-27 23:45:32,055 - src.utils - INFO - 0 2025-09-01 - 2025-09-27
+2025-09-27 23:45:32,056 - src.api_client - INFO - Using cached currency rates
+2025-09-27 23:45:33,406 - src.api_client - INFO - Using fallback stock prices
+2025-09-28 00:43:06,698 - __main__ - INFO - ...
+2025-09-28 00:43:06,699 - src.utils - INFO - data/operations.xlsx
+2025-09-28 00:43:06,949 - src.utils - INFO - 1000
+2025-09-28 00:43:06,949 - __main__ - INFO -
+2025-09-28 00:43:06,952 - src.reports - INFO - reports/spending_by_category_20250928_004306.json
+2025-09-28 00:43:06,954 - src.reports - INFO - reports/spending_by_weekday_20250928_004306.json
+2025-09-28 00:43:06,956 - src.reports - INFO - reports/spending_by_workday_20250928_004306.json
+2025-09-28 00:43:06,957 - src.reports - INFO - reports/monthly_summary_20250928_004306.json
+2025-09-28 00:48:04,163 - __main__ - INFO - ...
+2025-09-28 00:48:04,163 - src.utils - INFO - data/operations.xlsx
+2025-09-28 00:48:04,422 - src.utils - INFO - 1000
+2025-09-28 00:48:04,423 - __main__ - INFO -
+2025-09-28 00:48:04,427 - src.reports - INFO - reports/spending_by_category_20250928_004804.json
+2025-09-28 00:48:04,429 - src.reports - INFO - reports/spending_by_weekday_20250928_004804.json
+2025-09-28 00:48:04,431 - src.reports - INFO - reports/spending_by_workday_20250928_004804.json
+2025-09-28 00:48:04,432 - src.reports - INFO - reports/monthly_summary_20250928_004804.json
+2025-09-28 00:49:29,974 - __main__ - INFO - ...
+2025-09-28 00:49:29,975 - src.utils - INFO - data/operations.xlsx
+2025-09-28 00:49:30,227 - src.utils - INFO - 1000
+2025-09-28 00:49:30,228 - __main__ - INFO -
+2025-09-28 00:49:30,470 - src.utils - INFO - data/operations.xlsx
+2025-09-28 00:49:30,596 - src.utils - INFO - 1000
+2025-09-28 00:49:30,598 - src.utils - INFO - 0 2025-09-01 - 2025-09-28
+2025-09-28 00:49:32,451 - src.api_client - INFO - Using fallback stock prices
+2025-09-28 00:49:32,452 - src.utils - INFO - data/operations.xlsx
+2025-09-28 00:49:32,585 - src.utils - INFO - 1000
+2025-09-28 00:49:32,586 - src.utils - INFO - 0 2025-09-01 - 2025-09-28
+2025-09-28 00:49:32,587 - src.api_client - INFO - Using cached currency rates
+2025-09-28 00:49:33,863 - src.api_client - INFO - Using fallback stock prices
+2025-09-28 00:51:13,698 - __main__ - INFO - ...
+2025-09-28 00:51:13,699 - src.utils - INFO - data/operations.xlsx
+2025-09-28 00:51:13,953 - src.utils - INFO - 1000
+2025-09-28 00:51:13,953 - __main__ - INFO -
+2025-09-28 00:51:13,960 - src.services - INFO - simple_search
+2025-09-28 00:51:13,961 - src.services - INFO - simple_search
+2025-09-28 00:51:31,514 - __main__ - INFO - ...
+2025-09-28 00:51:31,514 - src.utils - INFO - data/operations.xlsx
+2025-09-28 00:51:31,759 - src.utils - INFO - 1000
+2025-09-28 00:51:31,759 - __main__ - INFO -
+2025-09-28 00:51:31,760 - src.services - INFO - profitable_cashback_categories
+2025-09-28 00:51:31,762 - src.services - WARNING - 9/2025
+2025-09-28 00:51:31,762 - src.services - INFO - profitable_cashback_categories
+2025-09-28 00:51:31,767 - src.services - INFO - investment_bank
+2025-09-28 00:51:31,768 - src.services - WARNING -
+2025-09-28 00:51:31,768 - src.services - WARNING -
+2025-09-28 00:51:31,768 - src.services - WARNING -
+2025-09-28 00:51:31,768 - src.services - WARNING -
+2025-09-28 00:51:31,768 - src.services - WARNING -
+2025-09-28 00:51:31,769 - src.services - WARNING -
+2025-09-28 00:51:31,769 - src.services - WARNING -
+2025-09-28 00:51:31,769 - src.services - WARNING -
+2025-09-28 00:51:31,769 - src.services - WARNING -
+2025-09-28 00:51:31,769 - src.services - WARNING -
+2025-09-28 00:51:31,769 - src.services - WARNING -
+2025-09-28 00:51:31,769 - src.services - WARNING -
+2025-09-28 00:51:31,769 - src.services - WARNING -
+2025-09-28 00:51:31,769 - src.services - WARNING -
+2025-09-28 00:51:31,769 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,770 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,771 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,772 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,773 - src.services - WARNING -
+2025-09-28 00:51:31,774 - src.services - WARNING -
+2025-09-28 00:51:31,774 - src.services - WARNING -
+2025-09-28 00:51:31,774 - src.services - WARNING -
+2025-09-28 00:51:31,774 - src.services - WARNING -
+2025-09-28 00:51:31,774 - src.services - WARNING -
+2025-09-28 00:51:31,774 - src.services - WARNING -
+2025-09-28 00:51:31,774 - src.services - WARNING -
+2025-09-28 00:51:31,774 - src.services - WARNING -
+2025-09-28 00:51:31,774 - src.services - WARNING -
+2025-09-28 00:51:31,775 - src.services - WARNING -
+2025-09-28 00:51:31,775 - src.services - WARNING -
+2025-09-28 00:51:31,775 - src.services - WARNING -
+2025-09-28 00:51:31,775 - src.services - WARNING -
+2025-09-28 00:51:31,775 - src.services - WARNING -
+2025-09-28 00:51:31,775 - src.services - WARNING -
+2025-09-28 00:51:31,775 - src.services - WARNING -
+2025-09-28 00:51:31,775 - src.services - WARNING -
+2025-09-28 00:51:31,775 - src.services - WARNING -
+2025-09-28 00:51:31,775 - src.services - WARNING -
+2025-09-28 00:51:31,775 - src.services - WARNING -
+2025-09-28 00:51:31,775 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,776 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,777 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,778 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,779 - src.services - WARNING -
+2025-09-28 00:51:31,780 - src.services - WARNING -
+2025-09-28 00:51:31,780 - src.services - WARNING -
+2025-09-28 00:51:31,780 - src.services - WARNING -
+2025-09-28 00:51:31,780 - src.services - WARNING -
+2025-09-28 00:51:31,780 - src.services - WARNING -
+2025-09-28 00:51:31,780 - src.services - WARNING -
+2025-09-28 00:51:31,780 - src.services - WARNING -
+2025-09-28 00:51:31,780 - src.services - WARNING -
+2025-09-28 00:51:31,780 - src.services - WARNING -
+2025-09-28 00:51:31,780 - src.services - WARNING -
+2025-09-28 00:51:31,780 - src.services - WARNING -
+2025-09-28 00:51:31,780 - src.services - WARNING -
+2025-09-28 00:51:31,780 - src.services - WARNING -
+2025-09-28 00:51:31,781 - src.services - WARNING -
+2025-09-28 00:51:31,781 - src.services - WARNING -
+2025-09-28 00:51:31,781 - src.services - WARNING -
+2025-09-28 00:51:31,781 - src.services - WARNING -
+2025-09-28 00:51:31,781 - src.services - WARNING -
+2025-09-28 00:51:31,781 - src.services - WARNING -
+2025-09-28 00:51:31,781 - src.services - WARNING -
+2025-09-28 00:51:31,781 - src.services - WARNING -
+2025-09-28 00:51:31,781 - src.services - WARNING -
+2025-09-28 00:51:31,781 - src.services - WARNING -
+2025-09-28 00:51:31,781 - src.services - WARNING -
+2025-09-28 00:51:31,781 - src.services - WARNING -
+2025-09-28 00:51:31,781 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,782 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,783 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,784 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,785 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,786 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,787 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,788 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,789 - src.services - WARNING -
+2025-09-28 00:51:31,790 - src.services - WARNING -
+2025-09-28 00:51:31,790 - src.services - WARNING -
+2025-09-28 00:51:31,790 - src.services - WARNING -
+2025-09-28 00:51:31,790 - src.services - WARNING -
+2025-09-28 00:51:31,790 - src.services - WARNING -
+2025-09-28 00:51:31,790 - src.services - WARNING -
+2025-09-28 00:51:31,790 - src.services - WARNING -
+2025-09-28 00:51:31,790 - src.services - WARNING -
+2025-09-28 00:51:31,791 - src.services - WARNING -
+2025-09-28 00:51:31,791 - src.services - WARNING -
+2025-09-28 00:51:31,791 - src.services - WARNING -
+2025-09-28 00:51:31,791 - src.services - WARNING -
+2025-09-28 00:51:31,791 - src.services - WARNING -
+2025-09-28 00:51:31,791 - src.services - WARNING -
+2025-09-28 00:51:31,791 - src.services - WARNING -
+2025-09-28 00:51:31,791 - src.services - WARNING -
+2025-09-28 00:51:31,791 - src.services - WARNING -
+2025-09-28 00:51:31,792 - src.services - WARNING -
+2025-09-28 00:51:31,792 - src.services - WARNING -
+2025-09-28 00:51:31,792 - src.services - WARNING -
+2025-09-28 00:51:31,792 - src.services - WARNING -
+2025-09-28 00:51:31,792 - src.services - WARNING -
+2025-09-28 00:51:31,792 - src.services - WARNING -
+2025-09-28 00:51:31,792 - src.services - WARNING -
+2025-09-28 00:51:31,792 - src.services - WARNING -
+2025-09-28 00:51:31,792 - src.services - WARNING -
+2025-09-28 00:51:31,792 - src.services - WARNING -
+2025-09-28 00:51:31,792 - src.services - WARNING -
+2025-09-28 00:51:31,792 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,793 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,794 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,795 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,796 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,797 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,798 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,799 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,800 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,801 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,802 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,803 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,804 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,805 - src.services - WARNING -
+2025-09-28 00:51:31,806 - src.services - WARNING -
+2025-09-28 00:51:31,806 - src.services - WARNING -
+2025-09-28 00:51:31,806 - src.services - WARNING -
+2025-09-28 00:51:31,806 - src.services - WARNING -
+2025-09-28 00:51:31,806 - src.services - WARNING -
+2025-09-28 00:51:31,806 - src.services - WARNING -
+2025-09-28 00:51:31,806 - src.services - WARNING -
+2025-09-28 00:51:31,806 - src.services - WARNING -
+2025-09-28 00:51:31,806 - src.services - WARNING -
+2025-09-28 00:51:31,807 - src.services - WARNING -
+2025-09-28 00:51:31,807 - src.services - WARNING -
+2025-09-28 00:51:31,807 - src.services - WARNING -
+2025-09-28 00:51:31,807 - src.services - WARNING -
+2025-09-28 00:51:31,807 - src.services - WARNING -
+2025-09-28 00:51:31,807 - src.services - WARNING -
+2025-09-28 00:51:31,807 - src.services - WARNING -
+2025-09-28 00:51:31,807 - src.services - WARNING -
+2025-09-28 00:51:31,807 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,808 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,809 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,810 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,811 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,812 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,813 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,814 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,815 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,816 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,817 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,818 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,819 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,820 - src.services - WARNING -
+2025-09-28 00:51:31,821 - src.services - WARNING -
+2025-09-28 00:51:31,821 - src.services - WARNING -
+2025-09-28 00:51:31,821 - src.services - WARNING -
+2025-09-28 00:51:31,821 - src.services - WARNING -
+2025-09-28 00:51:31,821 - src.services - WARNING -
+2025-09-28 00:51:31,821 - src.services - WARNING -
+2025-09-28 00:51:31,821 - src.services - WARNING -
+2025-09-28 00:51:31,821 - src.services - WARNING -
+2025-09-28 00:51:31,821 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,822 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,823 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,824 - src.services - WARNING -
+2025-09-28 00:51:31,825 - src.services - WARNING -
+2025-09-28 00:51:31,825 - src.services - WARNING -
+2025-09-28 00:51:31,825 - src.services - WARNING -
+2025-09-28 00:51:31,825 - src.services - WARNING -
+2025-09-28 00:51:31,825 - src.services - WARNING -
+2025-09-28 00:51:31,825 - src.services - WARNING -
+2025-09-28 00:51:31,825 - src.services - WARNING -
+2025-09-28 00:51:31,825 - src.services - WARNING -
+2025-09-28 00:51:31,825 - src.services - WARNING -
+2025-09-28 00:51:31,825 - src.services - WARNING -
+2025-09-28 00:51:31,825 - src.services - WARNING -
+2025-09-28 00:51:31,826 - src.services - WARNING -
+2025-09-28 00:51:31,826 - src.services - WARNING -
+2025-09-28 00:51:31,826 - src.services - WARNING -
+2025-09-28 00:51:31,826 - src.services - WARNING -
+2025-09-28 00:51:31,826 - src.services - WARNING -
+2025-09-28 00:51:31,826 - src.services - WARNING -
+2025-09-28 00:51:31,826 - src.services - WARNING -
+2025-09-28 00:51:31,826 - src.services - WARNING -
+2025-09-28 00:51:31,826 - src.services - WARNING -
+2025-09-28 00:51:31,826 - src.services - WARNING -
+2025-09-28 00:51:31,826 - src.services - WARNING -
+2025-09-28 00:51:31,826 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,827 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,828 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,829 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,830 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,831 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,832 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,833 - src.services - WARNING -
+2025-09-28 00:51:31,834 - src.services - WARNING -
+2025-09-28 00:51:31,834 - src.services - WARNING -
+2025-09-28 00:51:31,834 - src.services - WARNING -
+2025-09-28 00:51:31,834 - src.services - WARNING -
+2025-09-28 00:51:31,834 - src.services - WARNING -
+2025-09-28 00:51:31,834 - src.services - WARNING -
+2025-09-28 00:51:31,834 - src.services - WARNING -
+2025-09-28 00:51:31,834 - src.services - WARNING -
+2025-09-28 00:51:31,834 - src.services - WARNING -
+2025-09-28 00:51:31,834 - src.services - WARNING -
+2025-09-28 00:51:31,834 - src.services - WARNING -
+2025-09-28 00:51:31,834 - src.services - WARNING -
+2025-09-28 00:51:31,834 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,835 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,836 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,837 - src.services - WARNING -
+2025-09-28 00:51:31,838 - src.services - WARNING -
+2025-09-28 00:51:31,838 - src.services - WARNING -
+2025-09-28 00:51:31,838 - src.services - WARNING -
+2025-09-28 00:51:31,838 - src.services - WARNING -
+2025-09-28 00:51:31,838 - src.services - WARNING -
+2025-09-28 00:51:31,838 - src.services - WARNING -
+2025-09-28 00:51:31,838 - src.services - INFO - investment_bank
diff --git a/poetry.lock b/poetry.lock
index 56365a4..bc5a275 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,5 +1,235 @@
# This file is automatically @generated by Poetry 2.2.0 and should not be changed by hand.
+[[package]]
+name = "aiohappyeyeballs"
+version = "2.6.1"
+description = "Happy Eyeballs for asyncio"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"},
+ {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"},
+]
+
+[[package]]
+name = "aiohttp"
+version = "3.12.15"
+description = "Async http client/server framework (asyncio)"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc"},
+ {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af"},
+ {file = "aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421"},
+ {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79"},
+ {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77"},
+ {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c"},
+ {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4"},
+ {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6"},
+ {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2"},
+ {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d"},
+ {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb"},
+ {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5"},
+ {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b"},
+ {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065"},
+ {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1"},
+ {file = "aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a"},
+ {file = "aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830"},
+ {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117"},
+ {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe"},
+ {file = "aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9"},
+ {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5"},
+ {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728"},
+ {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16"},
+ {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0"},
+ {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b"},
+ {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd"},
+ {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8"},
+ {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50"},
+ {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676"},
+ {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7"},
+ {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7"},
+ {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685"},
+ {file = "aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b"},
+ {file = "aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d"},
+ {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7"},
+ {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444"},
+ {file = "aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d"},
+ {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c"},
+ {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0"},
+ {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab"},
+ {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb"},
+ {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545"},
+ {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c"},
+ {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd"},
+ {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f"},
+ {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d"},
+ {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519"},
+ {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea"},
+ {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3"},
+ {file = "aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1"},
+ {file = "aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34"},
+ {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315"},
+ {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd"},
+ {file = "aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4"},
+ {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7"},
+ {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d"},
+ {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b"},
+ {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d"},
+ {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d"},
+ {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645"},
+ {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461"},
+ {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9"},
+ {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d"},
+ {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693"},
+ {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64"},
+ {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51"},
+ {file = "aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0"},
+ {file = "aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84"},
+ {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:691d203c2bdf4f4637792efbbcdcd157ae11e55eaeb5e9c360c1206fb03d4d98"},
+ {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e995e1abc4ed2a454c731385bf4082be06f875822adc4c6d9eaadf96e20d406"},
+ {file = "aiohttp-3.12.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bd44d5936ab3193c617bfd6c9a7d8d1085a8dc8c3f44d5f1dcf554d17d04cf7d"},
+ {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46749be6e89cd78d6068cdf7da51dbcfa4321147ab8e4116ee6678d9a056a0cf"},
+ {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c643f4d75adea39e92c0f01b3fb83d57abdec8c9279b3078b68a3a52b3933b6"},
+ {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a23918fedc05806966a2438489dcffccbdf83e921a1170773b6178d04ade142"},
+ {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74bdd8c864b36c3673741023343565d95bfbd778ffe1eb4d412c135a28a8dc89"},
+ {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a146708808c9b7a988a4af3821379e379e0f0e5e466ca31a73dbdd0325b0263"},
+ {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7011a70b56facde58d6d26da4fec3280cc8e2a78c714c96b7a01a87930a9530"},
+ {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3bdd6e17e16e1dbd3db74d7f989e8af29c4d2e025f9828e6ef45fbdee158ec75"},
+ {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57d16590a351dfc914670bd72530fd78344b885a00b250e992faea565b7fdc05"},
+ {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bc9a0f6569ff990e0bbd75506c8d8fe7214c8f6579cca32f0546e54372a3bb54"},
+ {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:536ad7234747a37e50e7b6794ea868833d5220b49c92806ae2d7e8a9d6b5de02"},
+ {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f0adb4177fa748072546fb650d9bd7398caaf0e15b370ed3317280b13f4083b0"},
+ {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14954a2988feae3987f1eb49c706bff39947605f4b6fa4027c1d75743723eb09"},
+ {file = "aiohttp-3.12.15-cp39-cp39-win32.whl", hash = "sha256:b784d6ed757f27574dca1c336f968f4e81130b27595e458e69457e6878251f5d"},
+ {file = "aiohttp-3.12.15-cp39-cp39-win_amd64.whl", hash = "sha256:86ceded4e78a992f835209e236617bffae649371c4a50d5e5a3987f237db84b8"},
+ {file = "aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2"},
+]
+
+[package.dependencies]
+aiohappyeyeballs = ">=2.5.0"
+aiosignal = ">=1.4.0"
+async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""}
+attrs = ">=17.3.0"
+frozenlist = ">=1.1.1"
+multidict = ">=4.5,<7.0"
+propcache = ">=0.2.0"
+yarl = ">=1.17.0,<2.0"
+
+[package.extras]
+speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""]
+
+[[package]]
+name = "aiosignal"
+version = "1.4.0"
+description = "aiosignal: a list of registered asynchronous callbacks"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"},
+ {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"},
+]
+
+[package.dependencies]
+frozenlist = ">=1.1.0"
+typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""}
+
+[[package]]
+name = "async-timeout"
+version = "5.0.1"
+description = "Timeout context manager for asyncio programs"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version < \"3.11\""
+files = [
+ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
+ {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
+]
+
+[[package]]
+name = "attrs"
+version = "25.3.0"
+description = "Classes Without Boilerplate"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"},
+ {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"},
+]
+
+[package.extras]
+benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
+cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
+dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
+docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"]
+tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
+tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""]
+
+[[package]]
+name = "black"
+version = "25.9.0"
+description = "The uncompromising code formatter."
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"},
+ {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"},
+ {file = "black-25.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96b6726d690c96c60ba682955199f8c39abc1ae0c3a494a9c62c0184049a713"},
+ {file = "black-25.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:d119957b37cc641596063cd7db2656c5be3752ac17877017b2ffcdb9dfc4d2b1"},
+ {file = "black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa"},
+ {file = "black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d"},
+ {file = "black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608"},
+ {file = "black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f"},
+ {file = "black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0"},
+ {file = "black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4"},
+ {file = "black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e"},
+ {file = "black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a"},
+ {file = "black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175"},
+ {file = "black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f"},
+ {file = "black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831"},
+ {file = "black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357"},
+ {file = "black-25.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef69351df3c84485a8beb6f7b8f9721e2009e20ef80a8d619e2d1788b7816d47"},
+ {file = "black-25.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3c1f4cd5e93842774d9ee4ef6cd8d17790e65f44f7cdbaab5f2cf8ccf22a823"},
+ {file = "black-25.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:154b06d618233fe468236ba1f0e40823d4eb08b26f5e9261526fde34916b9140"},
+ {file = "black-25.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e593466de7b998374ea2585a471ba90553283fb9beefcfa430d84a2651ed5933"},
+ {file = "black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae"},
+ {file = "black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619"},
+]
+
+[package.dependencies]
+click = ">=8.0.0"
+mypy-extensions = ">=0.4.3"
+packaging = ">=22.0"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+pytokens = ">=0.1.10"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.10)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2)"]
+
+[[package]]
+name = "cachetools"
+version = "6.2.0"
+description = "Extensible memoizing collections and decorators"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "cachetools-6.2.0-py3-none-any.whl", hash = "sha256:1c76a8960c0041fcc21097e357f882197c79da0dbff766e7317890a65d7d8ba6"},
+ {file = "cachetools-6.2.0.tar.gz", hash = "sha256:38b328c0889450f05f5e120f56ab68c8abaf424e1275522b138ffc93253f7e32"},
+]
+
[[package]]
name = "certifi"
version = "2025.8.3"
@@ -101,6 +331,171 @@ files = [
{file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"},
]
+[[package]]
+name = "click"
+version = "8.1.8"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.7"
+groups = ["main", "dev"]
+markers = "python_version < \"3.11\""
+files = [
+ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
+ {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "click"
+version = "8.3.0"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.10"
+groups = ["main", "dev"]
+markers = "python_version >= \"3.11\""
+files = [
+ {file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"},
+ {file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["main", "dev"]
+markers = "sys_platform == \"win32\" or platform_system == \"Windows\""
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "coverage"
+version = "7.10.7"
+description = "Code coverage measurement for Python"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+ {file = "coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a"},
+ {file = "coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5"},
+ {file = "coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17"},
+ {file = "coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b"},
+ {file = "coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87"},
+ {file = "coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e"},
+ {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e"},
+ {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df"},
+ {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0"},
+ {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13"},
+ {file = "coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b"},
+ {file = "coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807"},
+ {file = "coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59"},
+ {file = "coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a"},
+ {file = "coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699"},
+ {file = "coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d"},
+ {file = "coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e"},
+ {file = "coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23"},
+ {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab"},
+ {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82"},
+ {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2"},
+ {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61"},
+ {file = "coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14"},
+ {file = "coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2"},
+ {file = "coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a"},
+ {file = "coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417"},
+ {file = "coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973"},
+ {file = "coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c"},
+ {file = "coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7"},
+ {file = "coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6"},
+ {file = "coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59"},
+ {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b"},
+ {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a"},
+ {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb"},
+ {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1"},
+ {file = "coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256"},
+ {file = "coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba"},
+ {file = "coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf"},
+ {file = "coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d"},
+ {file = "coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b"},
+ {file = "coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e"},
+ {file = "coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b"},
+ {file = "coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49"},
+ {file = "coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911"},
+ {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0"},
+ {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f"},
+ {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c"},
+ {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f"},
+ {file = "coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698"},
+ {file = "coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843"},
+ {file = "coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546"},
+ {file = "coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c"},
+ {file = "coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15"},
+ {file = "coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4"},
+ {file = "coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0"},
+ {file = "coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0"},
+ {file = "coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65"},
+ {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541"},
+ {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6"},
+ {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999"},
+ {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2"},
+ {file = "coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a"},
+ {file = "coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb"},
+ {file = "coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb"},
+ {file = "coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520"},
+ {file = "coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32"},
+ {file = "coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f"},
+ {file = "coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a"},
+ {file = "coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360"},
+ {file = "coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69"},
+ {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14"},
+ {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe"},
+ {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e"},
+ {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd"},
+ {file = "coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2"},
+ {file = "coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681"},
+ {file = "coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880"},
+ {file = "coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63"},
+ {file = "coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2"},
+ {file = "coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d"},
+ {file = "coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0"},
+ {file = "coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699"},
+ {file = "coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9"},
+ {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f"},
+ {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1"},
+ {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0"},
+ {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399"},
+ {file = "coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235"},
+ {file = "coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d"},
+ {file = "coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a"},
+ {file = "coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3"},
+ {file = "coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c"},
+ {file = "coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396"},
+ {file = "coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40"},
+ {file = "coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594"},
+ {file = "coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a"},
+ {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b"},
+ {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3"},
+ {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0"},
+ {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f"},
+ {file = "coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431"},
+ {file = "coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07"},
+ {file = "coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260"},
+ {file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"},
+]
+
+[package.dependencies]
+tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
+
+[package.extras]
+toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
+
[[package]]
name = "et-xmlfile"
version = "2.0.0"
@@ -113,6 +508,139 @@ files = [
{file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"},
]
+[[package]]
+name = "exceptiongroup"
+version = "1.3.0"
+description = "Backport of PEP 654 (exception groups)"
+optional = false
+python-versions = ">=3.7"
+groups = ["main", "dev"]
+markers = "python_version < \"3.11\""
+files = [
+ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"},
+ {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"},
+]
+
+[package.dependencies]
+typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""}
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "frozenlist"
+version = "1.7.0"
+description = "A list-like structure which implements collections.abc.MutableSequence"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"},
+ {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"},
+ {file = "frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d"},
+ {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e"},
+ {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9"},
+ {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c"},
+ {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981"},
+ {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615"},
+ {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50"},
+ {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa"},
+ {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577"},
+ {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59"},
+ {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e"},
+ {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd"},
+ {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718"},
+ {file = "frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e"},
+ {file = "frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464"},
+ {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a"},
+ {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750"},
+ {file = "frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd"},
+ {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2"},
+ {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f"},
+ {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30"},
+ {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98"},
+ {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86"},
+ {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae"},
+ {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8"},
+ {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31"},
+ {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7"},
+ {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5"},
+ {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898"},
+ {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56"},
+ {file = "frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7"},
+ {file = "frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d"},
+ {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"},
+ {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"},
+ {file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"},
+ {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"},
+ {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"},
+ {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"},
+ {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"},
+ {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"},
+ {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"},
+ {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"},
+ {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"},
+ {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"},
+ {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"},
+ {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"},
+ {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"},
+ {file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"},
+ {file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"},
+ {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"},
+ {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"},
+ {file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"},
+ {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"},
+ {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"},
+ {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"},
+ {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"},
+ {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"},
+ {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"},
+ {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"},
+ {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"},
+ {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"},
+ {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"},
+ {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"},
+ {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"},
+ {file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"},
+ {file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"},
+ {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cea3dbd15aea1341ea2de490574a4a37ca080b2ae24e4b4f4b51b9057b4c3630"},
+ {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d536ee086b23fecc36c2073c371572374ff50ef4db515e4e503925361c24f71"},
+ {file = "frozenlist-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfcebf56f703cb2e346315431699f00db126d158455e513bd14089d992101e44"},
+ {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974c5336e61d6e7eb1ea5b929cb645e882aadab0095c5a6974a111e6479f8878"},
+ {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c70db4a0ab5ab20878432c40563573229a7ed9241506181bba12f6b7d0dc41cb"},
+ {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1137b78384eebaf70560a36b7b229f752fb64d463d38d1304939984d5cb887b6"},
+ {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e793a9f01b3e8b5c0bc646fb59140ce0efcc580d22a3468d70766091beb81b35"},
+ {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74739ba8e4e38221d2c5c03d90a7e542cb8ad681915f4ca8f68d04f810ee0a87"},
+ {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e63344c4e929b1a01e29bc184bbb5fd82954869033765bfe8d65d09e336a677"},
+ {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ea2a7369eb76de2217a842f22087913cdf75f63cf1307b9024ab82dfb525938"},
+ {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:836b42f472a0e006e02499cef9352ce8097f33df43baaba3e0a28a964c26c7d2"},
+ {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e22b9a99741294b2571667c07d9f8cceec07cb92aae5ccda39ea1b6052ed4319"},
+ {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a19e85cc503d958abe5218953df722748d87172f71b73cf3c9257a91b999890"},
+ {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f22dac33bb3ee8fe3e013aa7b91dc12f60d61d05b7fe32191ffa84c3aafe77bd"},
+ {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ccec739a99e4ccf664ea0775149f2749b8a6418eb5b8384b4dc0a7d15d304cb"},
+ {file = "frozenlist-1.7.0-cp39-cp39-win32.whl", hash = "sha256:b3950f11058310008a87757f3eee16a8e1ca97979833239439586857bc25482e"},
+ {file = "frozenlist-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:43a82fce6769c70f2f5a06248b614a7d268080a9d20f7457ef10ecee5af82b63"},
+ {file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"},
+ {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"},
+]
+
[[package]]
name = "idna"
version = "3.10"
@@ -129,108 +657,283 @@ files = [
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
-name = "numpy"
-version = "1.24.4"
-description = "Fundamental package for array computing in Python"
+name = "iniconfig"
+version = "2.1.0"
+description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
+files = [
+ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"},
+ {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
+]
+
+[[package]]
+name = "isort"
+version = "6.0.1"
+description = "A Python utility / library to sort Python imports."
+optional = false
+python-versions = ">=3.9.0"
+groups = ["dev"]
+files = [
+ {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"},
+ {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"},
+]
+
+[package.extras]
+colors = ["colorama"]
+plugins = ["setuptools"]
+
+[[package]]
+name = "multidict"
+version = "6.6.4"
+description = "multidict implementation"
+optional = false
+python-versions = ">=3.9"
groups = ["main"]
-markers = "python_version < \"3.10\""
-files = [
- {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"},
- {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"},
- {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"},
- {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"},
- {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"},
- {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"},
- {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"},
- {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"},
- {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"},
- {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"},
- {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"},
- {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"},
- {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"},
- {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"},
- {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"},
- {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"},
- {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"},
- {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"},
- {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"},
- {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"},
- {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"},
- {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"},
- {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"},
- {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"},
- {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"},
- {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"},
- {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"},
- {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"},
+files = [
+ {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"},
+ {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"},
+ {file = "multidict-6.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0af5f9dee472371e36d6ae38bde009bd8ce65ac7335f55dcc240379d7bed1495"},
+ {file = "multidict-6.6.4-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d24f351e4d759f5054b641c81e8291e5d122af0fca5c72454ff77f7cbe492de8"},
+ {file = "multidict-6.6.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db6a3810eec08280a172a6cd541ff4a5f6a97b161d93ec94e6c4018917deb6b7"},
+ {file = "multidict-6.6.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a1b20a9d56b2d81e2ff52ecc0670d583eaabaa55f402e8d16dd062373dbbe796"},
+ {file = "multidict-6.6.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c9854df0eaa610a23494c32a6f44a3a550fb398b6b51a56e8c6b9b3689578db"},
+ {file = "multidict-6.6.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4bb7627fd7a968f41905a4d6343b0d63244a0623f006e9ed989fa2b78f4438a0"},
+ {file = "multidict-6.6.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caebafea30ed049c57c673d0b36238b1748683be2593965614d7b0e99125c877"},
+ {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ad887a8250eb47d3ab083d2f98db7f48098d13d42eb7a3b67d8a5c795f224ace"},
+ {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ed8358ae7d94ffb7c397cecb62cbac9578a83ecefc1eba27b9090ee910e2efb6"},
+ {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecab51ad2462197a4c000b6d5701fc8585b80eecb90583635d7e327b7b6923eb"},
+ {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c5c97aa666cf70e667dfa5af945424ba1329af5dd988a437efeb3a09430389fb"},
+ {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9a950b7cf54099c1209f455ac5970b1ea81410f2af60ed9eb3c3f14f0bfcf987"},
+ {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:163c7ea522ea9365a8a57832dea7618e6cbdc3cd75f8c627663587459a4e328f"},
+ {file = "multidict-6.6.4-cp310-cp310-win32.whl", hash = "sha256:17d2cbbfa6ff20821396b25890f155f40c986f9cfbce5667759696d83504954f"},
+ {file = "multidict-6.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce9a40fbe52e57e7edf20113a4eaddfacac0561a0879734e636aa6d4bb5e3fb0"},
+ {file = "multidict-6.6.4-cp310-cp310-win_arm64.whl", hash = "sha256:01d0959807a451fe9fdd4da3e139cb5b77f7328baf2140feeaf233e1d777b729"},
+ {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c"},
+ {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb"},
+ {file = "multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e"},
+ {file = "multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded"},
+ {file = "multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683"},
+ {file = "multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a"},
+ {file = "multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9"},
+ {file = "multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50"},
+ {file = "multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52"},
+ {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6"},
+ {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e"},
+ {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3"},
+ {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c"},
+ {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b"},
+ {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f"},
+ {file = "multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2"},
+ {file = "multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e"},
+ {file = "multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf"},
+ {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8"},
+ {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3"},
+ {file = "multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b"},
+ {file = "multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287"},
+ {file = "multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138"},
+ {file = "multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6"},
+ {file = "multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9"},
+ {file = "multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c"},
+ {file = "multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402"},
+ {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7"},
+ {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f"},
+ {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d"},
+ {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7"},
+ {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802"},
+ {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24"},
+ {file = "multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793"},
+ {file = "multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e"},
+ {file = "multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364"},
+ {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e"},
+ {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657"},
+ {file = "multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da"},
+ {file = "multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa"},
+ {file = "multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f"},
+ {file = "multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0"},
+ {file = "multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879"},
+ {file = "multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a"},
+ {file = "multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f"},
+ {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5"},
+ {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438"},
+ {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e"},
+ {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7"},
+ {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812"},
+ {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a"},
+ {file = "multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69"},
+ {file = "multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf"},
+ {file = "multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605"},
+ {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb"},
+ {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e"},
+ {file = "multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f"},
+ {file = "multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773"},
+ {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e"},
+ {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0"},
+ {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395"},
+ {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45"},
+ {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb"},
+ {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5"},
+ {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141"},
+ {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d"},
+ {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d"},
+ {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0"},
+ {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92"},
+ {file = "multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e"},
+ {file = "multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4"},
+ {file = "multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad"},
+ {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:af7618b591bae552b40dbb6f93f5518328a949dac626ee75927bba1ecdeea9f4"},
+ {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b6819f83aef06f560cb15482d619d0e623ce9bf155115150a85ab11b8342a665"},
+ {file = "multidict-6.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d09384e75788861e046330308e7af54dd306aaf20eb760eb1d0de26b2bea2cb"},
+ {file = "multidict-6.6.4-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a59c63061f1a07b861c004e53869eb1211ffd1a4acbca330e3322efa6dd02978"},
+ {file = "multidict-6.6.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350f6b0fe1ced61e778037fdc7613f4051c8baf64b1ee19371b42a3acdb016a0"},
+ {file = "multidict-6.6.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c5cbac6b55ad69cb6aa17ee9343dfbba903118fd530348c330211dc7aa756d1"},
+ {file = "multidict-6.6.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:630f70c32b8066ddfd920350bc236225814ad94dfa493fe1910ee17fe4365cbb"},
+ {file = "multidict-6.6.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8d4916a81697faec6cb724a273bd5457e4c6c43d82b29f9dc02c5542fd21fc9"},
+ {file = "multidict-6.6.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e42332cf8276bb7645d310cdecca93a16920256a5b01bebf747365f86a1675b"},
+ {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f3be27440f7644ab9a13a6fc86f09cdd90b347c3c5e30c6d6d860de822d7cb53"},
+ {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:21f216669109e02ef3e2415ede07f4f8987f00de8cdfa0cc0b3440d42534f9f0"},
+ {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d9890d68c45d1aeac5178ded1d1cccf3bc8d7accf1f976f79bf63099fb16e4bd"},
+ {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:edfdcae97cdc5d1a89477c436b61f472c4d40971774ac4729c613b4b133163cb"},
+ {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0b2e886624be5773e69cf32bcb8534aecdeb38943520b240fed3d5596a430f2f"},
+ {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:be5bf4b3224948032a845d12ab0f69f208293742df96dc14c4ff9b09e508fc17"},
+ {file = "multidict-6.6.4-cp39-cp39-win32.whl", hash = "sha256:10a68a9191f284fe9d501fef4efe93226e74df92ce7a24e301371293bd4918ae"},
+ {file = "multidict-6.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee25f82f53262f9ac93bd7e58e47ea1bdcc3393cef815847e397cba17e284210"},
+ {file = "multidict-6.6.4-cp39-cp39-win_arm64.whl", hash = "sha256:f9867e55590e0855bcec60d4f9a092b69476db64573c9fe17e92b0c50614c16a"},
+ {file = "multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c"},
+ {file = "multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd"},
+]
+
+[package.dependencies]
+typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""}
+
+[[package]]
+name = "mypy"
+version = "1.18.2"
+description = "Optional static typing for Python"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+ {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"},
+ {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"},
+ {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"},
+ {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"},
+ {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"},
+ {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"},
+ {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"},
+ {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"},
+ {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"},
+ {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"},
+ {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"},
+ {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"},
+ {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"},
+ {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"},
+ {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"},
+ {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"},
+ {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"},
+ {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"},
+ {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"},
+ {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"},
+ {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"},
+ {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"},
+ {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"},
+ {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"},
+ {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"},
+ {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"},
+ {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"},
+ {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"},
+ {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"},
+ {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"},
+ {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"},
+ {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"},
+ {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"},
+ {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"},
+ {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"},
+ {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"},
+ {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"},
+ {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"},
+]
+
+[package.dependencies]
+mypy_extensions = ">=1.0.0"
+pathspec = ">=0.9.0"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing_extensions = ">=4.6.0"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+faster-cache = ["orjson"]
+install-types = ["pip"]
+mypyc = ["setuptools (>=50)"]
+reports = ["lxml"]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+description = "Type system extensions for programs checked with the mypy type checker."
+optional = false
+python-versions = ">=3.8"
+groups = ["main", "dev"]
+files = [
+ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
+ {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
]
[[package]]
name = "numpy"
-version = "2.2.6"
+version = "2.0.2"
description = "Fundamental package for array computing in Python"
optional = false
-python-versions = ">=3.10"
+python-versions = ">=3.9"
groups = ["main"]
-markers = "python_version == \"3.10\""
-files = [
- {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"},
- {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"},
- {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"},
- {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"},
- {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"},
- {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"},
- {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"},
- {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"},
- {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"},
- {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"},
- {file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"},
- {file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"},
- {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"},
- {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"},
- {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"},
- {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"},
- {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"},
- {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"},
- {file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"},
- {file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"},
- {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"},
- {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"},
- {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"},
- {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"},
- {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"},
- {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"},
- {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"},
- {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"},
- {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"},
- {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"},
- {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"},
- {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"},
- {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"},
- {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"},
- {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"},
- {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"},
- {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"},
- {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"},
- {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"},
- {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"},
- {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"},
- {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"},
- {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"},
- {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"},
- {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"},
- {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"},
- {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"},
- {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"},
- {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"},
- {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"},
- {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"},
- {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"},
- {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"},
- {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"},
- {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"},
+markers = "python_version < \"3.11\""
+files = [
+ {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"},
+ {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"},
+ {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66"},
+ {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b"},
+ {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd"},
+ {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318"},
+ {file = "numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8"},
+ {file = "numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326"},
+ {file = "numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97"},
+ {file = "numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131"},
+ {file = "numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448"},
+ {file = "numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195"},
+ {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57"},
+ {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a"},
+ {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669"},
+ {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951"},
+ {file = "numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9"},
+ {file = "numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15"},
+ {file = "numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4"},
+ {file = "numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc"},
+ {file = "numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b"},
+ {file = "numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e"},
+ {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c"},
+ {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c"},
+ {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692"},
+ {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a"},
+ {file = "numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c"},
+ {file = "numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded"},
+ {file = "numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5"},
+ {file = "numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a"},
+ {file = "numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c"},
+ {file = "numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd"},
+ {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b"},
+ {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729"},
+ {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1"},
+ {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd"},
+ {file = "numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d"},
+ {file = "numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d"},
+ {file = "numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa"},
+ {file = "numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73"},
+ {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8"},
+ {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4"},
+ {file = "numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c"},
+ {file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"},
+ {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"},
]
[[package]]
@@ -334,121 +1037,80 @@ files = [
et-xmlfile = "*"
[[package]]
-name = "pandas"
-version = "2.0.3"
-description = "Powerful data structures for data analysis, time series, and statistics"
+name = "packaging"
+version = "25.0"
+description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
-groups = ["main"]
-markers = "python_version < \"3.10\""
-files = [
- {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"},
- {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"},
- {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"},
- {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"},
- {file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"},
- {file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"},
- {file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"},
- {file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"},
- {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"},
- {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"},
- {file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"},
- {file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"},
- {file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"},
- {file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"},
- {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"},
- {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"},
- {file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"},
- {file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"},
- {file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"},
- {file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"},
- {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"},
- {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"},
- {file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"},
- {file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"},
- {file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"},
+groups = ["main", "dev"]
+files = [
+ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
+ {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
]
-[package.dependencies]
-numpy = {version = ">=1.20.3", markers = "python_version < \"3.10\""}
-python-dateutil = ">=2.8.2"
-pytz = ">=2020.1"
-tzdata = ">=2022.1"
-
-[package.extras]
-all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"]
-aws = ["s3fs (>=2021.08.0)"]
-clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"]
-compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"]
-computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"]
-excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"]
-feather = ["pyarrow (>=7.0.0)"]
-fss = ["fsspec (>=2021.07.0)"]
-gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"]
-hdf5 = ["tables (>=3.6.1)"]
-html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"]
-mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"]
-output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"]
-parquet = ["pyarrow (>=7.0.0)"]
-performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"]
-plot = ["matplotlib (>=3.6.1)"]
-postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"]
-spss = ["pyreadstat (>=1.1.2)"]
-sql-other = ["SQLAlchemy (>=1.4.16)"]
-test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"]
-xml = ["lxml (>=4.6.3)"]
-
[[package]]
name = "pandas"
-version = "2.3.2"
+version = "2.3.3"
description = "Powerful data structures for data analysis, time series, and statistics"
optional = false
python-versions = ">=3.9"
groups = ["main"]
-markers = "python_version >= \"3.10\""
-files = [
- {file = "pandas-2.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52bc29a946304c360561974c6542d1dd628ddafa69134a7131fdfd6a5d7a1a35"},
- {file = "pandas-2.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:220cc5c35ffaa764dd5bb17cf42df283b5cb7fdf49e10a7b053a06c9cb48ee2b"},
- {file = "pandas-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c05e15111221384019897df20c6fe893b2f697d03c811ee67ec9e0bb5a3424"},
- {file = "pandas-2.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc03acc273c5515ab69f898df99d9d4f12c4d70dbfc24c3acc6203751d0804cf"},
- {file = "pandas-2.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d25c20a03e8870f6339bcf67281b946bd20b86f1a544ebbebb87e66a8d642cba"},
- {file = "pandas-2.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21bb612d148bb5860b7eb2c10faacf1a810799245afd342cf297d7551513fbb6"},
- {file = "pandas-2.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:b62d586eb25cb8cb70a5746a378fc3194cb7f11ea77170d59f889f5dfe3cec7a"},
- {file = "pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1333e9c299adcbb68ee89a9bb568fc3f20f9cbb419f1dd5225071e6cddb2a743"},
- {file = "pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76972bcbd7de8e91ad5f0ca884a9f2c477a2125354af624e022c49e5bd0dfff4"},
- {file = "pandas-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b98bdd7c456a05eef7cd21fd6b29e3ca243591fe531c62be94a2cc987efb5ac2"},
- {file = "pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d81573b3f7db40d020983f78721e9bfc425f411e616ef019a10ebf597aedb2e"},
- {file = "pandas-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e190b738675a73b581736cc8ec71ae113d6c3768d0bd18bffa5b9a0927b0b6ea"},
- {file = "pandas-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c253828cb08f47488d60f43c5fc95114c771bbfff085da54bfc79cb4f9e3a372"},
- {file = "pandas-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:9467697b8083f9667b212633ad6aa4ab32436dcbaf4cd57325debb0ddef2012f"},
- {file = "pandas-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fbb977f802156e7a3f829e9d1d5398f6192375a3e2d1a9ee0803e35fe70a2b9"},
- {file = "pandas-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b9b52693123dd234b7c985c68b709b0b009f4521000d0525f2b95c22f15944b"},
- {file = "pandas-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bd281310d4f412733f319a5bc552f86d62cddc5f51d2e392c8787335c994175"},
- {file = "pandas-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d31a6b4354e3b9b8a2c848af75d31da390657e3ac6f30c05c82068b9ed79b9"},
- {file = "pandas-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df4df0b9d02bb873a106971bb85d448378ef14b86ba96f035f50bbd3688456b4"},
- {file = "pandas-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:213a5adf93d020b74327cb2c1b842884dbdd37f895f42dcc2f09d451d949f811"},
- {file = "pandas-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c13b81a9347eb8c7548f53fd9a4f08d4dfe996836543f805c987bafa03317ae"},
- {file = "pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e"},
- {file = "pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9"},
- {file = "pandas-2.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0064187b80a5be6f2f9c9d6bdde29372468751dfa89f4211a3c5871854cfbf7a"},
- {file = "pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b"},
- {file = "pandas-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:114c2fe4f4328cf98ce5716d1532f3ab79c5919f95a9cfee81d9140064a2e4d6"},
- {file = "pandas-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:48fa91c4dfb3b2b9bfdb5c24cd3567575f4e13f9636810462ffed8925352be5a"},
- {file = "pandas-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b"},
- {file = "pandas-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c624b615ce97864eb588779ed4046186f967374185c047070545253a52ab2d57"},
- {file = "pandas-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0cee69d583b9b128823d9514171cabb6861e09409af805b54459bd0c821a35c2"},
- {file = "pandas-2.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2319656ed81124982900b4c37f0e0c58c015af9a7bbc62342ba5ad07ace82ba9"},
- {file = "pandas-2.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37205ad6f00d52f16b6d09f406434ba928c1a1966e2771006a9033c736d30d2"},
- {file = "pandas-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:837248b4fc3a9b83b9c6214699a13f069dc13510a6a6d7f9ba33145d2841a012"},
- {file = "pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370"},
- {file = "pandas-2.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88080a0ff8a55eac9c84e3ff3c7665b3b5476c6fbc484775ca1910ce1c3e0b87"},
- {file = "pandas-2.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d4a558c7620340a0931828d8065688b3cc5b4c8eb674bcaf33d18ff4a6870b4a"},
- {file = "pandas-2.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45178cf09d1858a1509dc73ec261bf5b25a625a389b65be2e47b559905f0ab6a"},
- {file = "pandas-2.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77cefe00e1b210f9c76c697fedd8fdb8d3dd86563e9c8adc9fa72b90f5e9e4c2"},
- {file = "pandas-2.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:13bd629c653856f00c53dc495191baa59bcafbbf54860a46ecc50d3a88421a96"},
- {file = "pandas-2.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:36d627906fd44b5fd63c943264e11e96e923f8de77d6016dc2f667b9ad193438"},
- {file = "pandas-2.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:a9d7ec92d71a420185dec44909c32e9a362248c4ae2238234b76d5be37f208cc"},
- {file = "pandas-2.3.2.tar.gz", hash = "sha256:ab7b58f8f82706890924ccdfb5f48002b83d2b5a3845976a9fb705d36c34dcdb"},
+files = [
+ {file = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"},
+ {file = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"},
+ {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1"},
+ {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838"},
+ {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250"},
+ {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4"},
+ {file = "pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826"},
+ {file = "pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523"},
+ {file = "pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45"},
+ {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66"},
+ {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b"},
+ {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791"},
+ {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151"},
+ {file = "pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c"},
+ {file = "pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53"},
+ {file = "pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35"},
+ {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908"},
+ {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89"},
+ {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98"},
+ {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084"},
+ {file = "pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b"},
+ {file = "pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713"},
+ {file = "pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8"},
+ {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d"},
+ {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac"},
+ {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c"},
+ {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493"},
+ {file = "pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee"},
+ {file = "pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5"},
+ {file = "pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21"},
+ {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78"},
+ {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110"},
+ {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86"},
+ {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc"},
+ {file = "pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0"},
+ {file = "pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593"},
+ {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c"},
+ {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b"},
+ {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6"},
+ {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3"},
+ {file = "pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5"},
+ {file = "pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec"},
+ {file = "pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7"},
+ {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450"},
+ {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5"},
+ {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788"},
+ {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87"},
+ {file = "pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2"},
+ {file = "pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8"},
+ {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff"},
+ {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29"},
+ {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73"},
+ {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9"},
+ {file = "pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa"},
+ {file = "pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b"},
]
[package.dependencies]
@@ -486,6 +1148,305 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d
test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
xml = ["lxml (>=4.9.2)"]
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+description = "Utility library for gitignore style pattern matching of file paths."
+optional = false
+python-versions = ">=3.8"
+groups = ["main", "dev"]
+files = [
+ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
+ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.4.0"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"},
+ {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"},
+]
+
+[package.extras]
+docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"]
+type = ["mypy (>=1.14.1)"]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
+ {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["coverage", "pytest", "pytest-benchmark"]
+
+[[package]]
+name = "propcache"
+version = "0.3.2"
+description = "Accelerated property cache"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"},
+ {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"},
+ {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"},
+ {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"},
+ {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"},
+ {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"},
+ {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"},
+ {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"},
+ {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"},
+ {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"},
+ {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"},
+ {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"},
+ {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"},
+ {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"},
+ {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"},
+ {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"},
+ {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"},
+ {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"},
+ {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"},
+ {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"},
+ {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"},
+ {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"},
+ {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"},
+ {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"},
+ {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"},
+ {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"},
+ {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"},
+ {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"},
+ {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"},
+ {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"},
+ {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"},
+ {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"},
+ {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"},
+ {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"},
+ {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"},
+ {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"},
+ {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"},
+ {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"},
+ {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"},
+ {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"},
+ {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"},
+ {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"},
+ {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"},
+ {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"},
+ {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"},
+ {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"},
+ {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"},
+ {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"},
+ {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"},
+ {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"},
+ {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"},
+ {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"},
+ {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"},
+ {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"},
+ {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"},
+ {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"},
+ {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"},
+ {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"},
+ {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"},
+ {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"},
+ {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"},
+ {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"},
+ {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"},
+ {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"},
+ {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"},
+ {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"},
+ {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"},
+ {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"},
+ {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"},
+ {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"},
+ {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"},
+ {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"},
+ {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"},
+ {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"},
+ {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"},
+ {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"},
+ {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"},
+ {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"},
+ {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"},
+ {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"},
+ {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"},
+ {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"},
+ {file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"},
+ {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"},
+ {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"},
+ {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"},
+ {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"},
+ {file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"},
+ {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"},
+ {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"},
+ {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"},
+ {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"},
+ {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"},
+ {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"},
+ {file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"},
+ {file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"},
+ {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"},
+ {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"},
+]
+
+[[package]]
+name = "pydantic"
+version = "1.10.24"
+description = "Data validation and settings management using python type hints"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+ {file = "pydantic-1.10.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef07ea2fba12f9188cfa2c50cb3eaa6516b56c33e2a8cc3cd288b4190ee6c0c"},
+ {file = "pydantic-1.10.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a42033fac69b9f1f867ecc3a2159f0e94dceb1abfc509ad57e9e88d49774683"},
+ {file = "pydantic-1.10.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c626596c1b95dc6d45f7129f10b6743fbb50f29d942d25a22b2ceead670c067d"},
+ {file = "pydantic-1.10.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8057172868b0d98f95e6fcddcc5f75d01570e85c6308702dd2c50ea673bc197b"},
+ {file = "pydantic-1.10.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:82f951210ebcdb778b1d93075af43adcd04e9ebfd4f44b1baa8eeb21fbd71e36"},
+ {file = "pydantic-1.10.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b66e4892d8ae005f436a5c5f1519ecf837574d8414b1c93860fb3c13943d9b37"},
+ {file = "pydantic-1.10.24-cp310-cp310-win_amd64.whl", hash = "sha256:50d9f8a207c07f347d4b34806dc576872000d9a60fd481ed9eb78ea8512e0666"},
+ {file = "pydantic-1.10.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:70152291488f8d2bbcf2027b5c28c27724c78a7949c91b466d28ad75d6d12702"},
+ {file = "pydantic-1.10.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:956b30638272c51c85caaff76851b60db4b339022c0ee6eca677c41e3646255b"},
+ {file = "pydantic-1.10.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bed9d6eea5fabbc6978c42e947190c7bd628ddaff3b56fc963fe696c3710ccd6"},
+ {file = "pydantic-1.10.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af8e2b3648128b8cadb1a71e2f8092a6f42d4ca123fad7a8d7ce6db8938b1db3"},
+ {file = "pydantic-1.10.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:076fff9da02ca716e4c8299c68512fdfbeac32fdefc9c160e6f80bdadca0993d"},
+ {file = "pydantic-1.10.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8f2447ca88a7e14fd4d268857521fb37535c53a367b594fa2d7c2551af905993"},
+ {file = "pydantic-1.10.24-cp311-cp311-win_amd64.whl", hash = "sha256:58d42a7c344882c00e3bb7c6c8c6f62db2e3aafa671f307271c45ad96e8ccf7a"},
+ {file = "pydantic-1.10.24-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:17e7610119483f03954569c18d4de16f4e92f1585f20975414033ac2d4a96624"},
+ {file = "pydantic-1.10.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e24435a9970dcb2b35648f2cf57505d4bd414fcca1a404c82e28d948183fe0a6"},
+ {file = "pydantic-1.10.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a9e92b9c78d7f3cfa085c21c110e7000894446e24a836d006aabfc6ae3f1813"},
+ {file = "pydantic-1.10.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef14dfa7c98b314a3e449e92df6f1479cafe74c626952f353ff0176b075070de"},
+ {file = "pydantic-1.10.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52219b4e70c1db185cfd103a804e416384e1c8950168a2d4f385664c7c35d21a"},
+ {file = "pydantic-1.10.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ce0986799248082e9a5a026c9b5d2f9fa2e24d2afb9b0eace9104334a58fdc1"},
+ {file = "pydantic-1.10.24-cp312-cp312-win_amd64.whl", hash = "sha256:874a78e4ed821258295a472e325eee7de3d91ba7a61d0639ce1b0367a3c63d4c"},
+ {file = "pydantic-1.10.24-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:265788a1120285c4955f8b3d52b3ea6a52c7a74db097c4c13a4d3567f0c6df3c"},
+ {file = "pydantic-1.10.24-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d255bebd927e5f1e026b32605684f7b6fc36a13e62b07cb97b29027b91657def"},
+ {file = "pydantic-1.10.24-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6e45dbc79a44e34c2c83ef1fcb56ff663040474dcf4dfc452db24a1de0f7574"},
+ {file = "pydantic-1.10.24-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af31565b12a7db5bfa5fe8c3a4f8fda4d32f5c2929998b1b241f1c22e9ab6e69"},
+ {file = "pydantic-1.10.24-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9c377fc30d9ca40dbff5fd79c5a5e1f0d6fff040fa47a18851bb6b0bd040a5d8"},
+ {file = "pydantic-1.10.24-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b644d6f14b2ce617d6def21622f9ba73961a16b7dffdba7f6692e2f66fa05d00"},
+ {file = "pydantic-1.10.24-cp313-cp313-win_amd64.whl", hash = "sha256:0cbbf306124ae41cc153fdc2559b37faa1bec9a23ef7b082c1756d1315ceffe6"},
+ {file = "pydantic-1.10.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7c8bbad6037a87effe9f3739bdf39851add6e0f7e101d103a601c504892ffa70"},
+ {file = "pydantic-1.10.24-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f154a8a46a0d950c055254f8f010ba07e742ac4404a3b6e281a31913ac45ccd0"},
+ {file = "pydantic-1.10.24-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f25d2f792afcd874cc8339c1da1cc52739f4f3d52993ed1f6c263ef2afadc47"},
+ {file = "pydantic-1.10.24-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:49a6f0178063f15eaea6cbcb2dba04db0b73db9834bc7b1e1c4dbea28c7cd22f"},
+ {file = "pydantic-1.10.24-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:bb3df10be3c7d264947180615819aeec0916f19650f2ba7309ed1fe546ead0d2"},
+ {file = "pydantic-1.10.24-cp37-cp37m-win_amd64.whl", hash = "sha256:fa0ebefc169439267e4b4147c7d458908788367640509ed32c90a91a63ebb579"},
+ {file = "pydantic-1.10.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1a5ef77efeb54def2695f2b8f4301aae8c7aa2b334bd15f61c18ef54317621"},
+ {file = "pydantic-1.10.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02f7a25e8949d8ca568e4bcef2ffed7881d7843286e7c3488bdd3b67f092059c"},
+ {file = "pydantic-1.10.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da2775712dda8b89e701ed2a72d5d81d23dbc6af84089da8a0f61a0be439c8c"},
+ {file = "pydantic-1.10.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75259be0558ca3af09192ad7b18557f2e9033ad4cbd48c252131f5292f6374fd"},
+ {file = "pydantic-1.10.24-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1a1ae996daa3d43c530b8d0bacc7e2d9cb55e3991f0e6b7cc2cb61a0fb9f6667"},
+ {file = "pydantic-1.10.24-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:34109b0afa63b36eec2f2b115694e48ae5ee52f7d3c1baa0be36f80e586bda52"},
+ {file = "pydantic-1.10.24-cp38-cp38-win_amd64.whl", hash = "sha256:4d7336bfcdb8cb58411e6b498772ba2cff84a2ce92f389bae3a8f1bb2c840c49"},
+ {file = "pydantic-1.10.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25fb9a69a21d711deb5acefdab9ff8fb49e6cc77fdd46d38217d433bff2e3de2"},
+ {file = "pydantic-1.10.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6af36a8fb3072526b5b38d3f341b12d8f423188e7d185f130c0079fe02cdec7f"},
+ {file = "pydantic-1.10.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fc35569dfd15d3b3fc06a22abee0a45fdde0784be644e650a8769cd0b2abd94"},
+ {file = "pydantic-1.10.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fac7fbcb65171959973f3136d0792c3d1668bc01fd414738f0898b01f692f1b4"},
+ {file = "pydantic-1.10.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fc3f4a6544517380658b63b144c7d43d5276a343012913b7e5d18d9fba2f12bb"},
+ {file = "pydantic-1.10.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:415c638ca5fd57b915a62dd38c18c8e0afe5adf5527be6f8ce16b4636b616816"},
+ {file = "pydantic-1.10.24-cp39-cp39-win_amd64.whl", hash = "sha256:a5bf94042efbc6ab56b18a5921f426ebbeefc04f554a911d76029e7be9057d01"},
+ {file = "pydantic-1.10.24-py3-none-any.whl", hash = "sha256:093768eba26db55a88b12f3073017e3fdee319ef60d3aef5c6c04a4e484db193"},
+ {file = "pydantic-1.10.24.tar.gz", hash = "sha256:7e6d1af1bd3d2312079f28c9baf2aafb4a452a06b50717526e5ac562e37baa53"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.2.0"
+
+[package.extras]
+dotenv = ["python-dotenv (>=0.10.4)"]
+email = ["email-validator (>=1.0.3)"]
+
+[[package]]
+name = "pytest"
+version = "7.4.4"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.7"
+groups = ["main", "dev"]
+files = [
+ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
+ {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<2.0"
+tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+
+[[package]]
+name = "pytest-asyncio"
+version = "0.23.8"
+description = "Pytest support for asyncio"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"},
+ {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"},
+]
+
+[package.dependencies]
+pytest = ">=7.0.0,<9"
+
+[package.extras]
+docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
+testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
+
+[[package]]
+name = "pytest-cov"
+version = "4.1.0"
+description = "Pytest plugin for measuring coverage."
+optional = false
+python-versions = ">=3.7"
+groups = ["dev"]
+files = [
+ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
+ {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
+]
+
+[package.dependencies]
+coverage = {version = ">=5.2.1", extras = ["toml"]}
+pytest = ">=4.6"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
+
+[[package]]
+name = "pytest-mock"
+version = "3.15.1"
+description = "Thin-wrapper around the mock package for easier use with pytest"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+ {file = "pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d"},
+ {file = "pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f"},
+]
+
+[package.dependencies]
+pytest = ">=6.2.5"
+
+[package.extras]
+dev = ["pre-commit", "pytest-asyncio", "tox"]
+
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
@@ -503,35 +1464,33 @@ six = ">=1.5"
[[package]]
name = "python-dotenv"
-version = "1.0.1"
+version = "1.1.1"
description = "Read key-value pairs from a .env file and set them as environment variables"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
-markers = "python_version < \"3.10\""
files = [
- {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
- {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
+ {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"},
+ {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"},
]
[package.extras]
cli = ["click (>=5.0)"]
[[package]]
-name = "python-dotenv"
-version = "1.1.1"
-description = "Read key-value pairs from a .env file and set them as environment variables"
+name = "pytokens"
+version = "0.1.10"
+description = "A Fast, spec compliant Python 3.12+ tokenizer that runs on older Pythons."
optional = false
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "python_version >= \"3.10\""
+python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
- {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"},
- {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"},
+ {file = "pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b"},
+ {file = "pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044"},
]
[package.extras]
-cli = ["click (>=5.0)"]
+dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"]
[[package]]
name = "pytz"
@@ -545,29 +1504,6 @@ files = [
{file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"},
]
-[[package]]
-name = "requests"
-version = "2.32.4"
-description = "Python HTTP for Humans."
-optional = false
-python-versions = ">=3.8"
-groups = ["main"]
-markers = "python_version < \"3.10\""
-files = [
- {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"},
- {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"},
-]
-
-[package.dependencies]
-certifi = ">=2017.4.17"
-charset_normalizer = ">=2,<4"
-idna = ">=2.5,<4"
-urllib3 = ">=1.21.1,<3"
-
-[package.extras]
-socks = ["PySocks (>=1.5.6,!=1.5.7)"]
-use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
-
[[package]]
name = "requests"
version = "2.32.5"
@@ -575,7 +1511,6 @@ description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.9"
groups = ["main"]
-markers = "python_version >= \"3.10\""
files = [
{file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"},
{file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"},
@@ -603,6 +1538,61 @@ files = [
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
]
+[[package]]
+name = "tomli"
+version = "2.2.1"
+description = "A lil' TOML parser"
+optional = false
+python-versions = ">=3.8"
+groups = ["main", "dev"]
+markers = "python_version < \"3.11\""
+files = [
+ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
+ {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
+ {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
+ {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
+ {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
+ {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
+ {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
+ {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
+ {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
+ {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
+ {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
+ {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
+ {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
+ {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
+ {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
+ {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
+ {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
+ {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
+ {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
+ {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
+ {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
+ {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
+ {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
+ {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
+ {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
+ {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
+ {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
+ {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
+ {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
+ {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
+ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
+ {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+description = "Backported and Experimental Type Hints for Python 3.9+"
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
+ {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
+]
+
[[package]]
name = "tzdata"
version = "2025.2"
@@ -617,15 +1607,14 @@ files = [
[[package]]
name = "urllib3"
-version = "2.2.3"
+version = "2.5.0"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
-markers = "python_version < \"3.10\""
files = [
- {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"},
- {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"},
+ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"},
+ {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"},
]
[package.extras]
@@ -635,25 +1624,125 @@ socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
-name = "urllib3"
-version = "2.5.0"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
+name = "yarl"
+version = "1.20.1"
+description = "Yet another URL library"
optional = false
python-versions = ">=3.9"
groups = ["main"]
-markers = "python_version >= \"3.10\""
files = [
- {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"},
- {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"},
+ {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"},
+ {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"},
+ {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"},
+ {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"},
+ {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"},
+ {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"},
+ {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"},
+ {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"},
+ {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"},
+ {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"},
+ {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"},
+ {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"},
+ {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"},
+ {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"},
+ {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"},
+ {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"},
+ {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"},
+ {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"},
+ {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"},
+ {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"},
+ {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"},
+ {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"},
+ {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"},
+ {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"},
+ {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"},
+ {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"},
+ {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"},
+ {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"},
+ {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"},
+ {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"},
+ {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"},
+ {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"},
+ {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"},
+ {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"},
+ {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"},
+ {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"},
+ {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"},
+ {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"},
+ {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"},
+ {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"},
+ {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"},
+ {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"},
+ {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"},
+ {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"},
+ {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"},
+ {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"},
+ {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"},
+ {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"},
+ {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"},
+ {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"},
+ {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"},
+ {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"},
+ {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"},
+ {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"},
+ {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"},
+ {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"},
+ {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"},
+ {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"},
+ {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"},
+ {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"},
+ {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"},
+ {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"},
+ {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"},
+ {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"},
+ {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"},
+ {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"},
+ {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"},
+ {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"},
+ {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"},
+ {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"},
+ {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"},
+ {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"},
+ {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"},
+ {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"},
+ {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"},
+ {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"},
+ {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"},
+ {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"},
+ {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"},
+ {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"},
+ {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"},
+ {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"},
+ {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"},
+ {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"},
+ {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"},
+ {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"},
+ {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"},
+ {file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"},
+ {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"},
+ {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"},
+ {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"},
+ {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"},
+ {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"},
+ {file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"},
+ {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"},
+ {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"},
+ {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"},
+ {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"},
+ {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"},
+ {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"},
+ {file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"},
+ {file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"},
+ {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"},
+ {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"},
]
-[package.extras]
-brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
-h2 = ["h2 (>=4,<5)"]
-socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
-zstd = ["zstandard (>=0.18.0)"]
+[package.dependencies]
+idna = ">=2.0"
+multidict = ">=4.0"
+propcache = ">=0.2.1"
[metadata]
lock-version = "2.1"
-python-versions = "^3.8"
-content-hash = "9badf4e23decfc843ed04ca2f4d28d84d39dcf2982b95c01a89b78c668c706df"
+python-versions = "^3.9"
+content-hash = "f4ffc54e3bc3ec1c6356a31f19e3051f6c80267f33996611da2fdc875190131a"
diff --git a/pyproject.toml b/pyproject.toml
index 3b9b19b..5f247f6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,38 +1,76 @@
[tool.poetry]
name = "transaction-analyzer"
-version = "0.1.0"
-description = "Анализатор банковских транзакций"
+version = "1.0.0"
+description = "Bank transaction analysis application"
authors = ["Your Name "]
+readme = "README.md"
[tool.poetry.dependencies]
-python = "^3.8"
-pandas = ">=2.0.0,<3.0.0"
-openpyxl = ">=3.1.0,<4.0.0"
-requests = ">=2.31.0,<3.0.0"
-python-dotenv = ">=1.0.0,<2.0.0"
-python-dateutil = ">=2.8.2,<3.0.0"
+python = "^3.9"
+pandas = "^2.0.0"
+openpyxl = "^3.1.0"
+requests = "^2.31.0"
+python-dateutil = "^2.8.2"
+pydantic = "^1.10.12"
+python-dotenv = "^1.0.0"
+aiohttp = "^3.8.0"
+cachetools = ">=5.3.0"
+pytest-asyncio = ">=0.21.0"
+black = ">=23.12.1"
-[poetry.group.dev.dependencies]
-pytest = "^7.0.0"
-pytest-cov = "^4.0.0"
-flake8 = "^5.0.0"
-black = "^22.0.0"
-isort = "^5.10.0"
-mypy = "^0.991.0"
+[tool.poetry.group.dev.dependencies]
+pytest = "^7.4.0"
+pytest-mock = "^3.11.0"
+pytest-cov = "^4.1.0"
+black = "^25.9.0"
+isort = "^6.0.1"
+mypy = "^1.18.2"
[tool.black]
-line-length = 119
-exclude = '''
+line-length = 120
+target-version = ['py39']
+include = '\.pyi?$'
+extend-exclude = '''
/(
- \.git
- | __pycache__
+ # directories
+ | \.eggs
+ | \.git
+ | \.venv
+ | build
+ | dist
+ # files
+ | poetry\.lock
)/
'''
[tool.isort]
-line_length = 119
+profile = "black"
+multi_line_output = 3
+line_length = 120
+known_first_party = ["your_package_name"] # Замените на имя вашего пакета
+skip_glob = ["**/migrations/*"] # Опционально: исключить миграции
[tool.mypy]
-disallow_untyped_defs = true
+python_version = "3.9"
warn_return_any = true
-exclude = ['venv/']
+warn_unused_configs = true
+disallow_untyped_defs = true
+check_untyped_defs = true
+no_implicit_optional = true
+strict_equality = true
+show_error_codes = true
+enable_error_code = ["ignore-without-code"]
+
+[[tool.mypy.overrides]]
+module = [
+ "django.*", # Если используете Django
+ "celery.*", # Если используете Celery
+]
+ignore_missing_imports = true
+
+[tool.pytest.ini_options]
+asyncio_mode = "auto"
+testpaths = ["tests"]
+python_files = ["test_*.py"]
+python_classes = ["Test*"]
+python_functions = ["test_*"]
diff --git a/requirements-dev.txt b/requirements-dev.txt
deleted file mode 100644
index c99e92b..0000000
--- a/requirements-dev.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-# Для разработки
-pytest>=7.4.0,<8.0.0
-pytest-cov>=4.1.0,<5.0.0
-pytest-mock>=3.11.1,<4.0.0
-flake8>=6.0.0,<7.0.0
-black>=23.7.0,<24.0.0
-isort>=5.12.0,<6.0.0
-mypy>=1.5.0,<2.0.0
-pre-commit>=3.3.0,<4.0.0
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 9eaa878..61b4b19 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,12 @@
-pandas>=2.0.0,<3.0.0
-openpyxl>=3.1.0,<4.0.0
-requests>=2.31.0,<3.0.0
-python-dotenv>=1.0.0,<2.0.0
-python-dateutil>=2.8.2,<3.0.0
+pandas>=2.0.0
+openpyxl>=3.1.0
+requests>=2.31.0
+python-dateutil>=2.8.2
+pydantic>=2.0.0
+python-dotenv>=1.0.0
+aiohttp>=3.8.0
+cachetools>=5.3.0
+pytest>=7.4.0
+pytest-asyncio>=0.21.0
+pytest-mock>=3.11.0
+black>=23.12.1
diff --git a/run_app.py b/run_app.py
new file mode 100644
index 0000000..8de1f9b
--- /dev/null
+++ b/run_app.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+"""
+Упрощенный запуск приложения
+"""
+
+import sys
+from pathlib import Path
+
+# Добавляем src в путь Python
+sys.path.insert(0, str(Path(__file__).parent / 'src'))
+
+# Создаем необходимые директории
+Path('logs').mkdir(exist_ok=True)
+Path('reports').mkdir(exist_ok=True)
+Path('data').mkdir(exist_ok=True)
+
+
+def main():
+ """Основная функция"""
+ try:
+ from src.utils import load_transactions, load_user_settings
+ from src.views import events_page, main_page
+
+ print("🚀 Запуск Transaction Analyzer...")
+
+ # Загрузка данных
+ load_transactions()
+ load_user_settings()
+
+ # Генерация данных для веб-страниц
+ current_time = "2023-12-20 15:30:00"
+
+ print("\n=== ГЛАВНАЯ СТРАНИЦА ===")
+ main_data = main_page(current_time)
+ print(f"Приветствие: {main_data['greeting']}")
+ print(f"Карты: {len(main_data['cards'])}")
+ print(f"Топ транзакций: {len(main_data['top_transactions'])}")
+
+ print("\n=== СТРАНИЦА СОБЫТИЙ ===")
+ events_data = events_page("2023-12-20", "M")
+ print(f"Расходы: {events_data['expenses']['total_amount']}")
+ print(f"Поступления: {events_data['income']['total_amount']}")
+
+ print("\n✅ Приложение успешно запущено!")
+
+ except Exception as e:
+ print(f"❌ Ошибка: {e}")
+ import traceback
+ traceback.print_exc()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/api_client.py b/src/api_client.py
new file mode 100644
index 0000000..db2f0b6
--- /dev/null
+++ b/src/api_client.py
@@ -0,0 +1,222 @@
+import asyncio
+import logging
+from typing import Dict, List, Optional
+
+import aiohttp
+import cachetools
+
+from .config import settings
+
+logger = logging.getLogger(__name__)
+
+# Кэш для API запросов
+cache = cachetools.TTLCache(maxsize=100, ttl=settings.cache_ttl)
+
+
+class APIClient:
+ """Клиент для работы с внешними API"""
+
+ def __init__(self):
+ self.session: Optional[aiohttp.ClientSession] = None
+
+ async def __aenter__(self):
+ import aiohttp
+ self.session = aiohttp.ClientSession()
+ return self
+
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
+ if self.session:
+ await self.session.close()
+
+ async def get_currency_rates(self, currencies: List[str]) -> List[Dict[str, float]]:
+ """Получение актуальных курсов валют"""
+ cache_key = f"currency_rates_{'_'.join(sorted(currencies))}"
+
+ if cache_key in cache:
+ logger.info("Using cached currency rates")
+ return cache[cache_key]
+
+ try:
+ # Пробуем разные API по очереди
+ rates = await self._get_currency_rates_exchangerate(currencies)
+ if not rates:
+ rates = await self._get_currency_rates_currencyapi(currencies)
+
+ if rates:
+ cache[cache_key] = rates
+ return rates
+ else:
+ # Fallback к статическим данным
+ return self._get_fallback_currency_rates(currencies)
+
+ except Exception as e:
+ logger.error(f"Error fetching currency rates: {e}")
+ return self._get_fallback_currency_rates(currencies)
+
+ async def _get_currency_rates_exchangerate(self, currencies: List[str]) -> List[Dict[str, float]]:
+ """Получение курсов валют через ExchangeRate API"""
+ try:
+ base_currency = "RUB"
+ target_currencies = [c for c in currencies if c != "RUB"]
+
+ if not target_currencies:
+ return [{"currency": "RUB", "rate": 1.0}]
+
+ async with self.session.get(
+ f"{settings.exchangerate_url}?base={base_currency}"
+ f"&symbols={','.join(target_currencies)}"
+ ) as response:
+ if response.status == 200:
+ data = await response.json()
+ rates = [{"currency": "RUB", "rate": 1.0}]
+
+ for currency in target_currencies:
+ if currency in data.get("rates", {}):
+ rate = 1 / data["rates"][currency] # Конвертируем из RUB в валюту
+ rates.append({"currency": currency, "rate": round(rate, 4)})
+
+ return rates
+ except Exception as e:
+ logger.warning(f"ExchangeRate API failed: {e}")
+ return []
+
+ async def _get_currency_rates_currencyapi(self, currencies: List[str]) -> List[Dict[str, float]]:
+ """Получение курсов валют через CurrencyAPI"""
+ try:
+ if not settings.currency_api_key:
+ return []
+
+ base_currency = "RUB"
+ target_currencies = [c for c in currencies if c != "RUB"]
+
+ async with self.session.get(
+ f"{settings.currency_api_url}?apikey={settings.currency_api_key}"
+ f"&base_currency={base_currency}"
+ ) as response:
+ if response.status == 200:
+ data = await response.json()
+ rates = [{"currency": "RUB", "rate": 1.0}]
+
+ for currency in target_currencies:
+ if currency in data.get("data", {}):
+ rate = 1 / data["data"][currency]["value"]
+ rates.append({"currency": currency, "rate": round(rate, 4)})
+
+ return rates
+ except Exception as e:
+ logger.warning(f"CurrencyAPI failed: {e}")
+ return []
+
+ async def get_stock_prices(self, stocks: List[str]) -> List[Dict[str, float]]:
+ """Получение актуальных цен акций"""
+ cache_key = f"stock_prices_{'_'.join(sorted(stocks))}"
+
+ if cache_key in cache:
+ logger.info("Using cached stock prices")
+ return cache[cache_key]
+
+ try:
+ prices = await self._get_stock_prices_alphavantage(stocks)
+ if prices:
+ cache[cache_key] = prices
+ return prices
+ else:
+ return self._get_fallback_stock_prices(stocks)
+
+ except Exception as e:
+ logger.error(f"Error fetching stock prices: {e}")
+ return self._get_fallback_stock_prices(stocks)
+
+ async def _get_stock_prices_alphavantage(self, stocks: List[str]) -> List[Dict[str, float]]:
+ """Получение цен акций через Alpha Vantage"""
+ try:
+ prices = []
+
+ for stock in stocks:
+ # Для демо-режима используем лимитированные запросы
+ if settings.alpha_vantage_api_key == "demo":
+ # Используем fallback данные для демо
+ continue
+
+ async with self.session.get(
+ f"{settings.alpha_vantage_url}?function=GLOBAL_QUOTE"
+ f"&symbol={stock}&apikey={settings.alpha_vantage_api_key}"
+ ) as response:
+ if response.status == 200:
+ data = await response.json()
+ quote = data.get("Global Quote", {})
+ if quote:
+ price = float(quote.get("05. price", 0))
+ prices.append({"stock": stock, "price": round(price, 2)})
+
+ return prices if prices else []
+
+ except Exception as e:
+ logger.warning(f"Alpha Vantage API failed: {e}")
+ return []
+
+ def _get_fallback_currency_rates(self, currencies: List[str]) -> List[Dict[str, float]]:
+ """Резервные данные о курсах валют"""
+ fallback_rates = {
+ "USD": 93.45,
+ "EUR": 101.23,
+ "GBP": 117.89,
+ "CNY": 12.87,
+ "JPY": 0.63,
+ "RUB": 1.0
+ }
+
+ rates = []
+ for currency in currencies:
+ rate = fallback_rates.get(currency, 1.0)
+ rates.append({"currency": currency, "rate": rate})
+
+ logger.info("Using fallback currency rates")
+ return rates
+
+ def _get_fallback_stock_prices(self, stocks: List[str]) -> List[Dict[str, float]]:
+ """Резервные данные о ценах акций"""
+ fallback_prices = {
+ "AAPL": 178.72,
+ "AMZN": 145.63,
+ "GOOGL": 138.21,
+ "MSFT": 374.51,
+ "TSLA": 235.49,
+ "META": 351.95,
+ "NVDA": 477.76,
+ "NFLX": 485.13
+ }
+
+ prices = []
+ for stock in stocks:
+ price = fallback_prices.get(stock, 0.0)
+ if price > 0:
+ prices.append({"stock": stock, "price": price})
+
+ logger.info("Using fallback stock prices")
+ return prices
+
+
+# Синхронная обертка для удобства использования
+class SyncAPIClient:
+ """Синхронная обертка для APIClient"""
+
+ @staticmethod
+ def get_currency_rates(currencies: List[str]) -> List[Dict[str, float]]:
+ """Получение курсов валют"""
+
+ async def _fetch():
+ async with APIClient() as client:
+ return await client.get_currency_rates(currencies)
+
+ return asyncio.run(_fetch())
+
+ @staticmethod
+ def get_stock_prices(stocks: List[str]) -> List[Dict[str, float]]:
+ """Получение цен акций"""
+
+ async def _fetch():
+ async with APIClient() as client:
+ return await client.get_stock_prices(stocks)
+
+ return asyncio.run(_fetch())
diff --git a/src/config.py b/src/config.py
new file mode 100644
index 0000000..cb8271e
--- /dev/null
+++ b/src/config.py
@@ -0,0 +1,43 @@
+import os
+from typing import List
+
+from dotenv import load_dotenv
+from pydantic import BaseSettings
+
+load_dotenv()
+
+
+class Settings(BaseSettings):
+ """Настройки приложения"""
+
+ # API Keys
+ alpha_vantage_api_key: str = os.getenv("ALPHA_VANTAGE_API_KEY", "demo")
+ exchangerate_api_key: str = os.getenv("EXCHANGERATE_API_KEY", "")
+ currency_api_key: str = os.getenv("CURRENCY_API_KEY", "")
+
+ # Paths
+ data_file_path: str = "data/operations.xlsx"
+ user_settings_path: str = "user_settings.json"
+
+ # API endpoints
+ alpha_vantage_url: str = "https://www.alphavantage.co/query"
+ exchangerate_url: str = "https://api.exchangerate.host/latest"
+ currency_api_url: str = "https://api.currencyapi.com/v3/latest"
+
+ # Cache settings
+ cache_ttl: int = 3600 # 1 hour
+
+ # Date formats to try
+ date_formats: List[str] = [
+ "%d.%m.%Y",
+ "%Y-%m-%d",
+ "%d/%m/%Y",
+ "%m/%d/%Y",
+ "%Y.%m.%d"
+ ]
+
+ class Config:
+ env_file = ".env"
+
+
+settings = Settings()
diff --git a/src/logs/transaction_analyzer.log b/src/logs/transaction_analyzer.log
new file mode 100644
index 0000000..d827da6
--- /dev/null
+++ b/src/logs/transaction_analyzer.log
@@ -0,0 +1,60 @@
+2025-09-27 22:32:18,219 - __main__ - INFO - ...
+2025-09-27 22:32:18,219 - src.utils - INFO - data/operations.xlsx
+2025-09-27 22:32:18,220 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:32:18,220 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:32:20,607 - __main__ - INFO - ...
+2025-09-27 22:32:20,608 - src.utils - INFO - data/operations.xlsx
+2025-09-27 22:32:20,608 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:32:20,609 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:36:18,943 - __main__ - INFO - ...
+2025-09-27 22:36:18,943 - src.utils - INFO - data/operations.xlsx
+2025-09-27 22:36:18,943 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:36:18,943 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:39:34,228 - __main__ - INFO - ...
+2025-09-27 22:39:34,229 - src.utils - INFO - data/operations.xlsx
+2025-09-27 22:39:34,229 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:39:34,229 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:39:38,088 - __main__ - INFO - ...
+2025-09-27 22:39:38,088 - src.utils - INFO - data/operations.xlsx
+2025-09-27 22:39:38,088 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:39:38,088 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:40:17,643 - __main__ - INFO - ...
+2025-09-27 22:40:17,644 - src.utils - INFO - data/operations.xlsx
+2025-09-27 22:40:17,645 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:40:17,645 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:40:50,211 - __main__ - INFO - ...
+2025-09-27 22:40:50,212 - src.utils - INFO - data/operations.xlsx
+2025-09-27 22:40:50,213 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:40:50,213 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:41:04,925 - __main__ - INFO - ...
+2025-09-27 22:41:04,926 - src.utils - INFO - data/operations.xlsx
+2025-09-27 22:41:04,927 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:41:04,927 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:42:25,867 - __main__ - INFO - ...
+2025-09-27 22:42:25,867 - src.utils - INFO - data/operations.xlsx
+2025-09-27 22:42:25,867 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:42:25,867 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:42:43,338 - __main__ - INFO - ...
+2025-09-27 22:42:43,338 - src.utils - INFO - data/operations.xlsx
+2025-09-27 22:42:43,338 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 22:42:43,338 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 23:12:13,905 - __main__ - INFO - ...
+2025-09-27 23:12:13,905 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:12:13,906 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 23:12:13,906 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 23:12:36,049 - __main__ - INFO - ...
+2025-09-27 23:12:36,049 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:12:36,049 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 23:12:36,049 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 23:21:59,058 - __main__ - INFO - ...
+2025-09-27 23:21:59,058 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:21:59,058 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 23:21:59,058 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 23:22:19,758 - __main__ - INFO - ...
+2025-09-27 23:22:19,759 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:22:19,759 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 23:22:19,759 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 23:34:17,097 - __main__ - INFO - ...
+2025-09-27 23:34:17,097 - src.utils - INFO - data/operations.xlsx
+2025-09-27 23:34:17,097 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
+2025-09-27 23:34:17,097 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx'
diff --git a/src/main.py b/src/main.py
index e42784b..4483084 100644
--- a/src/main.py
+++ b/src/main.py
@@ -1,52 +1,174 @@
+#!/usr/bin/env python3
+"""
+Основной модуль приложения для анализа транзакций
+"""
+import argparse
import json
-import pandas as pd
-from .utils import load_transactions
-from .views import home_page
-from .services import investment_bank, simple_search, find_phone_transactions
-from .reports import spending_by_category, spending_by_weekday
+import logging
+from datetime import datetime
+from pathlib import Path
+
+# Создаем необходимые директории
+Path('logs').mkdir(exist_ok=True)
+Path('reports').mkdir(exist_ok=True)
+Path('data').mkdir(exist_ok=True)
+
+# Настройка логирования
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ handlers=[
+ logging.FileHandler('logs/transaction_analyzer.log'),
+ logging.StreamHandler()
+ ]
+)
+
+logger = logging.getLogger(__name__)
+
+
+class TransactionAnalyzer:
+ """Основной класс приложения"""
+
+ def __init__(self, data_file: str = None):
+ from src.config import settings
+ self.data_file = data_file or settings.data_file_path
+ self.transactions_df = None
+ self.settings = None
+
+ def load_data(self):
+ """Загрузка данных"""
+ from src.utils import load_transactions, load_user_settings
+ logger.info("Загрузка данных...")
+ self.transactions_df = load_transactions(self.data_file)
+ self.settings = load_user_settings()
+ logger.info("Данные успешно загружены")
+
+ def generate_main_page(self, date_time: str) -> dict:
+ """Генерация данных для главной страницы"""
+ from src.views import main_page
+ return main_page(date_time, self.data_file)
+
+ def generate_events_page(self, date: str, period: str = 'M') -> dict:
+ """Генерация данных для страницы событий"""
+ from src.views import events_page
+ return events_page(date, period, self.data_file)
+
+ def analyze_cashback_categories(self, year: int, month: int) -> dict:
+ """Анализ выгодных категорий кешбэка"""
+ from src.services import profitable_cashback_categories
+ cashback_rules = self.settings.get('cashback_rules', {'default': 0.01})
+ return profitable_cashback_categories(
+ self.transactions_df, year, month, cashback_rules
+ )
+
+ def calculate_investment(self, month: str, limit: int) -> float:
+ """Расчет инвесткопилки"""
+ from src.services import investment_bank
+ transactions_list = self.transactions_df.to_dict('records')
+ return investment_bank(month, transactions_list, limit)
+
+ def search_transactions(self, search_string: str) -> list:
+ """Поиск транзакций"""
+ from src.services import simple_search
+ transactions_list = self.transactions_df.to_dict('records')
+ return simple_search(transactions_list, search_string)
+
+ def generate_reports(self):
+ """Генерация всех отчетов"""
+ from src.reports import ReportGenerator # Добавлен импорт
+
+ reports = {'spending_by_category': ReportGenerator.spending_by_category(
+ self.transactions_df, 'Супермаркеты'
+ ), 'spending_by_weekday': ReportGenerator.spending_by_weekday(
+ self.transactions_df
+ ), 'spending_by_workday': ReportGenerator.spending_by_workday(
+ self.transactions_df
+ ), 'monthly_summary': ReportGenerator.monthly_summary(
+ self.transactions_df
+ )}
+
+ # Отчет по категориям
+
+ # Отчет по дням недели
+
+ # Отчет по рабочим/выходным дням
+
+ # Сводный отчет
+
+ return reports
def main():
- """Основная функция приложения."""
+ """Основная функция приложения"""
+ parser = argparse.ArgumentParser(description='Анализатор банковских транзакций')
+ parser.add_argument('--data-file', help='Путь к файлу с транзакциями')
+ parser.add_argument('--command', choices=['web', 'report', 'analyze', 'test'],
+ default='web', help='Режим работы')
+
+ args = parser.parse_args()
+
try:
- # Загрузка данных
- transactions_df = load_transactions('data/operations.xlsx')
+ # Инициализация анализатора
+ analyzer = TransactionAnalyzer(args.data_file)
+ analyzer.load_data()
+
+ if args.command == 'web':
+ # Пример генерации данных для веб-страниц
+ current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+
+ main_data = analyzer.generate_main_page(current_time)
+ events_data = analyzer.generate_events_page(current_time.split()[0])
- # Загрузка настроек пользователя
- with open('user_settings.json', 'r') as f:
- user_settings = json.load(f)
+ print("=== ДАННЫЕ ДЛЯ ГЛАВНОЙ СТРАНИЦЫ ===")
+ print(json.dumps(main_data, ensure_ascii=False, indent=2))
- # Демонстрация функциональности
- print("=== Анализатор транзакций ===")
+ print("\n=== ДАННЫЕ ДЛЯ СТРАНИЦЫ СОБЫТИЙ ===")
+ print(json.dumps(events_data, ensure_ascii=False, indent=2))
- # Главная страница
- home_data = home_page("2023-12-20 15:30:00", transactions_df, user_settings)
- print("Главная страница сгенерирована")
+ elif args.command == 'report':
+ # Генерация отчетов
+ reports = analyzer.generate_reports()
+ print("=== ОТЧЕТЫ СГЕНЕРИРОВАНЫ ===")
- # Конвертация DataFrame в список словарей для сервисов
- transactions_list = transactions_df.to_dict('records')
+ for report_name, report_data in reports.items():
+ print(f"\n--- {report_name} ---")
+ if isinstance(report_data, dict):
+ print(json.dumps(report_data, ensure_ascii=False, indent=2))
+ elif hasattr(report_data, 'head'): # DataFrame
+ print(report_data.head())
+ else:
+ print(f"Тип данных: {type(report_data)}")
+ print(report_data)
- # Сервисы
- savings = investment_bank("2023-12", transactions_list, 50)
- print(f"Инвесткопилка: {savings} руб.")
+ print("\n Отчеты сохранены в папке 'reports/'")
- search_results = simple_search("кафе", transactions_list)
- print(f"Найдено транзакций по запросу 'кафе': {len(search_results)}")
+ elif args.command == 'analyze':
+ # Анализ данных
+ current_year = datetime.now().year
+ current_month = datetime.now().month
- phone_transactions = find_phone_transactions(transactions_list)
- print(f"Найдено транзакций с телефонами: {len(phone_transactions)}")
+ cashback_analysis = analyzer.analyze_cashback_categories(current_year, current_month)
+ investment = analyzer.calculate_investment(
+ f"{current_year}-{current_month:02d}", 50
+ )
- # Отчеты
- category_spending = spending_by_category(transactions_df, "Супермаркеты")
- print("Отчет по тратам по категории создан")
+ print("=== АНАЛИЗ ДАННЫХ ===")
+ print(f"Выгодные категории кешбэка: {cashback_analysis}")
+ print(f"Инвесткопилка за месяц: {investment} руб.")
- weekday_spending = spending_by_weekday(transactions_df)
- print("Отчет по тратам по дням недели создан")
+ elif args.command == 'test':
+ # Простой тест функциональности
+ print("=== ТЕСТ ФУНКЦИОНАЛЬНОСТИ ===")
+ print(f"Загружено транзакций: {len(analyzer.transactions_df)}")
+ print(f"Настройки: {list(analyzer.settings.keys())}")
- print("\nВсе функции выполнены успешно!")
+ # Тест поиска
+ search_results = analyzer.search_transactions("магазин")
+ print(f"Результатов поиска 'магазин': {len(search_results)}")
except Exception as e:
- print(f"Ошибка: {e}")
+ logger.error(f"Ошибка приложения: {e}")
+ raise
if __name__ == "__main__":
diff --git a/src/reports.py b/src/reports.py
index 4e47cbf..40bc114 100644
--- a/src/reports.py
+++ b/src/reports.py
@@ -1,33 +1,42 @@
import json
import logging
-import pandas as pd
+import os
from datetime import datetime, timedelta
-from typing import Dict, List, Any, Optional, Callable
from functools import wraps
+from typing import Any, Callable, Dict, Optional
+
+import pandas as pd
logger = logging.getLogger(__name__)
-def report(func: Optional[Callable] = None, filename: Optional[str] = None):
- """Декоратор для записи результатов отчетов в файл."""
+def report_decorator(filename: Optional[str] = None):
+ """Декоратор для сохранения отчетов в файл"""
- def decorator(report_func):
- @wraps(report_func)
+ def decorator(func: Callable) -> Callable:
+ @wraps(func)
def wrapper(*args, **kwargs):
- result = report_func(*args, **kwargs)
+ result = func(*args, **kwargs)
- # Генерация имени файла
- if filename:
- file_path = filename
+ # Генерация имени файла если не указано
+ if filename is None:
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
+ func_name = func.__name__
+ report_filename = f"reports/{func_name}_{timestamp}.json"
else:
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
- file_path = f"{report_func.__name__}_{timestamp}.json"
+ report_filename = filename
- # Запись в файл
+ # Создаем директорию если не существует
+ os.makedirs(os.path.dirname(report_filename), exist_ok=True)
+
+ # Сохранение результата
try:
- with open(file_path, 'w', encoding='utf-8') as f:
- json.dump(result, f, ensure_ascii=False, indent=2)
- logger.info(f"Отчет сохранен в файл: {file_path}")
+ with open(report_filename, 'w', encoding='utf-8') as f:
+ if isinstance(result, pd.DataFrame):
+ json.dump(result.to_dict('records'), f, ensure_ascii=False, indent=2)
+ else:
+ json.dump(result, f, ensure_ascii=False, indent=2)
+ logger.info(f"Отчет сохранен в {report_filename}")
except Exception as e:
logger.error(f"Ошибка сохранения отчета: {e}")
@@ -35,70 +44,203 @@ def wrapper(*args, **kwargs):
return wrapper
- if func is None:
- return decorator
- else:
- return decorator(func)
-
-
-@report
-def spending_by_category(transactions: pd.DataFrame, category: str,
- date: Optional[str] = None) -> Dict[str, float]:
- """Анализ трат по категории за последние 3 месяца."""
- try:
- if date is None:
- date = datetime.now().strftime("%Y-%m-%d")
-
- target_date = datetime.strptime(date, "%Y-%m-%d")
- three_months_ago = target_date - timedelta(days=90)
-
- # Фильтрация транзакций
- transactions['Дата операции'] = pd.to_datetime(transactions['Дата операции'])
- filtered_df = transactions[
- (transactions['Дата операции'] >= three_months_ago) &
- (transactions['Дата операции'] <= target_date) &
- (transactions['Категория'] == category)
- ]
-
- # Группировка по месяцам
- filtered_df['Месяц'] = filtered_df['Дата операции'].dt.to_period('M')
- monthly_spending = filtered_df.groupby('Месяц')['Сумма операции'].sum().abs()
-
- result = {str(month): round(amount, 2) for month, amount in monthly_spending.items()}
- logger.info(f"Проанализированы траты по категории '{category}'")
-
- return result
- except Exception as e:
- logger.error(f"Ошибка анализа трат по категории: {e}")
- return {}
-
-
-@report
-def spending_by_weekday(transactions: pd.DataFrame, date: Optional[str] = None) -> Dict[str, float]:
- """Средние траты по дням недели за последние 3 месяца."""
- try:
- if date is None:
- date = datetime.now().strftime("%Y-%m-%d")
-
- target_date = datetime.strptime(date, "%Y-%m-%d")
- three_months_ago = target_date - timedelta(days=90)
-
- # Фильтрация транзакций
- transactions['Дата операции'] = pd.to_datetime(transactions['Дата операции'])
- filtered_df = transactions[
- (transactions['Дата операции'] >= three_months_ago) &
- (transactions['Дата операции'] <= target_date)
- ]
-
- # Анализ по дням недели
- filtered_df['День недели'] = filtered_df['Дата операции'].dt.day_name()
- weekday_spending = filtered_df.groupby('День недели')['Сумма операции'].mean().abs()
-
- days_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
- result = {day: round(weekday_spending.get(day, 0), 2) for day in days_order}
-
- logger.info("Проанализированы траты по дням недели")
- return result
- except Exception as e:
- logger.error(f"Ошибка анализа трат по дням недели: {e}")
- return {}
\ No newline at end of file
+ return decorator
+
+
+class ReportGenerator:
+ """Генератор отчетов"""
+
+ @staticmethod
+ @report_decorator()
+ def spending_by_category(transactions: pd.DataFrame,
+ category: str,
+ date: Optional[str] = None) -> pd.DataFrame:
+ """Траты по категории за последние 3 месяца"""
+ try:
+ if date is None:
+ target_date = datetime.now()
+ else:
+ target_date = datetime.strptime(date, '%Y-%m-%d')
+
+ # Расчет даты начала периода (3 месяца назад)
+ start_date = target_date - timedelta(days=90)
+
+ # Фильтрация данных
+ mask = (transactions['Дата операции'] >= start_date) & \
+ (transactions['Дата операции'] <= target_date) & \
+ (transactions['Категория'] == category) & \
+ (transactions['Статус'] == 'OK') & \
+ (transactions['Сумма операции'] > 0)
+
+ filtered_data = transactions.loc[mask].copy()
+
+ if filtered_data.empty:
+ return pd.DataFrame(columns=['Месяц', 'Сумма'])
+
+ # Группировка по месяцам
+ filtered_data['Месяц'] = filtered_data['Дата операции'].dt.to_period('M')
+ monthly_spending = filtered_data.groupby('Месяц')['Сумма операции'].sum().reset_index()
+ monthly_spending['Месяц'] = monthly_spending['Месяц'].astype(str)
+ monthly_spending['Сумма'] = monthly_spending['Сумма операции'].round(2)
+
+ return monthly_spending[['Месяц', 'Сумма']]
+
+ except Exception as e:
+ logger.error(f"Ошибка в spending_by_category: {e}")
+ return pd.DataFrame()
+
+ @staticmethod
+ @report_decorator()
+ def spending_by_weekday(transactions: pd.DataFrame,
+ date: Optional[str] = None) -> pd.DataFrame:
+ """Средние траты по дням недели"""
+ try:
+ if date is None:
+ target_date = datetime.now()
+ else:
+ target_date = datetime.strptime(date, '%Y-%m-%d')
+
+ start_date = target_date - timedelta(days=90)
+
+ mask = (transactions['Дата операции'] >= start_date) & \
+ (transactions['Дата операции'] <= target_date) & \
+ (transactions['Статус'] == 'OK') & \
+ (transactions['Сумма операции'] > 0)
+
+ filtered_data = transactions.loc[mask].copy()
+
+ if filtered_data.empty:
+ return pd.DataFrame(columns=['День недели', 'Средняя сумма'])
+
+ # Маппинг дней недели на русский
+ day_mapping = {
+ 0: 'Понедельник',
+ 1: 'Вторник',
+ 2: 'Среда',
+ 3: 'Четверг',
+ 4: 'Пятница',
+ 5: 'Суббота',
+ 6: 'Воскресенье'
+ }
+
+ filtered_data['День недели'] = filtered_data['Дата операции'].dt.weekday.map(day_mapping)
+ avg_spending = filtered_data.groupby('День недели')['Сумма операции'].mean().reset_index()
+ avg_spending['Средняя сумма'] = avg_spending['Сумма операции'].round(2)
+
+ # Сортировка по порядку дней недели
+ day_order = list(day_mapping.values())
+ avg_spending['День недели'] = pd.Categorical(avg_spending['День недели'], categories=day_order,
+ ordered=True)
+ avg_spending = avg_spending.sort_values('День недели')
+
+ return avg_spending[['День недели', 'Средняя сумма']]
+
+ except Exception as e:
+ logger.error(f"Ошибка в spending_by_weekday: {e}")
+ return pd.DataFrame()
+
+ @staticmethod
+ @report_decorator()
+ def spending_by_workday(transactions: pd.DataFrame,
+ date: Optional[str] = None) -> pd.DataFrame:
+ """Средние траты в рабочие/выходные дни"""
+ try:
+ if date is None:
+ target_date = datetime.now()
+ else:
+ target_date = datetime.strptime(date, '%Y-%m-%d')
+
+ start_date = target_date - timedelta(days=90)
+
+ mask = (transactions['Дата операции'] >= start_date) & \
+ (transactions['Дата операции'] <= target_date) & \
+ (transactions['Статус'] == 'OK') & \
+ (transactions['Сумма операции'] > 0)
+
+ filtered_data = transactions.loc[mask].copy()
+
+ if filtered_data.empty:
+ return pd.DataFrame(columns=['Тип дня', 'Средняя сумма'])
+
+ filtered_data['День недели'] = filtered_data['Дата операции'].dt.weekday
+ filtered_data['Тип дня'] = filtered_data['День недели'].apply(
+ lambda x: 'Выходной' if x >= 5 else 'Рабочий'
+ )
+
+ avg_spending = filtered_data.groupby('Тип дня')['Сумма операции'].mean().reset_index()
+ avg_spending['Средняя сумма'] = avg_spending['Сумма операции'].round(2)
+
+ return avg_spending[['Тип дня', 'Средняя сумма']]
+
+ except Exception as e:
+ logger.error(f"Ошибка в spending_by_workday: {e}")
+ return pd.DataFrame()
+
+ @staticmethod
+ @report_decorator()
+ def monthly_summary(transactions: pd.DataFrame,
+ months: int = 6) -> Dict[str, Any]:
+ """Сводный отчет за несколько месяцев"""
+ try:
+ end_date = datetime.now()
+ start_date = end_date - timedelta(days=30 * months)
+
+ mask = (transactions['Дата операции'] >= start_date) & \
+ (transactions['Дата операции'] <= end_date)
+
+ filtered_data = transactions.loc[mask].copy()
+
+ if filtered_data.empty:
+ return {"error": "Нет данных за указанный период"}
+
+ # Расходы по месяцам
+ filtered_data['Месяц'] = filtered_data['Дата операции'].dt.to_period('M')
+ expenses = filtered_data[filtered_data['Сумма операции'] > 0]
+ income = filtered_data[filtered_data['Сумма операции'] < 0]
+
+ monthly_expenses = expenses.groupby('Месяц')['Сумма операции'].sum()
+ monthly_income = income.groupby('Месяц')['Сумма операции'].sum().abs()
+
+ # Топ категории расходов
+ top_categories = expenses.groupby('Категория')['Сумма операции'].sum().nlargest(5)
+
+ report = {
+ "period": f"{start_date.strftime('%Y-%m')} - {end_date.strftime('%Y-%m')}",
+ "total_expenses": round(expenses['Сумма операции'].sum(), 2),
+ "total_income": round(income['Сумма операции'].sum().abs(), 2),
+ "monthly_expenses": {
+ month.strftime('%Y-%m'): round(amount, 2)
+ for month, amount in monthly_expenses.items()
+ },
+ "monthly_income": {
+ month.strftime('%Y-%m'): round(amount, 2)
+ for month, amount in monthly_income.items()
+ },
+ "top_categories": {
+ category: round(amount, 2)
+ for category, amount in top_categories.items()
+ }
+ }
+
+ return report
+
+ except Exception as e:
+ logger.error(f"Ошибка в monthly_summary: {e}")
+ return {"error": str(e)}
+
+
+# Функции-обертки для совместимости
+def spending_by_category(transactions: pd.DataFrame,
+ category: str,
+ date: Optional[str] = None) -> pd.DataFrame:
+ return ReportGenerator.spending_by_category(transactions, category, date)
+
+
+def spending_by_weekday(transactions: pd.DataFrame,
+ date: Optional[str] = None) -> pd.DataFrame:
+ return ReportGenerator.spending_by_weekday(transactions, date)
+
+
+def spending_by_workday(transactions: pd.DataFrame,
+ date: Optional[str] = None) -> pd.DataFrame:
+ return ReportGenerator.spending_by_workday(transactions, date)
diff --git a/src/services.py b/src/services.py
index 0225262..b2ae28c 100644
--- a/src/services.py
+++ b/src/services.py
@@ -1,74 +1,232 @@
-import json
import logging
import re
from datetime import datetime
-from typing import Dict, List, Any, Optional
+from functools import reduce, wraps
+from typing import Any, Callable, Dict, List
+
+import pandas as pd
logger = logging.getLogger(__name__)
-def investment_bank(month: str, transactions: List[Dict[str, Any]], limit: int) -> float:
- """Расчет суммы для инвесткопилки через округление трат."""
- try:
- total_savings = 0.0
+class CashbackAnalyzer:
+ """Анализатор выгодности категорий кешбэка"""
+
+ def __init__(self, cashback_rules: Dict[str, float]):
+ self.cashback_rules = cashback_rules
+
+ def analyze_profitable_categories(self, data: pd.DataFrame, year: int,
+ month: int) -> Dict[str, float]:
+ """Анализ выгодности категорий повышенного кешбэка"""
+ try:
+ # Фильтрация данных по году и месяцу
+ mask = (data['Дата операции'].dt.year == year) & \
+ (data['Дата операции'].dt.month == month) & \
+ (data['Статус'] == 'OK') & \
+ (data['Сумма операции'] > 0) # Только расходы
+
+ filtered_data = data.loc[mask]
+
+ if filtered_data.empty:
+ logger.warning(f"Нет данных за {month}/{year}")
+ return {}
+
+ # Группировка по категориям и расчет потенциального кешбэка
+ cashback_analysis = {}
+
+ for category in filtered_data['Категория'].unique():
+ if pd.isna(category) or category == '':
+ continue
+
+ category_data = filtered_data[filtered_data['Категория'] == category]
+
+ # Расчет кешбэка по сложной логике
+ from .utils import calculate_cashback
+ potential_cashback = sum(
+ calculate_cashback(row['Сумма операции'], category, self.cashback_rules)
+ for _, row in category_data.iterrows()
+ )
+
+ cashback_analysis[category] = round(potential_cashback, 2)
+
+ # Сортировка по убыванию кешбэка
+ return dict(sorted(cashback_analysis.items(),
+ key=lambda x: x[1], reverse=True))
+
+ except Exception as e:
+ logger.error(f"Ошибка анализа категорий кешбэка: {e}")
+ return {}
+
+
+class InvestmentCalculator:
+ """Калькулятор инвесткопилки"""
+
+ @staticmethod
+ def investment_bank(month: str, transactions: List[Dict[str, Any]],
+ limit: int) -> float:
+ """Расчет суммы для инвесткопилки"""
+ try:
+ if limit not in [10, 50, 100]:
+ raise ValueError("Лимит округления должен быть 10, 50 или 100")
+
+ target_month = datetime.strptime(month, '%Y-%m')
+ total_savings = 0.0
+
+ for transaction in transactions:
+ # Валидация транзакции
+ if not InvestmentCalculator._validate_transaction(transaction):
+ continue
+
+ trans_date = datetime.strptime(transaction['Дата операции'], '%Y-%m-%d')
+
+ if (trans_date.year == target_month.year
+ and trans_date.month == target_month.month):
+
+ amount = float(transaction['Сумма операции'])
+ if amount > 0: # Только расходы
+ # Округление вверх до ближайшего кратного limit
+ rounded_amount = ((amount + limit - 1) // limit) * limit
+ savings = rounded_amount - amount
+ total_savings += savings
+
+ return round(total_savings, 2)
+
+ except Exception as e:
+ logger.error(f"Ошибка расчета инвесткопилки: {e}")
+ return 0.0
+
+ @staticmethod
+ def _validate_transaction(transaction: Dict[str, Any]) -> bool:
+ """Валидация транзакции"""
+ required_fields = ['Дата операции', 'Сумма операции']
+
+ for field in required_fields:
+ if field not in transaction:
+ logger.warning(f"Отсутствует поле {field} в транзакции")
+ return False
+
+ try:
+ datetime.strptime(transaction['Дата операции'], '%Y-%m-%d')
+ float(transaction['Сумма операции'])
+ return True
+ except (ValueError, TypeError):
+ logger.warning("Некорректные данные в транзакции")
+ return False
+
+
+class TransactionSearcher:
+ """Поисковик транзакций"""
+
+ @staticmethod
+ def simple_search(transactions: List[Dict[str, Any]],
+ search_string: str) -> List[Dict[str, Any]]:
+ """Простой поиск по описанию и категории"""
+ if not search_string or len(search_string.strip()) < 2:
+ raise ValueError("Строка поиска должна содержать минимум 2 символа")
+
+ def search_filter(transaction: Dict[str, Any]) -> bool:
+ description = str(transaction.get('Описание', '')).lower()
+ category = str(transaction.get('Категория', '')).lower()
+ search_lower = search_string.lower()
+
+ return (search_lower in description
+ or search_lower in category)
+
+ return list(filter(search_filter, transactions))
+
+ @staticmethod
+ def search_by_phone(transactions: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
+ """Поиск транзакций с телефонными номерами"""
+ # Паттерны для российских мобильных номеров
+ phone_patterns = [
+ r'\+7\s?\d{3}\s?\d{3}[\s-]?\d{2}[\s-]?\d{2}', # +7 XXX XXX-XX-XX
+ r'8\s?\d{3}\s?\d{3}[\s-]?\d{2}[\s-]?\d{2}', # 8 XXX XXX-XX-XX
+ r'\d{3}[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}' # XXX XXX-XX-XX
+ ]
+
+ def phone_filter(transaction: Dict[str, Any]) -> bool:
+ description = str(transaction.get('Описание', ''))
+
+ for pattern in phone_patterns:
+ if re.search(pattern, description):
+ return True
+ return False
+
+ return list(filter(phone_filter, transactions))
+
+ @staticmethod
+ def search_by_person_transfers(transactions: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
+ """Поиск переводов физическим лицам"""
+ # Паттерн для имени и фамилии с инициалом: "Имя Ф."
+ name_pattern = r'[А-Я][а-я]+\s[А-Я]\.'
+
+ def transfer_filter(transaction: Dict[str, Any]) -> bool:
+ category = transaction.get('Категория', '')
+ description = str(transaction.get('Описание', ''))
+
+ return (category == 'Переводы'
+ and bool(re.search(name_pattern, description)))
+
+ return list(filter(transfer_filter, transactions))
+
+
+# Функциональные утилиты
+def compose(*functions: Callable) -> Callable:
+ """Композиция функций"""
+ return reduce(lambda f, g: lambda x: f(g(x)), functions)
+
+
+def pipe(value: Any, *functions: Callable) -> Any:
+ """Конвейерная обработка значения через функции"""
+ return compose(*functions)(value)
+
- for transaction in transactions:
- # Проверяем, что транзакция в нужном месяце
- trans_date = datetime.strptime(transaction['Дата операции'], '%Y-%m-%d')
- if trans_date.strftime('%Y-%m') != month:
- continue
+# Декораторы для логирования
+def log_service_call(service_name: str):
+ """Декоратор для логирования вызовов сервисов"""
- amount = abs(transaction['Сумма операции'])
- if amount > 0: # Только расходы
- rounded_amount = _round_up_to_nearest(amount, limit)
- savings = rounded_amount - amount
- total_savings += savings
+ def decorator(func: Callable) -> Callable:
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ logger.info(f"Вызов сервиса {service_name}")
+ try:
+ result = func(*args, **kwargs)
+ logger.info(f"Сервис {service_name} выполнен успешно")
+ return result
+ except Exception as e:
+ logger.error(f"Ошибка в сервисе {service_name}: {e}")
+ raise
- logger.info(f"Сумма для инвесткопилки за {month}: {total_savings}")
- return round(total_savings, 2)
- except Exception as e:
- logger.error(f"Ошибка расчета инвесткопилки: {e}")
- return 0.0
+ return wrapper
+ return decorator
-def _round_up_to_nearest(amount: float, limit: int) -> float:
- """Округление до ближайшего кратного limit."""
- return ((amount + limit - 1) // limit) * limit
+# Экспорт основных функций с декораторами
+@log_service_call("profitable_cashback_categories")
+def profitable_cashback_categories(data: pd.DataFrame, year: int,
+ month: int, cashback_rules: Dict[str, float]) -> Dict[str, float]:
+ analyzer = CashbackAnalyzer(cashback_rules)
+ return analyzer.analyze_profitable_categories(data, year, month)
-def simple_search(query: str, transactions: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
- """Простой поиск транзакций по описанию или категории."""
- try:
- results = []
- query_lower = query.lower()
- for transaction in transactions:
- description = transaction.get('Описание', '').lower()
- category = transaction.get('Категория', '').lower()
+@log_service_call("investment_bank")
+def investment_bank(month: str, transactions: List[Dict[str, Any]],
+ limit: int) -> float:
+ return InvestmentCalculator.investment_bank(month, transactions, limit)
- if query_lower in description or query_lower in category:
- results.append(transaction)
- logger.info(f"Найдено {len(results)} транзакций по запросу '{query}'")
- return results
- except Exception as e:
- logger.error(f"Ошибка поиска: {e}")
- return []
+@log_service_call("simple_search")
+def simple_search(transactions: List[Dict[str, Any]],
+ search_string: str) -> List[Dict[str, Any]]:
+ return TransactionSearcher.simple_search(transactions, search_string)
-def find_phone_transactions(transactions: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
- """Поиск транзакций с телефонными номерами в описании."""
- try:
- phone_pattern = r'\+7\s?\(?\d{3}\)?\s?\d{3}[\s-]?\d{2}[\s-]?\d{2}'
- results = []
+@log_service_call("search_by_phone")
+def search_by_phone(transactions: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
+ return TransactionSearcher.search_by_phone(transactions)
- for transaction in transactions:
- description = transaction.get('Описание', '')
- if re.search(phone_pattern, description):
- results.append(transaction)
- logger.info(f"Найдено {len(results)} транзакций с телефонными номерами")
- return results
- except Exception as e:
- logger.error(f"Ошибка поиска телефонных номеров: {e}")
- return []
+@log_service_call("search_by_person_transfers")
+def search_by_person_transfers(transactions: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
+ return TransactionSearcher.search_by_person_transfers(transactions)
diff --git a/src/utils.py b/src/utils.py
index 8a0a74d..9af4160 100644
--- a/src/utils.py
+++ b/src/utils.py
@@ -1,103 +1,296 @@
import json
import logging
-import pandas as pd
-import requests
from datetime import datetime, timedelta
-from typing import Dict, List, Any, Optional
-import os
-from dotenv import load_dotenv
+from typing import Any, Dict, List, Tuple
+
+import pandas as pd
-load_dotenv()
+from src.config import settings
-logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
-def load_transactions(file_path: str) -> pd.DataFrame:
- """Загрузка транзакций из Excel файла."""
- try:
+class DataValidator :
+ """Валидатор данных транзакций"""
+
+ @staticmethod
+ def validate_transaction_data(df: pd.DataFrame) -> Tuple[pd.DataFrame, List[str]] :
+ """Проверка и очистка данных транзакций"""
+ errors = []
+
+ # Проверка обязательных колонок
+ required_columns = ['Дата операции', 'Сумма операции', 'Статус']
+ missing_columns = [col for col in required_columns if col not in df.columns]
+ if missing_columns :
+ raise ValueError(f"Отсутствуют обязательные колонки: {missing_columns}")
+
+ # Копируем данные для очистки
+ clean_df = df.copy()
+
+ # Обработка дат
+ clean_df, date_errors = DataValidator._process_dates(clean_df)
+ errors.extend(date_errors)
+
+ # Обработка числовых полей
+ clean_df, numeric_errors = DataValidator._process_numeric_fields(clean_df)
+ errors.extend(numeric_errors)
+
+ # Обработка текстовых полей
+ clean_df, text_errors = DataValidator._process_text_fields(clean_df)
+ errors.extend(text_errors)
+
+ # Удаление дубликатов
+ initial_count = len(clean_df)
+ clean_df = clean_df.drop_duplicates()
+ if len(clean_df) < initial_count :
+ errors.append(f"Удалено {initial_count - len(clean_df)} дубликатов")
+
+ # Удаление строк с критическими ошибками
+ initial_count = len(clean_df)
+ clean_df = clean_df.dropna(subset=['Дата операции', 'Сумма операции', 'Статус'])
+ if len(clean_df) < initial_count :
+ errors.append(f"Удалено {initial_count - len(clean_df)} строк с некорректными данными")
+
+ return clean_df, errors
+
+ @staticmethod
+ def _process_dates(df: pd.DataFrame) -> Tuple[pd.DataFrame, List[str]] :
+ """Обработка и валидация дат"""
+ errors = []
+ clean_df = df.copy()
+
+ date_columns = ['Дата операции', 'Дата платежа']
+
+ for col in date_columns :
+ if col not in clean_df.columns :
+ continue
+
+ original_non_null = clean_df[col].notna().sum()
+
+ # Пробуем разные форматы дат
+ for date_format in settings.date_formats :
+ try :
+ clean_df[col] = pd.to_datetime(
+ clean_df[col],
+ format=date_format,
+ errors='coerce'
+ )
+ # Если удалось преобразовать большинство дат, используем этот формат
+ if clean_df[col].notna().sum() > original_non_null * 0.8 :
+ break
+ except Exception:
+ continue
+
+ # Убираем устаревший параметр infer_datetime_format
+ # Просто используем errors='coerce' для оставшихся проблемных значений
+ if clean_df[col].isna().any() :
+ clean_df[col] = pd.to_datetime(clean_df[col], errors='coerce')
+
+ # Проверяем разумность дат (не в будущем и не слишком в прошлом)
+ if col in clean_df.columns and clean_df[col].notna().any() :
+ max_date = datetime.now() + timedelta(days=1) # Завтра
+ min_date = datetime(2000, 1, 1) # 2000 год
+
+ invalid_dates = clean_df[
+ (clean_df[col] > max_date) | (clean_df[col] < min_date)]
+
+ if len(invalid_dates) > 0 :
+ errors.append(f"Найдено {len(invalid_dates)} некорректных дат в колонке {col}")
+ clean_df.loc[invalid_dates.index, col] = pd.NaT
+
+ return clean_df, errors
+
+ @staticmethod
+ def _process_numeric_fields(df: pd.DataFrame) -> Tuple[pd.DataFrame, List[str]] :
+ """Обработка числовых полей"""
+ errors = []
+ clean_df = df.copy()
+
+ numeric_columns = ['Сумма операции', 'Сумма платежа', 'Кешбэк', 'Бонусы (включая кешбэк)',
+ 'Округление на «Инвесткопилку»', 'Сумма операции с округлением']
+
+ for col in numeric_columns :
+ if col not in clean_df.columns :
+ continue
+
+ # Заменяем запятые на точки и преобразуем в числа
+ clean_df[col] = pd.to_numeric(
+ clean_df[col].astype(str).str.replace(',', '.'),
+ errors='coerce'
+ )
+
+ # Проверяем на выбросы (суммы больше 10 млн)
+ if clean_df[col].notna().any() :
+ outliers = clean_df[clean_df[col].abs() > 10000000]
+ if len(outliers) > 0 :
+ errors.append(f"Найдено {len(outliers)} выбросов в колонке {col}")
+
+ return clean_df, errors
+
+ @staticmethod
+ def _process_text_fields(df: pd.DataFrame) -> Tuple[pd.DataFrame, List[str]] :
+ """Обработка текстовых полей"""
+ errors = []
+ clean_df = df.copy()
+
+ text_columns = ['Статус', 'Категория', 'Описание', 'Номер карты']
+
+ for col in text_columns :
+ if col not in clean_df.columns :
+ continue
+
+ clean_df[col] = clean_df[col].astype(str).str.strip()
+
+ # Замена NaN строк
+ clean_df[col] = clean_df[col].replace('nan', '').replace('None', '')
+
+ # Проверка на слишком длинные тексты
+ if col == 'Описание' :
+ too_long = clean_df[clean_df[col].str.len() > 500]
+ if len(too_long) > 0 :
+ errors.append(f"Найдено {len(too_long)} очень длинных описаний")
+ clean_df.loc[too_long.index, col] = clean_df.loc[too_long.index, col].str[:500]
+
+ return clean_df, errors
+
+
+def load_transactions(file_path: str = settings.data_file_path) -> pd.DataFrame :
+ """Загрузка и валидация транзакций из Excel файла"""
+ try :
+ logger.info(f"Загрузка данных из {file_path}")
+
+ # Чтение файла
df = pd.read_excel(file_path)
- logger.info(f"Успешно загружено {len(df)} транзакций")
- return df
- except Exception as e:
- logger.error(f"Ошибка загрузки файла: {e}")
+
+ if df.empty :
+ raise ValueError("Файл не содержит данных")
+
+ # Валидация и очистка данных
+ clean_df, errors = DataValidator.validate_transaction_data(df)
+
+ if errors :
+ logger.warning(f"Обнаружены проблемы при загрузке данных: {errors}")
+
+ logger.info(f"Успешно загружено {len(clean_df)} транзакций")
+ return clean_df
+
+ except Exception as e :
+ logger.error(f"Ошибка загрузки транзакций: {e}")
raise
-def filter_transactions_by_date(df: pd.DataFrame, date_str: str) -> pd.DataFrame:
- """Фильтрация транзакций по дате."""
- try:
- target_date = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
- start_of_month = target_date.replace(day=1, hour=0, minute=0, second=0)
+def get_date_range(date_str: str, period: str = 'M') -> Tuple[datetime, datetime] :
+ """Получение диапазона дат для анализа"""
+ try :
+ # Парсим дату с учетом времени
+ if ' ' in date_str :
+ date = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S')
+ else :
+ date = datetime.strptime(date_str, '%Y-%m-%d')
+
+ if period == 'W' : # Неделя
+ start_date = date - timedelta(days=date.weekday())
+ start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0)
+ end_date = start_date + timedelta(days=6)
+ elif period == 'M' : # Месяц
+ start_date = date.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
+ next_month = date.replace(day=28) + timedelta(days=4)
+ end_date = min(next_month.replace(day=1) - timedelta(days=1), date)
+ elif period == 'Y' : # Год
+ start_date = date.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
+ end_date = date
+ elif period == 'ALL' : # Все данные
+ start_date = datetime(2000, 1, 1)
+ end_date = date
+ else :
+ raise ValueError(f"Неизвестный период: {period}")
- df['Дата операции'] = pd.to_datetime(df['Дата операции'])
- filtered_df = df[(df['Дата операции'] >= start_of_month) &
- (df['Дата операции'] <= target_date)]
+ return start_date, end_date
- logger.info(f"Отфильтровано {len(filtered_df)} транзакций за период")
- return filtered_df
- except Exception as e:
- logger.error(f"Ошибка фильтрации по дате: {e}")
+ except ValueError as e :
+ logger.error(f"Ошибка парсинга даты {date_str}: {e}")
raise
-def get_greeting(time_str: str) -> str:
- """Получение приветствия в зависимости от времени суток."""
- try:
- time_obj = datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S").time()
+def filter_transactions_by_date(df: pd.DataFrame, start_date: datetime,
+ end_date: datetime) -> pd.DataFrame :
+ """Фильтрация транзакций по диапазону дат"""
+ mask = (df['Дата операции'] >= start_date) & (df['Дата операции'] <= end_date)
+ filtered_df = df.loc[mask].copy()
- if time_obj.hour < 6:
- return "Доброй ночи"
- elif time_obj.hour < 12:
+ logger.info(f"Отфильтровано {len(filtered_df)} транзакций за период {start_date.date()} - {end_date.date()}")
+ return filtered_df
+
+
+def get_greeting(time_str: str) -> str :
+ """Получение приветствия в зависимости от времени"""
+ try :
+ if ' ' in time_str :
+ hour = datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S').hour
+ else :
+ hour = datetime.strptime(time_str, '%Y-%m-%d').hour
+
+ if 5 <= hour < 12 :
return "Доброе утро"
- elif time_obj.hour < 18:
+ elif 12 <= hour < 17 :
return "Добрый день"
- else:
+ elif 17 <= hour < 23 :
return "Добрый вечер"
- except Exception as e:
- logger.error(f"Ошибка определения приветствия: {e}")
- return "Добрый день"
-
-
-def get_currency_rates(currencies: List[str]) -> List[Dict[str, Any]]:
- """Получение курсов валют через API."""
- try:
- # Заглушка для демонстрации - в реальном проекте используйте реальное API
- rates = []
- for currency in currencies:
- if currency == "USD":
- rates.append({"currency": currency, "rate": 73.21})
- elif currency == "EUR":
- rates.append({"currency": currency, "rate": 87.08})
- else:
- rates.append({"currency": currency, "rate": 1.0})
-
- logger.info("Курсы валют успешно получены")
- return rates
- except Exception as e:
- logger.error(f"Ошибка получения курсов валют: {e}")
- return []
-
-
-def get_stock_prices(stocks: List[str]) -> List[Dict[str, Any]]:
- """Получение цен акций через API."""
- try:
- # Заглушка для демонстрации
- prices = []
- stock_prices = {
- "AAPL": 150.12,
- "AMZN": 3173.18,
- "GOOGL": 2742.39,
- "MSFT": 296.71,
- "TSLA": 1007.08
- }
-
- for stock in stocks:
- price = stock_prices.get(stock, 0.0)
- prices.append({"stock": stock, "price": price})
-
- logger.info("Цены акций успешно получены")
- return prices
- except Exception as e:
- logger.error(f"Ошибка получения цен акций: {e}")
- return []
+ else :
+ return "Доброй ночи"
+
+ except ValueError :
+ return "Добрый день" # По умолчанию
+
+
+def load_user_settings() -> Dict[str, Any] :
+ """Загрузка пользовательских настроек"""
+ try :
+ with open(settings.user_settings_path, 'r', encoding='utf-8') as f :
+ settings_data = json.load(f)
+
+ # Валидация настроек
+ required_sections = ['user_currencies', 'user_stocks']
+ for section in required_sections :
+ if section not in settings_data :
+ raise ValueError(f"Отсутствует обязательный раздел {section} в настройках")
+
+ return settings_data
+
+ except FileNotFoundError :
+ logger.error(f"Файл настроек {settings.user_settings_path} не найден")
+ raise
+ except json.JSONDecodeError as e :
+ logger.error(f"Ошибка парсинга JSON в настройках: {e}")
+ raise
+
+
+def calculate_cashback(amount: float, category: str, cashback_rules: Dict[str, float]) -> float :
+ """Расчет кешбэка по сложной логике"""
+ try :
+ # Базовая ставка
+ base_rate = cashback_rules.get('default', 0.01)
+
+ # Повышенный кешбэк для категорий
+ category_rate = cashback_rules.get(category, base_rate)
+
+ # Дополнительный бонус для больших покупок (ИСПРАВЛЕНО)
+ bonus_rate = 0.0
+ if amount > 10000 :
+ bonus_rate = 0.02 # +2% для покупок > 10,000
+ elif amount > 5000 :
+ bonus_rate = 0.01 # +1% для покупок > 5,000
+
+ total_rate = category_rate + bonus_rate
+
+ # Ограничение максимального кешбэка 15%
+ total_rate = min(total_rate, 0.15)
+
+ cashback = amount * total_rate
+
+ # Округление до 2 знаков
+ return round(cashback, 2)
+
+ except Exception as e :
+ logger.warning(f"Ошибка расчета кешбэка: {e}")
+ return round(amount * 0.01, 2) # Fallback 1%
diff --git a/src/views.py b/src/views.py
index e856e82..e3a7ce1 100644
--- a/src/views.py
+++ b/src/views.py
@@ -1,80 +1,213 @@
-import json
import logging
+from typing import Any, Dict, List, Optional
+
import pandas as pd
-from datetime import datetime
-from typing import Dict, List, Any
-from .utils import get_greeting, get_currency_rates, get_stock_prices, filter_transactions_by_date
+
+from .api_client import SyncAPIClient
+from .utils import (
+ calculate_cashback,
+ filter_transactions_by_date,
+ get_date_range,
+ get_greeting,
+ load_transactions,
+ load_user_settings,
+)
logger = logging.getLogger(__name__)
-def home_page(date_str: str, transactions_df: pd.DataFrame, user_settings: Dict[str, Any]) -> Dict[str, Any]:
- """Главная страница с анализом транзакций."""
- try:
- # Фильтрация транзакций
- filtered_df = filter_transactions_by_date(transactions_df, date_str)
+class DataProcessor:
+ """Процессор данных для веб-страниц"""
+
+ @staticmethod
+ def process_main_page_data(df: pd.DataFrame, date_time: str,
+ settings: Dict[str, Any]) -> Dict[str, Any]:
+ """Обработка данных для главной страницы"""
+ try:
+ start_date, end_date = get_date_range(date_time, 'M')
+ filtered_df = filter_transactions_by_date(df, start_date, end_date)
+
+ cashback_rules = settings.get('cashback_rules', {'default': 0.01})
+
+ return {
+ 'greeting': get_greeting(date_time),
+ 'cards': DataProcessor._get_cards_data(filtered_df, cashback_rules),
+ 'top_transactions': DataProcessor._get_top_transactions(filtered_df, 5),
+ 'currency_rates': SyncAPIClient.get_currency_rates(settings['user_currencies']),
+ 'stock_prices': SyncAPIClient.get_stock_prices(settings['user_stocks'])
+ }
+ except Exception as e:
+ logger.error(f"Ошибка обработки данных главной страницы: {e}")
+ raise
+
+ @staticmethod
+ def process_events_page_data(df: pd.DataFrame, date: str, period: str,
+ settings: Dict[str, Any]) -> Dict[str, Any]:
+ """Обработка данных для страницы событий"""
+ try:
+ start_date, end_date = get_date_range(date, period)
+ filtered_df = filter_transactions_by_date(df, start_date, end_date)
+
+ return {
+ 'expenses': DataProcessor._get_expenses_data(filtered_df),
+ 'income': DataProcessor._get_income_data(filtered_df),
+ 'currency_rates': SyncAPIClient.get_currency_rates(settings['user_currencies']),
+ 'stock_prices': SyncAPIClient.get_stock_prices(settings['user_stocks'])
+ }
+ except Exception as e:
+ logger.error(f"Ошибка обработки данных страницы событий: {e}")
+ raise
+
+ @staticmethod
+ def _get_cards_data(df: pd.DataFrame, cashback_rules: Dict[str, float]) -> List[Dict[str, Any]]:
+ """Данные по картам"""
+ cards_data = []
+
+ # Получаем уникальные номера карт
+ card_numbers = [card for card in df['Номер карты'].unique()
+ if not pd.isna(card) and str(card).strip() != '']
+
+ for card in card_numbers:
+ card_df = df[df['Номер карты'] == card]
+ # Только успешные операции расходов
+ expenses_df = card_df[(card_df['Статус'] == 'OK')
+ & (card_df['Сумма операции'] > 0)]
+
+ if expenses_df.empty:
+ continue
+
+ total_spent = expenses_df['Сумма операции'].sum()
+
+ # Расчет общего кешбэка по сложной логике
+ total_cashback = 0
+ for _, transaction in expenses_df.iterrows():
+ category = transaction.get('Категория', '')
+ amount = transaction['Сумма операции']
+ total_cashback += calculate_cashback(amount, category, cashback_rules)
+
+ cards_data.append({
+ 'last_digits': str(card)[-4:],
+ 'total_spent': round(total_spent, 2),
+ 'cashback': round(total_cashback, 2)
+ })
+
+ # Сортировка по убыванию общей суммы расходов
+ return sorted(cards_data, key=lambda x: x['total_spent'], reverse=True)
+
+ @staticmethod
+ def _get_top_transactions(df: pd.DataFrame, limit: int) -> List[Dict[str, Any]]:
+ """Топ транзакций по сумме платежа"""
+ # Берем абсолютное значение для сравнения (учитываем и доходы и расходы)
+ df['Абсолютная сумма'] = df['Сумма платежа'].abs()
+ top_df = df.nlargest(limit, 'Абсолютная сумма')
+
+ transactions = []
+ for _, row in top_df.iterrows():
+ transactions.append({
+ 'date': row['Дата операции'].strftime('%d.%m.%Y'),
+ 'amount': round(row['Сумма платежа'], 2),
+ 'category': row.get('Категория', 'Не указана'),
+ 'description': row.get('Описание', '')[:100] # Ограничение длины
+ })
+
+ return transactions
- # Приветствие
- greeting = get_greeting(date_str)
+ @staticmethod
+ def _get_expenses_data(df: pd.DataFrame) -> Dict[str, Any]:
+ """Данные по расходам"""
+ expenses_df = df[(df['Статус'] == 'OK') & (df['Сумма операции'] > 0)]
- # Анализ по картам
- cards_analysis = _analyze_cards(filtered_df)
+ if expenses_df.empty:
+ return {
+ 'total_amount': 0,
+ 'main': [],
+ 'transfers_and_cash': []
+ }
- # Топ транзакций
- top_transactions = _get_top_transactions(filtered_df)
+ total_amount = expenses_df['Сумма операции'].sum()
- # Курсы валют и акции
- currency_rates = get_currency_rates(user_settings.get("user_currencies", []))
- stock_prices = get_stock_prices(user_settings.get("user_stocks", []))
+ # Основные категории (топ-6 + остальное)
+ category_expenses = expenses_df.groupby('Категория')['Сумма операции'].sum()
+ top_categories = category_expenses.nlargest(6)
+ other_categories = category_expenses.iloc[6:].sum() if len(category_expenses) > 6 else 0
+
+ main_categories = [
+ {'category': cat, 'amount': round(amount, 0)}
+ for cat, amount in top_categories.items() if not pd.isna(cat)
+ ]
+
+ if other_categories > 0:
+ main_categories.append({'category': 'Остальное', 'amount': round(other_categories, 0)})
+
+ # Переводы и наличные
+ transfers_cash = expenses_df[expenses_df['Категория'].isin(['Наличные', 'Переводы'])]
+ transfers_data = transfers_cash.groupby('Категория')['Сумма операции'].sum()
+
+ transfers_list = [
+ {'category': cat, 'amount': round(amount, 0)}
+ for cat, amount in transfers_data.items()
+ ]
return {
- "greeting": greeting,
- "cards": cards_analysis,
- "top_transactions": top_transactions,
- "currency_rates": currency_rates,
- "stock_prices": stock_prices
+ 'total_amount': round(total_amount, 0),
+ 'main': main_categories,
+ 'transfers_and_cash': transfers_list
}
- except Exception as e:
- logger.error(f"Ошибка генерации главной страницы: {e}")
- return {"error": str(e)}
+ @staticmethod
+ def _get_income_data(df: pd.DataFrame) -> Dict[str, Any]:
+ """Данные по поступлениям"""
+ income_df = df[(df['Статус'] == 'OK') & (df['Сумма операции'] < 0)]
-def _analyze_cards(df: pd.DataFrame) -> List[Dict[str, Any]]:
- """Анализ транзакций по картам."""
- cards_analysis = []
+ if income_df.empty:
+ return {
+ 'total_amount': 0,
+ 'main': []
+ }
- # Группировка по последним цифрам карты
- if 'Номер карты' in df.columns:
- for card in df['Номер карты'].dropna().unique():
- card_transactions = df[df['Номер карты'] == card]
- total_spent = card_transactions['Сумма операции'].sum()
- cashback = total_spent * 0.01 # 1% кешбэк
+ # Преобразуем отрицательные суммы в положительные
+ income_df = income_df.copy()
+ income_df['Сумма операции'] = income_df['Сумма операции'].abs()
- cards_analysis.append({
- "last_digits": str(card)[-4:],
- "total_spent": round(total_spent, 2),
- "cashback": round(cashback, 2)
- })
+ total_amount = income_df['Сумма операции'].sum()
- return cards_analysis
+ category_income = income_df.groupby('Категория')['Сумма операции'].sum()
+ main_income = [
+ {'category': cat, 'amount': round(amount, 0)}
+ for cat, amount in category_income.items() if not pd.isna(cat)
+ ]
-def _get_top_transactions(df: pd.DataFrame, top_n: int = 5) -> List[Dict[str, Any]]:
- """Получение топ-N транзакций по сумме."""
+ return {
+ 'total_amount': round(total_amount, 0),
+ 'main': sorted(main_income, key=lambda x: x['amount'], reverse=True)
+ }
+
+
+def main_page(date_time: str, data_file: Optional[str] = None) -> Dict[str, Any]:
+ """Главная страница - генерация JSON данных"""
try:
- # Берем абсолютные значения для сортировки
- df_sorted = df.nlargest(top_n, 'Сумма операции', keep='first')
-
- top_transactions = []
- for _, row in df_sorted.iterrows():
- top_transactions.append({
- "date": row['Дата операции'].strftime("%d.%m.%Y"),
- "amount": round(row['Сумма операции'], 2),
- "category": row.get('Категория', 'Неизвестно'),
- "description": row.get('Описание', '')
- })
+ df = load_transactions(data_file) if data_file else load_transactions()
+ settings = load_user_settings()
+
+ processor = DataProcessor()
+ return processor.process_main_page_data(df, date_time, settings)
+
+ except Exception as e:
+ logger.error(f"Ошибка генерации главной страницы: {e}")
+ return {'error': str(e), 'greeting': 'Добрый день'}
+
+
+def events_page(date: str, period: str = 'M',
+ data_file: Optional[str] = None) -> Dict[str, Any]:
+ """Страница событий - генерация JSON данных"""
+ try:
+ df = load_transactions(data_file) if data_file else load_transactions()
+ settings = load_user_settings()
+
+ processor = DataProcessor()
+ return processor.process_events_page_data(df, date, period, settings)
- return top_transactions
except Exception as e:
- logger.error(f"Ошибка получения топ транзакций: {e}")
- return []
+ logger.error(f"Ошибка генерации страницы событий: {e}")
+ return {'error': str(e)}
diff --git a/test_app.py b/test_app.py
new file mode 100644
index 0000000..3410c30
--- /dev/null
+++ b/test_app.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+"""
+Простой скрипт для тестирования приложения
+"""
+
+import sys
+from pathlib import Path
+
+# Добавляем src в путь Python
+sys.path.insert(0, str(Path(__file__).parent))
+
+# Создаем необходимые директории
+Path('logs').mkdir(exist_ok=True)
+Path('reports').mkdir(exist_ok=True)
+Path('data').mkdir(exist_ok=True)
+
+
+def test_basic_functionality():
+ """Тест базовой функциональности"""
+ try:
+ print("🧪 Тестирование Transaction Analyzer...")
+
+ # Импортируем модули
+ from src.reports import ReportGenerator
+ from src.utils import load_transactions, load_user_settings
+ from src.views import events_page, main_page
+
+ # 1. Загрузка данных
+ print("1. Загрузка данных...")
+ df = load_transactions()
+ settings = load_user_settings()
+ print(f" ✅ Транзакций: {len(df)}")
+ print(f" ✅ Настроек: {len(settings)}")
+
+ # 2. Тест веб-страниц
+ print("2. Тест веб-страниц...")
+ main_data = main_page("2023-12-20 15:30:00")
+ events_data = events_page("2023-12-20", "M")
+ print(f" ✅ Главная страница: {main_data['greeting']}")
+ print(f" ✅ Страница событий: расходы {events_data['expenses']['total_amount']}")
+
+ # 3. Тест отчетов
+ print("3. Тест отчетов...")
+ report = ReportGenerator.spending_by_category(df, "Супермаркеты")
+ print(f" ✅ Отчет по категории: {len(report)} записей")
+
+ # 4. Тест сервисов
+ print("4. Тест сервисов...")
+ from src.services import simple_search
+ transactions_list = df.head(10).to_dict('records')
+ results = simple_search(transactions_list, "магазин")
+ print(f" ✅ Поиск: {len(results)} результатов")
+
+ print("\n🎉 ВСЕ ТЕСТЫ ПРОЙДЕНЫ УСПЕШНО!")
+ return True
+
+ except Exception as e:
+ print(f"\n❌ ОШИБКА: {e}")
+ import traceback
+ traceback.print_exc()
+ return False
+
+
+if __name__ == "__main__":
+ test_basic_functionality()
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..c1a7056
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,39 @@
+import pandas as pd
+import pytest
+
+
+@pytest.fixture
+def sample_transactions():
+ """Фикстура с примером транзакций"""
+ dates = pd.date_range(start='2023-01-01', end='2023-12-31', freq='D')
+ return pd.DataFrame({
+ 'Дата операции': dates[:100],
+ 'Номер карты': ['1234567812345814'] * 50 + ['1234567812347512'] * 50,
+ 'Статус': ['OK'] * 100,
+ 'Сумма операции': [1000.0, 500.0] * 50,
+ 'Категория': ['Супермаркеты', 'Фастфуд'] * 50,
+ 'Описание': [f'Транзакция {i}' for i in range(100)],
+ 'Сумма платежа': [1000.0, 500.0] * 50
+ })
+
+
+@pytest.fixture
+def sample_settings():
+ """Фикстура с настройками"""
+ return {
+ "user_currencies": ["USD", "EUR"],
+ "user_stocks": ["AAPL", "AMZN"],
+ "cashback_rules": {
+ "Супермаркеты": 0.05,
+ "Фастфуд": 0.03,
+ "default": 0.01
+ }
+ }
+
+
+@pytest.fixture(autouse=True)
+def setup_teardown():
+ """Фикстура для настройки перед каждым тестом"""
+ # Настройка перед тестом
+ yield
+ # Очистка после теста
diff --git a/tests/reports/monthly_summary_20231220_000000.json b/tests/reports/monthly_summary_20231220_000000.json
new file mode 100644
index 0000000..c62910a
--- /dev/null
+++ b/tests/reports/monthly_summary_20231220_000000.json
@@ -0,0 +1,3 @@
+{
+ "error": "'numpy.float64' object has no attribute 'abs'"
+}
\ No newline at end of file
diff --git a/tests/reports/monthly_summary_20250930_221851.json b/tests/reports/monthly_summary_20250930_221851.json
new file mode 100644
index 0000000..b19b0ab
--- /dev/null
+++ b/tests/reports/monthly_summary_20250930_221851.json
@@ -0,0 +1,3 @@
+{
+ "error": "Нет данных за указанный период"
+}
\ No newline at end of file
diff --git a/tests/reports/monthly_summary_20250930_222642.json b/tests/reports/monthly_summary_20250930_222642.json
new file mode 100644
index 0000000..b19b0ab
--- /dev/null
+++ b/tests/reports/monthly_summary_20250930_222642.json
@@ -0,0 +1,3 @@
+{
+ "error": "Нет данных за указанный период"
+}
\ No newline at end of file
diff --git a/tests/reports/monthly_summary_20250930_222950.json b/tests/reports/monthly_summary_20250930_222950.json
new file mode 100644
index 0000000..b19b0ab
--- /dev/null
+++ b/tests/reports/monthly_summary_20250930_222950.json
@@ -0,0 +1,3 @@
+{
+ "error": "Нет данных за указанный период"
+}
\ No newline at end of file
diff --git a/tests/reports/monthly_summary_20250930_222959.json b/tests/reports/monthly_summary_20250930_222959.json
new file mode 100644
index 0000000..b19b0ab
--- /dev/null
+++ b/tests/reports/monthly_summary_20250930_222959.json
@@ -0,0 +1,3 @@
+{
+ "error": "Нет данных за указанный период"
+}
\ No newline at end of file
diff --git a/tests/reports/monthly_summary_20250930_223102.json b/tests/reports/monthly_summary_20250930_223102.json
new file mode 100644
index 0000000..b19b0ab
--- /dev/null
+++ b/tests/reports/monthly_summary_20250930_223102.json
@@ -0,0 +1,3 @@
+{
+ "error": "Нет данных за указанный период"
+}
\ No newline at end of file
diff --git a/tests/reports/monthly_summary_20250930_224838.json b/tests/reports/monthly_summary_20250930_224838.json
new file mode 100644
index 0000000..b19b0ab
--- /dev/null
+++ b/tests/reports/monthly_summary_20250930_224838.json
@@ -0,0 +1,3 @@
+{
+ "error": "Нет данных за указанный период"
+}
\ No newline at end of file
diff --git a/tests/reports/monthly_summary_20250930_225857.json b/tests/reports/monthly_summary_20250930_225857.json
new file mode 100644
index 0000000..b19b0ab
--- /dev/null
+++ b/tests/reports/monthly_summary_20250930_225857.json
@@ -0,0 +1,3 @@
+{
+ "error": "Нет данных за указанный период"
+}
\ No newline at end of file
diff --git a/tests/reports/monthly_summary_20250930_225902.json b/tests/reports/monthly_summary_20250930_225902.json
new file mode 100644
index 0000000..b19b0ab
--- /dev/null
+++ b/tests/reports/monthly_summary_20250930_225902.json
@@ -0,0 +1,3 @@
+{
+ "error": "Нет данных за указанный период"
+}
\ No newline at end of file
diff --git a/tests/reports/monthly_summary_20250930_230017.json b/tests/reports/monthly_summary_20250930_230017.json
new file mode 100644
index 0000000..b19b0ab
--- /dev/null
+++ b/tests/reports/monthly_summary_20250930_230017.json
@@ -0,0 +1,3 @@
+{
+ "error": "Нет данных за указанный период"
+}
\ No newline at end of file
diff --git a/tests/reports/monthly_summary_20250930_230123.json b/tests/reports/monthly_summary_20250930_230123.json
new file mode 100644
index 0000000..b19b0ab
--- /dev/null
+++ b/tests/reports/monthly_summary_20250930_230123.json
@@ -0,0 +1,3 @@
+{
+ "error": "Нет данных за указанный период"
+}
\ No newline at end of file
diff --git a/tests/reports/monthly_summary_20250930_230203.json b/tests/reports/monthly_summary_20250930_230203.json
new file mode 100644
index 0000000..b19b0ab
--- /dev/null
+++ b/tests/reports/monthly_summary_20250930_230203.json
@@ -0,0 +1,3 @@
+{
+ "error": "Нет данных за указанный период"
+}
\ No newline at end of file
diff --git a/tests/reports/monthly_summary_20250930_230204.json b/tests/reports/monthly_summary_20250930_230204.json
new file mode 100644
index 0000000..b19b0ab
--- /dev/null
+++ b/tests/reports/monthly_summary_20250930_230204.json
@@ -0,0 +1,3 @@
+{
+ "error": "Нет данных за указанный период"
+}
\ No newline at end of file
diff --git a/tests/reports/monthly_summary_20250930_230205.json b/tests/reports/monthly_summary_20250930_230205.json
new file mode 100644
index 0000000..b19b0ab
--- /dev/null
+++ b/tests/reports/monthly_summary_20250930_230205.json
@@ -0,0 +1,3 @@
+{
+ "error": "Нет данных за указанный период"
+}
\ No newline at end of file
diff --git a/tests/reports/spending_by_category_20250930_221851.json b/tests/reports/spending_by_category_20250930_221851.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_category_20250930_221851.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_category_20250930_222642.json b/tests/reports/spending_by_category_20250930_222642.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_category_20250930_222642.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_category_20250930_222949.json b/tests/reports/spending_by_category_20250930_222949.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_category_20250930_222949.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_category_20250930_222959.json b/tests/reports/spending_by_category_20250930_222959.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_category_20250930_222959.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_category_20250930_223101.json b/tests/reports/spending_by_category_20250930_223101.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_category_20250930_223101.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_category_20250930_224838.json b/tests/reports/spending_by_category_20250930_224838.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_category_20250930_224838.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_category_20250930_225857.json b/tests/reports/spending_by_category_20250930_225857.json
new file mode 100644
index 0000000..685786d
--- /dev/null
+++ b/tests/reports/spending_by_category_20250930_225857.json
@@ -0,0 +1,14 @@
+[
+ {
+ "Месяц": "2023-10",
+ "Сумма": 1000
+ },
+ {
+ "Месяц": "2023-11",
+ "Сумма": 2000
+ },
+ {
+ "Месяц": "2023-12",
+ "Сумма": 3000
+ }
+]
\ No newline at end of file
diff --git a/tests/reports/spending_by_weekday_20250930_221851.json b/tests/reports/spending_by_weekday_20250930_221851.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_weekday_20250930_221851.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_weekday_20250930_222642.json b/tests/reports/spending_by_weekday_20250930_222642.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_weekday_20250930_222642.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_weekday_20250930_222949.json b/tests/reports/spending_by_weekday_20250930_222949.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_weekday_20250930_222949.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_weekday_20250930_222959.json b/tests/reports/spending_by_weekday_20250930_222959.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_weekday_20250930_222959.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_weekday_20250930_223101.json b/tests/reports/spending_by_weekday_20250930_223101.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_weekday_20250930_223101.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_weekday_20250930_224838.json b/tests/reports/spending_by_weekday_20250930_224838.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_weekday_20250930_224838.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_weekday_20250930_225857.json b/tests/reports/spending_by_weekday_20250930_225857.json
new file mode 100644
index 0000000..c0ffc11
--- /dev/null
+++ b/tests/reports/spending_by_weekday_20250930_225857.json
@@ -0,0 +1,30 @@
+[
+ {
+ "День недели": "Понедельник",
+ "Средняя сумма": 100.0
+ },
+ {
+ "День недели": "Вторник",
+ "Средняя сумма": 200.0
+ },
+ {
+ "День недели": "Среда",
+ "Средняя сумма": 300.0
+ },
+ {
+ "День недели": "Четверг",
+ "Средняя сумма": 400.0
+ },
+ {
+ "День недели": "Пятница",
+ "Средняя сумма": 500.0
+ },
+ {
+ "День недели": "Суббота",
+ "Средняя сумма": 600.0
+ },
+ {
+ "День недели": "Воскресенье",
+ "Средняя сумма": 700.0
+ }
+]
\ No newline at end of file
diff --git a/tests/reports/spending_by_workday_20250930_221851.json b/tests/reports/spending_by_workday_20250930_221851.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_workday_20250930_221851.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_workday_20250930_222642.json b/tests/reports/spending_by_workday_20250930_222642.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_workday_20250930_222642.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_workday_20250930_222950.json b/tests/reports/spending_by_workday_20250930_222950.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_workday_20250930_222950.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_workday_20250930_222959.json b/tests/reports/spending_by_workday_20250930_222959.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_workday_20250930_222959.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_workday_20250930_223102.json b/tests/reports/spending_by_workday_20250930_223102.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_workday_20250930_223102.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_workday_20250930_224838.json b/tests/reports/spending_by_workday_20250930_224838.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_workday_20250930_224838.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_workday_20250930_224839.json b/tests/reports/spending_by_workday_20250930_224839.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/reports/spending_by_workday_20250930_224839.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/reports/spending_by_workday_20250930_225857.json b/tests/reports/spending_by_workday_20250930_225857.json
new file mode 100644
index 0000000..ddf1af6
--- /dev/null
+++ b/tests/reports/spending_by_workday_20250930_225857.json
@@ -0,0 +1,10 @@
+[
+ {
+ "Тип дня": "Выходной",
+ "Средняя сумма": 450.0
+ },
+ {
+ "Тип дня": "Рабочий",
+ "Средняя сумма": 200.0
+ }
+]
\ No newline at end of file
diff --git a/tests/reports/test_function_20231220_153000.json b/tests/reports/test_function_20231220_153000.json
new file mode 100644
index 0000000..d6faa34
--- /dev/null
+++ b/tests/reports/test_function_20231220_153000.json
@@ -0,0 +1,10 @@
+[
+ {
+ "col1": 1,
+ "col2": 3
+ },
+ {
+ "col1": 2,
+ "col2": 4
+ }
+]
\ No newline at end of file
diff --git a/tests/test_api_client.py b/tests/test_api_client.py
new file mode 100644
index 0000000..1793af3
--- /dev/null
+++ b/tests/test_api_client.py
@@ -0,0 +1,46 @@
+from unittest.mock import AsyncMock, patch
+
+import pytest
+
+
+class TestAPIClient:
+ @pytest.mark.asyncio
+ async def test_get_currency_rates(self):
+ async with AsyncMock() as mock_session:
+ with patch('aiohttp.ClientSession', return_value=mock_session):
+ from src.api_client import APIClient
+ client = APIClient()
+ client.session = mock_session
+
+ # Мок успешного ответа
+ mock_response = AsyncMock()
+ mock_response.status = 200
+ mock_response.json.return_value = {
+ "rates": {"USD": 0.0107, "EUR": 0.0099},
+ "base": "RUB"
+ }
+ mock_session.get.return_value.__aenter__.return_value = mock_response
+
+ rates = await client.get_currency_rates(["USD", "EUR"])
+ # Ожидаем только запрошенные валюты, RUB не должен включаться если не запрошен
+ assert len(rates) == 2
+ assert any(rate['currency'] == 'USD' for rate in rates)
+ assert any(rate['currency'] == 'EUR' for rate in rates)
+
+ @pytest.mark.asyncio
+ async def test_get_currency_rates_fallback(self):
+ async with AsyncMock() as mock_session:
+ with patch('aiohttp.ClientSession', return_value=mock_session):
+ from src.api_client import APIClient
+ client = APIClient()
+ client.session = mock_session
+
+ # Мок неудачного ответа
+ mock_response = AsyncMock()
+ mock_response.status = 500
+ mock_session.get.return_value.__aenter__.return_value = mock_response
+
+ rates = await client.get_currency_rates(["USD", "EUR"])
+ # Fallback должен вернуть только запрошенные валюты
+ assert len(rates) == 2
+ assert all(rate['rate'] > 0 for rate in rates)
diff --git a/tests/test_main.py b/tests/test_main.py
new file mode 100644
index 0000000..e4eebaf
--- /dev/null
+++ b/tests/test_main.py
@@ -0,0 +1,201 @@
+from unittest.mock import MagicMock, patch
+
+import pytest
+
+from src.main import TransactionAnalyzer, main
+
+
+class TestTransactionAnalyzer:
+ """Тесты для основного класса приложения"""
+
+ @pytest.fixture
+ def analyzer(self):
+ return TransactionAnalyzer()
+
+ def test_initialization(self, analyzer):
+ """Тест инициализации анализатора"""
+ assert analyzer.data_file is not None
+ assert analyzer.transactions_df is None
+ assert analyzer.settings is None
+
+ @patch('src.views.main_page')
+ def test_generate_main_page(self, mock_main_page, analyzer):
+ """Тест генерации главной страницы"""
+ # Мокируем данные
+ analyzer.transactions_df = MagicMock()
+ analyzer.settings = MagicMock()
+ expected_result = {'greeting': 'Добрый день'}
+ mock_main_page.return_value = expected_result
+
+ # Генерируем страницу
+ result = analyzer.generate_main_page('2023-12-20 15:30:00')
+
+ # Проверяем
+ assert result == expected_result
+ mock_main_page.assert_called_once_with('2023-12-20 15:30:00', 'data/operations.xlsx')
+
+ @patch('src.views.events_page')
+ def test_generate_events_page(self, mock_events_page, analyzer):
+ """Тест генерации страницы событий"""
+ # Мокируем данные
+ analyzer.transactions_df = MagicMock()
+ analyzer.settings = MagicMock()
+ expected_result = {'expenses': {'total_amount': 1000}}
+ mock_events_page.return_value = expected_result
+
+ # Генерируем страницу
+ result = analyzer.generate_events_page('2023-12-20', 'M')
+
+ # Проверяем
+ assert result == expected_result
+ mock_events_page.assert_called_once_with('2023-12-20', 'M', 'data/operations.xlsx')
+
+ @patch('src.services.profitable_cashback_categories')
+ def test_analyze_cashback_categories(self, mock_cashback, analyzer):
+ """Тест анализа кешбэка"""
+ # Мокируем данные
+ analyzer.transactions_df = MagicMock()
+ analyzer.settings = {'cashback_rules': {'default': 0.01}}
+ expected_result = {'Супермаркеты': 150.0}
+ mock_cashback.return_value = expected_result
+
+ # Анализируем
+ result = analyzer.analyze_cashback_categories(2023, 12)
+
+ # Проверяем
+ assert result == expected_result
+ mock_cashback.assert_called_once()
+
+ @patch('src.services.investment_bank')
+ def test_calculate_investment(self, mock_investment, analyzer):
+ """Тест расчета инвесткопилки"""
+ # Мокируем данные
+ mock_df = MagicMock()
+ mock_df.to_dict.return_value = [{'Дата операции': '2023-12-01', 'Сумма операции': 1000}]
+ analyzer.transactions_df = mock_df
+ mock_investment.return_value = 50.0
+
+ # Рассчитываем
+ result = analyzer.calculate_investment('2023-12', 50)
+
+ # Проверяем
+ assert result == 50.0
+ mock_investment.assert_called_once()
+
+ @patch('src.services.simple_search')
+ def test_search_transactions(self, mock_search, analyzer):
+ """Тест поиска транзакций"""
+ # Мокируем данные
+ mock_df = MagicMock()
+ mock_df.to_dict.return_value = [{'Описание': 'Магазин'}]
+ analyzer.transactions_df = mock_df
+ mock_search.return_value = [{'Описание': 'Магазин'}]
+
+ # Ищем
+ result = analyzer.search_transactions('магазин')
+
+ # Проверяем
+ assert len(result) == 1
+ mock_search.assert_called_once()
+
+ @patch('src.reports.ReportGenerator')
+ def test_generate_reports(self, mock_report_generator, analyzer):
+ """Тест генерации отчетов"""
+ # Мокируем данные
+ analyzer.transactions_df = MagicMock()
+ mock_report_generator.spending_by_category.return_value = MagicMock()
+ mock_report_generator.spending_by_weekday.return_value = MagicMock()
+ mock_report_generator.spending_by_workday.return_value = MagicMock()
+ mock_report_generator.monthly_summary.return_value = {'total': 1000}
+
+ # Генерируем отчеты
+ reports = analyzer.generate_reports()
+
+ # Проверяем
+ assert 'spending_by_category' in reports
+ assert 'monthly_summary' in reports
+ assert mock_report_generator.spending_by_category.call_count == 1
+
+
+class TestMainFunction:
+ """Тесты для основной функции"""
+
+ @patch('src.main.TransactionAnalyzer')
+ @patch('builtins.print')
+ def test_main_web_command(self, mock_print, mock_analyzer):
+ """Тест main с командой web"""
+ # Мокируем анализатор
+ mock_instance = MagicMock()
+ mock_instance.load_data.return_value = None
+ mock_instance.generate_main_page.return_value = {'greeting': 'Добрый день'}
+ mock_instance.generate_events_page.return_value = {'expenses': {'total_amount': 1000}}
+ mock_analyzer.return_value = mock_instance
+
+ # Запускаем с командой web
+ with patch('sys.argv', ['main.py', '--command', 'web']):
+ main()
+
+ # Проверяем вызовы
+ mock_instance.load_data.assert_called_once()
+ mock_instance.generate_main_page.assert_called_once()
+ mock_instance.generate_events_page.assert_called_once()
+
+ @patch('src.main.TransactionAnalyzer')
+ @patch('builtins.print')
+ def test_main_report_command(self, mock_print, mock_analyzer):
+ """Тест main с командой report"""
+ # Мокируем анализатор
+ mock_instance = MagicMock()
+ mock_instance.load_data.return_value = None
+ mock_instance.generate_reports.return_value = {
+ 'monthly_summary': {'total': 1000}
+ }
+ mock_analyzer.return_value = mock_instance
+
+ # Запускаем с командой report
+ with patch('sys.argv', ['main.py', '--command', 'report']):
+ main()
+
+ # Проверяем вызовы
+ mock_instance.load_data.assert_called_once()
+ mock_instance.generate_reports.assert_called_once()
+
+ @patch('src.main.TransactionAnalyzer')
+ @patch('builtins.print')
+ def test_main_analyze_command(self, mock_print, mock_analyzer):
+ """Тест main с командой analyze"""
+ # Мокируем анализатор
+ mock_instance = MagicMock()
+ mock_instance.load_data.return_value = None
+ mock_instance.analyze_cashback_categories.return_value = {'Категория': 100}
+ mock_instance.calculate_investment.return_value = 50.0
+ mock_analyzer.return_value = mock_instance
+
+ # Запускаем с командой analyze
+ with patch('sys.argv', ['main.py', '--command', 'analyze']):
+ main()
+
+ # Проверяем вызовы
+ mock_instance.load_data.assert_called_once()
+ mock_instance.analyze_cashback_categories.assert_called_once()
+ mock_instance.calculate_investment.assert_called_once()
+
+ @patch('src.main.TransactionAnalyzer')
+ @patch('builtins.print')
+ def test_main_test_command(self, mock_print, mock_analyzer):
+ """Тест main с командой test"""
+ # Мокируем анализатор
+ mock_instance = MagicMock()
+ mock_instance.load_data.return_value = None
+ mock_instance.transactions_df = MagicMock()
+ mock_instance.settings = {'user_currencies': ['USD']}
+ mock_instance.search_transactions.return_value = [{'Описание': 'Магазин'}]
+ mock_analyzer.return_value = mock_instance
+
+ # Запускаем с командой test
+ with patch('sys.argv', ['main.py', '--command', 'test']):
+ main()
+
+ # Проверяем вызовы
+ mock_instance.load_data.assert_called_once()
+ mock_instance.search_transactions.assert_called_once()
diff --git a/tests/test_reports.py b/tests/test_reports.py
new file mode 100644
index 0000000..f89bee8
--- /dev/null
+++ b/tests/test_reports.py
@@ -0,0 +1,251 @@
+import json
+from datetime import datetime
+from unittest.mock import patch
+
+import numpy as np
+import pandas as pd
+import pytest
+
+from src.reports import (
+ ReportGenerator,
+ report_decorator,
+ spending_by_category,
+ spending_by_weekday,
+ spending_by_workday,
+)
+
+
+class TestReportGeneratorExtended:
+ """Расширенные тесты для ReportGenerator"""
+
+ @pytest.fixture
+ def sample_transactions_complex(self):
+ """Фикстура с комплексными данными для отчетов"""
+ dates = pd.date_range(start='2023-09-01', end='2023-12-31', freq='D')
+ return pd.DataFrame({
+ 'Дата операции': dates,
+ 'Статус': ['OK'] * len(dates),
+ 'Категория': ['Супермаркеты'] * 30 + ['Фастфуд'] * 30 + ['Транспорт'] * 30 + ['Развлечения'] * 31,
+ 'Сумма операции': np.random.uniform(100, 5000, len(dates))
+ })
+
+ def test_spending_by_category_empty_data(self):
+ """Тест отчета по категориям с пустыми данными"""
+ df = pd.DataFrame(columns=['Дата операции', 'Категория', 'Сумма операции', 'Статус'])
+
+ result = ReportGenerator.spending_by_category(df, 'Супермаркеты', '2023-12-20')
+
+ assert result.empty
+ assert list(result.columns) == ['Месяц', 'Сумма']
+
+ def test_spending_by_category_no_matching_category(self):
+ """Тест отчета по категориям без совпадающих категорий"""
+ df = pd.DataFrame({
+ 'Дата операции': pd.to_datetime(['2023-12-01', '2023-12-02']),
+ 'Статус': ['OK', 'OK'],
+ 'Категория': ['Фастфуд', 'Транспорт'], # Нет Супермаркетов
+ 'Сумма операции': [1000, 2000]
+ })
+
+ result = ReportGenerator.spending_by_category(df, 'Супермаркеты', '2023-12-20')
+
+ assert result.empty
+
+ def test_spending_by_category_only_failed_transactions(self):
+ """Тест отчета по категориям только с неудачными транзакциями"""
+ df = pd.DataFrame({
+ 'Дата операции': pd.to_datetime(['2023-12-01', '2023-12-02']),
+ 'Статус': ['FAILED', 'FAILED'],
+ 'Категория': ['Супермаркеты', 'Супермаркеты'],
+ 'Сумма операции': [1000, 2000]
+ })
+
+ result = ReportGenerator.spending_by_category(df, 'Супермаркеты', '2023-12-20')
+
+ assert result.empty
+
+ def test_spending_by_category_only_income(self):
+ """Тест отчета по категориям только с доходами"""
+ df = pd.DataFrame({
+ 'Дата операции': pd.to_datetime(['2023-12-01', '2023-12-02']),
+ 'Статус': ['OK', 'OK'],
+ 'Категория': ['Супермаркеты', 'Супермаркеты'],
+ 'Сумма операции': [-1000, -2000] # Доходы
+ })
+
+ result = ReportGenerator.spending_by_category(df, 'Супермаркеты', '2023-12-20')
+
+ assert result.empty
+
+ def test_spending_by_category_different_months(self):
+ """Тест отчета по категориям за несколько месяцев"""
+ df = pd.DataFrame({
+ 'Дата операции': pd.to_datetime(['2023-10-15', '2023-11-15', '2023-12-15']),
+ 'Статус': ['OK', 'OK', 'OK'],
+ 'Категория': ['Супермаркеты', 'Супермаркеты', 'Супермаркеты'],
+ 'Сумма операции': [1000, 2000, 3000]
+ })
+
+ result = ReportGenerator.spending_by_category(df, 'Супермаркеты', '2023-12-20')
+
+ # Должны быть данные за 3 месяца (октябрь, ноябрь, декабрь)
+ assert len(result) == 3
+ assert set(result['Месяц']) == {'2023-10', '2023-11', '2023-12'}
+ assert result['Сумма'].sum() == 6000
+
+ def test_spending_by_weekday_empty_data(self):
+ """Тест отчета по дням недели с пустыми данными"""
+ df = pd.DataFrame(columns=['Дата операции', 'Сумма операции', 'Статус'])
+
+ result = ReportGenerator.spending_by_weekday(df, '2023-12-20')
+
+ assert result.empty
+ assert list(result.columns) == ['День недели', 'Средняя сумма']
+
+ def test_spending_by_weekday_correct_ordering(self):
+ """Тест правильного порядка дней недели в отчете"""
+ df = pd.DataFrame({
+ 'Дата операции': pd.to_datetime([
+ '2023-12-18', '2023-12-19', '2023-12-20', # Пн, Вт, Ср
+ '2023-12-21', '2023-12-22', '2023-12-23', '2023-12-24' # Чт, Пт, Сб, Вс
+ ]),
+ 'Статус': ['OK'] * 7,
+ 'Сумма операции': [100, 200, 300, 400, 500, 600, 700]
+ })
+
+ result = ReportGenerator.spending_by_weekday(df, '2023-12-24')
+
+ # Проверяем порядок дней недели
+ expected_order = ['Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье']
+ assert list(result['День недели']) == expected_order
+
+ # Проверяем средние значения (в данном случае они равны суммам, так по одной транзакции на день)
+ assert list(result['Средняя сумма']) == [100, 200, 300, 400, 500, 600, 700]
+
+ def test_spending_by_workday_empty_data(self):
+ """Тест отчета по рабочим/выходным с пустыми данными"""
+ df = pd.DataFrame(columns=['Дата операции', 'Сумма операции', 'Статус'])
+
+ result = ReportGenerator.spending_by_workday(df, '2023-12-20')
+
+ assert result.empty
+ assert list(result.columns) == ['Тип дня', 'Средняя сумма']
+
+ def test_spending_by_workday_correct_classification(self):
+ """Тест правильной классификации рабочих и выходных дней"""
+ df = pd.DataFrame({
+ 'Дата операции': pd.to_datetime([
+ '2023-12-18', '2023-12-19', '2023-12-20', # Пн, Вт, Ср - рабочие
+ '2023-12-23', '2023-12-24' # Сб, Вс - выходные
+ ]),
+ 'Статус': ['OK'] * 5,
+ 'Сумма операции': [100, 200, 300, 400, 500]
+ })
+
+ result = ReportGenerator.spending_by_workday(df, '2023-12-24')
+
+ assert len(result) == 2
+
+ workday_data = result[result['Тип дня'] == 'Рабочий']
+ weekend_data = result[result['Тип дня'] == 'Выходной']
+
+ # Средняя за рабочие дни: (100 + 200 + 300) / 3 = 200
+ assert workday_data['Средняя сумма'].iloc[0] == 200.0
+ # Средняя за выходные: (400 + 500) / 2 = 450
+ assert weekend_data['Средняя сумма'].iloc[0] == 450.0
+
+ def test_monthly_summary_empty_data(self):
+ """Тест сводного отчета с пустыми данными"""
+ df = pd.DataFrame(columns=['Дата операции', 'Сумма операции', 'Статус', 'Категория'])
+
+ result = ReportGenerator.monthly_summary(df, 6)
+
+ assert 'error' in result
+ assert result['error'] == "Нет данных за указанный период"
+
+
+class TestReportDecorator:
+ """Тесты декоратора отчетов"""
+
+ def test_report_decorator_with_filename(self, tmp_path):
+ """Тест декоратора с указанием имени файла"""
+ test_filename = tmp_path / "test_report.json"
+
+ @report_decorator(filename=str(test_filename))
+ def test_function():
+ return {"test": "data"}
+
+ result = test_function()
+
+ assert result == {"test": "data"}
+ assert test_filename.exists()
+
+ with open(test_filename, 'r') as f:
+ saved_data = json.load(f)
+ assert saved_data == {"test": "data"}
+
+ def test_report_decorator_with_dataframe(self, tmp_path):
+ """Тест декоратора с DataFrame"""
+
+ @report_decorator()
+ def test_function():
+ return pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]})
+
+ with patch('src.reports.datetime') as mock_datetime:
+ mock_datetime.now.return_value = datetime(2023, 12, 20, 15, 30, 0)
+
+ result = test_function()
+
+ # Проверяем, что функция возвращает правильный результат
+ assert isinstance(result, pd.DataFrame)
+ assert len(result) == 2
+
+ def test_report_decorator_exception(self, tmp_path):
+ """Тест декоратора при исключении в функции"""
+
+ @report_decorator(filename=str(tmp_path / "error_report.json"))
+ def failing_function():
+ raise ValueError("Test error")
+
+ with pytest.raises(ValueError, match="Test error"):
+ failing_function()
+
+ # Файл не должен быть создан при исключении
+ assert not (tmp_path / "error_report.json").exists()
+
+
+class TestReportsFunctions:
+ """Тесты функций-оберток отчетов"""
+
+ def test_spending_by_category_wrapper(self):
+ """Тест обертки spending_by_category"""
+ with patch('src.reports.ReportGenerator.spending_by_category') as mock_method:
+ mock_method.return_value = pd.DataFrame({'test': [1, 2, 3]})
+
+ df = pd.DataFrame()
+ result = spending_by_category(df, 'Супермаркеты', '2023-12-20')
+
+ assert isinstance(result, pd.DataFrame)
+ mock_method.assert_called_once_with(df, 'Супермаркеты', '2023-12-20')
+
+ def test_spending_by_weekday_wrapper(self):
+ """Тест обертки spending_by_weekday"""
+ with patch('src.reports.ReportGenerator.spending_by_weekday') as mock_method:
+ mock_method.return_value = pd.DataFrame({'test': [1, 2, 3]})
+
+ df = pd.DataFrame()
+ result = spending_by_weekday(df, '2023-12-20')
+
+ assert isinstance(result, pd.DataFrame)
+ mock_method.assert_called_once_with(df, '2023-12-20')
+
+ def test_spending_by_workday_wrapper(self):
+ """Тест обертки spending_by_workday"""
+ with patch('src.reports.ReportGenerator.spending_by_workday') as mock_method:
+ mock_method.return_value = pd.DataFrame({'test': [1, 2, 3]})
+
+ df = pd.DataFrame()
+ result = spending_by_workday(df, '2023-12-20')
+
+ assert isinstance(result, pd.DataFrame)
+ mock_method.assert_called_once_with(df, '2023-12-20')
diff --git a/tests/test_services.py b/tests/test_services.py
index 1454c13..69ab348 100644
--- a/tests/test_services.py
+++ b/tests/test_services.py
@@ -1,5 +1,18 @@
+from unittest.mock import patch
+
+import pandas as pd
import pytest
-from src.services import investment_bank, simple_search
+
+from src.services import (
+ CashbackAnalyzer,
+ InvestmentCalculator,
+ TransactionSearcher,
+ compose,
+ investment_bank,
+ log_service_call,
+ profitable_cashback_categories,
+ simple_search,
+)
@pytest.fixture
@@ -29,6 +42,233 @@ def test_investment_bank(sample_transactions_list):
def test_simple_search(sample_transactions_list):
"""Тест простого поиска."""
- results = simple_search("кафе", sample_transactions_list)
+ # Исправляем порядок аргументов: сначала транзакции, потом строка поиска
+ results = simple_search(sample_transactions_list, "кафе")
assert len(results) == 1
assert results[0]['Категория'] == 'Кафе'
+
+
+class TestCashbackAnalyzer:
+ """Тесты для анализатора кешбэка"""
+
+ def test_analyze_profitable_categories(self):
+ """Тест анализа выгодных категорий"""
+ analyzer = CashbackAnalyzer({'Супермаркеты': 0.05, 'default': 0.01})
+
+ # Создаем тестовые данные
+ data = pd.DataFrame({
+ 'Дата операции': pd.to_datetime(['2023-12-01', '2023-12-05']),
+ 'Статус': ['OK', 'OK'],
+ 'Сумма операции': [1000.0, 2000.0],
+ 'Категория': ['Супермаркеты', 'Фастфуд']
+ })
+
+ result = analyzer.analyze_profitable_categories(data, 2023, 12)
+
+ assert 'Супермаркеты' in result
+ assert 'Фастфуд' in result
+ assert result['Супермаркеты'] == 50.0 # 5% от 1000
+ assert result['Фастфуд'] == 20.0 # 1% от 2000
+
+ def test_analyze_profitable_categories_no_data(self):
+ """Тест анализа без данных"""
+ analyzer = CashbackAnalyzer({'default': 0.01})
+
+ # Пустые данные
+ data = pd.DataFrame(columns=['Дата операции', 'Статус', 'Сумма операции', 'Категория'])
+
+ result = analyzer.analyze_profitable_categories(data, 2023, 12)
+
+ assert result == {}
+
+ def test_analyze_profitable_categories_with_empty_category(self):
+ """Тест анализа с пустыми категориями"""
+ analyzer = CashbackAnalyzer({'default': 0.01})
+
+ data = pd.DataFrame({
+ 'Дата операции': pd.to_datetime(['2023-12-01']),
+ 'Статус': ['OK'],
+ 'Сумма операции': [1000.0],
+ 'Категория': ['']
+ })
+
+ result = analyzer.analyze_profitable_categories(data, 2023, 12)
+
+ # Пустые категории должны игнорироваться
+ assert '' not in result
+
+
+class TestInvestmentCalculator:
+ """Тесты для калькулятора инвесткопилки"""
+
+ def test_investment_bank_valid_transactions(self):
+ """Тест расчета с валидными транзакциями"""
+ transactions = [
+ {
+ 'Дата операции': '2023-12-01',
+ 'Сумма операции': '1047.0' # Округление до 1050 = +3
+ },
+ {
+ 'Дата операции': '2023-12-15',
+ 'Сумма операции': '1982.0' # Округление до 2000 = +18
+ }
+ ]
+
+ result = InvestmentCalculator.investment_bank('2023-12', transactions, 50)
+
+ assert result == 21.0 # 3 + 18
+
+ def test_investment_bank_invalid_transaction(self):
+ """Тест с невалидной транзакцией"""
+ transactions = [
+ {'Дата операции': 'invalid-date', 'Сумма операции': 'not-a-number'},
+ {'Дата операции': '2023-12-01', 'Сумма операции': '1000.0'}
+ ]
+
+ result = InvestmentCalculator.investment_bank('2023-12', transactions, 50)
+
+ # Только вторая транзакция должна учитываться
+ assert result == 0.0 # 1000 округляется до 1000 = 0
+
+ def test_validate_transaction_valid(self):
+ """Тест валидации корректной транзакции"""
+ transaction = {'Дата операции': '2023-12-01', 'Сумма операции': '1000.0'}
+
+ assert InvestmentCalculator._validate_transaction(transaction) is True
+
+ def test_validate_transaction_missing_fields(self):
+ """Тест валидации транзакции с отсутствующими полями"""
+ transaction = {'Дата операции': '2023-12-01'} # Нет Сумма операции
+
+ assert InvestmentCalculator._validate_transaction(transaction) is False
+
+ def test_validate_transaction_invalid_data(self):
+ """Тест валидации транзакции с невалидными данными"""
+ transaction = {'Дата операции': 'invalid-date', 'Сумма операции': 'not-a-number'}
+
+ assert InvestmentCalculator._validate_transaction(transaction) is False
+
+
+class TestTransactionSearcher:
+ """Тесты для поисковика транзакций"""
+
+ @pytest.fixture
+ def sample_transactions(self):
+ return [
+ {'Описание': 'Покупка в магазине', 'Категория': 'Супермаркеты'},
+ {'Описание': 'Обед в кафе', 'Категория': 'Фастфуд'},
+ {'Описание': 'Перевод Ивану И.', 'Категория': 'Переводы'},
+ {'Описание': 'Пополнение +7 921 123-45-67', 'Категория': 'Мобильная связь'}
+ ]
+
+ def test_simple_search_by_description(self, sample_transactions):
+ """Тест поиска по описанию"""
+ results = TransactionSearcher.simple_search(sample_transactions, 'магазин')
+
+ assert len(results) == 1
+ assert results[0]['Описание'] == 'Покупка в магазине'
+
+ def test_simple_search_by_category(self, sample_transactions):
+ """Тест поиска по категории"""
+ results = TransactionSearcher.simple_search(sample_transactions, 'Фастфуд')
+
+ assert len(results) == 1
+ assert results[0]['Категория'] == 'Фастфуд'
+
+ def test_simple_search_short_string(self):
+ """Тест поиска с короткой строкой"""
+ with pytest.raises(ValueError, match="Строка поиска должна содержать минимум 2 символа"):
+ TransactionSearcher.simple_search([], 'а')
+
+ def test_search_by_phone(self, sample_transactions):
+ """Тест поиска по телефонным номерам"""
+ results = TransactionSearcher.search_by_phone(sample_transactions)
+
+ assert len(results) == 1
+ assert '+7 921 123-45-67' in results[0]['Описание']
+
+ def test_search_by_person_transfers(self, sample_transactions):
+ """Тест поиска переводов физлицам"""
+ results = TransactionSearcher.search_by_person_transfers(sample_transactions)
+
+ assert len(results) == 1
+ assert results[0]['Категория'] == 'Переводы'
+ assert 'Ивану И.' in results[0]['Описание']
+
+
+class TestFunctionalUtilities:
+ """Тесты функциональных утилит"""
+
+ def test_compose(self):
+ """Тест композиции функций"""
+
+ def double(x):
+ return x * 2
+
+ def square(x):
+ return x * x
+
+ composed = compose(double, square)
+ result = composed(3) # square(3) = 9, double(9) = 18
+
+ assert result == 18
+
+
+class TestServiceDecorators:
+ """Тесты декораторов сервисов"""
+
+ def test_log_service_call_success(self):
+ """Тест декоратора при успешном выполнении"""
+
+ @log_service_call("test_service")
+ def test_function():
+ return "success"
+
+ with patch('src.services.logger') as mock_logger:
+ result = test_function()
+
+ assert result == "success"
+ mock_logger.info.assert_any_call("Вызов сервиса test_service")
+ mock_logger.info.assert_any_call("Сервис test_service выполнен успешно")
+
+ def test_log_service_call_exception(self):
+ """Тест декоратора при исключении"""
+
+ @log_service_call("test_service")
+ def test_function():
+ raise ValueError("Test error")
+
+ with patch('src.services.logger') as mock_logger:
+ with pytest.raises(ValueError, match="Test error"):
+ test_function()
+
+ mock_logger.info.assert_called_once_with("Вызов сервиса test_service")
+ mock_logger.error.assert_called_once_with("Ошибка в сервисе test_service: Test error")
+
+
+# Тесты для основных функций с декораторами
+def test_profitable_cashback_categories_integration():
+ """Интеграционный тест для profitable_cashback_categories"""
+ data = pd.DataFrame({
+ 'Дата операции': pd.to_datetime(['2023-12-01']),
+ 'Статус': ['OK'],
+ 'Сумма операции': [1000.0],
+ 'Категория': ['Супермаркеты']
+ })
+
+ with patch('src.services.logger') as mock_logger:
+ result = profitable_cashback_categories(data, 2023, 12, {'Супермаркеты': 0.05})
+
+ assert 'Супермаркеты' in result
+ mock_logger.info.assert_called()
+
+
+def test_investment_bank_integration():
+ """Интеграционный тест для investment_bank"""
+ transactions = [{'Дата операции': '2023-12-01', 'Сумма операции': '1047.0'}]
+
+ with patch('src.services.logger') as mock_logger:
+ result = investment_bank('2023-12', transactions, 50)
+
+ assert result == 3.0
+ mock_logger.info.assert_called()
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 0000000..383964b
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,84 @@
+# tests/test_utils.py
+import json
+import os
+import tempfile
+from datetime import datetime
+
+import pandas as pd
+
+from src.utils import DataValidator, calculate_cashback, get_date_range, get_greeting, load_user_settings
+
+
+class TestUtils:
+ def test_get_greeting(self):
+ assert get_greeting('2023-12-20 08:30:00') == 'Доброе утро'
+ assert get_greeting('2023-12-20 14:30:00') == 'Добрый день'
+ assert get_greeting('2023-12-20 20:30:00') == 'Добрый вечер'
+ assert get_greeting('2023-12-20 02:30:00') == 'Доброй ночи'
+
+ def test_get_date_range(self):
+ start, end = get_date_range('2023-12-20 15:30:00', 'M')
+ assert start == datetime(2023, 12, 1, 0, 0, 0) # Исправлено: добавили время
+ assert end == datetime(2023, 12, 20, 15, 30, 0)
+
+ start, end = get_date_range('2023-12-20', 'W')
+ assert start.weekday() == 0 # Понедельник
+ assert end.weekday() == 6 # Воскресенье
+
+ def test_calculate_cashback(self):
+ cashback_rules = {
+ 'Супермаркеты': 0.05,
+ 'default': 0.01
+ }
+
+ # Базовая логика
+ assert calculate_cashback(1000, 'Супермаркеты', cashback_rules) == 50.0
+ assert calculate_cashback(1000, 'Другое', cashback_rules) == 10.0
+
+ # Бонус за большие покупки - ИСПРАВЛЕНО ожидание
+ # 6000 * (5% + 1%) = 6000 * 0.06 = 360.0
+ assert calculate_cashback(6000, 'Супермаркеты', cashback_rules) == 360.0
+ # 11000 * (5% + 2%) = 11000 * 0.07 = 770.0
+ assert calculate_cashback(11000, 'Супермаркеты', cashback_rules) == 770.0
+
+ # Ограничение максимального кешбэка
+ high_cashback_rules = {'default': 0.20}
+ assert calculate_cashback(1000, 'Тест', high_cashback_rules) == 150.0 # Максимум 15%
+
+ def test_data_validator(self):
+ validator = DataValidator()
+
+ # Тестовые данные с корректными данными
+ test_data = pd.DataFrame({
+ 'Дата операции': ['15.01.2023', '20.02.2023', '10.03.2023'], # Только корректные даты
+ 'Сумма операции': ['1000.50', '2000', '1500.75'],
+ 'Статус': ['OK', 'OK', 'OK']
+ })
+
+ clean_data, errors = validator.validate_transaction_data(test_data)
+
+ assert len(clean_data) == 3 # Все строки должны быть корректными
+ assert isinstance(errors, list)
+
+ def test_load_user_settings(self):
+ # Создаем временный файл настроек
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
+ json.dump({
+ "user_currencies": ["USD", "EUR"],
+ "user_stocks": ["AAPL", "AMZN"]
+ }, f)
+ temp_path = f.name
+
+ try:
+ # Мокаем путь к настройкам
+ import src.utils
+ original_path = src.utils.settings.user_settings_path
+ src.utils.settings.user_settings_path = temp_path
+
+ settings = load_user_settings()
+ assert 'user_currencies' in settings
+ assert 'user_stocks' in settings
+
+ finally:
+ src.utils.settings.user_settings_path = original_path
+ os.unlink(temp_path)
diff --git a/tests/test_views.py b/tests/test_views.py
index 9549685..2fadf0d 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -1,47 +1,87 @@
-import pytest
+# tests/test_views.py
+from unittest.mock import patch
+
import pandas as pd
-from datetime import datetime
-from src.views import home_page
-from src.utils import get_greeting
-
-
-@pytest.fixture
-def sample_transactions():
- """Фикстура с тестовыми транзакциями."""
- return pd.DataFrame({
- 'Дата операции': ['2023-12-01', '2023-12-15', '2023-11-20'],
- 'Номер карты': ['1234567812345678', '1234567812345678', '8765432187654321'],
- 'Сумма операции': [1000.0, 500.0, 2000.0],
- 'Категория': ['Супермаркеты', 'Кафе', 'Транспорт'],
- 'Описание': ['Покупка в магазине', 'Обед в кафе', 'Такси']
- })
-
-
-@pytest.fixture
-def user_settings():
- """Фикстура с настройками пользователя."""
- return {
- "user_currencies": ["USD", "EUR"],
- "user_stocks": ["AAPL", "GOOGL"]
- }
-
-
-def test_home_page(sample_transactions, user_settings):
- """Тест главной страницы."""
- result = home_page("2023-12-20 15:30:00", sample_transactions, user_settings)
-
- assert "greeting" in result
- assert "cards" in result
- assert "top_transactions" in result
- assert len(result["cards"]) > 0
-
-
-@pytest.mark.parametrize("time_str,expected_greeting", [
- ("2023-12-20 08:30:00", "Доброе утро"),
- ("2023-12-20 14:30:00", "Добрый день"),
- ("2023-12-20 20:30:00", "Добрый вечер"),
- ("2023-12-20 02:30:00", "Доброй ночи"),
-])
-def test_get_greeting(time_str, expected_greeting):
- """Тест получения приветствия."""
- assert get_greeting(time_str) == expected_greeting
+import pytest
+
+from src.views import DataProcessor, events_page, main_page
+
+
+class TestViews:
+ @pytest.fixture
+ def sample_transactions(self):
+ """Фикстура с корректными тестовыми данными"""
+ return pd.DataFrame({
+ 'Дата операции': pd.to_datetime(['2023-12-01', '2023-12-05', '2023-12-10', '2023-12-15']),
+ 'Номер карты': ['1234567812345814', '1234567812345814', '1234567812347512', '1234567812347512'],
+ 'Статус': ['OK', 'OK', 'OK', 'OK'],
+ 'Сумма операции': [1000.0, 500.0, 300.0, 200.0], # Все расходы (положительные)
+ 'Категория': ['Супермаркеты', 'Фастфуд', 'Транспорт', 'Развлечения'],
+ 'Описание': ['Магазин', 'Кафе', 'Такси', 'Кино'],
+ 'Сумма платежа': [1000.0, 500.0, 300.0, 200.0]
+ })
+
+ @pytest.fixture
+ def sample_settings(self):
+ return {
+ "user_currencies": ["USD", "EUR"],
+ "user_stocks": ["AAPL", "AMZN"],
+ "cashback_rules": {"default": 0.01}
+ }
+
+ @patch('src.views.SyncAPIClient.get_currency_rates')
+ @patch('src.views.SyncAPIClient.get_stock_prices')
+ def test_main_page(self, mock_stocks, mock_currency, sample_transactions, sample_settings):
+ mock_currency.return_value = [{"currency": "USD", "rate": 93.45}]
+ mock_stocks.return_value = [{"stock": "AAPL", "price": 178.72}]
+
+ with patch('src.views.load_transactions', return_value=sample_transactions):
+ with patch('src.views.load_user_settings', return_value=sample_settings):
+ result = main_page('2023-12-20 15:30:00')
+
+ assert 'greeting' in result
+ assert 'cards' in result
+ assert 'top_transactions' in result
+ assert 'currency_rates' in result
+ assert 'stock_prices' in result
+ assert result['greeting'] == 'Добрый день'
+
+ def test_data_processor(self, sample_transactions, sample_settings):
+ processor = DataProcessor()
+
+ result = processor.process_main_page_data(
+ sample_transactions, '2023-12-20 15:30:00', sample_settings
+ )
+
+ # Теперь должно быть 2 карты с расходами (обе карты имеют расходы)
+ assert len(result['cards']) == 2
+ assert len(result['top_transactions']) == 4
+
+ # Проверяем расчет кешбэка
+ # Карта 5814: 1000 + 500 = 1500 * 1% = 15.0
+ # Карта 7512: 300 + 200 = 500 * 1% = 5.0
+
+ # Находим карты по last_digits
+ card_5814 = next(card for card in result['cards'] if card['last_digits'] == '5814')
+ card_7512 = next(card for card in result['cards'] if card['last_digits'] == '7512')
+
+ assert card_5814['total_spent'] == 1500.0
+ assert card_5814['cashback'] == 15.0
+ assert card_7512['total_spent'] == 500.0
+ assert card_7512['cashback'] == 5.0
+
+ @patch('src.views.SyncAPIClient.get_currency_rates')
+ @patch('src.views.SyncAPIClient.get_stock_prices')
+ def test_events_page(self, mock_stocks, mock_currency, sample_transactions, sample_settings):
+ mock_currency.return_value = [{"currency": "USD", "rate": 93.45}]
+ mock_stocks.return_value = [{"stock": "AAPL", "price": 178.72}]
+
+ with patch('src.views.load_transactions', return_value=sample_transactions):
+ with patch('src.views.load_user_settings', return_value=sample_settings):
+ result = events_page('2023-12-20', 'M')
+
+ assert 'expenses' in result
+ assert 'income' in result
+ # Все операции расходы, поэтому income = 0
+ assert result['expenses']['total_amount'] == 2000 # 1000+500+300+200
+ assert result['income']['total_amount'] == 0 # Нет отрицательных сумм
diff --git a/user_settings.json b/user_settings.json
new file mode 100644
index 0000000..9a7f774
--- /dev/null
+++ b/user_settings.json
@@ -0,0 +1,11 @@
+{
+ "user_currencies": ["USD", "EUR", "GBP", "CNY"],
+ "user_stocks": ["AAPL", "AMZN", "GOOGL", "MSFT", "TSLA", "META", "NVDA"],
+ "cashback_rules": {
+ "Супермаркеты": 0.05,
+ "Фастфуд": 0.03,
+ "Транспорт": 0.02,
+ "Развлечения": 0.02,
+ "default": 0.01
+ }
+}
\ No newline at end of file