# Build 025 — Удаление legacy `ExchangeSymbol` compatibility layer **Статус:** ✅ Завершён **Дата:** 2026-07-12 **Подсистема:** Instrument Reference / Exchange Integration **Этап:** Завершение миграции на каноническую модель `Instrument` --- # Цель Build Полностью удалить временный compatibility layer, использовавшийся во время поэтапного перехода от legacy-модели: ```text ExchangeSymbol ``` к канонической модели: ```text Instrument ``` После завершения Build все production-потребители работают через единый канонический контур: ```text Instrument Acquisition ↓ Instrument Store ↓ get_instruments() ↓ validate_symbol() ↓ Instrument ``` --- # Предпосылки На предыдущих этапах были выполнены: - создание канонической модели `Instrument`; - построение Instrument Acquisition pipeline; - создание `InstrumentStoreProtocol`; - реализация `InMemoryInstrumentStore`; - перенос кэша справочника инструментов в Storage; - добавление `ExchangeService.get_instruments()`; - перевод `validate_symbol()` на канонический справочник; - перевод Telegram UI и runtime-потребителей на `Instrument`; - удаление неиспользуемого legacy Market Handler. После этого legacy-контур сохранялся только как временная проекция: ```text Instrument ↓ compatibility.py ↓ ExchangeSymbol ↓ get_exchange_symbols() ``` Реальных production-потребителей этого контура больше не осталось. --- # Предварительный аудит Выполнен поиск production-зависимостей: ```bash grep -RIn \ --exclude-dir="__pycache__" \ --exclude="*.pyc" \ -E "ExchangeSymbol|get_exchange_symbols|map_instrument_to_exchange_symbol|map_instruments_to_exchange_symbols|_exchange_symbols_projection_cache" \ src ``` Установлено, что все найденные элементы находились только внутри самого legacy-контура: ```text src/market_data/acquisition/compatibility.py src/integrations/exchange/models.py src/integrations/exchange/service.py ``` Канонические production-потребители уже использовали: ```text get_instruments() validate_symbol() Instrument ``` --- # Аудит legacy parser helpers Проверены методы: ```text _extract_exchange_symbols_raw() _parse_exchange_symbol() _parse_exchange_symbol_status() _parse_market_modes() _extract_filter_value() ``` Выполнено: ```bash grep -RIn \ --exclude-dir="__pycache__" \ --exclude="*.pyc" \ -E "_extract_exchange_symbols_raw|_parse_exchange_symbol\(|_parse_exchange_symbol_status|_parse_market_modes|_extract_filter_value" \ src tests ``` Подтверждено, что эти методы использовались только устаревшим equivalence-тестом и больше не требовались production-коду. --- # Подготовка тестового контура Перед удалением production compatibility layer были обновлены канонические тесты. ## Обновлён файл ```text tests/unit/integrations/exchange/test_service_instruments.py ``` Из него удалены тесты legacy-проекции: ```text test_get_exchange_symbols_uses_get_instruments test_get_exchange_symbols_maps_canonical_instruments test_get_exchange_symbols_projection_cache_preserves_identity ``` Сохранены все тесты канонического поведения: - отключённая биржа; - Instrument Store hit; - Instrument Store miss; - загрузка через acquisition; - сохранение результата в Store; - повторное использование Store; - поддержка пустого immutable-набора; - общий Store между экземплярами `ExchangeService`; - обработка acquisition errors; - логирование ошибок; - отсутствие заполнения Store при ошибке. --- ## Обновлён файл ```text tests/unit/integrations/exchange/test_service_validate_symbol.py ``` Legacy-проверка через monkeypatch метода: ```text get_exchange_symbols() ``` заменена проверкой прямого использования: ```text get_instruments() ``` Дополнительно добавлен отрицательный архитектурный тест: ```python def test_exchange_service_has_no_legacy_get_exchange_symbols() -> None: assert not hasattr( ExchangeService, "get_exchange_symbols", ) ``` --- ## Обновлён файл ```text tests/unit/telegram/ui/test_currency_ui.py ``` Legacy-проверка отсутствия вызова `get_exchange_symbols()` заменена прямой проверкой использования канонического: ```text get_instruments() ``` --- # Проверка подготовительного пакета Выполнена компиляция: ```bash python -m py_compile \ tests/unit/integrations/exchange/test_service_instruments.py \ tests/unit/integrations/exchange/test_service_validate_symbol.py \ tests/unit/telegram/ui/test_currency_ui.py ``` Результат: ```text Ошибок нет. ``` Выполнены целевые тесты: ```bash python -m pytest \ tests/unit/integrations/exchange/test_service_instruments.py \ tests/unit/integrations/exchange/test_service_validate_symbol.py \ tests/unit/telegram/ui/test_currency_ui.py \ -q ``` Результат: ```text 49 passed ``` --- # Удалённые migration-only файлы Полностью удалены: ```text src/market_data/acquisition/compatibility.py tests/unit/market_data/acquisition/test_compatibility.py tests/unit/market_data/acquisition/test_equivalence_comparator.py tests/unit/integrations/exchange/test_service_exchange_symbols.py tests/integration/market_data/acquisition/test_instrument_reference_equivalence.py tests/support/instrument_reference_equivalence.py ``` Эти файлы обслуживали только временный compatibility/equivalence-контур и завершили свою миграционную задачу. --- # Изменения в `models.py` Из файла: ```text src/integrations/exchange/models.py ``` полностью удалена legacy-модель: ```python @dataclass(slots=True) class ExchangeSymbol: ... ``` Модель: ```python SymbolValidationResult ``` сохраняет канонический контракт: ```python symbol_info: Instrument | None ``` Импорт `Instrument` выполняется через: ```python TYPE_CHECKING ``` что исключает runtime-cycle и сохраняет корректную типизацию. --- # Изменения в `service.py` Из файла: ```text src/integrations/exchange/service.py ``` удалены следующие элементы. ## Legacy imports Удалены: ```python ExchangeSymbol ``` и: ```python from src.market_data.acquisition.compatibility import ( map_instruments_to_exchange_symbols, ) ``` --- ## Legacy projection cache Удалено поле: ```python _exchange_symbols_projection_cache ``` Теперь `ExchangeService` хранит только канонический Store: ```python _instrument_store: InstrumentStoreProtocol = InMemoryInstrumentStore() ``` --- ## Legacy API Полностью удалён метод: ```python get_exchange_symbols() ``` Единственным публичным API справочника инструментов остаётся: ```python get_instruments() ``` --- ## Legacy parser helpers Удалены методы: ```text _extract_exchange_symbols_raw() _parse_exchange_symbol() _parse_exchange_symbol_status() _parse_market_modes() _extract_filter_value() ``` Их функции полностью заменены новой pipeline: ```text Dzengi REST document ↓ Dzengi parser ↓ validation ↓ mapper ↓ Instrument ``` --- ## Сохранённый helper Метод: ```python _safe_str() ``` сохранён, так как продолжает использоваться обработкой trading fee payload. --- # Финальный production-контур После удаления compatibility layer работа со справочником инструментов выполняется так: ```text DzengiInstrumentDocumentSource ↓ DzengiInstrumentDocumentHandler ↓ InstrumentFeed ↓ InstrumentFeedRegistry ↓ InstrumentAcquisitionService ↓ tuple[Instrument, ...] ↓ Instrument Store ↓ ExchangeService.get_instruments() ``` Проверка пользовательского символа выполняется через: ```text validate_symbol() ↓ SymbolValidationResult ↓ symbol_info: Instrument | None ``` --- # Архитектурные отрицательные тесты Добавлены проверки физического отсутствия legacy API. ## Отсутствие legacy-метода ```python def test_exchange_service_has_no_legacy_get_exchange_symbols() -> None: assert not hasattr( ExchangeService, "get_exchange_symbols", ) ``` ## Отсутствие legacy projection cache ```python def test_exchange_service_has_no_legacy_projection_cache() -> None: assert not hasattr( ExchangeService, "_exchange_symbols_projection_cache", ) ``` --- # Финальный аудит Выполнено: ```bash grep -RIn \ --exclude-dir="__pycache__" \ --exclude="*.pyc" \ -E "ExchangeSymbol|get_exchange_symbols|map_instrument_to_exchange_symbol|map_instruments_to_exchange_symbols|_exchange_symbols_projection_cache|market_data\.acquisition\.compatibility" \ src tests ``` Остались только ожидаемые упоминания в отрицательных архитектурных тестах: ```text test_exchange_service_has_no_legacy_get_exchange_symbols test_exchange_service_has_no_legacy_projection_cache ``` Других production- или test-зависимостей не обнаружено. --- # Проверка компиляции Выполнено: ```bash python -m py_compile \ src/integrations/exchange/models.py \ src/integrations/exchange/service.py \ tests/unit/integrations/exchange/test_service_instruments.py \ tests/unit/integrations/exchange/test_service_validate_symbol.py \ tests/unit/telegram/ui/test_currency_ui.py ``` Результат: ```text Ошибок нет. ``` --- # Целевой Regression Suite Выполнено: ```bash python -m pytest \ tests/unit/integrations/exchange/test_service_instruments.py \ tests/unit/integrations/exchange/test_service_validate_symbol.py \ tests/unit/integrations/exchange/test_service_symbol_runtime_status.py \ tests/unit/integrations/exchange/test_market_stream.py \ tests/unit/integrations/exchange/test_market_data_runner.py \ tests/unit/telegram/ui/test_currency_ui.py \ -q ``` Результат: ```text 94 passed ``` --- # Полный Regression Suite Выполнено: ```bash python -m pytest -q ``` Результат: ```text 423 passed ``` Снижение общего числа тестов относительно предыдущего Build является ожидаемым, поскольку были удалены временные compatibility- и equivalence-тесты вместе с соответствующим legacy-кодом. --- # Архитектурный итог До Build 025: ```text Instrument ├── canonical consumers └── compatibility mapper ↓ ExchangeSymbol ↓ legacy projection cache ``` После Build 025: ```text Instrument ↓ Instrument Store ↓ canonical consumers ``` В проекте больше не существует: ```text ExchangeSymbol compatibility.py get_exchange_symbols() _exchange_symbols_projection_cache Instrument → ExchangeSymbol mapping legacy exchangeInfo parser helpers migration equivalence framework ``` --- # Итог Build Build 025 полностью завершён. Подтверждено: - все production-потребители переведены на `Instrument`; - удалена legacy-модель `ExchangeSymbol`; - удалён временный compatibility mapper; - удалён legacy-метод `get_exchange_symbols()`; - удалён projection cache; - удалены неиспользуемые parser helpers; - удалены migration-only и equivalence-тесты; - сохранены и усилены канонические тесты Instrument Store; - добавлены архитектурные тесты отсутствия legacy API; - компиляция проходит без ошибок; - целевой Regression Suite успешно пройден — **94 passed**; - полный Regression Suite успешно пройден — **423 passed**. **Статус Build:** ✅ Завершён.