Files
kadastr14/services/excel_processor.py
2025-08-12 23:53:44 +05:00

184 lines
8.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Сервис для работы с Excel файлами.
"""
import pandas as pd
from pathlib import Path
from typing import List, Set, Dict, Optional
class ExcelProcessor:
def __init__(self, excel_file_path: str):
self.excel_file_path = Path(excel_file_path)
self.cadastral_numbers: Set[str] = set()
self.df: Optional[pd.DataFrame] = None
self.cadastral_column: Optional[str] = None
def load_excel_file(self) -> bool:
"""Загружает Excel файл и находит колонку с кадастровыми номерами"""
try:
if not self.excel_file_path.exists():
print(f"Excel файл {self.excel_file_path} не найден")
return False
# Пробуем разные форматы Excel файлов
try:
self.df = pd.read_excel(self.excel_file_path, engine='openpyxl')
except:
self.df = pd.read_excel(self.excel_file_path, engine='xlrd')
# Ищем колонку с кадастровыми номерами
self.cadastral_column = self._find_cadastral_column()
if self.cadastral_column:
# Извлекаем кадастровые номера
self._extract_cadastral_numbers()
print(f"Загружено {len(self.cadastral_numbers)} кадастровых номеров из Excel")
return True
else:
print("Не найдена колонка с кадастровыми номерами в Excel файле")
return False
except Exception as e:
print(f"Ошибка при загрузке Excel файла: {e}")
return False
def _find_cadastral_column(self) -> Optional[str]:
"""Находит колонку с кадастровыми номерами"""
if self.df is None:
return None
# Ключевые слова для поиска колонки
keywords = ['кадастр', 'номер', 'cadastral', 'number', 'кн', 'cad']
for column in self.df.columns:
column_str = str(column).lower()
if any(keyword in column_str for keyword in keywords):
return column
# Если не найдено по названию, ищем по содержимому
for column in self.df.columns:
# Проверяем первые несколько значений в колонке
sample_values = self.df[column].dropna().head(5)
cadastral_count = 0
for value in sample_values:
if self._is_cadastral_number_format(str(value)):
cadastral_count += 1
# Если больше половины значений похожи на кадастровые номера
if cadastral_count >= len(sample_values) * 0.5:
return column
return None
def _extract_cadastral_numbers(self):
"""Извлекает кадастровые номера из найденной колонки"""
if self.df is None or self.cadastral_column is None:
return
cadastral_numbers = set()
for value in self.df[self.cadastral_column].dropna():
value_str = str(value).strip()
if self._is_cadastral_number_format(value_str):
cadastral_numbers.add(value_str)
self.cadastral_numbers = cadastral_numbers
def _is_cadastral_number_format(self, text: str) -> bool:
"""Проверяет, соответствует ли текст формату кадастрового номера"""
import re
pattern = r'^\d{1,2}:\d{1,2}:\d{6,8}:\d{1,5}$'
return bool(re.match(pattern, text))
def update_excel_with_cadastral_records(self, cadastral_records: List,
output_file_path: str = None) -> bool:
"""Обновляет Excel файл информацией о найденных кадастровых объектах"""
try:
from models.cadastral_record import CadastralRecord
if self.df is None or self.cadastral_column is None:
print("Excel файл не загружен или не найдена колонка с номерами")
return False
# Создаем карты для быстрого поиска
# номер -> запись объекта
number_to_record = {}
for record in cadastral_records:
# Основной номер
number_to_record[record.main_number] = record
# Все остальные номера тоже ведут к тому же объекту
for number in record.other_numbers:
number_to_record[number] = record
# Добавляем новые колонки
self.df['Найден в файле'] = ''
self.df['Основной номер объекта'] = ''
self.df['Дополнительные номера'] = ''
self.df['Кадастровый блок'] = ''
# Заполняем информацию для каждой строки
matched_count = 0
for index, row in self.df.iterrows():
cadastral_number = str(row[self.cadastral_column]).strip()
if cadastral_number in number_to_record:
record = number_to_record[cadastral_number]
matched_count += 1
# 1. Имя файла
file_name = Path(record.file_path).name
self.df.at[index, 'Найден в файле'] = file_name
# 2. Основной номер объекта
self.df.at[index, 'Основной номер объекта'] = record.main_number
# 3. Дополнительные номера в объекте (без основного)
other_numbers_sorted = sorted(record.other_numbers)
if other_numbers_sorted:
self.df.at[index, 'Дополнительные номера'] = ', '.join(other_numbers_sorted)
else:
self.df.at[index, 'Дополнительные номера'] = ''
# 4. Кадастровый блок (от основного номера)
cadastral_block = self._extract_cadastral_block(record.main_number)
if cadastral_block:
self.df.at[index, 'Кадастровый блок'] = cadastral_block
# Сохраняем обновленный файл
output_path = output_file_path or str(self.excel_file_path.with_suffix('.updated.xlsx'))
self.df.to_excel(output_path, index=False, engine='openpyxl')
print(f"Обновленный Excel файл сохранен: {output_path}")
print(f"Найдено совпадений: {matched_count} из {len(self.cadastral_numbers)}")
return True
except Exception as e:
print(f"Ошибка при обновлении Excel файла: {e}")
return False
def _extract_cadastral_block(self, cadastral_number: str) -> str:
"""Извлекает кадастровый блок из номера (первые три части)"""
try:
parts = cadastral_number.split(':')
if len(parts) >= 3:
return ':'.join(parts[:3])
return cadastral_number
except:
return cadastral_number
def get_cadastral_numbers(self) -> Set[str]:
"""Возвращает множество кадастровых номеров из Excel"""
return self.cadastral_numbers.copy()
def get_statistics(self) -> Dict[str, any]:
"""Возвращает статистику по Excel файлу"""
if self.df is None:
return {}
return {
'total_rows': len(self.df),
'cadastral_column': self.cadastral_column,
'cadastral_numbers_count': len(self.cadastral_numbers),
'columns': list(self.df.columns)
}