# Build 007 — Protocol и Exceptions

**Статус:** Завершён  
**Подсистема:** `market_data/acquisition`  
**Область:** Instrument Reference Data  
**Тип изменения:** Изолированное расширение новой архитектуры без подключения к production runtime  
**Результат полного набора тестов:** `113 passed`

---

## 1. Цель Build 007

Цель Build 007 — зафиксировать минимальные публичные контракты взаимодействия между следующими слоями новой подсистемы Instrument Reference Data:

```text
Build 008 — Dzengi REST Adapter
Build 009 — Instrument Handler
Build 010 — Instrument Feed
Build 011 — Registry
Build 012 — Acquisition Service
```

Также добавлена специализированная ошибка транспортного уровня для будущего REST adapter.

После завершения Build 007 архитектурная последовательность выглядит следующим образом:

```text
InstrumentDocumentSource
    ↓
InstrumentDocumentHandler
    ↓
InstrumentFeedProtocol
    ↓
Registry
    ↓
Acquisition Service
```

Build 007 не реализует получение или обработку данных и не подключает новую подсистему к существующему `ExchangeService`.

---

## 2. Почему Build 007 выполняется именно сейчас

До начала Build 007 были завершены предыдущие этапы:

```text
Build 001 — внутренняя модель Instrument Reference Data
Build 002 — raw-модели ответа Dzengi
Build 003 — структурная валидация exchangeInfo
Build 004 — parser exchangeInfo
Build 005 — value validation
Build 006 — mapper Dzengi → Instrument
```

К началу Build 007 уже существовал полный pipeline преобразования заранее полученного JSON-документа:

```text
raw JSON document
    ↓
Schema Validation
    ↓
Parser
    ↓
Dzengi Raw Models
    ↓
Value Validation
    ↓
Mapper
    ↓
tuple[Instrument, ...]
```

Следующие Build должны добавить транспортный источник, handler, feed, registry и acquisition service.

Перед их реализацией необходимо было определить минимальные интерфейсы взаимодействия между этими слоями и добавить специализированную ошибку транспортного уровня.

---

## 3. Архитектурная граница Build 007

Build 007 отвечает только за:

```text
определение контракта источника сырого документа;
определение контракта обработчика сырого документа;
определение контракта источника готовых Instrument;
добавление ошибки транспортного уровня.
```

Build 007 не выполняет:

```text
HTTP-запросы;
получение exchangeInfo;
schema validation;
parsing;
value validation;
mapping;
создание конкретного Handler;
создание конкретного Feed;
создание Registry;
создание Acquisition Service;
кэширование;
изменение ExchangeService;
изменение runtime;
изменение Telegram UI;
изменение автоторговли.
```

---

## 4. Изменённые файлы

В рамках Build 007 изменены:

```text
app/src/market_data/acquisition/protocol.py
app/src/market_data/acquisition/exceptions.py
```

Создан тестовый файл:

```text
app/tests/unit/market_data/acquisition/test_protocol.py
```

Не изменялись:

```text
app/src/market_data/acquisition/adapters/dzengi/rest.py
app/src/market_data/acquisition/handlers/instrument_handler.py
app/src/market_data/acquisition/feeds/instrument_feed.py
app/src/market_data/acquisition/registry.py
app/src/market_data/acquisition/service.py

app/src/market_data/acquisition/adapters/dzengi/models.py
app/src/market_data/acquisition/adapters/dzengi/parser.py
app/src/market_data/acquisition/adapters/dzengi/mapper.py

app/src/market_data/acquisition/validation/schema.py
app/src/market_data/acquisition/validation/values.py

app/src/integrations/exchange/*
app/src/telegram/*
app/src/trading/*
```

Работающий legacy-код бота не изменён.

---

## 5. Созданные Protocol

В файле:

```text
app/src/market_data/acquisition/protocol.py
```

созданы три минимальных протокола:

```text
InstrumentDocumentSource
InstrumentDocumentHandler
InstrumentFeedProtocol
```

Все протоколы основаны на структурной типизации Python:

```python
typing.Protocol
```

и объявлены как:

```python
@runtime_checkable
```

Это позволяет использовать их как для статической типизации, так и для ограниченной runtime-проверки через `isinstance()`.

---

## 6. InstrumentDocumentSource

Контракт:

```python
@runtime_checkable
class InstrumentDocumentSource(Protocol):
    def fetch_instrument_document(self) -> object:
        ...
```

Назначение:

```text
получить декодированный транспортный документ Instrument Reference Data.
```

Предполагаемый конкретный потребитель этого контракта появится в:

```text
Build 008 — Dzengi REST Adapter
```

Будущий REST adapter должен реализовать метод:

```python
fetch_instrument_document()
```

и вернуть сырой декодированный документ.

---

## 7. Почему InstrumentDocumentSource возвращает object

