Загрузка данных


# Dozor/modules/excel/excel_aggregate_inter.py


import re
from tkinter import filedialog, messagebox, ttk
import tkinter as tk
from pathlib import Path 
from modules.excel.excel_aggregate_work import AggregateWorkController
from modules.utils.window_behavior import LevelTwoBehavior

class ExpertAggregateInterface(tk.Toplevel):
    def __init__(self, parent):
        super().__init__(parent)
        
        behavior = LevelTwoBehavior()
        behavior.setup_level_two(self.winfo_toplevel())
        
        self.title("Агрегирование файлов")
        # Оставляем размер из оригинала
        self.geometry("470x250")  
        self.resizable(False, False) 
        
        self.controller = AggregateWorkController()
        main_frame = ttk.Frame(self)
        main_frame.pack(padx=10, pady=5, fill="both", expand=True)

        # === Блок создания файла (как было раньше) ===
        file_frame = ttk.Frame(main_frame)
        file_frame.pack(fill="x", pady=(0, 10))
        
        ttk.Label(file_frame, text="Название создаваемого файла:").pack(side="left")
        self.new_file_name_entry = tk.Entry(file_frame, width=20)
        self.new_file_name_entry.pack(side="left", padx=(5, 0), ipady=2)
        
        create_file_button = ttk.Button(
            file_frame, text="Создать файл", command=self.create_file_with_validation
        )
        create_button_packing_options = {"side": "right", "padx": (5, 0)}
        create_file_button.pack(**create_button_packing_options)

        # === Выбор файла Excel (как было раньше) ===
        select_frame = ttk.Frame(main_frame)
        select_frame.pack(fill="x", pady=(0, 10))
        
        ttk.Label(select_frame, text="Выберите файл Excel:").pack(side="left")
        self.file_path_entry = tk.Entry(select_frame, width=30)
        self.file_path_entry.pack(side="left", padx=(5, 0), ipady=2)
        
        browse_button = ttk.Button(
            select_frame, text="Обзор...", command=self.select_file_for_aggregation
        )
        browse_button.pack(side="right", padx=(5, 0))

        # === ПЕРЕМЕЩАЕМ СЮДА КНОПКУ ОБЪЕДИНЕНИЯ ===
        aggregate_button = ttk.Button(
            main_frame,
            text="Объединить информацию",
            command=self.aggregate_data
        )
        # В ДОНОРСКОМ файле она была во всю ширину и имела отступы сверху/слева
        aggregate_button.pack(fill="x", pady=(5, 10), padx=(0, 0)) 

        # === Проверка транзитивности (в точности как у вас) ===
        sheet_frame = ttk.Frame(main_frame)
        sheet_frame.pack(fill="x", pady=(0, 10))

        # Верхняя строка: поле выбора листа
        top_sheet_line = ttk.Frame(sheet_frame)
        top_sheet_line.pack(fill="x")

        label_sheet = ttk.Label(top_sheet_line, text="Выберите лист (группу):")
        label_sheet.pack(side="left")

        self.sheet_combobox = ttk.Combobox(top_sheet_line, state="readonly", width=20)
        self.sheet_combobox.pack(side="left", padx=(5, 0), ipady=2)
        self.sheet_combobox.bind(
            "<<ComboboxSelected>>", lambda _: self.reset_transitivity_field()
        )

        # Нижняя строка: метка + кнопка проверки
        bottom_check_line = ttk.Frame(sheet_frame)
        bottom_check_line.pack(fill="x", pady=(5, 0))

        check_label = ttk.Label(bottom_check_line, text="Проверить на транзитивность:")
        check_label.pack(side="right", padx=(0, 5))
        
        check_button = ttk.Button(
            bottom_check_line, text="Проверить", command=self.check_transitivity
        )
        check_button.pack(side="right", padx=(5, 0))

        # МЕТКА ПРОЦЕНТОВ (на самом правом краю, как было изначально!)
        # Это атрибут! Без него не будет работать проверка
        self.transitivity_percentage_label = tk.Label(  
            bottom_check_line,
            text="", relief="groove",
            width=8, bg="white"
        )
        self.transitivity_percentage_label.pack(side="right", padx=(5, 0))

        # === НИЖНИЙ БЛОК (Кнопки действий) ===
        # В вашем донорском файле они были НЕ растянутыми по ширине!
        buttons_frame = ttk.Frame(main_frame)
        buttons_frame.pack(fill="x", pady=15)

        fix_button = ttk.Button(
            buttons_frame,
            text="Исправить транзитивность",
            command=self.apply_transitivity_corrections,
        )
        # У вас был просто pack без расширений
        fix_button.pack(side="left", padx=5)
        
        send_to_db_button = ttk.Button(
            buttons_frame,
            text="Отправить в базу данных",
            command=self.send_to_database,
        )
        send_to_db_button.pack(side="right", padx=5)

    def create_file_with_validation(self):
        filename = self.new_file_name_entry.get().strip()
        invalid_chars_pattern = r'[\\/:*?"<>|]'
        
        if not filename:
            messagebox.showwarning("Предупреждение", "Имя файла не может быть пустым.")
            return
        if re.search(invalid_chars_pattern, filename):
            messagebox.showerror("Ошибка", "Имя файла содержит недопустимые символы.")
            return
            
        reserved_names = [
            "CON", "PRN", "AUX", "NUL", "COM1", "LPT1", "COM2", "LPT2", 
            "COM3", "LPT3", "COM4", "LPT4", "COM5", "LPT5"
        ]
        base_name = filename.split(".")[0].upper()
        if base_name in reserved_names:
            messagebox.showerror("Ошибка", f"'{base_name}' — зарезервированное имя.")
            return
            
        project_root = Path(__file__).resolve().parents[2]
        experts_folder = project_root / "experts"
        
        full_path = experts_folder / filename
        if not full_path.exists():
            with open(full_path, 'w') as f:
                pass
            messagebox.showinfo("Готово", f"Файл '{full_path.name}' создан.")
        else:
            messagebox.showinfo("Готово", f"Файл '{full_path.name}' уже существует.")

    def select_file_for_aggregation(self):
        # askopenfilenames вернет кортеж строк
        file_paths = filedialog.askopenfilenames(
            title="Выберите файлы экспертов", 
            filetypes=[("Excel Files", "*.xlsx")]
        )
        
        if not file_paths:
            return
            
        # Сохраняем список во внутренний атрибут контроллера
        self.controller.input_files = list(file_paths)
        
        # Обновляем текстовое поле пути (покажем первый файл как пример)
        display_path = str(Path(file_paths[0]).parent) + "/... (" + str(len(file_paths)) + " files)"
        self.file_path_entry.delete(0, tk.END)
        self.file_path_entry.insert(0, display_path)
        
        # Пробуем загрузить листы первого файла для комбобокса
        sheets = self.controller.load_sheets_from_file(file_paths[0])
        self.sheet_combobox["values"] = sheets
        if sheets:
            self.sheet_combobox.current(0)
            
        messagebox.showinfo("Выбор файлов", f"Выбрано {len(file_paths)} файлов.")
        # === ИСПРАВЛЕНИЕ: возвращаем фокус нашему окну ===
        self.lift()
        self.focus_force()

    def reset_transitivity_field(self):
        self.transitivity_percentage_label.config(text="")

    def check_transitivity(self):
        file_path = self.file_path_entry.get()
        selected_sheet = self.sheet_combobox.get()
        if not file_path or not selected_sheet:
            messagebox.showwarning("Внимание", "Пожалуйста, укажите файл и лист!")
            return
            
        result = self.controller.check_transitivity()
        self.transitivity_percentage_label.config(text=result)

    def apply_transitivity_corrections(self):
        file_path = self.file_path_entry.get()
        selected_sheet = self.sheet_combobox.get()
        if not file_path or not selected_sheet:
            messagebox.showwarning("Внимание", "Пожалуйста, укажите файл и лист!")
            return
            
        success = self.controller.apply_transitivity_corrections(file_path, selected_sheet)
        if success:
            messagebox.showinfo("Готово", "Транзитивность исправлена и файл сохранён.")

    def aggregate_data(self):
        output_file = self.new_file_name_entry.get().strip()
        if not output_file:
            messagebox.showwarning("Предупреждение", "Сначала создайте имя файла.")
            return
        if not output_file.endswith(".xlsx"):
            output_file += ".xlsx"
            
        # Передаем сохраненный ранее список файлов
        input_files = getattr(self.controller, 'input_files', [])
        
        success = self.controller.aggregate_data(output_file, input_files)
        if success:
            messagebox.showinfo("Готово", f"Данные объединены в {output_file}.")
            
                    # === ИСПРАВЛЕНИЕ: возвращаем фокус нашему окну ===
            self.lift()
            self.focus_force()
               

    def send_to_database(self):
        file_path = self.file_path_entry.get()
        if not file_path:
            messagebox.showwarning("Внимание", "Пожалуйста, выберите файл Excel!")
            return
            
        success = self.controller.send_to_database(file_path)
        if success:
            messagebox.showinfo("Готово", "Данные успешно отправлены в базу данных.")










