4c40a183cf
- 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.
62 lines
1.9 KiB
Go
62 lines
1.9 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Event — a normalized, derived observation from a fact. An event has an action
|
|
// (what happened, e.g. "refill") and an object (what it happened to, e.g.
|
|
// "cat_water_fountain"). Events are lossy — not every fact produces one.
|
|
// They are the input to the pattern detector.
|
|
type Event struct {
|
|
ID int64
|
|
FactID int64
|
|
Action string
|
|
Object string
|
|
Ts time.Time
|
|
}
|
|
|
|
// CreateEvent persists a derived event from a fact. Best-effort: the caller
|
|
// (the pattern extractor) decides whether the fact warrants an event; this
|
|
// just writes the row. Not every fact produces an event.
|
|
func (s *Store) CreateEvent(ctx context.Context, factID int64, action, object string, ts time.Time) (int64, error) {
|
|
res, err := s.db.ExecContext(ctx,
|
|
`INSERT INTO events (fact_id, action, object, ts) VALUES (?,?,?,?)`,
|
|
factID, action, object, ts.UnixMilli())
|
|
if err != nil {
|
|
return 0, fmt.Errorf("create event: %w", err)
|
|
}
|
|
id, err := res.LastInsertId()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("create event: last insert id: %w", err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// EventsFor returns all events matching action+object, ordered by ts ascending
|
|
// (oldest first — the order the pattern detector needs for interval computation).
|
|
func (s *Store) EventsFor(ctx context.Context, action, object string) ([]Event, error) {
|
|
rows, err := s.db.QueryContext(ctx, `
|
|
SELECT id, fact_id, action, object, ts
|
|
FROM events
|
|
WHERE action = ? AND object = ?
|
|
ORDER BY ts ASC, id ASC`, action, object)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("events for %s/%s: %w", action, object, err)
|
|
}
|
|
defer rows.Close()
|
|
var out []Event
|
|
for rows.Next() {
|
|
var e Event
|
|
var tsMillis int64
|
|
if err := rows.Scan(&e.ID, &e.FactID, &e.Action, &e.Object, &tsMillis); err != nil {
|
|
return nil, err
|
|
}
|
|
e.Ts = time.UnixMilli(tsMillis).UTC()
|
|
out = append(out, e)
|
|
}
|
|
return out, rows.Err()
|
|
}
|