Возвращаемый тип:

```python
object
```

выбран сознательно.

Транспортный источник не должен выполнять:

```text
schema validation;
parsing;
value validation;
mapping.
```

На транспортной границе REST adapter может получить произвольное декодированное JSON-значение:

```text
dict
list
str
int
float
bool
None
```

Проверка структуры является обязанностью:

```text
Schema Validation
```

Поэтому транспортный слой не должен преждевременно утверждать, что полученный документ является корректным JSON-объектом нужной структуры.

Архитектурная граница остаётся следующей:

```text
REST Adapter
    ↓
object
    ↓
Schema Validation
    ↓
ValidatedExchangeInfoDocument
```

---

## 8. InstrumentDocumentHandler

Контракт:

```python
@runtime_checkable
class InstrumentDocumentHandler(Protocol):
    def handle_instrument_document(
        self,
        document: object,
    ) -> tuple[Instrument, ...]:
        ...
```

Назначение:

```text
преобразовать сырой документ в проверенные внутренние модели Instrument.
```

Конкретная реализация появится в:

```text
Build 009 — Instrument Handler
```

Handler должен объединить уже существующий pipeline:

```text
object
    ↓
validate_exchange_info_schema()
    ↓
ValidatedExchangeInfoDocument
    ↓
parse_exchange_info()
    ↓
DzengiExchangeInfoResponse
    ↓
validate_exchange_info_values()
    ↓
map_dzengi_exchange_info_to_instruments()
    ↓
tuple[Instrument, ...]
```

При этом Protocol не знает:

```text
какая биржа является источником;
какой parser используется;
какой mapper используется;
какие source-specific raw-модели существуют.
```

---

## 9. InstrumentFeedProtocol

Контракт:

```python
@runtime_checkable
class InstrumentFeedProtocol(Protocol):
    def load_instruments(self) -> tuple[Instrument, ...]:
        ...
```

Назначение:

```text
получить полный immutable-набор внутренних моделей Instrument.
```

Конкретный Feed появится в:

```text
Build 010 — Instrument Feed
```

Предполагаемая композиция:

```text
InstrumentFeed
    ├── InstrumentDocumentSource
    └── InstrumentDocumentHandler
```

Рабочая последовательность:

```text
InstrumentFeed.load_instruments()
    ↓
InstrumentDocumentSource.fetch_instrument_document()
    ↓
object
    ↓
InstrumentDocumentHandler.handle_instrument_document()
    ↓
tuple[Instrument, ...]
```

---

## 10. Почему протокол называется InstrumentFeedProtocol

Будущий файл:

```text
app/src/market_data/acquisition/feeds/instrument_feed.py
```

предназначен для конкретной реализации Feed.

Чтобы избежать конфликта между интерфейсом и конкретным классом, протокол получил имя:

```text
InstrumentFeedProtocol
```

Конкретная реализация в Build 010 сможет называться:

```text
InstrumentFeed
```

Таким образом:

```text
InstrumentFeedProtocol
    контракт

InstrumentFeed
    конкретная реализация
```

---

## 11. Structural Typing

Реализации не обязаны наследоваться от Protocol напрямую.

Например:

```python
class StubInstrumentDocumentSource:
    def fetch_instrument_document(self) -> object:
        return {
            "symbols": [],
        }
```

Такой объект удовлетворяет контракту:

```text
InstrumentDocumentSource
```

без явного наследования:

```python
class StubInstrumentDocumentSource(InstrumentDocumentSource):
    ...
```

Это уменьшает связанность между конкретными реализациями и интерфейсами.

---

## 12. Runtime Checkable

Все три Protocol объявлены с:

```python
@runtime_checkable
```

Благодаря этому допустима проверка:

```python
isinstance(source, InstrumentDocumentSource)
```

Тестами подтверждено:

```text
объект с требуемым методом
    → соответствует Protocol

объект без требуемого метода
    → не соответствует Protocol
```

Важно: runtime-проверка Protocol подтверждает структурное наличие требуемых атрибутов и методов, но не выполняет полную глубокую проверку всех аннотаций типов и фактических возвращаемых значений.

---

## 13. Новая транспортная ошибка

В файл:

```text
app/src/market_data/acquisition/exceptions.py
```

добавлена:

```python
class InstrumentReferenceTransportError(
    MarketDataAcquisitionError
):
    pass
```

Она предназначена для будущего:

```text
Build 008 — Dzengi REST Adapter
```

---

## 14. Назначение InstrumentReferenceTransportError

Ошибка предназначена для проблем получения Instrument Reference Data от внешнего источника.

Потенциальные случаи:

```text
ошибка соединения;
timeout;
HTTP error;
ошибка JSON decoding;
непредвиденная ошибка REST-клиента.
```

Она не используется для:

