5.9 KiB
5.9 KiB
breakpoint-candidates(技术计划)
1. 计划目标
基于 spec.md 与 设计说明文档/文档换行算法.md v1.2.1,实现“候选断点生成与裁剪”模块,输出可控规模、确定性排序的 breakpoints 集合,保证:
- EN/TC 断点生成口径一致(断点
pos均是 token 边界索引) - 同输入必定同输出(去重、排序、裁剪与过滤全流程确定性)
- 候选规模上限严格生效(尤其 TC)
- 支持约束过滤:
forbiddenBreakRanges(必须)与protectedPhrases(可选优化)
本模块只产出 breakpoints,不做组合搜索与评分。
2. 默认技术决策(本计划采用)
- 输出结构:
Array<{ pos, kind, priority }>,最终按pos升序 - 去重策略:同一
pos若出现多个来源候选,保留priority更高者(priority 相同按kind固定序优先) - 裁剪策略(TC):先按“候选重要性排序”截断到
tcMaxCandidateBreaks,再按pos升序输出 - 过滤策略:
- 必做:
forbiddenBreakRanges命中直接剔除 - 可选优化:若已计算
protectedPhrases的 span,可在生成阶段剔除 span 内断点(否则交给评分阶段强惩罚淘汰)
- 必做:
3. 输入/输出与关键口径
3.1 输入(来自上游)
tokens: Token[]- EN:WORD tokens(不包含 SPACE token)
- TC:grapheme cluster tokens(允许包含空格 cluster
" ",用于 SPACE 断点)
lang: 'TC' | 'EN'maxLines: numberconstraints?: { protectedPhrases?: string[]; forbiddenBreakRanges?: Array<{ start: number; end: number }> }config: { tcMaxCandidateBreaks: number; tcPunctuations: string[]; balanceRange: number }
3.2 输出(确定性)
breakpoints: Array<{ pos: number; kind: 'PUNCT' | 'SPACE' | 'BALANCE' | 'OTHER'; priority: number }>pos:token 边界索引,范围0..N- 最终输出必须按
pos升序
meta?: { pruned: boolean; originalCount: number; finalCount: number }
3.3 断点边界定义(统一口径)
- 候选断点只生成在“行内断点”位置:
pos ∈ [1, N-1]pos=0与pos=N由搜索器作为“起止边界”处理(不作为候选断点输出)
4. 生成规则(按语言)
4.1 EN:词边界(kind=SPACE)
规则
- tokens 仅为 WORD,不生成 SPACE token
- 对每个词边界产生候选断点:
- 对
i in 1..N-1生成pos=i, kind='SPACE'
- 对
priority固定为基础值(建议priority=10)
验收要点
"I am so tired"(N=4)→ breakpoints.pos 必为[1,2,3](升序)
4.2 TC:标点/空格/BALANCE
4.2.1 标点后断点(kind=PUNCT,最高优先级)
- 若
tokens[i].text属于tcPunctuations:- 生成
pos=i+1, kind='PUNCT'
- 生成
priority建议最高(例如priority=30)
4.2.2 空格后断点(kind=SPACE,中优先级)
- 若
tokens[i].text === ' ':- 生成
pos=i+1, kind='SPACE'
- 生成
priority建议中等(例如priority=20)
注:若上游对 TC 也做了空白 NORMALIZE,则空格通常不会连写,断点仍保持确定性。
4.2.3 BALANCE 断点(kind=BALANCE,低优先级)
目的:在无标点时,仍在“接近理想位置”附近提供少量断点,提升可解性与观感。
理想位置计算(确定性简化版):
N = tokens.lengthtargetLines = min(maxLines, N)(至少 1,且不超过 N)- 对
lineIndex in 1..targetLines-1:idealPos = round((N * lineIndex) / targetLines)- 在区间
[idealPos - balanceRange, idealPos + balanceRange]生成少量候选pos
生成细则:
- 候选
pos必须落在[1, N-1] - 去重前可以允许重复(后续统一去重)
priority建议最低(例如priority=5)
裁决补充口径:BALANCE 断点允许落在 emotionPhrase/protectedPhrases 的 span 内;是否可用由评分阶段强惩罚决定(本模块不做语义裁决)。
5. 过滤、去重、裁剪与排序(必须确定性)
5.1 forbiddenBreakRanges 过滤(必须)
- 若
pos落在任一forbiddenBreakRanges的区间内(按项目约定:start <= pos <= end或半开区间,必须写死一种),则剔除该 breakpoint - 过滤必须发生在最终输出前,保证确定性
5.2 去重(必须)
同一 pos 可能来自多个来源(例如 PUNCT 与 BALANCE):
- 取
priority更高者 priority相同则按固定 kind 序:PUNCT > SPACE > BALANCE > OTHER
5.3 TC 规模上限裁剪(必须)
当 lang=TC 且候选数超过 tcMaxCandidateBreaks:
- 计算每个 pos 到最近
idealPos的距离distToIdeal(若无 idealPos 列表则设为大值) - 按以下 key 排序后截断(排序必须固定):
priority降序distToIdeal升序pos升序
- 取前
tcMaxCandidateBreaks
最后再按 pos 升序输出(输出顺序固定)。
5.4 meta 输出
originalCount:过滤/去重/裁剪前的候选数量finalCount:最终输出数量pruned:是否发生过裁剪(finalCount < originalCount)
6. 测试计划(Vitest)
6.1 EN 断点生成
- 输入 tokens=[I, am, so, tired] → pos=[1,2,3](确定性)
6.2 TC 标点/空格断点
tokens=['我','好','累',',','😮💨']且tcPunctuations包含,- 必须包含
pos=4(kind=PUNCT)
- 必须包含
6.3 BALANCE 与裁剪
- 构造无标点长文本,balanceRange>0 且
tcMaxCandidateBreaks很小- 验证裁剪后数量上限生效
- 验证输出仍按 pos 升序
6.4 forbiddenBreakRanges
- 给定 ranges,断言命中区间内的 pos 一律被剔除
6.5 确定性(关键)
- 同输入多次调用 breakpoints 输出完全一致(包括 meta)
7. 完成定义(DoD)
- EN/TC 候选断点生成口径与裁剪规则写死并实现
- 去重/排序/裁剪/过滤流程完全确定性
- TC 上限
tcMaxCandidateBreaks生效 - 单测覆盖:EN/TC 基础、BALANCE、裁剪、forbiddenBreakRanges、确定性