新增: 批次工作落地(推进链/评估闭环/事件总线/并发/加固) + 技术债清理 + 文档整理
后端: - 工作流推进链(D-03):advance_task/状态机/闸门走 df-nodes Node trait,conditions 条件引擎扩展 - 想法评估闭环:启发式评分+对抗评估,df-ideas/scoring + df-storage/idea_eval_repo + idea 前端打通 - 全局事件数据总线:df-ai/context+context_helpers+augmentation 跨模块解耦 - AI planner/plan_hint/intent:aichat B 路线并行多轮基础 - patch_file 加固(TD-03/04):读改写整体锁防 lost update,expected_hash 合约闭环 - 压缩超时兜底(F-15 卡死根治) - F-09 多会话并发:LlmConcurrency per-conv + streamingGuard 前端守护 + verify 脚本 - 知识注入 DRY/skills/audit 扩展 清理: - aichat 技术债(误报 allow/死导入/过时注释 30 项) - URGENT.md 删除(11 项加急全解决/迁 todo) - 文档整理(todo/待决策/待审查/ARCHITECTURE/INDEX + 总线/技术债审查新文档)
This commit is contained in:
@@ -126,9 +126,70 @@ impl ScoringEngine {
|
||||
|
||||
/// 统计 text 中命中任一关键词的数量(小写匹配,兼顾中英文)
|
||||
/// 局限:纯子串匹配,不识别"不复用""无用户增长"等否定前缀,接 LLM 后由语义层修正
|
||||
/// 单字否定前缀:关键词紧邻这些字时该命中不计(「不复用」不计入「复用」正向命中)。
|
||||
///
|
||||
/// 注意「别」单字歧义大(别具/别的/特别 均非否定),故不进单字清单——
|
||||
/// 祈使否定「别」改用两字清单兜底(别去/别再/别要/别用 等,见 NEGATION_PREFIXES_2)。
|
||||
const NEGATION_PREFIXES_1: &[char] = &['不', '无', '非', '未', '没', '勿'];
|
||||
/// 两字否定前缀:紧邻关键词的前两字符为这些时该命中不计(「没有复用」「并非简单」「别去复用」)。
|
||||
const NEGATION_PREFIXES_2: &[&str] = &[
|
||||
"没有", "并非", "毫无", "不用", "不是",
|
||||
// 「别X」祈使否定:双字形式可精确匹配,规避「别具/别的/特别」等单字误伤
|
||||
"别去", "别再", "别要", "别用", "别做", "别给",
|
||||
];
|
||||
|
||||
/// 关键词在 text 的 `end` 字节位置前是否处于否定上下文(紧邻否定词)。
|
||||
///
|
||||
/// `end` 是关键词起始字节位置(由 `str::find` 返回,必在 char 边界)。取 `text[..end]`
|
||||
/// 末尾 1-2 个 char 判否定:单字否定(不/无/非/...)或两字否定(没有/并非/...)。
|
||||
///
|
||||
/// 仅取末尾 ≤2 个 char(rev().take(2) 流式),省去每次命中的 `Vec<char>` 分配。
|
||||
fn is_negated_before(text: &str, end: usize) -> bool {
|
||||
if end == 0 {
|
||||
return false;
|
||||
}
|
||||
let before = &text[..end];
|
||||
// 末尾最多 2 char,按 [次末, 末] 顺序收集(n=1 时仅末位)
|
||||
let tail: Vec<char> = before.chars().rev().take(2).collect();
|
||||
if tail.is_empty() {
|
||||
return false;
|
||||
}
|
||||
// 单字否定:末位 char(tail[0]) 命中
|
||||
if NEGATION_PREFIXES_1.contains(&tail[0]) {
|
||||
return true;
|
||||
}
|
||||
// 两字否定:需凑齐 2 char,顺序为 [末, 次末] 反转回原序 [次末, 末]
|
||||
if tail.len() == 2 {
|
||||
let last2: String = [tail[1], tail[0]].iter().collect();
|
||||
if NEGATION_PREFIXES_2.contains(&last2.as_str()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// 统计 text 中命中任一关键词的数量(小写匹配,兼顾中英文)。
|
||||
///
|
||||
/// 否定前缀处理:关键词紧邻否定词(不复用/无增长/非紧急...)时该命中不计,
|
||||
/// 避免「不复用」反向计入「复用」正向分。双重否定(「不是不复用」)属罕见边缘,
|
||||
/// 不处理(接 LLM 后语义层兜底)。
|
||||
fn count_any(text: &str, keywords: &[&str]) -> usize {
|
||||
let lower = text.to_lowercase();
|
||||
keywords.iter().filter(|kw| lower.contains(*kw)).count()
|
||||
keywords
|
||||
.iter()
|
||||
.filter(|kw| {
|
||||
// 至少一个非否定上下文的出现即计为命中(同一词多次出现,一次有效即可)
|
||||
let mut idx = 0usize;
|
||||
while let Some(rel) = lower[idx..].find(*kw) {
|
||||
let abs = idx + rel;
|
||||
if !is_negated_before(&lower, abs) {
|
||||
return true;
|
||||
}
|
||||
idx = abs + kw.len();
|
||||
}
|
||||
false
|
||||
})
|
||||
.count()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -233,4 +294,35 @@ mod tests {
|
||||
println!(" 可行性={:.2} 影响力={:.2} 紧急度={:.2} 综合={:.2}", s.feasibility, s.impact, s.urgency, s.overall);
|
||||
assert!(s.feasibility >= 0.0 && s.impact >= 0.0 && s.urgency >= 0.0, "所有维度应≥0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn s9_single_char_negation_suppresses_hit() {
|
||||
// 「不复用已有组件」:复用 被「不」否定不计,已有 命中 → feasibility 低于「复用已有组件」(复用+已有 双命中)
|
||||
let neg = make_idea("不复用", "不复用已有组件", Priority::Medium, vec![]);
|
||||
let pos = make_idea("复用", "复用已有组件", Priority::Medium, vec![]);
|
||||
let s_neg = ScoringEngine::compute_default(&neg);
|
||||
let s_pos = ScoringEngine::compute_default(&pos);
|
||||
println!("\n[s9] 单字否定前缀抑制命中");
|
||||
println!(" 不复用已有组件 feasibility={:.2} 复用已有组件 feasibility={:.2}", s_neg.feasibility, s_pos.feasibility);
|
||||
assert!(
|
||||
s_neg.feasibility < s_pos.feasibility,
|
||||
"单字否定应抑制正向命中致 feasibility 更低: neg={} pos={}",
|
||||
s_neg.feasibility, s_pos.feasibility
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn s10_two_char_negation_suppresses_hit() {
|
||||
// 「没有复用」:两字否定 没有 抑制 复用 命中;「确实复用」无否定 复用 命中
|
||||
let neg = make_idea("t", "没有复用", Priority::Medium, vec![]);
|
||||
let pos = make_idea("t", "确实复用", Priority::Medium, vec![]);
|
||||
let s_neg = ScoringEngine::compute_default(&neg);
|
||||
let s_pos = ScoringEngine::compute_default(&pos);
|
||||
println!("\n[s10] 两字否定前缀抑制命中");
|
||||
println!(" 没有复用 feasibility={:.2} 确实复用 feasibility={:.2}", s_neg.feasibility, s_pos.feasibility);
|
||||
assert!(
|
||||
s_neg.feasibility < s_pos.feasibility,
|
||||
"两字否定「没有」应抑制命中: neg={} pos={}", s_neg.feasibility, s_pos.feasibility
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user