/// evolve/lesson.mbt —— 失败回流学习(learn-from-reject)的统一持久化层
///
/// 将「把一次被打回/失败的原因归档成一条 [lesson]<类目> 资产」这一语义收拢到此包,
/// 供以下两处共用(避免重复造轮子、避免跨包循环依赖):
/// - server/evolve_lesson.mbt:独立 `evolve_lesson` 工具的完整路径(内存 Archive + 落库)
/// - engine/omega_strong.mbt:Omega 打回时自动回流(仅落库,不经内存 Archive)
///
/// 落库都走 `store.evolve_upsert` 写 evolve_artifacts 表,goal 带 `[lesson]` 前缀,
/// 使 plan/claim 的 inject 与 dead_ends 可直接检索到「踩过的坑」。
///|
/// 构造 lesson 类目目标串:goal 前加 `[lesson]` 前缀(幂等,已有前缀则原样返回)。
pub fn lesson_goal(cat : String) -> String {
let c = cat.trim().to_string()
if c.has_prefix("[lesson]") {
c
} else {
"[lesson] " + c
}
}
///|
/// 从时间戳提取 id 片段(去分隔符、取前 16 位),用于生成可读且趋唯一的 lesson id。
pub fn lesson_ts_part(now : String) -> String {
let compact = now
.replace(old="-", new="")
.replace(old=":", new=".")
.replace(old="T", new=".")
if compact.length() > 16 {
compact.substring(start=0, end=16)
} else {
compact
}
}
///|
/// lesson 分数默认 0.5(教训典型中性样本,不参与 best,但参与死路地图权重)。
pub fn score_of_lesson(score : Double) -> Double {
if score == 0.0 {
0.5
} else {
score
}
}
///|
/// 把一条教训持久化为 [lesson] 资产(域无关,仅落库 evolve_artifacts)。
/// - store 为数据库句柄(None = 内存态,返回 false)
/// - id 由调用方生成并保证趋唯一(重复 id 走 upsert 覆盖,避免多轮打回互相覆盖)
/// - note 存失败原因、code 存纠偏提示(为空则取 reason)
/// 返回是否成功落库。
pub fn evolve_lesson_persist(
store : @store.SqliteStore?,
id~ : String,
cat : String,
reason : String,
fix : String,
now : String,
score? : Double = 0.5,
) -> Bool {
let goal = lesson_goal(cat)
let code = if fix == "" { reason } else { fix }
let sc = score_of_lesson(score)
match store {
None => false
Some(s) =>
match
s.evolve_upsert(
id~,
parent_id="",
goal~,
note=reason.to_string(),
code~,
score=sc,
parts_json="{}",
created_at=now,
) {
Ok(_) => true
Err(_) => false
}
}
}