diff --git a/python_parser/adapters/parsers/monitoring_fuel.py b/python_parser/adapters/parsers/monitoring_fuel.py index 528e883..6e51e6a 100644 --- a/python_parser/adapters/parsers/monitoring_fuel.py +++ b/python_parser/adapters/parsers/monitoring_fuel.py @@ -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 с именем месяца diff --git a/python_parser/adapters/parsers/monitoring_tar.py b/python_parser/adapters/parsers/monitoring_tar.py index d39134b..38404e1 100644 --- a/python_parser/adapters/parsers/monitoring_tar.py +++ b/python_parser/adapters/parsers/monitoring_tar.py @@ -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 "{}" \ No newline at end of file diff --git a/python_parser/adapters/parsers/oper_spravka_tech_pos.py b/python_parser/adapters/parsers/oper_spravka_tech_pos.py index 167a795..267241e 100644 --- a/python_parser/adapters/parsers/oper_spravka_tech_pos.py +++ b/python_parser/adapters/parsers/oper_spravka_tech_pos.py @@ -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 [] \ No newline at end of file diff --git a/python_parser/adapters/parsers/statuses_repair_ca.py b/python_parser/adapters/parsers/statuses_repair_ca.py index 19c5ecd..8332bc0 100644 --- a/python_parser/adapters/parsers/statuses_repair_ca.py +++ b/python_parser/adapters/parsers/statuses_repair_ca.py @@ -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]: diff --git a/python_parser/adapters/parsers/svodka_ca.py b/python_parser/adapters/parsers/svodka_ca.py index c536473..aaec505 100644 --- a/python_parser/adapters/parsers/svodka_ca.py +++ b/python_parser/adapters/parsers/svodka_ca.py @@ -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: diff --git a/python_parser/adapters/parsers/svodka_pm.py b/python_parser/adapters/parsers/svodka_pm.py index 749a688..c274e90 100644 --- a/python_parser/adapters/parsers/svodka_pm.py +++ b/python_parser/adapters/parsers/svodka_pm.py @@ -230,10 +230,10 @@ class SvodkaPMParser(ParserPort): def _get_svodka_value(self, df_svodka: pd.DataFrame, og_id: str, code: int, search_value: Optional[str] = None): """Служебная функция для простой выборке по сводке""" - logger.debug(f"🔍 DEBUG: Ищем код '{code}' для ОГ '{og_id}' в DataFrame с {len(df_svodka)} строками") - logger.debug(f"🔍 DEBUG: Первая строка данных: {df_svodka.iloc[0].tolist()}") - logger.debug(f"🔍 DEBUG: Доступные индексы: {list(df_svodka.index)}") - logger.debug(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: @@ -242,7 +242,7 @@ class SvodkaPMParser(ParserPort): # Получаем позицию строки с кодом code_row_loc = df_svodka.index.get_loc(code) - logger.debug(f"🔍 DEBUG: Код '{code}' в позиции {code_row_loc}") + logger.debug(f"🔍 Код '{code}' в позиции {code_row_loc}") # Определяем позиции для поиска if search_value is None: @@ -258,11 +258,11 @@ class SvodkaPMParser(ParserPort): if col_name == search_value: target_positions.append(i) - logger.debug(f"🔍 DEBUG: Найдены позиции для '{search_value}': {target_positions[:5]}...") - logger.debug(f"🔍 DEBUG: Позиции в первой строке: {target_positions[:5]}...") + logger.debug(f"🔍 Найдены позиции для '{search_value}': {target_positions[:5]}...") + logger.debug(f"🔍 Позиции в первой строке: {target_positions[:5]}...") - logger.debug(f"🔍 DEBUG: Ищем столбцы с названием '{search_value}'") - logger.debug(f"🔍 DEBUG: Целевые позиции: {target_positions[:10]}...") + logger.debug(f"🔍 Ищем столбцы с названием '{search_value}'") + logger.debug(f"🔍 Целевые позиции: {target_positions[:10]}...") if not target_positions: logger.warning(f"⚠️ Позиции '{search_value}' не найдены") @@ -289,7 +289,7 @@ class SvodkaPMParser(ParserPort): # Преобразуем в числовой формат numeric_values = pd.to_numeric(values, errors='coerce') - logger.debug(f"🔍 DEBUG: Числовые значения (первые 5): {numeric_values.tolist()[:5]}") + logger.debug(f"🔍 Числовые значения (первые 5): {numeric_values.tolist()[:5]}") # Попробуем альтернативное преобразование try: @@ -305,7 +305,7 @@ class SvodkaPMParser(ParserPort): except (ValueError, TypeError): manual_values.append(0) - logger.debug(f"🔍 DEBUG: Ручное преобразование (первые 5): {manual_values[:5]}") + logger.debug(f"🔍 Ручное преобразование (первые 5): {manual_values[:5]}") numeric_values = pd.Series(manual_values) except Exception as e: logger.warning(f"⚠️ Ошибка при ручном преобразовании: {e}") @@ -366,12 +366,12 @@ class SvodkaPMParser(ParserPort): if plan_df is None: logger.warning(f"❌ Невозможно обработать '{col}': нет данных плана для {og_id}") else: - logger.debug(f"🔍 DEBUG: ===== ОБРАБАТЫВАЕМ '{col}' ИЗ ДАННЫХ ПЛАНА =====") + logger.debug(f"🔍 ===== ОБРАБАТЫВАЕМ '{col}' ИЗ ДАННЫХ ПЛАНА =====") for code in codes: - logger.debug(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 - logger.debug(f"🔍 DEBUG: ===== ЗАВЕРШИЛИ ОБРАБОТКУ '{col}' =====") + logger.debug(f"🔍 ===== ЗАВЕРШИЛИ ОБРАБОТКУ '{col}' =====") elif col in ['ТБ', 'СЭБ', 'НЭБ']: if fact_df is None: diff --git a/python_parser/adapters/parsers/svodka_repair_ca.py b/python_parser/adapters/parsers/svodka_repair_ca.py index 63ea6fc..734ceb5 100644 --- a/python_parser/adapters/parsers/svodka_repair_ca.py +++ b/python_parser/adapters/parsers/svodka_repair_ca.py @@ -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]: diff --git a/python_parser/adapters/pconfig.py b/python_parser/adapters/pconfig.py index d2a0b33..6075b2a 100644 --- a/python_parser/adapters/pconfig.py +++ b/python_parser/adapters/pconfig.py @@ -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} строках.") diff --git a/python_parser/adapters/storage.py b/python_parser/adapters/storage.py index 45cc8b8..0011a79 100644 --- a/python_parser/adapters/storage.py +++ b/python_parser/adapters/storage.py @@ -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: diff --git a/python_parser/app/main.py b/python_parser/app/main.py index 837f664..820431b 100644 --- a/python_parser/app/main.py +++ b/python_parser/app/main.py @@ -9,10 +9,13 @@ from fastapi.responses import JSONResponse # Настройка логирования logging.basicConfig( level=logging.DEBUG, - format='%(asctime)s - %(levelname)s - %(name)s - %(lineno)d - %(message)s', + 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 @@ -162,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() @@ -1420,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, diff --git a/python_parser/core/services.py b/python_parser/core/services.py index 0e6becf..729907c 100644 --- a/python_parser/core/services.py +++ b/python_parser/core/services.py @@ -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,