fix: артикул — категории из component universe + видимость нераспознанных токенов

Генерация артикула резолвила lot_category из одного прайслиста конфигурации
(GetLocalLotCategoriesByServerPricelistID), поэтому world-only LOT (напр.
GPU_NV_RTX_PRO_6000D_SERVER_84GB_PCIE, есть только в world) молча выпадал из
артикула. Теперь категории берутся через GetLocalComponentCategoriesByLotNames
(тот же world ∪ estimate, что и весь конфигуратор). BuildOptions.ServerPricelist
убран; preview-article принимает pricelist_id, но игнорирует.

Нераспознанные токены больше не пишутся как UNK: в артикул идёт lot_category как
плейсхолдер (4xGPU, 2xCPU), сегмент помечается Recognized=false, добавляется
warning с именем LOT. Конфигуратор подсвечивает такие сегменты (amber) и выводит
список предупреждений; сохранение/обновление/откат логируют WARN. Без каталога
вендоров в репо детектируется только структурный сбой формы имени.

parseGPUModel: принимает суффикс-букву в номере модели (6000D → RTX6000D),
раньше терял её и схлопывал до RTX_84GB.

ADL bible-local/decisions/2026-09-01-article-category-from-component-universe.md,
2026-09-01-article-degraded-token-visibility.md; раздел «Article generation» в
02-architecture.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RAhfF4P1ySRZ67yyUUeVw2
This commit is contained in:
Mikhail Chusavitin
2026-09-01 09:01:56 +03:00
co-authored by Claude Sonnet 5
parent ecef030699
commit d7d4ea74b6
11 changed files with 512 additions and 153 deletions
+28
View File
@@ -157,6 +157,34 @@ Rules:
- `config_types[].categories` is an allowlist: a category absent from all types is shown everywhere;
- `qt_categories.name` and `qt_categories.name_ru` are not used by QF runtime; do not depend on them.
## Article generation
`internal/article` builds the `article` string (`{MODEL}-{CPU}-{MEM}-{GPU}-{DISK}-{NET}-{PSU}-{SUPPORT}`)
from the configuration's `items` + `server_model`.
- which segment a cart LOT belongs to is decided by its `lot_category`, resolved through
`ResolveLotCategories``GetLocalComponentCategoriesByLotNames`, i.e. the **component
universe** (latest active `world` `estimate`), the same source the configurator/BOM/pricing
tab use. It must **not** be scoped to the configuration's pinned pricelist: a world-only LOT
(e.g. `GPU_NV_RTX_PRO_6000D_...`, priced by the world fallback, never added to the estimate
pricelist) is a legitimate cart member and still carries a real `lot_category` in `world`.
Scoping to one pricelist silently dropped such LOTs from the article. See
[decisions/2026-09-01-article-category-from-component-universe.md](decisions/2026-09-01-article-category-from-component-universe.md);
- `BuildOptions` no longer takes a pricelist; `POST /api/configs/preview-article` still accepts
`pricelist_id` but ignores it;
- category comes only from real synced pricelist columns — never inferred from the `lot_name`
prefix (the `SVC_` SUPPORT segment is the sole lot_name-pattern exception, see below);
- within a segment the model/capacity/speed **token** is still parsed from the `lot_name`
that is the article text itself, not categorization;
- when a token can't be parsed (the `lot_name` doesn't fit `{GROUP}_{VENDOR}_{MODEL}[_{SPEC}…]`)
the segment carries the LOT's `lot_category` as the token — **never** a bare `UNK`. Such a
segment comes back with `Recognized = false` in `BuildResult.Segments`, plus a `Warnings`
entry naming the `lot_name`. The configurator highlights the segment (amber) and lists the
warnings; create/update/rollback log `WARN "article generation degraded"`. No catalog of real
lot_names / model names lives in the repo (`no-hardcoded-vendors`), so only *structural*
parse failure is detected, not a wrong-but-well-formed token. See
[decisions/2026-09-01-article-degraded-token-visibility.md](decisions/2026-09-01-article-degraded-token-visibility.md).
## Support as a BOM LOT
The Base tab's support-level picker adds/replaces a synthetic LOT in `cart` (e.g.