Files
strepsiades 4a9c444b10 first commit
2026-03-09 16:31:42 +08:00

122 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING, Any, List, Optional, Tuple
from ..common.types import BindingStatus
if TYPE_CHECKING:
from ..common.sync_node import SyncNode
class CoreBindingResult(str, Enum):
NORMAL = "NORMAL"
WARNING = "WARNING"
ABNORMAL = "ABNORMAL"
def classify_core_pair(
*,
has_binding_record: bool,
local_data: Optional[Any],
peer_data: Optional[Any],
local_valid: bool = True,
peer_valid: bool = True,
) -> Tuple[CoreBindingResult, CoreBindingResult]:
if not has_binding_record:
raise ValueError("classify_core_pair requires has_binding_record=True")
local_exists = local_data is not None
peer_exists = peer_data is not None
if local_exists and local_valid and peer_exists and peer_valid:
return CoreBindingResult.NORMAL, CoreBindingResult.NORMAL
if local_exists and local_valid and (not peer_exists or not peer_valid):
return CoreBindingResult.WARNING, CoreBindingResult.ABNORMAL
if (not local_exists or not local_valid) and peer_exists and peer_valid:
return CoreBindingResult.ABNORMAL, CoreBindingResult.WARNING
return CoreBindingResult.ABNORMAL, CoreBindingResult.ABNORMAL
def check_dependency_satisfied(
dependency_nodes: List[Optional["SyncNode"]],
field_values: Optional[List[str]] = None,
*,
allow_empty_dependency_ref: bool = True,
allow_missing_dep_data_id: bool = True,
) -> bool:
"""
判断依赖是否满足(供 strategy 组装 E15 上下文)。
该函数本质是“业务语义判断”,状态机只消费最终上下文:
- dep_satisfied (bool)
- dep_errors (结构化错误列表)
关键语义:
1) 空依赖引用可放行(allow_empty_dependency_ref=True
- 例如 contract_id="",表示该字段本轮不参与依赖约束。
2) 依赖节点 data_id 为空可放行(allow_missing_dep_data_id=True
- 这不是严格意义上的“无错误”,而是当前业务策略下的“可继续”。
- 原因:刚创建/回填中的依赖节点可能暂时无 data_id,但业务仍希望继续同步。
- 未来可通过 strategy 配置将其切换为严格阻断。
"""
if not dependency_nodes:
return True
values = field_values or []
for i, dep_node in enumerate(dependency_nodes):
dep_value = values[i] if i < len(values) else ""
dep_value_str = "" if dep_value is None else str(dep_value)
if dep_node is None:
if allow_empty_dependency_ref and dep_value_str == "":
continue
return False
if dep_node.binding_status != BindingStatus.NORMAL:
return False
if not dep_node.data_id and not allow_missing_dep_data_id:
return False
return True
def collect_dependency_errors(
dependency_nodes: List[Optional["SyncNode"]],
field_names: List[str],
field_types: List[str],
field_values: List[str],
*,
allow_empty_dependency_ref: bool = True,
allow_missing_dep_data_id: bool = True,
) -> List[str]:
"""
收集依赖错误(结构化错误码),由 strategy 传给状态机上下文。
设计原则:
- 该函数负责“业务层事实归纳”,不是状态迁移本身。
- 状态机只关心:是否满足(dep_satisfied) 与错误列表(dep_errors)。
关于“依赖项缺乏数据不一定是错”:
- 某些业务场景里,父节点可能刚创建或尚未回填 data_id;
但子节点仍需要进入同步队列,等待后续补齐。
- 因此默认 allow_missing_dep_data_id=True:不产出 no_data_id 错误。
- 后续可由 strategy 配置切换为严格模式(False),届时 no_data_id 将参与阻断。
"""
errors: List[str] = []
for i, dep_node in enumerate(dependency_nodes):
field_name = field_names[i]
field_value = field_values[i] if i < len(field_values) else ""
field_value_str = "" if field_value is None else str(field_value)
if dep_node is None:
if allow_empty_dependency_ref and field_value_str == "":
continue
errors.append(f"not_found|{field_name}|{field_types[i]}|{field_values[i]}")
continue
if dep_node.binding_status != BindingStatus.NORMAL:
errors.append(f"bad_status|{field_name}|{dep_node.binding_status.value}")
continue
if not dep_node.data_id and not allow_missing_dep_data_id:
status_info = f"status={dep_node.status.value}" if dep_node.status else "status=未知"
errors.append(f"no_data_id|{field_name}|{status_info}")
return errors