- 只翻注释/文档字符串:`go/scanner` 剥注释后按 token 序列+literal 逐 token 比对,**54/54 文件 SAME**;
改动文件总行数与 HEAD 漂移 = 0(严格 1:1,无合并/拆行)
- 裁决编号与 design 节号逐字保留(03.4.1/03.4.2/03.4.3/14.4.1/14.4.5/07.4.2-4、design/04 §3.1/§3.2 等);
`chat_template_kwargs` / `enable_thinking` / `reasoning_effort` / `thinking:{type}` 字段名与形状说明未动
- 存疑译法(已登记):`persona` 在叙事语境用「角色」、标识符语境保留 `persona`(仓库既有模块本身混用);
`rung`→「级」;`provider` 存在「提供商/提供者」两种落笔(可按需一键统一)
- 复核:build/vet 绿 + 两包测试绿;gofmt 干净;残留英文注释 0 条
- 既有存量(未修,只报告):`model/anthropic_test.go` 612 行 >500;`applyAnthropicEvent` 115 行 >80
124 lines
3.4 KiB
Go
124 lines
3.4 KiB
Go
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 为打扰决策提供用户当前状况
|
||
// (design/12 §2)。生产接线 IoT/遥测与日历;测试注入
|
||
// mock。它必须并发安全。
|
||
type StateProvider interface {
|
||
// Current 返回用户当前的 UserState。可 nil 的错误表示「用
|
||
// 中性状态」;硬错误由调用方记日志并按
|
||
// 中性处理。
|
||
Current(ctx context.Context) (UserState, error)
|
||
}
|
||
|
||
// IoTStateProvider 是基于 iot.Manager 的尽力而为 StateProvider。它
|
||
// 扫描纳管设备,并从任何 id/名称/类型提到对应关键词的设备
|
||
// 读取心率与睡眠信号(例如 "heart"、
|
||
// "心率"、"sleep"、"睡眠")。工作/忙碌来自设备/状态标志;什么都不
|
||
// 匹配时它返回中性状态而不是错误,因此即便没有完整
|
||
// 遥测接线,服务也能运行。
|
||
type IoTStateProvider struct {
|
||
mgr *iot.Manager
|
||
log *slog.Logger
|
||
}
|
||
|
||
// NewIoTStateProvider 把 mgr 包装为 StateProvider。
|
||
func NewIoTStateProvider(mgr *iot.Manager, log *slog.Logger) *IoTStateProvider {
|
||
return &IoTStateProvider{mgr: mgr, log: logger.WithSource(baseLog(log), "services/backthink")}
|
||
}
|
||
|
||
// Current 实现 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 从设备状态 map 中按 keys 读取首个数值。
|
||
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 在 keys 中读取一个 bool(或 "sleep"/"asleep" 字符串)。
|
||
func boolState(d iot.Device, keys ...string) bool {
|
||
b, _ := boolStateOK(d, keys...)
|
||
return b
|
||
}
|
||
|
||
// boolStateOK 报告是否找到了 bool 类型的状态标志及其取值。
|
||
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
|
||
}
|