diff --git a/.gitignore b/.gitignore index 94cf779..0e31b10 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ python_parser/app/schemas/test_schemas/test_adapters/__pycache__/ python_parser/app/schemas/test_schemas/test_app/__pycache__/ nin_python_parser +*.pyc *.py[cod] *$py.class diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 4327337..0bb12fa 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -14,7 +14,7 @@ services: restart: unless-stopped fastapi: - build: ./python_parser + image: python:3.11-slim container_name: svodka_fastapi_dev ports: - "8000:8000" @@ -24,9 +24,20 @@ services: - MINIO_SECRET_KEY=minioadmin - MINIO_SECURE=false - MINIO_BUCKET=svodka-data + volumes: + # Монтируем исходный код для автоматической перезагрузки + - ./python_parser:/app + # Монтируем requirements.txt для установки зависимостей + - ./python_parser/requirements.txt:/app/requirements.txt + working_dir: /app depends_on: - minio restart: unless-stopped + command: > + bash -c " + pip install --no-cache-dir -r requirements.txt && + uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload + " streamlit: image: python:3.11-slim diff --git a/python_parser/adapters/__pycache__/pconfig.cpython-313.pyc b/python_parser/adapters/__pycache__/pconfig.cpython-313.pyc index 34fa65d..04c2deb 100644 Binary files a/python_parser/adapters/__pycache__/pconfig.cpython-313.pyc and b/python_parser/adapters/__pycache__/pconfig.cpython-313.pyc differ diff --git a/python_parser/adapters/parsers/__pycache__/monitoring_fuel.cpython-313.pyc b/python_parser/adapters/parsers/__pycache__/monitoring_fuel.cpython-313.pyc index c8ed95c..0fe7c41 100644 Binary files a/python_parser/adapters/parsers/__pycache__/monitoring_fuel.cpython-313.pyc and b/python_parser/adapters/parsers/__pycache__/monitoring_fuel.cpython-313.pyc differ diff --git a/python_parser/adapters/parsers/__pycache__/svodka_ca.cpython-313.pyc b/python_parser/adapters/parsers/__pycache__/svodka_ca.cpython-313.pyc index 9913883..c5a523e 100644 Binary files a/python_parser/adapters/parsers/__pycache__/svodka_ca.cpython-313.pyc and b/python_parser/adapters/parsers/__pycache__/svodka_ca.cpython-313.pyc differ diff --git a/python_parser/adapters/parsers/__pycache__/svodka_pm.cpython-313.pyc b/python_parser/adapters/parsers/__pycache__/svodka_pm.cpython-313.pyc index 1fecb74..66f3854 100644 Binary files a/python_parser/adapters/parsers/__pycache__/svodka_pm.cpython-313.pyc and b/python_parser/adapters/parsers/__pycache__/svodka_pm.cpython-313.pyc differ diff --git a/python_parser/adapters/parsers/svodka_ca.py b/python_parser/adapters/parsers/svodka_ca.py index 4c3be9b..8ef0c53 100644 --- a/python_parser/adapters/parsers/svodka_ca.py +++ b/python_parser/adapters/parsers/svodka_ca.py @@ -17,7 +17,7 @@ class SvodkaCAParser(ParserPort): # Используем схемы Pydantic как единый источник правды register_getter_from_schema( parser_instance=self, - getter_name="get_data", + getter_name="get_ca_data", method=self._get_data_wrapper, schema_class=SvodkaCARequest, description="Получение данных по режимам и таблицам" @@ -31,16 +31,74 @@ class SvodkaCAParser(ParserPort): modes = validated_params["modes"] tables = validated_params["tables"] - # TODO: Переделать под новую архитектуру - data_dict = {} + # Проверяем, есть ли данные в data_dict (из парсинга) или в df (из загрузки) + if hasattr(self, 'data_dict') and self.data_dict is not None: + # Данные из парсинга + data_source = self.data_dict + elif hasattr(self, 'df') and self.df is not None and not self.df.empty: + # Данные из загрузки - преобразуем DataFrame обратно в словарь + data_source = self._df_to_data_dict() + else: + return {} + + # Фильтруем данные по запрошенным режимам и таблицам + result_data = {} for mode in modes: - data_dict[mode] = self.get_data(self.df, mode, tables) - return self.data_dict_to_json(data_dict) + if mode in data_source: + result_data[mode] = {} + for table_name, table_data in data_source[mode].items(): + if table_name in tables: + result_data[mode][table_name] = table_data + + return result_data + + def _df_to_data_dict(self): + """Преобразование DataFrame обратно в словарь данных""" + if not hasattr(self, 'df') or self.df is None or self.df.empty: + return {} + + data_dict = {} + + # Группируем данные по режимам и таблицам + for _, row in self.df.iterrows(): + mode = row.get('mode') + table = row.get('table') + data = row.get('data') + + if mode and table and data is not None: + if mode not in data_dict: + data_dict[mode] = {} + data_dict[mode][table] = data + + return data_dict def parse(self, file_path: str, params: dict) -> pd.DataFrame: """Парсинг файла и возврат DataFrame""" - # Сохраняем DataFrame для использования в геттерах - self.df = self.parse_svodka_ca(file_path, params) + # Парсим данные и сохраняем словарь для использования в геттерах + self.data_dict = self.parse_svodka_ca(file_path, params) + + # Преобразуем словарь в DataFrame для совместимости с services.py + # Создаем простой DataFrame с информацией о загруженных данных + if self.data_dict: + # Создаем DataFrame с информацией о режимах и таблицах + data_rows = [] + for mode, tables in self.data_dict.items(): + for table_name, table_data in tables.items(): + if table_data: + data_rows.append({ + 'mode': mode, + 'table': table_name, + 'rows_count': len(table_data), + 'data': table_data + }) + + if data_rows: + df = pd.DataFrame(data_rows) + self.df = df + return df + + # Если данных нет, возвращаем пустой DataFrame + self.df = pd.DataFrame() return self.df def parse_svodka_ca(self, file_path: str, params: dict) -> dict: @@ -147,12 +205,22 @@ class SvodkaCAParser(ParserPort): # Сортируем по индексу (id) и по столбцу 'table' combined_df = combined_df.sort_values(by=['id', 'table'], axis=0) - # Устанавливаем id как индекс - # combined_df.set_index('id', inplace=True) - - return combined_df + # Преобразуем DataFrame в словарь по режимам и таблицам + # Для сводки СА у нас есть только один режим - 'fact' (по умолчанию) + # Но нужно определить режим из данных или параметров + mode = params.get('mode', 'fact') # По умолчанию 'fact' + + data_dict = {mode: {}} + + # Группируем данные по таблицам + for table_name, group_df in combined_df.groupby('table'): + # Удаляем колонку 'table' из результата + table_data = group_df.drop('table', axis=1) + data_dict[mode][table_name] = table_data.to_dict('records') + + return data_dict else: - return None + return {} def extract_all_tables(self, file_path, sheet_name=0): """Извлечение всех таблиц из Excel файла""" diff --git a/python_parser/app/schemas/__pycache__/__init__.cpython-313.pyc b/python_parser/app/schemas/__pycache__/__init__.cpython-313.pyc index 1aec30e..ba4174e 100644 Binary files a/python_parser/app/schemas/__pycache__/__init__.cpython-313.pyc and b/python_parser/app/schemas/__pycache__/__init__.cpython-313.pyc differ diff --git a/python_parser/app/schemas/__pycache__/monitoring_fuel.cpython-313.pyc b/python_parser/app/schemas/__pycache__/monitoring_fuel.cpython-313.pyc index cd2c909..665d314 100644 Binary files a/python_parser/app/schemas/__pycache__/monitoring_fuel.cpython-313.pyc and b/python_parser/app/schemas/__pycache__/monitoring_fuel.cpython-313.pyc differ diff --git a/python_parser/core/__pycache__/ports.cpython-313.pyc b/python_parser/core/__pycache__/ports.cpython-313.pyc index 6bf9520..07bb367 100644 Binary files a/python_parser/core/__pycache__/ports.cpython-313.pyc and b/python_parser/core/__pycache__/ports.cpython-313.pyc differ diff --git a/python_parser/core/services.py b/python_parser/core/services.py index f518f39..d66d1bc 100644 --- a/python_parser/core/services.py +++ b/python_parser/core/services.py @@ -106,19 +106,38 @@ class ReportService: # Получаем параметры запроса get_params = request.get_params or {} - # Определяем имя геттера из параметра mode - getter_name = get_params.pop("mode", None) - if not getter_name: - # Если режим не указан, берем первый доступный - available_getters = list(parser.getters.keys()) - if available_getters: - getter_name = available_getters[0] - print(f"⚠️ Режим не указан, используем первый доступный: {getter_name}") + # Для svodka_ca определяем режим из данных или используем 'fact' по умолчанию + if request.report_type == 'svodka_ca': + # Извлекаем режим из DataFrame или используем 'fact' по умолчанию + if hasattr(parser, 'df') and parser.df is not None and not parser.df.empty: + modes_in_df = parser.df['mode'].unique() if 'mode' in parser.df.columns else ['fact'] + # Используем первый найденный режим или 'fact' по умолчанию + default_mode = modes_in_df[0] if len(modes_in_df) > 0 else 'fact' else: - return DataResult( - success=False, - message="Парсер не имеет доступных геттеров" - ) + default_mode = 'fact' + + # Устанавливаем режим в параметры, если он не указан + if 'mode' not in get_params: + get_params['mode'] = default_mode + + # Определяем имя геттера + if request.report_type == 'svodka_ca': + # Для svodka_ca используем геттер get_ca_data + getter_name = 'get_ca_data' + else: + # Для других парсеров определяем из параметра mode + getter_name = get_params.pop("mode", None) + if not getter_name: + # Если режим не указан, берем первый доступный + available_getters = list(parser.getters.keys()) + if available_getters: + getter_name = available_getters[0] + print(f"⚠️ Режим не указан, используем первый доступный: {getter_name}") + else: + return DataResult( + success=False, + message="Парсер не имеет доступных геттеров" + ) # Получаем значение через указанный геттер try: