init
This commit is contained in:
181
python_parser/adapters/pconfig.py
Normal file
181
python_parser/adapters/pconfig.py
Normal file
@@ -0,0 +1,181 @@
|
||||
import re
|
||||
from functools import lru_cache
|
||||
import json
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
OG_IDS = {
|
||||
"Комсомольский НПЗ": "KNPZ",
|
||||
"Ангарская НХК": "ANHK",
|
||||
"Ачинский НПЗ": "AchNPZ",
|
||||
"ЕНПЗ": "BASH",
|
||||
"УНПЗ": "UNPZ",
|
||||
"УНХ": "UNH",
|
||||
"Новойл": "NOV",
|
||||
"Новокуйбышевский НПЗ": "NovKuybNPZ",
|
||||
"Куйбышевский НПЗ": "KuybNPZ",
|
||||
"Сызранский НПЗ": "CyzNPZ",
|
||||
"Туапсинский НПЗ": "TuapsNPZ",
|
||||
"Саратовский НПЗ": "SNPZ",
|
||||
"Рязанская НПК": "RNPK",
|
||||
"Нижневартовское НПО": "NVNPO",
|
||||
"Красноленинский НПЗ": "KLNPZ",
|
||||
"Пурнефтепереработка": "PurNP",
|
||||
"ЯНОС": "YANOS",
|
||||
}
|
||||
|
||||
SNPZ_IDS = {
|
||||
"Висбрекинг": "SNPZ.VISB",
|
||||
"Изомеризация": "SNPZ.IZOM",
|
||||
"Л-24/6": "SNPZ.L24-6",
|
||||
"ЛЧ-35-11/300": "SNPZ.L35-300",
|
||||
"ЛЧ-35-11/600": "SNPZ.L35-600",
|
||||
"ОЗФХ т.у.т/сут": "SNPZ.OZPH",
|
||||
"УПНБ": "SNPZ.UPB",
|
||||
"УПЭС": "SNPZ.UPES",
|
||||
"ЭЛОУ АВТ-6": "SNPZ.EAVT6",
|
||||
"Итого": "SNPZ.TOTAL",
|
||||
"Норматив по фактическим загрузкам": "SNPZ.TOTAL.FACT",
|
||||
}
|
||||
|
||||
|
||||
def replace_id_in_path(file_path, new_id):
|
||||
return file_path.replace('ID', str(new_id))
|
||||
|
||||
|
||||
def get_table_name(exel):
|
||||
return re.sub(r'^data/(.+)\.(xlsm|xlsx)$', r'\1', exel)
|
||||
|
||||
|
||||
def normalize_and_tokenize(text):
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
return set()
|
||||
cleaned = re.sub(r'[^\w\s]', ' ', text.lower())
|
||||
cleaned = cleaned.replace('ё', 'е')
|
||||
words = [word.strip() for word in cleaned.split()]
|
||||
return set(word for word in words if word)
|
||||
|
||||
|
||||
@lru_cache(maxsize=512)
|
||||
def get_object_by_name(name):
|
||||
return get_id_by_name(name, SNPZ_IDS)
|
||||
|
||||
|
||||
@lru_cache(maxsize=512)
|
||||
def get_og_by_name(name):
|
||||
return get_id_by_name(name, OG_IDS)
|
||||
|
||||
|
||||
def get_id_by_name(name, dictionary):
|
||||
if not name or not isinstance(name, str):
|
||||
return None
|
||||
|
||||
query_words = normalize_and_tokenize(name)
|
||||
if not query_words:
|
||||
return None
|
||||
|
||||
best_match = None
|
||||
best_score = 0
|
||||
|
||||
for full_name, obj_id in dictionary.items():
|
||||
entry_words = normalize_and_tokenize(full_name)
|
||||
if not entry_words:
|
||||
continue
|
||||
|
||||
intersection = query_words & entry_words
|
||||
if not intersection:
|
||||
continue
|
||||
|
||||
# Полное совпадение
|
||||
if query_words == entry_words:
|
||||
return obj_id
|
||||
|
||||
# Все слова из словаря есть в запросе
|
||||
if entry_words <= query_words:
|
||||
score = len(entry_words)
|
||||
# Хорошее пересечение
|
||||
elif len(intersection) >= min(2, len(entry_words), len(query_words)):
|
||||
score = len(intersection) / max(len(query_words), len(entry_words))
|
||||
# Одно слово (аббревиатура)
|
||||
elif len(entry_words) == 1 and list(entry_words)[0] in query_words:
|
||||
score = 1.0
|
||||
else:
|
||||
continue
|
||||
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_match = obj_id
|
||||
|
||||
return best_match
|
||||
|
||||
|
||||
def data_to_json(data, indent=2, ensure_ascii=False):
|
||||
"""
|
||||
Полностью безопасная сериализация данных в JSON.
|
||||
Корректно обрабатывает:
|
||||
- np.nan, pd.NA, None → null
|
||||
- DataFrame, Series, numpy массивы и скаляры
|
||||
- вложенные структуры
|
||||
"""
|
||||
def is_nan_like(obj):
|
||||
"""Проверяет, является ли объект NaN-подобным."""
|
||||
if obj is None:
|
||||
return True
|
||||
if pd.isna(obj): # Ловит np.nan, pd.NA, pd.NaT, None
|
||||
return True
|
||||
return False
|
||||
|
||||
def convert_obj(obj):
|
||||
# --- DataFrame ---
|
||||
if isinstance(obj, pd.DataFrame):
|
||||
return [convert_obj(row) for _, row in obj.iterrows()] # каждая строка → dict
|
||||
|
||||
# --- Series ---
|
||||
if isinstance(obj, pd.Series):
|
||||
# Преобразуем индекс в значения, если нужно
|
||||
values = [convert_obj(v) for v in obj.values]
|
||||
# Убираем None (были NaN), но сохраняем структуру, если нужно
|
||||
return values
|
||||
|
||||
# --- numpy скаляры ---
|
||||
elif isinstance(obj, (np.integer, np.int64, np.int32)):
|
||||
return int(obj)
|
||||
elif isinstance(obj, (np.floating, np.float64, np.float32)):
|
||||
return None if is_nan_like(obj) else float(obj)
|
||||
elif isinstance(obj, np.ndarray):
|
||||
return [convert_obj(v) for v in obj]
|
||||
|
||||
# --- временные метки ---
|
||||
elif isinstance(obj, (pd.Timestamp, pd.Timedelta)):
|
||||
return obj.isoformat() if not pd.isna(obj) else None
|
||||
elif isinstance(obj, pd._libs.missing.NAType): # pd.NA
|
||||
return None
|
||||
|
||||
# --- рекурсия по dict и list ---
|
||||
elif isinstance(obj, dict):
|
||||
return {
|
||||
key: convert_obj(value)
|
||||
for key, value in obj.items()
|
||||
if not is_nan_like(key) # фильтруем NaN в ключах (недопустимы в JSON)
|
||||
}
|
||||
|
||||
elif isinstance(obj, list):
|
||||
return [convert_obj(item) for item in obj]
|
||||
|
||||
# --- None и NaN-подобные ---
|
||||
elif is_nan_like(obj):
|
||||
return None
|
||||
|
||||
# --- всё остальное ---
|
||||
else:
|
||||
try:
|
||||
return float(obj) if isinstance(obj, (int, float)) else str(obj)
|
||||
except Exception:
|
||||
return str(obj) # финальный fallback
|
||||
|
||||
try:
|
||||
cleaned_data = convert_obj(data)
|
||||
cleaned_data_str = json.dumps(cleaned_data, indent=indent, ensure_ascii=ensure_ascii)
|
||||
return cleaned_data
|
||||
except Exception as e:
|
||||
raise ValueError(f"Не удалось сериализовать данные в JSON: {e}")
|
||||
Reference in New Issue
Block a user