Files
QuoteForge/internal/article/categories.go
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

88 lines
2.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package article
import (
"fmt"
"strings"
"git.mchus.pro/mchus/quoteforge/internal/localdb"
)
type Group string
const (
GroupCPU Group = "CPU"
GroupMEM Group = "MEM"
GroupGPU Group = "GPU"
GroupDISK Group = "DISK"
GroupNET Group = "NET"
GroupPSU Group = "PSU"
)
// GroupForLotCategory maps pricelist lot_category codes into article groups.
// Unknown/unrelated categories return ok=false.
func GroupForLotCategory(cat string) (group Group, ok bool) {
c := strings.ToUpper(strings.TrimSpace(cat))
switch c {
case "CPU":
return GroupCPU, true
case "MEM":
return GroupMEM, true
case "GPU":
return GroupGPU, true
case "M2", "SSD", "HDD", "EDSFF", "HHHL":
return GroupDISK, true
case "NIC", "HCA", "DPU":
return GroupNET, true
case "HBA":
return GroupNET, true
case "PSU", "PS":
return GroupPSU, true
default:
return "", false
}
}
// ResolveLotCategories returns lot_category for each lotName, read from the component
// universe (latest active world estimate pricelists — the same source the configurator,
// BOM and pricing tab use, see bible-local/decisions/2026-07-24-component-universe-world-union.md).
// It must NOT be scoped to a single pricelist: a world-only LOT is a legitimate cart member
// and still carries a real lot_category in the world pricelist. LOTs present in neither
// pricelist are omitted — callers treat a missing key as "no category" and skip that lot.
func ResolveLotCategories(local *localdb.LocalDB, lotNames []string) (map[string]string, error) {
if local == nil {
return nil, fmt.Errorf("local db is nil")
}
cats, err := local.GetLocalComponentCategoriesByLotNames(lotNames)
if err != nil {
return nil, err
}
for lot, cat := range cats {
cats[lot] = strings.TrimSpace(cat)
}
return cats, nil
}
// NormalizeServerModel produces a stable article segment for the server model.
func NormalizeServerModel(model string) string {
trimmed := strings.TrimSpace(model)
if trimmed == "" {
return ""
}
upper := strings.ToUpper(trimmed)
var b strings.Builder
for _, r := range upper {
if r >= 'A' && r <= 'Z' {
b.WriteRune(r)
continue
}
if r >= '0' && r <= '9' {
b.WriteRune(r)
continue
}
if r == '.' {
b.WriteRune(r)
}
}
return b.String()
}