# Build 002 — Raw Models Dzengi exchangeInfo **Проект:** Dzentra **Подсистема:** Market Data Acquisition **Миграция:** Instrument Reference Data **Статус:** ✅ Завершён **Дата:** 2026-07-10 --- # Цель Build Создать транспортные модели (Raw Models), полностью описывающие ответ REST API Dzengi `exchangeInfo`. На данном этапе запрещалось: - изменять ExchangeService; - изменять ExchangeSymbol; - изменять SymbolValidationResult; - изменять runtime; - изменять Telegram UI; - выполнять parser JSON; - выполнять mapper в предметную модель Instrument. Build создаёт исключительно транспортный контракт между REST API и будущим parser. --- # Причина выполнения Build После Build 001 уже существует независимая предметная модель: ```text Instrument ``` Следующим архитектурным слоем является транспортная модель адаптера. До начала Build 002 существующая реализация выглядела следующим образом: ```text REST ↓ dict ↓ ExchangeService ↓ ExchangeSymbol ``` Parser и mapper были объединены внутри `ExchangeService`. Это нарушало принцип разделения ответственности. После Build 002 появилась отдельная транспортная модель: ```text REST ↓ Raw Models ``` которая станет входом для parser в следующем Build. --- # Проанализированные материалы Перед реализацией были полностью проанализированы: ```text app/tools/dzengi_probe/runtime_samples/rest/exchangeInfo/all.json docs/market_intelligence/information/dzengi_openapi.json ``` Также были повторно использованы результаты анализа: ```text app/src/integrations/exchange/service.py app/src/integrations/exchange/models.py ``` и результаты grep по использованию ExchangeSymbol. --- # Основные выводы анализа Подтверждено: реальный ответ API значительно богаче текущей модели ExchangeSymbol. В ответе присутствуют: - symbol - name - status - assetType - baseAsset - baseAssetPrecision - quoteAsset - quoteAssetId - quotePrecision - orderTypes - marketModes - marketType - country - sector - industry - tradingHours - tickSize - tickValue - tradingFee - exchangeFee - longRate - shortRate - swapChargeInterval - minSLGap - maxSLGap - minTPGap - maxTPGap - filters - rateLimits - exchangeFilters Часть этих данных ранее полностью терялась. --- # Принятые архитектурные решения ## 1. Raw Models полностью отделены от предметной модели Созданы транспортные модели. Они: - ничего не вычисляют; - ничего не нормализуют; - ничего не валидируют. Они только описывают транспортный контракт. --- ## 2. Поддержка двух форматов ответа API Поддерживаются оба варианта: ### Wrapped ```text status correlationId payload ``` и ### Unwrapped ```text timezone serverTime symbols ``` Parser следующего Build сможет привести оба формата к единому контракту. Классификация: **Улучшение надёжности.** --- ## 3. Decimal сознательно не используется Raw Models сохраняют транспортный тип. Например: ```json "stepSize": "0.001" ``` остаётся строкой. Преобразование в Decimal является обязанностью mapper. --- ## 4. Filters представлены отдельной иерархией Созданы: ```text DzengiLotSizeFilter DzengiMinNotionalFilter DzengiUnknownFilter ``` Неизвестные фильтры не приводят к ошибке. Они сохраняются для дальнейшего анализа. Классификация: **Улучшение надёжности.** --- ## 5. Все коллекции являются immutable Используются: ```python tuple ``` вместо ```python list ``` Причина: Raw Models являются снимком транспортного ответа. Их нельзя изменять после создания. --- ## 6. Все Raw Models являются immutable Все dataclass объявлены как: ```python @dataclass(frozen=True, slots=True) ``` Это гарантирует неизменяемость транспортного контракта. --- # Созданные файлы Создан: ```text app/src/market_data/acquisition/adapters/dzengi/models.py ``` Создан: ```text app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py ``` Другие production-файлы не изменялись. --- # Проверки Выполнены все проверки Build. --- ## Unit Test Статус: ✅ Passed ``` 6 passed ``` --- ## Импорт полной транспортной модели Проверено: - импортируются все классы; - создаётся полная иерархия объектов; - вложенные dataclass работают корректно. Статус: ✅ Passed --- ## Компиляция Проверено: ```text python -m py_compile ``` Статус: ✅ Passed --- ## Проверка отсутствия использования в production Подтверждено: Raw Models используются только: ```text src/market_data/acquisition/adapters/dzengi/models.py tests/unit/market_data/acquisition/adapters/dzengi/test_models.py ``` ExchangeService их не использует. Parser их ещё не использует. Mapper их ещё не использует. Статус: ✅ Passed --- ## Полный набор тестов Выполнено: ```text python -m pytest -q ``` Результат: ```text 11 passed ``` Статус: ✅ Passed --- # Архитектурный результат После Build 002 структура подсистемы стала выглядеть следующим образом: ```text Dzengi REST API │ ▼ Raw Models ``` Следующие слои пока отсутствуют: ```text Parser Mapper Instrument Handler Feed Service ``` Именно это соответствует утверждённому плану миграции. --- # Обратная совместимость Полностью сохранена. Не изменены: - ExchangeService - ExchangeSymbol - SymbolValidationResult - validate_symbol() - normalize_symbol() - symbol_candidates() - Runtime - Telegram UI Новые Raw Models пока не подключены к работающему боту. --- # Итог Build Build 002 завершён успешно. Получен полноценный транспортный контракт REST API Dzengi. Создан фундамент для следующих этапов миграции. Работающий бот не изменил своего поведения. --- # Следующий Build ## Build 003 — Структурная валидация exchangeInfo Следующий этап реализует parser, который будет преобразовывать сырой JSON REST API в созданные транспортные модели. На Build 003 по-прежнему: - ExchangeService изменяться не будет; - предметная модель Instrument использоваться не будет; - parser останется полностью независимым от бизнес-логики.