feat(v2): T-309 SQLite fall-event persistence (mattn/go-sqlite3)
Persist confirmed fall events to a local SQLite DB under artifacts/: camera host/port/channel + event id/track/source/config version/state/ latency/screenshot path/confirmed-at. Schema stores no password (guarded by test). Wire openRecorder/saveRecord into the alerts loop; failures are non-fatal. Re-applied the SILVER-POSE vendored os_windows.go patch that go mod vendor reverted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
// Package store persists confirmed fall events to a local SQLite database.
|
||||
// The database is local, untracked evidence: it records the camera host, port
|
||||
// and channel but never the password.
|
||||
package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// Record is one confirmed fall event row.
|
||||
type Record struct {
|
||||
EventID string
|
||||
TrackID string
|
||||
SourceID string
|
||||
CameraHost string
|
||||
CameraPort int
|
||||
CameraChannel string
|
||||
ConfigVersion string
|
||||
State string
|
||||
LatencySeconds float64
|
||||
ScreenshotPath string
|
||||
ConfirmedAtUTC string
|
||||
}
|
||||
|
||||
type Store struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
const schema = `
|
||||
CREATE TABLE IF NOT EXISTS fall_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_id TEXT NOT NULL,
|
||||
track_id TEXT,
|
||||
source_id TEXT,
|
||||
camera_host TEXT,
|
||||
camera_port INTEGER,
|
||||
camera_channel TEXT,
|
||||
config_version TEXT,
|
||||
state TEXT,
|
||||
latency_seconds REAL,
|
||||
screenshot_path TEXT,
|
||||
confirmed_at_utc TEXT
|
||||
);`
|
||||
|
||||
// Open opens (creating if needed) the SQLite database and ensures the schema.
|
||||
func Open(path string) (*Store, error) {
|
||||
db, err := sql.Open("sqlite3", path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open fall database: %w", err)
|
||||
}
|
||||
if _, err := db.Exec(schema); err != nil {
|
||||
_ = db.Close()
|
||||
return nil, fmt.Errorf("create fall table: %w", err)
|
||||
}
|
||||
return &Store{db: db}, nil
|
||||
}
|
||||
|
||||
// Save inserts one confirmed fall event.
|
||||
func (store *Store) Save(record Record) error {
|
||||
_, err := store.db.Exec(
|
||||
`INSERT INTO fall_events
|
||||
(event_id, track_id, source_id, camera_host, camera_port, camera_channel,
|
||||
config_version, state, latency_seconds, screenshot_path, confirmed_at_utc)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
record.EventID, record.TrackID, record.SourceID,
|
||||
record.CameraHost, record.CameraPort, record.CameraChannel,
|
||||
record.ConfigVersion, record.State, record.LatencySeconds,
|
||||
record.ScreenshotPath, record.ConfirmedAtUTC,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert fall event: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count returns the number of stored events (used by tests).
|
||||
func (store *Store) Count() (int, error) {
|
||||
var count int
|
||||
if err := store.db.QueryRow(`SELECT COUNT(*) FROM fall_events`).Scan(&count); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (store *Store) Close() error {
|
||||
if store == nil || store.db == nil {
|
||||
return nil
|
||||
}
|
||||
return store.db.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user