fix
This commit is contained in:
@@ -123,6 +123,27 @@ info:
|
||||
processed_product_id / raw_product_id). Use GET /products when a UUID is needed.
|
||||
Display name is items[].name (title omitted when identical).
|
||||
|
||||
### AI descriptions (HTML body, not a short blurb)
|
||||
|
||||
Default processing_type full runs normalize, parse specs, fill fields, EPREL
|
||||
(when enabled), categorize, then AI enhance. Enhance writes the display name
|
||||
and a full product body.
|
||||
|
||||
Completed items[].description is that body: one HTML string (tags matching
|
||||
section types h1/h2/h3/h4, p, ul) — never a JSON array and never a one- or
|
||||
two-sentence slogan. Length and structure come from the assigned category
|
||||
description_template (description formula): ordered sections with their own
|
||||
instructions. A formula can ask for a short paragraph, 100-word blocks,
|
||||
several heading/list sections, or a very large multi-section article. There
|
||||
is no request-body size enum; configure length on the category. When a
|
||||
category has no formula, the default is still 1-3 factual paragraphs with
|
||||
limited HTML, not a headline-only stub.
|
||||
|
||||
items[].description on the start body is optional source text the model may
|
||||
rewrite. Poll GET /products/process/{id} for the generated HTML.
|
||||
meta_title / meta_description are separate short SEO fields and may be
|
||||
omitted depending on company configuration.
|
||||
|
||||
Note: Dashboard JSON under /api/* uses session cookies + CSRF and is separate
|
||||
from this public API-key surface. Other path aliases
|
||||
(/categories/create, /attributes/create, /campaigns) appear next to canonical paths.
|
||||
@@ -674,7 +695,10 @@ paths:
|
||||
description: Enriched display title
|
||||
processed_description:
|
||||
type: string
|
||||
description: Enriched long description text
|
||||
description: |
|
||||
Enriched product body as one HTML string from the category
|
||||
description formula. Length follows that formula (including very
|
||||
large multi-section copy), not a short summary.
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
@@ -788,14 +812,23 @@ paths:
|
||||
- Processing
|
||||
summary: Start processing by EAN
|
||||
description: |
|
||||
Public process API (handleV1StartProcess). Upserts raw products from
|
||||
items[].ean (GTIN), enqueues one processing job, returns HTTP 200 with a data envelope.
|
||||
Public process API. Upserts raw products from items[].ean (GTIN), enqueues
|
||||
one processing job, returns HTTP 200 with a data envelope.
|
||||
|
||||
Default processing_type full runs the full pipeline including AI enhance:
|
||||
the model writes a retail title (name) and a full HTML product description
|
||||
driven by the category description formula. That body can be long —
|
||||
multiple headings, paragraphs, and lists, including very large
|
||||
multi-section articles — not a one- or two-sentence summary.
|
||||
|
||||
Primary body: items[].ean. Alternate body on the same handler: raw_product_ids
|
||||
(raw_products.id UUID list — not GET /products data[].id). Prefer items for public
|
||||
integrations. On items[].ean, assertV1ProcessGates runs before EnsureRaw so plan/credit
|
||||
failures cannot spam catalog writes (HTTP 402 coded envelope).
|
||||
|
||||
Poll GET /products/process/{id} until status is COMPLETED, then read
|
||||
items[].description for the generated HTML.
|
||||
|
||||
Not an alias of POST /process (flat ProcessingJob / 202). Do not mix envelopes.
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/StartProcessByEAN"
|
||||
@@ -903,9 +936,13 @@ paths:
|
||||
jobs omit items. Not the same shape as GET /process/{id} (flat ProcessingJob).
|
||||
|
||||
On COMPLETED items, name is the product display name (title is omitted when
|
||||
identical). Internal UUIDs (id / processed_product_id / raw_product_id) are
|
||||
omitted — use GET /products for those. Optional meta_title and
|
||||
meta_description may be omitted depending on company configuration.
|
||||
identical). description is the generated product body: one HTML string
|
||||
following the category description formula (h1/h2/h3/h4, p, ul). Expect
|
||||
real catalog copy — often several paragraphs and lists, sometimes a very
|
||||
large article — not a one- or two-sentence blurb. Internal UUIDs (id /
|
||||
processed_product_id / raw_product_id) are omitted — use GET /products for
|
||||
those. Optional meta_title and meta_description are short SEO fields and
|
||||
may be omitted depending on company configuration.
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
@@ -938,12 +975,13 @@ paths:
|
||||
category_id: '50'
|
||||
category_name: Cookers
|
||||
name: VOX electric cooker EHT 6020 WG
|
||||
description: "Vox Electronics electric cooker EHT 6020 WG offers strong value with four electric hobs and a 65 L fan oven."
|
||||
description: "<h1>VOX electric cooker EHT 6020 WG</h1><p>The VOX EHT 6020 WG is a free-standing electric cooker for everyday household cooking. Four ceramic hobs and a 65 L fan oven let you boil, fry, and bake at the same time, while energy class A helps keep running costs down. The 60 cm width fits a standard kitchen niche. An enamel oven cavity and removable inner door glass are designed for cleaning after heavy use.</p><p>Run sauces or pasta on the hob while the oven handles a roast or tray bake. Heat is even across the ceramic zones, and residual-heat indicators reduce the chance of accidental contact after cooking. Fan mode suits multi-level baking; conventional heat remains available for dishes that prefer still air. A full-width grill finishes cheese, gratins, and toast.</p><h2>Key features</h2><ul><li>Four electric ceramic hobs with residual-heat indicators</li><li>65 L fan oven plus grill, energy class A</li><li>60 cm free-standing body (0.6 m wide, 0.85 m high, 0.6 m deep)</li><li>Enamel cavity and removable inner door glass</li><li>60-month manufacturer warranty</li></ul><p>Install in a standard 60 cm cooker slot with the dedicated electric supply shown on the rating plate. Leave rear clearance for ventilation and use a heat-resistant worktop next to the hob. This product is for household kitchens, not commercial catering loads.</p><p>Use cookware that matches each zone so energy is not wasted around the pan. Wipe the ceramic surface regularly and empty crumbs from the oven floor to preserve appearance and efficiency. For spare parts, quote model EHT 6020 WG and the EAN on the rating plate.</p>"
|
||||
attributes:
|
||||
brand: Vox
|
||||
product_model: EHT6020WG
|
||||
main_image: https://images.example.com/products/vox-eht6020wg.jpg
|
||||
more_images: null
|
||||
more_images:
|
||||
- https://images.example.com/products/vox-eht6020wg-side.jpg
|
||||
eprel:
|
||||
id: "1234567"
|
||||
label: https://eprel.ec.europa.eu/label/Example
|
||||
@@ -4389,7 +4427,9 @@ paths:
|
||||
summary: Processing job status (raw_product_ids surface)
|
||||
description: |
|
||||
Public processing job JSON: status, counts, timestamps, processing_type.
|
||||
Omits current_step, step_progress, and company_id. Completed jobs still include items[].
|
||||
Omits current_step, step_progress, and company_id. Completed jobs still include
|
||||
items[] with the same AI HTML description contract as GET /products/process/{id}
|
||||
(category formula body, not a one- or two-sentence blurb).
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ID"
|
||||
responses:
|
||||
@@ -4421,6 +4461,7 @@ paths:
|
||||
status: processed
|
||||
name: Acme Wireless Earbuds ANC Black
|
||||
category: Headphones
|
||||
description: "<h1>Acme Wireless Earbuds ANC Black</h1><p>Acme Wireless Earbuds ANC Black are in-ear headphones for commuting and desk work. Active noise cancellation, a secure fit, and a charging case cover a full workday of listening. The compact buds sit flush enough for glasses and hats, and the case fits a jacket pocket.</p><p>Pair over Bluetooth and switch between a phone and a laptop with the onboard controls. Transparency mode lets nearby voices through without removing the buds. The default ear-tip set covers common sizes; swap tips if bass feels weak or the seal slips during a walk.</p><h2>In the box</h2><ul><li>Wireless earbuds (left and right)</li><li>Charging case with USB-C cable</li><li>Three ear-tip sizes</li><li>Quick-start guide</li></ul><p>Charge the case before first use. Keep the charging contacts dry and store the buds in the case so they sleep and recharge between sessions. For replacement tips or a spare case, quote the product name and the EAN on the box.</p>"
|
||||
total_items: 1
|
||||
'400':
|
||||
description: Path id is not a UUID.
|
||||
@@ -5133,7 +5174,10 @@ components:
|
||||
description: Optional product title seed for enrichment
|
||||
description:
|
||||
type: string
|
||||
description: Optional long description seed for enrichment
|
||||
description: |
|
||||
Optional source/seed text. AI enhance rewrites this into the
|
||||
category description-formula HTML body. It is not the final
|
||||
poll payload and does not cap length to a short summary.
|
||||
specifications:
|
||||
type: array
|
||||
description: Optional key/value specification pairs for attribute hints
|
||||
@@ -5193,10 +5237,13 @@ components:
|
||||
Not PresentProduct.id. Use GET /products data[].raw_product_id.
|
||||
processing_type:
|
||||
description: |
|
||||
full (default); named steps category|title|description|attributes (string or
|
||||
full (default) runs the whole pipeline including AI enhance, which
|
||||
writes formula-driven HTML descriptions (not a one- or two-sentence
|
||||
blurb). Named steps category|title|description|attributes (string or
|
||||
array of those steps); dual-mode also accepts v2 dashboard types such as
|
||||
normalize_only, enhance_only, attributes_only, eprel_only, categorize_only
|
||||
(ParseV1ProcessingType). Alias processingType accepted when values match.
|
||||
The description step still emits the category formula HTML body.
|
||||
oneOf:
|
||||
- type: string
|
||||
enum:
|
||||
@@ -5550,8 +5597,13 @@ components:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
description: |
|
||||
Product body. After processing this is the AI HTML description
|
||||
(category formula), not a one- or two-sentence summary.
|
||||
processed_description:
|
||||
type: string
|
||||
description: |
|
||||
Enriched HTML body from AI enhance. Same contract as ProcessItem.description.
|
||||
status:
|
||||
type: string
|
||||
example: completed
|
||||
@@ -5664,8 +5716,16 @@ components:
|
||||
nullable: true
|
||||
title_template:
|
||||
nullable: true
|
||||
description: |
|
||||
Title formula for AI enhance. Controls how processed name is built
|
||||
(type, brand, model, and other slots).
|
||||
description_template:
|
||||
nullable: true
|
||||
description: |
|
||||
Description formula for AI enhance. Ordered sections (h1/h2/h3/h4, p, ul)
|
||||
with per-section instructions. Controls HTML structure and length of
|
||||
processed items[].description — short, long, or very large copy.
|
||||
Not a one- or two-sentence cap.
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
@@ -6297,10 +6357,11 @@ components:
|
||||
description: |
|
||||
One COMPLETED process line (public contract). Successful items
|
||||
expose category as the human-readable display name (category_id holds
|
||||
categories.unique_id), a description string that may include section
|
||||
HTML (h1/h2/h3/h4, p, ul — never a JSON array), optional SEO meta_title /
|
||||
meta_description (plain text; some companies omit these fields),
|
||||
optional eprel object or null, clean attributes, and images.
|
||||
categories.unique_id), a description string that is generated HTML
|
||||
product copy (h1/h2/h3/h4, p, ul — never a JSON array and never a one-
|
||||
or two-sentence slogan), optional SEO meta_title / meta_description
|
||||
(plain text; some companies omit these fields), optional eprel object
|
||||
or null, clean attributes, and images.
|
||||
Product display name is name (primary). title is omitted when identical to name.
|
||||
Internal UUIDs (id, processed_product_id, raw_product_id) are omitted from this
|
||||
public shape — use catalog APIs when a product UUID is required.
|
||||
@@ -6317,8 +6378,6 @@ components:
|
||||
description: |
|
||||
Human-readable category display name (same value as category_name when
|
||||
resolved). Prefer this for UI. Machine unique_id is category_id.
|
||||
ASSUMPTION: historically this field held categories.unique_id; clients
|
||||
that need the opaque id must read category_id (additive).
|
||||
category_id:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -6356,9 +6415,14 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
description: |
|
||||
Product body description as a string — never a one-element JSON array.
|
||||
May include section HTML tags (h1/h2/h3/h4, p, ul). Plain-text
|
||||
descriptions remain plain. Optional meta_description stays plain SEO text.
|
||||
Generated product body as one HTML string — never a JSON array and
|
||||
never a one- or two-sentence slogan. AI enhance follows the assigned
|
||||
category description_template (description formula): ordered sections
|
||||
of type h1, h2, h3, h4, p, and ul, each with its own instructions.
|
||||
Output can be a short paragraph, several 100-word blocks, or a very
|
||||
large multi-section article. When no formula is set, expect 1-3
|
||||
factual paragraphs with limited HTML. Optional meta_description stays
|
||||
plain SEO text and is not a substitute for this field.
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
@@ -6477,7 +6541,7 @@ components:
|
||||
category_id: '50'
|
||||
category_name: Cookers
|
||||
name: VOX electric cooker EHT 6020 WG
|
||||
description: "Vox Electronics electric cooker EHT 6020 WG offers strong value with four electric hobs and a 65 L fan oven. Energy class A with practical everyday capacity."
|
||||
description: "<h1>VOX electric cooker EHT 6020 WG</h1><p>The VOX EHT 6020 WG is a free-standing electric cooker for everyday household cooking. Four ceramic hobs and a 65 L fan oven let you boil, fry, and bake at the same time, while energy class A helps keep running costs down. The 60 cm width fits a standard kitchen niche. An enamel oven cavity and removable inner door glass are designed for cleaning after heavy use.</p><p>Run sauces or pasta on the hob while the oven handles a roast or tray bake. Heat is even across the ceramic zones, and residual-heat indicators reduce the chance of accidental contact after cooking. Fan mode suits multi-level baking; conventional heat remains available for dishes that prefer still air. A full-width grill finishes cheese, gratins, and toast.</p><h2>Key features</h2><ul><li>Four electric ceramic hobs with residual-heat indicators</li><li>65 L fan oven plus grill, energy class A</li><li>60 cm free-standing body (0.6 m wide, 0.85 m high, 0.6 m deep)</li><li>Enamel cavity and removable inner door glass</li><li>60-month manufacturer warranty</li></ul><p>Install in a standard 60 cm cooker slot with the dedicated electric supply shown on the rating plate. Leave rear clearance for ventilation and use a heat-resistant worktop next to the hob. This product is for household kitchens, not commercial catering loads.</p><p>Use cookware that matches each zone so energy is not wasted around the pan. Wipe the ceramic surface regularly and empty crumbs from the oven floor to preserve appearance and efficiency. For spare parts, quote model EHT 6020 WG and the EAN on the rating plate.</p>"
|
||||
attributes:
|
||||
brand: Vox
|
||||
product_model: EHT6020WG
|
||||
|
||||
@@ -467,6 +467,44 @@ func TestV1OpenAPIYAMLOmitsInternalPromptWording(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestV1OpenAPIYAMLDocumentsAIDescriptionHTML(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
doc := string(v1OpenAPIYAML)
|
||||
for _, needle := range []string{
|
||||
"AI descriptions (HTML body, not a short blurb)",
|
||||
"description_template (description formula)",
|
||||
"one- or two-sentence",
|
||||
"<h1>VOX electric cooker EHT 6020 WG</h1>",
|
||||
"<h2>Key features</h2>",
|
||||
} {
|
||||
if !strings.Contains(doc, needle) {
|
||||
t.Fatalf("openapi missing AI description contract text %q", needle)
|
||||
}
|
||||
}
|
||||
|
||||
root := mustParseOpenAPIRoot(t, v1OpenAPIYAML)
|
||||
comps, _ := root["components"].(map[string]any)
|
||||
examples, _ := comps["examples"].(map[string]any)
|
||||
completed, _ := examples["ProcessCompletedExample"].(map[string]any)
|
||||
val, _ := completed["value"].(map[string]any)
|
||||
data, _ := val["data"].(map[string]any)
|
||||
items, _ := data["items"].([]any)
|
||||
if len(items) == 0 {
|
||||
t.Fatal("ProcessCompletedExample: expected items")
|
||||
}
|
||||
item, _ := items[0].(map[string]any)
|
||||
desc, _ := item["description"].(string)
|
||||
for _, tag := range []string{"<h1>", "<p>", "<h2>", "<ul>", "<li>"} {
|
||||
if !strings.Contains(desc, tag) {
|
||||
t.Fatalf("ProcessCompletedExample description must include %s (got %q)", tag, desc)
|
||||
}
|
||||
}
|
||||
if len(desc) < 800 {
|
||||
t.Fatalf("ProcessCompletedExample description too short (%d chars); expected full AI HTML body", len(desc))
|
||||
}
|
||||
}
|
||||
|
||||
func TestV1OpenAPIYAMLProcessItemCompletedExamplesOmitLeakyShape(t *testing.T) {
|
||||
t.Parallel()
|
||||
root := mustParseOpenAPIRoot(t, v1OpenAPIYAML)
|
||||
|
||||
Reference in New Issue
Block a user