```text
неверной структуры документа;
ошибки parsing;
недопустимых значений;
ошибки mapping.
```

Эти случаи уже имеют специализированные типы исключений.

---

## 15. Итоговая иерархия ошибок

После Build 007 иерархия выглядит следующим образом:

```text
MarketDataAcquisitionError
├── InstrumentReferenceTransportError
├── InstrumentReferenceSchemaError
├── InstrumentReferenceParseError
├── InstrumentReferenceValueError
└── InstrumentReferenceMappingError
```

Назначение ошибок:

| Ошибка | Ответственность |
|---|---|
| `InstrumentReferenceTransportError` | Получение данных от внешнего источника |
| `InstrumentReferenceSchemaError` | Структура исходного документа |
| `InstrumentReferenceParseError` | Преобразование проверенного документа в raw-модели |
| `InstrumentReferenceValueError` | Допустимость значений |
| `InstrumentReferenceMappingError` | Преобразование raw-модели во внутреннюю модель `Instrument` |

---

## 16. Какие дополнительные Protocol не создавались

В Build 007 сознательно не создавались отдельные Protocol для:

```text
Schema Validator
Parser
Value Validator
Mapper
Registry
Acquisition Service
```

Причины:

```text
validators, parser и mapper уже реализованы как чистые функции;

Registry и Acquisition Service пока не имеют нескольких реализаций;

дополнительные интерфейсы сейчас не используются;

создание таких контрактов было бы преждевременной абстракцией.
```

Это соответствует принципу:

```text
не создавать абстракции «на будущее» без конкретного потребителя.
```

---

## 17. Какие дополнительные Exceptions не создавались

В Build 007 сознательно не добавлялись:

```text
InstrumentReferenceHandlerError
InstrumentReferenceFeedError
InstrumentReferenceRegistryError
InstrumentReferenceServiceError
```

Причины:

```text
Handler может передавать точные ошибки Schema, Parser, Value и Mapping;

Feed может передавать точные ошибки Transport и Processing;

Registry ещё не реализован;

Acquisition Service ещё не реализован;

новые типы ошибок следует вводить только там, где появляется реальная новая категория отказа.
```

---

## 18. Реализованные тестовые сценарии

Создан файл:

```text
app/tests/unit/market_data/acquisition/test_protocol.py
```

Реализовано 7 тестов.

Проверены:

1. соответствие корректного source объекта `InstrumentDocumentSource`;
2. соответствие корректного handler объекта `InstrumentDocumentHandler`;
3. соответствие корректного feed объекта `InstrumentFeedProtocol`;
4. отклонение объектов без обязательных методов;
5. structural typing без явного наследования;
6. наследование `InstrumentReferenceTransportError` от `MarketDataAcquisitionError`;
7. наличие общего базового типа у всех ошибок Instrument Reference Data.

---

## 19. Выполненные проверки

### Проверка 1 — unit-тесты Protocol и Exceptions

Команда:

```bash
python -m pytest \
  tests/unit/market_data/acquisition/test_protocol.py \
  -q
```

Результат:

```text
....... [100%]
7 passed in 0.01s
```

Статус:

```text
PASSED
```

---

### Проверка 2 — Python compilation

Команда:

```bash
python -m py_compile \
  src/market_data/acquisition/protocol.py \
  src/market_data/acquisition/exceptions.py \
  tests/unit/market_data/acquisition/test_protocol.py
```

Результат:

```text
Команда завершилась без ошибок и без вывода.
```

Статус:

```text
PASSED
```

---

### Проверка 3 — полный набор тестов проекта

Команда:

```bash
python -m pytest -q
```

Результат:

```text
................................................................................................................. [100%]
113 passed in 0.06s
```

Статус:

```text
PASSED
```

---

### Проверка 4 — отсутствие преждевременной production-интеграции

Команда:

```bash
grep -RIn \
  --exclude-dir="__pycache__" \
  --exclude="*.pyc" \
  -E "InstrumentDocumentSource|InstrumentDocumentHandler|InstrumentFeedProtocol|InstrumentReferenceTransportError" \
  src tests
```

Подтверждено:

```text
InstrumentDocumentSource
    используется только в protocol.py и unit-тестах

InstrumentDocumentHandler
    используется только в protocol.py и unit-тестах

InstrumentFeedProtocol
    используется только в protocol.py и unit-тестах

InstrumentReferenceTransportError
    объявлена в exceptions.py и используется только в unit-тестах
```

Не обнаружено подключения к:

```text
src/integrations/exchange/*
src/telegram/*
src/trading/*
```

Статус:

```text
PASSED
```

---

## 20. Архитектура после Build 007

После завершения Build 007 новая часть подсистемы имеет следующую структуру:

