@@ -15,7 +15,9 @@ pub const KIND_FACT: &str = "fact";
1515pub const KIND_ACTION : & str = "action" ;
1616
1717/// 步骤语义元词(命中任一即判 action——中文教程/操作高频词)。
18- const STEP_META_WORDS : [ & str ; 6 ] = [ "第一步" , "步骤" , "做法" , "流程" , "操作步骤" , "教程" ] ;
18+ const STEP_META_WORDS : [ & str ; 7 ] = [ "第一步" , "步骤" , "做法" , "流程" , "操作步骤" , "教程" , "首先" ] ;
19+ /// "先…然后"成对信号的误判守卫词(普通叙事高频,非步骤语义)。
20+ const PAIR_SIGNAL_GUARD : [ & str ; 4 ] = [ "先生" , "先后" , "优先" , "先修" ] ;
1921
2022/// 生成的卡片候选(front/back + 内容分型 kind)。
2123#[ derive( Debug , Clone , PartialEq , Eq ) ]
@@ -130,11 +132,25 @@ pub fn card_from_fragment(text: &str) -> Option<CardCandidate> {
130132
131133/// 步骤语义信号判定(REQ-199):步骤元词命中,或"先…然后"成对出现。
132134///
133- /// @ai-context: "先/然后"单现不算信号(普通叙事高频词,误判会把知识卡
134- /// 伪装成动作卡);成对出现才隐含操作顺序。
135+ /// @ai-context: 审查修复(2026-08-22):"先"单字高频(先生/先后/优先/先修),
136+ /// 且"先…然后"普通叙事(如"先修课…然后…")不隐含操作顺序——
137+ /// 成对信号须满足:守卫词不命中 + 至少 3 句 + 两词跨句(步骤
138+ /// 序列的最小形态),元词命中不受限(词义明确)。
135139fn has_step_signal ( text : & str ) -> bool {
136- STEP_META_WORDS . iter ( ) . any ( |w| text. contains ( w) )
137- || ( text. contains ( "先" ) && text. contains ( "然后" ) )
140+ if STEP_META_WORDS . iter ( ) . any ( |w| text. contains ( w) ) {
141+ return true ;
142+ }
143+ if PAIR_SIGNAL_GUARD . iter ( ) . any ( |w| text. contains ( w) ) {
144+ return false ;
145+ }
146+ let sentences: Vec < & str > = text
147+ . split ( |c| "。!?;\n " . contains ( c) )
148+ . map ( |s| s. trim ( ) )
149+ . filter ( |s| !s. is_empty ( ) )
150+ . collect ( ) ;
151+ let xian_at = sentences. iter ( ) . position ( |s| s. contains ( "先" ) ) ;
152+ let ranhou_at = sentences. iter ( ) . position ( |s| s. contains ( "然后" ) ) ;
153+ sentences. len ( ) >= 3 && xian_at. is_some ( ) && ranhou_at. is_some ( ) && xian_at != ranhou_at
138154}
139155
140156#[ cfg( test) ]
0 commit comments