Сводка CA работает корректно
This commit is contained in:
@@ -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)
|
|
||||||
|
|
||||||
# Фильтруем таблицы: оставляем только те, где первая строка содержит нужные заголовки
|
# Выгружаем План
|
||||||
filtered_tables = []
|
inclusion_list_plan = {
|
||||||
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
|
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'}")
|
||||||
|
|
||||||
# === Итоговый список таблиц датафреймов ===
|
# Выгружаем Факт
|
||||||
result_list = []
|
inclusion_list_fact = {
|
||||||
|
"ТиП, %",
|
||||||
|
"Топливо итого, тонн",
|
||||||
|
"Топливо итого, %",
|
||||||
|
"Топливо на технологию, тонн",
|
||||||
|
"Топливо на технологию, %",
|
||||||
|
"Топливо на энергетику, тонн",
|
||||||
|
"Топливо на энергетику, %",
|
||||||
|
"Потери итого, тонн",
|
||||||
|
"Потери итого, %",
|
||||||
|
"в т.ч. Идентифицированные безвозвратные потери, тонн",
|
||||||
|
"в т.ч. Идентифицированные безвозвратные потери, %",
|
||||||
|
"в т.ч. Неидентифицированные потери, тонн",
|
||||||
|
"в т.ч. Неидентифицированные потери, %"
|
||||||
|
}
|
||||||
|
|
||||||
for table in tables:
|
df_ca_fact = self.parse_sheet(file_path, 'Факт', inclusion_list_fact)
|
||||||
if table.empty:
|
print(f"🔍 DEBUG: Объединённый и отсортированный Факт: {df_ca_fact.shape if df_ca_fact is not None else 'None'}")
|
||||||
continue
|
|
||||||
|
|
||||||
# Получаем первую строку (до удаления)
|
# Выгружаем Норматив
|
||||||
first_row_values = table.iloc[0].astype(str).str.strip().tolist()
|
inclusion_list_normativ = {
|
||||||
|
"Топливо итого, тонн",
|
||||||
|
"Топливо итого, %",
|
||||||
|
"Топливо на технологию, тонн",
|
||||||
|
"Топливо на технологию, %",
|
||||||
|
"Топливо на энергетику, тонн",
|
||||||
|
"Топливо на энергетику, %",
|
||||||
|
"Потери итого, тонн",
|
||||||
|
"Потери итого, %",
|
||||||
|
"в т.ч. Идентифицированные безвозвратные потери, тонн**",
|
||||||
|
"в т.ч. Идентифицированные безвозвратные потери, %**",
|
||||||
|
"в т.ч. Неидентифицированные потери, тонн**",
|
||||||
|
"в т.ч. Неидентифицированные потери, %**"
|
||||||
|
}
|
||||||
|
|
||||||
# Находим, какой элемент из inclusion_list присутствует
|
df_ca_normativ = self.parse_sheet(file_path, 'Норматив', inclusion_list_normativ)
|
||||||
matched_key = None
|
print(f"🔍 DEBUG: Объединённый и отсортированный Норматив: {df_ca_normativ.shape if df_ca_normativ is not None else 'None'}")
|
||||||
for val in first_row_values:
|
|
||||||
if val in inclusion_list:
|
|
||||||
matched_key = val
|
|
||||||
break # берём первый совпадающий заголовок
|
|
||||||
|
|
||||||
if matched_key is None:
|
# Преобразуем DataFrame в словарь по режимам и таблицам
|
||||||
continue # на всякий случай (хотя уже отфильтровано)
|
data_dict = {}
|
||||||
|
|
||||||
# Удаляем первую строку (заголовок) и сбрасываем индекс
|
# Обрабатываем План
|
||||||
df_cleaned = table.iloc[1:].copy().reset_index(drop=True)
|
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'):
|
||||||
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 файла"""
|
||||||
|
|||||||
@@ -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 {}
|
||||||
|
|||||||
Reference in New Issue
Block a user