```text
market_data/
└── acquisition/
    ├── exceptions.py
    │   ├── MarketDataAcquisitionError
    │   ├── InstrumentReferenceTransportError
    │   ├── InstrumentReferenceSchemaError
    │   ├── InstrumentReferenceParseError
    │   ├── InstrumentReferenceValueError
    │   └── InstrumentReferenceMappingError
    │
    ├── protocol.py
    │   ├── InstrumentDocumentSource
    │   ├── InstrumentDocumentHandler
    │   └── InstrumentFeedProtocol
    │
    ├── models/
    │   └── instrument.py
    │       └── Instrument
    │
    ├── validation/
    │   ├── schema.py
    │   └── values.py
    │
    └── adapters/
        └── dzengi/
            ├── models.py
            ├── parser.py
            ├── mapper.py
            └── rest.py
```

На текущем этапе:

```text
rest.py
    ещё не реализован

instrument_handler.py
    ещё не реализован

instrument_feed.py
    ещё не реализован

registry.py
    ещё не реализован

service.py
    ещё не реализован
```

---

## 21. Полная архитектурная цепочка после Build 007

Уже реализовано:

```text
raw JSON document
    ↓
validate_exchange_info_schema()
    ↓
ValidatedExchangeInfoDocument
    ↓
parse_exchange_info()
    ↓
DzengiExchangeInfoResponse
    ↓
validate_exchange_info_values()
    ↓
map_dzengi_exchange_info_to_instruments()
    ↓
tuple[Instrument, ...]
```

Зафиксированы контракты для будущей orchestration-цепочки:

```text
InstrumentDocumentSource
    ↓
InstrumentDocumentHandler
    ↓
InstrumentFeedProtocol
    ↓
Registry
    ↓
Acquisition Service
```

После реализации Build 008–012 предполагаемая полная цепочка будет выглядеть так:

```text
Dzengi REST API
    ↓
Dzengi REST Adapter
    implements InstrumentDocumentSource
    ↓
object
    ↓
Instrument Handler
    implements InstrumentDocumentHandler
    ↓
tuple[Instrument, ...]
    ↓
Instrument Feed
    implements InstrumentFeedProtocol
    ↓
Registry
    ↓
Acquisition Service
```

---

## 22. Влияние на legacy-систему

Build 007 не подключён к существующим компонентам:

```text
ExchangeService
ExchangeSymbol
SymbolValidationResult
Telegram UI
AutoTrade
Market Stream
Market Data Runner
Execution Quality
```

Не изменены:

```text
ExchangeService.get_exchange_symbols()
ExchangeService.validate_symbol()
ExchangeService.get_symbol_runtime_status()
normalize_symbol()
symbol_candidates()
```

Старый production-путь продолжает работать без изменений.

Новая подсистема развивается параллельно.

---

## 23. Обратная совместимость

Подтверждено сохранение:

```text
сигнатур существующих методов;
старых импортов;
существующего формата ошибок;
Telegram UI;
автоторговли;
runtime-поведения;
legacy ExchangeSymbol;
legacy-кэша.
```

Build 007 имеет полную обратную совместимость.

---

## 24. Классификация изменений

| Изменение | Классификация |
|---|---|
| `InstrumentDocumentSource` | Обязательное архитектурное изменение |
| `InstrumentDocumentHandler` | Обязательное архитектурное изменение |
| `InstrumentFeedProtocol` | Обязательное архитектурное изменение |
| `InstrumentReferenceTransportError` | Обязательное архитектурное изменение |
| Structural typing | Улучшение архитектурной независимости |
| `runtime_checkable` | Улучшение тестируемости и проверяемости контрактов |
| Дополнительные преждевременные Protocol | Не создавались |
| Дополнительные преждевременные Exceptions | Не создавались |
| Изменение production-поведения | Отсутствует |

---

## 25. Итог Build 007

Build 007 завершён успешно.

Реализовано:

```text
InstrumentDocumentSource
InstrumentDocumentHandler
InstrumentFeedProtocol
InstrumentReferenceTransportError
```

Подтверждено:

```text
7 protocol tests passed
113 total project tests passed
Python compilation passed
No production integration detected
Legacy bot behavior unchanged
```

Итоговый статус:

```text
BUILD 007 — COMPLETE
```

---

## 26. Следующий этап

Следующий этап утверждённого плана:

```text
Build 008 — Dzengi REST Adapter
```

Его задача — реализовать конкретный транспортный источник, соответствующий контракту:

```text
InstrumentDocumentSource
```

Будущая граница Build 008:

```text
Dzengi REST API
    ↓
Dzengi REST Adapter
    ↓
object
```

Build 008 не должен выполнять:

```text
schema validation;
parsing;
value validation;
mapping;
создание Instrument;
создание Handler;
создание Feed;
создание Registry;
создание Acquisition Service;
изменение ExchangeService;
production-подключение.
```