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

2.8 KiB

BOM Decomposition Pattern Notes

This file keeps examples and reference types. The normative rules live in contract.md.

Canonical JSON Shape

{
  "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

{
  "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

{
  "vendor_spec": [
    {
      "sort_order": 10,
      "vendor_partnumber": "ABC-123",
      "primary_lot": "LOT_CPU",
      "secondary_lots": ["LOT_RAIL"]
    }
  ]
}

Reference Go Types

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

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
}