# Dozor/modules/excel/excel_aggregate_work.py

import os
from pathlib import Path
from statistics import mode, StatisticsError
from openpyxl import load_workbook, Workbook
from collections import defaultdict

# --- ДОБАВЛЕНО ДЛЯ ИСПРАВЛЕНИЯ ОШИБКИ ---
try:
    from tkinter import messagebox
except ImportError:
    # Заглушка на случай запуска вне Tkinter
    class _DummyMsg:
        @staticmethod
        def showwarning(*args, **kwargs): pass
        @staticmethod
        def showerror(*args, **kwargs): pass
        @staticmethod
        def showinfo(*args, **kwargs): pass
    messagebox = _DummyMsg()
    
try:
    from modules.data.database_module import get_db_connection
except ImportError:
    def get_db_connection():
        return None

def normalize_excel_value(value):
    """
    Приводит любое значение из Excel к числу или строке для сравнения.
    Убирает запятые, обрабатывает пустоту.
    """
    if value is None or str(value).strip() == "":
        return "EMPTY"
    
    val_str = str(value).replace(',', '.').strip()
    
    try:
        num = float(val_str)
        return num
    except ValueError:
        return val_str.upper()

def aggregate_matrices_by_mode(matrices):
    """
    Принимает список матриц. 
    Возвращает одну матрицу согласно правилам:
    - Нормализация через точку.
    - Если мода есть (хотя бы 2 одинаковых значения) -> берем её.
    - Если все три значения разные (3, 1, 0.333) -> ставим 1.
    """
    if not matrices:
        return []
        
    max_rows = max(len(mat) for mat in matrices)
    max_cols = max(len(row) for mat in matrices for row in mat)
    
    result = [[None for _ in range(max_cols)] for _ in range(max_rows)]
    
    for r in range(max_rows):
        for c in range(max_cols):
            cell_values = []
            for matrix in matrices:
                if r < len(matrix) and c < len(matrix[r]):
                    norm_val = normalize_excel_value(matrix[r][c])
                    if norm_val != "EMPTY":
                        cell_values.append(norm_val)
            
            if not cell_values:
                result[r][c] = ""
                continue

            # Считаем частоту каждого уникального значения
            counts = {}
            for v in cell_values:
                counts[v] = counts.get(v, 0) + 1
                
            # Находим максимальное количество повторений
            max_count = max(counts.values())
            
            # Правило: если хотя бы два значения совпали (max_count >= 2), это мода
            if max_count >= 2:
                candidates = [k for k, v in counts.items() if v == max_count]
                final_val = candidates[0]
            else:
                # Правило: если все разные (например: 3.0, 1.0, 0.333) -> ставим 1
                final_val = 1.0
                
            # Если итоговое число целое (3.0), сохраняем как int для красоты
            if isinstance(final_val, float) and abs(final_val - int(final_val)) < 1e-9:
                result[r][c] = int(final_val)
            else:
                result[r][c] = round(float(final_val), 3) if isinstance(final_val, (int, float)) else final_val
                
    return result

