Сводка CA работает корректно

This commit is contained in:
2025-09-03 14:22:28 +03:00
parent 1fcb44193d
commit 0a328f9781
2 changed files with 111 additions and 113 deletions

View File

@@ -25,20 +25,28 @@ class SvodkaCAParser(ParserPort):
def _get_data_wrapper(self, params: dict): def _get_data_wrapper(self, params: dict):
"""Получение данных по режимам и таблицам""" """Получение данных по режимам и таблицам"""
print(f"🔍 DEBUG: _get_data_wrapper вызван с параметрами: {params}")
# Валидируем параметры с помощью схемы Pydantic # Валидируем параметры с помощью схемы Pydantic
validated_params = validate_params_with_schema(params, SvodkaCARequest) validated_params = validate_params_with_schema(params, SvodkaCARequest)
modes = validated_params["modes"] modes = validated_params["modes"]
tables = validated_params["tables"] tables = validated_params["tables"]
print(f"🔍 DEBUG: Запрошенные режимы: {modes}")
print(f"🔍 DEBUG: Запрошенные таблицы: {tables}")
# Проверяем, есть ли данные в data_dict (из парсинга) или в df (из загрузки) # Проверяем, есть ли данные в data_dict (из парсинга) или в df (из загрузки)
if hasattr(self, 'data_dict') and self.data_dict is not None: if hasattr(self, 'data_dict') and self.data_dict is not None:
# Данные из парсинга # Данные из парсинга
data_source = self.data_dict 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: elif hasattr(self, 'df') and self.df is not None and not self.df.empty:
# Данные из загрузки - преобразуем DataFrame обратно в словарь # Данные из загрузки - преобразуем DataFrame обратно в словарь
data_source = self._df_to_data_dict() data_source = self._df_to_data_dict()
print(f"🔍 DEBUG: Используем df, преобразованный в data_dict с режимами: {list(data_source.keys())}")
else: else:
print(f"🔍 DEBUG: Нет данных! data_dict={getattr(self, 'data_dict', 'None')}, df={getattr(self, 'df', 'None')}")
return {} return {}
# Фильтруем данные по запрошенным режимам и таблицам # Фильтруем данные по запрошенным режимам и таблицам
@@ -46,10 +54,19 @@ class SvodkaCAParser(ParserPort):
for mode in modes: for mode in modes:
if mode in data_source: if mode in data_source:
result_data[mode] = {} 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(): 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 return result_data
def _df_to_data_dict(self): def _df_to_data_dict(self):
@@ -74,6 +91,8 @@ class SvodkaCAParser(ParserPort):
def parse(self, file_path: str, params: dict) -> pd.DataFrame: def parse(self, file_path: str, params: dict) -> pd.DataFrame:
"""Парсинг файла и возврат DataFrame""" """Парсинг файла и возврат DataFrame"""
print(f"🔍 DEBUG: SvodkaCAParser.parse вызван с файлом: {file_path}")
# Парсим данные и сохраняем словарь для использования в геттерах # Парсим данные и сохраняем словарь для использования в геттерах
self.data_dict = self.parse_svodka_ca(file_path, params) self.data_dict = self.parse_svodka_ca(file_path, params)
@@ -95,132 +114,108 @@ class SvodkaCAParser(ParserPort):
if data_rows: if data_rows:
df = pd.DataFrame(data_rows) df = pd.DataFrame(data_rows)
self.df = df self.df = df
print(f"🔍 DEBUG: Создан DataFrame с {len(data_rows)} записями")
return df return df
# Если данных нет, возвращаем пустой DataFrame # Если данных нет, возвращаем пустой DataFrame
self.df = pd.DataFrame() self.df = pd.DataFrame()
print(f"🔍 DEBUG: Возвращаем пустой DataFrame")
return self.df return self.df
def parse_svodka_ca(self, file_path: str, params: dict) -> dict: def parse_svodka_ca(self, file_path: str, params: dict) -> dict:
"""Парсинг сводки СА""" """Парсинг сводки СА - работает с тремя листами: План, Факт, Норматив"""
# Получаем параметры из params print(f"🔍 DEBUG: Начинаем парсинг сводки СА из файла: {file_path}")
sheet_name = params.get('sheet_name', 0) # По умолчанию первый лист
inclusion_list = params.get('inclusion_list', {'ТиП', 'Топливо', 'Потери'})
# === Извлечение и фильтрация === # === Точка входа. Нужно выгрузить три таблицы: План, Факт и Норматив ===
tables = self.extract_all_tables(file_path, sheet_name)
# Выгружаем План
inclusion_list_plan = {
"ТиП, %",
"Топливо итого, тонн",
"Топливо итого, %",
"Топливо на технологию, тонн",
"Топливо на технологию, %",
"Топливо на энергетику, тонн",
"Топливо на энергетику, %",
"Потери итого, тонн",
"Потери итого, %",
"в т.ч. Идентифицированные безвозвратные потери, тонн**",
"в т.ч. Идентифицированные безвозвратные потери, %**",
"в т.ч. Неидентифицированные потери, тонн**",
"в т.ч. Неидентифицированные потери, %**"
}
# Фильтруем таблицы: оставляем только те, где первая строка содержит нужные заголовки df_ca_plan = self.parse_sheet(file_path, 'План', inclusion_list_plan)
filtered_tables = [] print(f"🔍 DEBUG: Объединённый и отсортированный План: {df_ca_plan.shape if df_ca_plan is not None else 'None'}")
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)
tables = filtered_tables # Выгружаем Факт
inclusion_list_fact = {
"ТиП, %",
"Топливо итого, тонн",
"Топливо итого, %",
"Топливо на технологию, тонн",
"Топливо на технологию, %",
"Топливо на энергетику, тонн",
"Топливо на энергетику, %",
"Потери итого, тонн",
"Потери итого, %",
"в т.ч. Идентифицированные безвозвратные потери, тонн",
"в т.ч. Идентифицированные безвозвратные потери, %",
"в т.ч. Неидентифицированные потери, тонн",
"в т.ч. Неидентифицированные потери, %"
}
# === Итоговый список таблиц датафреймов === df_ca_fact = self.parse_sheet(file_path, 'Факт', inclusion_list_fact)
result_list = [] print(f"🔍 DEBUG: Объединённый и отсортированный Факт: {df_ca_fact.shape if df_ca_fact is not None else 'None'}")
for table in tables: # Выгружаем Норматив
if table.empty: inclusion_list_normativ = {
continue "Топливо итого, тонн",
"Топливо итого, %",
"Топливо на технологию, тонн",
"Топливо на технологию, %",
"Топливо на энергетику, тонн",
"Топливо на энергетику, %",
"Потери итого, тонн",
"Потери итого, %",
"в т.ч. Идентифицированные безвозвратные потери, тонн**",
"в т.ч. Идентифицированные безвозвратные потери, %**",
"в т.ч. Неидентифицированные потери, тонн**",
"в т.ч. Неидентифицированные потери, %**"
}
# Получаем первую строку (до удаления) df_ca_normativ = self.parse_sheet(file_path, 'Норматив', inclusion_list_normativ)
first_row_values = table.iloc[0].astype(str).str.strip().tolist() print(f"🔍 DEBUG: Объединённый и отсортированный Норматив: {df_ca_normativ.shape if df_ca_normativ is not None else 'None'}")
# Находим, какой элемент из inclusion_list присутствует # Преобразуем DataFrame в словарь по режимам и таблицам
matched_key = None data_dict = {}
for val in first_row_values:
if val in inclusion_list: # Обрабатываем План
matched_key = val if df_ca_plan is not None and not df_ca_plan.empty:
break # берём первый совпадающий заголовок data_dict['plan'] = {}
for table_name, group_df in df_ca_plan.groupby('table'):
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' из результата
table_data = group_df.drop('table', axis=1) table_data = group_df.drop('table', axis=1)
data_dict[mode][table_name] = table_data.to_dict('records') data_dict['plan'][table_name] = table_data.to_dict('records')
return data_dict # Обрабатываем Факт
else: if df_ca_fact is not None and not df_ca_fact.empty:
return {} 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): def extract_all_tables(self, file_path, sheet_name=0):
"""Извлечение всех таблиц из Excel файла""" """Извлечение всех таблиц из Excel файла"""

View File

@@ -102,6 +102,9 @@ class ReportService:
# Устанавливаем DataFrame в парсер для использования в геттерах # Устанавливаем DataFrame в парсер для использования в геттерах
parser.df = df 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 {} get_params = request.get_params or {}