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