Merge branch 'logging-upgrade'
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
import pandas as pd
|
||||
import re
|
||||
import zipfile
|
||||
import logging
|
||||
from typing import Dict, Tuple
|
||||
from core.ports import ParserPort
|
||||
from core.schema_utils import register_getter_from_schema, validate_params_with_schema
|
||||
from app.schemas.monitoring_fuel import MonitoringFuelTotalRequest, MonitoringFuelMonthRequest
|
||||
from adapters.pconfig import data_to_json
|
||||
|
||||
# Настройка логгера для модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MonitoringFuelParser(ParserPort):
|
||||
"""Парсер для мониторинга топлива"""
|
||||
@@ -157,19 +161,19 @@ class MonitoringFuelParser(ParserPort):
|
||||
if len(candidates) == 1:
|
||||
file = candidates[0]
|
||||
|
||||
print(f'Загрузка {file}')
|
||||
logger.info(f'Загрузка {file}')
|
||||
with zip_ref.open(file) as excel_file:
|
||||
try:
|
||||
df = self.parse_single(excel_file, 'Мониторинг потребления')
|
||||
df_monitorings[mm] = df
|
||||
|
||||
print(f"✅ Данные за месяц {mm} загружены")
|
||||
logger.info(f"✅ Данные за месяц {mm} загружены")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка при загрузке файла {file_temp}: {e}")
|
||||
logger.error(f"Ошибка при загрузке файла {file_temp}: {e}")
|
||||
|
||||
else:
|
||||
print(f"⚠️ Файл не найден: {file_temp}")
|
||||
logger.warning(f"⚠️ Файл не найден: {file_temp}")
|
||||
|
||||
return df_monitorings
|
||||
|
||||
@@ -187,7 +191,7 @@ class MonitoringFuelParser(ParserPort):
|
||||
# Ищем строку, где хотя бы в одном столбце встречается искомое значение
|
||||
for idx, row in df_temp.iterrows():
|
||||
if row.astype(str).str.strip().str.contains(f"^{search_value}$", case=False, regex=True).any():
|
||||
print(f"Заголовок найден в строке {idx} (Excel: {idx + 1})")
|
||||
logger.debug(f"Заголовок найден в строке {idx} (Excel: {idx + 1})")
|
||||
return idx + 1 # возвращаем индекс строки (0-based)
|
||||
|
||||
raise ValueError(f"Не найдена строка с заголовком '{search_value}' в первых {max_rows} строках.")
|
||||
@@ -237,7 +241,7 @@ class MonitoringFuelParser(ParserPort):
|
||||
|
||||
# Устанавливаем id как индекс
|
||||
df_full.set_index('id', inplace=True)
|
||||
print(f"Окончательное количество столбцов: {len(df_full.columns)}")
|
||||
logger.debug(f"Окончательное количество столбцов: {len(df_full.columns)}")
|
||||
return df_full
|
||||
|
||||
def aggregate_by_columns(self, df_dict: Dict[str, pd.DataFrame], columns: list) -> Tuple[pd.DataFrame, Dict[str, pd.DataFrame]]:
|
||||
@@ -250,7 +254,7 @@ class MonitoringFuelParser(ParserPort):
|
||||
|
||||
for file_key, df in df_dict.items():
|
||||
if col not in df.columns:
|
||||
print(f"Колонка '{col}' не найдена в {file_key}, пропускаем.")
|
||||
logger.warning(f"Колонка '{col}' не найдена в {file_key}, пропускаем.")
|
||||
continue
|
||||
|
||||
# Берём колонку, оставляем id как индекс
|
||||
@@ -302,7 +306,7 @@ class MonitoringFuelParser(ParserPort):
|
||||
|
||||
for file, df in df_dict.items():
|
||||
if column not in df.columns:
|
||||
print(f"Колонка '{column}' не найдена в {file}, пропускаем.")
|
||||
logger.warning(f"Колонка '{column}' не найдена в {file}, пропускаем.")
|
||||
continue
|
||||
|
||||
# Берём колонку и сохраняем как Series с именем месяца
|
||||
|
||||
@@ -2,10 +2,14 @@ import os
|
||||
import zipfile
|
||||
import tempfile
|
||||
import pandas as pd
|
||||
import logging
|
||||
from typing import Dict, Any, List
|
||||
from core.ports import ParserPort
|
||||
from adapters.pconfig import find_header_row, SNPZ_IDS, data_to_json
|
||||
|
||||
# Настройка логгера для модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MonitoringTarParser(ParserPort):
|
||||
"""Парсер для мониторинга ТЭР (топливно-энергетических ресурсов)"""
|
||||
@@ -23,7 +27,7 @@ class MonitoringTarParser(ParserPort):
|
||||
|
||||
def parse(self, file_path: str, params: Dict[str, Any] = None) -> pd.DataFrame:
|
||||
"""Парсит ZIP архив с файлами мониторинга ТЭР"""
|
||||
print(f"🔍 DEBUG: MonitoringTarParser.parse вызван с файлом: {file_path}")
|
||||
logger.debug(f"🔍 MonitoringTarParser.parse вызван с файлом: {file_path}")
|
||||
|
||||
if not file_path.endswith('.zip'):
|
||||
raise ValueError("MonitoringTarParser поддерживает только ZIP архивы")
|
||||
@@ -42,15 +46,15 @@ class MonitoringTarParser(ParserPort):
|
||||
})
|
||||
|
||||
df = pd.DataFrame(data_list)
|
||||
print(f"🔍 DEBUG: Создан DataFrame с {len(df)} записями")
|
||||
logger.debug(f"🔍 Создан DataFrame с {len(df)} записями")
|
||||
return df
|
||||
else:
|
||||
print("🔍 DEBUG: Возвращаем пустой DataFrame")
|
||||
logger.debug("🔍 Возвращаем пустой DataFrame")
|
||||
return pd.DataFrame()
|
||||
|
||||
def _parse_zip_archive(self, zip_path: str) -> Dict[str, Any]:
|
||||
"""Парсит ZIP архив с файлами мониторинга ТЭР"""
|
||||
print(f"📦 Обработка ZIP архива: {zip_path}")
|
||||
logger.info(f"📦 Обработка ZIP архива: {zip_path}")
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
@@ -67,17 +71,17 @@ class MonitoringTarParser(ParserPort):
|
||||
if not tar_files:
|
||||
raise ValueError("В архиве не найдены файлы мониторинга ТЭР")
|
||||
|
||||
print(f"📁 Найдено {len(tar_files)} файлов мониторинга ТЭР")
|
||||
logger.info(f"📁 Найдено {len(tar_files)} файлов мониторинга ТЭР")
|
||||
|
||||
# Обрабатываем каждый файл
|
||||
all_data = {}
|
||||
for file_path in tar_files:
|
||||
print(f"📁 Обработка файла: {file_path}")
|
||||
logger.info(f"📁 Обработка файла: {file_path}")
|
||||
|
||||
# Извлекаем номер месяца из имени файла
|
||||
filename = os.path.basename(file_path)
|
||||
month_str = self._extract_month_from_filename(filename)
|
||||
print(f"📅 Месяц: {month_str}")
|
||||
logger.debug(f"📅 Месяц: {month_str}")
|
||||
|
||||
# Парсим файл
|
||||
file_data = self._parse_single_file(file_path, month_str)
|
||||
@@ -102,7 +106,7 @@ class MonitoringTarParser(ParserPort):
|
||||
excel_file = pd.ExcelFile(file_path)
|
||||
available_sheets = excel_file.sheet_names
|
||||
except Exception as e:
|
||||
print(f"❌ Не удалось открыть Excel-файл {file_path}: {e}")
|
||||
logger.error(f"❌ Не удалось открыть Excel-файл {file_path}: {e}")
|
||||
return {}
|
||||
|
||||
# Словарь для хранения данных: id -> {'total': [], 'last_day': []}
|
||||
@@ -115,7 +119,7 @@ class MonitoringTarParser(ParserPort):
|
||||
# Обрабатываем файлы svodka_tar_*.xlsx с SNPZ_IDS
|
||||
for name, id in SNPZ_IDS.items():
|
||||
if name not in available_sheets:
|
||||
print(f"🟡 Лист '{name}' отсутствует в файле {file_path}")
|
||||
logger.warning(f"🟡 Лист '{name}' отсутствует в файле {file_path}")
|
||||
continue
|
||||
|
||||
# Парсим оба типа строк
|
||||
@@ -140,7 +144,7 @@ class MonitoringTarParser(ParserPort):
|
||||
|
||||
for sheet_name, id in monitoring_sheets.items():
|
||||
if sheet_name not in available_sheets:
|
||||
print(f"🟡 Лист '{sheet_name}' отсутствует в файле {file_path}")
|
||||
logger.warning(f"🟡 Лист '{sheet_name}' отсутствует в файле {file_path}")
|
||||
continue
|
||||
|
||||
# Парсим оба типа строк
|
||||
@@ -168,7 +172,7 @@ class MonitoringTarParser(ParserPort):
|
||||
else:
|
||||
df_svodka_tar[id]['last_day'] = pd.DataFrame()
|
||||
|
||||
print(f"✅ Агрегировано: {len(df_svodka_tar[id]['total'])} 'total' и "
|
||||
logger.info(f"✅ Агрегировано: {len(df_svodka_tar[id]['total'])} 'total' и "
|
||||
f"{len(df_svodka_tar[id]['last_day'])} 'last_day' записей для id='{id}'")
|
||||
|
||||
return df_svodka_tar
|
||||
@@ -178,7 +182,7 @@ class MonitoringTarParser(ParserPort):
|
||||
try:
|
||||
# Проверяем наличие листа
|
||||
if sheet not in pd.ExcelFile(file).sheet_names:
|
||||
print(f"🟡 Лист '{sheet}' не найден в файле {file}")
|
||||
logger.warning(f"🟡 Лист '{sheet}' не найден в файле {file}")
|
||||
return {'total': None, 'last_day': None}
|
||||
|
||||
# Определяем номер заголовка в зависимости от типа файла
|
||||
@@ -187,16 +191,16 @@ class MonitoringTarParser(ParserPort):
|
||||
# Для файлов svodka_tar_*.xlsx ищем заголовок по значению "1"
|
||||
header_num = find_header_row(file, sheet, search_value="1")
|
||||
if header_num is None:
|
||||
print(f"❌ Не найдена строка с заголовком '1' в файле {file}, лист '{sheet}'")
|
||||
logger.error(f"❌ Не найдена строка с заголовком '1' в файле {file}, лист '{sheet}'")
|
||||
return {'total': None, 'last_day': None}
|
||||
elif filename.startswith('monitoring_'):
|
||||
# Для файлов monitoring_*.xlsm заголовок находится в строке 5
|
||||
header_num = 5
|
||||
else:
|
||||
print(f"❌ Неизвестный тип файла: {filename}")
|
||||
logger.error(f"❌ Неизвестный тип файла: {filename}")
|
||||
return {'total': None, 'last_day': None}
|
||||
|
||||
print(f"🔍 DEBUG: Используем заголовок в строке {header_num} для листа '{sheet}'")
|
||||
logger.debug(f"🔍 Используем заголовок в строке {header_num} для листа '{sheet}'")
|
||||
|
||||
# Читаем с двумя уровнями заголовков
|
||||
df = pd.read_excel(
|
||||
@@ -212,7 +216,7 @@ class MonitoringTarParser(ParserPort):
|
||||
# Удаляем строки, где все значения — NaN
|
||||
df = df.dropna(how='all').reset_index(drop=True)
|
||||
if df.empty:
|
||||
print(f"🟡 Нет данных после очистки в файле {file}, лист '{sheet}'")
|
||||
logger.warning(f"🟡 Нет данных после очистки в файле {file}, лист '{sheet}'")
|
||||
return {'total': None, 'last_day': None}
|
||||
|
||||
# === 1. Обработка строки "Всего" ===
|
||||
@@ -240,12 +244,12 @@ class MonitoringTarParser(ParserPort):
|
||||
return {'total': df_total, 'last_day': df_last_day}
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при обработке файла {file}, лист '{sheet}': {e}")
|
||||
logger.error(f"❌ Ошибка при обработке файла {file}, лист '{sheet}': {e}")
|
||||
return {'total': None, 'last_day': None}
|
||||
|
||||
def _get_tar_data_wrapper(self, params: Dict[str, Any] = None) -> str:
|
||||
"""Обертка для получения данных мониторинга ТЭР с фильтрацией по режиму"""
|
||||
print(f"🔍 DEBUG: _get_tar_data_wrapper вызван с параметрами: {params}")
|
||||
logger.debug(f"🔍 _get_tar_data_wrapper вызван с параметрами: {params}")
|
||||
|
||||
# Получаем режим из параметров
|
||||
mode = params.get('mode', 'total') if params else 'total'
|
||||
@@ -274,12 +278,12 @@ class MonitoringTarParser(ParserPort):
|
||||
result_json = data_to_json(filtered_data)
|
||||
return result_json
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при конвертации данных в JSON: {e}")
|
||||
logger.error(f"❌ Ошибка при конвертации данных в JSON: {e}")
|
||||
return "{}"
|
||||
|
||||
def _get_tar_full_data_wrapper(self, params: Dict[str, Any] = None) -> str:
|
||||
"""Обертка для получения всех данных мониторинга ТЭР"""
|
||||
print(f"🔍 DEBUG: _get_tar_full_data_wrapper вызван с параметрами: {params}")
|
||||
logger.debug(f"🔍 _get_tar_full_data_wrapper вызван с параметрами: {params}")
|
||||
|
||||
# Получаем все данные
|
||||
full_data = {}
|
||||
@@ -298,5 +302,5 @@ class MonitoringTarParser(ParserPort):
|
||||
result_json = data_to_json(full_data)
|
||||
return result_json
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при конвертации данных в JSON: {e}")
|
||||
logger.error(f"❌ Ошибка при конвертации данных в JSON: {e}")
|
||||
return "{}"
|
||||
@@ -2,11 +2,15 @@ import os
|
||||
import tempfile
|
||||
import zipfile
|
||||
import pandas as pd
|
||||
import logging
|
||||
from typing import Dict, Any, List
|
||||
from datetime import datetime
|
||||
from core.ports import ParserPort
|
||||
from adapters.pconfig import find_header_row, get_object_by_name, data_to_json
|
||||
|
||||
# Настройка логгера для модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OperSpravkaTechPosParser(ParserPort):
|
||||
"""Парсер для операционных справок технологических позиций"""
|
||||
@@ -23,7 +27,7 @@ class OperSpravkaTechPosParser(ParserPort):
|
||||
|
||||
def parse(self, file_path: str, params: Dict[str, Any] = None) -> pd.DataFrame:
|
||||
"""Парсит ZIP архив с файлами операционных справок технологических позиций"""
|
||||
print(f"🔍 DEBUG: OperSpravkaTechPosParser.parse вызван с файлом: {file_path}")
|
||||
logger.debug(f"🔍 OperSpravkaTechPosParser.parse вызван с файлом: {file_path}")
|
||||
|
||||
if not file_path.endswith('.zip'):
|
||||
raise ValueError("OperSpravkaTechPosParser поддерживает только ZIP архивы")
|
||||
@@ -44,15 +48,15 @@ class OperSpravkaTechPosParser(ParserPort):
|
||||
})
|
||||
|
||||
df = pd.DataFrame(data_list)
|
||||
print(f"🔍 DEBUG: Создан DataFrame с {len(df)} записями")
|
||||
logger.debug(f"🔍 Создан DataFrame с {len(df)} записями")
|
||||
return df
|
||||
else:
|
||||
print("🔍 DEBUG: Возвращаем пустой DataFrame")
|
||||
logger.debug("🔍 Возвращаем пустой DataFrame")
|
||||
return pd.DataFrame()
|
||||
|
||||
def _parse_zip_archive(self, zip_path: str) -> Dict[str, pd.DataFrame]:
|
||||
"""Парсит ZIP архив с файлами операционных справок"""
|
||||
print(f"📦 Обработка ZIP архива: {zip_path}")
|
||||
logger.info(f"📦 Обработка ZIP архива: {zip_path}")
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
@@ -69,17 +73,17 @@ class OperSpravkaTechPosParser(ParserPort):
|
||||
if not tech_pos_files:
|
||||
raise ValueError("В архиве не найдены файлы операционных справок технологических позиций")
|
||||
|
||||
print(f"📁 Найдено {len(tech_pos_files)} файлов операционных справок")
|
||||
logger.info(f"📁 Найдено {len(tech_pos_files)} файлов операционных справок")
|
||||
|
||||
# Обрабатываем каждый файл
|
||||
all_data = {}
|
||||
for file_path in tech_pos_files:
|
||||
print(f"📁 Обработка файла: {file_path}")
|
||||
logger.info(f"📁 Обработка файла: {file_path}")
|
||||
|
||||
# Извлекаем ID ОГ из имени файла
|
||||
filename = os.path.basename(file_path)
|
||||
og_id = self._extract_og_id_from_filename(filename)
|
||||
print(f"🏭 ОГ ID: {og_id}")
|
||||
logger.debug(f"🏭 ОГ ID: {og_id}")
|
||||
|
||||
# Парсим файл
|
||||
file_data = self._parse_single_file(file_path)
|
||||
@@ -102,11 +106,11 @@ class OperSpravkaTechPosParser(ParserPort):
|
||||
try:
|
||||
# Находим актуальный лист
|
||||
actual_sheet = self._find_actual_sheet_num(file_path)
|
||||
print(f"📅 Актуальный лист: {actual_sheet}")
|
||||
logger.debug(f"📅 Актуальный лист: {actual_sheet}")
|
||||
|
||||
# Находим заголовок
|
||||
header_row = self._find_header_row(file_path, actual_sheet)
|
||||
print(f"📋 Заголовок найден в строке {header_row}")
|
||||
logger.debug(f"📋 Заголовок найден в строке {header_row}")
|
||||
|
||||
# Парсим данные
|
||||
df = self._parse_tech_pos_data(file_path, actual_sheet, header_row)
|
||||
@@ -117,11 +121,11 @@ class OperSpravkaTechPosParser(ParserPort):
|
||||
og_id = self._extract_og_id_from_filename(filename)
|
||||
return {og_id: df}
|
||||
else:
|
||||
print(f"⚠️ Нет данных в файле {file_path}")
|
||||
logger.warning(f"⚠️ Нет данных в файле {file_path}")
|
||||
return {}
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при обработке файла {file_path}: {e}")
|
||||
logger.error(f"❌ Ошибка при обработке файла {file_path}: {e}")
|
||||
return {}
|
||||
|
||||
def _find_actual_sheet_num(self, file_path: str) -> str:
|
||||
@@ -160,7 +164,7 @@ class OperSpravkaTechPosParser(ParserPort):
|
||||
except:
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"⚠️ Ошибка при поиске актуального листа: {e}")
|
||||
logger.warning(f"⚠️ Ошибка при поиске актуального листа: {e}")
|
||||
|
||||
return actual_sheet
|
||||
|
||||
@@ -173,12 +177,12 @@ class OperSpravkaTechPosParser(ParserPort):
|
||||
# Ищем строку с искомым значением
|
||||
for idx, row in df_temp.iterrows():
|
||||
if row.astype(str).str.contains(search_value, case=False, regex=False).any():
|
||||
print(f"Заголовок найден в строке {idx} (Excel: {idx + 1})")
|
||||
logger.debug(f"Заголовок найден в строке {idx} (Excel: {idx + 1})")
|
||||
return idx + 1 # возвращаем индекс строки (0-based), который будет использован как `header=`
|
||||
|
||||
raise ValueError(f"Не найдена строка с заголовком '{search_value}'.")
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при поиске заголовка: {e}")
|
||||
logger.error(f"❌ Ошибка при поиске заголовка: {e}")
|
||||
return 0
|
||||
|
||||
def _parse_tech_pos_data(self, file_path: str, sheet_name: str, header_row: int) -> pd.DataFrame:
|
||||
@@ -193,8 +197,8 @@ class OperSpravkaTechPosParser(ParserPort):
|
||||
usecols=range(1, 5)
|
||||
)
|
||||
|
||||
print(f"🔍 DEBUG: Прочитано {len(df_temp)} строк из Excel")
|
||||
print(f"🔍 DEBUG: Колонки: {list(df_temp.columns)}")
|
||||
logger.debug(f"🔍 Прочитано {len(df_temp)} строк из Excel")
|
||||
logger.debug(f"🔍 Колонки: {list(df_temp.columns)}")
|
||||
|
||||
# Фильтруем по валидным процессам
|
||||
df_cleaned = df_temp[
|
||||
@@ -202,11 +206,11 @@ class OperSpravkaTechPosParser(ParserPort):
|
||||
df_temp['Процесс'].notna()
|
||||
].copy()
|
||||
|
||||
print(f"🔍 DEBUG: После фильтрации осталось {len(df_cleaned)} строк")
|
||||
logger.debug(f"🔍 После фильтрации осталось {len(df_cleaned)} строк")
|
||||
|
||||
if df_cleaned.empty:
|
||||
print("⚠️ Нет данных после фильтрации по процессам")
|
||||
print(f"🔍 DEBUG: Доступные процессы в данных: {df_temp['Процесс'].unique()}")
|
||||
logger.warning("⚠️ Нет данных после фильтрации по процессам")
|
||||
logger.debug(f"🔍 Доступные процессы в данных: {df_temp['Процесс'].unique()}")
|
||||
return pd.DataFrame()
|
||||
|
||||
df_cleaned['Процесс'] = df_cleaned['Процесс'].astype(str).str.strip()
|
||||
@@ -214,68 +218,68 @@ class OperSpravkaTechPosParser(ParserPort):
|
||||
# Добавляем ID установки
|
||||
if 'Установка' in df_cleaned.columns:
|
||||
df_cleaned['id'] = df_cleaned['Установка'].apply(get_object_by_name)
|
||||
print(f"🔍 DEBUG: Добавлены ID установок: {df_cleaned['id'].unique()}")
|
||||
logger.debug(f"🔍 Добавлены ID установок: {df_cleaned['id'].unique()}")
|
||||
else:
|
||||
print("⚠️ Колонка 'Установка' не найдена")
|
||||
logger.warning("⚠️ Колонка 'Установка' не найдена")
|
||||
|
||||
print(f"✅ Получено {len(df_cleaned)} записей")
|
||||
logger.info(f"✅ Получено {len(df_cleaned)} записей")
|
||||
return df_cleaned
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при парсинге данных: {e}")
|
||||
logger.error(f"❌ Ошибка при парсинге данных: {e}")
|
||||
return pd.DataFrame()
|
||||
|
||||
def _get_tech_pos_wrapper(self, params: Dict[str, Any] = None) -> str:
|
||||
"""Обертка для получения данных технологических позиций"""
|
||||
print(f"🔍 DEBUG: _get_tech_pos_wrapper вызван с параметрами: {params}")
|
||||
logger.debug(f"🔍 _get_tech_pos_wrapper вызван с параметрами: {params}")
|
||||
|
||||
# Получаем ID ОГ из параметров
|
||||
og_id = params.get('id') if params else None
|
||||
if not og_id:
|
||||
print("❌ Не указан ID ОГ")
|
||||
logger.error("❌ Не указан ID ОГ")
|
||||
return "{}"
|
||||
|
||||
# Получаем данные
|
||||
tech_pos_data = {}
|
||||
if hasattr(self, 'df') and self.df is not None and not self.df.empty:
|
||||
# Данные из MinIO
|
||||
print(f"🔍 DEBUG: Ищем данные для ОГ '{og_id}' в DataFrame с {len(self.df)} записями")
|
||||
logger.debug(f"🔍 Ищем данные для ОГ '{og_id}' в DataFrame с {len(self.df)} записями")
|
||||
available_ogs = self.df['id'].tolist()
|
||||
print(f"🔍 DEBUG: Доступные ОГ в данных: {available_ogs}")
|
||||
logger.debug(f"🔍 Доступные ОГ в данных: {available_ogs}")
|
||||
|
||||
for _, row in self.df.iterrows():
|
||||
if row['id'] == og_id:
|
||||
tech_pos_data = row['data']
|
||||
print(f"✅ Найдены данные для ОГ '{og_id}': {len(tech_pos_data)} записей")
|
||||
logger.info(f"✅ Найдены данные для ОГ '{og_id}': {len(tech_pos_data)} записей")
|
||||
break
|
||||
else:
|
||||
print(f"❌ Данные для ОГ '{og_id}' не найдены")
|
||||
logger.warning(f"❌ Данные для ОГ '{og_id}' не найдены")
|
||||
elif hasattr(self, 'data_dict') and self.data_dict:
|
||||
# Локальные данные
|
||||
print(f"🔍 DEBUG: Ищем данные для ОГ '{og_id}' в data_dict")
|
||||
logger.debug(f"🔍 Ищем данные для ОГ '{og_id}' в data_dict")
|
||||
available_ogs = list(self.data_dict.keys())
|
||||
print(f"🔍 DEBUG: Доступные ОГ в data_dict: {available_ogs}")
|
||||
logger.debug(f"🔍 Доступные ОГ в data_dict: {available_ogs}")
|
||||
|
||||
if og_id in self.data_dict:
|
||||
tech_pos_data = self.data_dict[og_id].to_dict(orient='records')
|
||||
print(f"✅ Найдены данные для ОГ '{og_id}': {len(tech_pos_data)} записей")
|
||||
logger.info(f"✅ Найдены данные для ОГ '{og_id}': {len(tech_pos_data)} записей")
|
||||
else:
|
||||
print(f"❌ Данные для ОГ '{og_id}' не найдены в data_dict")
|
||||
logger.warning(f"❌ Данные для ОГ '{og_id}' не найдены в data_dict")
|
||||
|
||||
# Конвертируем в список записей
|
||||
try:
|
||||
if isinstance(tech_pos_data, pd.DataFrame):
|
||||
# Если это DataFrame, конвертируем в список словарей
|
||||
result_list = tech_pos_data.to_dict(orient='records')
|
||||
print(f"🔍 DEBUG: Конвертировано в список: {len(result_list)} записей")
|
||||
logger.debug(f"🔍 Конвертировано в список: {len(result_list)} записей")
|
||||
return result_list
|
||||
elif isinstance(tech_pos_data, list):
|
||||
# Если уже список, возвращаем как есть
|
||||
print(f"🔍 DEBUG: Уже список: {len(tech_pos_data)} записей")
|
||||
logger.debug(f"🔍 Уже список: {len(tech_pos_data)} записей")
|
||||
return tech_pos_data
|
||||
else:
|
||||
print(f"🔍 DEBUG: Неожиданный тип данных: {type(tech_pos_data)}")
|
||||
logger.warning(f"🔍 Неожиданный тип данных: {type(tech_pos_data)}")
|
||||
return []
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при конвертации данных: {e}")
|
||||
logger.error(f"❌ Ошибка при конвертации данных: {e}")
|
||||
return []
|
||||
@@ -2,12 +2,16 @@ import pandas as pd
|
||||
import os
|
||||
import tempfile
|
||||
import zipfile
|
||||
import logging
|
||||
from typing import Dict, Any, List, Tuple, Optional
|
||||
from core.ports import ParserPort
|
||||
from core.schema_utils import register_getter_from_schema, validate_params_with_schema
|
||||
from app.schemas.statuses_repair_ca import StatusesRepairCARequest
|
||||
from adapters.pconfig import find_header_row, get_og_by_name, data_to_json
|
||||
|
||||
# Настройка логгера для модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StatusesRepairCAParser(ParserPort):
|
||||
"""Парсер для статусов ремонта СА"""
|
||||
@@ -26,7 +30,7 @@ class StatusesRepairCAParser(ParserPort):
|
||||
|
||||
def parse(self, file_path: str, params: dict) -> Dict[str, Any]:
|
||||
"""Парсинг файла статусов ремонта СА"""
|
||||
print(f"🔍 DEBUG: StatusesRepairCAParser.parse вызван с файлом: {file_path}")
|
||||
logger.debug(f"🔍 StatusesRepairCAParser.parse вызван с файлом: {file_path}")
|
||||
|
||||
try:
|
||||
# Определяем тип файла
|
||||
@@ -38,7 +42,7 @@ class StatusesRepairCAParser(ParserPort):
|
||||
raise ValueError(f"Неподдерживаемый формат файла: {file_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при парсинге файла {file_path}: {e}")
|
||||
logger.error(f"❌ Ошибка при парсинге файла {file_path}: {e}")
|
||||
raise
|
||||
|
||||
def _parse_zip_file(self, zip_path: str) -> Dict[str, Any]:
|
||||
@@ -59,19 +63,19 @@ class StatusesRepairCAParser(ParserPort):
|
||||
|
||||
# Берем первый найденный Excel файл
|
||||
excel_file = excel_files[0]
|
||||
print(f"🔍 DEBUG: Найден Excel файл в архиве: {excel_file}")
|
||||
logger.debug(f"🔍 Найден Excel файл в архиве: {excel_file}")
|
||||
|
||||
return self._parse_excel_file(excel_file)
|
||||
|
||||
def _parse_excel_file(self, file_path: str) -> Dict[str, Any]:
|
||||
"""Парсинг Excel файла"""
|
||||
print(f"🔍 DEBUG: Парсинг Excel файла: {file_path}")
|
||||
logger.debug(f"🔍 Парсинг Excel файла: {file_path}")
|
||||
|
||||
# Парсим данные
|
||||
df_statuses = self._parse_statuses_repair_ca(file_path, 0)
|
||||
|
||||
if df_statuses.empty:
|
||||
print("⚠️ Нет данных после парсинга")
|
||||
logger.warning("⚠️ Нет данных после парсинга")
|
||||
return {"data": [], "records_count": 0}
|
||||
|
||||
# Преобразуем в список словарей для хранения
|
||||
@@ -85,7 +89,7 @@ class StatusesRepairCAParser(ParserPort):
|
||||
# Устанавливаем данные в парсер для использования в геттерах
|
||||
self.data_dict = result
|
||||
|
||||
print(f"✅ Парсинг завершен. Получено {len(data_list)} записей")
|
||||
logger.info(f"✅ Парсинг завершен. Получено {len(data_list)} записей")
|
||||
return result
|
||||
|
||||
def _parse_statuses_repair_ca(self, file: str, sheet: int, header_num: Optional[int] = None) -> pd.DataFrame:
|
||||
@@ -236,7 +240,7 @@ class StatusesRepairCAParser(ParserPort):
|
||||
|
||||
def _get_repair_statuses_wrapper(self, params: dict):
|
||||
"""Обертка для получения статусов ремонта"""
|
||||
print(f"🔍 DEBUG: _get_repair_statuses_wrapper вызван с параметрами: {params}")
|
||||
logger.debug(f"🔍 _get_repair_statuses_wrapper вызван с параметрами: {params}")
|
||||
|
||||
# Валидация параметров
|
||||
validated_params = validate_params_with_schema(params, StatusesRepairCARequest)
|
||||
@@ -244,8 +248,8 @@ class StatusesRepairCAParser(ParserPort):
|
||||
ids = validated_params.get('ids')
|
||||
keys = validated_params.get('keys')
|
||||
|
||||
print(f"🔍 DEBUG: Запрошенные ОГ: {ids}")
|
||||
print(f"🔍 DEBUG: Запрошенные ключи: {keys}")
|
||||
logger.debug(f"🔍 Запрошенные ОГ: {ids}")
|
||||
logger.debug(f"🔍 Запрошенные ключи: {keys}")
|
||||
|
||||
# Получаем данные из парсера
|
||||
if hasattr(self, 'df') and self.df is not None:
|
||||
@@ -265,15 +269,15 @@ class StatusesRepairCAParser(ParserPort):
|
||||
# Данные из локального парсинга
|
||||
data_source = self.data_dict.get('data', [])
|
||||
else:
|
||||
print("⚠️ Нет данных в парсере")
|
||||
logger.warning("⚠️ Нет данных в парсере")
|
||||
return []
|
||||
|
||||
print(f"🔍 DEBUG: Используем данные с {len(data_source)} записями")
|
||||
logger.debug(f"🔍 Используем данные с {len(data_source)} записями")
|
||||
|
||||
# Фильтруем данные
|
||||
filtered_data = self._filter_statuses_data(data_source, ids, keys)
|
||||
|
||||
print(f"🔍 DEBUG: Отфильтровано {len(filtered_data)} записей")
|
||||
logger.debug(f"🔍 Отфильтровано {len(filtered_data)} записей")
|
||||
return filtered_data
|
||||
|
||||
def _filter_statuses_data(self, data_source: List[Dict], ids: Optional[List[str]], keys: Optional[List[List[str]]]) -> List[Dict]:
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import logging
|
||||
|
||||
from core.ports import ParserPort
|
||||
from core.schema_utils import register_getter_from_schema, validate_params_with_schema
|
||||
from app.schemas.svodka_ca import SvodkaCARequest
|
||||
from adapters.pconfig import get_og_by_name
|
||||
|
||||
# Настройка логгера для модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SvodkaCAParser(ParserPort):
|
||||
"""Парсер для сводок СА"""
|
||||
@@ -25,7 +29,7 @@ class SvodkaCAParser(ParserPort):
|
||||
|
||||
def _get_data_wrapper(self, params: dict):
|
||||
"""Получение данных по режимам и таблицам"""
|
||||
print(f"🔍 DEBUG: _get_data_wrapper вызван с параметрами: {params}")
|
||||
logger.debug(f"🔍 _get_data_wrapper вызван с параметрами: {params}")
|
||||
|
||||
# Валидируем параметры с помощью схемы Pydantic
|
||||
validated_params = validate_params_with_schema(params, SvodkaCARequest)
|
||||
@@ -33,20 +37,20 @@ class SvodkaCAParser(ParserPort):
|
||||
modes = validated_params["modes"]
|
||||
tables = validated_params["tables"]
|
||||
|
||||
print(f"🔍 DEBUG: Запрошенные режимы: {modes}")
|
||||
print(f"🔍 DEBUG: Запрошенные таблицы: {tables}")
|
||||
logger.debug(f"🔍 Запрошенные режимы: {modes}")
|
||||
logger.debug(f"🔍 Запрошенные таблицы: {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())}")
|
||||
logger.debug(f"🔍 Используем 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())}")
|
||||
logger.debug(f"🔍 Используем df, преобразованный в data_dict с режимами: {list(data_source.keys())}")
|
||||
else:
|
||||
print(f"🔍 DEBUG: Нет данных! data_dict={getattr(self, 'data_dict', 'None')}, df={getattr(self, 'df', 'None')}")
|
||||
logger.warning(f"🔍 Нет данных! data_dict={getattr(self, 'data_dict', 'None')}, df={getattr(self, 'df', 'None')}")
|
||||
return {}
|
||||
|
||||
# Фильтруем данные по запрошенным режимам и таблицам
|
||||
@@ -55,18 +59,18 @@ class SvodkaCAParser(ParserPort):
|
||||
if mode in data_source:
|
||||
result_data[mode] = {}
|
||||
available_tables = list(data_source[mode].keys())
|
||||
print(f"🔍 DEBUG: Режим '{mode}' содержит таблицы: {available_tables}")
|
||||
logger.debug(f"🔍 Режим '{mode}' содержит таблицы: {available_tables}")
|
||||
for table_name, table_data in data_source[mode].items():
|
||||
# Ищем таблицы по частичному совпадению
|
||||
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)} записями")
|
||||
logger.debug(f"🔍 Добавлена таблица '{table_name}' (совпадение с '{requested_table}') с {len(table_data)} записями")
|
||||
break # Найдено совпадение, переходим к следующей таблице
|
||||
else:
|
||||
print(f"🔍 DEBUG: Режим '{mode}' не найден в data_source")
|
||||
logger.warning(f"🔍 Режим '{mode}' не найден в data_source")
|
||||
|
||||
print(f"🔍 DEBUG: Итоговый результат содержит режимы: {list(result_data.keys())}")
|
||||
logger.debug(f"🔍 Итоговый результат содержит режимы: {list(result_data.keys())}")
|
||||
return result_data
|
||||
|
||||
def _df_to_data_dict(self):
|
||||
@@ -91,7 +95,7 @@ class SvodkaCAParser(ParserPort):
|
||||
|
||||
def parse(self, file_path: str, params: dict) -> pd.DataFrame:
|
||||
"""Парсинг файла и возврат DataFrame"""
|
||||
print(f"🔍 DEBUG: SvodkaCAParser.parse вызван с файлом: {file_path}")
|
||||
logger.debug(f"🔍 SvodkaCAParser.parse вызван с файлом: {file_path}")
|
||||
|
||||
# Парсим данные и сохраняем словарь для использования в геттерах
|
||||
self.data_dict = self.parse_svodka_ca(file_path, params)
|
||||
@@ -114,17 +118,17 @@ class SvodkaCAParser(ParserPort):
|
||||
if data_rows:
|
||||
df = pd.DataFrame(data_rows)
|
||||
self.df = df
|
||||
print(f"🔍 DEBUG: Создан DataFrame с {len(data_rows)} записями")
|
||||
logger.debug(f"🔍 Создан DataFrame с {len(data_rows)} записями")
|
||||
return df
|
||||
|
||||
# Если данных нет, возвращаем пустой DataFrame
|
||||
self.df = pd.DataFrame()
|
||||
print(f"🔍 DEBUG: Возвращаем пустой DataFrame")
|
||||
logger.debug(f"🔍 Возвращаем пустой DataFrame")
|
||||
return self.df
|
||||
|
||||
def parse_svodka_ca(self, file_path: str, params: dict) -> dict:
|
||||
"""Парсинг сводки СА - работает с тремя листами: План, Факт, Норматив"""
|
||||
print(f"🔍 DEBUG: Начинаем парсинг сводки СА из файла: {file_path}")
|
||||
logger.debug(f"🔍 Начинаем парсинг сводки СА из файла: {file_path}")
|
||||
|
||||
# === Точка входа. Нужно выгрузить три таблицы: План, Факт и Норматив ===
|
||||
|
||||
@@ -146,7 +150,7 @@ class SvodkaCAParser(ParserPort):
|
||||
}
|
||||
|
||||
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'}")
|
||||
logger.debug(f"🔍 Объединённый и отсортированный План: {df_ca_plan.shape if df_ca_plan is not None else 'None'}")
|
||||
|
||||
# Выгружаем Факт
|
||||
inclusion_list_fact = {
|
||||
@@ -166,7 +170,7 @@ class SvodkaCAParser(ParserPort):
|
||||
}
|
||||
|
||||
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'}")
|
||||
logger.debug(f"🔍 Объединённый и отсортированный Факт: {df_ca_fact.shape if df_ca_fact is not None else 'None'}")
|
||||
|
||||
# Выгружаем Норматив
|
||||
inclusion_list_normativ = {
|
||||
@@ -185,7 +189,7 @@ class SvodkaCAParser(ParserPort):
|
||||
}
|
||||
|
||||
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'}")
|
||||
logger.debug(f"🔍 Объединённый и отсортированный Норматив: {df_ca_normativ.shape if df_ca_normativ is not None else 'None'}")
|
||||
|
||||
# Преобразуем DataFrame в словарь по режимам и таблицам
|
||||
data_dict = {}
|
||||
@@ -211,9 +215,9 @@ class SvodkaCAParser(ParserPort):
|
||||
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())}")
|
||||
logger.debug(f"🔍 Итоговый data_dict содержит режимы: {list(data_dict.keys())}")
|
||||
for mode, tables in data_dict.items():
|
||||
print(f"🔍 DEBUG: Режим '{mode}' содержит таблицы: {list(tables.keys())}")
|
||||
logger.debug(f"🔍 Режим '{mode}' содержит таблицы: {list(tables.keys())}")
|
||||
|
||||
return data_dict
|
||||
|
||||
@@ -368,7 +372,7 @@ class SvodkaCAParser(ParserPort):
|
||||
|
||||
# Проверяем, что колонка 'name' существует
|
||||
if 'name' not in df_cleaned.columns:
|
||||
print(
|
||||
logger.debug(
|
||||
f"Внимание: колонка 'name' отсутствует в таблице для '{matched_key}'. Пропускаем добавление 'id'.")
|
||||
continue # или обработать по-другому
|
||||
else:
|
||||
|
||||
@@ -6,10 +6,14 @@ import json
|
||||
import zipfile
|
||||
import tempfile
|
||||
import shutil
|
||||
import logging
|
||||
from typing import Dict, Any, List, Optional
|
||||
from core.ports import ParserPort
|
||||
from adapters.pconfig import SINGLE_OGS, replace_id_in_path, find_header_row, data_to_json
|
||||
|
||||
# Настройка логгера для модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SvodkaPMParser(ParserPort):
|
||||
"""Парсер для сводок ПМ (план и факт)"""
|
||||
@@ -51,17 +55,17 @@ class SvodkaPMParser(ParserPort):
|
||||
# Разархивируем файл
|
||||
with zipfile.ZipFile(file_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(temp_dir)
|
||||
print(f"📦 Архив разархивирован в: {temp_dir}")
|
||||
logger.info(f"📦 Архив разархивирован в: {temp_dir}")
|
||||
|
||||
# Посмотрим, что находится в архиве
|
||||
print(f"🔍 Содержимое архива:")
|
||||
logger.debug(f"🔍 Содержимое архива:")
|
||||
for root, dirs, files in os.walk(temp_dir):
|
||||
level = root.replace(temp_dir, '').count(os.sep)
|
||||
indent = ' ' * 2 * level
|
||||
print(f"{indent}{os.path.basename(root)}/")
|
||||
logger.debug(f"{indent}{os.path.basename(root)}/")
|
||||
subindent = ' ' * 2 * (level + 1)
|
||||
for file in files:
|
||||
print(f"{subindent}{file}")
|
||||
logger.debug(f"{subindent}{file}")
|
||||
|
||||
# Создаем словари для хранения данных как в оригинале
|
||||
df_pm_facts = {} # Словарь с данными факта, ключ - ID ОГ
|
||||
@@ -80,8 +84,8 @@ class SvodkaPMParser(ParserPort):
|
||||
elif 'plan' in file.lower() or 'план' in file.lower():
|
||||
plan_files.append(full_path)
|
||||
|
||||
print(f"📊 Найдено файлов факта: {len(fact_files)}")
|
||||
print(f"📊 Найдено файлов плана: {len(plan_files)}")
|
||||
logger.info(f"📊 Найдено файлов факта: {len(fact_files)}")
|
||||
logger.info(f"📊 Найдено файлов плана: {len(plan_files)}")
|
||||
|
||||
# Обрабатываем найденные файлы
|
||||
for fact_file in fact_files:
|
||||
@@ -91,9 +95,9 @@ class SvodkaPMParser(ParserPort):
|
||||
if 'svodka_fact_pm_' in filename:
|
||||
og_id = filename.replace('svodka_fact_pm_', '').replace('.xlsx', '').replace('.xlsm', '')
|
||||
if og_id in SINGLE_OGS:
|
||||
print(f'📊 Загрузка факта: {fact_file} (ОГ: {og_id})')
|
||||
logger.info(f'📊 Загрузка факта: {fact_file} (ОГ: {og_id})')
|
||||
df_pm_facts[og_id] = self._parse_svodka_pm(fact_file, 'Сводка Нефтепереработка')
|
||||
print(f"✅ Факт загружен для {og_id}")
|
||||
logger.info(f"✅ Факт загружен для {og_id}")
|
||||
|
||||
for plan_file in plan_files:
|
||||
# Извлекаем ID ОГ из имени файла
|
||||
@@ -102,9 +106,9 @@ class SvodkaPMParser(ParserPort):
|
||||
if 'svodka_plan_pm_' in filename:
|
||||
og_id = filename.replace('svodka_plan_pm_', '').replace('.xlsx', '').replace('.xlsm', '')
|
||||
if og_id in SINGLE_OGS:
|
||||
print(f'📊 Загрузка плана: {plan_file} (ОГ: {og_id})')
|
||||
logger.info(f'📊 Загрузка плана: {plan_file} (ОГ: {og_id})')
|
||||
df_pm_plans[og_id] = self._parse_svodka_pm(plan_file, 'Сводка Нефтепереработка')
|
||||
print(f"✅ План загружен для {og_id}")
|
||||
logger.info(f"✅ План загружен для {og_id}")
|
||||
|
||||
# Инициализируем None для ОГ, для которых файлы не найдены
|
||||
for og_id in SINGLE_OGS:
|
||||
@@ -123,14 +127,14 @@ class SvodkaPMParser(ParserPort):
|
||||
'df_pm_plans': df_pm_plans
|
||||
}
|
||||
|
||||
print(f"🎯 Обработано ОГ: {len([k for k, v in df_pm_facts.items() if v is not None])} факт, {len([k for k, v in df_pm_plans.items() if v is not None])} план")
|
||||
logger.info(f"🎯 Обработано ОГ: {len([k for k, v in df_pm_facts.items() if v is not None])} факт, {len([k for k, v in df_pm_plans.items() if v is not None])} план")
|
||||
|
||||
return result
|
||||
|
||||
finally:
|
||||
# Удаляем временную директорию
|
||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||
print(f"🗑️ Временная директория удалена: {temp_dir}")
|
||||
logger.debug(f"🗑️ Временная директория удалена: {temp_dir}")
|
||||
|
||||
def _parse_svodka_pm(self, file_path: str, sheet_name: str, header_num: Optional[int] = None) -> pd.DataFrame:
|
||||
"""Парсинг отчетов одного ОГ для БП, ПП и факта"""
|
||||
@@ -226,19 +230,19 @@ class SvodkaPMParser(ParserPort):
|
||||
|
||||
def _get_svodka_value(self, df_svodka: pd.DataFrame, og_id: str, code: int, search_value: Optional[str] = None):
|
||||
"""Служебная функция для простой выборке по сводке"""
|
||||
print(f"🔍 DEBUG: Ищем код '{code}' для ОГ '{og_id}' в DataFrame с {len(df_svodka)} строками")
|
||||
print(f"🔍 DEBUG: Первая строка данных: {df_svodka.iloc[0].tolist()}")
|
||||
print(f"🔍 DEBUG: Доступные индексы: {list(df_svodka.index)}")
|
||||
print(f"🔍 DEBUG: Доступные столбцы: {list(df_svodka.columns)}")
|
||||
logger.debug(f"🔍 Ищем код '{code}' для ОГ '{og_id}' в DataFrame с {len(df_svodka)} строками")
|
||||
logger.debug(f"🔍 Первая строка данных: {df_svodka.iloc[0].tolist()}")
|
||||
logger.debug(f"🔍 Доступные индексы: {list(df_svodka.index)}")
|
||||
logger.debug(f"🔍 Доступные столбцы: {list(df_svodka.columns)}")
|
||||
|
||||
# Проверяем, есть ли код в индексе
|
||||
if code not in df_svodka.index:
|
||||
print(f"⚠️ Код '{code}' не найден в индексе")
|
||||
logger.warning(f"⚠️ Код '{code}' не найден в индексе")
|
||||
return 0
|
||||
|
||||
# Получаем позицию строки с кодом
|
||||
code_row_loc = df_svodka.index.get_loc(code)
|
||||
print(f"🔍 DEBUG: Код '{code}' в позиции {code_row_loc}")
|
||||
logger.debug(f"🔍 Код '{code}' в позиции {code_row_loc}")
|
||||
|
||||
# Определяем позиции для поиска
|
||||
if search_value is None:
|
||||
@@ -254,14 +258,14 @@ class SvodkaPMParser(ParserPort):
|
||||
if col_name == search_value:
|
||||
target_positions.append(i)
|
||||
|
||||
print(f"🔍 DEBUG: Найдены позиции для '{search_value}': {target_positions[:5]}...")
|
||||
print(f"🔍 DEBUG: Позиции в первой строке: {target_positions[:5]}...")
|
||||
logger.debug(f"🔍 Найдены позиции для '{search_value}': {target_positions[:5]}...")
|
||||
logger.debug(f"🔍 Позиции в первой строке: {target_positions[:5]}...")
|
||||
|
||||
print(f"🔍 DEBUG: Ищем столбцы с названием '{search_value}'")
|
||||
print(f"🔍 DEBUG: Целевые позиции: {target_positions[:10]}...")
|
||||
logger.debug(f"🔍 Ищем столбцы с названием '{search_value}'")
|
||||
logger.debug(f"🔍 Целевые позиции: {target_positions[:10]}...")
|
||||
|
||||
if not target_positions:
|
||||
print(f"⚠️ Позиции '{search_value}' не найдены")
|
||||
logger.warning(f"⚠️ Позиции '{search_value}' не найдены")
|
||||
return 0
|
||||
|
||||
# Извлекаем значения из найденных позиций
|
||||
@@ -285,7 +289,7 @@ class SvodkaPMParser(ParserPort):
|
||||
|
||||
# Преобразуем в числовой формат
|
||||
numeric_values = pd.to_numeric(values, errors='coerce')
|
||||
print(f"🔍 DEBUG: Числовые значения (первые 5): {numeric_values.tolist()[:5]}")
|
||||
logger.debug(f"🔍 Числовые значения (первые 5): {numeric_values.tolist()[:5]}")
|
||||
|
||||
# Попробуем альтернативное преобразование
|
||||
try:
|
||||
@@ -301,10 +305,10 @@ class SvodkaPMParser(ParserPort):
|
||||
except (ValueError, TypeError):
|
||||
manual_values.append(0)
|
||||
|
||||
print(f"🔍 DEBUG: Ручное преобразование (первые 5): {manual_values[:5]}")
|
||||
logger.debug(f"🔍 Ручное преобразование (первые 5): {manual_values[:5]}")
|
||||
numeric_values = pd.Series(manual_values)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Ошибка при ручном преобразовании: {e}")
|
||||
logger.warning(f"⚠️ Ошибка при ручном преобразовании: {e}")
|
||||
# Используем исходные значения
|
||||
numeric_values = pd.Series([0 if pd.isna(v) or v is None else v for v in values])
|
||||
|
||||
@@ -338,7 +342,7 @@ class SvodkaPMParser(ParserPort):
|
||||
|
||||
# Получаем данные из сохраненных словарей (через self.df)
|
||||
if not hasattr(self, 'df') or self.df is None:
|
||||
print("❌ Данные не загружены. Сначала загрузите ZIP архив.")
|
||||
logger.error("❌ Данные не загружены. Сначала загрузите ZIP архив.")
|
||||
return {col: {str(code): None for code in codes} for col in columns}
|
||||
|
||||
# Извлекаем словари из сохраненных данных
|
||||
@@ -349,10 +353,10 @@ class SvodkaPMParser(ParserPort):
|
||||
fact_df = df_pm_facts.get(og_id)
|
||||
plan_df = df_pm_plans.get(og_id)
|
||||
|
||||
print(f"🔍 ===== НАЧАЛО ОБРАБОТКИ ОГ {og_id} =====")
|
||||
print(f"🔍 Коды: {codes}")
|
||||
print(f"🔍 Столбцы: {columns}")
|
||||
print(f"🔍 Получены данные для {og_id}: факт={'✅' if fact_df is not None else '❌'}, план={'✅' if plan_df is not None else '❌'}")
|
||||
logger.debug(f"🔍 ===== НАЧАЛО ОБРАБОТКИ ОГ {og_id} =====")
|
||||
logger.debug(f"🔍 Коды: {codes}")
|
||||
logger.debug(f"🔍 Столбцы: {columns}")
|
||||
logger.debug(f"🔍 Получены данные для {og_id}: факт={'✅' if fact_df is not None else '❌'}, план={'✅' if plan_df is not None else '❌'}")
|
||||
|
||||
# Определяем, какие столбцы из какого датафрейма брать
|
||||
for col in columns:
|
||||
@@ -360,24 +364,24 @@ class SvodkaPMParser(ParserPort):
|
||||
|
||||
if col in ['ПП', 'БП']:
|
||||
if plan_df is None:
|
||||
print(f"❌ Невозможно обработать '{col}': нет данных плана для {og_id}")
|
||||
logger.warning(f"❌ Невозможно обработать '{col}': нет данных плана для {og_id}")
|
||||
else:
|
||||
print(f"🔍 DEBUG: ===== ОБРАБАТЫВАЕМ '{col}' ИЗ ДАННЫХ ПЛАНА =====")
|
||||
logger.debug(f"🔍 ===== ОБРАБАТЫВАЕМ '{col}' ИЗ ДАННЫХ ПЛАНА =====")
|
||||
for code in codes:
|
||||
print(f"🔍 DEBUG: --- Код {code} для {col} ---")
|
||||
logger.debug(f"🔍 --- Код {code} для {col} ---")
|
||||
val = self._get_svodka_value(plan_df, og_id, code, col)
|
||||
col_result[str(code)] = val
|
||||
print(f"🔍 DEBUG: ===== ЗАВЕРШИЛИ ОБРАБОТКУ '{col}' =====")
|
||||
logger.debug(f"🔍 ===== ЗАВЕРШИЛИ ОБРАБОТКУ '{col}' =====")
|
||||
|
||||
elif col in ['ТБ', 'СЭБ', 'НЭБ']:
|
||||
if fact_df is None:
|
||||
print(f"❌ Невозможно обработать '{col}': нет данных факта для {og_id}")
|
||||
logger.warning(f"❌ Невозможно обработать '{col}': нет данных факта для {og_id}")
|
||||
else:
|
||||
for code in codes:
|
||||
val = self._get_svodka_value(fact_df, og_id, code, col)
|
||||
col_result[str(code)] = val
|
||||
else:
|
||||
print(f"⚠️ Неизвестный столбец: '{col}'. Пропускаем.")
|
||||
logger.warning(f"⚠️ Неизвестный столбец: '{col}'. Пропускаем.")
|
||||
col_result = {str(code): None for code in codes}
|
||||
|
||||
result[col] = col_result
|
||||
@@ -443,7 +447,7 @@ class SvodkaPMParser(ParserPort):
|
||||
data = self._get_svodka_og(og_id, codes, columns, search)
|
||||
total_result[og_id] = data
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при обработке {og_id}: {e}")
|
||||
logger.error(f"❌ Ошибка при обработке {og_id}: {e}")
|
||||
total_result[og_id] = None
|
||||
|
||||
json_result = data_to_json(total_result)
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
import tempfile
|
||||
import shutil
|
||||
import zipfile
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
from core.ports import ParserPort
|
||||
@@ -11,6 +12,9 @@ from core.schema_utils import register_getter_from_schema, validate_params_with_
|
||||
from app.schemas.svodka_repair_ca import SvodkaRepairCARequest
|
||||
from adapters.pconfig import SINGLE_OGS, find_header_row, get_og_by_name
|
||||
|
||||
# Настройка логгера для модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SvodkaRepairCAParser(ParserPort):
|
||||
"""Парсер для сводок ремонта СА"""
|
||||
@@ -29,7 +33,7 @@ class SvodkaRepairCAParser(ParserPort):
|
||||
|
||||
def _get_repair_data_wrapper(self, params: dict):
|
||||
"""Получение данных о ремонтных работах"""
|
||||
print(f"🔍 DEBUG: _get_repair_data_wrapper вызван с параметрами: {params}")
|
||||
logger.debug(f"🔍 _get_repair_data_wrapper вызван с параметрами: {params}")
|
||||
|
||||
# Валидируем параметры с помощью схемы Pydantic
|
||||
validated_params = validate_params_with_schema(params, SvodkaRepairCARequest)
|
||||
@@ -39,21 +43,21 @@ class SvodkaRepairCAParser(ParserPort):
|
||||
include_planned = validated_params.get("include_planned", True)
|
||||
include_factual = validated_params.get("include_factual", True)
|
||||
|
||||
print(f"🔍 DEBUG: Запрошенные ОГ: {og_ids}")
|
||||
print(f"🔍 DEBUG: Запрошенные типы ремонта: {repair_types}")
|
||||
print(f"🔍 DEBUG: Включать плановые: {include_planned}, фактические: {include_factual}")
|
||||
logger.debug(f"🔍 Запрошенные ОГ: {og_ids}")
|
||||
logger.debug(f"🔍 Запрошенные типы ремонта: {repair_types}")
|
||||
logger.debug(f"🔍 Включать плановые: {include_planned}, фактические: {include_factual}")
|
||||
|
||||
# Проверяем, есть ли данные в 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 с {len(data_source)} записями")
|
||||
logger.debug(f"🔍 Используем data_dict с {len(data_source)} записями")
|
||||
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 с {len(data_source)} записями")
|
||||
logger.debug(f"🔍 Используем df, преобразованный в data_dict с {len(data_source)} записями")
|
||||
else:
|
||||
print(f"🔍 DEBUG: Нет данных! data_dict={getattr(self, 'data_dict', 'None')}, df={getattr(self, 'df', 'None')}")
|
||||
logger.warning(f"🔍 Нет данных! data_dict={getattr(self, 'data_dict', 'None')}, df={getattr(self, 'df', 'None')}")
|
||||
return []
|
||||
|
||||
# Группируем данные по ОГ (как в оригинале)
|
||||
@@ -86,8 +90,8 @@ class SvodkaRepairCAParser(ParserPort):
|
||||
grouped_data[og_id].append(filtered_item)
|
||||
|
||||
total_records = sum(len(v) for v in grouped_data.values())
|
||||
print(f"🔍 DEBUG: Отфильтровано {total_records} записей из {len(data_source)}")
|
||||
print(f"🔍 DEBUG: Группировано по {len(grouped_data)} ОГ: {list(grouped_data.keys())}")
|
||||
logger.debug(f"🔍 Отфильтровано {total_records} записей из {len(data_source)}")
|
||||
logger.debug(f"🔍 Группировано по {len(grouped_data)} ОГ: {list(grouped_data.keys())}")
|
||||
return grouped_data
|
||||
|
||||
def _df_to_data_dict(self):
|
||||
@@ -109,7 +113,7 @@ class SvodkaRepairCAParser(ParserPort):
|
||||
|
||||
def parse(self, file_path: str, params: dict) -> pd.DataFrame:
|
||||
"""Парсинг файла и возврат DataFrame"""
|
||||
print(f"🔍 DEBUG: SvodkaRepairCAParser.parse вызван с файлом: {file_path}")
|
||||
logger.debug(f"🔍 SvodkaRepairCAParser.parse вызван с файлом: {file_path}")
|
||||
|
||||
# Определяем, это ZIP архив или одиночный файл
|
||||
if file_path.lower().endswith('.zip'):
|
||||
@@ -133,17 +137,17 @@ class SvodkaRepairCAParser(ParserPort):
|
||||
if data_rows:
|
||||
df = pd.DataFrame(data_rows)
|
||||
self.df = df
|
||||
print(f"🔍 DEBUG: Создан DataFrame с {len(data_rows)} записями")
|
||||
logger.debug(f"🔍 Создан DataFrame с {len(data_rows)} записями")
|
||||
return df
|
||||
|
||||
# Если данных нет, возвращаем пустой DataFrame
|
||||
self.df = pd.DataFrame()
|
||||
print(f"🔍 DEBUG: Возвращаем пустой DataFrame")
|
||||
logger.debug(f"🔍 Возвращаем пустой DataFrame")
|
||||
return self.df
|
||||
|
||||
def _parse_zip_archive(self, file_path: str, params: dict) -> List[Dict]:
|
||||
"""Парсинг ZIP архива с файлами ремонта СА"""
|
||||
print(f"🔍 DEBUG: Парсинг ZIP архива: {file_path}")
|
||||
logger.info(f"🔍 Парсинг ZIP архива: {file_path}")
|
||||
|
||||
all_data = []
|
||||
temp_dir = None
|
||||
@@ -151,7 +155,7 @@ class SvodkaRepairCAParser(ParserPort):
|
||||
try:
|
||||
# Создаем временную директорию
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
print(f"📦 Архив разархивирован в: {temp_dir}")
|
||||
logger.debug(f"📦 Архив разархивирован в: {temp_dir}")
|
||||
|
||||
# Разархивируем файл
|
||||
with zipfile.ZipFile(file_path, 'r') as zip_ref:
|
||||
@@ -164,30 +168,30 @@ class SvodkaRepairCAParser(ParserPort):
|
||||
if file.lower().endswith(('.xlsx', '.xlsm', '.xls')):
|
||||
excel_files.append(os.path.join(root, file))
|
||||
|
||||
print(f"📊 Найдено Excel файлов: {len(excel_files)}")
|
||||
logger.info(f"📊 Найдено Excel файлов: {len(excel_files)}")
|
||||
|
||||
# Обрабатываем каждый найденный файл
|
||||
for excel_file in excel_files:
|
||||
print(f"📊 Обработка файла: {excel_file}")
|
||||
logger.info(f"📊 Обработка файла: {excel_file}")
|
||||
file_data = self._parse_single_file(excel_file, params)
|
||||
if file_data:
|
||||
all_data.extend(file_data)
|
||||
|
||||
print(f"🎯 Всего обработано записей: {len(all_data)}")
|
||||
logger.info(f"🎯 Всего обработано записей: {len(all_data)}")
|
||||
return all_data
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при обработке ZIP архива: {e}")
|
||||
logger.error(f"❌ Ошибка при обработке ZIP архива: {e}")
|
||||
return []
|
||||
finally:
|
||||
# Удаляем временную директорию
|
||||
if temp_dir:
|
||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||
print(f"🗑️ Временная директория удалена: {temp_dir}")
|
||||
logger.debug(f"🗑️ Временная директория удалена: {temp_dir}")
|
||||
|
||||
def _parse_single_file(self, file_path: str, params: dict) -> List[Dict]:
|
||||
"""Парсинг одиночного Excel файла"""
|
||||
print(f"🔍 DEBUG: Парсинг файла: {file_path}")
|
||||
logger.debug(f"🔍 Парсинг файла: {file_path}")
|
||||
|
||||
try:
|
||||
# Получаем параметры
|
||||
@@ -198,10 +202,10 @@ class SvodkaRepairCAParser(ParserPort):
|
||||
if header_num is None:
|
||||
header_num = find_header_row(file_path, sheet_name, search_value="ОГ")
|
||||
if header_num is None:
|
||||
print(f"❌ Не найден заголовок в файле {file_path}")
|
||||
logger.error(f"❌ Не найден заголовок в файле {file_path}")
|
||||
return []
|
||||
|
||||
print(f"🔍 DEBUG: Заголовок найден в строке {header_num}")
|
||||
logger.debug(f"🔍 Заголовок найден в строке {header_num}")
|
||||
|
||||
# Читаем Excel файл
|
||||
df = pd.read_excel(
|
||||
@@ -213,23 +217,23 @@ class SvodkaRepairCAParser(ParserPort):
|
||||
)
|
||||
|
||||
if df.empty:
|
||||
print(f"❌ Файл {file_path} пуст")
|
||||
logger.error(f"❌ Файл {file_path} пуст")
|
||||
return []
|
||||
|
||||
if "ОГ" not in df.columns:
|
||||
print(f"⚠️ Предупреждение: Колонка 'ОГ' не найдена в файле {file_path}")
|
||||
logger.warning(f"⚠️ Предупреждение: Колонка 'ОГ' не найдена в файле {file_path}")
|
||||
return []
|
||||
|
||||
# Обрабатываем данные
|
||||
return self._process_repair_data(df)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при парсинге файла {file_path}: {e}")
|
||||
logger.error(f"❌ Ошибка при парсинге файла {file_path}: {e}")
|
||||
return []
|
||||
|
||||
def _process_repair_data(self, df: pd.DataFrame) -> List[Dict]:
|
||||
"""Обработка данных о ремонте"""
|
||||
print(f"🔍 DEBUG: Обработка данных с {len(df)} строками")
|
||||
logger.debug(f"🔍 Обработка данных с {len(df)} строками")
|
||||
|
||||
# Шаг 1: Нормализация ОГ
|
||||
def safe_replace(val):
|
||||
@@ -254,7 +258,7 @@ class SvodkaRepairCAParser(ParserPort):
|
||||
df = df[mask_og].copy()
|
||||
|
||||
if df.empty:
|
||||
print(f"❌ Нет данных после фильтрации по ОГ")
|
||||
logger.info(f"❌ Нет данных после фильтрации по ОГ")
|
||||
return []
|
||||
|
||||
# Шаг 4: Удаление строк без "Вид простоя"
|
||||
@@ -263,7 +267,7 @@ class SvodkaRepairCAParser(ParserPort):
|
||||
mask_downtime = (downtime_clean != "") & (downtime_clean != "nan")
|
||||
df = df[mask_downtime].copy()
|
||||
else:
|
||||
print("⚠️ Предупреждение: Колонка 'Вид простоя' не найдена.")
|
||||
logger.info("⚠️ Предупреждение: Колонка 'Вид простоя' не найдена.")
|
||||
return []
|
||||
|
||||
# Шаг 5: Удаление ненужных колонок
|
||||
@@ -278,7 +282,7 @@ class SvodkaRepairCAParser(ParserPort):
|
||||
|
||||
# Шаг 6: Переименование первых 8 колонок по порядку
|
||||
if df.shape[1] < 8:
|
||||
print(f"⚠️ Внимание: В DataFrame только {df.shape[1]} колонок, требуется минимум 8.")
|
||||
logger.info(f"⚠️ Внимание: В DataFrame только {df.shape[1]} колонок, требуется минимум 8.")
|
||||
return []
|
||||
|
||||
new_names = ["id", "name", "type", "start_date", "end_date", "plan", "fact", "downtime"]
|
||||
@@ -328,10 +332,10 @@ class SvodkaRepairCAParser(ParserPort):
|
||||
result_data.append(record)
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Ошибка при обработке строки: {e}")
|
||||
logger.info(f"⚠️ Ошибка при обработке строки: {e}")
|
||||
continue
|
||||
|
||||
print(f"✅ Обработано {len(result_data)} записей")
|
||||
logger.info(f"✅ Обработано {len(result_data)} записей")
|
||||
return result_data
|
||||
|
||||
def _parse_date(self, value) -> Optional[str]:
|
||||
|
||||
@@ -4,6 +4,10 @@ import json
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import os
|
||||
import logging
|
||||
|
||||
# Настройка логгера для модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
OG_IDS = {
|
||||
"Комсомольский НПЗ": "KNPZ",
|
||||
@@ -163,7 +167,7 @@ def find_header_row(file, sheet, search_value="Итого", max_rows=50):
|
||||
# Ищем строку, где хотя бы в одном столбце встречается искомое значение
|
||||
for idx, row in df_temp.iterrows():
|
||||
if row.astype(str).str.strip().str.contains(f"^{search_value}$", case=False, regex=True).any():
|
||||
print(f"Заголовок найден в строке {idx} (Excel: {idx + 1})")
|
||||
logger.debug(f"Заголовок найден в строке {idx} (Excel: {idx + 1})")
|
||||
return idx # 0-based index — то, что нужно для header=
|
||||
|
||||
raise ValueError(f"Не найдена строка с заголовком '{search_value}' в первых {max_rows} строках.")
|
||||
|
||||
@@ -4,12 +4,16 @@
|
||||
import os
|
||||
import pickle
|
||||
import io
|
||||
import logging
|
||||
from typing import Optional
|
||||
from minio import Minio # boto3
|
||||
import pandas as pd
|
||||
|
||||
from core.ports import StoragePort
|
||||
|
||||
# Настройка логгера для модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MinIOStorageAdapter(StoragePort):
|
||||
"""Адаптер для MinIO хранилища"""
|
||||
@@ -37,8 +41,8 @@ class MinIOStorageAdapter(StoragePort):
|
||||
# Проверяем bucket только при первом использовании
|
||||
self._ensure_bucket_exists()
|
||||
except Exception as e:
|
||||
print(f"⚠️ Не удалось подключиться к MinIO: {e}")
|
||||
print("MinIO будет недоступен, но приложение продолжит работать")
|
||||
logger.warning(f"⚠️ Не удалось подключиться к MinIO: {e}")
|
||||
logger.warning("MinIO будет недоступен, но приложение продолжит работать")
|
||||
return None
|
||||
return self._client
|
||||
|
||||
@@ -50,16 +54,16 @@ class MinIOStorageAdapter(StoragePort):
|
||||
try:
|
||||
if not self.client.bucket_exists(self._bucket_name):
|
||||
self.client.make_bucket(self._bucket_name)
|
||||
print(f"✅ Bucket '{self._bucket_name}' создан")
|
||||
logger.info(f"✅ Bucket '{self._bucket_name}' создан")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при работе с bucket: {e}")
|
||||
logger.error(f"❌ Ошибка при работе с bucket: {e}")
|
||||
return False
|
||||
|
||||
def save_dataframe(self, df: pd.DataFrame, object_id: str) -> bool:
|
||||
"""Сохранение DataFrame в MinIO"""
|
||||
if self.client is None:
|
||||
print("⚠️ MinIO недоступен, данные не сохранены")
|
||||
logger.warning("⚠️ MinIO недоступен, данные не сохранены")
|
||||
return False
|
||||
|
||||
try:
|
||||
@@ -78,16 +82,16 @@ class MinIOStorageAdapter(StoragePort):
|
||||
content_type='application/octet-stream'
|
||||
)
|
||||
|
||||
print(f"✅ DataFrame успешно сохранен в MinIO: {self._bucket_name}/{object_id}")
|
||||
logger.info(f"✅ DataFrame успешно сохранен в MinIO: {self._bucket_name}/{object_id}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при сохранении в MinIO: {e}")
|
||||
logger.error(f"❌ Ошибка при сохранении в MinIO: {e}")
|
||||
return False
|
||||
|
||||
def load_dataframe(self, object_id: str) -> Optional[pd.DataFrame]:
|
||||
"""Загрузка DataFrame из MinIO"""
|
||||
if self.client is None:
|
||||
print("⚠️ MinIO недоступен, данные не загружены")
|
||||
logger.warning("⚠️ MinIO недоступен, данные не загружены")
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -102,7 +106,7 @@ class MinIOStorageAdapter(StoragePort):
|
||||
|
||||
return df
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при загрузке данных из MinIO: {e}")
|
||||
logger.error(f"❌ Ошибка при загрузке данных из MinIO: {e}")
|
||||
return None
|
||||
finally:
|
||||
if 'response' in locals():
|
||||
@@ -112,15 +116,15 @@ class MinIOStorageAdapter(StoragePort):
|
||||
def delete_object(self, object_id: str) -> bool:
|
||||
"""Удаление объекта из MinIO"""
|
||||
if self.client is None:
|
||||
print("⚠️ MinIO недоступен, объект не удален")
|
||||
logger.warning("⚠️ MinIO недоступен, объект не удален")
|
||||
return False
|
||||
|
||||
try:
|
||||
self.client.remove_object(self._bucket_name, object_id)
|
||||
print(f"✅ Объект успешно удален из MinIO: {self._bucket_name}/{object_id}")
|
||||
logger.info(f"✅ Объект успешно удален из MinIO: {self._bucket_name}/{object_id}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при удалении объекта из MinIO: {e}")
|
||||
logger.error(f"❌ Ошибка при удалении объекта из MinIO: {e}")
|
||||
return False
|
||||
|
||||
def object_exists(self, object_id: str) -> bool:
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
import os
|
||||
import multiprocessing
|
||||
import uvicorn
|
||||
import logging
|
||||
from typing import Dict, List
|
||||
from fastapi import FastAPI, File, UploadFile, HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
# Настройка логирования
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s - %(levelname)s - %(name)s:%(lineno)d - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# Настройка логгера для модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from adapters.storage import MinIOStorageAdapter
|
||||
from adapters.parsers import SvodkaPMParser, SvodkaCAParser, MonitoringFuelParser, MonitoringTarParser, SvodkaRepairCAParser, StatusesRepairCAParser, OperSpravkaTechPosParser
|
||||
|
||||
@@ -154,7 +165,7 @@ async def get_available_ogs(parser_name: str):
|
||||
if available_ogs:
|
||||
return {"parser": parser_name, "available_ogs": available_ogs}
|
||||
except Exception as e:
|
||||
print(f"⚠️ Ошибка при получении ОГ: {e}")
|
||||
logger.error(f"⚠️ Ошибка при получении ОГ: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
@@ -1412,7 +1423,7 @@ async def get_oper_spravka_tech_pos_data(request: OperSpravkaTechPosRequest):
|
||||
if result.success:
|
||||
# Извлекаем данные из результата
|
||||
value_data = result.data.get("value", []) if isinstance(result.data.get("value"), list) else []
|
||||
print(f"🔍 DEBUG: API возвращает данные: {type(value_data)}, длина: {len(value_data) if isinstance(value_data, (list, dict)) else 'N/A'}")
|
||||
logger.debug(f"🔍 API возвращает данные: {type(value_data)}, длина: {len(value_data) if isinstance(value_data, (list, dict)) else 'N/A'}")
|
||||
|
||||
return OperSpravkaTechPosResponse(
|
||||
success=True,
|
||||
|
||||
@@ -3,11 +3,15 @@
|
||||
"""
|
||||
import tempfile
|
||||
import os
|
||||
import logging
|
||||
from typing import Dict, Type
|
||||
|
||||
from core.models import UploadRequest, UploadResult, DataRequest, DataResult
|
||||
from core.ports import ParserPort, StoragePort
|
||||
|
||||
# Настройка логгера для модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Глобальный словарь парсеров
|
||||
PARSERS: Dict[str, Type[ParserPort]] = {}
|
||||
@@ -51,7 +55,7 @@ class ReportService:
|
||||
# Удаляем старый объект, если он существует и хранилище доступно
|
||||
if self.storage.object_exists(object_id):
|
||||
self.storage.delete_object(object_id)
|
||||
print(f"Старый объект удален: {object_id}")
|
||||
logger.debug(f"Старый объект удален: {object_id}")
|
||||
|
||||
# Сохраняем в хранилище
|
||||
if self.storage.save_dataframe(parse_result, object_id):
|
||||
@@ -102,18 +106,18 @@ class ReportService:
|
||||
|
||||
# Устанавливаем данные в парсер для использования в геттерах
|
||||
parser.df = loaded_data
|
||||
print(f"🔍 DEBUG: ReportService.get_data - установлены данные в парсер {request.report_type}")
|
||||
logger.debug(f"🔍 ReportService.get_data - установлены данные в парсер {request.report_type}")
|
||||
|
||||
# Проверяем тип загруженных данных
|
||||
if hasattr(loaded_data, 'shape'):
|
||||
# Это DataFrame
|
||||
print(f"🔍 DEBUG: DataFrame shape: {loaded_data.shape}")
|
||||
print(f"🔍 DEBUG: DataFrame columns: {list(loaded_data.columns) if not loaded_data.empty else 'Empty'}")
|
||||
logger.debug(f"🔍 DataFrame shape: {loaded_data.shape}")
|
||||
logger.debug(f"🔍 DataFrame columns: {list(loaded_data.columns) if not loaded_data.empty else 'Empty'}")
|
||||
elif isinstance(loaded_data, dict):
|
||||
# Это словарь (для парсера ПМ)
|
||||
print(f"🔍 DEBUG: Словарь с ключами: {list(loaded_data.keys())}")
|
||||
logger.debug(f"🔍 Словарь с ключами: {list(loaded_data.keys())}")
|
||||
else:
|
||||
print(f"🔍 DEBUG: Неизвестный тип данных: {type(loaded_data)}")
|
||||
logger.debug(f"🔍 Неизвестный тип данных: {type(loaded_data)}")
|
||||
|
||||
# Получаем параметры запроса
|
||||
get_params = request.get_params or {}
|
||||
@@ -158,7 +162,7 @@ class ReportService:
|
||||
available_getters = list(parser.getters.keys())
|
||||
if available_getters:
|
||||
getter_name = available_getters[0]
|
||||
print(f"⚠️ Режим не указан, используем первый доступный: {getter_name}")
|
||||
logger.warning(f"⚠️ Режим не указан, используем первый доступный: {getter_name}")
|
||||
else:
|
||||
return DataResult(
|
||||
success=False,
|
||||
@@ -172,7 +176,7 @@ class ReportService:
|
||||
available_getters = list(parser.getters.keys())
|
||||
if available_getters:
|
||||
getter_name = available_getters[0]
|
||||
print(f"⚠️ Режим не указан, используем первый доступный: {getter_name}")
|
||||
logger.warning(f"⚠️ Режим не указан, используем первый доступный: {getter_name}")
|
||||
else:
|
||||
return DataResult(
|
||||
success=False,
|
||||
@@ -189,7 +193,7 @@ class ReportService:
|
||||
available_getters = list(parser.getters.keys())
|
||||
if available_getters:
|
||||
getter_name = available_getters[0]
|
||||
print(f"⚠️ Режим не указан, используем первый доступный: {getter_name}")
|
||||
logger.warning(f"⚠️ Режим не указан, используем первый доступный: {getter_name}")
|
||||
else:
|
||||
return DataResult(
|
||||
success=False,
|
||||
|
||||
Reference in New Issue
Block a user