132 lines
4.0 KiB
JavaScript
132 lines
4.0 KiB
JavaScript
const clamp = (value, min, max) => {
|
|
return Math.min(max, Math.max(min, value));
|
|
};
|
|
|
|
const normalizeText = (value) => {
|
|
return String(value || '')
|
|
.toLowerCase()
|
|
.normalize('NFD')
|
|
.replace(/[\u0300-\u036f]/g, '')
|
|
.replace(/\s+/g, ' ')
|
|
.trim();
|
|
};
|
|
|
|
const GERMAN_COMMON_NAME_HINTS = [
|
|
'weihnachtsstern',
|
|
'weinachtsstern',
|
|
'einblatt',
|
|
'fensterblatt',
|
|
'korbmarante',
|
|
'glucksfeder',
|
|
'gluecksfeder',
|
|
'efeutute',
|
|
'drachenbaum',
|
|
'gummibaum',
|
|
'geigenfeige',
|
|
'bogenhanf',
|
|
'yucca palme',
|
|
'gluckskastanie',
|
|
'glueckskastanie',
|
|
];
|
|
|
|
const isLikelyGermanCommonName = (value) => {
|
|
const raw = String(value || '').trim();
|
|
if (!raw) return false;
|
|
if (/[äöüß]/i.test(raw)) return true;
|
|
|
|
const normalized = normalizeText(raw).replace(/[^a-z0-9 ]+/g, ' ');
|
|
if (!normalized) return false;
|
|
if (/\b(der|die|das|ein|eine)\b/.test(normalized)) return true;
|
|
|
|
return GERMAN_COMMON_NAME_HINTS.some((hint) => normalized.includes(hint));
|
|
};
|
|
|
|
const isLikelyBotanicalName = (value, botanicalName) => {
|
|
const raw = String(value || '').trim();
|
|
const botanicalRaw = String(botanicalName || '').trim();
|
|
if (!raw) return false;
|
|
|
|
if (normalizeText(raw) === normalizeText(botanicalRaw)) return true;
|
|
|
|
return /^[A-Z][a-z-]+(?:\s[a-z.-]+){1,2}$/.test(raw);
|
|
};
|
|
|
|
const findCatalogMatch = (aiResult, entries) => {
|
|
if (!aiResult || !Array.isArray(entries) || entries.length === 0) return null;
|
|
const aiBotanical = normalizeText(aiResult.botanicalName);
|
|
const aiName = normalizeText(aiResult.name);
|
|
if (!aiBotanical && !aiName) return null;
|
|
|
|
const byExactBotanical = entries.find((entry) => normalizeText(entry.botanicalName) === aiBotanical);
|
|
if (byExactBotanical) return byExactBotanical;
|
|
|
|
const byExactName = entries.find((entry) => normalizeText(entry.name) === aiName);
|
|
if (byExactName) return byExactName;
|
|
|
|
if (aiBotanical) {
|
|
const aiGenus = aiBotanical.split(' ')[0];
|
|
if (aiGenus) {
|
|
const byGenus = entries.find((entry) => normalizeText(entry.botanicalName).startsWith(`${aiGenus} `));
|
|
if (byGenus) return byGenus;
|
|
}
|
|
}
|
|
|
|
const byContains = entries.find((entry) => {
|
|
const plantName = normalizeText(entry.name);
|
|
const botanical = normalizeText(entry.botanicalName);
|
|
return (aiName && (plantName.includes(aiName) || aiName.includes(plantName)))
|
|
|| (aiBotanical && (botanical.includes(aiBotanical) || aiBotanical.includes(botanical)));
|
|
});
|
|
if (byContains) return byContains;
|
|
|
|
return null;
|
|
};
|
|
|
|
const shouldUseCatalogNameOverride = ({ language, aiResult, matchedEntry }) => {
|
|
const catalogName = String(matchedEntry?.name || '').trim();
|
|
if (!catalogName) return false;
|
|
if (language !== 'en') return true;
|
|
|
|
if (isLikelyBotanicalName(catalogName, matchedEntry?.botanicalName || aiResult?.botanicalName)) {
|
|
return true;
|
|
}
|
|
|
|
if (isLikelyGermanCommonName(catalogName)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
const applyCatalogGrounding = (aiResult, catalogEntries, language = 'en') => {
|
|
const matchedEntry = findCatalogMatch(aiResult, catalogEntries);
|
|
if (!matchedEntry) {
|
|
return { grounded: false, result: aiResult };
|
|
}
|
|
|
|
const useCatalogName = shouldUseCatalogNameOverride({ language, aiResult, matchedEntry });
|
|
|
|
return {
|
|
grounded: true,
|
|
result: {
|
|
name: useCatalogName ? matchedEntry.name || aiResult.name : aiResult.name,
|
|
botanicalName: matchedEntry.botanicalName || aiResult.botanicalName,
|
|
confidence: clamp(Math.max(aiResult.confidence || 0.6, 0.78), 0.05, 0.99),
|
|
description: aiResult.description || matchedEntry.description || '',
|
|
careInfo: {
|
|
waterIntervalDays: Math.max(1, Number(matchedEntry.careInfo?.waterIntervalDays) || Number(aiResult.careInfo?.waterIntervalDays) || 7),
|
|
light: matchedEntry.careInfo?.light || aiResult.careInfo?.light || 'Unknown',
|
|
temp: matchedEntry.careInfo?.temp || aiResult.careInfo?.temp || 'Unknown',
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
module.exports = {
|
|
applyCatalogGrounding,
|
|
findCatalogMatch,
|
|
isLikelyGermanCommonName,
|
|
normalizeText,
|
|
shouldUseCatalogNameOverride,
|
|
};
|