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