'use strict'; const mappingData = require('../data/pdd1688PropertyMappings.json'); function normalizeText(value, maxLength = 1000) { return String(value ?? '').trim().slice(0, maxLength); } function normalizeMatchText(value) { return normalizeText(value, 1000) .toLowerCase() .replace(/[\s_\-—::()()【】\[\]\/、,,;;]+/gu, ''); } function findCategoryMapping({ categoryName = '', categoryPath = [], sourceCategory = '' } = {}) { const candidates = [ categoryName, ...(Array.isArray(categoryPath) ? categoryPath : []), sourceCategory, ].map(normalizeMatchText).filter(Boolean); return (mappingData.categories || []) .map((category) => ({ category, key: normalizeMatchText(category.categoryName), })) .filter((entry) => entry.key && candidates.some((candidate) => ( candidate === entry.key || candidate.includes(entry.key) || entry.key.includes(candidate) ))) .sort((left, right) => right.key.length - left.key.length)[0]?.category || null; } function buildSourceAttributeMap(attributes = []) { const map = new Map(); for (const row of Array.isArray(attributes) ? attributes : []) { const name = normalizeText(row?.name || row?.attributeName || row?.key, 255); const value = normalizeText(row?.value || row?.attributeValue || row?.text, 2000); if (!name || !value) continue; map.set(normalizeMatchText(name), { name, value }); } return map; } function findEnumValue(property = {}, expectedValue = '') { const expected = normalizeMatchText(expectedValue); if (!expected) return null; const values = Array.isArray(property.values) ? property.values : []; return values.find((row) => normalizeMatchText(row.value) === expected) || values.find((row) => { const value = normalizeMatchText(row.value); return value && (value.includes(expected) || expected.includes(value)); }) || null; } function sourceValueMatches(sourceValue, expectedValues = []) { const source = normalizeMatchText(sourceValue); return expectedValues.some((value) => { const expected = normalizeMatchText(value); return expected && (source === expected || source.includes(expected) || expected.includes(source)); }); } function resolveExcelMappedProperties({ categoryName, categoryPath, sourceCategory, sourceAttributes, categoryRule, } = {}) { const categoryMapping = findCategoryMapping({ categoryName, categoryPath, sourceCategory }); if (!categoryMapping) { return { categoryMapping: null, properties: [], lockedRefPids: [] }; } const sourceByName = buildSourceAttributeMap(sourceAttributes); const properties = []; for (const mapping of categoryMapping.propertyMappings || []) { if (!mapping.sourceProperty || mapping.sourceProperty === '无') continue; const rule = (categoryRule?.properties || []).find((row) => ( normalizeMatchText(row.name) === normalizeMatchText(mapping.pddProperty) )); if (!rule) continue; const source = sourceByName.get(normalizeMatchText(mapping.sourceProperty)); if (!source) continue; let selected = null; for (const valueMapping of mapping.valueMappings || []) { if (!sourceValueMatches(source.value, valueMapping.sourceValues)) continue; const enumValue = findEnumValue(rule, valueMapping.pddValue); if (enumValue) { selected = { refPid: rule.refPid, vid: enumValue.vid, value: enumValue.value }; break; } } if (!selected && (mapping.valueMappings || []).length === 0) { const enumValue = findEnumValue(rule, source.value); if (enumValue) { selected = { refPid: rule.refPid, vid: enumValue.vid, value: enumValue.value }; } else if (!(rule.values || []).length) { selected = { refPid: rule.refPid, vid: 0, value: source.value }; } } if (!selected) continue; properties.push({ ...selected, propertyName: rule.name, source: 'excel', sourceProperty: source.name, sourceValue: source.value, evidence: `Excel映射:1688「${source.name}=${source.value}」`, confidence: 1, locked: true, }); } return { categoryMapping: categoryMapping.categoryName, properties, lockedRefPids: properties.map((row) => row.refPid), }; } function mergePropertyRecommendations(categoryRule, excelResult, aiRows = []) { const merged = new Map((excelResult?.properties || []).map((row) => [Number(row.refPid), row])); for (const row of Array.isArray(aiRows) ? aiRows : []) { const refPid = Number(row.refPid || row.ref_pid); if (!refPid || merged.has(refPid)) continue; const rule = (categoryRule?.properties || []).find((property) => Number(property.refPid) === refPid); if (!rule) continue; const enumValue = row.vid ? (rule.values || []).find((value) => Number(value.vid) === Number(row.vid)) : findEnumValue(rule, row.value); if ((rule.values || []).length > 0 && !enumValue) continue; const value = enumValue?.value || normalizeText(row.value, 500); if (!value) continue; merged.set(refPid, { refPid, vid: enumValue?.vid || 0, value, propertyName: rule.name, source: 'ai', evidence: normalizeText(row.evidence, 1000), confidence: Math.min(Math.max(Number(row.confidence) || 0, 0), 1), locked: false, }); } return Array.from(merged.values()); } module.exports = { buildSourceAttributeMap, findCategoryMapping, findEnumValue, mergePropertyRecommendations, normalizeMatchText, resolveExcelMappedProperties, };