Files
kadastr14/excel_processor.py

242 lines
11 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, Dict, Optional
from collections import defaultdict
class ExcelProcessor:
"""Класс для работы с Excel файлами списков кадастровых номеров."""
def __init__(self, excel_file_path: str):
self.excel_file_path = Path(excel_file_path)
self.df = None
self.cadastral_column = None
def load_excel(self, sheet_name: Optional[str] = None, cadastral_column: Optional[str] = None):
"""Загружает Excel файл и определяет колонку с кадастровыми номерами."""
try:
# Загружаем Excel файл
if sheet_name:
self.df = pd.read_excel(self.excel_file_path, sheet_name=sheet_name)
else:
# Берем первый лист
self.df = pd.read_excel(self.excel_file_path)
print(f"✅ Загружен Excel файл: {self.excel_file_path}")
print(f"📊 Размер таблицы: {len(self.df)} строк, {len(self.df.columns)} колонок")
print(f"📋 Колонки: {list(self.df.columns)}")
# Определяем колонку с кадастровыми номерами
if cadastral_column:
self.cadastral_column = cadastral_column
else:
self.cadastral_column = self._find_cadastral_column()
if self.cadastral_column:
print(f"🎯 Найдена колонка с кадастровыми номерами: '{self.cadastral_column}'")
cadastral_count = self.df[self.cadastral_column].notna().sum()
print(f"📄 Кадастровых номеров в файле: {cadastral_count}")
else:
raise ValueError("Не удалось найти колонку с кадастровыми номерами")
except Exception as e:
print(f"❌ Ошибка при загрузке Excel файла: {e}")
raise
def _find_cadastral_column(self) -> Optional[str]:
"""Автоматически находит колонку с кадастровыми номерами."""
# Возможные названия колонок
possible_names = [
'кадастровый номер', 'кадастровый_номер', 'кн', 'cadastral_number',
'номер', 'cadastral', 'кадастр', 'kad_number'
]
# Ищем по названию колонки
for col in self.df.columns:
col_lower = str(col).lower().strip()
for name in possible_names:
if name in col_lower:
return col
# Ищем по содержимому (ищем колонку где есть номера вида XX:XX:XXXXXX:XXXX)
import re
pattern = r'\d{1,2}:\d{1,2}:\d{6,7}:\d{1,4}'
for col in self.df.columns:
# Проверяем первые несколько непустых значений
sample_values = self.df[col].dropna().head(10)
match_count = 0
for value in sample_values:
if isinstance(value, str) and re.match(pattern, value.strip()):
match_count += 1
# Если больше половины значений похожи на кадастровые номера
if match_count > len(sample_values) * 0.5 and match_count > 0:
return col
return None
def get_cadastral_numbers_from_excel(self) -> List[str]:
"""Возвращает список всех кадастровых номеров из Excel файла."""
if not self.cadastral_column:
return []
# Получаем все непустые кадастровые номера
cadastral_numbers = []
for value in self.df[self.cadastral_column].dropna():
if isinstance(value, str):
clean_value = value.strip()
if clean_value:
cadastral_numbers.append(clean_value)
elif pd.notna(value):
# Если число, преобразуем в строку
cadastral_numbers.append(str(value).strip())
return cadastral_numbers
def create_cadastral_mapping(self, cadastral_records: List) -> Dict[str, Dict]:
"""Создает маппинг кадастровых номеров к их данным."""
mapping = defaultdict(lambda: {
'source_files': set(),
'related_numbers': set(),
'quarter_numbers': set()
})
for record in cadastral_records:
cadastral_num = record.cadastral_number
# Добавляем источник файла
source_file = record.source_xml_info.get_display_name()
mapping[cadastral_num]['source_files'].add(source_file)
# Добавляем связанные номера
for related in record.related_numbers:
mapping[cadastral_num]['related_numbers'].add(related)
# Добавляем квартал
if record.quarter_number:
mapping[cadastral_num]['quarter_numbers'].add(record.quarter_number)
# Преобразуем sets в списки для удобства
for cadastral_num in mapping:
mapping[cadastral_num]['source_files'] = list(mapping[cadastral_num]['source_files'])
mapping[cadastral_num]['related_numbers'] = list(mapping[cadastral_num]['related_numbers'])
mapping[cadastral_num]['quarter_numbers'] = list(mapping[cadastral_num]['quarter_numbers'])
return dict(mapping)
def fill_cadastral_info(self, cadastral_records: List, output_file: Optional[str] = None):
"""Заполняет информацию о кадастровых номерах в Excel файле."""
if self.df is None or not self.cadastral_column:
raise ValueError("Excel файл не загружен или не найдена колонка с кадастровыми номерами")
print(f"\n{'='*80}")
print(f"ЗАПОЛНЕНИЕ ИНФОРМАЦИИ В EXCEL ФАЙЛЕ")
print(f"{'='*80}")
# Создаем маппинг
cadastral_mapping = self.create_cadastral_mapping(cadastral_records)
# Добавляем новые колонки если их нет
if 'Источники файлов' not in self.df.columns:
self.df['Источники файлов'] = ''
if 'Смежные номера' not in self.df.columns:
self.df['Смежные номера'] = ''
if 'Кадастровый блок' not in self.df.columns:
self.df['Кадастровый блок'] = ''
# Статистика
found_count = 0
not_found_count = 0
# Заполняем информацию для каждого кадастрового номера
for index, row in self.df.iterrows():
cadastral_num = str(row[self.cadastral_column]).strip()
if cadastral_num in cadastral_mapping:
data = cadastral_mapping[cadastral_num]
# Заполняем источники файлов
sources = ', '.join(data['source_files'])
self.df.at[index, 'Источники файлов'] = sources
# Заполняем смежные номера
related = ', '.join(data['related_numbers'])
self.df.at[index, 'Смежные номера'] = related
# Заполняем кадастровый блок (квартал)
quarters = ', '.join(data['quarter_numbers'])
self.df.at[index, 'Кадастровый блок'] = quarters
found_count += 1
print(f"{cadastral_num}: найден в {len(data['source_files'])} файлах")
else:
not_found_count += 1
print(f"{cadastral_num}: не найден в XML файлах")
print(f"\n📊 Статистика заполнения:")
print(f" Найдено: {found_count}")
print(f" Не найдено: {not_found_count}")
print(f" Всего: {found_count + not_found_count}")
# Сохраняем результат
if output_file:
output_path = Path(output_file)
else:
# Создаем имя файла с результатами
output_path = self.excel_file_path.parent / f"{self.excel_file_path.stem}_результат.xlsx"
try:
self.df.to_excel(output_path, index=False)
print(f"\n💾 Результат сохранен в: {output_path}")
return output_path
except Exception as e:
print(f"❌ Ошибка при сохранении: {e}")
raise
def print_excel_preview(self, rows: int = 10):
"""Выводит предварительный просмотр Excel файла."""
if self.df is None:
print("Excel файл не загружен")
return
print(f"\n📋 Предварительный просмотр Excel файла (первые {rows} строк):")
print("-" * 80)
# Показываем первые строки
preview_df = self.df.head(rows)
# Ограничиваем ширину колонок для читаемости
pd.set_option('display.max_columns', None)
pd.set_option('display.width', None)
pd.set_option('display.max_colwidth', 50)
print(preview_df.to_string(index=False))
if len(self.df) > rows:
print(f"\n... и еще {len(self.df) - rows} строк")
def get_statistics(self):
"""Возвращает статистику по Excel файлу."""
if self.df is None:
return {}
stats = {
'total_rows': len(self.df),
'total_columns': len(self.df.columns),
'columns': list(self.df.columns),
'cadastral_column': self.cadastral_column
}
if self.cadastral_column:
cadastral_numbers = self.get_cadastral_numbers_from_excel()
stats['cadastral_numbers_count'] = len(cadastral_numbers)
stats['empty_cadastral_count'] = len(self.df) - len(cadastral_numbers)
return stats