feat(backthink): 后台自主思考(heartbeat+事件触发 + 打扰决策 fail-closed + 主动推送活跃→IM 兜底 + 限制 + 思考记录)
This commit is contained in:
1 parent
05e23ea590
commit
6f97fded3a
13 files changed
+1968
No files matched your search
@@ -0,0 +1,123 @@
|
||||
package backthink
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/iot"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||||
)
|
||||
|
||||
// StateProvider supplies the user's current condition for the interrupt decision
|
||||
// (design/12 §2). Production wires IoT/telemetry and calendar; tests inject a
|
||||
// mock. It must be concurrency-safe.
|
||||
type StateProvider interface {
|
||||
// Current returns the user's current UserState. A nil-able error means "use
|
||||
// a neutral state"; a hard error is logged by the caller and treated as
|
||||
// neutral.
|
||||
Current(ctx context.Context) (UserState, error)
|
||||
}
|
||||
|
||||
// IoTStateProvider is a best-effort StateProvider built over an iot.Manager. It
|
||||
// scans the managed devices and reads heart-rate and sleep signals from any
|
||||
// device whose id/name/type mentions the corresponding keyword (e.g. "heart",
|
||||
// "心率", "sleep", "睡眠"). Work/busy come from a device/state flag; when nothing
|
||||
// matches it returns a neutral state rather than an error, so the service still
|
||||
// runs without full telemetry wiring.
|
||||
type IoTStateProvider struct {
|
||||
mgr *iot.Manager
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
// NewIoTStateProvider wraps mgr as a StateProvider.
|
||||
func NewIoTStateProvider(mgr *iot.Manager, log *slog.Logger) *IoTStateProvider {
|
||||
return &IoTStateProvider{mgr: mgr, log: logger.WithSource(baseLog(log), "services/backthink")}
|
||||
}
|
||||
|
||||
// Current implements StateProvider.
|
||||
func (p *IoTStateProvider) Current(ctx context.Context) (UserState, error) {
|
||||
if p.mgr == nil {
|
||||
return UserState{}, fmt.Errorf("backthink: state: %w", iot.ErrProviderNotRegistered)
|
||||
}
|
||||
devices, err := p.mgr.List(ctx)
|
||||
if err != nil {
|
||||
return UserState{}, fmt.Errorf("backthink: state: list: %w", err)
|
||||
}
|
||||
|
||||
var state UserState
|
||||
for _, d := range devices {
|
||||
key := strings.ToLower(d.ID + " " + d.Name + " " + d.Type)
|
||||
switch {
|
||||
case strings.Contains(key, "heart") || strings.Contains(key, "心率"):
|
||||
if v, ok := numState(d, "bpm", "heart_rate", "rate"); ok {
|
||||
state.HeartRateBPM = v
|
||||
}
|
||||
case strings.Contains(key, "sleep") || strings.Contains(key, "睡眠"):
|
||||
state.Sleeping = boolState(d, "sleeping", "asleep", "state")
|
||||
}
|
||||
if b, ok := boolStateOK(d, "working"); ok {
|
||||
state.Working = b
|
||||
}
|
||||
if b, ok := boolStateOK(d, "busy"); ok {
|
||||
state.Busy = b
|
||||
}
|
||||
}
|
||||
if p.log != nil {
|
||||
p.log.Debug("backthink iot state sampled", "devices", len(devices), "state", state.String())
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// numState reads the first numeric value among keys from a device's state map.
|
||||
func numState(d iot.Device, keys ...string) (int, bool) {
|
||||
for _, k := range keys {
|
||||
v, ok := d.State[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch n := v.(type) {
|
||||
case int:
|
||||
return n, true
|
||||
case int64:
|
||||
return int(n), true
|
||||
case float64:
|
||||
return int(n), true
|
||||
case string:
|
||||
if f, err := strconv.Atoi(n); err == nil {
|
||||
return f, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// boolState reads a bool (or a "sleep"/"asleep" string) among keys.
|
||||
func boolState(d iot.Device, keys ...string) bool {
|
||||
b, _ := boolStateOK(d, keys...)
|
||||
return b
|
||||
}
|
||||
|
||||
// boolStateOK reports whether a bool-typed state flag was found and its value.
|
||||
func boolStateOK(d iot.Device, keys ...string) (bool, bool) {
|
||||
for _, k := range keys {
|
||||
v, ok := d.State[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch n := v.(type) {
|
||||
case bool:
|
||||
return n, true
|
||||
case string:
|
||||
switch strings.ToLower(n) {
|
||||
case "sleep", "asleep", "busy", "working", "yes", "true":
|
||||
return true, true
|
||||
case "awake", "free", "idle", "no", "false":
|
||||
return false, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
Reference in new issue
Block a user