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


# -*- coding: utf-8 -*-
# gui/forecast_window.py

import tkinter as tk
import sqlite3
from tkinter import ttk
from modules.forecasting import monte_carlo_forecast
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from tkcalendar import DateEntry 
from modules.project_database import ProjectDatabase, TABLE_PROJECT_DATA
import numpy as np

# --- НОВЫЙ БЛОК: Класс для виджета Аккордеон (финальный) ---
class AccordionFrame(ttk.Frame):
    """
    Виджет "Аккордеон" для tkinter с автоматическим выравниванием заголовков.
    Использует временную метку для измерения ширины текста.
    """
    def __init__(self, parent, root_window, **kwargs):
        super().__init__(parent, **kwargs)
        self.parent = parent
        self.root_window = root_window
        self.panels = {}
        self.current_open = None
        self.max_button_width = 0

        self.header_frame = ttk.Frame(self)
        self.header_frame.pack(fill="x", padx=10, pady=(10, 0))

        self.content_frame = ttk.Frame(self)
        self.content_frame.pack(fill="both", expand=True, padx=10, pady=5)

    def add_panel(self, title, content_frame):
        """
        Добавляет новую панель в аккордеон.
        Автоматически выравнивает ширину всех кнопок-заголовков.
        """
        self.panels[title] = {'frame': content_frame, 'state': 'closed'}

        def on_click():
            if self.current_open == title:
                self._hide_panel(title)
                self.current_open = None
            else:
                if self.current_open:
                    self._hide_panel(self.current_open)
                self._show_panel(title)
                self.current_open = title

        btn = ttk.Button(self.header_frame, text=f"+ {title}", command=on_click)
        
        # --- ИСПРАВЛЕННАЯ ЛОГИКА ИЗМЕРЕНИЯ ---
        # Создаем временную метку (Label) для измерения ширины текста.
        # Это самый надежный способ в Tkinter.
        try:
            temp_label = tk.Label(self.root_window)
            text_width = temp_label.winfo_font().measure(f"+ {title}")
            temp_label.destroy() # Убираем временный виджет

            if text_width > self.max_button_width:
                self.max_button_width = text_width

            for panel_info in self.panels.values():
                if 'button' in panel_info:
                    panel_info['button'].config(width=self.max_button_width)
                    
        except Exception as e:
            print(f"[DEBUG] Ошибка при выравнивании заголовков: {e}. Заголовки могут быть неровными.")
        
        btn.pack(anchor='w', pady=2, fill='x')
        self.panels[title]['button'] = btn

    def _show_panel(self, title):
        """Показывает панель"""
        panel_info = self.panels[title]
        panel_info['frame'].pack(fill="both", expand=True, padx=10, pady=10)
        panel_info['button'].config(text=f"- {title}")

    def _hide_panel(self, title):
        """Скрывает панель"""
        panel_info = self.panels[title]
        panel_info['frame'].pack_forget()
        panel_info['button'].config(text=f"+ {title}")

