refactor(question_gen): reduce cyclomatic complexity in sampler_v2
Extract shared _resolve_subtree helper to eliminate repeated tri-level node resolution. Break _validate_sampling_constraints into focused single-purpose helpers: - _count_l3_descendants - _has_frames - _count_subtitles - _resolve_subtree / _find_l3_parent Extract _subtitles_from_l2_list and _frames_from_l2_list to simplify collection functions. Complexity improvements: - _validate_sampling_constraints: D(23) -> B(8) - _collect_subtitle_sentences: C(16) -> A(3) - _collect_frame_paths: C(13) -> A(2) All functions now grade B or better per radon cc. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+152
-108
@@ -95,6 +95,19 @@ class MaterialContext:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class _ResolvedSubtree:
|
||||||
|
"""节点解析结果 — 将任意层级 node_id 统一解析为 L2 节点列表 + 父 L1。
|
||||||
|
|
||||||
|
属性:
|
||||||
|
l2_nodes: 与目标节点关联的 L2 节点列表。
|
||||||
|
parent_l1: 父 L1 节点(用于跨 L2 判断)。
|
||||||
|
"""
|
||||||
|
|
||||||
|
l2_nodes: list[L2Node]
|
||||||
|
parent_l1: L1Node | None
|
||||||
|
|
||||||
|
|
||||||
def _find_l2_node(tree: TreeIndex, l2_id: str) -> tuple[L2Node, L1Node] | None:
|
def _find_l2_node(tree: TreeIndex, l2_id: str) -> tuple[L2Node, L1Node] | None:
|
||||||
"""按 ID 定位 L2 节点及其父 L1。
|
"""按 ID 定位 L2 节点及其父 L1。
|
||||||
|
|
||||||
@@ -128,6 +141,100 @@ def _find_l1_node(tree: TreeIndex, l1_id: str) -> L1Node | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_l3_parent(tree: TreeIndex, l3_id: str) -> tuple[L2Node, L1Node] | None:
|
||||||
|
"""按 L3 ID 定位其所属的 L2 和 L1 节点。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
tree: 三层树索引。
|
||||||
|
l3_id: L3 节点 ID。
|
||||||
|
|
||||||
|
返回:
|
||||||
|
(L2Node, L1Node) 元组;未找到返回 None。
|
||||||
|
"""
|
||||||
|
for l1 in tree.roots:
|
||||||
|
for l2 in l1.children:
|
||||||
|
for l3 in l2.children:
|
||||||
|
if l3.id == l3_id:
|
||||||
|
return (l2, l1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_subtree(tree: TreeIndex, node_id: str) -> _ResolvedSubtree | None:
|
||||||
|
"""将任意层级节点 ID 解析为关联的 L2 节点列表和父 L1。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
tree: 三层树索引。
|
||||||
|
node_id: 任意层级的节点 ID。
|
||||||
|
|
||||||
|
返回:
|
||||||
|
_ResolvedSubtree 实例;节点不存在时返回 None。
|
||||||
|
"""
|
||||||
|
# 尝试作为 L2
|
||||||
|
result = _find_l2_node(tree, node_id)
|
||||||
|
if result is not None:
|
||||||
|
l2_node, parent_l1 = result
|
||||||
|
return _ResolvedSubtree(l2_nodes=[l2_node], parent_l1=parent_l1)
|
||||||
|
|
||||||
|
# 尝试作为 L1
|
||||||
|
l1_node = _find_l1_node(tree, node_id)
|
||||||
|
if l1_node is not None:
|
||||||
|
return _ResolvedSubtree(l2_nodes=list(l1_node.children), parent_l1=l1_node)
|
||||||
|
|
||||||
|
# 尝试作为 L3
|
||||||
|
l3_result = _find_l3_parent(tree, node_id)
|
||||||
|
if l3_result is not None:
|
||||||
|
l2_node, parent_l1 = l3_result
|
||||||
|
return _ResolvedSubtree(l2_nodes=[l2_node], parent_l1=parent_l1)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 约束检查辅助(单一职责)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _count_l3_descendants(l2_nodes: list[L2Node]) -> int:
|
||||||
|
"""统计 L2 节点列表下的 L3 总数。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
l2_nodes: L2 节点列表。
|
||||||
|
|
||||||
|
返回:
|
||||||
|
L3 节点总数。
|
||||||
|
"""
|
||||||
|
return sum(len(l2.children) for l2 in l2_nodes)
|
||||||
|
|
||||||
|
|
||||||
|
def _has_frames(l2_nodes: list[L2Node]) -> bool:
|
||||||
|
"""检查 L2 节点列表的子树中是否有可用帧。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
l2_nodes: L2 节点列表。
|
||||||
|
|
||||||
|
返回:
|
||||||
|
True 表示至少有一个 L3 有 frame_path。
|
||||||
|
"""
|
||||||
|
return any(l3.frame_path for l2 in l2_nodes for l3 in l2.children)
|
||||||
|
|
||||||
|
|
||||||
|
def _count_subtitles(l2_nodes: list[L2Node]) -> int:
|
||||||
|
"""统计 L2 节点列表中全部字幕数(L2 + L3)。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
l2_nodes: L2 节点列表。
|
||||||
|
|
||||||
|
返回:
|
||||||
|
非空字幕总数。
|
||||||
|
"""
|
||||||
|
count = 0
|
||||||
|
for l2 in l2_nodes:
|
||||||
|
if l2.card.subtitle:
|
||||||
|
count += 1
|
||||||
|
count += sum(1 for l3 in l2.children if l3.card.subtitle)
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 公开辅助函数
|
# 公开辅助函数
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -151,62 +258,39 @@ def _validate_sampling_constraints(
|
|||||||
返回:
|
返回:
|
||||||
True 表示满足所有约束,False 表示至少一项不满足。
|
True 表示满足所有约束,False 表示至少一项不满足。
|
||||||
"""
|
"""
|
||||||
# Phase 1: 确定目标 L2 节点列表
|
resolved = _resolve_subtree(tree, node_id)
|
||||||
target_l2_nodes: list[L2Node] = []
|
if resolved is None:
|
||||||
parent_l1: L1Node | None = None
|
|
||||||
|
|
||||||
# 先尝试作为 L2
|
|
||||||
result = _find_l2_node(tree, node_id)
|
|
||||||
if result is not None:
|
|
||||||
l2_node, parent_l1 = result
|
|
||||||
target_l2_nodes = [l2_node]
|
|
||||||
else:
|
|
||||||
# 尝试作为 L1
|
|
||||||
l1_node = _find_l1_node(tree, node_id)
|
|
||||||
if l1_node is not None:
|
|
||||||
target_l2_nodes = list(l1_node.children)
|
|
||||||
parent_l1 = l1_node
|
|
||||||
else:
|
|
||||||
# 尝试作为 L3 — 找到其所属 L2
|
|
||||||
for l1 in tree.roots:
|
|
||||||
for l2 in l1.children:
|
|
||||||
for l3 in l2.children:
|
|
||||||
if l3.id == node_id:
|
|
||||||
target_l2_nodes = [l2]
|
|
||||||
parent_l1 = l1
|
|
||||||
break
|
|
||||||
if target_l2_nodes:
|
|
||||||
break
|
|
||||||
if target_l2_nodes:
|
|
||||||
break
|
|
||||||
|
|
||||||
if not target_l2_nodes:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Phase 2: 统计 L3 节点数
|
if _count_l3_descendants(resolved.l2_nodes) < constraint.min_l3_nodes:
|
||||||
total_l3 = sum(len(l2.children) for l2 in target_l2_nodes)
|
|
||||||
if total_l3 < constraint.min_l3_nodes:
|
|
||||||
return False
|
return False
|
||||||
|
if constraint.require_frames and not _has_frames(resolved.l2_nodes):
|
||||||
# Phase 3: 检查帧路径可用性
|
|
||||||
if constraint.require_frames:
|
|
||||||
has_frame = any(l3.frame_path for l2 in target_l2_nodes for l3 in l2.children)
|
|
||||||
if not has_frame:
|
|
||||||
return False
|
return False
|
||||||
|
if _count_subtitles(resolved.l2_nodes) < constraint.min_subtitles:
|
||||||
|
return False
|
||||||
|
return not (
|
||||||
|
constraint.cross_l2_span
|
||||||
|
and (resolved.parent_l1 is None or len(resolved.parent_l1.children) < 2)
|
||||||
|
)
|
||||||
|
|
||||||
# Phase 4: 统计字幕数
|
|
||||||
subtitle_count = 0
|
def _subtitles_from_l2_list(l2_nodes: list[L2Node]) -> list[str]:
|
||||||
for l2 in target_l2_nodes:
|
"""从 L2 节点列表收集全部非空字幕。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
l2_nodes: L2 节点列表。
|
||||||
|
|
||||||
|
返回:
|
||||||
|
非空字幕字符串列表。
|
||||||
|
"""
|
||||||
|
sentences: list[str] = []
|
||||||
|
for l2 in l2_nodes:
|
||||||
if l2.card.subtitle:
|
if l2.card.subtitle:
|
||||||
subtitle_count += 1
|
sentences.append(l2.card.subtitle)
|
||||||
for l3 in l2.children:
|
for l3 in l2.children:
|
||||||
if l3.card.subtitle:
|
if l3.card.subtitle:
|
||||||
subtitle_count += 1
|
sentences.append(l3.card.subtitle)
|
||||||
if subtitle_count < constraint.min_subtitles:
|
return sentences
|
||||||
return False
|
|
||||||
|
|
||||||
# Phase 5: 检查跨 L2 可用性
|
|
||||||
return not (constraint.cross_l2_span and (parent_l1 is None or len(parent_l1.children) < 2))
|
|
||||||
|
|
||||||
|
|
||||||
def _collect_subtitle_sentences(tree: TreeIndex, node_ids: tuple[str, ...]) -> list[str]:
|
def _collect_subtitle_sentences(tree: TreeIndex, node_ids: tuple[str, ...]) -> list[str]:
|
||||||
@@ -220,40 +304,13 @@ def _collect_subtitle_sentences(tree: TreeIndex, node_ids: tuple[str, ...]) -> l
|
|||||||
node_ids: 待收集字幕的节点 ID 元组。
|
node_ids: 待收集字幕的节点 ID 元组。
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
非空字幕句列表(去除空白后非空的字幕)。
|
非空字幕句列表。
|
||||||
"""
|
"""
|
||||||
sentences: list[str] = []
|
sentences: list[str] = []
|
||||||
|
|
||||||
for nid in node_ids:
|
for nid in node_ids:
|
||||||
# 尝试作为 L2
|
resolved = _resolve_subtree(tree, nid)
|
||||||
result = _find_l2_node(tree, nid)
|
if resolved is not None:
|
||||||
if result is not None:
|
sentences.extend(_subtitles_from_l2_list(resolved.l2_nodes))
|
||||||
l2_node, _ = result
|
|
||||||
if l2_node.card.subtitle:
|
|
||||||
sentences.append(l2_node.card.subtitle)
|
|
||||||
for l3 in l2_node.children:
|
|
||||||
if l3.card.subtitle:
|
|
||||||
sentences.append(l3.card.subtitle)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 尝试作为 L1
|
|
||||||
l1_node = _find_l1_node(tree, nid)
|
|
||||||
if l1_node is not None:
|
|
||||||
for l2 in l1_node.children:
|
|
||||||
if l2.card.subtitle:
|
|
||||||
sentences.append(l2.card.subtitle)
|
|
||||||
for l3 in l2.children:
|
|
||||||
if l3.card.subtitle:
|
|
||||||
sentences.append(l3.card.subtitle)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 尝试作为 L3
|
|
||||||
for l1 in tree.roots:
|
|
||||||
for l2 in l1.children:
|
|
||||||
for l3 in l2.children:
|
|
||||||
if l3.id == nid and l3.card.subtitle:
|
|
||||||
sentences.append(l3.card.subtitle)
|
|
||||||
|
|
||||||
return sentences
|
return sentences
|
||||||
|
|
||||||
|
|
||||||
@@ -369,6 +426,18 @@ def _sample_l1_node(
|
|||||||
return rng.choice(candidates)
|
return rng.choice(candidates)
|
||||||
|
|
||||||
|
|
||||||
|
def _frames_from_l2_list(l2_nodes: list[L2Node]) -> list[str]:
|
||||||
|
"""从 L2 节点列表收集全部可用帧路径。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
l2_nodes: L2 节点列表。
|
||||||
|
|
||||||
|
返回:
|
||||||
|
帧路径字符串列表。
|
||||||
|
"""
|
||||||
|
return [l3.frame_path for l2 in l2_nodes for l3 in l2.children if l3.frame_path]
|
||||||
|
|
||||||
|
|
||||||
def _collect_frame_paths(tree: TreeIndex, node_id: str) -> list[str]:
|
def _collect_frame_paths(tree: TreeIndex, node_id: str) -> list[str]:
|
||||||
"""收集节点子树下的所有可用帧路径。
|
"""收集节点子树下的所有可用帧路径。
|
||||||
|
|
||||||
@@ -379,35 +448,10 @@ def _collect_frame_paths(tree: TreeIndex, node_id: str) -> list[str]:
|
|||||||
返回:
|
返回:
|
||||||
帧路径列表。
|
帧路径列表。
|
||||||
"""
|
"""
|
||||||
paths: list[str] = []
|
resolved = _resolve_subtree(tree, node_id)
|
||||||
|
if resolved is None:
|
||||||
# L2 节点
|
return []
|
||||||
result = _find_l2_node(tree, node_id)
|
return _frames_from_l2_list(resolved.l2_nodes)
|
||||||
if result is not None:
|
|
||||||
l2_node, _ = result
|
|
||||||
for l3 in l2_node.children:
|
|
||||||
if l3.frame_path:
|
|
||||||
paths.append(l3.frame_path)
|
|
||||||
return paths
|
|
||||||
|
|
||||||
# L1 节点
|
|
||||||
l1_node = _find_l1_node(tree, node_id)
|
|
||||||
if l1_node is not None:
|
|
||||||
for l2 in l1_node.children:
|
|
||||||
for l3 in l2.children:
|
|
||||||
if l3.frame_path:
|
|
||||||
paths.append(l3.frame_path)
|
|
||||||
return paths
|
|
||||||
|
|
||||||
# L3 节点
|
|
||||||
for l1 in tree.roots:
|
|
||||||
for l2 in l1.children:
|
|
||||||
for l3 in l2.children:
|
|
||||||
if l3.id == node_id and l3.frame_path:
|
|
||||||
paths.append(l3.frame_path)
|
|
||||||
return paths
|
|
||||||
|
|
||||||
return paths
|
|
||||||
|
|
||||||
|
|
||||||
def _collect_source_nodes(tree: TreeIndex, node_id: str) -> tuple[str, ...]:
|
def _collect_source_nodes(tree: TreeIndex, node_id: str) -> tuple[str, ...]:
|
||||||
|
|||||||
Reference in New Issue
Block a user