diff --git a/internal/app/replyclassify/returndate.go b/internal/app/replyclassify/returndate.go index d0070d1aa..e64755fa2 100644 --- a/internal/app/replyclassify/returndate.go +++ b/internal/app/replyclassify/returndate.go @@ -109,7 +109,13 @@ var endCues = map[string]bool{"bis": true, "bis zum": true, "bis einschliesslich // rangeBreak is a sentence end or a return word between an "ab" date and a // "bis" date, which makes them two statements instead of one range. -var rangeBreak = regexp.MustCompile(`[a-z][.!?;:](\s|$)|[!?;]|\b(wieder|zurueck|erreichbar|buero|da)\b`) +var rangeBreak = regexp.MustCompile(sentenceMark + `|[a-z][.!?;:](\s|$)|[!?;]|\b(wieder|zurueck|erreichbar|buero|da)\b`) + +// sentenceMark stands in for a sentence that opens on "Bis" after a date's +// dot ("ab dem 4.9. Bis zum 11.9. vertritt mich"), which lower-casing hides. +const sentenceMark = "\x00" + +var sentenceStartBis = regexp.MustCompile(`([.!?;:])(\s+)Bis\b`) // rangeNoise is taken out before rangeBreak reads the text: an absence said // as a negated return word ("ab dem 7.9. nicht erreichbar, bis 18.9.") and a @@ -319,6 +325,7 @@ func foldAccents(s string) string { // "KW" the week cues and pattern read, and "KW42" spaced so a cue ending in // "kw" still ends on a word boundary. func normalizeForDates(s string) string { + s = sentenceStartBis.ReplaceAllString(s, "$1"+sentenceMark+"$2Bis") return weekDigits.ReplaceAllString(strings.ReplaceAll(foldAccents(s), "kalenderwoche", "kw"), "kw $1") } diff --git a/internal/app/replyclassify/returndate_test.go b/internal/app/replyclassify/returndate_test.go index 07191efaa..105c0d1d2 100644 --- a/internal/app/replyclassify/returndate_test.go +++ b/internal/app/replyclassify/returndate_test.go @@ -76,6 +76,7 @@ func TestParseReturnDate(t *testing.T) { {name: "a month abbreviation keeps the range", subject: "Abwesenheitsnotiz", body: "Ich bin ab dem 4. Sept. bis zum 18. Sept. im Urlaub.", want: "2026-09-18"}, {name: "a return word ends the range", subject: "Abwesenheitsnotiz", body: "Ab dem 14.9. bin ich wieder erreichbar, bis zum 30.9. jedoch nur eingeschränkt.", want: "2026-09-14"}, {name: "a sentence ends the range", subject: "Abwesenheitsnotiz", body: "Ab Montag, 14.9., bin ich im Haus. Bis 30.9. gilt unser Aktionspreis.", want: "2026-09-14"}, + {name: "a sentence opening on bis after a date ends the range", subject: "Abwesenheitsnotiz", body: "Ich bin ab dem 4.9. Bis zum 18.9. vertritt mich Frau Klein, danach Herr Braun.", want: "2026-09-04"}, {name: "a bis before the return date is not its end", subject: "Abwesenheitsnotiz", body: "Ab dem 14.9. wieder da. Bis zum 11.9. vertritt mich Frau Klein.", want: "2026-09-14"}, {name: "a week already over is not a return date", subject: "Abwesenheitsnotiz", body: "Ich bin bis KW 30 im Urlaub."}, {name: "a week that does not exist", subject: "Abwesenheitsnotiz", body: "Ich bin ab KW 60 wieder da."},