package processing import ( "bytes" "encoding/json" ) // v1ProcessItemKeyOrder is the human-readable ProcessItem property order. // Important catalog fields first; internal ids / SEO last. var v1ProcessItemKeyOrder = []string{ "ean", "title", "name", "category", "category_id", "category_name", "description", "attributes", "main_image", "more_images", "eprel", "status", "error", "meta_title", "meta_description", } // v1ProcessItemInternalKeys must never appear on public ProcessItem JSON. var v1ProcessItemInternalKeys = map[string]struct{}{ "id": {}, "processed_product_id": {}, "raw_product_id": {}, "field_sources": {}, "enhance_input_hash": {}, "ai_enhance": {}, "finish_reason": {}, "gpt_response": {}, "total_tokens": {}, "prompt_tokens": {}, "completion_tokens": {}, "ai_provider_mode": {}, "notes": {}, "worker": {}, "model": {}, "provider": {}, } func stripV1ProcessItemInternalKeys(item V1ProcessJobItem) { if item == nil { return } for k := range v1ProcessItemInternalKeys { delete(item, k) } } // MarshalJSON emits only the public ProcessItem allowlist in a stable order. // Unexpected keys (including pipeline internals) cannot ship. func (item V1ProcessJobItem) MarshalJSON() ([]byte, error) { if item == nil { return []byte("null"), nil } var buf bytes.Buffer buf.WriteByte('{') first := true writePair := func(k string, v any) error { if !first { buf.WriteByte(',') } first = false kb, err := json.Marshal(k) if err != nil { return err } vb, err := json.Marshal(v) if err != nil { return err } buf.Write(kb) buf.WriteByte(':') buf.Write(vb) return nil } for _, k := range v1ProcessItemKeyOrder { v, ok := item[k] if !ok { continue } if err := writePair(k, v); err != nil { return nil, err } } buf.WriteByte('}') return buf.Bytes(), nil }