class AggregateWorkController:
    def __init__(self):
        self.input_files = []

    def select_file(self):
        file_paths = filedialog.askopenfilenames(
            title="Выберите файлы экспертов", 
            filetypes=[("Excel Files", "*.xlsx")]
        )
        self.input_files = list(file_paths) if file_paths else []
        return self.input_files

    def load_sheets_from_file(self, file_path):
        try:
            wb = load_workbook(file_path, data_only=True)
            return wb.sheetnames
        except Exception as e:
            print(f"[AGG CTRL] Ошибка чтения {file_path}: {e}")
            return []

    def check_transitivity(self):
        return "N/A"

    def apply_transitivity_corrections(self, file_path=None, sheet_name=None):
        """
        Заглушка: просто показывает сообщение, чтобы кнопка работала.
        Реальная логика правки матрицы должна быть здесь.
        """
        messagebox.showinfo("Транзитивность", "Функция исправления транзитивности пока находится в разработке.")
    def create_empty_excel_template(self, filename):
        project_root = Path(__file__).resolve().parents[2]
        experts_folder = project_root / "experts"
        os.makedirs(experts_folder, exist_ok=True)
        full_path = experts_folder / filename
        wb = Workbook()
        if "Sheet" in wb.sheetnames:
            del wb["Sheet"]
        wb.save(full_path)

    def send_to_database(self, file_path):
        pass

    def aggregate_data(self, output_filename, input_files):
        if not input_files or len(input_files) < 2:
            messagebox.showwarning("Внимание", "Нужно выбрать минимум 2 файла.")
            return False

        all_matrices = []
        conn = get_db_connection()
        
        # --- ЭТАП 1: ЧТЕНИЕ ---
        for file_path in input_files:
            try:
                wb = load_workbook(file_path, data_only=True)
                for sheet_name in wb.sheetnames:
                    ws = wb[sheet_name]
                    
                    matrix = []
                    for row in ws.iter_rows(min_row=2, min_col=4, values_only=True):
                        current_row = []
                        for cell in row:
                            current_row.append(cell)
                        if current_row:
                            matrix.append(current_row)
                            
                    all_matrices.append({'name': sheet_name, 'data': matrix})
            except Exception as e:
                messagebox.showerror("Ошибка", f"Файл битый или защищен:\n{os.path.basename(file_path)}")
                return False

        # --- ЭТАП 2: ГРУППИРОВКА ПО ЛИСТАМ ---
        grouped_by_sheet = defaultdict(list)
        for item in all_matrices:
            grouped_by_sheet[item['name']].append(item['data'])
            
        # --- ЭТАП 3: ЗАПИСЬ ---
        project_root = Path(__file__).resolve().parents[2]
        experts_folder = project_root / "experts"
        os.makedirs(experts_folder, exist_ok=True)
        dest_path = experts_folder / output_filename
        
        final_wb = Workbook()
        if "Sheet" in final_wb.sheetnames:
            del final_wb["Sheet"]
            
        success = True
        
        for sheet_name, matrices_list in grouped_by_sheet.items():
            agg_matrix = aggregate_matrices_by_mode(matrices_list)
            
            new_ws = final_wb.create_sheet(title=sheet_name)
            
            # Пишем заголовки кодов (I-1, I-2...)
            num_indicators = len(agg_matrix)
            for col_idx in range(num_indicators):
                new_ws.cell(row=1, column=col_idx + 4, value=f"I-{col_idx+1}")
                
            # Пишем тело матрицы
            for r_idx, row in enumerate(agg_matrix):
                for c_idx, val in enumerate(row):
                    target_cell = new_ws.cell(row=r_idx + 2, column=c_idx + 4)
                    
                    if val == "" or val is None:
                        target_cell.value = ""
                    elif isinstance(val, int):
                        target_cell.value = val
                    elif isinstance(val, float):
                        # Форматируем числа с точкой всегда
                        target_cell.value = round(val, 3)
                    else:
                        target_cell.value = str(val)
                        
        try:
            final_wb.save(dest_path)
            messagebox.showinfo("Готово", f"Сводный отчет создан:\n{dest_path}")
            return True
        except Exception as e:
            messagebox.showerror("Ошибка сохранения", f"{e}")
            return False










