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,56 @@
|
||||
package pattern
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestExtractSimpleAction(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
value string
|
||||
wantAction string
|
||||
wantObject string
|
||||
wantNil bool
|
||||
}{
|
||||
{"refill english", "water_fountain", "refilled", "refill", "water_fountain", false},
|
||||
{"refill russian", "cat_water", "налил", "refill", "cat_water", false},
|
||||
{"fed cat", "cat_food", "fed", "feed", "cat_food", false},
|
||||
{"clean litter", "litter_box", "почистил", "clean", "litter_box", false},
|
||||
{"walked dog", "dog_walk", "walked", "walk", "dog_walk", false},
|
||||
{"took medicine", "medicine", "took", "take", "medicine", false},
|
||||
{"watered plants", "plants", "watered", "water", "plants", false},
|
||||
{"json value skipped", "weight", `{"kg": 82}`, "", "", true},
|
||||
{"empty value skipped", "something", "", "", "", true},
|
||||
{"unrecognized action", "door", "opened", "", "", true},
|
||||
{"case insensitive", "WATER_FOUNTAIN", "REFILLED", "refill", "water_fountain", false},
|
||||
{"whitespace trimmed", " cat_bed ", " cleaned ", "clean", "cat_bed", false},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ev := Extract(42, tc.key, tc.value, now)
|
||||
if tc.wantNil {
|
||||
if ev != nil {
|
||||
t.Fatalf("want nil, got %+v", ev)
|
||||
}
|
||||
return
|
||||
}
|
||||
if ev == nil {
|
||||
t.Fatal("want event, got nil")
|
||||
}
|
||||
if ev.Action != tc.wantAction {
|
||||
t.Fatalf("action: want %q, got %q", tc.wantAction, ev.Action)
|
||||
}
|
||||
if ev.Object != tc.wantObject {
|
||||
t.Fatalf("object: want %q, got %q", tc.wantObject, ev.Object)
|
||||
}
|
||||
if ev.FactID != 42 {
|
||||
t.Fatalf("fact_id: want 42, got %d", ev.FactID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user