Files
QuoteForge/bible-local/decisions/2026-09-01-article-category-from-component-universe.md
T
Mikhail ChusavitinandClaude Sonnet 5 d7d4ea74b6 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
2026-09-01 09:01:56 +03:00

3.2 KiB
Raw Blame History

Decision: article generation reads lot_category from the component universe, not one pricelist

Date: 2026-09-01 Status: active

Context

internal/article/generator.go classifies each cart LOT into an article segment (CPU / MEM / GPU / DISK / NET / PSU) by its lot_category. That category was resolved by ResolveLotCategoriesGetLocalLotCategoriesByServerPricelistID, which queries exactly one pricelist — the configuration's pinned pricelist_id (an estimate pricelist). A segment builder skips any LOT whose category is unknown, so a LOT absent from that one pricelist vanished from the article with no warning.

GPU_NV_RTX_PRO_6000D_SERVER_84GB_PCIE exposed this: it exists only in world pricelists (where it correctly carries lot_category = GPU) and in no estimate pricelist. It was priced (world fallback), exported, and shown in the cart, but dropped from the article.

This is the same class of bug as 2026-07-24-component-universe-world-union.md: the cart's LOT set is world estimate, and no read path may silently drop a LOT that only lives in world. Article generation was the one component-reading path still scoped to a single pricelist.

Deriving the category from the lot_name prefix was rejected — it is forbidden by the pricelist-contract rules (02-architecture.md, bible/rules/patterns/): category comes only from real synced pricelist columns.

Decision

ResolveLotCategories(local, lotNames) now calls LocalDB.GetLocalComponentCategoriesByLotNames, which reads the component universe (componentUniverse() — latest active world estimate, estimate wins a collision). The server-pricelist parameter is gone.

  • article.BuildOptions no longer has ServerPricelist.
  • POST /api/configs/preview-article and the create/update paths still accept pricelist_id but ignore it for article purposes.
  • The lot_name-token parsing inside a segment (CPU model, memory size, GPU model, disk capacity, port speed, wattage) still parses the token from the lot_name — that produces the article text, it does not categorize. One parser bug was fixed alongside: parseGPUModel required the model-number token to be all digits (isNumeric), so RTX_PRO_6000D lost its D and collapsed to RTX_84GB — indistinguishable from a real RTX card and different from its sibling RTX_PRO_6000RTX6000_96GB. It now accepts a digit-first alphanumeric token (isModelNumber), giving RTX6000D_84GB.

Consequences

  • A world-only LOT lands in the article with its real category. Article generation no longer depends on which pricelist a LOT was added to.
  • The article can shift if the latest active world/estimate pricelist recategorizes a LOT — acceptable, and consistent with how the configurator's category tabs already behave.
  • GetLocalLotCategoriesByServerPricelistID still exists and is still used by services/rental.go and services/export.go. Those paths have the same single-pricelist blind spot for world-only LOTs; migrating them is out of scope here but should follow the same direction.
  • Covered by TestResolveLotCategories_WorldOnlyLot in internal/article/categories_test.go.