fix: cart premlink fixed
This commit is contained in:
parent
d1b657cba7
commit
db9b09e09a
@ -43,7 +43,12 @@ describe("automatic storefront upsells", () => {
|
|||||||
|
|
||||||
it("returns no upsell when the AI chooses null", () => {
|
it("returns no upsell when the AI chooses null", () => {
|
||||||
const result = parseAiUpsellChoice(
|
const result = parseAiUpsellChoice(
|
||||||
JSON.stringify({ productId: null, reason: "No clearly complementary product." }),
|
JSON.stringify({
|
||||||
|
productId: null,
|
||||||
|
relationshipType: "uncertain",
|
||||||
|
confidence: 0,
|
||||||
|
reason: "No clearly complementary product.",
|
||||||
|
}),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
title: "Candidate Product",
|
title: "Candidate Product",
|
||||||
@ -61,9 +66,64 @@ describe("automatic storefront upsells", () => {
|
|||||||
expect(result).toBeNull()
|
expect(result).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("rejects AI choices marked as substitutes", () => {
|
||||||
|
const result = parseAiUpsellChoice(
|
||||||
|
JSON.stringify({
|
||||||
|
productId: "candidate-1",
|
||||||
|
relationshipType: "substitute",
|
||||||
|
confidence: 0.9,
|
||||||
|
reason: "This is an alternative, not an add-on.",
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
title: "Candidate Product",
|
||||||
|
productType: "Same Category",
|
||||||
|
details: null,
|
||||||
|
product: {
|
||||||
|
productId: "candidate-1",
|
||||||
|
title: "Candidate Product",
|
||||||
|
variantId: "v-candidate",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("accepts high-confidence complementary AI choices", () => {
|
||||||
|
const result = parseAiUpsellChoice(
|
||||||
|
JSON.stringify({
|
||||||
|
productId: "candidate-1",
|
||||||
|
relationshipType: "complementary_accessory",
|
||||||
|
confidence: 0.88,
|
||||||
|
reason: "This is used together with the base product.",
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
title: "Candidate Product",
|
||||||
|
productType: "Accessory",
|
||||||
|
details: null,
|
||||||
|
product: {
|
||||||
|
productId: "candidate-1",
|
||||||
|
title: "Candidate Product",
|
||||||
|
variantId: "v-candidate",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result?.productId).toBe("candidate-1")
|
||||||
|
})
|
||||||
|
|
||||||
it("only accepts AI choices that exist in the candidate list", () => {
|
it("only accepts AI choices that exist in the candidate list", () => {
|
||||||
const result = parseAiUpsellChoice(
|
const result = parseAiUpsellChoice(
|
||||||
JSON.stringify({ productId: "unknown-product", reason: "Not in the provided list." }),
|
JSON.stringify({
|
||||||
|
productId: "unknown-product",
|
||||||
|
relationshipType: "complementary_accessory",
|
||||||
|
confidence: 0.9,
|
||||||
|
reason: "Not in the provided list.",
|
||||||
|
}),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
title: "Candidate Product",
|
title: "Candidate Product",
|
||||||
|
|||||||
@ -21,6 +21,14 @@ type UpsellCatalogCandidate = {
|
|||||||
details: string | null
|
details: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ACCEPTED_UPSELL_RELATIONSHIPS = new Set([
|
||||||
|
"complementary_accessory",
|
||||||
|
"care_item",
|
||||||
|
"consumable",
|
||||||
|
"bundle_addon",
|
||||||
|
"used_together",
|
||||||
|
])
|
||||||
|
|
||||||
function imageUrlFromImages(images: unknown): string | null {
|
function imageUrlFromImages(images: unknown): string | null {
|
||||||
const rows = typeof images === "string"
|
const rows = typeof images === "string"
|
||||||
? (() => {
|
? (() => {
|
||||||
@ -104,9 +112,20 @@ export function parseAiUpsellChoice(
|
|||||||
): StorefrontActionProduct | null {
|
): StorefrontActionProduct | null {
|
||||||
if (!content) return null
|
if (!content) return null
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(content) as { productId?: unknown }
|
const parsed = JSON.parse(content) as {
|
||||||
|
productId?: unknown
|
||||||
|
relationshipType?: unknown
|
||||||
|
confidence?: unknown
|
||||||
|
}
|
||||||
const productId = typeof parsed.productId === "string" ? parsed.productId.trim() : ""
|
const productId = typeof parsed.productId === "string" ? parsed.productId.trim() : ""
|
||||||
if (!productId) return null
|
if (!productId) return null
|
||||||
|
const relationshipType =
|
||||||
|
typeof parsed.relationshipType === "string"
|
||||||
|
? parsed.relationshipType.trim().toLowerCase()
|
||||||
|
: ""
|
||||||
|
if (!ACCEPTED_UPSELL_RELATIONSHIPS.has(relationshipType)) return null
|
||||||
|
const confidence = Number(parsed.confidence)
|
||||||
|
if (!Number.isFinite(confidence) || confidence < 0.75) return null
|
||||||
return candidates.find((candidate) => candidate.product.productId === productId)?.product ?? null
|
return candidates.find((candidate) => candidate.product.productId === productId)?.product ?? null
|
||||||
} catch {
|
} catch {
|
||||||
return null
|
return null
|
||||||
@ -122,10 +141,14 @@ async function selectAiUpsellCandidate(params: {
|
|||||||
|
|
||||||
const system = [
|
const system = [
|
||||||
"You select one ecommerce upsell item, or no item.",
|
"You select one ecommerce upsell item, or no item.",
|
||||||
"Use only the provided catalog candidates. Return JSON only: {\"productId\":\"...\"|null,\"reason\":\"...\"}.",
|
"Use only the provided catalog candidates.",
|
||||||
"A valid upsell must be clearly complementary to the base product: accessory, care item, consumable, add-on, matching bundle item, or item normally used together.",
|
"Return JSON only: {\"productId\":\"...\"|null,\"relationshipType\":\"complementary_accessory|care_item|consumable|bundle_addon|used_together|substitute|alternative|same_category|unrelated|uncertain\",\"confidence\":0-1,\"reason\":\"...\"}.",
|
||||||
"Do not choose substitutes or alternatives to the same requested product type, even if they share words, audience, color, brand, season, or price.",
|
"A valid upsell must be something the customer would reasonably buy in addition to the base product and use together with it.",
|
||||||
"If the relationship is weak, generic, or uncertain, return productId null.",
|
"Choose only these valid relationship types: complementary_accessory, care_item, consumable, bundle_addon, used_together.",
|
||||||
|
"Do not choose substitutes, alternatives, or same-category replacements, even if they share words, audience, color, brand, season, style, or price.",
|
||||||
|
"Invalid examples: sandals -> flip flops, slippers -> shoes, rain boots -> formal shoes, one backpack -> another backpack.",
|
||||||
|
"Valid examples: shoes -> socks, shoes -> laces, leather shoes -> polish, phone -> case, backpack -> lunch box.",
|
||||||
|
"If there is no clear add-on with confidence at least 0.75, return productId null with relationshipType uncertain.",
|
||||||
].join(" ")
|
].join(" ")
|
||||||
|
|
||||||
const user = JSON.stringify({
|
const user = JSON.stringify({
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user