diff --git a/controllers/worshipController.js b/controllers/worshipController.js index 91ba64c..80b2c2d 100644 --- a/controllers/worshipController.js +++ b/controllers/worshipController.js @@ -497,22 +497,62 @@ function resolveEventPlaceIdFromHeader(eventPlaces, headerCell) { if (!raw) return null; const nameOnly = normalizeText(raw.split('(')[0]); const normalized = nameOnly.toLowerCase(); + const places = eventPlaces || []; - // Prefer exact name match. - const exact = (eventPlaces || []).find((p) => normalizeText(p.name).toLowerCase() === normalized); + // 1) Schnellpfad: exakter Name. + const exact = places.find((p) => normalizeText(p.name).toLowerCase() === normalized); if (exact) return exact.id; - // Fallback: contains. - const contains = (eventPlaces || []).find((p) => normalizeText(p.name).toLowerCase().includes(normalized)); - if (contains) return contains.id; + // 2) DB-basierter Score statt harter Sonderfalllisten. + const tokens = normalized + .replace(/[^a-z0-9äöüß\- ]/gi, ' ') + .split(/\s+/) + .filter((t) => t.length >= 3); - // Hardcoded fallbacks for known CSV headers. + const scorePlace = (placeName) => { + const n = normalizeText(placeName).toLowerCase(); + let score = 0; + + // Starker Treffer für ganze Header-Zeile im Ortsnamen. + if (n.includes(normalized)) score += 120; + + // Token-Überlappung (Ortsteile etc.). + for (const t of tokens) { + if (n.includes(t)) score += 20; + } + + // Für Gottesdienst-Orte: Kirche bevorzugen, Gemeinde* leicht abwerten. + if (/\bkirche\b/.test(n)) score += 40; + if (/\bevangelisch/.test(n)) score += 15; + if (/\bgemeindeb[uü]ro\b/.test(n)) score -= 25; + if (/\bgemeindehaus\b/.test(n)) score -= 20; + if (/\bgemeindezentrum\b/.test(n)) score -= 20; + + return score; + }; + + let best = null; + let bestScore = -Infinity; + for (const place of places) { + const s = scorePlace(place.name || ''); + if (s > bestScore) { + bestScore = s; + best = place; + } + } + + // Mindestschwelle: verhindert Zufallstreffer ohne Ortsbezug. + if (best && bestScore >= 25) { + return best.id; + } + + // 3) Kleiner, defensiver Fallback für bekannte Ortsnamen. if (/am b[üu]gel/i.test(raw)) return 12; - if (/bonames/i.test(raw)) return 7; - if (/kalbach/i.test(raw)) return 2; if (/nieder-eschbach/i.test(raw)) return 14; - if (/harheim/i.test(raw)) return 15; if (/nieder-erlenbach/i.test(raw)) return 13; + if (/harheim/i.test(raw)) return 15; + if (/kalbach/i.test(raw)) return 11; + if (/bonames/i.test(raw)) return 1; return null; }