router: read the hour he said out loud (V-469)
"напомни мне позвонить маме в семь вечера" answered "не получилось разобрать время напоминания", while "в 19:00" set the reminder. Reminders arrive through speech, and speech says the hour in words, so this was the ordinary case failing and the typed one working. SpellOutDigits rewrites a spoken number as digits, but only when a time word stands beside it — "в три часа" becomes "в 3 часа" and "купить три яблока" is left alone. Both parsers see it: dateparser already rewrites "7 вечера" to "7 pm" and never saw a digit to rewrite, and the stub floor now reads the qualifier itself.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
package router
|
||||
|
||||
import "strings"
|
||||
|
||||
// ruNumerals — spoken numbers as digits, for the clock hours and the minutes
|
||||
// that follow them. Every case ending he might say is listed rather than
|
||||
// stemmed: "в семь", "к семи", "около семи" are three forms of one hour, and a
|
||||
// prefix rule short enough to cover them also matches "семья".
|
||||
//
|
||||
// Stops at thirty, which is as far as a spoken time goes ("без двадцати
|
||||
// восемь", "в половине шестого"). Anything larger is said in digits.
|
||||
var ruNumerals = map[string]string{
|
||||
"один": "1", "одного": "1", "одну": "1", "час": "1", "часу": "1",
|
||||
"два": "2", "две": "2", "двух": "2",
|
||||
"три": "3", "трёх": "3", "трех": "3",
|
||||
"четыре": "4", "четырёх": "4", "четырех": "4",
|
||||
"пять": "5", "пяти": "5",
|
||||
"шесть": "6", "шести": "6",
|
||||
"семь": "7", "семи": "7",
|
||||
"восемь": "8", "восьми": "8",
|
||||
"девять": "9", "девяти": "9",
|
||||
"десять": "10", "десяти": "10",
|
||||
"одиннадцать": "11", "одиннадцати": "11",
|
||||
"двенадцать": "12", "двенадцати": "12",
|
||||
"тринадцать": "13", "тринадцати": "13",
|
||||
"четырнадцать": "14", "четырнадцати": "14",
|
||||
"пятнадцать": "15", "пятнадцати": "15",
|
||||
"шестнадцать": "16", "шестнадцати": "16",
|
||||
"семнадцать": "17", "семнадцати": "17",
|
||||
"восемнадцать": "18", "восемнадцати": "18",
|
||||
"девятнадцать": "19", "девятнадцати": "19",
|
||||
"двадцать": "20", "двадцати": "20",
|
||||
"тридцать": "30", "тридцати": "30",
|
||||
"сорок": "40", "сорока": "40",
|
||||
"пятьдесят": "50", "пятидесяти": "50",
|
||||
}
|
||||
|
||||
// numeralContext — the words that make a numeral a time. A numeral is only
|
||||
// rewritten when one of these sits next to it, so "три яблока" in a note is
|
||||
// left alone and "в три часа" is not.
|
||||
var numeralContext = map[string]bool{
|
||||
"в": true, "во": true, "к": true, "около": true, "на": true,
|
||||
"часа": true, "часов": true, "час": true, "часу": true,
|
||||
"утра": true, "вечера": true, "дня": true, "ночи": true,
|
||||
"минут": true, "минуты": true, "минуту": true,
|
||||
"at": true, "by": true,
|
||||
}
|
||||
|
||||
// SpellOutDigits rewrites spoken numbers as digits so the date parsers see the
|
||||
// shape they know.
|
||||
//
|
||||
// "напомни мне позвонить маме в семь вечера" parsed to nothing, while "в 19:00"
|
||||
// parsed fine (Vikunja #469). Speech is where reminders come from, and speech
|
||||
// says the hour in words, so this is not a long-tail case — it is the ordinary
|
||||
// one. dateparser reads "в 7 вечера" through the qualifier rewrite the python
|
||||
// script already does; it does not read "в семь вечера".
|
||||
//
|
||||
// Conservative by construction: a numeral is only rewritten when a time word
|
||||
// stands beside it. "три часа" becomes "3 часа"; "три яблока" stays as it is,
|
||||
// and a note or a fact carrying a spoken number is untouched.
|
||||
func SpellOutDigits(text string) string {
|
||||
toks := strings.Fields(text)
|
||||
if len(toks) == 0 {
|
||||
return text
|
||||
}
|
||||
out := make([]string, len(toks))
|
||||
copy(out, toks)
|
||||
for i, tok := range toks {
|
||||
key := strings.ToLower(strings.Trim(tok, ".,!?;:«»\"'"))
|
||||
digit, ok := ruNumerals[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// "час" and "часу" are the hour noun as often as they are the number
|
||||
// one, and rewriting "в час дня" to "в 1 дня" is right either way. What
|
||||
// must not happen is rewriting the noun that gives another numeral its
|
||||
// context: "в семь часов" must keep "часов".
|
||||
if !hasTimeNeighbour(toks, i) {
|
||||
continue
|
||||
}
|
||||
out[i] = digit
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
// hasTimeNeighbour reports whether the token before or after i is a time word.
|
||||
func hasTimeNeighbour(toks []string, i int) bool {
|
||||
for _, j := range []int{i - 1, i + 1} {
|
||||
if j < 0 || j >= len(toks) {
|
||||
continue
|
||||
}
|
||||
if numeralContext[strings.ToLower(strings.Trim(toks[j], ".,!?;:«»\"'"))] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user