cangku/backend/src/services/pdd1688PropertyMappingService.js

158 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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,
};