Files
bible/rules/patterns/bom-decomposition/README.md
2026-04-02 13:48:36 +03:00

118 lines
2.8 KiB
Markdown

# BOM Decomposition Pattern Notes
This file keeps examples and reference types. The normative rules live in `contract.md`.
## Canonical JSON Shape
```json
{
"sort_order": 10,
"item_code": "SYS-821GE-TNHR",
"quantity": 3,
"description": "Vendor bundle",
"unit_price": 12000.00,
"total_price": 36000.00,
"component_mappings": [
{ "component_ref": "CHASSIS_X13_8GPU", "quantity_per_item": 1 },
{ "component_ref": "PS_3000W_Titanium", "quantity_per_item": 2 },
{ "component_ref": "RAILKIT_X13", "quantity_per_item": 1 }
]
}
```
Project-specific aliases are acceptable if the semantics stay identical:
- `item_code` -> `vendor_partnumber`
- `component_ref` -> `lot_name`
- `component_mappings` -> `lot_mappings`
- `quantity_per_item` -> `quantity_per_pn`
## Persistence Example
```json
{
"vendor_spec": [
{
"sort_order": 10,
"vendor_partnumber": "ABC-123",
"quantity": 2,
"description": "Bundle",
"lot_mappings": [
{ "lot_name": "LOT_CPU", "quantity_per_pn": 1 },
{ "lot_name": "LOT_RAIL", "quantity_per_pn": 1 }
]
}
]
}
```
## Wrong Shape
```json
{
"vendor_spec": [
{
"sort_order": 10,
"vendor_partnumber": "ABC-123",
"primary_lot": "LOT_CPU",
"secondary_lots": ["LOT_RAIL"]
}
]
}
```
## Reference Go Types
```go
type BOMItem struct {
SortOrder int `json:"sort_order"`
ItemCode string `json:"item_code"`
Quantity int `json:"quantity"`
Description string `json:"description,omitempty"`
UnitPrice *float64 `json:"unit_price,omitempty"`
TotalPrice *float64 `json:"total_price,omitempty"`
ComponentMappings []ComponentMapping `json:"component_mappings,omitempty"`
}
type ComponentMapping struct {
ComponentRef string `json:"component_ref"`
QuantityPerItem int `json:"quantity_per_item"`
}
```
## Normalization Sketch
```go
func NormalizeComponentMappings(in []ComponentMapping) ([]ComponentMapping, error) {
if len(in) == 0 {
return nil, nil
}
merged := map[string]int{}
order := make([]string, 0, len(in))
for _, m := range in {
ref := strings.TrimSpace(m.ComponentRef)
if ref == "" {
continue
}
if m.QuantityPerItem <= 0 {
return nil, fmt.Errorf("component %q has invalid quantity_per_item %d", ref, m.QuantityPerItem)
}
if _, exists := merged[ref]; !exists {
order = append(order, ref)
}
merged[ref] += m.QuantityPerItem
}
out := make([]ComponentMapping, 0, len(order))
for _, ref := range order {
out = append(out, ComponentMapping{
ComponentRef: ref,
QuantityPerItem: merged[ref],
})
}
return out, nil
}
```