122 lines
4.6 KiB
Python
122 lines
4.6 KiB
Python
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
|