# Dozor/modules/excel/excel_export_inter.py

import tkinter as tk
from tkinter import ttk, messagebox
from modules.excel.excel_export_work import ExportWorkController
from modules.utils.window_behavior import LevelTwoBehavior

class ExpertExportInterface(tk.Toplevel):
    def __init__(self, parent):
        super().__init__(parent)
        
        behavior = LevelTwoBehavior()
        behavior.setup_level_two(self.winfo_toplevel())
        
        self.title("Создание файла для эксперта")
        self.geometry("450x200")
        self.resizable(False, False)
        
        self.controller = ExportWorkController()
        main_frame = ttk.Frame(self)
        main_frame.pack(padx=10, pady=10, fill="both", expand=True)
        
        ttk.Label(main_frame, text="Группа:").grid(row=0, column=0, sticky="w")
        self.group_var = tk.StringVar()
        self.combobox = ttk.Combobox(
            main_frame, textvariable=self.group_var, state="readonly", width=25
        )
        self.combobox.grid(row=0, column=1, padx=(5, 10), sticky="ew")
        self.refresh_groups_list()
        
        add_group_btn = ttk.Button(
            main_frame, text="Добавить группу", command=self.add_group
        )
        add_group_btn.grid(row=0, column=2, padx=(0, 10), sticky="e")
        
        ttk.Label(main_frame, text="Выбранные группы:").grid(row=1, column=0, sticky="w")
        self.selected_groups_text = tk.Text(main_frame, width=30, height=4)
        self.selected_groups_text.grid(
            row=1, column=1, columnspan=2, pady=(5, 10), sticky="ew"
        )
        
        ttk.Label(main_frame, text="Количество экспертов:").grid(
            row=2, column=0, sticky="w"
        )
        self.num_experts = tk.IntVar(value=1)
        self.experts_count_entry = ttk.Entry(main_frame, textvariable=self.num_experts, width=5)
        self.experts_count_entry.grid(row=2, column=1, sticky="w")
        
        remove_group_btn = ttk.Button(
            main_frame, text="Удалить группу", command=self.remove_group
        )
        remove_group_btn.grid(row=2, column=2, sticky="e")
        
        export_btn = ttk.Button(
            main_frame, text="Экспортировать в Excel", command=self.export_to_excel
        )
        export_btn.grid(row=3, column=1, pady=(10, 0))

    def refresh_groups_list(self):
        self.controller.refresh_groups()
        self.combobox["values"] = self.controller.groups
        if self.controller.groups:
            self.combobox.current(0)

    def add_group(self):
        group_name = self.group_var.get()
        self.controller.add_group(group_name)
        self._update_selected_text()

    def remove_group(self):
        self.controller.remove_group()
        self._update_selected_text()

    def _update_selected_text(self):
        current_text = "\n".join(self.controller.selected_groups)
        self.selected_groups_text.delete("1.0", tk.END)
        self.selected_groups_text.insert(tk.END, current_text)

    def export_to_excel(self):
        selected_group_names = (
            self.selected_groups_text.get("1.0", tk.END).strip().split("\n")
        )
        try:
            num_experts = int(self.num_experts.get())
            if num_experts <= 0:
                raise ValueError
        except Exception:
            messagebox.showerror(
                "Ошибка", "Количество экспертов должно быть положительным числом."
            )
            return
            
        success = self.controller.export_to_excel(selected_group_names, num_experts)
        if success:
            messagebox.showinfo(
                title="Экспорт завершён",
                message=f"Успешно создано {num_experts} файлов.\n\nРасположение:\n.../experts/",
            )