class ForecastWindow:
    """
    Окно для отображения прогноза.
    Принимает на вход родительское окно (root), чтобы иметь доступ к его данным.
    """

    def __init__(self, parent, legend_checkbox_widget=None):
        # Сохраняем ссылку на главное окно
        self.root_window = parent

        # Сохраняем переданный виджет флажка (или None, если не передан)
        self.legend_checkbox_widget = legend_checkbox_widget

        # --- НОВОЕ: Создаем переменную для управления легендой ---
        # Если виджет был передан, используем его переменную.
        # Если нет (вызов из main.py) - создаем свою собственную.
        if self.legend_checkbox_widget:
            # Если виджет есть, берем его переменную (например, .var)
            # Это для случая, если мы все-таки передадим виджет в будущем.
            try:
                self.show_legend_var = self.legend_checkbox_widget.var
            except AttributeError:
                # Если у виджета нет атрибута .var, просто используем его напрямую в update_graph
                self.show_legend_var = None
        else:
            # Если виджет не передан (наш случай), создаем свою переменную
            self.show_legend_var = tk.BooleanVar(value=True)

        # Создаем новое окно (Toplevel)
        self.window = tk.Toplevel(parent)
        self.window.title("Прогнозирование")
        self.window.geometry("1050x650")
        self.window.resizable(True, True)
        
        self.project_creation_date = None
        
        # --- ГЛАВНЫЙ КОНТЕЙНЕР ---
        main_container = ttk.Frame(self.window)
        main_container.pack(fill="both", expand=True)

        # Делим главное окно на две части: Верх (График+Настройки) и Низ (Результаты)
        main_container.grid_rowconfigure(0, weight=4)  # Верхняя часть выше
        main_container.grid_rowconfigure(1, weight=1)  # Нижняя часть ниже

        # --- ВЕРХНИЙ БЛОК (График и Настройки) ---
        top_block = ttk.Frame(main_container)
        top_block.grid(row=0, column=0, sticky="nsew")

        # Внутри верхнего блока делим на два фрейма: График (слева) и Настройки (справа)
        top_block.grid_columnconfigure(0, weight=3)  # График шире
        top_block.grid_columnconfigure(1, weight=2)  # Настройки уже

        # 1. Левая панель: График
        graph_frame = ttk.LabelFrame(top_block, text="График прогноза", padding="10")
        graph_frame.grid(row=0, column=0, sticky="nsew")

        self.figure = Figure(figsize=(5, 4), dpi=100)
        self.ax = self.figure.add_subplot(111)
        self.ax.grid(True, linestyle='--', alpha=0.7)

        self.canvas = FigureCanvasTkAgg(self.figure, master=graph_frame)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack(fill="both", expand=True)

        # --- ПРАВАЯ ПАНЕЛЬ: ЗАМЕНА НА АККОРДЕОН ---
        # ИСПРАВЛЕНО: Передаем self.window как второй аргумент
        self.accordion = AccordionFrame(top_block, self.window)
        self.accordion.grid(row=0, column=1, sticky="ns")

        # --- СОЗДАЕМ ФРЕЙМЫ ДЛЯ КАЖДОЙ ПАНЕЛИ АККОРДЕОНА ---

        # Панель 1: Основные настройки (ВЕРСТКА В 1 КОЛОНКУ)
        project_panel = ttk.Frame(self.accordion.content_frame)

        # --- Блок 1: Выбрать проект ---
        project_frame = ttk.Frame(project_panel)
        project_frame.pack(fill="x", pady=(0, 10)) # pady добавит отступ между блоками

        ttk.Label(project_frame, text="Выбрать проект:").pack(side="left", anchor="w", padx=(0, 5))
        self.project_combobox = ttk.Combobox(project_frame, state="readonly", width=25)
        self.project_combobox.pack(side="left", anchor="w")
        self.refresh_button = ttk.Button(project_panel, text="Обновить данные", command=self.update_project_data)
        self.refresh_button.pack() # Кнопка остается в основном фрейме, но под блоком выбора

        # Блок - Начало и Календарь
        date_frame = ttk.Frame(project_panel)
        date_frame.pack(fill="x", pady=(0, 10))

        ttk.Label(date_frame, text="Дата начала:").pack(side="left", anchor="w", padx=(0, 5))
        # Создаем календарь БЕЗ начальной даты (initialdate=None)
        self.start_date_picker = DateEntry(
            date_frame,
            width=18,
            background='darkblue',
            foreground='white',
            borderwidth=2,
            date_pattern='y-mm-dd',
            initialdate=None # <-- ВАЖНОЕ ИЗМЕНЕНИЕ
        )
        self.start_date_picker.pack(side="left", anchor="w")

        # --- Блок 3: Верхняя граница (Smax) ---
        s_max_frame = ttk.Frame(project_panel)
        s_max_frame.pack(fill="x", pady=(0, 10))

        ttk.Label(s_max_frame, text="Верхняя граница (Smax):").pack(side="left", anchor="w", padx=(0, 5))
        self.s_max_entry = ttk.Entry(s_max_frame, width=10)
        self.s_max_entry.insert(0, "95")
        self.s_max_entry.pack(side="left", anchor="w")

        # --- Блок 4: Нижняя граница (Smin) ---
        s_min_frame = ttk.Frame(project_panel)
        s_min_frame.pack(fill="x") # У последнего блока нет отступа снизу

        ttk.Label(s_min_frame, text="Нижняя граница (Smin):").pack(side="left", anchor="w", padx=(0, 5))
        self.s_min_entry = ttk.Entry(s_min_frame, width=10)
        self.s_min_entry.insert(0, "5")
        self.s_min_entry.pack(side="left", anchor="w")


        # Добавляем готовую панель в аккордеон
        self.accordion.add_panel("Основные настройки", project_panel)

        # Панель 2: Краткосрочный прогноз - НОВАЯ ВЕРСТКА
        model_panel = ttk.Frame(self.accordion.content_frame)

        # Флажок ручного ввода
        self.manual_input_var = tk.BooleanVar(value=True)
        self.manual_checkbox = ttk.Checkbutton(
            model_panel,
            text="Ручной ввод",
            variable=self.manual_input_var,
            command=self.toggle_manual_mode
        )
        self.manual_checkbox.pack(pady=(5, 10)) # Добавляем отступ снизу

        # Блок "Интенсивность событий"
        intensity_frame = ttk.Frame(model_panel)
        intensity_frame.pack(fill="x")
        ttk.Label(intensity_frame, text="Интенсивность (в день):").pack(side="left")
        self.lambda_entry = ttk.Entry(intensity_frame, width=10)
        self.lambda_entry.insert(0, "5")
        self.lambda_entry.pack(side="left", padx=5)

        # Блок "Среднее за период"
        avg_frame = ttk.Frame(model_panel)
        avg_frame.pack(fill="x")
        ttk.Label(avg_frame, text="Среднее за:").pack(side="left")
        self.avg_combobox = ttk.Combobox(avg_frame, state='readonly', values=[3, 5, 7, 10, 30], width=5)
        self.avg_combobox.current(2) # По умолчанию '7'
        self.avg_combobox.pack(side="left", padx=5)

        # Блок "Прогноз на"
        period_frame = ttk.Frame(model_panel)
        period_frame.pack(fill="x", pady=(5, 15))
        ttk.Label(period_frame, text="Прогноз на:").pack(side="left")
        self.period_combobox = ttk.Combobox(period_frame, state='readonly', values=["Месяц (30 дней)", "Квартал (90 дней)", "Полгода (180 дней)"], width=25)
        self.period_combobox.current(0)
        self.period_combobox.pack(side="left")

        self.accordion.add_panel("Краткосрочный прогноз", model_panel)

        # Панель 3: Настройки симуляции
        sim_panel = ttk.Frame(self.accordion.content_frame)

        sim_settings_frame = ttk.Frame(sim_panel)
        sim_settings_frame.pack(fill="x", padx=10, pady=(5, 25))

        ttk.Label(sim_settings_frame, text="Кол-во итераций:\n(в симуляции)", justify="left").pack(
         side="left", anchor="nw", padx=(0, 10))
         
        self.iterations_entry = ttk.Entry(sim_settings_frame, width=25)
        self.iterations_entry.insert(0, "10000")
        self.iterations_entry.pack(side="left", anchor="w")

        # ДОБАВЛЯЕМ ПАНЕЛЬ СРАЗУ ПОСЛЕ ЕЁ СОЗДАНИЯ
        self.accordion.add_panel("Настройки симуляции", sim_panel)

        # --- ДОБАВЛЯЕМ НОВЫЕ ПАНЕЛИ С ЗАГЛУШКАМИ ---

        # Панель 4: Настройка графика - НОВАЯ ВЕРСТКА
        panel4 = ttk.Frame(self.accordion.content_frame)

        # Переносим Шаг (дни)
        step_days_frame = ttk.Frame(panel4)
        step_days_frame.pack(fill="x", padx=10)
        ttk.Label(step_days_frame, text="Шаг (дни):").pack(side="left")
        self.step_entry = ttk.Entry(step_days_frame, width=8)
        self.step_entry.insert(0, "1") # Значение по умолчанию
        self.step_entry.pack(side="left", padx=(5, 25))

        # Добавляем Шаг (события)
        step_events_frame = ttk.Frame(panel4)
        step_events_frame.pack(fill="x", padx=10)
        ttk.Label(step_events_frame, text="Шаг (события):").pack(side="left")
        self.step_events_entry = ttk.Entry(step_events_frame, width=8)
        self.step_events_entry.insert(0, "1") # Значение по умолчанию
        self.step_events_entry.pack(side="left")

        # --- ИЗМЕНЕНИЕ: Создаем НОВЫЙ флажок для легенды прямо здесь ---
        # Создаем переменную для него
        self.show_legend_var = tk.BooleanVar(value=True) # По умолчанию легенда включена

        legend_checkbox = ttk.Checkbutton(
            panel4,
            text='Показать легенду',
            variable=self.show_legend_var, # Связываем с переменной
            command=self.redraw_graph # При клике будем перерисовывать график
        )
        legend_checkbox.pack(pady=(25, 15))

        panel4.update()

        self.accordion.add_panel("Настройка графика", panel4)

        # Панель 5: Заголовок 5
        panel5 = ttk.Frame(self.accordion.content_frame)
        tk.Label(panel5, text="Это заглушка для пятой панели. Здесь будет функционал.").pack(pady=20)
        self.accordion.add_panel("Заголовок 5", panel5)

        # Панель 6: Заголовок 6
        panel6 = ttk.Frame(self.accordion.content_frame)
        tk.Label(panel6, text="Это заглушка для шестой панели. Здесь будет функционал.").pack(pady=20)
        self.accordion.add_panel("Заголовок 6", panel6)


         
        # --- КНОПКА "ПОСТРОИТЬ ПРОГНОЗ" ---
        # Размещаем её под аккордеоном в той же колонке (column=1), но в новой строке (row=1)
        button_frame = ttk.Frame(top_block)
        button_frame.grid(row=1, column=1) 

        self.build_button = ttk.Button(button_frame, text="Построить прогноз", command=self.run_forecast)
        self.build_button.pack(pady=30)


        # --- НИЖНИЙ БЛОК (Результаты расчета) ---
        result_frame = ttk.LabelFrame(main_container, text="Результаты расчета", padding="5")
        result_frame.grid(row=1, column=0, sticky="ew")

        self.result_text = tk.Text(result_frame, height=2, wrap='word', state='disabled')
        scrollbar = ttk.Scrollbar(result_frame, command=self.result_text.yview)
        scrollbar.pack(side="right", fill="y")
        self.result_text.configure(yscrollcommand=scrollbar.set)
        self.result_text.pack(padx=10, pady=5, fill="x")
        
        self.window.after(100, self.load_projects)

    def update_project_data(self):
        """
        Обновляет данные проекта И список доступных проектов.
        """
        self._insert_into_result("--- Начало обновления данных проекта ---")
        
        # 1. Сначала обновляем список проектов в выпадающем меню
        success = self.load_projects()
        if not success:
            self._insert_into_result("ОШИБКА: Не удалось обновить список проектов.")
            return

        # 2. Проверяем, выбран ли проект из обновленного списка
        selected_project_name = self.project_combobox.get()
        if not selected_project_name:
            self._insert_into_result("ОШИБКА: Выберите проект из списка.")
            return

        # 3. Если проект выбран, приступаем к обновлению его данных
        try:
            # Находим объект проекта по имени, чтобы получить его код (ID)
            projects = self.root_window.project_manager.list_projects()
            selected_project = next((p for p in projects if p.name == selected_project_name), None)
            
            if not selected_project:
                raise ValueError(f"Проект '{selected_project_name}' не найден в менеджере.")
                
            project_code = selected_project.id
            self._insert_into_result(f"Код проекта: {project_code}")

            # Создаем экземпляр класса для работы с базой данных проекта
            db = ProjectDatabase(project_code=project_code)
            conn = db.connect() # Устанавливаем соединение

            self._insert_into_result("Соединение с базой данных установлено.")

            # --- ЗДЕСЬ НАЧИНАЕТСЯ ЛОГИКА ОБНОВЛЕНИЯ ---
            cursor = conn.cursor()
            
            # ПРИМЕР: Проверяем структуру таблицы
            cursor.execute(f"PRAGMA table_info({TABLE_PROJECT_DATA})") 
            columns = [col[1] for col in cursor.fetchall()]
            self._insert_into_result(f"Текущие колонки в таблице: {columns}")

            # --- КОНЕЦ ЛОГИКИ ОБНОВЛЕНИЯ ---

            self._insert_into_result("Данные успешно обновлены!")

        # Блок 'except' должен быть на том же уровне отступа, что и 'try'
        except Exception as e:
            # В случае любой ошибки выводим её текст
            error_msg = f"Ошибка при обновлении данных: {e}"
            print(error_msg)
            self._insert_into_result("ERROR: " + error_msg)
        finally:
            # Убедимся, что соединение закрыто, даже если произошла ошибка
            if 'db' in locals():
                db.close()

    def _insert_into_result(self, text):
        """Вставляет текст в поле результатов."""
        self.result_text.config(state='normal')
        self.result_text.insert(tk.END, text + "\n") # Добавляем \n для красоты вывода
        self.result_text.config(state='disabled')
        self.result_text.see(tk.END) # Автоматически прокручивает вниз

    def _clear_result_text(self):
        """Очищает поле результатов перед новым расчетом."""
        self.result_text.config(state='normal')
        self.result_text.delete(1.0, tk.END)
        self.result_text.config(state='disabled')
  
    def load_projects(self):
        """
        Загружает список проектов и устанавливает дату начала выбранного проекта.
        """
        try:
            # 1. Получаем список всех проектов из главного окна
            projects = self.root_window.project_manager.list_projects()
            if not projects:
                print("Нет доступных проектов.")
                self.project_combobox['values'] = [] # Очищаем список на всякий случай
                return False

            # 2. Создаем списки имен и кодов проектов
            project_names = [p.name for p in projects]
            project_ids = [p.id for p in projects]

            # 3. Заполняем выпадающий список именами
            self.project_combobox['values'] = project_names

            # 4. Проверяем, есть ли уже выбранный проект
            selected_index = self.project_combobox.current()
            if selected_index != -1 and project_ids:
                # Если проект выбран, получаем его код (ID)
                selected_project_id = project_ids[selected_index]

                # --- НАЧАЛО БЛОКА ПОЛУЧЕНИЯ ДАТЫ ИЗ БАЗЫ ДАННЫХ ---
                try:
                    db = ProjectDatabase(project_code=selected_project_id)
                    conn = db.connect()
                    cursor = conn.cursor()

                    # Запрашиваем дату создания из таблицы project_data
                    cursor.execute(
                        "SELECT created_at FROM project_data WHERE project_code = ? LIMIT 1",
                        (selected_project_id,)
                    )
                    row = cursor.fetchone()

                    # Если дата найдена, сохраняем её в атрибут класса
                    if row and row[0]:
                        self.project_creation_date = row[0] # Дата в формате 'YYYY-MM-DD'
                        print(f"[DEBUG] Дата создания проекта: {self.project_creation_date}")
                    else:
                        self.project_creation_date = None
                        print("[DEBUG] Дата создания не найдена в БД.")

                except Exception as e:
                    print(f"[DEBUG] Ошибка при получении даты: {e}")
                    self.project_creation_date = None
                finally:
                    # Важно: всегда закрываем соединение с БД
                    if 'db' in locals():
                        db.close()
                # --- КОНЕЦ БЛОКА ПОЛУЧЕНИЯ ДАТЫ ---


                # --- НАЧАЛО БЛОКА УСТАНОВКИ ДАТЫ В КАЛЕНДАРЬ ---
                # Проверяем, удалось ли нам получить дату из базы
                if self.project_creation_date:
                    # Если дата есть, устанавливаем её в виджет календаря
                    self.start_date_picker.set_date(self.project_creation_date)
                    print(f"[DEBUG] Дата установлена в календаре: {self.project_creation_date}")
                # --- КОНЕЦ БЛОКА УСТАНОВКИ ДАТЫ В КАЛЕНДАРЬ ---

            return True

        except AttributeError as e:
            print(f"Ошибка: Не найден project_manager в главном окне. {e}")
            return False

    def run_forecast(self):
        """Запускает процесс прогнозирования и отрисовывает результаты."""
        self._clear_result_text()

        try:
            # --- ВАЛИДАЦИЯ ВХОДНЫХ ДАННЫХ ---
            # Считываем и проверяем числовые параметры
            iterations = int(self.iterations_entry.get())
            lambda_val = float(self.lambda_entry.get())
            step_days = int(self.step_entry.get())

            # Получаем период из выпадающего списка
            selected_period = self.period_combobox.get()
            days_forward_dict = {"Месяц": 30, "Квартал": 90, "Полгода": 180}
            period_key = next((k for k in days_forward_dict if k in selected_period), None)
            days_forward = days_forward_dict.get(period_key, 30)

            # Считываем и проверяем границы (Smin и Smax)
            s_min_str = self.s_min_entry.get().replace(',', '.')
            s_max_str = self.s_max_entry.get().replace(',', '.')

            s_min = float(s_min_str)
            s_max = float(s_max_str)

            if not (0 <= s_min <= 100) or not (0 <= s_max <= 100):
                raise ValueError("Границы (Smin и Smax) должны быть в диапазоне от 0 до 100.")

            if s_min >= s_max:
                raise ValueError("Нижняя граница (Smin) должна быть строго меньше верхней (Smax).")

            # Переводим проценты в доли для функции прогноза
            p1 = s_min / 100.0
            p2 = s_max / 100.0

            # Проверяем, что все основные параметры положительны
            if iterations <= 0 or lambda_val <= 0 or days_forward <= 0 or step_days <= 0:
                raise ValueError("Все параметры (итерации, интенсивность, период, шаг) должны быть больше нуля.")

            # --- ГЕНЕРАЦИЯ ТРАЕКТОРИИ И СИМУЛЯЦИЯ ---
            from datetime import datetime, timedelta
            import numpy as np

            # Пул возможных значений для одного события
            value_pool_positive = [1, 2, 3, 4, 5]
            
            trajectory_daily = []  # История по дням для точного поиска даты
            trajectory_for_plot = [] # История с учетом шага для отрисовки на графике
            
            current_value = 0
            start_date = datetime.now().date()
            
            for day_offset in range(days_forward + 1):
                # Генерируем события за день и суммируем их влияние
                num_events = np.random.poisson(lam=lambda_val)
                daily_sum = sum(np.random.choice(value_pool_positive) for _ in range(num_events))
                current_value += daily_sum
                
                trajectory_daily.append(current_value) # Сохраняем каждый день

                # Добавляем точку на график согласно шагу отображения
                if day_offset % step_days == 0 or day_offset == days_forward:
                    trajectory_for_plot.append(current_value)

                # Отладочный вывод в консоль (можно убрать потом)
                current_date = start_date + timedelta(days=day_offset)
                print(f"{current_date}: Событий: {num_events:2d}. Итог дня: {daily_sum:6.2f}. Накоплено: {current_value:6.2f}")

            if not trajectory_for_plot:
                raise RuntimeError("Не удалось сгенерировать данные для прогноза.")


            # --- РАСЧЕТ ГРАНИЦ ПРОГНОЗА ---
            # Здесь мы используем динамические p1 и p2 вместо фиксированных значений!
            sim_result = monte_carlo_forecast(
                pool_values=trajectory_daily,
                num_simulations=iterations,
                p1=p1,
                p2=p2
            )
            
            final_lower = sim_result["lower_bound"]
            final_upper = sim_result["upper_bound"]
            final_median = sim_result["median"]

            # --- ИЩЕМ ДАТУ ПЕРЕСЕЧЕНИЯ С НИЖНЕЙ ГРАНИЦЕЙ ---
            date_of_intersection = None
            for idx, val in enumerate(trajectory_daily):
                if val >= final_lower:
                    date_of_intersection = start_date + timedelta(days=idx)
                    break

            # --- ФОРМИРУЕМ ТЕКСТ ДЛЯ ВЫВОДА ---
            output_text = (
                f"ПРОГНОЗ ДОСТИЖЕНИЯ ЦЕЛИ:\\n"
                f" Цель (Нижняя граница): {final_lower:.2f}\\n\\n"
                f" Прогнозируемый диапазон значений:\\n"
                f" Минимум: {final_lower:.2f}\\n"
                f" Максимум: {final_upper:.2f}\\n\\n"
            )
            
            if date_of_intersection:
                output_text += f"Достижение минимума прогнозируется: {date_of_intersection.strftime('%d.%m.%Y')}"
            else:
                output_text += "Достижение минимума в заданный период не прогнозируется."

            self._insert_into_result(output_text)

            # --- ПОДГОТОВКА ДАННЫХ ДЛЯ ГРАФИКА ---
            result_data = {
                "lower_bound": final_lower,
                "upper_bound": final_upper,
                "median": final_median,
                "base_level": trajectory_for_plot[0],
                "delta_history": trajectory_for_plot,
                "intersections": {
                    "date": date_of_intersection,
                    "value": final_lower
                }
            }
            
            self.update_graph(result_data)

        except ValueError as e:
            error_msg = f"Ошибка ввода: {e}. Проверьте все поля."
            print(error_msg)
            self._insert_into_result("ОШИБКА: " + str(e))
        except Exception as e:
            error_msg = f"Произошла непредвиденная ошибка: {e}"
            print(error_msg)
            self._insert_into_result("НЕПРЕДВИДЕННАЯ ОШИБКА: " + str(e))

    def toggle_manual_mode(self):
        """Включает/выключает режимы ручного и автоматического ввода."""
        if self.manual_input_var.get():
            # Ручной режим ВКЛЮЧЕН: поле ввода активно, список заблокирован
            self.lambda_entry.config(state='normal')
            self.avg_combobox.config(state='disabled')
        else:
            # Ручной режим ВЫКЛЮЧЕН: поле ввода заблокировано, список активен
            self.lambda_entry.config(state='disabled')
            self.avg_combobox.config(state='readonly')

    def redraw_graph(self):
        """Перерисовывает график, сохраняя текущие настройки."""
        # Этот метод будет вызываться, когда вы кликаете по флажку
        # Он просто заново запускает процесс прогнозирования
        self.run_forecast()
 
    def get_project_history(self):
        """
        Получает историю проекта из БД.
        Этот метод может требовать доработки под вашу структуру базы данных.
        """
        print("--- Режим работы с реальной базой данных ---")
        
        selected_index = self.project_combobox.current()
        if selected_index == -1:
            print("Ошибка: Не выбран проект.")
            return None

        projects = self.root_window.project_manager.list_projects()
        try:
            selected_project_id = projects[selected_index].id
        except IndexError:
            print("Ошибка: Проект не найден в списке.")
            return None

        try:
            # --- ИСПРАВЛЕННЫЙ БЛОК ---
            # Весь код внутри этого 'try' должен иметь одинаковый отступ
            conn = sqlite3.connect(f"project_dbs/{selected_project_id}.db")
            cursor = conn.cursor()
            cursor.execute("""
                SELECT event_sign FROM project_data WHERE project_code = ?
            """, (selected_project_id,))
            rows = cursor.fetchall()
            conn.close()
            
            delta_history = [row[0] for row in rows if row[0] is not None]
            print(f"Динамика из БД: {delta_history}")
            return {"base_level": 0, "delta_history": delta_history}
            # --- КОНЕЦ ИСПРАВЛЕННОГО БЛОКА ---
            
        except Exception as e:
            # Блок 'except' находится на том же уровне, что и 'try'
            print(f"Ошибка при работе с базой данных: {e}")
            return None

    def update_graph(self, result_data):
        """
        Обновляет график на основе переданных данных.
        """
        lower_bound = result_data['lower_bound']
        median = result_data['median']
        upper_bound = result_data['upper_bound']
        base_level = result_data['base_level']
        trajectory = result_data['delta_history']
        
        # Очищаем график
        self.ax.clear()
        
        # Очищаем график
        self.ax.clear()

        # --- ИЗМЕНЕНИЕ ЛОГИКИ ЛЕГЕНДЫ ---
        # Рисуем горизонтальные линии границ и медианы
        # Добавляем label='...' к каждому элементу, который мы хотим видеть в легенде
        if lower_bound is not None:
            self.ax.axhline(y=lower_bound, color='red', linestyle='--', label='Нижняя граница')
        if median is not None:
            self.ax.axhline(y=median , color='green', linestyle='-', label='Медиана')
        if upper_bound is not None:
            self.ax.axhline(y=upper_bound , color='blue', linestyle='--', label='Верхняя граница')

        # Рисуем траекторию (линию графика), если есть данные
        if trajectory and len(trajectory) > 1:
            from datetime import datetime, timedelta
            start_date = datetime.now().date()
            step_days = int(self.step_entry.get())
            dates_for_plot = []
            for i in range(len(trajectory)):
                day_offset = i * step_days
                plot_date = start_date + timedelta(days=day_offset)
                dates_for_plot.append(plot_date)

            # Сама линия траектории
            self.ax.plot(dates_for_plot, trajectory,
                        color='orange', marker='o',
                        linewidth=2, markersize=6,
                        label='Смоделированная динамика')

        # --- ИЗМЕНЕНИЕ: Показываем легенду только если флажок включен ---
        # Проверяем состояние нашей переменной self.show_legend_var
    # Определяем, нужно ли показывать легенду
        show_legend = False # По умолчанию скрываем

        if self.show_legend_var: # Проверяем, что переменная существует
            # Если это наша собственная переменная (из Шага 1)
            if isinstance(self.show_legend_var, tk.BooleanVar):
                show_legend = self.show_legend_var.get()
            # Если это переменная от внешнего виджета
            else:
                show_legend = self.legend_checkbox_widget.instate(['selected'])

        if show_legend:
            self.ax.legend(loc='upper left')

        # Настраиваем внешний вид графика (всегда)
        self.ax.set_ylabel('Накопленное значение')
        self.ax.set_xlabel('Дата')
        self.ax.grid(True, linestyle='--', alpha=0.7)

        # Перерисовываем canvas (это очень важно!)
        self.canvas.draw()