|
|
|
@@ -353,15 +353,14 @@ async function generateDebugResponse(productsToUse, res) {
|
|
|
|
WHERE prompt_type = 'system'
|
|
|
|
WHERE prompt_type = 'system'
|
|
|
|
`);
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
|
|
// Get system prompt or use default
|
|
|
|
if (systemPromptResult.rows.length === 0) {
|
|
|
|
let systemPrompt = null;
|
|
|
|
console.error("❌ No system prompt found in database");
|
|
|
|
if (systemPromptResult.rows.length > 0) {
|
|
|
|
throw new Error("No system prompt found in database");
|
|
|
|
systemPrompt = systemPromptResult.rows[0];
|
|
|
|
|
|
|
|
console.log("📝 Loaded system prompt from database, ID:", systemPrompt.id);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
console.warn("⚠️ No system prompt found in database, will use default");
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const systemPrompt = systemPromptResult.rows[0];
|
|
|
|
|
|
|
|
console.log("📝 Loaded system prompt from database, ID:", systemPrompt.id);
|
|
|
|
|
|
|
|
|
|
|
|
// Then, fetch the general prompt using the consolidated endpoint approach
|
|
|
|
// Then, fetch the general prompt using the consolidated endpoint approach
|
|
|
|
const generalPromptResult = await pool.query(`
|
|
|
|
const generalPromptResult = await pool.query(`
|
|
|
|
SELECT * FROM ai_prompts
|
|
|
|
SELECT * FROM ai_prompts
|
|
|
|
@@ -369,7 +368,7 @@ async function generateDebugResponse(productsToUse, res) {
|
|
|
|
`);
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
|
|
if (generalPromptResult.rows.length === 0) {
|
|
|
|
if (generalPromptResult.rows.length === 0) {
|
|
|
|
console.warn("⚠️ No general prompt found in database");
|
|
|
|
console.error("❌ No general prompt found in database");
|
|
|
|
throw new Error("No general prompt found in database");
|
|
|
|
throw new Error("No general prompt found in database");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -481,22 +480,15 @@ async function generateDebugResponse(productsToUse, res) {
|
|
|
|
: null,
|
|
|
|
: null,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
: null,
|
|
|
|
: null,
|
|
|
|
basePrompt: systemPrompt ? systemPrompt.prompt_text + "\n\n" + generalPrompt.prompt_text : generalPrompt.prompt_text,
|
|
|
|
basePrompt: systemPrompt.prompt_text + "\n\n" + generalPrompt.prompt_text,
|
|
|
|
sampleFullPrompt: fullUserPrompt,
|
|
|
|
sampleFullPrompt: fullUserPrompt,
|
|
|
|
promptLength: promptLength,
|
|
|
|
promptLength: promptLength,
|
|
|
|
apiFormat: apiMessages,
|
|
|
|
apiFormat: apiMessages,
|
|
|
|
promptSources: {
|
|
|
|
promptSources: {
|
|
|
|
...(systemPrompt ? {
|
|
|
|
systemPrompt: {
|
|
|
|
systemPrompt: {
|
|
|
|
id: systemPrompt.id,
|
|
|
|
id: systemPrompt.id,
|
|
|
|
prompt_text: systemPrompt.prompt_text
|
|
|
|
prompt_text: systemPrompt.prompt_text
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
} : {
|
|
|
|
|
|
|
|
systemPrompt: {
|
|
|
|
|
|
|
|
id: 0,
|
|
|
|
|
|
|
|
prompt_text: `You are a specialized e-commerce product data processor for a crafting supplies website tasked with providing complete, correct, appealing, and SEO-friendly product listings. You should write professionally, but in a friendly and engaging tone. You have meticulous attention to detail and are a master at your craft.`
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
generalPrompt: {
|
|
|
|
generalPrompt: {
|
|
|
|
id: generalPrompt.id,
|
|
|
|
id: generalPrompt.id,
|
|
|
|
prompt_text: generalPrompt.prompt_text
|
|
|
|
prompt_text: generalPrompt.prompt_text
|
|
|
|
@@ -702,17 +694,14 @@ async function loadPrompt(connection, productsToValidate = null, appPool = null)
|
|
|
|
WHERE prompt_type = 'system'
|
|
|
|
WHERE prompt_type = 'system'
|
|
|
|
`);
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
|
|
// Default system instructions in case the system prompt is not found
|
|
|
|
if (systemPromptResult.rows.length === 0) {
|
|
|
|
let systemInstructions = `You are a specialized e-commerce product data processor for a crafting supplies website tasked with providing complete, correct, appealing, and SEO-friendly product listings. You should write professionally, but in a friendly and engaging tone. You have meticulous attention to detail and are a master at your craft.`;
|
|
|
|
console.error("❌ No system prompt found in database");
|
|
|
|
|
|
|
|
throw new Error("No system prompt found in database");
|
|
|
|
// If system prompt exists in the database, use it
|
|
|
|
|
|
|
|
if (systemPromptResult.rows.length > 0) {
|
|
|
|
|
|
|
|
systemInstructions = systemPromptResult.rows[0].prompt_text;
|
|
|
|
|
|
|
|
console.log("📝 Loaded system prompt from database");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
console.warn("⚠️ No system prompt found in database, using default");
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const systemInstructions = systemPromptResult.rows[0].prompt_text;
|
|
|
|
|
|
|
|
console.log("📝 Loaded system prompt from database");
|
|
|
|
|
|
|
|
|
|
|
|
// Fetch the general prompt using the consolidated endpoint approach
|
|
|
|
// Fetch the general prompt using the consolidated endpoint approach
|
|
|
|
const generalPromptResult = await pool.query(`
|
|
|
|
const generalPromptResult = await pool.query(`
|
|
|
|
SELECT * FROM ai_prompts
|
|
|
|
SELECT * FROM ai_prompts
|
|
|
|
@@ -720,7 +709,7 @@ async function loadPrompt(connection, productsToValidate = null, appPool = null)
|
|
|
|
`);
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
|
|
if (generalPromptResult.rows.length === 0) {
|
|
|
|
if (generalPromptResult.rows.length === 0) {
|
|
|
|
console.warn("⚠️ No general prompt found in database");
|
|
|
|
console.error("❌ No general prompt found in database");
|
|
|
|
throw new Error("No general prompt found in database");
|
|
|
|
throw new Error("No general prompt found in database");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -779,84 +768,87 @@ async function loadPrompt(connection, productsToValidate = null, appPool = null)
|
|
|
|
combinedPrompt += "\n--- END COMPANY-SPECIFIC INSTRUCTIONS ---\n";
|
|
|
|
combinedPrompt += "\n--- END COMPANY-SPECIFIC INSTRUCTIONS ---\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If we have products to validate, create a filtered prompt
|
|
|
|
// Products are required for validation
|
|
|
|
if (productsToValidate) {
|
|
|
|
if (!productsToValidate || !Array.isArray(productsToValidate) || productsToValidate.length === 0) {
|
|
|
|
console.log("Creating filtered prompt for products:", productsToValidate);
|
|
|
|
throw new Error("Products are required for prompt generation");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Extract unique values from products for non-core attributes
|
|
|
|
console.log("Creating filtered prompt for products:", productsToValidate);
|
|
|
|
const uniqueValues = {
|
|
|
|
|
|
|
|
supplierIds: new Set(),
|
|
|
|
|
|
|
|
companyIds: new Set(),
|
|
|
|
|
|
|
|
artistIds: new Set(),
|
|
|
|
|
|
|
|
lineIds: new Set(),
|
|
|
|
|
|
|
|
subLineIds: new Set(),
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Collect any values that exist in the products
|
|
|
|
// Extract unique values from products for non-core attributes
|
|
|
|
productsToValidate.forEach((product) => {
|
|
|
|
const uniqueValues = {
|
|
|
|
Object.entries(product).forEach(([key, value]) => {
|
|
|
|
supplierIds: new Set(),
|
|
|
|
if (value === undefined || value === null) return;
|
|
|
|
companyIds: new Set(),
|
|
|
|
|
|
|
|
artistIds: new Set(),
|
|
|
|
|
|
|
|
lineIds: new Set(),
|
|
|
|
|
|
|
|
subLineIds: new Set(),
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Map field names to their respective sets
|
|
|
|
// Collect any values that exist in the products
|
|
|
|
const fieldMap = {
|
|
|
|
productsToValidate.forEach((product) => {
|
|
|
|
supplierid: "supplierIds",
|
|
|
|
Object.entries(product).forEach(([key, value]) => {
|
|
|
|
supplier: "supplierIds",
|
|
|
|
if (value === undefined || value === null) return;
|
|
|
|
company: "companyIds",
|
|
|
|
|
|
|
|
artist: "artistIds",
|
|
|
|
|
|
|
|
line: "lineIds",
|
|
|
|
|
|
|
|
subline: "subLineIds",
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (fieldMap[key]) {
|
|
|
|
// Map field names to their respective sets
|
|
|
|
uniqueValues[fieldMap[key]].add(Number(value));
|
|
|
|
const fieldMap = {
|
|
|
|
}
|
|
|
|
supplierid: "supplierIds",
|
|
|
|
});
|
|
|
|
supplier: "supplierIds",
|
|
|
|
|
|
|
|
company: "companyIds",
|
|
|
|
|
|
|
|
artist: "artistIds",
|
|
|
|
|
|
|
|
line: "lineIds",
|
|
|
|
|
|
|
|
subline: "subLineIds",
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (fieldMap[key]) {
|
|
|
|
|
|
|
|
uniqueValues[fieldMap[key]].add(Number(value));
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
console.log("Unique values collected:", {
|
|
|
|
console.log("Unique values collected:", {
|
|
|
|
suppliers: Array.from(uniqueValues.supplierIds),
|
|
|
|
suppliers: Array.from(uniqueValues.supplierIds),
|
|
|
|
companies: Array.from(uniqueValues.companyIds),
|
|
|
|
companies: Array.from(uniqueValues.companyIds),
|
|
|
|
artists: Array.from(uniqueValues.artistIds),
|
|
|
|
artists: Array.from(uniqueValues.artistIds),
|
|
|
|
lines: Array.from(uniqueValues.lineIds),
|
|
|
|
lines: Array.from(uniqueValues.lineIds),
|
|
|
|
subLines: Array.from(uniqueValues.subLineIds),
|
|
|
|
subLines: Array.from(uniqueValues.subLineIds),
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Create mixed taxonomy with filtered non-core data and full core data
|
|
|
|
// Create mixed taxonomy with filtered non-core data and full core data
|
|
|
|
const mixedTaxonomy = {
|
|
|
|
const mixedTaxonomy = {
|
|
|
|
// Keep full data for core attributes
|
|
|
|
// Keep full data for core attributes
|
|
|
|
categories: taxonomy.categories,
|
|
|
|
categories: taxonomy.categories,
|
|
|
|
themes: taxonomy.themes,
|
|
|
|
themes: taxonomy.themes,
|
|
|
|
colors: taxonomy.colors,
|
|
|
|
colors: taxonomy.colors,
|
|
|
|
taxCodes: taxonomy.taxCodes,
|
|
|
|
taxCodes: taxonomy.taxCodes,
|
|
|
|
sizeCategories: taxonomy.sizeCategories,
|
|
|
|
sizeCategories: taxonomy.sizeCategories,
|
|
|
|
// For non-core data, only include items that are actually used
|
|
|
|
// For non-core data, only include items that are actually used
|
|
|
|
suppliers: taxonomy.suppliers.filter(([id]) =>
|
|
|
|
suppliers: taxonomy.suppliers.filter(([id]) =>
|
|
|
|
uniqueValues.supplierIds.has(Number(id))
|
|
|
|
uniqueValues.supplierIds.has(Number(id))
|
|
|
|
),
|
|
|
|
),
|
|
|
|
companies: taxonomy.companies.filter(([id]) =>
|
|
|
|
companies: taxonomy.companies.filter(([id]) =>
|
|
|
|
uniqueValues.companyIds.has(Number(id))
|
|
|
|
uniqueValues.companyIds.has(Number(id))
|
|
|
|
),
|
|
|
|
),
|
|
|
|
artists: taxonomy.artists.filter(([id]) =>
|
|
|
|
artists: taxonomy.artists.filter(([id]) =>
|
|
|
|
uniqueValues.artistIds.has(Number(id))
|
|
|
|
uniqueValues.artistIds.has(Number(id))
|
|
|
|
),
|
|
|
|
),
|
|
|
|
lines: taxonomy.lines.filter(([id]) =>
|
|
|
|
lines: taxonomy.lines.filter(([id]) =>
|
|
|
|
uniqueValues.lineIds.has(Number(id))
|
|
|
|
uniqueValues.lineIds.has(Number(id))
|
|
|
|
),
|
|
|
|
),
|
|
|
|
subLines: taxonomy.subLines.filter(([id]) =>
|
|
|
|
subLines: taxonomy.subLines.filter(([id]) =>
|
|
|
|
uniqueValues.subLineIds.has(Number(id))
|
|
|
|
uniqueValues.subLineIds.has(Number(id))
|
|
|
|
),
|
|
|
|
),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
console.log("Filtered taxonomy counts:", {
|
|
|
|
console.log("Filtered taxonomy counts:", {
|
|
|
|
suppliers: mixedTaxonomy.suppliers.length,
|
|
|
|
suppliers: mixedTaxonomy.suppliers.length,
|
|
|
|
companies: mixedTaxonomy.companies.length,
|
|
|
|
companies: mixedTaxonomy.companies.length,
|
|
|
|
artists: mixedTaxonomy.artists.length,
|
|
|
|
artists: mixedTaxonomy.artists.length,
|
|
|
|
lines: mixedTaxonomy.lines.length,
|
|
|
|
lines: mixedTaxonomy.lines.length,
|
|
|
|
subLines: mixedTaxonomy.subLines.length,
|
|
|
|
subLines: mixedTaxonomy.subLines.length,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Format taxonomy data for the prompt, only including sections with values
|
|
|
|
// Format taxonomy data for the prompt, only including sections with values
|
|
|
|
const taxonomySection = `
|
|
|
|
const taxonomySection = `
|
|
|
|
All Available Categories:
|
|
|
|
All Available Categories:
|
|
|
|
${JSON.stringify(mixedTaxonomy.categories)}
|
|
|
|
${JSON.stringify(mixedTaxonomy.categories)}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -871,73 +863,38 @@ ${JSON.stringify(mixedTaxonomy.taxCodes)}
|
|
|
|
|
|
|
|
|
|
|
|
All Available Size Categories:
|
|
|
|
All Available Size Categories:
|
|
|
|
${JSON.stringify(mixedTaxonomy.sizeCategories)}${
|
|
|
|
${JSON.stringify(mixedTaxonomy.sizeCategories)}${
|
|
|
|
mixedTaxonomy.suppliers.length
|
|
|
|
mixedTaxonomy.suppliers.length
|
|
|
|
? `\n\nSuppliers Used In This Data:\n${JSON.stringify(
|
|
|
|
? `\n\nSuppliers Used In This Data:\n${JSON.stringify(
|
|
|
|
mixedTaxonomy.suppliers
|
|
|
|
mixedTaxonomy.suppliers
|
|
|
|
)}`
|
|
|
|
)}`
|
|
|
|
: ""
|
|
|
|
: ""
|
|
|
|
}${
|
|
|
|
}${
|
|
|
|
mixedTaxonomy.companies.length
|
|
|
|
mixedTaxonomy.companies.length
|
|
|
|
? `\n\nCompanies Used In This Data:\n${JSON.stringify(
|
|
|
|
? `\n\nCompanies Used In This Data:\n${JSON.stringify(
|
|
|
|
mixedTaxonomy.companies
|
|
|
|
mixedTaxonomy.companies
|
|
|
|
)}`
|
|
|
|
)}`
|
|
|
|
: ""
|
|
|
|
: ""
|
|
|
|
}${
|
|
|
|
}${
|
|
|
|
mixedTaxonomy.artists.length
|
|
|
|
mixedTaxonomy.artists.length
|
|
|
|
? `\n\nArtists Used In This Data:\n${JSON.stringify(
|
|
|
|
? `\n\nArtists Used In This Data:\n${JSON.stringify(
|
|
|
|
mixedTaxonomy.artists
|
|
|
|
mixedTaxonomy.artists
|
|
|
|
)}`
|
|
|
|
)}`
|
|
|
|
: ""
|
|
|
|
: ""
|
|
|
|
}${
|
|
|
|
}${
|
|
|
|
mixedTaxonomy.lines.length
|
|
|
|
mixedTaxonomy.lines.length
|
|
|
|
? `\n\nLines Used In This Data:\n${JSON.stringify(
|
|
|
|
? `\n\nLines Used In This Data:\n${JSON.stringify(
|
|
|
|
mixedTaxonomy.lines
|
|
|
|
mixedTaxonomy.lines
|
|
|
|
)}`
|
|
|
|
)}`
|
|
|
|
: ""
|
|
|
|
: ""
|
|
|
|
}${
|
|
|
|
}${
|
|
|
|
mixedTaxonomy.subLines.length
|
|
|
|
mixedTaxonomy.subLines.length
|
|
|
|
? `\n\nSub-Lines Used In This Data:\n${JSON.stringify(
|
|
|
|
? `\n\nSub-Lines Used In This Data:\n${JSON.stringify(
|
|
|
|
mixedTaxonomy.subLines
|
|
|
|
mixedTaxonomy.subLines
|
|
|
|
)}`
|
|
|
|
)}`
|
|
|
|
: ""
|
|
|
|
: ""
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
----------Here is the product data to validate----------`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Return both system instructions and user content separately
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
systemInstructions,
|
|
|
|
|
|
|
|
userContent: combinedPrompt + "\n" + taxonomySection
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Generate the full unfiltered prompt for taxonomy section
|
|
|
|
----------Here is the product data to validate----------`;
|
|
|
|
const taxonomySection = `
|
|
|
|
|
|
|
|
Available Categories:
|
|
|
|
|
|
|
|
${JSON.stringify(taxonomy.categories)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Available Themes:
|
|
|
|
|
|
|
|
${JSON.stringify(taxonomy.themes)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Available Colors:
|
|
|
|
|
|
|
|
${JSON.stringify(taxonomy.colors)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Available Tax Codes:
|
|
|
|
|
|
|
|
${JSON.stringify(taxonomy.taxCodes)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Available Size Categories:
|
|
|
|
|
|
|
|
${JSON.stringify(taxonomy.sizeCategories)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Available Suppliers:
|
|
|
|
|
|
|
|
${JSON.stringify(taxonomy.suppliers)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Available Companies:
|
|
|
|
|
|
|
|
${JSON.stringify(taxonomy.companies)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Available Artists:
|
|
|
|
|
|
|
|
${JSON.stringify(taxonomy.artists)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Here is the product data to validate:`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Return both system instructions and user content separately
|
|
|
|
// Return both system instructions and user content separately
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
@@ -1007,7 +964,7 @@ router.post("/validate", async (req, res) => {
|
|
|
|
input: [
|
|
|
|
input: [
|
|
|
|
{
|
|
|
|
{
|
|
|
|
role: "developer",
|
|
|
|
role: "developer",
|
|
|
|
content: `${promptData.systemInstructions}\n\nYou MUST respond with a single valid JSON object containing the following top-level keys: correctedData, changes, warnings, summary, metadata.\n- correctedData: array of product objects reflecting the updated data.\n- changes: array of human-readable bullet points summarizing the nature of updates.\n- warnings: array of caveats or risks that still require review.\n- summary: a concise paragraph (<=75 words) describing overall data quality and improvements.\n- metadata: object containing any supplemental machine-readable information (optional fields allowed).\nDo NOT include Markdown code fences or any text outside the JSON object.`,
|
|
|
|
content: promptData.systemInstructions,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
role: "user",
|
|
|
|
role: "user",
|
|
|
|
@@ -1068,60 +1025,87 @@ router.post("/validate", async (req, res) => {
|
|
|
|
Object.keys(aiResponse)
|
|
|
|
Object.keys(aiResponse)
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Create a detailed comparison between original and corrected data
|
|
|
|
// Merge AI changes back into original products
|
|
|
|
|
|
|
|
// AI now only returns changed products and changed fields
|
|
|
|
|
|
|
|
const mergedProducts = products.map((original, index) => ({ ...original }));
|
|
|
|
const changeDetails = [];
|
|
|
|
const changeDetails = [];
|
|
|
|
|
|
|
|
|
|
|
|
// Compare original and corrected data
|
|
|
|
if (aiResponse.correctedData && Array.isArray(aiResponse.correctedData)) {
|
|
|
|
if (aiResponse.correctedData) {
|
|
|
|
console.log("📊 Processing AI changes - received", aiResponse.correctedData.length, "products with changes");
|
|
|
|
console.log("📊 Changes summary:");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Debug: Log the first product's fields
|
|
|
|
// Process each changed product from AI
|
|
|
|
if (products.length > 0) {
|
|
|
|
aiResponse.correctedData.forEach((changedProduct) => {
|
|
|
|
console.log("🔍 First product fields:", Object.keys(products[0]));
|
|
|
|
// Find the matching original product using stable identifiers in priority order
|
|
|
|
}
|
|
|
|
// Priority: upc > supplier_no > notions_no
|
|
|
|
|
|
|
|
// These fields should not change during validation
|
|
|
|
products.forEach((original, index) => {
|
|
|
|
const identifiers = ['upc', 'supplier_no', 'notions_no'];
|
|
|
|
const corrected = aiResponse.correctedData[index];
|
|
|
|
let matchedIndex = -1;
|
|
|
|
if (corrected) {
|
|
|
|
let matchedBy = null;
|
|
|
|
const productChanges = {
|
|
|
|
|
|
|
|
productIndex: index,
|
|
|
|
for (const identifier of identifiers) {
|
|
|
|
title: original.name || original.title || `Product ${index + 1}`,
|
|
|
|
if (changedProduct[identifier] !== undefined && changedProduct[identifier] !== null && changedProduct[identifier] !== '') {
|
|
|
|
changes: []
|
|
|
|
matchedIndex = products.findIndex(
|
|
|
|
};
|
|
|
|
(p) => p[identifier] !== undefined &&
|
|
|
|
|
|
|
|
p[identifier] !== null &&
|
|
|
|
const changes = Object.keys(corrected).filter(
|
|
|
|
p[identifier] !== '' &&
|
|
|
|
(key) =>
|
|
|
|
String(p[identifier]).trim() === String(changedProduct[identifier]).trim()
|
|
|
|
JSON.stringify(original[key]) !==
|
|
|
|
);
|
|
|
|
JSON.stringify(corrected[key])
|
|
|
|
if (matchedIndex !== -1) {
|
|
|
|
);
|
|
|
|
matchedBy = identifier;
|
|
|
|
|
|
|
|
console.log(`✓ Matched product by ${identifier}:`, changedProduct[identifier]);
|
|
|
|
if (changes.length > 0) {
|
|
|
|
break;
|
|
|
|
console.log(`\nProduct ${index + 1} changes:`);
|
|
|
|
|
|
|
|
changes.forEach((key) => {
|
|
|
|
|
|
|
|
console.log(` ${key}:`);
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
|
|
` - Original: ${JSON.stringify(original[key])}`
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
|
|
` - Corrected: ${JSON.stringify(corrected[key])}`
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add to our detailed changes array
|
|
|
|
|
|
|
|
productChanges.changes.push({
|
|
|
|
|
|
|
|
field: key,
|
|
|
|
|
|
|
|
original: original[key],
|
|
|
|
|
|
|
|
corrected: corrected[key]
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Only add products that have changes
|
|
|
|
|
|
|
|
if (productChanges.changes.length > 0) {
|
|
|
|
|
|
|
|
changeDetails.push(productChanges);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If no identifier match found, log an error with details
|
|
|
|
|
|
|
|
if (matchedIndex === -1) {
|
|
|
|
|
|
|
|
console.error("❌ Could not match changed product to original. Product identifiers:", {
|
|
|
|
|
|
|
|
upc: changedProduct.upc,
|
|
|
|
|
|
|
|
supplier_no: changedProduct.supplier_no,
|
|
|
|
|
|
|
|
notions_no: changedProduct.notions_no
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const original = products[matchedIndex];
|
|
|
|
|
|
|
|
const productChanges = {
|
|
|
|
|
|
|
|
productIndex: matchedIndex,
|
|
|
|
|
|
|
|
title: original.name || original.title || `Product ${matchedIndex + 1}`,
|
|
|
|
|
|
|
|
changes: []
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Apply each changed field to the merged product
|
|
|
|
|
|
|
|
Object.keys(changedProduct).forEach((key) => {
|
|
|
|
|
|
|
|
// Check if the value actually changed
|
|
|
|
|
|
|
|
if (JSON.stringify(original[key]) !== JSON.stringify(changedProduct[key])) {
|
|
|
|
|
|
|
|
console.log(`\nProduct ${matchedIndex + 1} - Field ${key}:`);
|
|
|
|
|
|
|
|
console.log(` - Original: ${JSON.stringify(original[key])}`);
|
|
|
|
|
|
|
|
console.log(` - Corrected: ${JSON.stringify(changedProduct[key])}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Apply the change to merged product
|
|
|
|
|
|
|
|
mergedProducts[matchedIndex][key] = changedProduct[key];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Track the change
|
|
|
|
|
|
|
|
productChanges.changes.push({
|
|
|
|
|
|
|
|
field: key,
|
|
|
|
|
|
|
|
original: original[key],
|
|
|
|
|
|
|
|
corrected: changedProduct[key]
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Only add to changeDetails if there were actual changes
|
|
|
|
|
|
|
|
if (productChanges.changes.length > 0) {
|
|
|
|
|
|
|
|
changeDetails.push(productChanges);
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`📊 Applied changes to ${changeDetails.length} products`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Replace aiResponse.correctedData with the fully merged product array
|
|
|
|
|
|
|
|
aiResponse.correctedData = mergedProducts;
|
|
|
|
|
|
|
|
|
|
|
|
// Record performance metrics after successful validation
|
|
|
|
// Record performance metrics after successful validation
|
|
|
|
const endTime = new Date();
|
|
|
|
const endTime = new Date();
|
|
|
|
@@ -1242,26 +1226,15 @@ router.post("/validate", async (req, res) => {
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// Set prompt sources
|
|
|
|
// Set prompt sources
|
|
|
|
if (generalPromptResult.rows.length > 0) {
|
|
|
|
if (generalPromptResult.rows.length > 0 && systemPromptResult.rows.length > 0) {
|
|
|
|
const generalPrompt = generalPromptResult.rows[0];
|
|
|
|
const generalPrompt = generalPromptResult.rows[0];
|
|
|
|
let systemPrompt = null;
|
|
|
|
const systemPrompt = systemPromptResult.rows[0];
|
|
|
|
|
|
|
|
|
|
|
|
if (systemPromptResult.rows.length > 0) {
|
|
|
|
|
|
|
|
systemPrompt = systemPromptResult.rows[0];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
promptSources = {
|
|
|
|
promptSources = {
|
|
|
|
...(systemPrompt ? {
|
|
|
|
systemPrompt: {
|
|
|
|
systemPrompt: {
|
|
|
|
id: systemPrompt.id,
|
|
|
|
id: systemPrompt.id,
|
|
|
|
prompt_text: systemPrompt.prompt_text
|
|
|
|
prompt_text: systemPrompt.prompt_text
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
} : {
|
|
|
|
|
|
|
|
systemPrompt: {
|
|
|
|
|
|
|
|
id: 0,
|
|
|
|
|
|
|
|
prompt_text: `You are a specialized e-commerce product data processor for a crafting supplies website tasked with providing complete, correct, appealing, and SEO-friendly product listings. You should write professionally, but in a friendly and engaging tone. You have meticulous attention to detail and are a master at your craft.`
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
generalPrompt: {
|
|
|
|
generalPrompt: {
|
|
|
|
id: generalPrompt.id,
|
|
|
|
id: generalPrompt.id,
|
|
|
|
prompt_text: generalPrompt.prompt_text
|
|
|
|
prompt_text: generalPrompt.prompt_text
|
|
|
|
|