"""场景切片纯函数:段落聚合切窗与确定性身份;同输入同输出。""" import pytest from muse.资料研究.场景切片 import 切片计划哈希, 切片身份, 段落切窗 from muse.资料研究.接口 import 计算切片 @pytest.mark.case_id( "NC-w33-a10025", environment="离线纯函数", given="含多段落的参考书原文与目标窗长", when="执行段落聚合切窗", then=["窗内长度达目标即闭合;末窗过短并入邻窗;窗口首尾相接覆盖全文"], contract="docs/系统架构/新版设计/模块设计/B03-资料研究.md#场景片段切片与索引", ) def test_段落聚合切窗覆盖全文且末窗并邻__a10025(): 段 = "机甲的合金装甲在月光下泛着冷光。\n" 文本 = 段 * 200 # 每段约 17 字,200 段约 3400 字 窗口 = 段落切窗(文本, 窗长=1800) assert 窗口[0]["起点"] == 0 assert 窗口[-1]["终点"] == len(文本) - 1 # 尾部换行不是内容,不入窗 for 甲, 乙 in zip(窗口, 窗口[1:], strict=False): assert 乙["起点"] > 甲["终点"] # 段间换行符不入窗 assert 甲["终点"] - 甲["起点"] >= 1800 assert len(窗口) == 2 @pytest.mark.case_id( "NC-w33-a10026", environment="离线纯函数", given="短于最短窗长的原文", when="执行段落切窗", then=["整文成单窗,不因短而拒绝"], contract="docs/系统架构/新版设计/模块设计/B03-资料研究.md#场景片段切片与索引", ) def test_短文成单窗__a10026(): 文本 = "短打斗。\n一刀封喉。\n" 窗口 = 段落切窗(文本, 窗长=1800) assert len(窗口) == 1 assert 窗口[0] == {"起点": 0, "终点": len(文本) - 1} @pytest.mark.case_id( "NC-w33-a10027", environment="离线纯函数", given="同一原文、来源身份与窗参数", when="重复计算切片", then=["切片身份、窗计划哈希与检索文本哈希逐次一致(确定性)"], contract="docs/系统架构/新版设计/模块设计/B03-资料研究.md#场景片段切片与索引", ) def test_切片计算确定性__a10027(): 文本 = "刀锋擦过装甲。\n火花四散。\n" * 300 甲 = 计算切片(文本, "11111111-1111-1111-1111-111111111111", 1) 乙 = 计算切片(文本, "11111111-1111-1111-1111-111111111111", 1) assert [r["slice_id"] for r in 甲] == [r["slice_id"] for r in 乙] assert 甲[0]["窗计划哈希"] == 切片计划哈希(段落切窗(文本, 窗长=1800), 1800) assert 甲[0]["slice_id"] == 切片身份( "11111111-1111-1111-1111-111111111111", 1, 甲[0]["窗计划哈希"], 甲[0]["起点"] ) # 版本号变化即身份变化:同文本不同版本不同身份。 丙 = 计算切片(文本, "11111111-1111-1111-1111-111111111111", 2) assert 甲[0]["slice_id"] != 丙[0]["slice_id"] @pytest.mark.case_id( "NC-w33-a10028", environment="离线纯函数", given="空原文或非法窗长", when="执行段落切窗", then=["拒绝为资料错误,不产出空窗"], contract="docs/系统架构/新版设计/模块设计/B03-资料研究.md#场景片段切片与索引", ) def test_空文与非法窗长拒绝__a10028(): from muse.资料研究.模型 import 资料错误 with pytest.raises(资料错误): 段落切窗("", 窗长=1800) with pytest.raises(资料错误): 段落切窗("有字。\n", 窗长=100)