# Dozor/modules/excel/excel_export_work.py

import os
from pathlib import Path
from openpyxl import Workbook

try:
    from modules.data.database_module import get_db_connection
except ImportError:
    print("[EXPORT WORK] Не найден database_module.")
    
    def get_db_connection():
        return None

def create_excel_template(groups_objects, output_filename):
    wb = Workbook()
    
    if "Sheet" in wb.sheetnames:
        del wb["Sheet"]
        
    created_any_sheet = False
    
    for group_data in groups_objects:
        indicators_list = group_data.get('indicators', [])
        if not indicators_list:
            continue
            
        sheet_title = f"{group_data['name']}"
        if len(sheet_title) > 31:
            sheet_title = sheet_title[:31]
            
        ws = wb.create_sheet(title=sheet_title)
        created_any_sheet = True
        
        # --- ЗАГОЛОВКИ ЛЕВОЙ ЧАСТИ ---
        headers = ["№ п/п", "Наименование показателя", "Код"]
        for col_idx, header in enumerate(headers, start=1):
            ws.cell(row=1, column=col_idx, value=header)
        
        # --- ДАННЫЕ ЛЕВОЙ ЧАСТИ ---
        for idx, ind_tuple in enumerate(indicators_list, start=1):
            name, code = ind_tuple[0], ind_tuple[1]
            ws.cell(row=idx + 1, column=1, value=str(idx))
            ws.cell(row=idx + 1, column=2, value=name)
            ws.cell(row=idx + 1, column=3, value=code)
        
        # --- ШАПКА МАТРИЦЫ (ВЕРХ) ---
        matrix_start_col = 4
        matrix_start_row = 1
        
        for col_offset, ind_tuple in enumerate(indicators_list):
            code = ind_tuple[1]
            ws.cell(row=matrix_start_row, column=matrix_start_col + col_offset, value=code)
            
        # --- ТЕЛО МАТРИЦЫ ---
        size = len(indicators_list)
        
        for r in range(size):
            for c in range(size):
                cell = ws.cell(
                    row=matrix_start_row + r + 1,
                    column=matrix_start_col + c
                )
                
                if r == c:
                    cell.value = 1.0
                else:
                    cell.value = ""
                    
    if not created_any_sheet:
        ws = wb.create_sheet(title="Пусто")
        ws['A1'] = 'Нет индикаторов.'
        
    try:
        project_root = Path(__file__).resolve().parents[2]
        experts_folder = project_root / "experts"
        os.makedirs(experts_folder, exist_ok=True)
        full_path = experts_folder / output_filename
        wb.save(full_path)
    except Exception as e:
        print(f"[EXPORT WORK ERROR] {e}")

