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
+3 -1
View File
@@ -40,5 +40,7 @@ type ArticlePreviewRequest struct {
Items models.ConfigItems `json:"items"`
ServerModel string `json:"server_model"`
SupportCode string `json:"support_code,omitempty"`
PricelistID *uint `json:"pricelist_id,omitempty"`
// PricelistID is accepted for backward compatibility but ignored: article
// categories come from the component universe (world estimate), not one pricelist.
PricelistID *uint `json:"pricelist_id,omitempty"`
}
+24 -12
View File
@@ -70,13 +70,18 @@ func (s *LocalConfigurationService) Create(ownerUsername string, req *CreateConf
if strings.TrimSpace(req.ServerModel) != "" {
articleResult, articleErr := article.Build(s.localDB, req.Items, article.BuildOptions{
ServerModel: req.ServerModel,
ServerPricelist: pricelistID,
ServerModel: req.ServerModel,
})
if articleErr != nil {
return nil, articleErr
}
req.Article = articleResult.Article
if len(articleResult.Warnings) > 0 {
slog.Warn("article generation degraded",
"server_model", req.ServerModel,
"article", articleResult.Article,
"warnings", articleResult.Warnings)
}
}
total := req.Items.Total()
@@ -166,13 +171,18 @@ func (s *LocalConfigurationService) Update(uuid string, ownerUsername string, re
if strings.TrimSpace(req.ServerModel) != "" {
articleResult, articleErr := article.Build(s.localDB, req.Items, article.BuildOptions{
ServerModel: req.ServerModel,
ServerPricelist: pricelistID,
ServerModel: req.ServerModel,
})
if articleErr != nil {
return nil, articleErr
}
req.Article = articleResult.Article
if len(articleResult.Warnings) > 0 {
slog.Warn("article generation degraded",
"server_model", req.ServerModel,
"article", articleResult.Article,
"warnings", articleResult.Warnings)
}
}
total := req.Items.Total()
@@ -216,13 +226,10 @@ func (s *LocalConfigurationService) Update(uuid string, ownerUsername string, re
// BuildArticlePreview generates server article based on current items and server_model/support_code.
func (s *LocalConfigurationService) BuildArticlePreview(req *ArticlePreviewRequest) (article.BuildResult, error) {
pricelistID, err := s.resolvePricelistID(req.PricelistID)
if err != nil {
return article.BuildResult{}, err
}
// Categories for the article come from the component universe (world estimate),
// not from a specific pricelist, so req.PricelistID is no longer needed here.
return article.Build(s.localDB, req.Items, article.BuildOptions{
ServerModel: req.ServerModel,
ServerPricelist: pricelistID,
ServerModel: req.ServerModel,
})
}
@@ -527,13 +534,18 @@ func (s *LocalConfigurationService) UpdateNoAuth(uuid string, req *CreateConfigR
if strings.TrimSpace(req.ServerModel) != "" {
articleResult, articleErr := article.Build(s.localDB, req.Items, article.BuildOptions{
ServerModel: req.ServerModel,
ServerPricelist: pricelistID,
ServerModel: req.ServerModel,
})
if articleErr != nil {
return nil, articleErr
}
req.Article = articleResult.Article
if len(articleResult.Warnings) > 0 {
slog.Warn("article generation degraded",
"server_model", req.ServerModel,
"article", articleResult.Article,
"warnings", articleResult.Warnings)
}
}
total := req.Items.Total()