158 lines
5.5 KiB
JavaScript
158 lines
5.5 KiB
JavaScript
'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,
|
||
};
|