| # Гайд по интеграции рефакторинга | |
| ## 🚀 Быстрый старт | |
| После рефакторинга были созданы новые модули в папке `common/`. Ниже показано, как их использовать. | |
| ## 📋 Содержание | |
| 1. [Константы вместо магических чисел](#константы) | |
| 2. [Логирование](#логирование) | |
| 3. [Валидация данных](#валидация) | |
| 4. [Типизированные структуры](#структуры) | |
| 5. [Обработка ошибок](#ошибки) | |
| --- | |
| ## Константы | |
| ### Прежде всего обновите импорты | |
| ```python | |
| # app/gui_app.py | |
| from common import ( | |
| UIColors, | |
| UIDimensions, | |
| Messages, | |
| FontConfig, | |
| AudioFormats, | |
| get_logger | |
| ) | |
| logger = get_logger(__name__) | |
| ``` | |
| ### UI размеры | |
| ```python | |
| # ДО | |
| self.setGeometry(100, 100, 1200, 800) | |
| # ПОСЛЕ | |
| self.setGeometry( | |
| 100, 100, | |
| UIDimensions.MAIN_WINDOW_WIDTH, | |
| UIDimensions.MAIN_WINDOW_HEIGHT | |
| ) | |
| ``` | |
| ### UI цвета | |
| ```python | |
| # ДО | |
| self.start_btn.setStyleSheet(""" | |
| QPushButton { | |
| background-color: #4CAF50; | |
| color: white; | |
| } | |
| QPushButton:hover { | |
| background-color: #45a049; | |
| } | |
| """) | |
| # ПОСЛЕ | |
| self.start_btn.setStyleSheet(f""" | |
| QPushButton {{ | |
| background-color: {UIColors.PRIMARY_GREEN}; | |
| color: white; | |
| }} | |
| QPushButton:hover {{ | |
| background-color: {UIColors.HOVER_GREEN}; | |
| }} | |
| """) | |
| ``` | |
| ### Текстовые сообщения | |
| ```python | |
| # ДО | |
| if not self.audio_path: | |
| QMessageBox.warning( | |
| self, | |
| "Ошибка", | |
| "Пожалуйста, выберите аудиофайл!" | |
| ) | |
| return | |
| # ПОСЛЕ | |
| if not self.audio_path: | |
| QMessageBox.warning( | |
| self, | |
| Messages.WARNING_TITLE, | |
| Messages.ERROR_NO_AUDIO_FILE | |
| ) | |
| return | |
| ``` | |
| ### Диалоги выбора файлов | |
| ```python | |
| # ДО | |
| file_path, _ = QFileDialog.getOpenFileName( | |
| self, | |
| "Выберите аудиофайл", | |
| "", | |
| "Audio Files (*.wav *.mp3 *.m4a);;All Files (*)" | |
| ) | |
| # ПОСЛЕ | |
| file_path, _ = QFileDialog.getOpenFileName( | |
| self, | |
| "Выберите аудиофайл", | |
| "", | |
| AudioFormats.FILE_DIALOG_FILTER | |
| ) | |
| ``` | |
| --- | |
| ## Логирование | |
| ### Инициализация (в main.py или run_gui.py) | |
| ```python | |
| from common import configure_logging, get_logger | |
| if __name__ == "__main__": | |
| # Один раз в начале программы | |
| configure_logging() # Создаст папку logs/ и файл логов | |
| app = QApplication(sys.argv) | |
| window = MedicalTranscriptionApp() | |
| window.show() | |
| sys.exit(app.exec()) | |
| ``` | |
| ### Использование в модулях | |
| ```python | |
| # В каждом файле | |
| from common import get_logger | |
| logger = get_logger(__name__) | |
| def my_function(): | |
| logger.info("Starting operation") | |
| try: | |
| # ... | |
| logger.debug("Processing step 1") | |
| except Exception as e: | |
| logger.error(f"Error occurred: {e}", exc_info=True) | |
| ``` | |
| ### Удалите старый код логирования | |
| ```python | |
| # ДО (удалить) | |
| import logging | |
| logger = logging.getLogger(__name__) | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(levelname)s - %(message)s' | |
| ) | |
| # ПОСЛЕ (достаточно) | |
| from common import get_logger | |
| logger = get_logger(__name__) | |
| ``` | |
| --- | |
| ## Валидация | |
| ### Валидация аудиофайлов | |
| ```python | |
| from common import Validator, AudioFileException | |
| from pathlib import Path | |
| def start_transcription(self): | |
| try: | |
| # Валидирует файл, проверяет существование, формат и размер | |
| audio_file = Validator.validate_audio_file(self.audio_path) | |
| # audio_file является объектом Path | |
| except AudioFileException as e: | |
| QMessageBox.critical(self, "Ошибка аудиофайла", e.message) | |
| ``` | |
| ### Валидация пациента | |
| ```python | |
| from common import Validator, ValidationException | |
| def open_patient_dialog(self): | |
| dialog = PatientDataDialog(self) | |
| if dialog.exec() == QDialog.DialogCode.Accepted: | |
| try: | |
| data = dialog.get_data() | |
| # Валидация каждого поля | |
| patient_name = Validator.validate_patient_name(data["patient_name"]) | |
| patient_dob = Validator.validate_date(data["patient_dob"]) | |
| self.patient_data = data | |
| except ValidationException as e: | |
| QMessageBox.warning( | |
| self, | |
| f"Ошибка в поле {e.field}", | |
| e.message | |
| ) | |
| ``` | |
| ### Валидация текста | |
| ```python | |
| from common import Validator, ValidationException | |
| def correct_text(text): | |
| try: | |
| validated_text = Validator.validate_text(text, "transcription") | |
| # Дальше работаем с проверенным текстом | |
| except ValidationException as e: | |
| logger.error(f"Validation error: {e.message}") | |
| ``` | |
| --- | |
| ## Структуры данных | |
| ### Использование типизированных результатов | |
| ```python | |
| from common import PipelineResult, TranscriptionResult, PatientMetadata | |
| from datetime import datetime | |
| from pathlib import Path | |
| def process_pipeline(): | |
| # Создание структурированного результата | |
| result = PipelineResult( | |
| timestamp=datetime.now(), | |
| audio_file=Path("audio.wav"), | |
| patient_data=PatientMetadata( | |
| name="Иванов Иван Иванович", | |
| date_of_birth="01.01.1980", | |
| study_area="МРТ головы" | |
| ), | |
| transcription=TranscriptionResult( | |
| timestamp=datetime.now(), | |
| audio_file=Path("audio.wav"), | |
| original_text="исходный текст", | |
| corrected_text="исправленный текст", | |
| corrections_count=5 | |
| ), | |
| status="success" | |
| ) | |
| # IDE будет подсказывать все доступные поля! | |
| print(result.status) | |
| print(result.transcription.corrections_count) | |
| print(result.is_successful()) # Вспомогательный метод | |
| # Сериализация в JSON | |
| result_dict = result.to_dict() | |
| json.dump(result_dict, f) | |
| ``` | |
| ### Создание метаданных пациента | |
| ```python | |
| from common import PatientMetadata | |
| patient_data = PatientMetadata( | |
| name="Петров Петр Петрович", | |
| date_of_birth="15.03.1975", | |
| study_area="КТ грудной клетки", | |
| study_number="12345", | |
| study_date="16.01.2026", | |
| doctor_name="Сидоров С.С." | |
| ) | |
| # Проверка полноты данных | |
| if patient_data.is_complete(): | |
| print("Все необходимые данные заполнены") | |
| # Преобразование в словарь | |
| patient_dict = patient_data.to_dict() | |
| ``` | |
| --- | |
| ## Обработка ошибок | |
| ### Специфичные исключения | |
| ```python | |
| from common import ( | |
| AudioFileException, | |
| TranscriptionException, | |
| APIException, | |
| ValidationException, | |
| ConfigurationException | |
| ) | |
| def pipeline_process(): | |
| try: | |
| # ... | |
| pass | |
| except AudioFileException as e: | |
| # Обработка ошибок с аудио файлом | |
| logger.error(f"Audio file error: {e.message}") | |
| show_error_dialog(f"Ошибка аудиофайла: {e.file_path}") | |
| except APIException as e: | |
| # Обработка ошибок API | |
| logger.error(f"API error: {e.message} (code: {e.status_code})") | |
| show_error_dialog(f"Ошибка API: {e.status_code}") | |
| except ValidationException as e: | |
| # Обработка ошибок валидации | |
| logger.warning(f"Validation error in {e.field}: {e.message}") | |
| show_warning_dialog(f"Проверьте поле '{e.field}'") | |
| except ConfigurationException as e: | |
| # Обработка ошибок конфигурации | |
| logger.error(f"Config error: {e}") | |
| show_error_dialog("Неверная конфигурация") | |
| ``` | |
| ### Информативные ошибки с контекстом | |
| ```python | |
| # ДО | |
| except Exception as e: | |
| logger.error(f"Error: {e}") | |
| # Непонятно, что произошло | |
| # ПОСЛЕ | |
| except APIException as e: | |
| logger.error( | |
| f"API request failed for {e.endpoint} " | |
| f"with status {e.status_code}: {e.message}" | |
| ) | |
| # Точно известно, что произошло, где и почему | |
| ``` | |
| --- | |
| ## Шаблон для новых модулей | |
| При создании нового модуля используйте этот шаблон: | |
| ```python | |
| """ | |
| Описание модуля. | |
| Example: | |
| >>> from my_module import MyClass | |
| >>> obj = MyClass() | |
| >>> result = obj.my_method() | |
| """ | |
| from pathlib import Path | |
| from typing import Optional, Dict, Any | |
| from common import get_logger, Validator, ValidationException | |
| logger = get_logger(__name__) | |
| class MyClass: | |
| """Описание класса.""" | |
| def __init__(self, param: str) -> None: | |
| """ | |
| Initialize. | |
| Args: | |
| param: Parameter description | |
| Raises: | |
| ValueError: If param is invalid | |
| """ | |
| self.param = param | |
| logger.info(f"Initialized MyClass with param: {param}") | |
| def my_method(self, data: str) -> Dict[str, Any]: | |
| """ | |
| Do something. | |
| Args: | |
| data: Input data | |
| Returns: | |
| Result dictionary | |
| Raises: | |
| ValidationException: If data is invalid | |
| """ | |
| try: | |
| validated_data = Validator.validate_text(data) | |
| logger.debug(f"Processing {len(validated_data)} characters") | |
| result = {"status": "success", "data": validated_data} | |
| logger.info("Processing completed successfully") | |
| return result | |
| except ValidationException as e: | |
| logger.error(f"Validation failed: {e.message}") | |
| raise | |
| ``` | |
| --- | |
| ## Чек-лист для интеграции | |
| ### Phase 1: Основные импорты | |
| - [ ] `common/exceptions.py` создан ✅ | |
| - [ ] `common/constants.py` создан ✅ | |
| - [ ] `common/logger.py` создан ✅ | |
| - [ ] `common/validators.py` создан ✅ | |
| - [ ] `common/models.py` создан ✅ | |
| - [ ] `common/__init__.py` создан ✅ | |
| ### Phase 2: Обновление импортов в app/ | |
| - [ ] `app/gui_app.py` - добавить импорты common | |
| - [ ] `app/main.py` - вызвать `configure_logging()` | |
| - [ ] Заменить все магические числа на константы | |
| - [ ] Заменить все `Exception` на специфичные типы | |
| ### Phase 3: Обновление импортов в pipeline/ | |
| - [ ] `pipeline/medical_pipeline.py` - использовать новые структуры | |
| - [ ] `pipeline/pipeline_config.py` - использовать константы | |
| ### Phase 4: Обновление импортов в corrector/ | |
| - [ ] `corrector/llm_corrector.py` - улучшить типизацию ✅ | |
| - [ ] `corrector/openrouter_client.py` - использовать APISettings ✅ | |
| - [ ] `corrector/report_generator.py` - использовать ReportDefaults | |
| ### Phase 5: Обновление импортов в stt/ | |
| - [ ] `stt/whisper_transcriber.py` - использовать ModelDefaults | |
| - [ ] `stt/audio_processor.py` - использовать AudioFormats | |
| ### Phase 6: Обновление импортов в knowledge_base/ | |
| - [ ] `knowledge_base/term_manager.py` - использовать новые структуры | |
| --- | |
| ## Полезные ссылки в коде | |
| ```python | |
| # Все константы | |
| from common import UIColors, UIDimensions, Messages, etc. | |
| # Логирование | |
| from common import get_logger, configure_logging | |
| # Валидация | |
| from common import Validator | |
| # Структуры данных | |
| from common import PatientMetadata, PipelineResult, etc. | |
| # Исключения | |
| from common import ( | |
| AudioFileException, | |
| ValidationException, | |
| APIException, | |
| etc. | |
| ) | |
| ``` | |
| --- | |
| ## Итого | |
| 1. **Константы** - используйте вместо магических чисел | |
| 2. **Логирование** - вызовите `configure_logging()` в main, затем `get_logger()` | |
| 3. **Валидация** - используйте `Validator.validate_*()` | |
| 4. **Структуры** - создавайте с типизацией вместо dict | |
| 5. **Ошибки** - ловите специфичные исключения | |
| Это сделает код более читаемым, надёжным и поддерживаемым! 🎉 | |