|
| 1 | +package feed |
| 2 | + |
| 3 | +import ( |
| 4 | + "strconv" |
| 5 | + "strings" |
| 6 | +) |
| 7 | + |
| 8 | +// Feed is a collection of products n the ACP product feed. |
| 9 | +type Feed []Product |
| 10 | + |
| 11 | +// New creates new feed from a list of products. |
| 12 | +func New(products []Product) Feed { |
| 13 | + return Feed(products) |
| 14 | +} |
| 15 | + |
| 16 | +// Product describes a single product entry in an ACP product feed. |
| 17 | +// Field names follow the feed specification for JSONL and CSV export. |
| 18 | +type Product struct { |
| 19 | + // EnableSearch controls whether the product can be surfaced in ChatGPT search results. |
| 20 | + EnableSearch bool `json:"enable_search" csv:"enable_search"` |
| 21 | + // EnableCheckout allows direct purchase inside ChatGPT when EnableSearch is true. |
| 22 | + EnableCheckout bool `json:"enable_checkout" csv:"enable_checkout"` |
| 23 | + // ID is the merchant product identifier and must remain stable over time. |
| 24 | + ID string `json:"id" csv:"id"` |
| 25 | + // GTIN is a universal product identifier such as GTIN, UPC, or ISBN. |
| 26 | + GTIN string `json:"gtin,omitempty" csv:"gtin"` |
| 27 | + // MPN is the manufacturer part number, required if GTIN is absent. |
| 28 | + MPN string `json:"mpn,omitempty" csv:"mpn"` |
| 29 | + // Title is the product title shown to shoppers. |
| 30 | + Title string `json:"title" csv:"title"` |
| 31 | + // Description is the full product description, plain text. |
| 32 | + Description string `json:"description" csv:"description"` |
| 33 | + // Link is the product detail page URL. |
| 34 | + Link string `json:"link" csv:"link"` |
| 35 | + // Condition is the product condition such as new, refurbished, or used. |
| 36 | + Condition string `json:"condition,omitempty" csv:"condition"` |
| 37 | + // ProductCategory is the taxonomy path using a ">" separator. |
| 38 | + ProductCategory string `json:"product_category" csv:"product_category"` |
| 39 | + // Brand is the product brand name. |
| 40 | + Brand string `json:"brand,omitempty" csv:"brand"` |
| 41 | + // Material describes the primary material. |
| 42 | + Material string `json:"material,omitempty" csv:"material"` |
| 43 | + // Dimensions is the overall size formatted as LxWxH with a unit. |
| 44 | + Dimensions string `json:"dimensions,omitempty" csv:"dimensions"` |
| 45 | + // Length is the individual length dimension with a unit. |
| 46 | + Length string `json:"length,omitempty" csv:"length"` |
| 47 | + // Width is the individual width dimension with a unit. |
| 48 | + Width string `json:"width,omitempty" csv:"width"` |
| 49 | + // Height is the individual height dimension with a unit. |
| 50 | + Height string `json:"height,omitempty" csv:"height"` |
| 51 | + // Weight is the product weight with a unit. |
| 52 | + Weight string `json:"weight,omitempty" csv:"weight"` |
| 53 | + // AgeGroup is the target demographic such as newborn, infant, toddler, kids, or adult. |
| 54 | + AgeGroup string `json:"age_group,omitempty" csv:"age_group"` |
| 55 | + // ImageLink is the main product image URL. |
| 56 | + ImageLink string `json:"image_link" csv:"image_link"` |
| 57 | + // AdditionalImageLink lists extra image URLs (comma-separated in CSV). |
| 58 | + AdditionalImageLink []string `json:"additional_image_link,omitempty" csv:"additional_image_link"` |
| 59 | + // VideoLink is a publicly accessible product video URL. |
| 60 | + VideoLink string `json:"video_link,omitempty" csv:"video_link"` |
| 61 | + // Model3DLink is a 3D model URL (GLB/GLTF preferred). |
| 62 | + Model3DLink string `json:"model_3d_link,omitempty" csv:"model_3d_link"` |
| 63 | + // Price is the regular price with ISO 4217 currency code. |
| 64 | + Price string `json:"price" csv:"price"` |
| 65 | + // SalePrice is the discounted price with currency code. |
| 66 | + SalePrice string `json:"sale_price,omitempty" csv:"sale_price"` |
| 67 | + // SalePriceEffectiveDate is the ISO 8601 sale window date range. |
| 68 | + SalePriceEffectiveDate string `json:"sale_price_effective_date,omitempty" csv:"sale_price_effective_date"` |
| 69 | + // UnitPricingMeasure and BaseMeasure describe unit pricing (both required together). |
| 70 | + UnitPricingMeasure string `json:"unit_pricing_measure,omitempty" csv:"unit_pricing_measure"` |
| 71 | + // BaseMeasure is the base unit used for unit pricing. |
| 72 | + BaseMeasure string `json:"base_measure,omitempty" csv:"base_measure"` |
| 73 | + // PricingTrend is a short string like "Lowest price in N months". |
| 74 | + PricingTrend string `json:"pricing_trend,omitempty" csv:"pricing_trend"` |
| 75 | + // Availability is the stock status: in_stock, out_of_stock, or preorder. |
| 76 | + Availability string `json:"availability" csv:"availability"` |
| 77 | + // AvailabilityDate is the ISO 8601 availability date for preorder items. |
| 78 | + AvailabilityDate string `json:"availability_date,omitempty" csv:"availability_date"` |
| 79 | + // InventoryQuantity is the non-negative stock count. |
| 80 | + InventoryQuantity *int `json:"inventory_quantity,omitempty" csv:"inventory_quantity"` |
| 81 | + // ExpirationDate is the ISO 8601 date to remove the product after. |
| 82 | + ExpirationDate string `json:"expiration_date,omitempty" csv:"expiration_date"` |
| 83 | + // PickupMethod specifies pickup options: in_store, reserve, or not_supported. |
| 84 | + PickupMethod string `json:"pickup_method,omitempty" csv:"pickup_method"` |
| 85 | + // PickupSLA is the pickup service-level agreement, like "1 day". |
| 86 | + PickupSLA string `json:"pickup_sla,omitempty" csv:"pickup_sla"` |
| 87 | + // ItemGroupID groups variants under a canonical product listing. |
| 88 | + ItemGroupID string `json:"item_group_id,omitempty" csv:"item_group_id"` |
| 89 | + // ItemGroupTitle is the title for the variant group. |
| 90 | + ItemGroupTitle string `json:"item_group_title,omitempty" csv:"item_group_title"` |
| 91 | + // Color is the variant color. |
| 92 | + Color string `json:"color,omitempty" csv:"color"` |
| 93 | + // Size is the variant size. |
| 94 | + Size string `json:"size,omitempty" csv:"size"` |
| 95 | + // SizeSystem is the ISO 3166 size system code, such as US. |
| 96 | + SizeSystem string `json:"size_system,omitempty" csv:"size_system"` |
| 97 | + // Gender is the target gender: male, female, or unisex. |
| 98 | + Gender string `json:"gender,omitempty" csv:"gender"` |
| 99 | + // OfferID identifies a specific offer (SKU + seller + price), unique within the feed. |
| 100 | + OfferID string `json:"offer_id,omitempty" csv:"offer_id"` |
| 101 | + // CustomVariant1Category names the first custom variant dimension. |
| 102 | + CustomVariant1Category string `json:"custom_variant1_category,omitempty" csv:"custom_variant1_category"` |
| 103 | + // CustomVariant1Option provides the option value for custom variant 1. |
| 104 | + CustomVariant1Option string `json:"custom_variant1_option,omitempty" csv:"custom_variant1_option"` |
| 105 | + // CustomVariant2Category names the second custom variant dimension. |
| 106 | + CustomVariant2Category string `json:"custom_variant2_category,omitempty" csv:"custom_variant2_category"` |
| 107 | + // CustomVariant2Option provides the option value for custom variant 2. |
| 108 | + CustomVariant2Option string `json:"custom_variant2_option,omitempty" csv:"custom_variant2_option"` |
| 109 | + // CustomVariant3Category names the third custom variant dimension. |
| 110 | + CustomVariant3Category string `json:"custom_variant3_category,omitempty" csv:"custom_variant3_category"` |
| 111 | + // CustomVariant3Option provides the option value for custom variant 3. |
| 112 | + CustomVariant3Option string `json:"custom_variant3_option,omitempty" csv:"custom_variant3_option"` |
| 113 | + // Shipping lists shipping entries in country:region:service_class:price format. |
| 114 | + Shipping []string `json:"shipping,omitempty" csv:"shipping"` |
| 115 | + // DeliveryEstimate is the ISO 8601 estimated arrival date. |
| 116 | + DeliveryEstimate string `json:"delivery_estimate,omitempty" csv:"delivery_estimate"` |
| 117 | + // SellerName is the merchant display name. |
| 118 | + SellerName string `json:"seller_name" csv:"seller_name"` |
| 119 | + // SellerURL is the merchant storefront URL. |
| 120 | + SellerURL string `json:"seller_url" csv:"seller_url"` |
| 121 | + // SellerPrivacyPolicy is the seller-specific privacy policy URL. |
| 122 | + SellerPrivacyPolicy string `json:"seller_privacy_policy,omitempty" csv:"seller_privacy_policy"` |
| 123 | + // SellerTOS is the seller-specific terms of service URL. |
| 124 | + SellerTOS string `json:"seller_tos,omitempty" csv:"seller_tos"` |
| 125 | + // ReturnPolicy is the return policy URL. |
| 126 | + ReturnPolicy string `json:"return_policy,omitempty" csv:"return_policy"` |
| 127 | + // ReturnWindow is the number of days allowed for returns. |
| 128 | + ReturnWindow *int `json:"return_window,omitempty" csv:"return_window"` |
| 129 | + // PopularityScore is a popularity indicator (for example, 0-5 scale). |
| 130 | + PopularityScore *float64 `json:"popularity_score,omitempty" csv:"popularity_score"` |
| 131 | + // ReturnRate is the percentage of returns, 0-100%. |
| 132 | + ReturnRate string `json:"return_rate,omitempty" csv:"return_rate"` |
| 133 | + // Warning is a product disclaimer or regulatory warning. |
| 134 | + Warning string `json:"warning,omitempty" csv:"warning"` |
| 135 | + // WarningURL links to warning details and must resolve. |
| 136 | + WarningURL string `json:"warning_url,omitempty" csv:"warning_url"` |
| 137 | + // AgeRestriction is the minimum purchase age. |
| 138 | + AgeRestriction *int `json:"age_restriction,omitempty" csv:"age_restriction"` |
| 139 | + // ProductReviewCount is the number of product reviews. |
| 140 | + ProductReviewCount *int `json:"product_review_count,omitempty" csv:"product_review_count"` |
| 141 | + // ProductReviewRating is the average product review score. |
| 142 | + ProductReviewRating *float64 `json:"product_review_rating,omitempty" csv:"product_review_rating"` |
| 143 | + // StoreReviewCount is the number of brand or store reviews. |
| 144 | + StoreReviewCount *int `json:"store_review_count,omitempty" csv:"store_review_count"` |
| 145 | + // StoreReviewRating is the average brand or store rating. |
| 146 | + StoreReviewRating *float64 `json:"store_review_rating,omitempty" csv:"store_review_rating"` |
| 147 | + // QAndA is FAQ content in plain text. |
| 148 | + QAndA string `json:"q_and_a,omitempty" csv:"q_and_a"` |
| 149 | + // RawReviewData contains raw review payloads and may include JSON blobs. |
| 150 | + RawReviewData string `json:"raw_review_data,omitempty" csv:"raw_review_data"` |
| 151 | + // RelatedProductID lists associated product IDs (comma-separated in CSV). |
| 152 | + RelatedProductID []string `json:"related_product_id,omitempty" csv:"related_product_id"` |
| 153 | + // RelationshipType describes how related products connect (for example, part_of_set). |
| 154 | + RelationshipType string `json:"relationship_type,omitempty" csv:"relationship_type"` |
| 155 | + // GeoPrice lists country-specific prices using ISO 3166-1 country codes. |
| 156 | + GeoPrice []string `json:"geo_price,omitempty" csv:"geo_price"` |
| 157 | + // GeoAvailability lists country-specific availability using ISO 3166-1 country codes. |
| 158 | + GeoAvailability []string `json:"geo_availability,omitempty" csv:"geo_availability"` |
| 159 | +} |
| 160 | + |
| 161 | +// joinStrings combines list values using a comma, returning empty for nil slices. |
| 162 | +func joinStrings(values []string) string { |
| 163 | + if len(values) == 0 { |
| 164 | + return "" |
| 165 | + } |
| 166 | + return strings.Join(values, ",") |
| 167 | +} |
| 168 | + |
| 169 | +// formatInt converts an optional int to a string, returning empty when nil. |
| 170 | +func formatInt(value *int) string { |
| 171 | + if value == nil { |
| 172 | + return "" |
| 173 | + } |
| 174 | + return strconv.Itoa(*value) |
| 175 | +} |
| 176 | + |
| 177 | +// formatFloat converts an optional float to a string, returning empty when nil. |
| 178 | +func formatFloat(value *float64) string { |
| 179 | + if value == nil { |
| 180 | + return "" |
| 181 | + } |
| 182 | + return strconv.FormatFloat(*value, 'f', -1, 64) |
| 183 | +} |
0 commit comments