class ExportWorkController:
    def __init__(self):
        self.groups = []
        self.selected_groups = []

    def refresh_groups(self):
        """Читает названия групп из БД."""
        conn = get_db_connection()
        if not conn:
            self.groups = ["Ошибка БД"]
            return
            
        cursor = conn.cursor()
        cursor.execute("SELECT id, name FROM groups ORDER BY name")
        rows = cursor.fetchall()
        
        self.groups = [row[1] for row in rows]
        # Сохраняем ID групп для экспорта
        self._gid_cache = {row[1]: row[0] for row in rows}
        
        conn.close()

    # === НЕДОСТАЮЩИЕ МЕТОДЫ ДЛЯ ИНТЕРФЕЙСА ===
    def add_group(self, group_name):
        """Добавляет группу в список выбранных (вызывается при нажатии кнопки)."""
        if group_name and group_name.strip() and group_name not in self.selected_groups:
            self.selected_groups.append(group_name)

    def remove_group(self):
        """Удаляет последнюю добавленную группу (кнопка Удалить)."""
        if len(self.selected_groups) > 0:
            self.selected_groups.pop(-1)

    def export_to_excel(self, selected_group_names, num_experts):
        if int(num_experts) <= 0 or not selected_group_names:
            return False

        conn = get_db_connection()
        if not conn:
            return False
            
        cursor = conn.cursor()
        
        enriched_groups = []
        for gname in selected_group_names:
            gid = self._gid_cache.get(gname)
            if not gid:
                continue
                
            cursor.execute("""
                SELECT name, code 
                FROM indicators 
                WHERE group_code IN (
                    SELECT code FROM groups WHERE id = ?
                ) 
                ORDER BY name
            """, (gid,))
            
            indicators = cursor.fetchall()
            
            enriched_groups.append({
                'name': gname,
                'indicators': indicators
            })
            
        conn.close()

        if not enriched_groups:
            return False

        project_root = Path(__file__).resolve().parents[2]
        experts_folder = project_root / "experts"
        os.makedirs(experts_folder, exist_ok=True)
        
        success = True
        for expert_num in range(1, int(num_experts) + 1):
            filename = f"Эксперт_{expert_num}.xlsx"
            full_path = experts_folder / filename
            
            create_excel_template(enriched_groups, str(filename))
            
            if not full_path.exists():
                success = False
                
        return success












