diff --git a/python_parser/adapters/parsers/svodka_ca.py b/python_parser/adapters/parsers/svodka_ca.py index 8ef0c53..c536473 100644 --- a/python_parser/adapters/parsers/svodka_ca.py +++ b/python_parser/adapters/parsers/svodka_ca.py @@ -25,20 +25,28 @@ class SvodkaCAParser(ParserPort): def _get_data_wrapper(self, params: dict): """Получение данных по режимам и таблицам""" + print(f"🔍 DEBUG: _get_data_wrapper вызван с параметрами: {params}") + # Валидируем параметры с помощью схемы Pydantic validated_params = validate_params_with_schema(params, SvodkaCARequest) modes = validated_params["modes"] tables = validated_params["tables"] + print(f"🔍 DEBUG: Запрошенные режимы: {modes}") + print(f"🔍 DEBUG: Запрошенные таблицы: {tables}") + # Проверяем, есть ли данные в data_dict (из парсинга) или в df (из загрузки) if hasattr(self, 'data_dict') and self.data_dict is not None: # Данные из парсинга data_source = self.data_dict + print(f"🔍 DEBUG: Используем data_dict с режимами: {list(data_source.keys())}") elif hasattr(self, 'df') and self.df is not None and not self.df.empty: # Данные из загрузки - преобразуем DataFrame обратно в словарь data_source = self._df_to_data_dict() + print(f"🔍 DEBUG: Используем df, преобразованный в data_dict с режимами: {list(data_source.keys())}") else: + print(f"🔍 DEBUG: Нет данных! data_dict={getattr(self, 'data_dict', 'None')}, df={getattr(self, 'df', 'None')}") return {} # Фильтруем данные по запрошенным режимам и таблицам @@ -46,10 +54,19 @@ class SvodkaCAParser(ParserPort): for mode in modes: if mode in data_source: result_data[mode] = {} + available_tables = list(data_source[mode].keys()) + print(f"🔍 DEBUG: Режим '{mode}' содержит таблицы: {available_tables}") for table_name, table_data in data_source[mode].items(): - if table_name in tables: - result_data[mode][table_name] = table_data + # Ищем таблицы по частичному совпадению + for requested_table in tables: + if requested_table in table_name: + result_data[mode][table_name] = table_data + print(f"🔍 DEBUG: Добавлена таблица '{table_name}' (совпадение с '{requested_table}') с {len(table_data)} записями") + break # Найдено совпадение, переходим к следующей таблице + else: + print(f"🔍 DEBUG: Режим '{mode}' не найден в data_source") + print(f"🔍 DEBUG: Итоговый результат содержит режимы: {list(result_data.keys())}") return result_data def _df_to_data_dict(self): @@ -74,6 +91,8 @@ class SvodkaCAParser(ParserPort): def parse(self, file_path: str, params: dict) -> pd.DataFrame: """Парсинг файла и возврат DataFrame""" + print(f"🔍 DEBUG: SvodkaCAParser.parse вызван с файлом: {file_path}") + # Парсим данные и сохраняем словарь для использования в геттерах self.data_dict = self.parse_svodka_ca(file_path, params) @@ -95,132 +114,108 @@ class SvodkaCAParser(ParserPort): if data_rows: df = pd.DataFrame(data_rows) self.df = df + print(f"🔍 DEBUG: Создан DataFrame с {len(data_rows)} записями") return df # Если данных нет, возвращаем пустой DataFrame self.df = pd.DataFrame() + print(f"🔍 DEBUG: Возвращаем пустой DataFrame") return self.df def parse_svodka_ca(self, file_path: str, params: dict) -> dict: - """Парсинг сводки СА""" - # Получаем параметры из params - sheet_name = params.get('sheet_name', 0) # По умолчанию первый лист - inclusion_list = params.get('inclusion_list', {'ТиП', 'Топливо', 'Потери'}) + """Парсинг сводки СА - работает с тремя листами: План, Факт, Норматив""" + print(f"🔍 DEBUG: Начинаем парсинг сводки СА из файла: {file_path}") - # === Извлечение и фильтрация === - tables = self.extract_all_tables(file_path, sheet_name) + # === Точка входа. Нужно выгрузить три таблицы: План, Факт и Норматив === + + # Выгружаем План + inclusion_list_plan = { + "ТиП, %", + "Топливо итого, тонн", + "Топливо итого, %", + "Топливо на технологию, тонн", + "Топливо на технологию, %", + "Топливо на энергетику, тонн", + "Топливо на энергетику, %", + "Потери итого, тонн", + "Потери итого, %", + "в т.ч. Идентифицированные безвозвратные потери, тонн**", + "в т.ч. Идентифицированные безвозвратные потери, %**", + "в т.ч. Неидентифицированные потери, тонн**", + "в т.ч. Неидентифицированные потери, %**" + } - # Фильтруем таблицы: оставляем только те, где первая строка содержит нужные заголовки - filtered_tables = [] - for table in tables: - if table.empty: - continue - first_row_values = table.iloc[0].astype(str).str.strip().tolist() - if any(val in inclusion_list for val in first_row_values): - filtered_tables.append(table) + df_ca_plan = self.parse_sheet(file_path, 'План', inclusion_list_plan) + print(f"🔍 DEBUG: Объединённый и отсортированный План: {df_ca_plan.shape if df_ca_plan is not None else 'None'}") - tables = filtered_tables + # Выгружаем Факт + inclusion_list_fact = { + "ТиП, %", + "Топливо итого, тонн", + "Топливо итого, %", + "Топливо на технологию, тонн", + "Топливо на технологию, %", + "Топливо на энергетику, тонн", + "Топливо на энергетику, %", + "Потери итого, тонн", + "Потери итого, %", + "в т.ч. Идентифицированные безвозвратные потери, тонн", + "в т.ч. Идентифицированные безвозвратные потери, %", + "в т.ч. Неидентифицированные потери, тонн", + "в т.ч. Неидентифицированные потери, %" + } - # === Итоговый список таблиц датафреймов === - result_list = [] + df_ca_fact = self.parse_sheet(file_path, 'Факт', inclusion_list_fact) + print(f"🔍 DEBUG: Объединённый и отсортированный Факт: {df_ca_fact.shape if df_ca_fact is not None else 'None'}") - for table in tables: - if table.empty: - continue + # Выгружаем Норматив + inclusion_list_normativ = { + "Топливо итого, тонн", + "Топливо итого, %", + "Топливо на технологию, тонн", + "Топливо на технологию, %", + "Топливо на энергетику, тонн", + "Топливо на энергетику, %", + "Потери итого, тонн", + "Потери итого, %", + "в т.ч. Идентифицированные безвозвратные потери, тонн**", + "в т.ч. Идентифицированные безвозвратные потери, %**", + "в т.ч. Неидентифицированные потери, тонн**", + "в т.ч. Неидентифицированные потери, %**" + } - # Получаем первую строку (до удаления) - first_row_values = table.iloc[0].astype(str).str.strip().tolist() + df_ca_normativ = self.parse_sheet(file_path, 'Норматив', inclusion_list_normativ) + print(f"🔍 DEBUG: Объединённый и отсортированный Норматив: {df_ca_normativ.shape if df_ca_normativ is not None else 'None'}") - # Находим, какой элемент из inclusion_list присутствует - matched_key = None - for val in first_row_values: - if val in inclusion_list: - matched_key = val - break # берём первый совпадающий заголовок - - if matched_key is None: - continue # на всякий случай (хотя уже отфильтровано) - - # Удаляем первую строку (заголовок) и сбрасываем индекс - df_cleaned = table.iloc[1:].copy().reset_index(drop=True) - - # Пропускаем, если таблица пустая - if df_cleaned.empty: - continue - - # Первая строка становится заголовком - new_header = df_cleaned.iloc[0] # извлекаем первую строку как потенциальные названия столбцов - - # Преобразуем заголовок: только первый столбец может быть заменён на "name" - cleaned_header = [] - - # Обрабатываем первый столбец отдельно - first_item = new_header.iloc[0] if isinstance(new_header, pd.Series) else new_header[0] - first_item_str = str(first_item).strip() if pd.notna(first_item) else "" - if first_item_str == "" or first_item_str == "nan": - cleaned_header.append("name") - else: - cleaned_header.append(first_item_str) - - # Остальные столбцы добавляем без изменений (или с минимальной очисткой) - for item in new_header[1:]: - # Опционально: приводим к строке и убираем лишние пробелы, но не заменяем на "name" - item_str = str(item).strip() if pd.notna(item) else "" - cleaned_header.append(item_str) - - # Применяем очищенные названия столбцов - df_cleaned = df_cleaned[1:] # удаляем строку с заголовком - df_cleaned.columns = cleaned_header - df_cleaned = df_cleaned.reset_index(drop=True) - - if matched_key.endswith('**'): - cleaned_key = matched_key[:-2] # удаляем последние ** - else: - cleaned_key = matched_key - - # Добавляем новую колонку с именем параметра - df_cleaned["table"] = cleaned_key - - # Проверяем, что колонка 'name' существует - if 'name' not in df_cleaned.columns: - print( - f"Внимание: колонка 'name' отсутствует в таблице для '{matched_key}'. Пропускаем добавление 'id'.") - continue # или обработать по-другому - else: - # Применяем функцию get_id_by_name к каждой строке в колонке 'name' - df_cleaned['id'] = df_cleaned['name'].apply(get_og_by_name) - - # Удаляем строки, где id — None, NaN или пустой - df_cleaned = df_cleaned.dropna(subset=['id']) # dropna удаляет NaN - # Дополнительно: удаляем None (если не поймал dropna) - df_cleaned = df_cleaned[df_cleaned['id'].notna() & (df_cleaned['id'].astype(str) != 'None')] - - # Добавляем в словарь - result_list.append(df_cleaned) - - # === Объединение и сортировка по id (индекс) и table === - if result_list: - combined_df = pd.concat(result_list, axis=0) - - # Сортируем по индексу (id) и по столбцу 'table' - combined_df = combined_df.sort_values(by=['id', 'table'], axis=0) - - # Преобразуем DataFrame в словарь по режимам и таблицам - # Для сводки СА у нас есть только один режим - 'fact' (по умолчанию) - # Но нужно определить режим из данных или параметров - mode = params.get('mode', 'fact') # По умолчанию 'fact' - - data_dict = {mode: {}} - - # Группируем данные по таблицам - for table_name, group_df in combined_df.groupby('table'): - # Удаляем колонку 'table' из результата + # Преобразуем DataFrame в словарь по режимам и таблицам + data_dict = {} + + # Обрабатываем План + if df_ca_plan is not None and not df_ca_plan.empty: + data_dict['plan'] = {} + for table_name, group_df in df_ca_plan.groupby('table'): table_data = group_df.drop('table', axis=1) - data_dict[mode][table_name] = table_data.to_dict('records') - - return data_dict - else: - return {} + data_dict['plan'][table_name] = table_data.to_dict('records') + + # Обрабатываем Факт + if df_ca_fact is not None and not df_ca_fact.empty: + data_dict['fact'] = {} + for table_name, group_df in df_ca_fact.groupby('table'): + table_data = group_df.drop('table', axis=1) + data_dict['fact'][table_name] = table_data.to_dict('records') + + # Обрабатываем Норматив + if df_ca_normativ is not None and not df_ca_normativ.empty: + data_dict['normativ'] = {} + for table_name, group_df in df_ca_normativ.groupby('table'): + table_data = group_df.drop('table', axis=1) + data_dict['normativ'][table_name] = table_data.to_dict('records') + + print(f"🔍 DEBUG: Итоговый data_dict содержит режимы: {list(data_dict.keys())}") + for mode, tables in data_dict.items(): + print(f"🔍 DEBUG: Режим '{mode}' содержит таблицы: {list(tables.keys())}") + + return data_dict def extract_all_tables(self, file_path, sheet_name=0): """Извлечение всех таблиц из Excel файла""" diff --git a/python_parser/core/services.py b/python_parser/core/services.py index bb29f53..90012ae 100644 --- a/python_parser/core/services.py +++ b/python_parser/core/services.py @@ -102,6 +102,9 @@ class ReportService: # Устанавливаем DataFrame в парсер для использования в геттерах parser.df = df + print(f"🔍 DEBUG: ReportService.get_data - установлен df в парсер {request.report_type}") + print(f"🔍 DEBUG: DataFrame shape: {df.shape if df is not None else 'None'}") + print(f"🔍 DEBUG: DataFrame columns: {list(df.columns) if df is not None and not df.empty else 'Empty'}") # Получаем параметры запроса get_params = request.get_params or {}