Алиас приоритетнее имени при резолве контрагента (#57) #59

Merged
aep merged 1 commits from feat/57-alias-priority into main 2026-10-07 23:36:17 +03:00
Owner

Closes #57

Что сделано

  • CounterpartyIndex (api/src/repositories/counterparties.py): добавлены раздельные словари by_alias (только legal_entity_alias.normalized_alias) и by_normalized_name (только normalize_alias(legal_entity.name)); объединённый by_normalized оставлен (его читает recommended в кандидатах ручного сопоставления).
  • resolve: порядок шагов ИНН -> точное имя -> алиас -> нормализованное имя (OQ-128, §3 п. 4). Единственный владелец алиаса берётся с match_kind = ALIAS, даже при ЮЛ-тёзках по нормализованному имени. NULL остаётся при нескольких совпадениях только по имени без алиаса. counterparty_match_kind (DBML v5.16) не менялся: и алиас, и нормализованное имя по-прежнему дают ALIAS, различать их в БД нечем и не нужно.
  • Одно правило для обоих мест: маппинг (result_mapping._with_counterparty) и дорезолв свода на чтение (consolidated_osv._candidate_ids и domain.consolidated_osv) вызывают один и тот же CounterpartyIndex.resolve.
  • Тесты: test_counterparty_resolve.py (тёзка + алиас, несколько тёзок + алиас, точное имя раньше алиаса, несколько владельцев алиаса; старый тест «тёзка + алиас -> None» заменён), test_api_consolidated_osv.py (дорезолв на чтении: алиас побеждает тёзок, тёзки без алиаса остаются несопоставленными). До правки три новых теста падали.
  • Спеки osv-result-mapping (требование «Резолв контрагента» + сценарий) и consolidated-osv (сценарий дорезолва), README (описание дорезолва).

Проверки

  • make check: ruff + mypy чисто, 1217 passed, 44 skipped (fixtures без ARM_KA_FIXTURES).
  • make check-full (с DBML= и COMPOSE_FILE= абсолютными путями и ARM_KA_FIXTURES, см. O-1): exit 0; unit 1223 passed, 38 skipped; test-integration 821 passed, 1 skipped; dbml-check, check-infra, e2e (skip без профиля services), docker-build, web-check — без ошибок.

Rulings I made

  • R-1: если у одного normalized_alias окажется несколько владельцев (глобальная уникальность алиаса — правило приложения ALIAS_TAKEN, в БД уникален только (legal_entity_id, normalized_alias); возможно через импорт), resolve возвращает NULL, а не выбирает молча и не проваливается к нормализованному имени. Если ошибка: при испорченных данных строка уйдёт на ручное сопоставление вместо автоматического выбора.
  • R-2: объединённый by_normalized сохранён для recommended в кандидатах (OQ-127), там нужны оба источника. Если ошибка: рекомендация перестанет совпадать с резолвом (не затронуто).
  • R-3: тест test_mapping_from_slice_plan_propagates_and_creates_alias (срез 2c/4) обновлён: строка по 76 с ИНН, которого нет в справочнике, раньше после ручного сопоставле��ия оставалась несопоставленной (имя двусмысленно: ЮЛ-владелец и тёзка), теперь дорезолвится по алиасу (ALIAS, match_stored = false, unmapped_total 0 вместо 1). Хранимые значения не меняются (проверка (None, None) сохранена). Это прямое следствие OQ-128, а не регрессия.

Находки

Маркер Что Статус
R-3 / T1.1 Старое ожидание в тесте среза 4 (unmapped_total == 1) противоречило новому правилу fixed 7dcffbd
O-1 make check-full в worktree .wt/<id>/arm-core не находит ../../docs и ../docs/dev/docker-compose.yml (Makefile рассчитан на .wt/<срез>/ с соседним docs); ARM_KA_FIXTURES не выставлен — fixtures-тесты skip deferred (запуск через DBML=…/docs/model/… COMPOSE_FILE=…/docs/dev/docker-compose.yml ARM_KA_FIXTURES=…)

🤖 Generated with Claude Code

https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2

Closes #57 ## Что сделано - `CounterpartyIndex` (`api/src/repositories/counterparties.py`): добавлены раздельные словари `by_alias` (только `legal_entity_alias.normalized_alias`) и `by_normalized_name` (только `normalize_alias(legal_entity.name)`); объединённый `by_normalized` оставлен (его читает `recommended` в кандидатах ручного сопоставления). - `resolve`: порядок шагов ИНН -> точное имя -> **алиас** -> нормализованное имя (OQ-128, §3 п. 4). Единственный владелец алиаса берётся с `match_kind = ALIAS`, даже при ЮЛ-тёзках по нормализованному имени. NULL остаётся при нескольких совпадениях только по имени без алиаса. `counterparty_match_kind` (DBML v5.16) не менялся: и алиас, и нормализованное имя по-прежнему дают `ALIAS`, различать их в БД нечем и не нужно. - Одно правило для обоих мест: маппинг (`result_mapping._with_counterparty`) и дорезолв свода на чтение (`consolidated_osv._candidate_ids` и `domain.consolidated_osv`) вызывают один и тот же `CounterpartyIndex.resolve`. - Тесты: `test_counterparty_resolve.py` (тёзка + алиас, несколько тёзок + алиас, точное имя раньше алиаса, несколько владельцев алиаса; старый тест «тёзка + алиас -> None» заменён), `test_api_consolidated_osv.py` (дорезолв на чтении: алиас побеждает тёзок, тёзки без алиаса остаются несопоставленными). До правки три новых теста падали. - Спеки `osv-result-mapping` (требование «Резолв контрагента» + сценарий) и `consolidated-osv` (сценарий дорезолва), README (описание дорезолва). ## Проверки - `make check`: ruff + mypy чисто, 1217 passed, 44 skipped (fixtures без `ARM_KA_FIXTURES`). - `make check-full` (с `DBML=` и `COMPOSE_FILE=` абсолютными путями и `ARM_KA_FIXTURES`, см. O-1): exit 0; unit 1223 passed, 38 skipped; `test-integration` 821 passed, 1 skipped; dbml-check, check-infra, e2e (skip без профиля `services`), docker-build, web-check — без ошибок. ## Rulings I made - R-1: если у одного `normalized_alias` окажется несколько владельцев (глобальная уникальность алиаса — правило приложения `ALIAS_TAKEN`, в БД уникален только `(legal_entity_id, normalized_alias)`; возможно через импорт), `resolve` возвращает NULL, а не выбирает молча и не проваливается к нормализованному имени. Если ошибка: при испорченных данных строка уйдёт на ручное сопоставление вместо автоматического выбора. - R-2: объединённый `by_normalized` сохранён для `recommended` в кандидатах (OQ-127), там нужны оба источника. Если ошибка: рекомендация перестанет совпадать с резолвом (не затронуто). - R-3: тест `test_mapping_from_slice_plan_propagates_and_creates_alias` (срез 2c/4) обновлён: строка по 76 с ИНН, которого нет в справочнике, раньше после ручного сопоставле��ия оставалась несопоставленной (имя двусмысленно: ЮЛ-владелец и тёзка), теперь дорезолвится по алиасу (`ALIAS`, `match_stored = false`, `unmapped_total` 0 вместо 1). Хранимые значения не меняются (проверка `(None, None)` сохранена). Это прямое следствие OQ-128, а не регрессия. ## Находки | Маркер | Что | Статус | |---|---|---| | R-3 / T1.1 | Старое ожидание в тесте среза 4 (`unmapped_total == 1`) противоречило новому правилу | fixed 7dcffbd | | O-1 | `make check-full` в worktree `.wt/<id>/arm-core` не находит `../../docs` и `../docs/dev/docker-compose.yml` (Makefile рассчитан на `.wt/<срез>/` с соседним `docs`); `ARM_KA_FIXTURES` не выставлен — fixtures-тесты skip | deferred (запуск через `DBML=…/docs/model/… COMPOSE_FILE=…/docs/dev/docker-compose.yml ARM_KA_FIXTURES=…`) | 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
aep added 1 commit 2026-10-07 23:21:39 +03:00
CounterpartyIndex.resolve: порядок шагов ИНН -> точное имя -> алиас -> нормализованное
имя (OQ-128, §3 п. 4). Алиас уникален (ALIAS_TAKEN), поэтому владелец алиаса берётся
с match_kind = ALIAS, даже если есть ЮЛ-тёзки по нормализованному имени; NULL остаётся
при нескольких совпадениях только по имени. Правило общее для маппинга (Срез 2c) и
дорезолва свода на чтение (§3 п. 7).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
aep merged commit 07ccf98f76 into main 2026-10-07 23:36:17 +03:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RSHB/arm-core#59
No description provided.