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.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
// Package pattern extracts normalized events from facts and detects recurring
|
||||
// patterns to propose as routines (recurring reminders).
|
||||
//
|
||||
// The pipeline: fact write → extractor (action+object) → detector (intervals) →
|
||||
// proposed routine → human confirms → recurring reminder.
|
||||
package pattern
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Event is a normalized, derived observation — the output of the extractor
|
||||
// and the input to the pattern detector.
|
||||
type Event struct {
|
||||
FactID int64
|
||||
Action string // normalized action, e.g. "refill"
|
||||
Object string // normalized object, e.g. "cat_water_fountain"
|
||||
Ts time.Time
|
||||
}
|
||||
|
||||
// actionLexicon maps observed value words to canonical actions. The key is the
|
||||
// canonical form; the values are observed inflections/alternatives (lowercase).
|
||||
// Pure additive — unrecognized values silently produce no event (false
|
||||
// negative), which is harmless.
|
||||
var actionLexicon = map[string][]string{
|
||||
"refill": {"refilled", "refill", "fills", "filling", "заправил", "налил", "долил", "пополнил"},
|
||||
"feed": {"fed", "feed", "feeds", "feeding", "покормил", "кормил", "покормить"},
|
||||
"change": {"changed", "change", "changes", "changing", "поменял", "сменил", "заменил"},
|
||||
"clean": {"cleaned", "clean", "cleans", "cleaning", "почистил", "убрал", "убирал", "помыл", "мыл"},
|
||||
"take": {"took", "take", "takes", "taking", "принял", "выпил", "пил", "съел", "ел"},
|
||||
"walk": {"walked", "walk", "walks", "walking", "гулял", "выгулял", "прогулка"},
|
||||
"water": {"watered", "water", "waters", "watering", "полил", "поливал"},
|
||||
}
|
||||
|
||||
// invertLexicon builds a fast map from any variant → canonical action.
|
||||
var variantToAction map[string]string
|
||||
|
||||
func init() {
|
||||
variantToAction = make(map[string]string)
|
||||
for canonical, variants := range actionLexicon {
|
||||
for _, v := range variants {
|
||||
variantToAction[v] = canonical
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract attempts to normalize a fact (key + value) into an Event.
|
||||
// Returns nil when the fact doesn't describe a recognizable action —
|
||||
// structured JSON values, empty values, and unrecognized actions all
|
||||
// produce no event (false negative by design).
|
||||
//
|
||||
// Rules:
|
||||
// - If value starts with '{' or '[' (JSON), skip — it's structured data,
|
||||
// not an action statement.
|
||||
// - The value (trimmed, lowered) is looked up in the action lexicon.
|
||||
// - The key (trimmed, lowered) becomes the object.
|
||||
// - Both action and object must be non-empty.
|
||||
func Extract(factID int64, key, value string, ts time.Time) *Event {
|
||||
v := strings.TrimSpace(value)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
// Skip structured JSON values — measurements, config, etc.
|
||||
if v[0] == '{' || v[0] == '[' {
|
||||
return nil
|
||||
}
|
||||
|
||||
action, ok := variantToAction[strings.ToLower(v)]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
object := strings.TrimSpace(strings.ToLower(key))
|
||||
if object == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Event{
|
||||
FactID: factID,
|
||||
Action: action,
|
||||
Object: object,
|
||||
Ts: ts,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user