020 update city code resolving
This commit is contained in:
+6
-5
@@ -1,7 +1,7 @@
|
||||
# Spec Tasks Index
|
||||
|
||||
> ⚠️ This file is generated. Do not edit manually.
|
||||
> Generated at (UTC): `2026-03-18T18:41:29+00:00`
|
||||
> Generated at (UTC): `2026-03-21T07:05:32+00:00`
|
||||
|
||||
## Tasks
|
||||
|
||||
@@ -25,11 +25,12 @@
|
||||
| 015 | DONE | 2026-03-13 | Add minimal OpenTelemetry tracing | `spec/tasks/015_add_minimal_opentelemetry_tracing.md` |
|
||||
| 016 | DONE | 2026-03-14 | Add CDEK order registration adapter | `spec/tasks/016_add_cdek_order_registration_adapter.md` |
|
||||
| 017 | DONE | 2026-03-14 | Return all tariffs from CDEK price calculation | `spec/tasks/017_return_all_cdek_tariffs.md` |
|
||||
| 018 | TODO | 2026-03-16 | Add optional parcel type filter to price request | `spec/tasks/018_add_optional_parcel_type_filter_to_price_request.md` |
|
||||
| 018 | DONE | 2026-03-16 | Add optional parcel type filter to price request | `spec/tasks/018_add_optional_parcel_type_filter_to_price_request.md` |
|
||||
| 019 | TODO | 2026-03-14 | Add CDEK order creation endpoint | `spec/tasks/019_add_cdek_order_creation_endpoint.md` |
|
||||
| 020 | DONE | 2026-03-21 | Rename price request model and use cities_map for CDEK codes | `spec/tasks/020_rename_price_request_model_and_use_cities_map_for_cdek_codes.md` |
|
||||
|
||||
## Summary
|
||||
|
||||
- Total: **20**
|
||||
- TODO: **2**
|
||||
- DONE: **18**
|
||||
- Total: **21**
|
||||
- TODO: **1**
|
||||
- DONE: **20**
|
||||
|
||||
+18
-8
@@ -15,7 +15,7 @@
|
||||
|
||||
## Продуктовые требования
|
||||
|
||||
- Принимать запрос на расчёт стоимости доставки (откуда, куда, вес, габариты)
|
||||
- Принимать запрос на расчёт стоимости доставки (идентификаторы городов отправления/назначения, вес, габариты)
|
||||
- Поддерживать необязательный параметр `parcel_type` в запросе расчёта стоимости доставки для фильтрации тарифов по типу отправления
|
||||
- Принимать запрос на создание заказа CDEK по контракту из `http-client.http` для сценария "доставка, до двери"
|
||||
- Опрашивать всех зарегистрированных провайдеров параллельно
|
||||
@@ -23,6 +23,7 @@
|
||||
- Если провайдер вернул ошибку или не ответил вовремя — исключить его из результата, не падая целиком
|
||||
- Кешировать ответы провайдеров для исключения повторных внешних запросов. Время кэширования вынести в конфиг
|
||||
- Применять к цене, полученной от провайдера, конфигурируемый мультипликатор и округлять итоговую цену до целого значения
|
||||
- Для расчёта стоимости использовать локальный справочник `cities_map` с provider-specific данными города; для CDEK брать `cdek.code` без обращения к API подсказок городов
|
||||
|
||||
---
|
||||
|
||||
@@ -42,14 +43,14 @@
|
||||
## Компоненты
|
||||
|
||||
### Controller (`app/controllers/v1/delivery.py`)
|
||||
- `POST /api/v1/delivery/price` — принимает `DeliveryRequest`, возвращает `list[DeliveryPrice]`
|
||||
- `POST /api/v1/delivery/price` — принимает `DeliveryCalculationRequest`, возвращает `list[DeliveryPrice]`
|
||||
- `POST /api/v1/delivery/order` — принимает `OrderCreateRequest`, возвращает `OrderCreateResponse`
|
||||
- Парсинг и валидация входных данных через Pydantic
|
||||
- Маппинг исключений сервиса в HTTP-ответы
|
||||
- Каждый endpoint вызывает ровно один метод Service: `AggregatorService.get_all_prices()` или `AggregatorService.create_order()`
|
||||
|
||||
### Service (`app/services/aggregator.py`)
|
||||
- `AggregatorService.get_all_prices(request: DeliveryRequest) -> list[DeliveryPrice]`
|
||||
- `AggregatorService.get_all_prices(request: DeliveryCalculationRequest) -> list[DeliveryPrice]`
|
||||
- Распределяет запросы по всем зарегистрированным провайдерам через `asyncio.gather(..., return_exceptions=True)`
|
||||
- Принимает от каждого провайдера список тарифов и объединяет их в единый список
|
||||
- Фильтрует упавшие результаты
|
||||
@@ -64,7 +65,7 @@
|
||||
- Логика сравнения цен
|
||||
- Правила применения конфигурируемого мультипликатора к ценам провайдеров
|
||||
- Округление цены после применения мультипликатора до целого значения по детерминированному правилу
|
||||
- Нормализация входных данных (например, правила округления веса)
|
||||
- Нормализация входных данных (например, идентификаторов городов и правил округления веса)
|
||||
- Чистые функции, без IO, без зависимостей от фреймворков
|
||||
|
||||
### Repository (`app/repositories/cache/`)
|
||||
@@ -77,25 +78,34 @@
|
||||
```python
|
||||
class DeliveryProvider(ABC):
|
||||
name: str
|
||||
async def get_prices(self, request: DeliveryRequest) -> list[DeliveryPrice]: ...
|
||||
async def get_prices(self, request: DeliveryCalculationRequest) -> list[DeliveryPrice]: ...
|
||||
```
|
||||
- `cdek/client.py` — HTTP-клиент (httpx AsyncClient), аутентификация, ретраи, таймаут (10с)
|
||||
- `cdek/auth.py` — управление OAuth2-токеном
|
||||
- `cdek/mapper.py` — ответ CDEK → `list[DeliveryPrice]`
|
||||
- `cdek/order_mapper.py` — request/response mapping для регистрации заказа CDEK
|
||||
- Каждый адаптер владеет своей конфигурацией; наружу экспонирует только service-facing methods, необходимые соответствующему use-case
|
||||
- Для расчёта тарифа CDEK adapter принимает city identifiers из `DeliveryCalculationRequest`, находит запись в `cities_map`, берёт `cdek.code` и передаёт его в CDEK API
|
||||
|
||||
Для сценария создания заказа CDEK adapter принимает валидированную order model, отправляет контракт `Регистрация заказа (тип "доставка", до двери)` из `http-client.http` и возвращает внутреннюю response model без утечки HTTP-деталей в Service.
|
||||
|
||||
---
|
||||
|
||||
## Reference Data
|
||||
|
||||
- `cities_map` содержит city identifier и provider-specific данные города для price flow.
|
||||
- Справочник может быть частично заполнен; отсутствие provider-specific данных для города должно обрабатываться детерминированно.
|
||||
- Структура справочника должна допускать появление дополнительных провайдеров без изменения публичного API расчёта.
|
||||
|
||||
---
|
||||
|
||||
## Модели данных
|
||||
|
||||
### Входная: `DeliveryRequest`
|
||||
### Входная: `DeliveryCalculationRequest`
|
||||
```
|
||||
entity: Enum(individual, legal)
|
||||
from_city: str
|
||||
to_city: str
|
||||
from_city: int
|
||||
to_city: int
|
||||
weight_kg: float
|
||||
length_cm: float
|
||||
width_cm: float
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
id: 020
|
||||
title: Rename price request model and use cities_map for CDEK codes
|
||||
status: DONE
|
||||
created: 2026-03-21
|
||||
---
|
||||
|
||||
## Context
|
||||
Текущий price flow принимает `DeliveryRequest` со строковыми `from_city` и `to_city`, а также использует внешний lookup города через `_resolve_city_code()` в CDEK client. Новый контракт API переходит на идентификаторы городов и локальный справочник `cities_map`, который хранит provider-specific данные города. Для этого изменения нужен только минимальный набор новых валидаций, необходимый для корректного price flow.
|
||||
|
||||
## Goal
|
||||
Переименовать request model расчёта доставки в `DeliveryCalculationRequest`, перевести поля `from_city` и `to_city` на тип `int`, убрать `country_code` из price API и использовать `cities_map` как источник provider city codes для CDEK tariff request вместо `_resolve_city_code()`, добавив только минимально необходимые валидации для этого перехода.
|
||||
|
||||
## Constraints
|
||||
- Scope задачи ограничен price calculation flow: request schema, controller/service wiring, domain normalization, cache key composition, provider contract и CDEK adapter/client.
|
||||
- `POST /api/v1/delivery/order`, `OrderCreateRequest`, `OrderCreateResponse` и `country_code` внутри order payload не изменять.
|
||||
- Controller не добавляет business rules; Service не реализует provider-specific lookup; pure validation и normalization остаются в Business Logic.
|
||||
- Для расчёта CDEK использовать только локальный `cities_map`; внешний city lookup через CDEK API и `_resolve_city_code()` должен быть удалён из client.
|
||||
- Не требовать полноты `cities_map`; отсутствие записи города или provider-specific данных должно обрабатываться детерминированно.
|
||||
- Учитывать provider-specific структуру `cities_map`, но не реализовывать новых providers в рамках этой задачи.
|
||||
- Новые проверки ограничить минимально необходимыми для работы контракта и CDEK lookup: тип city identifier во входной schema и наличие валидного `cdek.code` для используемого города.
|
||||
- Не добавлять предварительную валидацию всего `cities_map`, проверку неиспользуемых provider sections, проверку `city_uuid`, `full_name`, `label`, `country` или иные дополнительные consistency-checks.
|
||||
- Не добавлять новые range/business validations для city identifier сверх тех, что нужны для поиска города в `cities_map`.
|
||||
- Не изменять файлы в `spec/`, кроме этой задачи, `spec/overview.md` и сгенерированного `spec/index.md`.
|
||||
|
||||
## Acceptance criteria
|
||||
- Модель запроса расчёта переименована в `DeliveryCalculationRequest`; controller, service и provider contract для price flow используют новое имя.
|
||||
- Поля `from_city` и `to_city` в price request принимают целочисленные идентификаторы города; поле `country_code` отсутствует в публичном API расчёта доставки.
|
||||
- Domain normalization и service cache key больше не используют `country_code` и корректно работают с city identifiers.
|
||||
- Новые валидации, добавленные этой задачей, ограничены:
|
||||
- schema-level проверкой, что `from_city` и `to_city` передаются как `int`
|
||||
- runtime-проверкой, что для конкретного city identifier, использованного в CDEK price flow, существует валидный `cdek.code`
|
||||
- CDEK client строит payload `from_location.code` и `to_location.code` на основе `cities_map` и значений `cdek.code` для переданных city identifiers.
|
||||
- Если city identifier отсутствует в `cities_map` или для него нет валидного `cdek.code`, CDEK price flow завершается детерминированной provider request error без внешнего city lookup.
|
||||
- Метод `_resolve_city_code()` и связанный HTTP lookup `location/suggest/cities` удалены из CDEK client; price flow не обращается к CDEK API подсказок городов.
|
||||
- Existing order creation flow и order schemas остаются без изменения.
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Обновлены request schema, controller/service/provider type hints и связанные импорты для нового имени `DeliveryCalculationRequest`.
|
||||
- [ ] Удалён `country_code` из price calculation contract, normalization и cache key.
|
||||
- [ ] Реализован lookup CDEK city codes через `cities_map` без внешнего city suggest API.
|
||||
- [ ] Удалён `_resolve_city_code()` и обновлены adapter tests под новое поведение.
|
||||
- [ ] Добавлены или обновлены tests только для минимально необходимой schema validation, service wiring, cache key и CDEK payload mapping/error scenarios.
|
||||
|
||||
## Tests
|
||||
- Обновить `tests/domain/test_price.py` только для проверки, что normalizer корректно принимает и сохраняет city identifiers без `country_code`.
|
||||
- Обновить `tests/services/test_aggregator.py` для проверки нового request model, отсутствия `country_code` в service flow и обновлённого cache key.
|
||||
- Обновить `tests/controllers/v1/test_delivery.py` только для проверки API schema с `from_city`/`to_city` типа `int` и 422 на невалидные значения типа.
|
||||
- Обновить `tests/adapters/delivery_providers/cdek/test_client.py` для проверки построения payload по `cities_map`, ошибок при отсутствии `cdek.code` или city entry и отсутствия city lookup request.
|
||||
- При необходимости обновить `tests/smoke/test_app_import.py` только для совместимости нового request model с app wiring.
|
||||
|
||||
## Commands
|
||||
- `poetry run pytest tests/domain/test_price.py -q`
|
||||
- `poetry run pytest tests/services/test_aggregator.py -q`
|
||||
- `poetry run pytest tests/controllers/v1/test_delivery.py -q`
|
||||
- `poetry run pytest tests/adapters/delivery_providers/cdek/test_client.py -q`
|
||||
- `poetry run pytest tests/smoke/test_app_import.py -q`
|
||||
- `python3 spec/gen_spec_index.py --check`
|
||||
Reference in New Issue
Block a user