Compare commits
3 Commits
d823143611
...
6733a50cfb
| Author | SHA1 | Date | |
|---|---|---|---|
| 6733a50cfb | |||
| d9113bb89a | |||
| d2567e158c |
@@ -464,6 +464,79 @@ export class ReceiptImportService {
|
|||||||
if (hit) return hit;
|
if (hit) return hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Regel: Grädde/matlagningsgrädde (icke-allergi) ─────────────────
|
||||||
|
const hasCreamSignal =
|
||||||
|
/\bvispgradde\b/.test(normalized) ||
|
||||||
|
/\bmatlagningsgradde\b/.test(normalized) ||
|
||||||
|
/\bgradde\b/.test(normalized) ||
|
||||||
|
/\bcreme\s+fraiche\b/.test(normalized) ||
|
||||||
|
/\bgraddfil\b/.test(normalized);
|
||||||
|
|
||||||
|
const hasPlantOrAllergySignal =
|
||||||
|
/\blaktosfri\b/.test(normalized) ||
|
||||||
|
/\bvegetabilisk\b/.test(normalized) ||
|
||||||
|
/\bhavre\b/.test(normalized) ||
|
||||||
|
/\bsoja\b/.test(normalized) ||
|
||||||
|
/\brisdryck\b/.test(normalized) ||
|
||||||
|
/\bplant\b/.test(normalized);
|
||||||
|
|
||||||
|
if (hasCreamSignal && !hasPlantOrAllergySignal) {
|
||||||
|
const l2CookingDairy = findCategory({
|
||||||
|
name: 'matlagning',
|
||||||
|
startsWith: 'mejeri, ost & ägg > ',
|
||||||
|
});
|
||||||
|
const hit = toSuggestion(l2CookingDairy, 'high');
|
||||||
|
if (hit) return hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Regel: Vanlig mjölk (inte laktosfri/allergi) ───────────────────
|
||||||
|
const hasMilkSignal =
|
||||||
|
/\bmjolk\b/.test(normalized) ||
|
||||||
|
/\bstandardmjolk\b/.test(normalized) ||
|
||||||
|
/\bstandmjolk\b/.test(normalized) ||
|
||||||
|
/\besl\b/.test(normalized);
|
||||||
|
const hasLactoseFreeSignal =
|
||||||
|
/\blaktosfri\b/.test(normalized) ||
|
||||||
|
/\blactose\s*free\b/.test(normalized);
|
||||||
|
|
||||||
|
if (hasMilkSignal && !hasPlantOrAllergySignal && !hasLactoseFreeSignal) {
|
||||||
|
const l3StandardMilk = findCategory({
|
||||||
|
name: 'standardmjölk',
|
||||||
|
startsWith: 'mejeri, ost & ägg > mjölk > ',
|
||||||
|
});
|
||||||
|
const hit = toSuggestion(l3StandardMilk, 'high');
|
||||||
|
if (hit) return hit;
|
||||||
|
|
||||||
|
const l2Milk = findCategory({
|
||||||
|
name: 'mjölk',
|
||||||
|
startsWith: 'mejeri, ost & ägg > ',
|
||||||
|
});
|
||||||
|
const fallbackHit = toSuggestion(l2Milk, 'high');
|
||||||
|
if (fallbackHit) return fallbackHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Regel: Ägg (saknar egen L2/L3 i nuvarande träd) ────────────────
|
||||||
|
const hasEggSignal =
|
||||||
|
/\bagg\b/.test(normalized) ||
|
||||||
|
/\begg\b/.test(normalized) ||
|
||||||
|
/\binne\b/.test(normalized) ||
|
||||||
|
/\b24p\b/.test(normalized);
|
||||||
|
|
||||||
|
if (hasEggSignal) {
|
||||||
|
const l1DairyEgg = categories.find(
|
||||||
|
(c) => c.path.toLowerCase() === 'mejeri, ost & ägg',
|
||||||
|
);
|
||||||
|
if (l1DairyEgg) {
|
||||||
|
return {
|
||||||
|
categoryId: l1DairyEgg.id,
|
||||||
|
categoryName: l1DairyEgg.name,
|
||||||
|
path: l1DairyEgg.path,
|
||||||
|
confidence: 'high',
|
||||||
|
usedFallback: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Regel: Te ────────────────────────────────────────────────────────
|
// ── Regel: Te ────────────────────────────────────────────────────────
|
||||||
const isTea =
|
const isTea =
|
||||||
/\bte\b/.test(normalized) ||
|
/\bte\b/.test(normalized) ||
|
||||||
@@ -583,8 +656,7 @@ export class ReceiptImportService {
|
|||||||
/\bkarr[eé]\b/.test(normalized) ||
|
/\bkarr[eé]\b/.test(normalized) ||
|
||||||
/\bkotlett\b/.test(normalized);
|
/\bkotlett\b/.test(normalized);
|
||||||
|
|
||||||
if (!hasPorkSignal) return suggestion;
|
if (hasPorkSignal) {
|
||||||
|
|
||||||
const aiPath = suggestion.path.toLowerCase();
|
const aiPath = suggestion.path.toLowerCase();
|
||||||
const isClearlyWrongBranch =
|
const isClearlyWrongBranch =
|
||||||
aiPath.includes('köttbullar & färsprodukter') || aiPath.includes('köttfärs');
|
aiPath.includes('köttbullar & färsprodukter') || aiPath.includes('köttfärs');
|
||||||
@@ -609,4 +681,102 @@ export class ReceiptImportService {
|
|||||||
usedFallback: true,
|
usedFallback: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasMilkSignal =
|
||||||
|
/\bmjolk\b/.test(normalized) ||
|
||||||
|
/\bstandardmjolk\b/.test(normalized) ||
|
||||||
|
/\bstandmjolk\b/.test(normalized) ||
|
||||||
|
/\besl\b/.test(normalized);
|
||||||
|
const hasLactoseFreeSignal =
|
||||||
|
/\blaktosfri\b/.test(normalized) ||
|
||||||
|
/\blactose\s*free\b/.test(normalized);
|
||||||
|
|
||||||
|
if (hasMilkSignal && !hasLactoseFreeSignal) {
|
||||||
|
const isWrongLactoseFreeBranch =
|
||||||
|
suggestion.path.toLowerCase().includes('allergi mejeri > laktosfri mjölk');
|
||||||
|
if (isWrongLactoseFreeBranch) {
|
||||||
|
const l3StandardMilk = categories.find(
|
||||||
|
(c) =>
|
||||||
|
c.name.toLowerCase() === 'standardmjölk' &&
|
||||||
|
c.path.toLowerCase().startsWith('mejeri, ost & ägg > mjölk > '),
|
||||||
|
);
|
||||||
|
if (l3StandardMilk) {
|
||||||
|
this.logger.log(
|
||||||
|
`AI contradiction-guard: "${rawName}" remappas från "${suggestion.path}" till "${l3StandardMilk.path}"`,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
categoryId: l3StandardMilk.id,
|
||||||
|
categoryName: l3StandardMilk.name,
|
||||||
|
path: l3StandardMilk.path,
|
||||||
|
confidence: 'high',
|
||||||
|
usedFallback: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasEggSignal =
|
||||||
|
/\bagg\b/.test(normalized) ||
|
||||||
|
/\begg\b/.test(normalized) ||
|
||||||
|
/\binne\b/.test(normalized) ||
|
||||||
|
/\b24p\b/.test(normalized);
|
||||||
|
|
||||||
|
if (hasEggSignal && suggestion.path.toLowerCase().includes('allergi mejeri')) {
|
||||||
|
const l1DairyEgg = categories.find(
|
||||||
|
(c) => c.path.toLowerCase() === 'mejeri, ost & ägg',
|
||||||
|
);
|
||||||
|
if (l1DairyEgg) {
|
||||||
|
this.logger.log(
|
||||||
|
`AI contradiction-guard: "${rawName}" remappas från "${suggestion.path}" till "${l1DairyEgg.path}"`,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
categoryId: l1DairyEgg.id,
|
||||||
|
categoryName: l1DairyEgg.name,
|
||||||
|
path: l1DairyEgg.path,
|
||||||
|
confidence: 'high',
|
||||||
|
usedFallback: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasCreamSignal =
|
||||||
|
/\bvispgradde\b/.test(normalized) ||
|
||||||
|
/\bmatlagningsgradde\b/.test(normalized) ||
|
||||||
|
/\bgradde\b/.test(normalized) ||
|
||||||
|
/\bcreme\s+fraiche\b/.test(normalized) ||
|
||||||
|
/\bgraddfil\b/.test(normalized);
|
||||||
|
const hasPlantOrAllergySignal =
|
||||||
|
/\blaktosfri\b/.test(normalized) ||
|
||||||
|
/\bvegetabilisk\b/.test(normalized) ||
|
||||||
|
/\bhavre\b/.test(normalized) ||
|
||||||
|
/\bsoja\b/.test(normalized) ||
|
||||||
|
/\brisdryck\b/.test(normalized) ||
|
||||||
|
/\bplant\b/.test(normalized);
|
||||||
|
|
||||||
|
if (hasCreamSignal && !hasPlantOrAllergySignal) {
|
||||||
|
const aiPath = suggestion.path.toLowerCase();
|
||||||
|
const isOutsideDairy = !aiPath.startsWith('mejeri, ost & ägg > matlagning');
|
||||||
|
if (!isOutsideDairy) return suggestion;
|
||||||
|
|
||||||
|
const l2CookingDairy = categories.find(
|
||||||
|
(c) =>
|
||||||
|
c.name.toLowerCase() === 'matlagning' &&
|
||||||
|
c.path.toLowerCase() === 'mejeri, ost & ägg > matlagning',
|
||||||
|
);
|
||||||
|
if (!l2CookingDairy) return suggestion;
|
||||||
|
|
||||||
|
this.logger.log(
|
||||||
|
`AI contradiction-guard: "${rawName}" remappas från "${suggestion.path}" till "${l2CookingDairy.path}"`,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
categoryId: l2CookingDairy.id,
|
||||||
|
categoryName: l2CookingDairy.name,
|
||||||
|
path: l2CookingDairy.path,
|
||||||
|
confidence: 'high',
|
||||||
|
usedFallback: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user