#!/bin/bash # Скрипт для добавления функционала сохранения/загрузки данных в JSON # Запускать из корневой директории проекта set -e echo "📁 Добавление функционала сохранения/загрузки данных в JSON..." echo "================================================================" # Создаём директорию для моделей, если её нет mkdir -p calculations/models # 1. СОЗДАЁМ МОДЕЛЬ ДАННЫХ ДЛЯ СОХРАНЕНИЯ cat > calculations/models/project_data.py << 'EOF' """ Модель данных проекта для сохранения/загрузки в JSON """ from typing import List, Dict, Optional from dataclasses import dataclass, asdict from datetime import datetime import json @dataclass class ReagentData: """Данные реагента""" name: str percentage: float unit: str conversion_factor: float dilution_factor: float def to_dict(self) -> Dict: return asdict(self) @classmethod def from_dict(cls, data: Dict) -> 'ReagentData': return cls( name=data['name'], percentage=data['percentage'], unit=data['unit'], conversion_factor=data.get('conversion_factor', 1.0), dilution_factor=data.get('dilution_factor', 1.0) ) @dataclass class FactorData: """Данные фактора эксперимента""" name: str center: float low: float high: float step: float step_type: str unit: str percentage: Optional[float] = None dilution_factor: Optional[float] = None def to_dict(self) -> Dict: d = asdict(self) # Удаляем None значения return {k: v for k, v in d.items() if v is not None} @classmethod def from_dict(cls, data: Dict) -> 'FactorData': return cls( name=data['name'], center=data['center'], low=data['low'], high=data['high'], step=data.get('step', 0), step_type=data.get('step_type', 'абс'), unit=data.get('unit', ''), percentage=data.get('percentage'), dilution_factor=data.get('dilution_factor') ) @dataclass class ResponseData: """Данные отклика эксперимента""" name: str unit: str def to_dict(self) -> Dict: return asdict(self) @classmethod def from_dict(cls, data: Dict) -> 'ResponseData': return cls( name=data['name'], unit=data.get('unit', '') ) @dataclass class ExperimentResultsData: """Результаты эксперимента""" design: List[Dict] # План эксперимента results: List[List[float]] # Результаты измерений responses: List[ResponseData] # Отклики def to_dict(self) -> Dict: return { 'design': self.design, 'results': self.results, 'responses': [r.to_dict() for r in self.responses] } @classmethod def from_dict(cls, data: Dict) -> 'ExperimentResultsData': return cls( design=data['design'], results=data['results'], responses=[ResponseData.from_dict(r) for r in data['responses']] ) @dataclass class ProjectData: """Полные данные проекта""" # Информация о проекте project_name: str created_at: str modified_at: str version: str = "1.0" # Данные калькулятора сред medium_total_volume: float = 1000.0 medium_volume_unit: str = "мл" medium_solvent: str = "Вода" medium_reagents: List[ReagentData] = None # Данные эксперимента experiment_factors: List[FactorData] = None experiment_responses: List[ResponseData] = None experiment_center_points: int = 3 experiment_randomize: bool = True experiment_results: Optional[ExperimentResultsData] = None def __post_init__(self): if self.medium_reagents is None: self.medium_reagents = [] if self.experiment_factors is None: self.experiment_factors = [] if self.experiment_responses is None: self.experiment_responses = [] def to_dict(self) -> Dict: """Конвертирует в словарь для JSON""" return { 'project_info': { 'name': self.project_name, 'created_at': self.created_at, 'modified_at': self.modified_at, 'version': self.version }, 'medium_calculator': { 'total_volume': self.medium_total_volume, 'volume_unit': self.medium_volume_unit, 'solvent': self.medium_solvent, 'reagents': [r.to_dict() for r in self.medium_reagents] }, 'experiment': { 'factors': [f.to_dict() for f in self.experiment_factors], 'responses': [r.to_dict() for r in self.experiment_responses], 'center_points': self.experiment_center_points, 'randomize': self.experiment_randomize, 'results': self.experiment_results.to_dict() if self.experiment_results else None } } @classmethod def from_dict(cls, data: Dict) -> 'ProjectData': """Создаёт объект из словаря""" project_info = data.get('project_info', {}) medium = data.get('medium_calculator', {}) experiment = data.get('experiment', {}) # Создаём объект obj = cls( project_name=project_info.get('name', 'Новый проект'), created_at=project_info.get('created_at', datetime.now().isoformat()), modified_at=project_info.get('modified_at', datetime.now().isoformat()), version=project_info.get('version', '1.0'), medium_total_volume=medium.get('total_volume', 1000.0), medium_volume_unit=medium.get('volume_unit', 'мл'), medium_solvent=medium.get('solvent', 'Вода'), medium_reagents=[ReagentData.from_dict(r) for r in medium.get('reagents', [])], experiment_factors=[FactorData.from_dict(f) for f in experiment.get('factors', [])], experiment_responses=[ResponseData.from_dict(r) for r in experiment.get('responses', [])], experiment_center_points=experiment.get('center_points', 3), experiment_randomize=experiment.get('randomize', True), experiment_results=ExperimentResultsData.from_dict(experiment['results']) if experiment.get('results') else None ) return obj def save_to_file(self, filename: str): """Сохраняет проект в JSON файл""" self.modified_at = datetime.now().isoformat() with open(filename, 'w', encoding='utf-8') as f: json.dump(self.to_dict(), f, ensure_ascii=False, indent=2) @classmethod def load_from_file(cls, filename: str) -> 'ProjectData': """Загружает проект из JSON файла""" with open(filename, 'r', encoding='utf-8') as f: data = json.load(f) return cls.from_dict(data) def create_new_project(name: str = "Новый проект") -> ProjectData: """Создаёт новый проект с текущей датой""" now = datetime.now().isoformat() return ProjectData( project_name=name, created_at=now, modified_at=now ) EOF echo "✅ Создан calculations/models/project_data.py" # 2. ДОБАВЛЯЕМ ФУНКЦИИ В GUI ДЛЯ РАБОТЫ С JSON # Создаём патч для gui.py cat > gui_patch.py << 'EOF' # ДОБАВИТЬ В gui.py ПОСЛЕ ИМПОРТОВ: from calculations.models.project_data import ( ProjectData, ReagentData, FactorData, ResponseData, ExperimentResultsData, create_new_project ) # ДОБАВИТЬ В КЛАСС MainWindow НОВЫЕ МЕТОДЫ: def save_project_to_json(self): """Сохраняет весь проект в JSON файл""" filename, _ = QFileDialog.getSaveFileName( self, "Сохранить проект", "", "JSON Files (*.json);;All Files (*)" ) if not filename: return if not filename.endswith('.json'): filename += '.json' try: # Собираем данные из GUI project = self._collect_current_data() project.save_to_file(filename) QMessageBox.information(self, "Успех", f"Проект сохранён в {filename}") except Exception as e: QMessageBox.critical(self, "Ошибка", f"Не удалось сохранить проект: {str(e)}") def load_project_from_json(self): """Загружает проект из JSON файла""" filename, _ = QFileDialog.getOpenFileName( self, "Загрузить проект", "", "JSON Files (*.json);;All Files (*)" ) if not filename: return try: project = ProjectData.load_from_file(filename) self._apply_project_data(project) QMessageBox.information(self, "Успех", f"Проект загружен из {filename}") except FileNotFoundError: QMessageBox.critical(self, "Ошибка", f"Файл не найден: {filename}") except Exception as e: QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить проект: {str(e)}") def _collect_current_data(self) -> ProjectData: """Собирает текущие данные из всех виджетов""" # Данные калькулятора сред reagents = [] for row in range(self.reagents_table.rowCount()): name_item = self.reagents_table.item(row, 0) percent_item = self.reagents_table.item(row, 1) unit_widget = self.reagents_table.cellWidget(row, 2) coeff_item = self.reagents_table.item(row, 3) dilution_item = self.reagents_table.item(row, 4) if name_item and percent_item: try: reagent = ReagentData( name=name_item.text(), percentage=float(percent_item.text()) if percent_item.text() else 0, unit=unit_widget.currentText() if unit_widget else "мг", conversion_factor=float(coeff_item.text()) if coeff_item and coeff_item.text() else 1.0, dilution_factor=float(dilution_item.text()) if dilution_item and dilution_item.text() else 1.0 ) reagents.append(reagent) except ValueError: pass # Данные факторов эксперимента factors = [] for row in range(self.factors_table.rowCount()): name_item = self.factors_table.item(row, 0) center_item = self.factors_table.item(row, 3) low_item = self.factors_table.item(row, 7) high_item = self.factors_table.item(row, 6) step_item = self.factors_table.item(row, 4) unit_item = self.factors_table.item(row, 8) step_combo = self.factors_table.cellWidget(row, 5) percent_item = self.factors_table.item(row, 1) dilution_item = self.factors_table.item(row, 2) if name_item and center_item: try: factor = FactorData( name=name_item.text(), center=float(center_item.text()) if center_item.text() else 0, low=float(low_item.text()) if low_item and low_item.text() else 0, high=float(high_item.text()) if high_item and high_item.text() else 0, step=float(step_item.text()) if step_item and step_item.text() else 0, step_type=step_combo.currentText() if step_combo else "абс", unit=unit_item.text() if unit_item else "", percentage=float(percent_item.text()) if percent_item and percent_item.text() else None, dilution_factor=float(dilution_item.text()) if dilution_item and dilution_item.text() else None ) factors.append(factor) except ValueError: pass # Данные результатов (если есть) results_data = None if hasattr(self, 'generated_design') and self.generated_design: results = [] for i in range(self.results_table.rowCount()): item = self.results_table.item(i, 1) if item and item.text(): try: results.append([float(item.text())]) except ValueError: pass if results: responses = [ResponseData(name="Отклик", unit="")] results_data = ExperimentResultsData( design=self.generated_design, results=results, responses=responses ) # Создаём проект project = create_new_project("Мой проект") project.medium_total_volume = self.total_volume_spin.value() project.medium_volume_unit = self.volume_unit_combo.currentText() project.medium_solvent = self.solvent_input.text() project.medium_reagents = reagents project.experiment_factors = factors project.experiment_center_points = self.center_points_spin.value() project.experiment_randomize = self.randomize_check.isChecked() project.experiment_results = results_data return project def _apply_project_data(self, project: ProjectData): """Применяет загруженные данные к GUI""" # Применяем данные калькулятора сред self.total_volume_spin.setValue(project.medium_total_volume) index = self.volume_unit_combo.findText(project.medium_volume_unit) if index >= 0: self.volume_unit_combo.setCurrentIndex(index) self.solvent_input.setText(project.medium_solvent) # Очищаем и заполняем таблицу реагентов self.reagents_table.setRowCount(0) for reagent in project.medium_reagents: row = self.reagents_table.rowCount() self.reagents_table.insertRow(row) self.reagents_table.setItem(row, 0, QTableWidgetItem(reagent.name)) self.reagents_table.setItem(row, 1, QTableWidgetItem(str(reagent.percentage))) unit_combo = QComboBox() unit_combo.addItems(["мг", "г", "кг", "мкг", "нг", "мл", "мкл", "л", "нл"]) unit_combo.setCurrentText(reagent.unit) self.reagents_table.setCellWidget(row, 2, unit_combo) self.reagents_table.setItem(row, 3, QTableWidgetItem(str(reagent.conversion_factor))) self.reagents_table.setItem(row, 4, QTableWidgetItem(str(reagent.dilution_factor))) self.reagents_table.setItem(row, 5, QTableWidgetItem("")) # Применяем данные факторов эксперимента self.factors_table.setRowCount(0) for factor in project.experiment_factors: row = self.factors_table.rowCount() self.factors_table.insertRow(row) self.factors_table.setItem(row, 0, QTableWidgetItem(factor.name)) if factor.percentage is not None: self.factors_table.setItem(row, 1, QTableWidgetItem(str(factor.percentage))) else: self.factors_table.setItem(row, 1, QTableWidgetItem("0")) if factor.dilution_factor is not None: self.factors_table.setItem(row, 2, QTableWidgetItem(str(factor.dilution_factor))) else: self.factors_table.setItem(row, 2, QTableWidgetItem("1")) self.factors_table.setItem(row, 3, QTableWidgetItem(str(factor.center))) self.factors_table.setItem(row, 4, QTableWidgetItem(str(factor.step))) step_combo = QComboBox() step_combo.addItems(["абс", "%"]) step_combo.setCurrentText(factor.step_type) self.factors_table.setCellWidget(row, 5, step_combo) self.factors_table.setItem(row, 6, QTableWidgetItem(str(factor.high))) self.factors_table.setItem(row, 7, QTableWidgetItem(str(factor.low))) self.factors_table.setItem(row, 8, QTableWidgetItem(factor.unit)) # Применяем настройки эксперимента self.center_points_spin.setValue(project.experiment_center_points) self.randomize_check.setChecked(project.experiment_randomize) # Если есть результаты, загружаем их if project.experiment_results and project.experiment_results.design: self.generated_design = project.experiment_results.design self._display_design_matrix() # Загружаем результаты в таблицу if project.experiment_results.results: for i, row_results in enumerate(project.experiment_results.results): if i < self.results_table.rowCount() and row_results: self.results_table.setItem(i, 1, QTableWidgetItem(str(row_results[0]))) # Переключаемся на вкладку с факторами self.tab_widget.setCurrentIndex(1) def _display_design_matrix(self): """Отображает матрицу планирования (добавить в _generate_design или отдельно)""" if not self.generated_design: return n_exp = len(self.generated_design) n_factors = len(self._get_factors_from_table()) self.design_matrix.setRowCount(n_exp) self.design_matrix.setColumnCount(n_factors + 2) headers = ["№"] + [f['name'] for f in self._get_factors_from_table()] + ["Тип"] self.design_matrix.setHorizontalHeaderLabels(headers) for exp_idx, exp in enumerate(self.generated_design): self.design_matrix.setItem(exp_idx, 0, QTableWidgetItem(str(exp_idx + 1))) for f_idx in range(n_factors): key = f"Фактор_{f_idx + 1}" if key not in exp: continue value = exp[key]['natural'] unit = self._get_factors_from_table()[f_idx]['unit'] display = self._format_number(value) if unit: display += f" {unit}" item = QTableWidgetItem(display) if exp.get('is_center', False): item.setBackground(QColor(255, 255, 200)) self.design_matrix.setItem(exp_idx, f_idx + 1, item) if exp.get('is_center', False): type_item = QTableWidgetItem(f"Центр #{exp['center_num']}") type_item.setBackground(QColor(255, 255, 200)) else: type_item = QTableWidgetItem("Факторная") self.design_matrix.setItem(exp_idx, n_factors + 1, type_item) self.design_matrix.resizeColumnsToContents() self.export_csv_btn.setEnabled(True) self._setup_results_table(n_exp) # ДОБАВИТЬ В МЕТОД _setup_ui ПОСЛЕ СОЗДАНИЯ ВКЛАДОК: # Добавляем кнопки сохранения/загрузки в тулбар toolbar = self.addToolBar("Файл") toolbar.setMovable(False) save_action = toolbar.addAction("💾 Сохранить проект") save_action.triggered.connect(self.save_project_to_json) load_action = toolbar.addAction("📂 Загрузить проект") load_action.triggered.connect(self.load_project_from_json) toolbar.addSeparator() # Сохраняем ссылку на tab_widget для переключения вкладок self.tab_widget = tabs EOF echo "✅ Создан патч gui_patch.py" # 3. ПРИМЕНЯЕМ ПАТЧ К GUI.PY # Создаём резервную копию cp gui.py gui.py.backup echo "✅ Создана резервная копия gui.py.backup" # Вставляем импорты после существующих импортов sed -i '/from calculations.doe import/a\ from calculations.models.project_data import (\ ProjectData, ReagentData, FactorData, \ ResponseData, ExperimentResultsData, create_new_project\ )' gui.py # Находим строку с self._setup_ui() и добавляем после неё toolbar sed -i '/self._setup_ui()/a\ self._add_file_toolbar()' gui.py # Добавляем новый метод _add_file_toolbar после _setup_ui cat >> gui.py.new_method << 'EOF' def _add_file_toolbar(self): """Добавляет панель инструментов с кнопками сохранения/загрузки""" toolbar = self.addToolBar("Файл") toolbar.setMovable(False) save_action = toolbar.addAction("💾 Сохранить проект") save_action.triggered.connect(self.save_project_to_json) load_action = toolbar.addAction("📂 Загрузить проект") load_action.triggered.connect(self.load_project_from_json) toolbar.addSeparator() EOF # Вставляем новый метод после _setup_ui sed -i '/def _setup_ui(self):/,/def _get_stylesheet(self):/ { /def _get_stylesheet(self):/ { r gui.py.new_method } }' gui.py rm gui.py.new_method # Добавляем методы сохранения/загрузки в конец файла перед if __name__ cat >> gui.py << 'EOF' # ========== МЕТОДЫ РАБОТЫ С JSON ========== def save_project_to_json(self): """Сохраняет весь проект в JSON файл""" from PyQt5.QtWidgets import QFileDialog, QMessageBox filename, _ = QFileDialog.getSaveFileName( self, "Сохранить проект", "", "JSON Files (*.json);;All Files (*)" ) if not filename: return if not filename.endswith('.json'): filename += '.json' try: project = self._collect_current_data() project.save_to_file(filename) QMessageBox.information(self, "Успех", f"Проект сохранён в {filename}") except Exception as e: QMessageBox.critical(self, "Ошибка", f"Не удалось сохранить проект: {str(e)}") def load_project_from_json(self): """Загружает проект из JSON файла""" from PyQt5.QtWidgets import QFileDialog, QMessageBox, QComboBox, QTableWidgetItem filename, _ = QFileDialog.getOpenFileName( self, "Загрузить проект", "", "JSON Files (*.json);;All Files (*)" ) if not filename: return try: project = ProjectData.load_from_file(filename) self._apply_project_data(project) QMessageBox.information(self, "Успех", f"Проект загружен из {filename}") except FileNotFoundError: QMessageBox.critical(self, "Ошибка", f"Файл не найден: {filename}") except Exception as e: QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить проект: {str(e)}") def _collect_current_data(self) -> ProjectData: """Собирает текущие данные из всех виджетов""" from PyQt5.QtWidgets import QComboBox # Данные калькулятора сред reagents = [] for row in range(self.reagents_table.rowCount()): name_item = self.reagents_table.item(row, 0) percent_item = self.reagents_table.item(row, 1) unit_widget = self.reagents_table.cellWidget(row, 2) coeff_item = self.reagents_table.item(row, 3) dilution_item = self.reagents_table.item(row, 4) if name_item and percent_item and percent_item.text(): try: reagent = ReagentData( name=name_item.text(), percentage=float(percent_item.text()), unit=unit_widget.currentText() if unit_widget else "мг", conversion_factor=float(coeff_item.text()) if coeff_item and coeff_item.text() else 1.0, dilution_factor=float(dilution_item.text()) if dilution_item and dilution_item.text() else 1.0 ) reagents.append(reagent) except ValueError: pass # Данные факторов эксперимента factors = [] for row in range(self.factors_table.rowCount()): name_item = self.factors_table.item(row, 0) center_item = self.factors_table.item(row, 3) low_item = self.factors_table.item(row, 7) high_item = self.factors_table.item(row, 6) step_item = self.factors_table.item(row, 4) unit_item = self.factors_table.item(row, 8) step_combo = self.factors_table.cellWidget(row, 5) percent_item = self.factors_table.item(row, 1) dilution_item = self.factors_table.item(row, 2) if name_item and center_item and center_item.text(): try: factor = FactorData( name=name_item.text(), center=float(center_item.text()), low=float(low_item.text()) if low_item and low_item.text() else 0, high=float(high_item.text()) if high_item and high_item.text() else 0, step=float(step_item.text()) if step_item and step_item.text() else 0, step_type=step_combo.currentText() if step_combo else "абс", unit=unit_item.text() if unit_item else "", percentage=float(percent_item.text()) if percent_item and percent_item.text() else None, dilution_factor=float(dilution_item.text()) if dilution_item and dilution_item.text() else None ) factors.append(factor) except ValueError: pass # Данные результатов (если есть) results_data = None if hasattr(self, 'generated_design') and self.generated_design: results = [] for i in range(self.results_table.rowCount()): item = self.results_table.item(i, 1) if item and item.text(): try: results.append([float(item.text())]) except ValueError: pass if results: responses = [ResponseData(name="Отклик", unit="")] results_data = ExperimentResultsData( design=self.generated_design, results=results, responses=responses ) # Создаём проект project = create_new_project("Мой проект") project.medium_total_volume = self.total_volume_spin.value() project.medium_volume_unit = self.volume_unit_combo.currentText() project.medium_solvent = self.solvent_input.text() project.medium_reagents = reagents project.experiment_factors = factors project.experiment_center_points = self.center_points_spin.value() project.experiment_randomize = self.randomize_check.isChecked() project.experiment_results = results_data return project def _apply_project_data(self, project: ProjectData): """Применяет загруженные данные к GUI""" from PyQt5.QtWidgets import QComboBox, QTableWidgetItem from PyQt5.QtCore import Qt # Применяем данные калькулятора сред self.total_volume_spin.setValue(project.medium_total_volume) index = self.volume_unit_combo.findText(project.medium_volume_unit) if index >= 0: self.volume_unit_combo.setCurrentIndex(index) self.solvent_input.setText(project.medium_solvent) # Очищаем и заполняем таблицу реагентов self.reagents_table.setRowCount(0) for reagent in project.medium_reagents: row = self.reagents_table.rowCount() self.reagents_table.insertRow(row) self.reagents_table.setItem(row, 0, QTableWidgetItem(reagent.name)) self.reagents_table.setItem(row, 1, QTableWidgetItem(str(reagent.percentage))) unit_combo = QComboBox() unit_combo.addItems(["мг", "г", "кг", "мкг", "нг", "мл", "мкл", "л", "нл"]) unit_combo.setCurrentText(reagent.unit) self.reagents_table.setCellWidget(row, 2, unit_combo) self.reagents_table.setItem(row, 3, QTableWidgetItem(str(reagent.conversion_factor))) self.reagents_table.setItem(row, 4, QTableWidgetItem(str(reagent.dilution_factor))) self.reagents_table.setItem(row, 5, QTableWidgetItem("")) # Применяем данные факторов эксперимента self.factors_table.setRowCount(0) for factor in project.experiment_factors: row = self.factors_table.rowCount() self.factors_table.insertRow(row) self.factors_table.setItem(row, 0, QTableWidgetItem(factor.name)) if factor.percentage is not None: self.factors_table.setItem(row, 1, QTableWidgetItem(str(factor.percentage))) else: self.factors_table.setItem(row, 1, QTableWidgetItem("0")) if factor.dilution_factor is not None: self.factors_table.setItem(row, 2, QTableWidgetItem(str(factor.dilution_factor))) else: self.factors_table.setItem(row, 2, QTableWidgetItem("1")) self.factors_table.setItem(row, 3, QTableWidgetItem(str(factor.center))) self.factors_table.setItem(row, 4, QTableWidgetItem(str(factor.step))) step_combo = QComboBox() step_combo.addItems(["абс", "%"]) step_combo.setCurrentText(factor.step_type) self.factors_table.setCellWidget(row, 5, step_combo) self.factors_table.setItem(row, 6, QTableWidgetItem(str(factor.high))) self.factors_table.setItem(row, 7, QTableWidgetItem(str(factor.low))) self.factors_table.setItem(row, 8, QTableWidgetItem(factor.unit)) # Применяем настройки эксперимента self.center_points_spin.setValue(project.experiment_center_points) self.randomize_check.setChecked(project.experiment_randomize) # Если есть результаты, загружаем их if project.experiment_results and project.experiment_results.design: self.generated_design = project.experiment_results.design # Обновляем отображение матрицы self._refresh_design_matrix() # Загружаем результаты в таблицу if project.experiment_results.results: for i, row_results in enumerate(project.experiment_results.results): if i < self.results_table.rowCount() and row_results: self.results_table.setItem(i, 1, QTableWidgetItem(str(row_results[0]))) # Переключаемся на вкладку с факторами if hasattr(self, 'tab_widget'): self.tab_widget.setCurrentIndex(1) def _refresh_design_matrix(self): """Обновляет отображение матрицы планирования""" if not self.generated_design: return factors = self._get_factors_from_table() n_exp = len(self.generated_design) n_factors = len(factors) self.design_matrix.setRowCount(n_exp) self.design_matrix.setColumnCount(n_factors + 2) headers = ["№"] + [f['name'] for f in factors] + ["Тип"] self.design_matrix.setHorizontalHeaderLabels(headers) for exp_idx, exp in enumerate(self.generated_design): self.design_matrix.setItem(exp_idx, 0, QTableWidgetItem(str(exp_idx + 1))) for f_idx in range(n_factors): key = f"Фактор_{f_idx + 1}" if key not in exp: continue value = exp[key]['natural'] unit = factors[f_idx]['unit'] display = self._format_number(value) if unit: display += f" {unit}" item = QTableWidgetItem(display) if exp.get('is_center', False): item.setBackground(QColor(255, 255, 200)) self.design_matrix.setItem(exp_idx, f_idx + 1, item) if exp.get('is_center', False): type_item = QTableWidgetItem(f"Центр #{exp['center_num']}") type_item.setBackground(QColor(255, 255, 200)) else: type_item = QTableWidgetItem("Факторная") self.design_matrix.setItem(exp_idx, n_factors + 1, type_item) self.design_matrix.resizeColumnsToContents() if hasattr(self, 'export_csv_btn'): self.export_csv_btn.setEnabled(True) n_factorial = 2 ** n_factors n_center = self.center_points_spin.value() self.design_info.setText( f"📊 Факторных точек: {n_factorial}, Центральных: {n_center}, Всего: {n_exp}" ) self._setup_results_table(n_exp) EOF echo "✅ Добавлены методы работы с JSON в gui.py" # 4. СОЗДАЁМ ПРИМЕР JSON ФАЙЛА cat > example_project.json << 'EOF' { "project_info": { "name": "Пример проекта", "created_at": "2026-05-14T16:00:00", "modified_at": "2026-05-14T16:00:00", "version": "1.0" }, "medium_calculator": { "total_volume": 1000, "volume_unit": "мл", "solvent": "Вода", "reagents": [ { "name": "Глюкоза", "percentage": 2.0, "unit": "г", "conversion_factor": 1.0, "dilution_factor": 1.0 }, { "name": "Пептон", "percentage": 1.0, "unit": "г", "conversion_factor": 1.0, "dilution_factor": 1.0 } ] }, "experiment": { "factors": [ { "name": "Глюкоза", "center": 20.0, "low": 18.0, "high": 22.0, "step": 2.0, "step_type": "абс", "unit": "г", "percentage": 2.0, "dilution_factor": 1.0 }, { "name": "Пептон", "center": 10.0, "low": 9.0, "high": 11.0, "step": 1.0, "step_type": "абс", "unit": "г", "percentage": 1.0, "dilution_factor": 1.0 } ], "responses": [ {"name": "Отклик", "unit": ""} ], "center_points": 3, "randomize": true, "results": null } } EOF echo "✅ Создан example_project.json" # 5. СОЗДАЁМ requirements.txt ЕСЛИ ЕГО НЕТ if [ ! -f requirements.txt ]; then cat > requirements.txt << 'EOF' PyQt5>=5.15.0 numpy>=1.21.0 EOF echo "✅ Создан requirements.txt" fi echo "" echo "================================================================" echo "✅ ГОТОВО! Функционал сохранения/загрузки JSON добавлен!" echo "================================================================" echo "" echo "Что было добавлено:" echo " 1. Модель данных в calculations/models/project_data.py" echo " 2. Кнопки 'Сохранить проект' и 'Загрузить проект' в toolbar" echo " 3. Методы сохранения/загрузки в gui.py" echo " 4. Пример JSON файла example_project.json" echo "" echo "Для запуска программы:" echo " python main.py" echo "" echo "Для восстановления оригинального gui.py (если нужно):" echo " cp gui.py.backup gui.py" echo "" echo "Чтобы применить изменения, перезапустите программу" echo "================================================================"