Files
Cyrene_v2/internal/services/backthink/state.go
T
AskaEth 3dc1232bc2
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
docs(comments): 注释中文化 · internal/runtime/model + internal/services/backthink(54 文件,纯替换)
- 只翻注释/文档字符串:`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
2026-10-04 16:27:54 +08:00

124 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}