# 📚 Руководство по разработке парсеров

Полное руководство по созданию новых парсеров для системы NIN Excel Parsers API.

## 📋 Содержание

1. [Архитектура системы](#архитектура-системы)
2. [Структура проекта](#структура-проекта)
3. [Создание нового парсера](#создание-нового-парсера)
4. [Регистрация парсера](#регистрация-парсера)
5. [Создание API эндпоинтов](#создание-api-эндпоинтов)
6. [Интеграция с Streamlit](#интеграция-с-streamlit)
7. [Тестирование](#тестирование)
8. [Лучшие практики](#лучшие-практики)
9. [Примеры](#примеры)

---

## 🏗️ Архитектура системы

### Hexagonal Architecture

Система построена на принципах **Hexagonal Architecture** (Ports & Adapters):

```
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Streamlit UI  │    │   FastAPI       │    │   MinIO Storage │
│   (Adapter)     │◄──►│   (Application) │◄──►│   (Adapter)     │
└─────────────────┘    └─────────────────┘    └─────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │   ParserPort    │
                    │   (Core)        │
                    └─────────────────┘
                              ▲
                              │
                    ┌─────────────────┐
                    │   Parser        │
                    │   (Adapter)     │
                    └─────────────────┘
```

### Ключевые компоненты

- **ParserPort** - базовый класс для всех парсеров
- **ReportService** - сервис для управления отчетами
- **MinIOStorageAdapter** - адаптер для хранения данных
- **FastAPI** - веб-фреймворк для API
- **Streamlit** - веб-интерфейс

---

## 📁 Структура проекта

```
python_parser/
├── adapters/
│   ├── parsers/           # Парсеры (адаптеры)
│   │   ├── __init__.py
│   │   ├── monitoring_fuel.py
│   │   ├── monitoring_tar.py
│   │   ├── svodka_ca.py
│   │   ├── svodka_pm.py
│   │   ├── svodka_repair_ca.py
│   │   └── statuses_repair_ca.py
│   ├── pconfig.py         # Конфигурация парсеров
│   └── storage.py         # Адаптер хранилища
├── app/
│   ├── main.py           # FastAPI приложение
│   └── schemas/          # Pydantic схемы
│       ├── monitoring_fuel.py
│       ├── monitoring_tar.py
│       ├── svodka_ca.py
│       ├── svodka_pm.py
│       ├── svodka_repair_ca.py
│       └── statuses_repair_ca.py
├── core/
│   ├── models.py         # Модели данных
│   ├── ports.py          # Базовые порты
│   ├── schema_utils.py   # Утилиты схем
│   └── services.py       # Сервисы
└── requirements.txt

streamlit_app/
├── streamlit_app.py      # Streamlit интерфейс
└── requirements.txt
```

---

## 🔧 Создание нового парсера

### 1. Создание Pydantic схем

Создайте файл схемы в `python_parser/app/schemas/your_parser.py`:

```python
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from enum import Enum

class YourParserMode(str, Enum):
    """Режимы работы парсера"""
    MODE1 = "mode1"
    MODE2 = "mode2"

class YourParserRequest(BaseModel):
    """Схема запроса для основного геттера"""
    param1: Optional[str] = Field(
        None,
        description="Описание параметра 1",
        example="example_value"
    )
    param2: Optional[List[str]] = Field(
        None,
        description="Описание параметра 2",
        example=["value1", "value2"]
    )
    mode: Optional[YourParserMode] = Field(
        None,
        description="Режим работы парсера",
        example="mode1"
    )

    class Config:
        json_schema_extra = {
            "example": {
                "param1": "example_value",
                "param2": ["value1", "value2"],
                "mode": "mode1"
            }
        }

class YourParserFullRequest(BaseModel):
    """Схема запроса для получения всех данных"""
    # Пустая схема - возвращает все данные без фильтрации
    pass

    class Config:
        json_schema_extra = {
            "example": {}
        }
```

### 2. Создание парсера

Создайте файл парсера в `python_parser/adapters/parsers/your_parser.py`:

```python
import pandas as pd
import os
import zipfile
import tempfile
from typing import Dict, Any, Optional
from core.ports import ParserPort
from adapters.pconfig import find_header_row, data_to_json
from app.schemas.your_parser import YourParserRequest, YourParserFullRequest


class YourParser(ParserPort):
    """Парсер для вашего типа данных"""
    
    name = "your_parser"  # Уникальное имя парсера
    
    def __init__(self):
        super().__init__()
        # Регистрируем геттеры
        self.register_getter("get_data", YourParserRequest, self._get_data_wrapper)
        self.register_getter("get_full_data", YourParserFullRequest, self._get_full_data_wrapper)
        
        # Данные парсера
        self.data_dict = {}
    
    def parse(self, file_path: str, params: dict) -> Dict[str, Any]:
        """Основной метод парсинга"""
        print(f"🔍 DEBUG: YourParser.parse вызван с файлом: {file_path}")
        
        try:
            # Проверяем тип файла (пример для ZIP-only парсера)
            if not file_path.endswith('.zip'):
                raise ValueError(f"Неподдерживаемый тип файла: {file_path}. Ожидается только ZIP архив.")
            
            # Обрабатываем ZIP архив
            result = self._parse_zip_archive(file_path)
            
            # Сохраняем результат
            self.data_dict = result
            print(f"✅ Парсинг завершен. Получено {len(result)} записей")
            return result
            
        except Exception as e:
            print(f"❌ Ошибка при парсинге: {e}")
            raise
    
    def _parse_zip_archive(self, zip_path: str) -> Dict[str, Any]:
        """Парсинг ZIP архива"""
        print(f"📦 Обработка ZIP архива: {zip_path}")
        
        with tempfile.TemporaryDirectory() as temp_dir:
            with zipfile.ZipFile(zip_path, 'r') as zip_ref:
                zip_ref.extractall(temp_dir)
            
            # Ищем нужные файлы
            target_files = []
            for root, dirs, files in os.walk(temp_dir):
                for file in files:
                    if file.endswith(('.xlsx', '.xls')):
                        target_files.append(os.path.join(root, file))
            
            if not target_files:
                raise ValueError("В архиве не найдены поддерживаемые файлы")
            
            # Парсим все найденные файлы
            all_data = {}
            for file_path in target_files:
                file_data = self._parse_single_file(file_path)
                # Объединяем данные
                all_data.update(file_data)
            
            return all_data
    
    def _parse_single_file(self, file_path: str) -> Dict[str, Any]:
        """Парсинг одного файла"""
        print(f"📁 Обработка файла: {file_path}")
        
        try:
            # Читаем Excel файл
            excel_file = pd.ExcelFile(file_path)
            available_sheets = excel_file.sheet_names
            
            # Обрабатываем нужные листы
            result_data = {}
            for sheet_name in available_sheets:
                if self._should_process_sheet(sheet_name):
                    sheet_data = self._parse_sheet(file_path, sheet_name)
                    result_data.update(sheet_data)
            
            return result_data
            
        except Exception as e:
            print(f"❌ Ошибка при обработке файла {file_path}: {e}")
            return {}
    
    def _should_process_sheet(self, sheet_name: str) -> bool:
        """Определяет, нужно ли обрабатывать лист"""
        # Логика фильтрации листов
        return True  # Или ваша логика
    
    def _parse_sheet(self, file_path: str, sheet_name: str) -> Dict[str, Any]:
        """Парсинг конкретного листа"""
        try:
            # Находим заголовок
            header_num = find_header_row(file_path, sheet_name, search_value="1")
            if header_num is None:
                print(f"❌ Не найден заголовок в листе {sheet_name}")
                return {}
            
            # Читаем данные
            df = pd.read_excel(
                file_path,
                sheet_name=sheet_name,
                header=header_num,
                index_col=None
            )
            
            # Обрабатываем данные
            processed_data = self._process_dataframe(df, sheet_name)
            
            return processed_data
            
        except Exception as e:
            print(f"❌ Ошибка при обработке листа {sheet_name}: {e}")
            return {}
    
    def _process_dataframe(self, df: pd.DataFrame, sheet_name: str) -> Dict[str, Any]:
        """Обработка DataFrame"""
        # Ваша логика обработки данных
        return {"sheet_name": sheet_name, "data": df.to_dict('records')}
    
    def _get_data_wrapper(self, params: dict) -> Dict[str, Any]:
        """Обертка для основного геттера"""
        print(f"🔍 DEBUG: _get_data_wrapper вызван с параметрами: {params}")
        
        # Валидируем параметры
        validated_params = YourParserRequest(**params)
        
        # Получаем данные из парсера
        data_source = self._get_data_source()
        
        if not data_source:
            print("⚠️ Нет данных в парсере")
            return {}
        
        # Фильтруем данные по параметрам
        filtered_data = self._filter_data(data_source, validated_params)
        
        # Конвертируем в JSON
        try:
            result_json = data_to_json(filtered_data)
            return result_json
        except Exception as e:
            print(f"❌ Ошибка при конвертации данных в JSON: {e}")
            return {}
    
    def _get_full_data_wrapper(self, params: dict) -> Dict[str, Any]:
        """Обертка для геттера всех данных"""
        print(f"🔍 DEBUG: _get_full_data_wrapper вызван с параметрами: {params}")
        
        # Получаем данные из парсера
        data_source = self._get_data_source()
        
        if not data_source:
            print("⚠️ Нет данных в парсере")
            return {}
        
        # Конвертируем все данные в JSON
        try:
            result_json = data_to_json(data_source)
            return result_json
        except Exception as e:
            print(f"❌ Ошибка при конвертации данных в JSON: {e}")
            return {}
    
    def _get_data_source(self) -> Dict[str, Any]:
        """Получает источник данных"""
        if hasattr(self, 'df') and self.df is not None:
            # Данные загружены из MinIO
            if isinstance(self.df, dict):
                return self.df
            else:
                return {}
        elif hasattr(self, 'data_dict') and self.data_dict:
            # Данные из локального парсинга
            return self.data_dict
        else:
            return {}
    
    def _filter_data(self, data_source: Dict[str, Any], params: YourParserRequest) -> Dict[str, Any]:
        """Фильтрует данные по параметрам"""
        # Ваша логика фильтрации
        return data_source
```

---

## 📝 Регистрация парсера

### 1. Регистрация в __init__.py

Добавьте импорт в `python_parser/adapters/parsers/__init__.py`:

```python
from .monitoring_fuel import MonitoringFuelParser
from .monitoring_tar import MonitoringTarParser
from .your_parser import YourParser  # Добавить эту строку
from .svodka_ca import SvodkaCAParser
from .svodka_pm import SvodkaPMParser
from .svodka_repair_ca import SvodkaRepairCAParser
from .statuses_repair_ca import StatusesRepairCAParser

__all__ = [
    'MonitoringFuelParser',
    'MonitoringTarParser',
    'YourParser',  # Добавить эту строку
    'SvodkaCAParser',
    'SvodkaPMParser',
    'SvodkaRepairCAParser',
    'StatusesRepairCAParser'
]
```

### 2. Регистрация в main.py

Добавьте импорт и регистрацию в `python_parser/app/main.py`:

```python
# Импорты
from adapters.parsers import SvodkaPMParser, SvodkaCAParser, MonitoringFuelParser, MonitoringTarParser, YourParser, SvodkaRepairCAParser, StatusesRepairCAParser

from app.schemas.your_parser import YourParserRequest, YourParserFullRequest

# Регистрация парсера
PARSERS.update({
    'svodka_pm': SvodkaPMParser,
    'svodka_ca': SvodkaCAParser,
    'monitoring_fuel': MonitoringFuelParser,
    'monitoring_tar': MonitoringTarParser,
    'your_parser': YourParser,  # Добавить эту строку
    'svodka_repair_ca': SvodkaRepairCAParser,
    'statuses_repair_ca': StatusesRepairCAParser,
})
```

### 3. Регистрация в services.py

Добавьте логику выбора геттера в `python_parser/core/services.py`:

```python
elif request.report_type == 'monitoring_tar':
    # Для monitoring_tar используем геттер get_tar_data
    getter_name = 'get_tar_data'
elif request.report_type == 'your_parser':  # Добавить эту секцию
    # Для your_parser используем геттер get_data
    getter_name = 'get_data'
elif request.report_type == 'monitoring_fuel':
```

---

## 🌐 Создание API эндпоинтов

Добавьте эндпоинты в `python_parser/app/main.py`:

```python
# ====== YOUR PARSER ENDPOINTS ======

@app.post("/your_parser/upload", tags=[YourParser.name],
          summary="Загрузка отчета вашего типа")
async def upload_your_parser(
    file: UploadFile = File(...)
):
    """Загрузка и обработка отчета вашего типа

    ### Поддерживаемые форматы:
    - **ZIP архивы** с файлами (только ZIP)

    ### Структура данных:
    - Описание структуры данных
    - Какие данные извлекаются
    - Формат возвращаемых данных
    """
    report_service = get_report_service()

    try:
        # Проверяем тип файла - только ZIP архивы
        if not file.filename.endswith('.zip'):
            raise HTTPException(
                status_code=400,
                detail="Неподдерживаемый тип файла. Ожидается только ZIP архив (.zip)"
            )

        # Читаем содержимое файла
        file_content = await file.read()

        # Создаем запрос на загрузку
        upload_request = UploadRequest(
            report_type='your_parser',
            file_content=file_content,
            file_name=file.filename
        )

        # Загружаем отчет
        result = report_service.upload_report(upload_request)

        if result.success:
            return UploadResponse(
                success=True,
                message="Отчет успешно загружен и обработан",
                report_id=result.object_id,
                filename=file.filename
            ).model_dump()
        else:
            return UploadErrorResponse(
                success=False,
                message=result.message,
                error_code="ERR_UPLOAD",
                details=None
            ).model_dump()

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")


@app.post("/your_parser/get_data", tags=[YourParser.name],
          summary="Получение данных из отчета")
async def get_your_parser_data(
    request_data: YourParserRequest
):
    """Получение данных из отчета

    ### Структура параметров:
    - `param1`: **Описание параметра 1** (опциональный)
    - `param2`: **Описание параметра 2** (опциональный)
    - `mode`: **Режим работы** (опциональный)

    ### Пример тела запроса:
    ```json
    {
        "param1": "example_value",
        "param2": ["value1", "value2"],
        "mode": "mode1"
    }
    ```
    """
    report_service = get_report_service()

    try:
        # Создаем запрос
        request_dict = request_data.model_dump()
        request = DataRequest(
            report_type='your_parser',
            get_params=request_dict
        )

        # Получаем данные
        result = report_service.get_data(request)

        if result.success:
            return {
                "success": True,
                "data": result.data
            }
        else:
            raise HTTPException(status_code=404, detail=result.message)

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")


@app.post("/your_parser/get_full_data", tags=[YourParser.name],
          summary="Получение всех данных из отчета")
async def get_your_parser_full_data():
    """Получение всех данных из отчета без фильтрации

    ### Возвращает:
    - Все данные без фильтрации
    - Полная структура данных
    """
    report_service = get_report_service()

    try:
        # Создаем запрос без параметров
        request = DataRequest(
            report_type='your_parser',
            get_params={}
        )

        # Получаем данные через геттер get_full_data
        result = report_service.get_data(request, getter_name='get_full_data')

        if result.success:
            return {
                "success": True,
                "data": result.data
            }
        else:
            raise HTTPException(status_code=404, detail=result.message)

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
```

---

## 🎨 Интеграция с Streamlit

### 1. Добавление новой вкладки

В `streamlit_app/streamlit_app.py`:

```python
# Обновите список вкладок
tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs([
    "📊 Сводки ПМ", 
    "🏭 Сводки СА", 
    "⛽ Мониторинг топлива",
    "⚡ Мониторинг ТЭР",
    "🔧 Ремонт СА",
    "📋 Статусы ремонта СА",
    "🆕 Ваш парсер"  # Добавить новую вкладку
])

# Добавьте новую вкладку
with tab7:
    st.header("🆕 Ваш парсер")
    
    # Секция загрузки файлов
    st.subheader("📤 Загрузка файлов")
            uploaded_file = st.file_uploader(
            "Выберите ZIP архив для вашего парсера",
            type=['zip'],
            key="your_parser_upload"
        )
    
    if uploaded_file is not None:
        if st.button("📤 Загрузить файл", key="your_parser_upload_btn"):
            with st.spinner("Загружаем файл..."):
                file_data = uploaded_file.read()
                result, status_code = upload_file_to_api("/your_parser/upload", file_data, uploaded_file.name)
                
                if status_code == 200:
                    st.success("✅ Файл успешно загружен!")
                    st.json(result)
                else:
                    st.error(f"❌ Ошибка загрузки: {result}")
    
    # Секция получения данных
    st.subheader("📊 Получение данных")
    
    col1, col2 = st.columns(2)
    
    with col1:
        st.subheader("🔍 Фильтрованные данные")
        
        # Параметры запроса
        param1 = st.text_input("Параметр 1:", key="your_parser_param1")
        param2 = st.multiselect("Параметр 2:", ["value1", "value2", "value3"], key="your_parser_param2")
        mode = st.selectbox("Режим:", ["mode1", "mode2"], key="your_parser_mode")
        
        if st.button("📊 Получить данные", key="your_parser_get_data_btn"):
            with st.spinner("Получаем данные..."):
                request_data = {
                    "param1": param1 if param1 else None,
                    "param2": param2 if param2 else None,
                    "mode": mode if mode else None
                }
                result, status_code = make_api_request("/your_parser/get_data", request_data)
                
                if status_code == 200 and result.get("success"):
                    st.success("✅ Данные успешно получены!")
                    
                    # Показываем данные
                    data = result.get("data", {}).get("value", {})
                    if data:
                        st.subheader("📋 Результат:")
                        st.json(data)
                    else:
                        st.info("📋 Нет данных для отображения")
                else:
                    st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
    
    with col2:
        st.subheader("📋 Все данные")
        
        if st.button("📊 Получить все данные", key="your_parser_get_full_data_btn"):
            with st.spinner("Получаем все данные..."):
                result, status_code = make_api_request("/your_parser/get_full_data", {})
                
                if status_code == 200 and result.get("success"):
                    st.success("✅ Все данные успешно получены!")
                    
                    # Показываем данные
                    data = result.get("data", {}).get("value", {})
                    if data:
                        st.subheader("📋 Результат:")
                        st.json(data)
                    else:
                        st.info("📋 Нет данных для отображения")
                else:
                    st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
```

### 2. Обновление информации о проекте

```python
# В секции "О проекте"
**Возможности:**
- 📊 Парсинг сводок ПМ (план и факт)
- 🏭 Парсинг сводок СА
- ⛽ Мониторинг топлива
- ⚡ Мониторинг ТЭР (Топливно-энергетические ресурсы)
- 🔧 Управление ремонтными работами СА
- 📋 Мониторинг статусов ремонта СА
- 🆕 Ваш новый парсер  # Добавить эту строку
```

---

## 🧪 Тестирование

### 1. Создание тестового скрипта

Создайте `test_your_parser.py`:

```python
#!/usr/bin/env python3
"""
Тестовый скрипт для проверки парсера Your Parser
"""

import requests
import os

API_BASE_URL = "http://localhost:8000"

def test_upload_file():
    """Тест загрузки файла"""
    print("🔍 Тестируем загрузку файла...")
    
    # Используем тестовый файл
    file_path = "path/to/your/test/file.xlsx"
    
    if not os.path.exists(file_path):
        print(f"❌ Файл не найден: {file_path}")
        return False
    
    print(f"📁 Найден файл: {file_path}")
    
    with open(file_path, 'rb') as f:
        file_content = f.read()
    
    files = {
        'file': ('test_file.xlsx', file_content, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
    }
    
    try:
        response = requests.post(f"{API_BASE_URL}/your_parser/upload", files=files)
        print(f"📊 Статус ответа: {response.status_code}")
        print(f"📄 Ответ: {response.text}")
        
        if response.status_code == 200:
            result = response.json()
            if result.get("success"):
                print("✅ Загрузка успешна!")
                return True
            else:
                print("❌ Ошибка загрузки!")
                return False
        else:
            print("❌ Ошибка HTTP!")
            return False
            
    except Exception as e:
        print(f"❌ Исключение: {e}")
        return False

def test_get_data():
    """Тест получения данных"""
    print("\n🔍 Тестируем получение данных...")
    
    test_data = {
        "param1": "test_value",
        "param2": ["value1", "value2"],
        "mode": "mode1"
    }
    
    try:
        response = requests.post(f"{API_BASE_URL}/your_parser/get_data", json=test_data)
        print(f"📊 Статус ответа: {response.status_code}")
        
        if response.status_code == 200:
            result = response.json()
            if result.get("success"):
                print("✅ Получение данных успешно!")
                data = result.get("data", {}).get("value", {})
                print(f"📊 Получено данных: {len(data)} записей")
                return True
            else:
                print("❌ Ошибка получения данных!")
                print(f"📄 Ответ: {response.text}")
                return False
        else:
            print("❌ Ошибка HTTP!")
            print(f"📄 Ответ: {response.text}")
            return False
            
    except Exception as e:
        print(f"❌ Исключение: {e}")
        return False

def main():
    """Основная функция тестирования"""
    print("🚀 Тестирование парсера Your Parser")
    print("=" * 70)
    
    # Тест 1: Загрузка файла
    upload_success = test_upload_file()
    
    if upload_success:
        # Тест 2: Получение данных
        get_data_success = test_get_data()
        
        print("\n" + "=" * 70)
        print("📋 Результаты тестирования:")
        print(f"  ✅ Загрузка файла: {'ПРОЙДЕН' if upload_success else 'ПРОВАЛЕН'}")
        print(f"  ✅ Получение данных: {'ПРОЙДЕН' if get_data_success else 'ПРОВАЛЕН'}")
        
        if upload_success and get_data_success:
            print("\n🎉 Все тесты пройдены! Парсер работает корректно!")
        else:
            print("\n❌ Некоторые тесты провалены.")
    else:
        print("\n❌ Загрузка файла провалена. Пропускаем остальные тесты.")

if __name__ == "__main__":
    main()
```

### 2. Запуск тестов

```bash
# Запуск тестового скрипта
python test_your_parser.py

# Проверка через curl
curl -X POST "http://localhost:8000/your_parser/upload" \
  -H "accept: application/json" \
  -H "Content-Type: multipart/form-data" \
  -F "file=@test_file.xlsx"

curl -X POST "http://localhost:8000/your_parser/get_data" \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"param1": "test_value", "mode": "mode1"}'
```

---

## 📋 Лучшие практики

### 1. Именование

- **Парсер**: `YourParser` (PascalCase)
- **Файл парсера**: `your_parser.py` (snake_case)
- **Имя парсера**: `"your_parser"` (snake_case)
- **Геттеры**: `get_data`, `get_full_data` (snake_case)
- **Эндпоинты**: `/your_parser/upload`, `/your_parser/get_data`

### 2. Структура данных

```python
# Рекомендуемая структура возвращаемых данных
{
    "installation_id": {
        "data_type1": [
            {"field1": "value1", "field2": "value2"},
            {"field1": "value3", "field2": "value4"}
        ],
        "data_type2": [
            {"field1": "value5", "field2": "value6"}
        ]
    }
}
```

### 3. Обработка ошибок

```python
try:
    # Ваш код
    result = some_operation()
    return result
except SpecificException as e:
    print(f"❌ Специфическая ошибка: {e}")
    return {}
except Exception as e:
    print(f"❌ Общая ошибка: {e}")
    raise
```

### 4. Логирование

```python
# Используйте эмодзи для разных типов сообщений
print(f"🔍 DEBUG: Отладочная информация")
print(f"📁 INFO: Информационное сообщение")
print(f"✅ SUCCESS: Успешная операция")
print(f"⚠️ WARNING: Предупреждение")
print(f"❌ ERROR: Ошибка")
```

### 5. Валидация данных

```python
# Всегда валидируйте входные параметры
validated_params = YourParserRequest(**params)

# Проверяйте наличие данных
if not data_source:
    print("⚠️ Нет данных в парсере")
    return {}
```

### 6. Документация

- Добавляйте docstrings ко всем методам
- Описывайте параметры в Pydantic схемах
- Добавляйте примеры в FastAPI эндпоинты
- Комментируйте сложную логику

---

## 📚 Примеры

### Пример 1: Простой парсер

```python
class SimpleParser(ParserPort):
    """Простой парсер для демонстрации"""
    
    name = "simple_parser"
    
    def __init__(self):
        super().__init__()
        self.register_getter("get_data", SimpleRequest, self._get_data_wrapper)
    
    def parse(self, file_path: str, params: dict) -> Dict[str, Any]:
        """Парсинг простого Excel файла"""
        df = pd.read_excel(file_path)
        return {"data": df.to_dict('records')}
    
    def _get_data_wrapper(self, params: dict) -> Dict[str, Any]:
        """Обертка для геттера"""
        data_source = self._get_data_source()
        return data_to_json(data_source)
```

### Пример 2: Парсер с множественными геттерами

```python
class MultiGetterParser(ParserPort):
    """Парсер с несколькими геттерами"""
    
    name = "multi_getter_parser"
    
    def __init__(self):
        super().__init__()
        # Регистрируем несколько геттеров
        self.register_getter("get_summary", SummaryRequest, self._get_summary_wrapper)
        self.register_getter("get_details", DetailsRequest, self._get_details_wrapper)
        self.register_getter("get_statistics", StatisticsRequest, self._get_statistics_wrapper)
    
    def _get_summary_wrapper(self, params: dict) -> Dict[str, Any]:
        """Геттер для получения сводки"""
        # Логика получения сводки
        pass
    
    def _get_details_wrapper(self, params: dict) -> Dict[str, Any]:
        """Геттер для получения деталей"""
        # Логика получения деталей
        pass
    
    def _get_statistics_wrapper(self, params: dict) -> Dict[str, Any]:
        """Геттер для получения статистики"""
        # Логика получения статистики
        pass
```

### Пример 3: Парсер с фильтрацией

```python
class FilteredParser(ParserPort):
    """Парсер с продвинутой фильтрацией"""
    
    def _filter_data(self, data_source: Dict[str, Any], params: FilterRequest) -> Dict[str, Any]:
        """Фильтрация данных по параметрам"""
        filtered_data = {}
        
        for key, value in data_source.items():
            # Фильтр по дате
            if params.start_date and value.get('date') < params.start_date:
                continue
            
            # Фильтр по типу
            if params.type and value.get('type') != params.type:
                continue
            
            # Фильтр по статусу
            if params.status and value.get('status') not in params.status:
                continue
            
            filtered_data[key] = value
        
        return filtered_data
```

---

## 🚀 Заключение

Это руководство покрывает все аспекты создания новых парсеров в системе NIN Excel Parsers API. Следуйте этим инструкциям для создания качественных, поддерживаемых и тестируемых парсеров.

### Чек-лист для нового парсера:

- [ ] Создана Pydantic схема
- [ ] Создан класс парсера с геттерами
- [ ] Парсер зарегистрирован в `__init__.py`
- [ ] Парсер зарегистрирован в `main.py`
- [ ] Добавлена логика в `services.py`
- [ ] Созданы FastAPI эндпоинты
- [ ] Добавлена вкладка в Streamlit
- [ ] Создан тестовый скрипт
- [ ] Проведено тестирование
- [ ] Обновлена документация

### Полезные ссылки:

- [FastAPI Documentation](https://fastapi.tiangolo.com/)
- [Pydantic Documentation](https://pydantic-docs.helpmanual.io/)
- [Streamlit Documentation](https://docs.streamlit.io/)
- [Pandas Documentation](https://pandas.pydata.org/docs/)

---

**Удачной разработки! 🚀**