Skip to content

Commit 152cf56

Browse files
committed
fix(asr): v0.20 审查修复——CER 超长护栏/SRT 跨块合并/进度换行
1 parent 8fbcd2c commit 152cf56

3 files changed

Lines changed: 29 additions & 16 deletions

File tree

‎app/src-tauri/src/bin/asr_eval.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ fn main() -> ExitCode {
169169
}
170170
print!("{}. ", s.name);
171171
}
172+
if !rows.is_empty() {
173+
println!();
174+
}
172175

173176
// ── 汇总渲染 ──
174177
out.push_str(&render_rows(&rows));

‎app/src-tauri/src/cer.rs‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,26 @@
1414

1515
use crate::asr_rescore::strip_punct;
1616

17+
/// CER 单侧长度上限(字符):levenshtein 为 O(n×m) 时间(滚动行内存安全),
18+
/// 超长文本(≥20k 字 ≈ 半小时以上口播)在工具路径会消耗分钟级 CPU——
19+
/// 审查修复(2026-09-05):超限诚实返回 None(不可比),由调用方决定跳过;
20+
/// 画像侧更严护栏见 eval_confusion(2000)。harness 样本段级测量不受影响。
21+
const CER_MAX_CHARS: usize = 20_000;
22+
1723
/// CER 计算(纯函数):编辑距离 / 参考字符数。
1824
///
1925
/// @ai-context: 返回 0.0~1.0+(插入错误可使 CER >1,如实反映退化);参考文本
20-
/// 为空 → None(无法计算——诚实表达,不返回假 0)。
26+
/// 为空 → None(无法计算——诚实表达,不返回假 0);任一侧超
27+
/// CER_MAX_CHARS → None(时间成本护栏,诚实不可比)。
2128
pub fn cer(reference: &str, hypothesis: &str) -> Option<f32> {
2229
let ref_chars: Vec<char> = strip_punct(reference);
2330
let hyp_chars: Vec<char> = strip_punct(hypothesis);
2431
if ref_chars.is_empty() {
2532
return None;
2633
}
34+
if ref_chars.len() > CER_MAX_CHARS || hyp_chars.len() > CER_MAX_CHARS {
35+
return None;
36+
}
2737
let dist = crate::asr_rescore::levenshtein(
2838
&ref_chars.iter().collect::<String>(),
2939
&hyp_chars.iter().collect::<String>(),
@@ -80,6 +90,14 @@ mod tests {
8090
assert_eq!(cer(" ", "有内容"), None);
8191
}
8292

93+
#[test]
94+
fn cer_overlong_side_is_none_guard() {
95+
// 超长护栏(审查修复):任一侧超 20k 字 → None(O(n×m) 时间成本诚实不可比)
96+
let long = "字".repeat(CER_MAX_CHARS + 1);
97+
assert_eq!(cer(&long, "短"), None);
98+
assert_eq!(cer("短", &long), None);
99+
}
100+
83101
#[test]
84102
fn cer_punctuation_ignored() {
85103
// 标点差异不计入错误(与 strip_punct 同口径)

‎app/src-tauri/src/eval_samples.rs‎

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ pub fn parse_srt(text: &str) -> Vec<SrtCue> {
3939
if block.trim().is_empty() {
4040
continue;
4141
}
42-
// 块内解析:首行数字=序号(可缺失);含 "-->" 的时间轴行一律跳过;
43-
// 其余行按行序推入(同块多行合并为一条 cue,保持行序)
42+
// 块内解析:首行数字=序号(可缺失,用计数);含 "-->" 的时间轴行跳过;
43+
// 内容行**块内累积**、块末一次性推入——防两块同序号时跨块误并文本
4444
let mut block_idx = index_counter;
45+
let mut lines: Vec<&str> = Vec::new();
4546
for (pos, line) in block.lines().enumerate() {
4647
let t = line.trim();
4748
if t.is_empty() {
@@ -56,25 +57,16 @@ pub fn parse_srt(text: &str) -> Vec<SrtCue> {
5657
if t.contains("-->") {
5758
continue;
5859
}
59-
push_content_line(&mut cues, block_idx, t);
60+
lines.push(t);
61+
}
62+
if !lines.is_empty() {
63+
cues.push(SrtCue { index: block_idx, text: lines.join("") });
6064
}
6165
index_counter += 1;
6266
}
6367
cues
6468
}
6569

66-
fn push_content_line(cues: &mut Vec<SrtCue>, idx: u32, line: &str) {
67-
let line = line.trim();
68-
if line.is_empty() {
69-
return;
70-
}
71-
match cues.last_mut() {
72-
// 同序号多行 → 拼接为一条 cue 文本(保持行序)
73-
Some(last) if last.index == idx => last.text.push_str(line),
74-
_ => cues.push(SrtCue { index: idx, text: line.to_string() }),
75-
}
76-
}
77-
7870
/// cue 集合 → 参考文本(纯函数):按序号排序后拼接。
7971
///
8072
/// @ai-context: SRT 内乱序少见但可能(采集时序),按 index 排序保证参考

0 commit comments

Comments
 (0)