# Dozor/modules/excel/excel_ui.py

import tkinter as tk
from tkinter import ttk
from pathlib import Path
from modules.utils.window_behavior import LevelTwoBehavior
from modules.excel.excel_export_inter import ExpertExportInterface
from modules.excel.excel_aggregate_inter import ExpertAggregateInterface

class ExpertsFacade(tk.Toplevel):
    def __init__(self, master, current_user):
        super().__init__(master)
        
        self.title("Dozor - Эксперты")
        # Фиксируем минимальный размер, чтобы окно не схлопнулось при centering
        self.minsize(400, 300) 
        self.resizable(False, False)
        
        self.current_user = current_user
        
        # --- СЕТКА ОКНА ---
        self.grid_rowconfigure(0, weight=1) # Хедер
        self.grid_rowconfigure(1, weight=5) # Тело (растягивается)
        self.grid_columnconfigure(0, weight=1)
        
        self._create_header()
        self._create_body()
        
        # Даем Tkinter время рассчитать размеры виджетов ДО перемещения окна
        self.update_idletasks()
        
        # Применяем поведение уровня 2 ПОСЛЕ создания контента
        behavior = LevelTwoBehavior()
        behavior.setup_level_two(self)

    def _create_header(self):
        header = tk.Frame(self, bg="#34495e", height=50)
        header.grid(row=0, column=0, sticky="ew")
        
        title = tk.Label(header, text="Раздел Эксперта", bg="#34495e", fg="white", font=("Arial", 16))
        title.pack(side="left", padx=20, pady=10)
        
        back_btn = tk.Button(header, text="← Назад", command=self.destroy, bg="#7f8c8d", fg="white", relief="flat", bd=0, activebackground="#95a5a6")
        back_btn.pack(side="right", padx=20, pady=10)

    def _create_body(self):
        body = tk.Frame(self, bg="white") 
        body.grid(row=1, column=0, sticky="nsew", padx=30, pady=(30, 30)) # Увеличили нижний отступ для баланса
        
        # Настройка сетки: пустое пространство сверху -> кнопка 1 -> кнопка 2 -> пустое снизу
        body.grid_rowconfigure(0, weight=2)   # Пустота сверху (тянется сильнее)
        body.grid_rowconfigure(1, weight=1)   # Кнопка экспорта
        body.grid_rowconfigure(2, weight=1)   # Кнопка агрегирования
        body.grid_rowconfigure(3, weight=2)   # Пустота снизу (тянется сильнее)
        body.grid_columnconfigure(0, weight=1)
        
        export_btn = tk.Button(
            body, text="Создание Excel файлов", font=("Arial", 14), width=25, height=2,
            command=self.open_export_window,
            bg="#3498db", fg="white", relief="raised", bd=2, activebackground="#5dade2"
        )
        export_btn.grid(row=1, column=0, pady=5, sticky="ew")
        
        aggregate_btn = tk.Button(
            body, text="Агрегирование Excel файлов", font=("Arial", 14), width=25, height=2,
            command=self.open_aggregate_window,
            bg="#27ae60", fg="white", relief="raised", bd=2, activebackground="#52be80"
        )
        aggregate_btn.grid(row=2, column=0, pady=5, sticky="ew")

    def open_export_window(self):
        win = ExpertExportInterface(self)
        self.wait_window(win)

    def open_aggregate_window(self):
        win = ExpertAggregateInterface(self)
        self.wait_window(win)