Files
Maven/internal/ttsnorm/ttsnorm.go
T
kami 4c40a183cf feat: event store, pattern inference, and routine proposals
- Add events table (migration #4): stores normalized (action, object, ts)
  triples extracted from facts, indexed for recurrence detection.
- Add proposed_routines table: stores inferred recurring patterns with
  proposed/accepted/dismissed status and optional linked reminder.
- Add pattern package: Extractor normalizes fact text into (action, object)
  pairs with TTS normalization; Detector groups events to find recurring
  patterns and proposes routines.
- Add internal/ttsnorm: text normalization pipeline for Russian/English
  (lowercase, punctuation strip, number normalization, stopword removal).
- Add chat seed file for LLM phraser.
2026-07-10 15:49:03 +04:00

59 lines
1.9 KiB
Go

// Package ttsnorm rewrites machine-formatted dates/times/numbers into RU text
// a TTS voice speaks naturally — so "10.07.2026" is not read as "number dot
// number dot number". Pure, deterministic; runs on reply/nudge text before synth.
package ttsnorm
import (
"regexp"
"strconv"
"strings"
)
var months = [...]string{"", "января", "февраля", "марта", "апреля", "мая",
"июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"}
var (
reDateY = regexp.MustCompile(`\b(\d{1,2})\.(\d{1,2})\.(\d{4})\b`)
reDate = regexp.MustCompile(`\b(\d{1,2})\.(\d{1,2})\b`)
reTime = regexp.MustCompile(`\b(\d{1,2}):(\d{2})\b`)
reDots = regexp.MustCompile(`\b\d+(?:\.\d+){2,}\b`)
)
// Speakable rewrites d.m.y, d.m, h:mm, and residual dotted-number runs.
// Order matters: dates with year first, then times, then multi-dot numbers
// (3+ parts — never valid dates), then 2-part dates.
func Speakable(s string) string {
s = reDateY.ReplaceAllStringFunc(s, func(m string) string {
p := reDateY.FindStringSubmatch(m)
return spokenDate(p[1], p[2], p[3])
})
s = reTime.ReplaceAllStringFunc(s, func(m string) string {
p := reTime.FindStringSubmatch(m)
return p[1] + " часов " + p[2] + " минут"
})
s = reDots.ReplaceAllStringFunc(s, func(m string) string {
return strings.Join(strings.Split(m, "."), " точка ")
})
s = reDate.ReplaceAllStringFunc(s, func(m string) string {
p := reDate.FindStringSubmatch(m)
return spokenDate(p[1], p[2], "")
})
return s
}
func spokenDate(dd, mm, yyyy string) string {
mi, _ := strconv.Atoi(mm)
if mi < 1 || mi > 12 {
return dd + " " + mm + gap(yyyy)
}
day := strconv.Itoa(mustInt(dd))
out := day + " " + months[mi]
if yyyy != "" {
out += " " + yyyy
}
return out
}
func mustInt(s string) int { n, _ := strconv.Atoi(s); return n }
func gap(y string) string { if y == "" { return "" }; return " " + y }