新增修改
This commit is contained in:
0
final/src/__init__.py
Normal file
0
final/src/__init__.py
Normal file
BIN
final/src/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
final/src/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
final/src/__pycache__/attribute_schema.cpython-313.pyc
Normal file
BIN
final/src/__pycache__/attribute_schema.cpython-313.pyc
Normal file
Binary file not shown.
BIN
final/src/__pycache__/cleaner.cpython-313.pyc
Normal file
BIN
final/src/__pycache__/cleaner.cpython-313.pyc
Normal file
Binary file not shown.
BIN
final/src/__pycache__/cleaner.cpython-38.pyc
Normal file
BIN
final/src/__pycache__/cleaner.cpython-38.pyc
Normal file
Binary file not shown.
BIN
final/src/__pycache__/data_loader.cpython-313.pyc
Normal file
BIN
final/src/__pycache__/data_loader.cpython-313.pyc
Normal file
Binary file not shown.
BIN
final/src/__pycache__/data_loader.cpython-38.pyc
Normal file
BIN
final/src/__pycache__/data_loader.cpython-38.pyc
Normal file
Binary file not shown.
BIN
final/src/__pycache__/formalization_pipeline.cpython-313.pyc
Normal file
BIN
final/src/__pycache__/formalization_pipeline.cpython-313.pyc
Normal file
Binary file not shown.
BIN
final/src/__pycache__/main_227.cpython-313.pyc
Normal file
BIN
final/src/__pycache__/main_227.cpython-313.pyc
Normal file
Binary file not shown.
BIN
final/src/__pycache__/triplet_cleaner.cpython-313.pyc
Normal file
BIN
final/src/__pycache__/triplet_cleaner.cpython-313.pyc
Normal file
Binary file not shown.
701
final/src/attribute_schema.py
Normal file
701
final/src/attribute_schema.py
Normal file
@@ -0,0 +1,701 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
属性规范定义模块 - 基于 1125暂时属性需求
|
||||
实现内容与格式的双重对齐:格式校验(结构类型)+ 内容校验(取值范围、类型)
|
||||
"""
|
||||
|
||||
import math
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
# ============ 目标 Schema 定义(来自 1125暂时属性需求.xlsx) ============
|
||||
|
||||
# function_vector: 字典,键为功能维度,值为 [0,1] 浮点数
|
||||
FUNCTION_VECTOR_KEYS = [
|
||||
"f_IC", # 情报收集能力
|
||||
"f_IA", # 信息分析能力
|
||||
"f_CS", # 协同作战能力
|
||||
"f_IT", # 信息传输能力
|
||||
"f_DP", # 数据处理能力
|
||||
"f_CPS", # 指挥控制能力
|
||||
"f_CC", # 综合保障能力
|
||||
]
|
||||
|
||||
# spatial: 字典
|
||||
SPATIAL_KEYS = {
|
||||
"position": lambda v: isinstance(v, (list, tuple)) and len(v) >= 2
|
||||
and all(isinstance(x, (int, float)) for x in v[:2]),
|
||||
"effective_radius": lambda v: isinstance(v, (int, float)) and not isinstance(v, bool),
|
||||
}
|
||||
|
||||
# temporal: 字典
|
||||
TEMPORAL_KEYS = {
|
||||
"response_time": lambda v: isinstance(v, (int, float)) and not isinstance(v, bool),
|
||||
"cycle_time": lambda v: isinstance(v, (int, float)) and not isinstance(v, bool),
|
||||
"data_age": lambda v: isinstance(v, (int, float)) and not isinstance(v, bool),
|
||||
"time_window": lambda v: isinstance(v, (int, float)) and not isinstance(v, bool),
|
||||
}
|
||||
|
||||
# performance: 字典
|
||||
PERFORMANCE_KEYS = {
|
||||
"core_performance": lambda v: isinstance(v, (int, float)) and not isinstance(v, bool)
|
||||
and 0 <= float(v) <= 1,
|
||||
"survivability": lambda v: isinstance(v, (int, float)) and not isinstance(v, bool)
|
||||
and 0 <= float(v) <= 1,
|
||||
"mtbf": lambda v: isinstance(v, (int, float)) and not isinstance(v, bool),
|
||||
}
|
||||
|
||||
# nodes.json 输出字段(完全对齐 xlsx,仅含规范字段 + 节点标识)
|
||||
# TARGET_ID 为节点唯一标识(三元组 head/tail 引用所需)
|
||||
OUTPUT_SCHEMA_FIELDS = [
|
||||
"TARGET_ID",
|
||||
"function_vector",
|
||||
"spatial",
|
||||
"temporal",
|
||||
"performance",
|
||||
"protocol_list",
|
||||
"format_list",
|
||||
"interface_list",
|
||||
"security_level",
|
||||
"org_unit",
|
||||
"nation",
|
||||
"history_success",
|
||||
]
|
||||
|
||||
# 原始字段 -> 目标 schema 映射(用于 flat 数据转换为规范格式)
|
||||
RAW_TO_TARGET_MAPPING = {
|
||||
# function_vector 从多个能力字段推导
|
||||
"function_vector": {
|
||||
"f_IC": ["DETECTION_ACCURACY", "TARGET_RECOGNITION_CAPABILITY"], # 情报收集
|
||||
"f_IA": ["INFORMATION_FUSION_CAPABILITY", "DETECTION_ACCURACY"], # 信息分析
|
||||
"f_CS": ["MOBILITY", "ENVIRONMENT_ADAPTABILITY"], # 协同作战
|
||||
"f_IT": ["ANTI_JAMMING_CAPABILITY"], # 信息传输(抗干扰)
|
||||
"f_DP": ["PROCESSING_CAPACITY", "INFORMATION_FUSION_CAPABILITY"], # 数据处理
|
||||
"f_CPS": ["DECISION_RESPONSE_TIME"], # 指挥控制(响应时间反推)
|
||||
"f_CC": ["SUPPORT_CAPACITY", "LOAD_CAPACITY"], # 综合保障
|
||||
},
|
||||
"spatial": {
|
||||
"position": ["X", "Y"],
|
||||
"effective_radius": ["COMMUNICATION_RANGE"],
|
||||
},
|
||||
"temporal": {
|
||||
"response_time": ["DECISION_RESPONSE_TIME"],
|
||||
"cycle_time": ["REFRESH_RATE"],
|
||||
"data_age": ["TRANSMISSION_DELAY"], # 传输延迟可近似数据新鲜度
|
||||
"time_window": ["REFRESH_RATE"], # 周期倒数近似时间窗口
|
||||
},
|
||||
"performance": {
|
||||
"core_performance": ["STRIKE_ACCURACY", "DETECTION_ACCURACY", "MOBILITY"],
|
||||
"survivability": ["ENVIRONMENT_ADAPTABILITY", "ANTI_JAMMING_CAPABILITY"],
|
||||
"mtbf": ["RELIABILITY", "SYSTEM_RELIABILITY"],
|
||||
},
|
||||
"nation": ["COUNTRY_REGION"],
|
||||
"org_unit": ["LEVEL", "ROLE_ID"],
|
||||
"history_success": [], # 无直接映射,需默认或缺失
|
||||
}
|
||||
|
||||
|
||||
def _clamp_0_1(val: Any) -> Optional[float]:
|
||||
"""将值限制在 [0,1] 范围内,无效返回 None"""
|
||||
if val is None or (isinstance(val, float) and (val != val)):
|
||||
return None
|
||||
try:
|
||||
f = float(val)
|
||||
if f < 0:
|
||||
return 0.0
|
||||
if f > 1:
|
||||
return 1.0
|
||||
return f
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _safe_float(val: Any) -> Optional[float]:
|
||||
if val is None or (isinstance(val, float) and (val != val)):
|
||||
return None
|
||||
try:
|
||||
return float(val)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _safe_int(val: Any, lo: int = None, hi: int = None) -> Optional[int]:
|
||||
if val is None:
|
||||
return None
|
||||
try:
|
||||
i = int(float(val))
|
||||
if lo is not None and i < lo:
|
||||
return lo
|
||||
if hi is not None and i > hi:
|
||||
return hi
|
||||
return i
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _ensure_list_of_strings(val: Any) -> List[str]:
|
||||
"""确保为字符串列表"""
|
||||
if val is None:
|
||||
return []
|
||||
if isinstance(val, list):
|
||||
return [str(x).strip() for x in val if x is not None and str(x).strip()]
|
||||
if isinstance(val, str) and val.strip():
|
||||
return [val.strip()]
|
||||
return []
|
||||
|
||||
|
||||
# ---------- 固定参考值转换(用于 function_vector 中需特殊归一化的维度)----------
|
||||
# 参考时间(秒):响应时间 t 越短,f_CPS 越高
|
||||
_CPS_REF_TIME = 30.0
|
||||
# 处理能力参考上限
|
||||
_FDP_REF_CAP = 10000.0
|
||||
# 保障/载荷能力参考
|
||||
_FCC_REF_SUPPORT = 10000.0
|
||||
_FCC_REF_LOAD = 100.0
|
||||
|
||||
|
||||
def _response_time_to_cps(val: Any) -> Optional[float]:
|
||||
"""响应时间(秒) -> f_CPS [0,1],响应越快能力越高"""
|
||||
f = _safe_float(val)
|
||||
if f is None or f < 0:
|
||||
return None
|
||||
v = 1.0 / (1.0 + f / _CPS_REF_TIME)
|
||||
return min(1.0, max(0.0, v))
|
||||
|
||||
|
||||
def _processing_to_fdp(raw: dict) -> Optional[float]:
|
||||
"""PROCESSING_CAPACITY 或 INFORMATION_FUSION_CAPABILITY -> f_DP [0,1]"""
|
||||
pc = raw.get("PROCESSING_CAPACITY")
|
||||
if pc is not None and (not isinstance(pc, float) or pc == pc):
|
||||
try:
|
||||
v = float(pc)
|
||||
if v < 0:
|
||||
return 0.0
|
||||
norm = math.log10(1 + v) / math.log10(1 + _FDP_REF_CAP)
|
||||
return min(1.0, norm)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
ifc = raw.get("INFORMATION_FUSION_CAPABILITY")
|
||||
if ifc is not None and (not isinstance(ifc, float) or ifc == ifc):
|
||||
return _clamp_0_1(ifc)
|
||||
return None
|
||||
|
||||
|
||||
def _support_load_to_fcc(raw: dict) -> Optional[float]:
|
||||
"""SUPPORT_CAPACITY 与 LOAD_CAPACITY -> f_CC [0,1],支持组合"""
|
||||
support = raw.get("SUPPORT_CAPACITY")
|
||||
load = raw.get("LOAD_CAPACITY")
|
||||
s_norm = None
|
||||
l_norm = None
|
||||
if support is not None and (not isinstance(support, float) or support == support):
|
||||
try:
|
||||
v = float(support)
|
||||
if v >= 0:
|
||||
s_norm = min(1.0, math.log10(1 + v) / math.log10(1 + _FCC_REF_SUPPORT))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
if load is not None and (not isinstance(load, float) or load == load):
|
||||
try:
|
||||
v = float(load)
|
||||
if v >= 0:
|
||||
l_norm = min(1.0, v / _FCC_REF_LOAD)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
if s_norm is not None and l_norm is not None:
|
||||
return round(0.6 * s_norm + 0.4 * l_norm, 4)
|
||||
if s_norm is not None:
|
||||
return s_norm
|
||||
return l_norm
|
||||
|
||||
|
||||
# ============ 格式校验 ============
|
||||
|
||||
def check_format_function_vector(val: Any) -> Tuple[bool, str]:
|
||||
"""校验 function_vector 格式:必须为字典"""
|
||||
if val is None:
|
||||
return True, "optional"
|
||||
if not isinstance(val, dict):
|
||||
return False, f"期望 dict,实际 {type(val).__name__}"
|
||||
for k in val:
|
||||
if not isinstance(k, str):
|
||||
return False, f"键必须为 str,发现 {type(k).__name__}"
|
||||
v = val[k]
|
||||
if not isinstance(v, (int, float)) or isinstance(v, bool):
|
||||
return False, f"键 {k} 的值应为 [0,1] 浮点数,实际 {type(v).__name__}"
|
||||
f = float(v)
|
||||
if f < 0 or f > 1:
|
||||
return False, f"键 {k} 的值 {f} 超出 [0,1]"
|
||||
return True, "ok"
|
||||
|
||||
|
||||
def check_format_spatial(val: Any) -> Tuple[bool, str]:
|
||||
"""校验 spatial 格式:必须为字典,含 position 与 effective_radius"""
|
||||
if val is None:
|
||||
return True, "optional"
|
||||
if not isinstance(val, dict):
|
||||
return False, f"期望 dict,实际 {type(val).__name__}"
|
||||
pos = val.get("position")
|
||||
if pos is not None:
|
||||
if not isinstance(pos, (list, tuple)) or len(pos) < 2:
|
||||
return False, "position 应为 [x, y] 列表"
|
||||
if not all(isinstance(x, (int, float)) for x in pos[:2]):
|
||||
return False, "position 元素应为数字"
|
||||
rad = val.get("effective_radius")
|
||||
if rad is not None and not isinstance(rad, (int, float)):
|
||||
return False, "effective_radius 应为数字"
|
||||
return True, "ok"
|
||||
|
||||
|
||||
def check_format_temporal(val: Any) -> Tuple[bool, str]:
|
||||
"""校验 temporal 格式"""
|
||||
if val is None:
|
||||
return True, "optional"
|
||||
if not isinstance(val, dict):
|
||||
return False, f"期望 dict,实际 {type(val).__name__}"
|
||||
for k in ["response_time", "cycle_time", "data_age", "time_window"]:
|
||||
v = val.get(k)
|
||||
if v is not None and not isinstance(v, (int, float)):
|
||||
return False, f"{k} 应为数字"
|
||||
return True, "ok"
|
||||
|
||||
|
||||
def check_format_performance(val: Any) -> Tuple[bool, str]:
|
||||
"""校验 performance 格式"""
|
||||
if val is None:
|
||||
return True, "optional"
|
||||
if not isinstance(val, dict):
|
||||
return False, f"期望 dict,实际 {type(val).__name__}"
|
||||
for k in ["core_performance", "survivability"]:
|
||||
v = val.get(k)
|
||||
if v is not None:
|
||||
try:
|
||||
f = float(v)
|
||||
if f < 0 or f > 1:
|
||||
return False, f"{k} 应在 [0,1],实际 {f}"
|
||||
except (TypeError, ValueError):
|
||||
return False, f"{k} 应为数字"
|
||||
return True, "ok"
|
||||
|
||||
|
||||
def check_format_protocol_list(val: Any) -> Tuple[bool, str]:
|
||||
if val is None:
|
||||
return True, "optional"
|
||||
if not isinstance(val, list):
|
||||
return False, f"期望 list,实际 {type(val).__name__}"
|
||||
for i, x in enumerate(val):
|
||||
if not isinstance(x, str):
|
||||
return False, f"元素[{i}] 应为 str,实际 {type(x).__name__}"
|
||||
return True, "ok"
|
||||
|
||||
|
||||
def check_format_format_list(val: Any) -> Tuple[bool, str]:
|
||||
return check_format_protocol_list(val)
|
||||
|
||||
|
||||
def check_format_interface_list(val: Any) -> Tuple[bool, str]:
|
||||
return check_format_protocol_list(val)
|
||||
|
||||
|
||||
def check_format_security_level(val: Any) -> Tuple[bool, str]:
|
||||
if val is None:
|
||||
return True, "optional"
|
||||
if not isinstance(val, int) or isinstance(val, bool):
|
||||
try:
|
||||
int(val)
|
||||
except (TypeError, ValueError):
|
||||
return False, f"期望 int [1,5],实际 {type(val).__name__}"
|
||||
v = int(val)
|
||||
if v < 1 or v > 5:
|
||||
return False, f"security_level 应在 [1,5],实际 {v}"
|
||||
return True, "ok"
|
||||
|
||||
|
||||
def check_format_org_unit(val: Any) -> Tuple[bool, str]:
|
||||
if val is None:
|
||||
return True, "optional"
|
||||
if not isinstance(val, str):
|
||||
return False, f"期望 str,实际 {type(val).__name__}"
|
||||
return True, "ok"
|
||||
|
||||
|
||||
def check_format_nation(val: Any) -> Tuple[bool, str]:
|
||||
return check_format_org_unit(val)
|
||||
|
||||
|
||||
def check_format_history_success(val: Any) -> Tuple[bool, str]:
|
||||
if val is None:
|
||||
return True, "optional"
|
||||
if not isinstance(val, (int, float)) or isinstance(val, bool):
|
||||
return False, f"期望 float [0,1],实际 {type(val).__name__}"
|
||||
f = float(val)
|
||||
if f < 0 or f > 1:
|
||||
return False, f"history_success 应在 [0,1],实际 {f}"
|
||||
return True, "ok"
|
||||
|
||||
|
||||
# ============ 内容校验(取值范围、语义) ============
|
||||
|
||||
def check_content_function_vector(val: dict) -> List[str]:
|
||||
"""内容校验:键是否在规范内,值是否合法"""
|
||||
issues = []
|
||||
if not isinstance(val, dict):
|
||||
return ["非字典类型"]
|
||||
for k, v in val.items():
|
||||
if k not in FUNCTION_VECTOR_KEYS:
|
||||
issues.append(f"未知键: {k}")
|
||||
else:
|
||||
try:
|
||||
f = float(v)
|
||||
if f < 0 or f > 1:
|
||||
issues.append(f"{k}={f} 超出 [0,1]")
|
||||
except (TypeError, ValueError):
|
||||
issues.append(f"{k} 值非数字: {v}")
|
||||
return issues
|
||||
|
||||
|
||||
def check_content_spatial(val: dict) -> List[str]:
|
||||
issues = []
|
||||
if not isinstance(val, dict):
|
||||
return ["非字典类型"]
|
||||
pos = val.get("position")
|
||||
if pos is not None and len(pos) >= 2:
|
||||
if not all(isinstance(x, (int, float)) for x in pos[:2]):
|
||||
issues.append("position 含非数字")
|
||||
rad = val.get("effective_radius")
|
||||
if rad is not None:
|
||||
try:
|
||||
f = float(rad)
|
||||
if f < 0:
|
||||
issues.append("effective_radius 不应为负")
|
||||
except (TypeError, ValueError):
|
||||
issues.append("effective_radius 非数字")
|
||||
return issues
|
||||
|
||||
|
||||
def check_content_temporal(val: dict) -> List[str]:
|
||||
issues = []
|
||||
if not isinstance(val, dict):
|
||||
return ["非字典类型"]
|
||||
for k in ["response_time", "cycle_time", "data_age", "time_window"]:
|
||||
v = val.get(k)
|
||||
if v is not None:
|
||||
try:
|
||||
f = float(v)
|
||||
if f < 0:
|
||||
issues.append(f"{k} 不应为负: {f}")
|
||||
except (TypeError, ValueError):
|
||||
issues.append(f"{k} 非数字: {v}")
|
||||
return issues
|
||||
|
||||
|
||||
def check_content_performance(val: dict) -> List[str]:
|
||||
issues = []
|
||||
if not isinstance(val, dict):
|
||||
return ["非字典类型"]
|
||||
for k in ["core_performance", "survivability"]:
|
||||
v = val.get(k)
|
||||
if v is not None:
|
||||
try:
|
||||
f = float(v)
|
||||
if f < 0 or f > 1:
|
||||
issues.append(f"{k} 超出 [0,1]: {f}")
|
||||
except (TypeError, ValueError):
|
||||
issues.append(f"{k} 非数字: {v}")
|
||||
mtbf = val.get("mtbf")
|
||||
if mtbf is not None:
|
||||
try:
|
||||
f = float(mtbf)
|
||||
if f < 0:
|
||||
issues.append("mtbf 不应为负")
|
||||
except (TypeError, ValueError):
|
||||
issues.append("mtbf 非数字")
|
||||
return issues
|
||||
|
||||
|
||||
# ============ 统一校验入口 ============
|
||||
|
||||
FORMAT_CHECKERS = {
|
||||
"function_vector": check_format_function_vector,
|
||||
"spatial": check_format_spatial,
|
||||
"temporal": check_format_temporal,
|
||||
"performance": check_format_performance,
|
||||
"protocol_list": check_format_protocol_list,
|
||||
"format_list": check_format_format_list,
|
||||
"interface_list": check_format_interface_list,
|
||||
"security_level": check_format_security_level,
|
||||
"org_unit": check_format_org_unit,
|
||||
"nation": check_format_nation,
|
||||
"history_success": check_format_history_success,
|
||||
}
|
||||
|
||||
CONTENT_CHECKERS = {
|
||||
"function_vector": check_content_function_vector,
|
||||
"spatial": check_content_spatial,
|
||||
"temporal": check_content_temporal,
|
||||
"performance": check_content_performance,
|
||||
}
|
||||
|
||||
|
||||
def validate_record(
|
||||
record: dict,
|
||||
target_schema_fields: Optional[List[str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
对单条记录进行格式与内容双重校验。
|
||||
若 record 已包含目标 schema 字段,则直接校验;
|
||||
否则会先通过 raw_to_target_schema 转换后再校验。
|
||||
"""
|
||||
target_fields = target_schema_fields or list(FORMAT_CHECKERS.keys())
|
||||
result = {
|
||||
"format_errors": {},
|
||||
"content_errors": {},
|
||||
"aligned": False,
|
||||
"transformed": False,
|
||||
}
|
||||
|
||||
# 若缺少目标字段,尝试从原始字段转换
|
||||
has_target = any(f in record for f in target_fields)
|
||||
data = record
|
||||
if not has_target:
|
||||
data = raw_to_target_schema(record)
|
||||
result["transformed"] = True
|
||||
|
||||
# 格式校验
|
||||
for field in target_fields:
|
||||
val = data.get(field)
|
||||
if val is None and field in ["protocol_list", "format_list", "interface_list"]:
|
||||
val = []
|
||||
checker = FORMAT_CHECKERS.get(field)
|
||||
if checker:
|
||||
ok, msg = checker(val)
|
||||
else:
|
||||
ok, msg = True, "no_checker"
|
||||
if not ok:
|
||||
result["format_errors"][field] = msg
|
||||
|
||||
# 内容校验(仅对复杂类型)
|
||||
for field in CONTENT_CHECKERS:
|
||||
val = data.get(field)
|
||||
if val is None:
|
||||
continue
|
||||
issues = CONTENT_CHECKERS[field](val)
|
||||
if issues:
|
||||
result["content_errors"][field] = issues
|
||||
|
||||
result["aligned"] = (
|
||||
len(result["format_errors"]) == 0 and len(result["content_errors"]) == 0
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
# ============ 原始数据 -> 目标 Schema 转换 ============
|
||||
|
||||
def _get_first_valid(row: dict, keys: List[str], cast=_clamp_0_1) -> Optional[Any]:
|
||||
"""从 row 中按 keys 顺序取第一个非空值并转换"""
|
||||
for k in keys:
|
||||
v = row.get(k)
|
||||
if v is not None and (not isinstance(v, float) or v == v):
|
||||
return cast(v) if cast else v
|
||||
return None
|
||||
|
||||
|
||||
def raw_to_target_schema(raw: dict) -> dict:
|
||||
"""
|
||||
将扁平原始记录转换为符合 1125 属性规范的目标结构。
|
||||
仅输出 xlsx 定义字段,不保留任何原始其他字段。
|
||||
"""
|
||||
# TARGET_ID:节点唯一标识
|
||||
tid = raw.get("TARGET_ID")
|
||||
tid = str(tid).strip() if tid is not None and str(tid).strip() else None
|
||||
|
||||
out: Dict[str, Any] = {f: None for f in OUTPUT_SCHEMA_FIELDS}
|
||||
out["TARGET_ID"] = tid
|
||||
|
||||
# function_vector(f_CPS/f_DP/f_CC 使用固定参考值转换,其余用 _clamp_0_1)
|
||||
fv = {}
|
||||
_fv_mapping = RAW_TO_TARGET_MAPPING["function_vector"]
|
||||
for fk, raw_keys in _fv_mapping.items():
|
||||
if fk == "f_CPS":
|
||||
t = raw.get("DECISION_RESPONSE_TIME")
|
||||
val = _response_time_to_cps(t)
|
||||
elif fk == "f_DP":
|
||||
val = _processing_to_fdp(raw)
|
||||
elif fk == "f_CC":
|
||||
val = _support_load_to_fcc(raw)
|
||||
else:
|
||||
val = _get_first_valid(raw, raw_keys, _clamp_0_1)
|
||||
if val is not None:
|
||||
fv[fk] = round(val, 4)
|
||||
# 下游 f_relation1125/f_net1125 要求必为 dict,无推导值时用默认(避免 NoneType)
|
||||
if not fv:
|
||||
fv = {fk: 0.5 for fk in FUNCTION_VECTOR_KEYS}
|
||||
out["function_vector"] = fv
|
||||
|
||||
# spatial(支持 X/x、Y/y、position、POSITION_X/Y、LONGITUDE/LATITUDE;确保元素恒为 float)
|
||||
def _safe_coord(v: Any) -> float:
|
||||
if v is None or (isinstance(v, float) and v != v):
|
||||
return 0.0
|
||||
try:
|
||||
return round(float(v), 2)
|
||||
except (TypeError, ValueError):
|
||||
return 0.0
|
||||
|
||||
pos_raw = raw.get("position") or raw.get("POSITION")
|
||||
if isinstance(pos_raw, (list, tuple)) and len(pos_raw) >= 2:
|
||||
x, y = _safe_coord(pos_raw[0]), _safe_coord(pos_raw[1])
|
||||
else:
|
||||
x = raw.get("X") or raw.get("x") or raw.get("POSITION_X") or raw.get("LONGITUDE")
|
||||
y = raw.get("Y") or raw.get("y") or raw.get("POSITION_Y") or raw.get("LATITUDE")
|
||||
x, y = _safe_coord(x), _safe_coord(y)
|
||||
pos = [x, y]
|
||||
radius = _get_first_valid(raw, RAW_TO_TARGET_MAPPING["spatial"]["effective_radius"], _safe_float)
|
||||
sp = {
|
||||
"position": pos,
|
||||
"effective_radius": round(float(radius), 4) if radius is not None else 1.0,
|
||||
}
|
||||
out["spatial"] = sp
|
||||
|
||||
# temporal
|
||||
_temporal_defaults = {"response_time": 1.0, "cycle_time": 1.0, "data_age": 1.0, "time_window": 24.0}
|
||||
temporal = {}
|
||||
for tk, rks in RAW_TO_TARGET_MAPPING["temporal"].items():
|
||||
v = _get_first_valid(raw, rks, _safe_float)
|
||||
if v is not None:
|
||||
temporal[tk] = round(float(v), 4)
|
||||
for k, default in _temporal_defaults.items():
|
||||
temporal.setdefault(k, default)
|
||||
out["temporal"] = temporal
|
||||
|
||||
# performance
|
||||
_perf_defaults = {"core_performance": 0.5, "survivability": 0.5, "mtbf": 1.0}
|
||||
perf = {}
|
||||
for pk, rks in RAW_TO_TARGET_MAPPING["performance"].items():
|
||||
cast = _clamp_0_1 if pk in ["core_performance", "survivability"] else _safe_float
|
||||
v = _get_first_valid(raw, rks, cast)
|
||||
if v is not None:
|
||||
perf[pk] = round(float(v), 4)
|
||||
for k, default in _perf_defaults.items():
|
||||
perf.setdefault(k, default)
|
||||
out["performance"] = perf
|
||||
|
||||
# protocol_list, format_list, interface_list
|
||||
for field in ["protocol_list", "format_list", "interface_list"]:
|
||||
raw_key = field.upper().replace("_LIST", "")
|
||||
out[field] = _ensure_list_of_strings(raw.get(raw_key, raw.get(field)))
|
||||
|
||||
# security_level:下游 utils.get_security_compatibility 会做 lv_i - lv_j,不可为 None
|
||||
sl = raw.get("security_level") or raw.get("SECURITY_LEVEL")
|
||||
out["security_level"] = _safe_int(sl, 1, 5) if sl is not None else 3
|
||||
|
||||
# org_unit, nation
|
||||
nation = _get_first_valid(raw, RAW_TO_TARGET_MAPPING["nation"], lambda v: str(v).strip() if v is not None else None)
|
||||
out["nation"] = nation
|
||||
org = _get_first_valid(raw, RAW_TO_TARGET_MAPPING["org_unit"], lambda v: str(v).strip() if v is not None else None)
|
||||
out["org_unit"] = org
|
||||
|
||||
# history_success:下游 utils.get_interaction_history 会做 (his_i + his_j)/2,不可为 None
|
||||
hs = raw.get("history_success") or raw.get("HISTORY_SUCCESS")
|
||||
if hs is not None:
|
||||
v = _clamp_0_1(hs)
|
||||
out["history_success"] = round(v, 4) if v is not None else 0.8
|
||||
else:
|
||||
out["history_success"] = 0.8
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def align_and_validate_records(
|
||||
records: List[dict],
|
||||
id_key: str = "TARGET_ID",
|
||||
) -> Tuple[List[dict], dict]:
|
||||
"""
|
||||
对记录列表进行转换、格式与内容校验。
|
||||
返回:(对齐后的记录列表, 统计报告)
|
||||
"""
|
||||
aligned = []
|
||||
report = {
|
||||
"total": len(records),
|
||||
"format_errors_by_field": {},
|
||||
"content_errors_by_field": {},
|
||||
"records_with_errors": 0,
|
||||
"aligned_count": 0,
|
||||
"transformed_count": 0,
|
||||
"samples": [],
|
||||
}
|
||||
|
||||
for rec in records:
|
||||
rid = rec.get(id_key, "?")
|
||||
val_result = validate_record(rec)
|
||||
aligned_rec = raw_to_target_schema(rec)
|
||||
aligned.append(aligned_rec)
|
||||
|
||||
if val_result["transformed"]:
|
||||
report["transformed_count"] += 1
|
||||
if val_result["aligned"]:
|
||||
report["aligned_count"] += 1
|
||||
else:
|
||||
report["records_with_errors"] += 1
|
||||
for f, msg in val_result["format_errors"].items():
|
||||
report["format_errors_by_field"].setdefault(f, 0)
|
||||
report["format_errors_by_field"][f] += 1
|
||||
for f, issues in val_result["content_errors"].items():
|
||||
report["content_errors_by_field"].setdefault(f, 0)
|
||||
report["content_errors_by_field"][f] += 1
|
||||
if len(report["samples"]) < 5:
|
||||
report["samples"].append({
|
||||
"id": rid,
|
||||
"format_errors": val_result["format_errors"],
|
||||
"content_errors": val_result["content_errors"],
|
||||
})
|
||||
|
||||
return aligned, report
|
||||
|
||||
|
||||
def sanitize_node_for_relation_calc(node: dict) -> dict:
|
||||
"""
|
||||
写入 nodes.json 前对节点做最终净化,确保 f_relation1125/utils 中参与运算的字段无 None。
|
||||
避免 euclidean、get_security_compatibility、get_interaction_history 等出现 NoneType 运算错误。
|
||||
"""
|
||||
import copy
|
||||
n = copy.deepcopy(node)
|
||||
|
||||
def _safe_float(v: Any, default: float) -> float:
|
||||
if v is None or (isinstance(v, float) and v != v):
|
||||
return default
|
||||
try:
|
||||
return float(v)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
# spatial.position 必须为 [float, float]
|
||||
sp = n.get("spatial")
|
||||
if not isinstance(sp, dict):
|
||||
sp = {}
|
||||
pos = sp.get("position")
|
||||
if not isinstance(pos, (list, tuple)) or len(pos) < 2:
|
||||
pos = [0.0, 0.0]
|
||||
else:
|
||||
pos = [_safe_float(pos[0], 0.0), _safe_float(pos[1], 0.0)]
|
||||
sp["position"] = pos
|
||||
sp["effective_radius"] = _safe_float(sp.get("effective_radius"), 1.0)
|
||||
n["spatial"] = sp
|
||||
|
||||
# temporal 参与 time_window_overlap
|
||||
tmp = n.get("temporal")
|
||||
if not isinstance(tmp, dict):
|
||||
tmp = {}
|
||||
for k in ["response_time", "cycle_time", "data_age", "time_window"]:
|
||||
if tmp.get(k) is None:
|
||||
tmp[k] = 24.0 if k == "time_window" else 1.0
|
||||
n["temporal"] = tmp
|
||||
|
||||
# security_level、history_success 参与 utils 运算
|
||||
sl = n.get("security_level")
|
||||
n["security_level"] = int(_safe_float(sl, 3)) if sl is not None else 3
|
||||
if n["security_level"] < 1 or n["security_level"] > 5:
|
||||
n["security_level"] = 3
|
||||
|
||||
hs = n.get("history_success")
|
||||
n["history_success"] = _safe_float(hs, 0.8)
|
||||
n["history_success"] = max(0.0, min(1.0, n["history_success"]))
|
||||
|
||||
return n
|
||||
294
final/src/cleaner.py
Normal file
294
final/src/cleaner.py
Normal file
@@ -0,0 +1,294 @@
|
||||
import json
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from datetime import datetime
|
||||
try:
|
||||
from .attribute_schema import align_and_validate_records, sanitize_node_for_relation_calc
|
||||
except ImportError:
|
||||
from attribute_schema import align_and_validate_records, sanitize_node_for_relation_calc
|
||||
|
||||
|
||||
def _json_serializer(obj):
|
||||
"""处理 Timestamp、datetime、numpy、NaN 等不可直接 JSON 序列化的类型"""
|
||||
if hasattr(obj, 'isoformat'):
|
||||
return obj.isoformat()
|
||||
if hasattr(obj, 'item'):
|
||||
return obj.item()
|
||||
if isinstance(obj, float) and (obj != obj or obj == float('inf') or obj == float('-inf')):
|
||||
return None # NaN, Inf -> null
|
||||
raise TypeError(f'Object of type {type(obj).__name__} is not JSON serializable')
|
||||
|
||||
class AdvancedDataCleaner:
|
||||
def __init__(self, output_file, report_file, data=None, input_file=None):
|
||||
"""
|
||||
支持两种初始化方式:
|
||||
1. data=... 直接传入数据列表(从配置/数据库加载时使用)
|
||||
2. input_file=... 传入 JSON 文件路径(兼容旧用法)
|
||||
"""
|
||||
self.output_file = output_file
|
||||
self.report_file = report_file
|
||||
self.data = data if data is not None else []
|
||||
self.input_file = input_file
|
||||
|
||||
# 报告结构
|
||||
self.report = {
|
||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"summary": {
|
||||
"total_records": 0,
|
||||
"final_records": 0,
|
||||
"duplicates_removed": 0
|
||||
},
|
||||
"details": {
|
||||
"missing_values_fixed": {}, # 字段: 填充数量
|
||||
"outliers_corrected": {
|
||||
"count": 0,
|
||||
"examples": [] # 记录具体的修改案例
|
||||
},
|
||||
"noise_reduction": {
|
||||
"method": "Kalman Filter",
|
||||
"fields_processed": ["COMMUNICATION_RANGE"],
|
||||
"total_smoothed": 0
|
||||
},
|
||||
"standardization": [],
|
||||
"attribute_alignment": { # 内容与格式双重对齐(基于 1125 属性需求)
|
||||
"enabled": True,
|
||||
"format_errors_by_field": {},
|
||||
"content_errors_by_field": {},
|
||||
"records_with_errors": 0,
|
||||
"aligned_count": 0,
|
||||
"transformed_count": 0,
|
||||
"samples": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.norm_fields = [
|
||||
"TARGET_RECOGNITION_CAPABILITY", "STRIKE_ACCURACY",
|
||||
"ANTI_JAMMING_CAPABILITY", "ENVIRONMENT_ADAPTABILITY", "MOBILITY"
|
||||
]
|
||||
|
||||
def load_data(self):
|
||||
if not self.data and self.input_file:
|
||||
with open(self.input_file, 'r', encoding='utf-8') as f:
|
||||
self.data = json.load(f)
|
||||
self.report["summary"]["total_records"] = len(self.data)
|
||||
self.df = pd.DataFrame(self.data)
|
||||
|
||||
def clean_duplicates(self):
|
||||
"""高级去重并记录"""
|
||||
initial_count = len(self.df)
|
||||
# 优先保留创建时间最新的(如果有时间字段),否则保留第一个
|
||||
if 'CREATED_TIME' in self.df.columns:
|
||||
self.df.sort_values('CREATED_TIME', ascending=False, inplace=True)
|
||||
|
||||
self.df.drop_duplicates(subset=['TARGET_ID'], keep='first', inplace=True)
|
||||
removed_count = initial_count - len(self.df)
|
||||
self.report["summary"]["duplicates_removed"] = removed_count
|
||||
|
||||
def handle_missing_values(self):
|
||||
"""智能填充并记录细节"""
|
||||
numeric_cols = self.df.select_dtypes(include=[np.number]).columns
|
||||
|
||||
for col in numeric_cols:
|
||||
if col == "ID": continue
|
||||
n_missing = int(self.df[col].isnull().sum())
|
||||
|
||||
if n_missing > 0:
|
||||
self.report["details"]["missing_values_fixed"][col] = n_missing
|
||||
# 分组填充
|
||||
self.df[col] = self.df.groupby("ROLE_ID")[col].transform(lambda x: x.fillna(x.mean()))
|
||||
# 兜底填充
|
||||
self.df[col] = self.df[col].fillna(self.df[col].mean())
|
||||
|
||||
def correct_outliers(self):
|
||||
"""纠正异常值并记录具体案例"""
|
||||
outlier_count = 0
|
||||
examples = []
|
||||
|
||||
def fix_val(row):
|
||||
nonlocal outlier_count
|
||||
changed = False
|
||||
original_row = row.copy()
|
||||
|
||||
for field in self.norm_fields:
|
||||
if pd.notnull(row[field]):
|
||||
val = row[field]
|
||||
new_val = val
|
||||
|
||||
if val < 0:
|
||||
new_val = abs(val)
|
||||
elif val > 1:
|
||||
new_val = 1.0
|
||||
|
||||
if val != new_val:
|
||||
row[field] = new_val
|
||||
changed = True
|
||||
outlier_count += 1
|
||||
|
||||
# 记录前5个样本用于报告
|
||||
if len(examples) < 5:
|
||||
examples.append({
|
||||
"id": row.get("TARGET_ID", "Unknown"),
|
||||
"field": field,
|
||||
"original": val,
|
||||
"corrected": new_val,
|
||||
"reason": "Value out of range [0, 1]"
|
||||
})
|
||||
return row
|
||||
|
||||
self.df = self.df.apply(fix_val, axis=1)
|
||||
self.report["details"]["outliers_corrected"]["count"] = outlier_count
|
||||
self.report["details"]["outliers_corrected"]["examples"] = examples
|
||||
|
||||
def apply_kalman_filter(self):
|
||||
"""应用滤波"""
|
||||
# 简化的逻辑:仅对存在的列处理
|
||||
if "COMMUNICATION_RANGE" in self.df.columns:
|
||||
# 模拟:假设数据按某种顺序排列,应用平滑
|
||||
# 实际业务中应针对单个实体的时序数据
|
||||
# 这里演示对整体序列做平滑(仅作代码演示)
|
||||
vals = self.df["COMMUNICATION_RANGE"].fillna(0).values
|
||||
# 简单移动平均代替卡尔曼演示(效果类似平滑)
|
||||
smoothed = pd.Series(vals).rolling(window=3, min_periods=1).mean().values
|
||||
|
||||
self.df["COMMUNICATION_RANGE"] = np.round(smoothed, 2)
|
||||
self.report["details"]["noise_reduction"]["total_smoothed"] = len(vals)
|
||||
|
||||
def align_attribute_schema(self):
|
||||
"""内容与格式双重对齐:按 1125 属性需求转换并校验"""
|
||||
if self.df is None or len(self.df) == 0:
|
||||
return
|
||||
records = self.df.to_dict('records')
|
||||
aligned, align_report = align_and_validate_records(records, id_key="TARGET_ID")
|
||||
self.df = pd.DataFrame(aligned)
|
||||
self.report["details"]["attribute_alignment"].update({
|
||||
"format_errors_by_field": align_report.get("format_errors_by_field", {}),
|
||||
"content_errors_by_field": align_report.get("content_errors_by_field", {}),
|
||||
"records_with_errors": align_report.get("records_with_errors", 0),
|
||||
"aligned_count": align_report.get("aligned_count", 0),
|
||||
"transformed_count": align_report.get("transformed_count", 0),
|
||||
"samples": align_report.get("samples", []),
|
||||
})
|
||||
|
||||
def run(self):
|
||||
print("正在执行高级清洗...")
|
||||
self.load_data()
|
||||
self.clean_duplicates()
|
||||
self.handle_missing_values()
|
||||
self.correct_outliers()
|
||||
self.apply_kalman_filter()
|
||||
self.align_attribute_schema()
|
||||
|
||||
# 最终统计
|
||||
self.report["summary"]["final_records"] = len(self.df)
|
||||
self.report["details"]["standardization"].append("Coordinates normalized to 2 decimal places")
|
||||
self.report["details"]["standardization"].append("Timestamps formatted to ISO-8601")
|
||||
self.report["details"]["standardization"].append("Attributes aligned to 1125 schema (format + content)")
|
||||
|
||||
# 保存数据(确保输出目录存在)
|
||||
import os
|
||||
output_dir = os.path.dirname(self.output_file)
|
||||
if output_dir:
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
records = self.df.to_dict('records')
|
||||
# 输出格式兼容 main_227 / f_relation1125:{nodes: {TARGET_ID: obj}, global_params: {mtbf_max}}
|
||||
nodes_dict = {}
|
||||
mtbf_values = []
|
||||
id_col = 'TARGET_ID'
|
||||
for r in records:
|
||||
nid = r.get(id_col)
|
||||
if nid is not None:
|
||||
nid = str(nid).strip()
|
||||
if nid:
|
||||
nodes_dict[nid] = sanitize_node_for_relation_calc(r)
|
||||
perf = r.get('performance') if isinstance(r.get('performance'), dict) else {}
|
||||
mtbf = perf.get('mtbf')
|
||||
if mtbf is not None and (isinstance(mtbf, (int, float)) and not (isinstance(mtbf, float) and mtbf != mtbf)):
|
||||
mtbf_values.append(float(mtbf))
|
||||
mtbf_max = max(mtbf_values, default=1.0) if mtbf_values else 1.0
|
||||
result_data = {
|
||||
"nodes": nodes_dict,
|
||||
"global_params": {"mtbf_max": round(mtbf_max, 6)},
|
||||
}
|
||||
with open(self.output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(result_data, f, ensure_ascii=False, indent=2, default=_json_serializer)
|
||||
|
||||
# 保存详细报告(确保目录存在)
|
||||
report_dir = os.path.dirname(self.report_file)
|
||||
if report_dir:
|
||||
os.makedirs(report_dir, exist_ok=True)
|
||||
with open(self.report_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(self.report, f, ensure_ascii=False, indent=2, default=_json_serializer)
|
||||
|
||||
print(f"完成!报告已生成至 {self.report_file}")
|
||||
|
||||
def get_valid_target_ids(self) -> set:
|
||||
"""返回清洗后的有效 TARGET_ID 集合(用于三元组 filter_orphans)"""
|
||||
if self.df is None or 'TARGET_ID' not in self.df.columns:
|
||||
return set()
|
||||
return set(self.df['TARGET_ID'].dropna().astype(str).str.strip().unique())
|
||||
|
||||
def get_id_to_role(self) -> dict:
|
||||
"""返回 TARGET_ID -> ROLE_ID 映射(用于三元组 head_type/tail_type)"""
|
||||
if hasattr(self, '_id_to_role'):
|
||||
return self._id_to_role
|
||||
if self.df is None or 'TARGET_ID' not in self.df.columns or 'ROLE_ID' not in self.df.columns:
|
||||
return {}
|
||||
return dict(
|
||||
zip(
|
||||
self.df['TARGET_ID'].dropna().astype(str).str.strip(),
|
||||
self.df['ROLE_ID'].fillna('').astype(str).str.strip(),
|
||||
)
|
||||
)
|
||||
|
||||
def load_config(project_root: str) -> dict:
|
||||
"""加载 config/config.json 或 config.yaml(json 优先,便于部署时只改 json)"""
|
||||
import os
|
||||
config_dir = os.path.join(project_root, 'config')
|
||||
json_path = os.path.join(config_dir, 'config.json')
|
||||
yaml_path = os.path.join(config_dir, 'config.yaml')
|
||||
if os.path.exists(json_path):
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
if os.path.exists(yaml_path):
|
||||
try:
|
||||
import yaml
|
||||
with open(yaml_path, 'r', encoding='utf-8') as f:
|
||||
return yaml.safe_load(f)
|
||||
except ImportError:
|
||||
pass
|
||||
return {
|
||||
'source': 'json',
|
||||
'json': {
|
||||
'input_file': 'data/raw_data_sample.json',
|
||||
'output_file': 'data/nodes.json',
|
||||
'report_file': 'report/detailed_cleaning_report.json',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import os
|
||||
from data_loader import load_data as load_data_source
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(script_dir)
|
||||
|
||||
config = load_config(project_root)
|
||||
source = config.get('source', 'json')
|
||||
print(f"数据源模式: {source}")
|
||||
|
||||
try:
|
||||
data, paths = load_data_source(config, project_root)
|
||||
except (FileNotFoundError, ValueError) as e:
|
||||
print(f"错误:{e}")
|
||||
exit(1)
|
||||
|
||||
os.makedirs(os.path.dirname(paths['report']), exist_ok=True)
|
||||
|
||||
cleaner = AdvancedDataCleaner(
|
||||
output_file=paths['output'],
|
||||
report_file=paths['report'],
|
||||
data=data,
|
||||
)
|
||||
cleaner.run()
|
||||
614
final/src/data_loader.py
Normal file
614
final/src/data_loader.py
Normal file
@@ -0,0 +1,614 @@
|
||||
"""
|
||||
数据加载模块:支持从 JSON 文件或达梦数据库读取数据
|
||||
开发阶段使用 JSON,生产环境切换到达梦时只需修改 config 中的 source
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
try:
|
||||
from attribute_schema import FUNCTION_VECTOR_KEYS, raw_to_target_schema
|
||||
except ImportError:
|
||||
from src.attribute_schema import FUNCTION_VECTOR_KEYS, raw_to_target_schema
|
||||
|
||||
|
||||
def load_from_json(file_path: str, project_root: str = None) -> list:
|
||||
"""从 JSON 文件加载数据"""
|
||||
path = file_path
|
||||
if project_root and not os.path.isabs(file_path):
|
||||
path = os.path.join(project_root, file_path)
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def _dm_connect(config: dict):
|
||||
"""创建达梦连接,返回 (conn, db_cfg)。需环境变量 DM_PASS。"""
|
||||
try:
|
||||
import dmPython
|
||||
except ImportError as e:
|
||||
raise RuntimeError(
|
||||
"达梦数据库模式需要安装 dmPython。\n"
|
||||
"请执行: pip install dmPython\n"
|
||||
"并在有达梦客户端的环境运行(需配置 DM_HOME/LD_LIBRARY_PATH)"
|
||||
) from e
|
||||
|
||||
db_cfg = config.get('db', {})
|
||||
host = os.getenv('DM_HOST', db_cfg.get('host', '127.0.0.1'))
|
||||
port = int(os.getenv('DM_PORT', str(db_cfg.get('port', 9080))))
|
||||
user = os.getenv('DM_USER', db_cfg.get('user', 'SYSDBA'))
|
||||
password = os.getenv('DM_PASS')
|
||||
if not password:
|
||||
raise ValueError(
|
||||
"达梦模式需要设置环境变量 DM_PASS。\n"
|
||||
"示例: export DM_PASS=你的密码"
|
||||
)
|
||||
|
||||
conn = dmPython.connect(
|
||||
user=user,
|
||||
password=password,
|
||||
server=host,
|
||||
port=port,
|
||||
schema=db_cfg.get('schema', 'SYSDBA'),
|
||||
)
|
||||
return conn, db_cfg
|
||||
|
||||
|
||||
def load_from_dm(config: dict, project_root: str = None) -> list:
|
||||
"""
|
||||
从达梦数据库加载数据
|
||||
需要安装 dmPython 及达梦客户端库,仅在 source=db 时调用
|
||||
"""
|
||||
conn, db_cfg = _dm_connect(config)
|
||||
|
||||
try:
|
||||
cursor = conn.cursor()
|
||||
query = (db_cfg.get('query') or '').strip()
|
||||
table = db_cfg.get('table')
|
||||
if query:
|
||||
cursor.execute(query)
|
||||
elif table:
|
||||
schema = db_cfg.get('schema', 'SYSDBA')
|
||||
cursor.execute(f'SELECT * FROM "{schema}"."{table}"')
|
||||
else:
|
||||
raise ValueError(
|
||||
"达梦模式需配置 db.query(自定义 SQL)或 db.table。\n"
|
||||
"当前达梦为规范化表,需通过 query 编写 JOIN 得到扁平化数据。"
|
||||
)
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
rows = cursor.fetchall()
|
||||
cursor.close()
|
||||
return [dict(zip(columns, row)) for row in rows]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def load_data(config: dict, project_root: str) -> Tuple[list, dict]:
|
||||
"""
|
||||
根据配置加载数据,返回 (数据列表, 路径配置)
|
||||
"""
|
||||
source = config.get('source', 'json')
|
||||
project_root = project_root or os.getcwd()
|
||||
|
||||
if source == 'json':
|
||||
json_cfg = config.get('json', {})
|
||||
data = load_from_json(json_cfg.get('input_file', 'data/raw_data_sample.json'), project_root)
|
||||
paths = {
|
||||
'output': _resolve_path(json_cfg.get('output_file', 'data/nodes.json'), project_root),
|
||||
'report': _resolve_path(json_cfg.get('report_file', 'report/detailed_cleaning_report.json'), project_root),
|
||||
}
|
||||
return data, paths
|
||||
elif source == 'db':
|
||||
data = load_from_dm(config, project_root)
|
||||
json_cfg = config.get('json', {})
|
||||
paths = {
|
||||
'output': _resolve_path(json_cfg.get('output_file', 'data/nodes.json'), project_root),
|
||||
'report': _resolve_path(json_cfg.get('report_file', 'report/detailed_cleaning_report.json'), project_root),
|
||||
}
|
||||
return data, paths
|
||||
else:
|
||||
raise ValueError(f"不支持的 source 类型: {source},应为 json 或 db")
|
||||
|
||||
|
||||
def load_triplets_from_dm(config: dict, project_root: str = None) -> list:
|
||||
"""
|
||||
从达梦数据库读取三元组数据(RELATION_INSTANCE 表)
|
||||
返回格式: [{"HEAD_ID": x, "RELATION_TYPE": y, "TAIL_ID": z, ...}, ...]
|
||||
"""
|
||||
conn, db_cfg = _dm_connect(config)
|
||||
|
||||
try:
|
||||
cursor = conn.cursor()
|
||||
schema = db_cfg.get('schema', 'SYSDBA')
|
||||
triplet_table = db_cfg.get('triplet_table', 'RELATION_INSTANCE')
|
||||
query = db_cfg.get('triplet_query')
|
||||
if query and query.strip():
|
||||
cursor.execute(query.strip())
|
||||
else:
|
||||
cursor.execute(f'SELECT * FROM "{schema}"."{triplet_table}"')
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
rows = cursor.fetchall()
|
||||
cursor.close()
|
||||
return [dict(zip(columns, row)) for row in rows]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _ci_key_map(d: dict) -> Dict[str, str]:
|
||||
return {str(k).upper(): k for k in d.keys()}
|
||||
|
||||
|
||||
def _get_ci(d: dict, key: str) -> Any:
|
||||
k = _ci_key_map(d).get(str(key).upper())
|
||||
return d.get(k) if k else None
|
||||
|
||||
|
||||
def _set_ci(d: dict, key: str, value: Any) -> None:
|
||||
um = _ci_key_map(d)
|
||||
k = um.get(str(key).upper())
|
||||
if k is None:
|
||||
d[key] = value
|
||||
else:
|
||||
d[k] = value
|
||||
|
||||
|
||||
def _table_columns(cursor, schema: str, table: str) -> List[str]:
|
||||
cursor.execute(f'SELECT * FROM "{schema}"."{table}" WHERE 1=0')
|
||||
return [desc[0] for desc in cursor.description]
|
||||
|
||||
|
||||
def _serialize_metric_value(val: Any) -> str:
|
||||
if val is None:
|
||||
return ""
|
||||
if isinstance(val, (dict, list)):
|
||||
return json.dumps(val, ensure_ascii=False)
|
||||
return str(val)
|
||||
|
||||
|
||||
def _value_from_aligned_node(node: dict, attr: str) -> Any:
|
||||
"""从 raw_to_target_schema 输出中取 required_attrs 对应值。"""
|
||||
if attr in FUNCTION_VECTOR_KEYS:
|
||||
return (node.get("function_vector") or {}).get(attr)
|
||||
if attr in ("position", "effective_radius"):
|
||||
return (node.get("spatial") or {}).get(attr)
|
||||
if attr in ("response_time", "cycle_time", "data_age", "time_window"):
|
||||
return (node.get("temporal") or {}).get(attr)
|
||||
if attr in ("core_performance", "survivability", "mtbf"):
|
||||
return (node.get("performance") or {}).get(attr)
|
||||
if attr in ("protocol_list", "format_list", "interface_list"):
|
||||
return node.get(attr)
|
||||
if attr in ("security_level", "org_unit", "nation", "history_success"):
|
||||
return node.get(attr)
|
||||
return None
|
||||
|
||||
|
||||
def _fetch_reasoned_triples(cursor, config: dict, formal: dict) -> List[dict]:
|
||||
db_cfg = config.get("db", {})
|
||||
schema = db_cfg.get("schema", "SYSDBA")
|
||||
user_id = formal.get("user_id", "ADMIN")
|
||||
task_id = formal.get("task_id", "TASK_FULL_KB")
|
||||
source_sys_type = formal.get("source_sys_type", "关系推理后的体系")
|
||||
raw_query = (formal.get("source_query") or "").strip()
|
||||
if raw_query:
|
||||
cursor.execute(raw_query)
|
||||
else:
|
||||
table = formal.get("triplet_table") or db_cfg.get("triplet_table", "RELATION_INSTANCE")
|
||||
uc = formal.get("user_column", "USER_ID")
|
||||
tc = formal.get("task_column", "TASK_ID")
|
||||
sc = formal.get("sys_type_column", "SYS_TYPE")
|
||||
sql = (
|
||||
f'SELECT * FROM "{schema}"."{table}" '
|
||||
f'WHERE "{uc}" = ? AND "{tc}" = ? AND "{sc}" = ?'
|
||||
)
|
||||
cursor.execute(sql, [user_id, task_id, source_sys_type])
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
rows = cursor.fetchall()
|
||||
return [dict(zip(columns, row)) for row in rows]
|
||||
|
||||
|
||||
def _validate_source_sys_type(rows: List[dict], formal: dict) -> Optional[str]:
|
||||
"""若存在与 source_sys_type 不一致的 SYS_TYPE,返回错误信息(硬失败)。"""
|
||||
if formal.get("skip_sys_type_validation"):
|
||||
return None
|
||||
sc = formal.get("sys_type_column", "SYS_TYPE")
|
||||
expected = formal.get("source_sys_type", "关系推理后的体系")
|
||||
if not rows:
|
||||
return None
|
||||
vals = [_get_ci(r, sc) for r in rows]
|
||||
if all(v is None for v in vals):
|
||||
# 结果集中无 SYS_TYPE 列(例如仅用 source_query 投影),由 SQL 保证类型
|
||||
return None
|
||||
bad_indices = []
|
||||
for i, v in enumerate(vals):
|
||||
if v is None or str(v).strip() != str(expected).strip():
|
||||
bad_indices.append(i)
|
||||
if bad_indices:
|
||||
return f"sys_type_mismatch: expected={expected!r}, bad_row_indices={bad_indices[:20]}"
|
||||
return None
|
||||
|
||||
|
||||
def _entity_ids_from_triples(triples: List[dict]) -> List[str]:
|
||||
ids = set()
|
||||
for t in triples:
|
||||
h = _get_ci(t, "HEAD_ID") or t.get("head")
|
||||
tail = _get_ci(t, "TAIL_ID") or t.get("tail")
|
||||
if h is not None and str(h).strip():
|
||||
ids.add(str(h).strip())
|
||||
if tail is not None and str(tail).strip():
|
||||
ids.add(str(tail).strip())
|
||||
return sorted(ids)
|
||||
|
||||
|
||||
def _load_objectives_by_target(
|
||||
cursor, schema: str, table: str, entity_col: str, target_ids: List[str],
|
||||
) -> Dict[str, dict]:
|
||||
if not target_ids:
|
||||
return {}
|
||||
cols = _table_columns(cursor, schema, table)
|
||||
ec = None
|
||||
for c in cols:
|
||||
if c.upper() == entity_col.upper():
|
||||
ec = c
|
||||
break
|
||||
if not ec:
|
||||
return {}
|
||||
placeholders = ",".join(["?"] * len(target_ids))
|
||||
sql = f'SELECT * FROM "{schema}"."{table}" WHERE "{ec}" IN ({placeholders})'
|
||||
cursor.execute(sql, target_ids)
|
||||
out = {}
|
||||
row_cols = [desc[0] for desc in cursor.description]
|
||||
for row in cursor.fetchall():
|
||||
d = dict(zip(row_cols, row))
|
||||
tid = _get_ci(d, entity_col)
|
||||
if tid is not None:
|
||||
out[str(tid).strip()] = d
|
||||
return out
|
||||
|
||||
|
||||
def _load_existing_metric_keys(
|
||||
cursor, schema: str, attr_table: str, entity_col: str,
|
||||
metric_col: str, target_ids: List[str],
|
||||
) -> Dict[str, set]:
|
||||
"""target_id -> set(METRIC_ID) 已有记录。"""
|
||||
if not target_ids:
|
||||
return {}
|
||||
tcols = _table_columns(cursor, schema, attr_table)
|
||||
ec = next((c for c in tcols if c.upper() == entity_col.upper()), None)
|
||||
mc = next((c for c in tcols if c.upper() == metric_col.upper()), None)
|
||||
if not ec or not mc:
|
||||
return {}
|
||||
placeholders = ",".join(["?"] * len(target_ids))
|
||||
sql = (
|
||||
f'SELECT "{ec}", "{mc}" FROM "{schema}"."{attr_table}" '
|
||||
f'WHERE "{ec}" IN ({placeholders})'
|
||||
)
|
||||
cursor.execute(sql, target_ids)
|
||||
out: Dict[str, set] = {}
|
||||
for row in cursor.fetchall():
|
||||
tid, mid = row[0], row[1]
|
||||
if tid is None or mid is None:
|
||||
continue
|
||||
tid_s = str(tid).strip()
|
||||
out.setdefault(tid_s, set()).add(str(mid).strip())
|
||||
return out
|
||||
|
||||
|
||||
def _insert_generated_metrics(
|
||||
cursor,
|
||||
schema: str,
|
||||
attr_table: str,
|
||||
table_cols: List[str],
|
||||
entity_col: str,
|
||||
metric_col: str,
|
||||
value_col: str,
|
||||
target_id: str,
|
||||
metric_id: str,
|
||||
value_str: str,
|
||||
static_extra: Optional[dict],
|
||||
) -> bool:
|
||||
"""插入一条指标行;仅写入表中存在的列。未写入任何列时返回 False。"""
|
||||
um = {c.upper(): c for c in table_cols}
|
||||
row: Dict[str, Any] = {}
|
||||
if entity_col.upper() in um:
|
||||
row[um[entity_col.upper()]] = target_id
|
||||
if metric_col.upper() in um:
|
||||
row[um[metric_col.upper()]] = metric_id
|
||||
if value_col.upper() in um:
|
||||
row[um[value_col.upper()]] = value_str
|
||||
if static_extra:
|
||||
for k, v in static_extra.items():
|
||||
ku = str(k).upper()
|
||||
if ku in um:
|
||||
row[um[ku]] = v
|
||||
cols = list(row.keys())
|
||||
if entity_col.upper() not in um or metric_col.upper() not in um:
|
||||
return False
|
||||
if not cols:
|
||||
return False
|
||||
quoted = ",".join(f'"{c}"' for c in cols)
|
||||
ph = ",".join(["?"] * len(cols))
|
||||
sql = f'INSERT INTO "{schema}"."{attr_table}" ({quoted}) VALUES ({ph})'
|
||||
cursor.execute(sql, [row[c] for c in cols])
|
||||
return True
|
||||
|
||||
|
||||
def _build_formal_triple_rows(
|
||||
source_rows: List[dict],
|
||||
formal: dict,
|
||||
batch_id: str,
|
||||
result_table_cols: List[str],
|
||||
) -> Tuple[List[dict], int]:
|
||||
"""复制源行,写入 target_sys_type 与 batch_id;丢弃无效核字段。"""
|
||||
sys_col = formal.get("sys_type_column", "SYS_TYPE")
|
||||
target_sys = formal.get("target_sys_type", "形式化后的体系")
|
||||
pk = formal.get("triplet_primary_key") or formal.get("triplet_pk")
|
||||
omit_pk = formal.get("triplet_omit_primary_key_on_insert", True)
|
||||
batch_col = formal.get("triple_batch_column", "BATCH_ID")
|
||||
colset_upper = {c.upper() for c in result_table_cols}
|
||||
out_rows = []
|
||||
skipped = 0
|
||||
for r in source_rows:
|
||||
h = _get_ci(r, "HEAD_ID") or r.get("head")
|
||||
tail = _get_ci(r, "TAIL_ID") or r.get("tail")
|
||||
rel = _get_ci(r, "RELATION_TYPE") or r.get("relation")
|
||||
if not h or not tail or not rel:
|
||||
skipped += 1
|
||||
continue
|
||||
new_r = dict(r)
|
||||
_set_ci(new_r, sys_col, target_sys)
|
||||
if batch_col.upper() in colset_upper:
|
||||
_set_ci(new_r, batch_col, batch_id)
|
||||
if omit_pk and pk:
|
||||
kmap = _ci_key_map(new_r)
|
||||
pk_actual = kmap.get(str(pk).upper())
|
||||
if pk_actual and pk_actual in new_r:
|
||||
del new_r[pk_actual]
|
||||
filtered = {}
|
||||
for k, v in new_r.items():
|
||||
if k in result_table_cols:
|
||||
filtered[k] = v
|
||||
out_rows.append(filtered)
|
||||
return out_rows, skipped
|
||||
|
||||
|
||||
def _insert_rows_executemany(
|
||||
cursor, schema: str, table: str, rows: List[dict],
|
||||
) -> int:
|
||||
if not rows:
|
||||
return 0
|
||||
all_keys = []
|
||||
for r in rows:
|
||||
for k in r:
|
||||
if k not in all_keys:
|
||||
all_keys.append(k)
|
||||
quoted = ",".join(f'"{k}"' for k in all_keys)
|
||||
ph = ",".join(["?"] * len(all_keys))
|
||||
sql = f'INSERT INTO "{schema}"."{table}" ({quoted}) VALUES ({ph})'
|
||||
params = [tuple(r.get(k) for k in all_keys) for r in rows]
|
||||
cursor.executemany(sql, params)
|
||||
return len(rows)
|
||||
|
||||
|
||||
def _insert_res_formal_analysis(
|
||||
cursor,
|
||||
schema: str,
|
||||
analysis_table: str,
|
||||
batch_id: str,
|
||||
formal: dict,
|
||||
stats: dict,
|
||||
detail_obj: dict,
|
||||
) -> bool:
|
||||
cols = _table_columns(cursor, schema, analysis_table)
|
||||
cu = {c.upper(): c for c in cols}
|
||||
row: Dict[str, Any] = {}
|
||||
mapping = [
|
||||
("BATCH_ID", batch_id),
|
||||
("USER_ID", formal.get("user_id", "ADMIN")),
|
||||
("TASK_ID", formal.get("task_id", "TASK_FULL_KB")),
|
||||
("SOURCE_SYS_TYPE", formal.get("source_sys_type", "")),
|
||||
("TARGET_SYS_TYPE", formal.get("target_sys_type", "")),
|
||||
]
|
||||
for key, val in mapping:
|
||||
if key in cu:
|
||||
row[cu[key]] = val
|
||||
detail_json = json.dumps(detail_obj, ensure_ascii=False)
|
||||
detail_candidates = (
|
||||
"DETAIL_JSON",
|
||||
"DETAIL",
|
||||
"ANALYSIS_JSON",
|
||||
"CONTENT",
|
||||
"RULE_DETAIL",
|
||||
"STATS_JSON",
|
||||
"RES_FORMAL_ANALYSIS",
|
||||
)
|
||||
detail_key_col = None
|
||||
for cand in detail_candidates:
|
||||
if cand in cu:
|
||||
detail_key_col = cu[cand]
|
||||
break
|
||||
if not detail_key_col:
|
||||
for c in cols:
|
||||
u = c.upper()
|
||||
if any(
|
||||
token in u
|
||||
for token in (
|
||||
"DETAIL",
|
||||
"JSON",
|
||||
"CONTENT",
|
||||
"RULE",
|
||||
"TEXT",
|
||||
"MEMO",
|
||||
"DESC",
|
||||
"REMARK",
|
||||
"INFO",
|
||||
"ANALYSIS",
|
||||
)
|
||||
):
|
||||
detail_key_col = c
|
||||
break
|
||||
if detail_key_col:
|
||||
row[detail_key_col] = detail_json
|
||||
static = formal.get("analysis_static_columns") or formal.get("analysis_static") or {}
|
||||
for k, v in static.items():
|
||||
if str(k).upper() in cu:
|
||||
row[cu[str(k).upper()]] = v
|
||||
insert_cols = formal.get("analysis_insert_columns")
|
||||
if insert_cols:
|
||||
use_cols = [c for c in insert_cols if c in cols]
|
||||
if not use_cols:
|
||||
return False
|
||||
vals = [row.get(c) for c in use_cols]
|
||||
ph = ",".join(["?"] * len(use_cols))
|
||||
qc = ",".join(f'"{c}"' for c in use_cols)
|
||||
sql = f'INSERT INTO "{schema}"."{analysis_table}" ({qc}) VALUES ({ph})'
|
||||
cursor.execute(sql, vals)
|
||||
return True
|
||||
if not row:
|
||||
return False
|
||||
quoted = ",".join(f'"{c}"' for c in row.keys())
|
||||
ph = ",".join(["?"] * len(row))
|
||||
sql = f'INSERT INTO "{schema}"."{analysis_table}" ({quoted}) VALUES ({ph})'
|
||||
cursor.execute(sql, list(row.values()))
|
||||
return True
|
||||
|
||||
|
||||
def run_formalization_pipeline(config: dict) -> dict:
|
||||
"""
|
||||
模块 1.3:读取「关系推理后的体系」三元组,补齐实体属性并写回,
|
||||
将形式化结果作为新记录写入 formal_result_table,并写入 RES_FORMAL_ANALYSIS。
|
||||
需在 config 中 formal.enabled=true 且 source=db;达梦密码 DM_PASS。
|
||||
"""
|
||||
formal = config.get("formal") or {}
|
||||
if not formal.get("enabled", False):
|
||||
return {"ok": False, "reason": "formal.disabled", "batch_id": None, "stats": {}}
|
||||
if config.get("source", "json") != "db":
|
||||
return {"ok": False, "reason": "source_not_db", "batch_id": None, "stats": {}}
|
||||
|
||||
batch_id = str(uuid.uuid4())
|
||||
stats: Dict[str, Any] = {
|
||||
"total_triples": 0,
|
||||
"inserted_formal_count": 0,
|
||||
"generated_attrs_count": 0,
|
||||
"skipped_triples": 0,
|
||||
"analysis_written": False,
|
||||
}
|
||||
|
||||
conn = None
|
||||
try:
|
||||
conn, db_cfg = _dm_connect(config)
|
||||
except (RuntimeError, ValueError) as e:
|
||||
return {"ok": False, "reason": str(e), "batch_id": None, "stats": stats}
|
||||
|
||||
schema = db_cfg.get("schema", "SYSDBA")
|
||||
user_id = formal.get("user_id", "ADMIN")
|
||||
task_id = formal.get("task_id", "TASK_FULL_KB")
|
||||
source_sys_type = formal.get("source_sys_type", "关系推理后的体系")
|
||||
target_sys_type = formal.get("target_sys_type", "形式化后的体系")
|
||||
attr_table = formal.get("attribute_table", "METRIC_VALUE_INSTANCE")
|
||||
result_table = formal.get("formal_result_table", "RELATION_INSTANCE")
|
||||
analysis_table = formal.get("analysis_table", "RES_FORMAL_ANALYSIS")
|
||||
entity_col = formal.get("entity_id_column", "TARGET_ID")
|
||||
metric_col = formal.get("metric_id_column", "METRIC_ID")
|
||||
value_col = formal.get("metric_value_column", "METRIC_VALUE")
|
||||
objective_table = formal.get("objective_table") or db_cfg.get("table", "OBJECTIVE_INSTANCE")
|
||||
required_attrs: List[str] = list(formal.get("required_attrs") or [])
|
||||
|
||||
try:
|
||||
if hasattr(conn, "autocommit"):
|
||||
conn.autocommit = False
|
||||
cursor = conn.cursor()
|
||||
|
||||
triples = _fetch_reasoned_triples(cursor, config, formal)
|
||||
stats["total_triples"] = len(triples)
|
||||
err = _validate_source_sys_type(triples, formal)
|
||||
if err:
|
||||
conn.rollback()
|
||||
return {"ok": False, "reason": err, "batch_id": batch_id, "stats": stats}
|
||||
|
||||
eids = _entity_ids_from_triples(triples)
|
||||
objectives = _load_objectives_by_target(
|
||||
cursor, schema, objective_table, entity_col, eids,
|
||||
)
|
||||
existing_metrics = _load_existing_metric_keys(
|
||||
cursor, schema, attr_table, entity_col, metric_col, eids,
|
||||
)
|
||||
mvi_cols = _table_columns(cursor, schema, attr_table)
|
||||
static_metric = formal.get("metric_insert_static") or {}
|
||||
|
||||
for tid in eids:
|
||||
raw = dict(objectives.get(tid, {}))
|
||||
raw["TARGET_ID"] = tid
|
||||
aligned = raw_to_target_schema(raw)
|
||||
have = existing_metrics.get(tid, set())
|
||||
for attr in required_attrs:
|
||||
if attr in have:
|
||||
continue
|
||||
val = _value_from_aligned_node(aligned, attr)
|
||||
vstr = _serialize_metric_value(val)
|
||||
if _insert_generated_metrics(
|
||||
cursor,
|
||||
schema,
|
||||
attr_table,
|
||||
mvi_cols,
|
||||
entity_col,
|
||||
metric_col,
|
||||
value_col,
|
||||
tid,
|
||||
attr,
|
||||
vstr,
|
||||
static_metric,
|
||||
):
|
||||
stats["generated_attrs_count"] += 1
|
||||
have.add(attr)
|
||||
|
||||
result_cols = _table_columns(cursor, schema, result_table)
|
||||
formal_rows, skipped = _build_formal_triple_rows(
|
||||
triples, formal, batch_id, result_cols,
|
||||
)
|
||||
stats["skipped_triples"] = skipped
|
||||
if formal_rows:
|
||||
_insert_rows_executemany(cursor, schema, result_table, formal_rows)
|
||||
stats["inserted_formal_count"] = len(formal_rows)
|
||||
|
||||
detail = {
|
||||
"batch_id": batch_id,
|
||||
"user_id": user_id,
|
||||
"task_id": task_id,
|
||||
"source_sys_type": source_sys_type,
|
||||
"target_sys_type": target_sys_type,
|
||||
"stats": stats,
|
||||
"rules": formal.get("rules_summary")
|
||||
or "结构校验+属性对齐(raw_to_target_schema)+sys_type切换为形式化后的体系",
|
||||
}
|
||||
stats["analysis_written"] = _insert_res_formal_analysis(
|
||||
cursor, schema, analysis_table, batch_id, formal, stats, detail,
|
||||
)
|
||||
if not stats["analysis_written"]:
|
||||
raise RuntimeError(
|
||||
"RES_FORMAL_ANALYSIS 未写入:表无可匹配列。请配置 formal.analysis_insert_columns "
|
||||
"或在表中提供 BATCH_ID/USER_ID/TASK_ID 及 DETAIL/JSON 类文本列。"
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
return {"ok": True, "reason": None, "batch_id": batch_id, "stats": stats}
|
||||
except Exception as e:
|
||||
if conn is not None:
|
||||
try:
|
||||
conn.rollback()
|
||||
except Exception:
|
||||
pass
|
||||
stats.setdefault("error", str(e))
|
||||
return {"ok": False, "reason": str(e), "batch_id": batch_id, "stats": stats}
|
||||
finally:
|
||||
if conn is not None:
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _resolve_path(path: str, project_root: str) -> str:
|
||||
if os.path.isabs(path):
|
||||
return path
|
||||
return os.path.join(project_root, path)
|
||||
666
final/src/main_227.py
Normal file
666
final/src/main_227.py
Normal file
@@ -0,0 +1,666 @@
|
||||
# main.py
|
||||
import numpy as np
|
||||
import json, copy
|
||||
import pandas as pd
|
||||
from typing import Dict, List
|
||||
import sys
|
||||
from pathlib import Path
|
||||
sys.path.append(str(Path(__file__).parent.parent))
|
||||
from scripts.f_relation1125 import RelationCalculator
|
||||
from scripts.f_net1125 import TaskNetworkEvaluator
|
||||
|
||||
# ========== 新增:导入 cleaner ==========
|
||||
sys.path.append(str(Path(__file__).parent)) # 添加 src 目录到路径
|
||||
from cleaner import load_config, AdvancedDataCleaner
|
||||
from data_loader import load_data as load_data_source, run_formalization_pipeline
|
||||
|
||||
# -------------- 默认配置(与原来硬编码保持一致) --------------
|
||||
DEFAULT_CFG = {
|
||||
"W_REL": { # 5 类关系权重
|
||||
"IS": {"w_f": 0.35, "w_s": 0.25, "w_t": 0.15, "w_p": 0.15, "w_i": 0.10},
|
||||
"CC": {"w_f": 0.30, "w_s": 0.20, "w_t": 0.20, "w_p": 0.20, "w_i": 0.10},
|
||||
"SF": {"w_f": 0.30, "w_s": 0.20, "w_t": 0.25, "w_p": 0.15, "w_i": 0.10},
|
||||
"PD": {"w_f": 0.25, "w_s": 0.35, "w_t": 0.15, "w_p": 0.15, "w_i": 0.10},
|
||||
"CO": {"w_f": 0.30, "w_s": 0.20, "w_t": 0.20, "w_p": 0.20, "w_i": 0.10},
|
||||
},
|
||||
"W_NET": { # 5 类任务网络权重
|
||||
"defense": {"w1": 0.30, "w2": 0.25, "w3": 0.20, "w4": 0.15, "w5": 0.10},
|
||||
"fire": {"w1": 0.35, "w2": 0.25, "w3": 0.20, "w4": 0.10, "w5": 0.10},
|
||||
"logistics":{"w1": 0.30, "w2": 0.25, "w3": 0.20, "w4": 0.15, "w5": 0.10},
|
||||
"medical": {"w1": 0.35, "w2": 0.25, "w3": 0.20, "w4": 0.10, "w5": 0.10},
|
||||
"evacuation":{"w1":0.30, "w2": 0.25, "w3": 0.20, "w4": 0.15, "w5": 0.10},
|
||||
},
|
||||
"LAMBDA": {"IS": 100, "CC": 50, "SF": 80, "PD": 30, "CO": 60},
|
||||
"TIME": {"alpha": 0.5, "beta": 0.6, "gamma": 0.4},
|
||||
"PD_SGM": 20,
|
||||
"THRESHOLD": 0.15 # 关系过滤阈值
|
||||
}
|
||||
|
||||
|
||||
def var_desc():
|
||||
return {
|
||||
# 共用
|
||||
"dij": "节点 i 与 j 之间的欧氏距离",
|
||||
"Ri": "节点 i 的有效作用半径",
|
||||
"Rmatch(i,j)": "两节点作用范围重叠度",
|
||||
"s_area": "作战区域关联度(同区 1.0 / 邻区 0.7 / 异区 0.3)",
|
||||
"stdij": "接口标准兼容性(0-1)",
|
||||
"rij": "任务角色匹配度(0-1)",
|
||||
"t_resp": "节点响应时间得分 exp(-α·T_resp)",
|
||||
"t_cycle": "节点处理周期得分 exp(-β·T_cycle)",
|
||||
"fresh_i": "数据新鲜度 exp(-γ·数据年龄)",
|
||||
"wij": "时间窗口重叠比例",
|
||||
"p_core": "节点核心性能",
|
||||
"p_rel": "节点可靠性 MTBF/MTBF_max",
|
||||
"p_surv": "节点生存性",
|
||||
"protij": "协议兼容性",
|
||||
"fmtij": "数据格式兼容性",
|
||||
"secij": "安全等级兼容性",
|
||||
"orgij": "组织隶属关联度",
|
||||
"histij": "历史交互成功率",
|
||||
# 关系专用
|
||||
"M_F^IS": "情报保障功能匹配项",
|
||||
"M_S^IS": "情报保障空间匹配项",
|
||||
"M_T^IS": "情报保障时间匹配项",
|
||||
"M_P^IS": "情报保障性能匹配项",
|
||||
"M_I^IS": "情报保障交互匹配项",
|
||||
"λ_IS": "情报特征距离(λ_IS=100 km)",
|
||||
# 网络专用
|
||||
"N_IC": "信息对抗节点数量",
|
||||
"∑f_IC/N_IC": "信息对抗节点平均能力",
|
||||
"∑L_IS/N_IS": "情报保障关系平均强度",
|
||||
"∑t_resp/N": "系统平均响应速度",
|
||||
"∑L_CC/N_CC": "指挥控制关系平均强度",
|
||||
"∑f_CS/N_CS": "协同打击节点平均能力",
|
||||
"∑f_IA/N_IA": "信息获取节点平均能力",
|
||||
"∑L_CO/N_CO": "协同作战关系平均强度",
|
||||
"∑p_core·f_CS/N_CS": "打击节点性能加权能力",
|
||||
"∑f_CPS/N_CPS": "综合保障节点平均能力",
|
||||
"∑f_DP/N_DP": "部署平台节点平均能力",
|
||||
"∑L_PD/N_PD": "平台部署关系平均强度",
|
||||
"∑p_rel/N": "系统平均可靠性",
|
||||
"∑f_IT/N_IT": "信息传输节点平均能力",
|
||||
"∑L_SF/N_SF": "状态反馈关系平均强度",
|
||||
"∑fresh_i/N": "系统平均数据新鲜度",
|
||||
"α1...α5": "综合防御权重 [0.30,0.25,0.20,0.15,0.10]",
|
||||
"β1...β5": "火力打击权重 [0.35,0.25,0.20,0.10,0.10]",
|
||||
"γ1...γ5": "后勤保障权重 [0.30,0.25,0.20,0.15,0.10]",
|
||||
"δ1...δ5": "医疗救援权重 [0.35,0.25,0.20,0.10,0.10]",
|
||||
"ε1...ε5": "紧急疏散权重 [0.30,0.25,0.20,0.15,0.10]",
|
||||
}
|
||||
|
||||
|
||||
def load_data(project_root=None):
|
||||
"""加载节点和三联数据,路径从配置读取(兼容 run_all 输出)"""
|
||||
if project_root is None:
|
||||
project_root = Path(__file__).resolve().parent.parent
|
||||
project_root = Path(project_root)
|
||||
cfg = load_config(str(project_root))
|
||||
eval_cfg = cfg.get("eval", {})
|
||||
json_cfg = cfg.get("json", {})
|
||||
triplet_cfg = cfg.get("triplet", {})
|
||||
|
||||
# 节点文件:优先 eval.nodes_file,否则 json.output_file
|
||||
nodes_file = eval_cfg.get("nodes_file") or json_cfg.get("output_file", "data/nodes.json")
|
||||
nodes_path = project_root / nodes_file if not Path(nodes_file).is_absolute() else Path(nodes_file)
|
||||
with open(nodes_path, 'r', encoding='utf-8') as f:
|
||||
nodes_data = json.load(f)
|
||||
|
||||
# 三元组文件:eval.triples_file,不存在则用 triplet.output_file
|
||||
triples_file = eval_cfg.get("triples_file", "data/triples.json")
|
||||
triples_path = project_root / triples_file if not Path(triples_file).is_absolute() else Path(triples_file)
|
||||
if not triples_path.exists():
|
||||
triples_path = project_root / triplet_cfg.get("output_file", "data/triples.json")
|
||||
with open(triples_path, 'r', encoding='utf-8') as f:
|
||||
triples_raw = json.load(f)
|
||||
# 兼容 list 格式(run_all 输出的 triples.json)
|
||||
triples_data = triples_raw if isinstance(triples_raw, dict) and "triples" in triples_raw else {"triples": triples_raw if isinstance(triples_raw, list) else []}
|
||||
|
||||
return nodes_data, triples_data
|
||||
|
||||
|
||||
def calculate_and_filter_relations(nodes_data: Dict, triples_data: Dict, threshold, cfg):
|
||||
"""
|
||||
计算所有三联关系的关系强度,并过滤强度大于阈值的边
|
||||
|
||||
返回:
|
||||
- filtered_relations: 过滤后的关系字典
|
||||
- filtered_triples: 过滤后的三元组列表(保持原始结构)
|
||||
- relation_stats: 关系统计信息
|
||||
"""
|
||||
print("开始计算关系强度并过滤...")
|
||||
|
||||
# 初始化关系计算器
|
||||
|
||||
calculator = RelationCalculator(nodes_data, cfg) # 改造见下
|
||||
# 关系类型映射(中文 -> 英文缩写)
|
||||
relation_mapping = {
|
||||
"情报保障": "IS",
|
||||
"指挥控制": "CC",
|
||||
"状态反馈": "SF",
|
||||
"平台部署": "PD",
|
||||
"协同作战": "CO"
|
||||
}
|
||||
|
||||
all_relations = {}
|
||||
filtered_relations = {}
|
||||
filtered_triples = []
|
||||
|
||||
valid_triples = 0
|
||||
skipped_triples = 0
|
||||
passed_triples = 0
|
||||
|
||||
for triple in triples_data["triples"]:
|
||||
head = triple["head"]
|
||||
tail = triple["tail"]
|
||||
relation_ch = triple["relation"]
|
||||
|
||||
# 检查节点是否存在
|
||||
if head not in nodes_data["nodes"] or tail not in nodes_data["nodes"]:
|
||||
print(f"警告: 节点不存在 - 头: {head}, 尾: {tail},跳过此三元组")
|
||||
skipped_triples += 1
|
||||
continue
|
||||
|
||||
# 映射关系类型
|
||||
relation_en = relation_mapping.get(relation_ch)
|
||||
if not relation_en:
|
||||
print(f"警告: 未知关系类型 '{relation_ch}',跳过此三元组")
|
||||
skipped_triples += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
# 根据关系类型调用对应的计算方法
|
||||
if relation_en == "IS":
|
||||
strength = calculator.calculate_IS_relation(head, tail)
|
||||
elif relation_en == "CC":
|
||||
strength = calculator.calculate_CC_relation(head, tail)
|
||||
elif relation_en == "SF":
|
||||
strength = calculator.calculate_SF_relation(head, tail)
|
||||
elif relation_en == "PD":
|
||||
strength = calculator.calculate_PD_relation(head, tail)
|
||||
elif relation_en == "CO":
|
||||
strength = calculator.calculate_CO_relation(head, tail)
|
||||
else:
|
||||
print(f"警告: 未实现的关系类型 '{relation_en}',跳过此三元组")
|
||||
skipped_triples += 1
|
||||
continue
|
||||
|
||||
# 存储所有关系强度(用于统计)
|
||||
all_relations[(relation_en, head, tail)] = strength
|
||||
valid_triples += 1
|
||||
|
||||
# 过滤:只保留强度大于阈值的关系
|
||||
if strength > threshold:
|
||||
filtered_relations[(relation_en, head, tail)] = strength
|
||||
# 创建过滤后的三元组(保持原始结构,添加强度字段)
|
||||
filtered_triple = triple.copy()
|
||||
filtered_triple["strength"] = float(strength) # 转换为Python float类型
|
||||
filtered_triples.append(filtered_triple)
|
||||
passed_triples += 1
|
||||
|
||||
if valid_triples % 10 == 0: # 每10个关系打印一次进度
|
||||
print(f"已计算 {valid_triples} 个关系,通过 {passed_triples} 个...")
|
||||
|
||||
except Exception as e:
|
||||
print(f"错误: 计算关系失败 - 头: {head}, 尾: {tail}, 关系: {relation_en}, 错误: {e}")
|
||||
skipped_triples += 1
|
||||
# 1. 先按类型细分
|
||||
type_detail = {}
|
||||
for (rel, _, _), val in all_relations.items():
|
||||
type_detail.setdefault(rel, {"all": 0, "pass": 0})
|
||||
type_detail[rel]["all"] += 1
|
||||
# if val > threshold:
|
||||
# type_detail[rel]["pass"] += 1
|
||||
# 新增:按类型收集被过滤的关系
|
||||
filtered_out = {} # rel -> [(head, tail, strength), ...]
|
||||
for (rel, head, tail), val in all_relations.items():
|
||||
if val <= threshold:
|
||||
filtered_out.setdefault(rel, []).append((head, tail, val))
|
||||
|
||||
# 2. 再打包大字典
|
||||
relation_stats = {
|
||||
"total_triples": len(triples_data["triples"]),
|
||||
"valid_triples": valid_triples,
|
||||
"skipped_triples": skipped_triples,
|
||||
"passed_triples": passed_triples,
|
||||
"pass_rate": passed_triples / valid_triples if valid_triples else 0,
|
||||
"threshold": threshold,
|
||||
"filtered_out":filtered_out,
|
||||
"type_detail": {
|
||||
# rel: {"all": d["all"], "pass": d["pass"], "rate": d["pass"] / d["all"] if d["all"] else 0.}
|
||||
rel: {"all": d["all"]}
|
||||
for rel, d in type_detail.items()
|
||||
}
|
||||
}
|
||||
|
||||
print(f"关系强度计算和过滤完成!")
|
||||
print(f"总三元组: {relation_stats['total_triples']}, 有效计算: {valid_triples}, 跳过: {skipped_triples}")
|
||||
print(f"通过过滤(> {threshold}): {passed_triples}, 通过率: {relation_stats['pass_rate']:.1%}")
|
||||
|
||||
return filtered_relations, filtered_triples, relation_stats, all_relations
|
||||
|
||||
|
||||
def evaluate_task_networks(nodes_data, relations, cfg):
|
||||
"""
|
||||
评估所有任务网络效能
|
||||
"""
|
||||
print("\n开始评估任务网络效能...")
|
||||
|
||||
# 初始化网络评估器
|
||||
evaluator = TaskNetworkEvaluator(nodes_data["nodes"], relations, weights=cfg["W_NET"])
|
||||
|
||||
# 评估所有任务网络
|
||||
results = evaluator.eval_all()
|
||||
|
||||
print("任务网络效能评估完成!")
|
||||
return results
|
||||
|
||||
|
||||
def generate_evaluation_report(relation_stats: Dict, network_results: Dict,
|
||||
filtered_relations: Dict, all_relations: Dict,
|
||||
threshold: float, cfg) -> str:
|
||||
"""
|
||||
生成详细的评价报告
|
||||
"""
|
||||
report = []
|
||||
report.append("=" * 60)
|
||||
report.append("网络关系与效能评价报告")
|
||||
report.append("=" * 60)
|
||||
report.append("")
|
||||
|
||||
# 1. 基本信息
|
||||
report.append("1. 基本信息")
|
||||
report.append("-" * 40)
|
||||
report.append(f"过滤阈值: {threshold} 【关系强度阈值】")
|
||||
report.append(f"总三元组数量: {relation_stats['total_triples']}")
|
||||
line = " ".join(f"{rel}={d['all']}" for rel, d in relation_stats["type_detail"].items())
|
||||
report.append(f"分别有:{line}")
|
||||
report.append(f"有效计算三元组: {relation_stats['valid_triples']}")
|
||||
report.append(f"跳过三元组: {relation_stats['skipped_triples']}")
|
||||
report.append(f"通过过滤三元组: {relation_stats['passed_triples']}")
|
||||
report.append(f"通过率: {relation_stats['pass_rate']:.1%}")
|
||||
report.append("")
|
||||
|
||||
# 添加边关系计算公式和说明
|
||||
report.append("2. 计算公式")
|
||||
report.append("边关系计算公式")
|
||||
report.append("-" * 50)
|
||||
rel_cn = {"IS": "情报保障", "CC": "指挥控制", "SF": "状态反馈", "PD": "平台部署", "CO": "协同作战"}
|
||||
desc = var_desc()
|
||||
for rel in ["IS", "CC", "SF", "PD", "CO"]:
|
||||
report.append(
|
||||
f"{rel_cn[rel]}关系 L_{rel}(i,j) = w_f·M_F^{rel} + w_s·M_S^{rel} + w_t·M_T^{rel} + w_p·M_P^{rel} + w_i·M_I^{rel}")
|
||||
report.append("【符号说明】")
|
||||
for sym, exp in desc.items():
|
||||
if sym.startswith(("M_F^", "M_S^", "M_T^", "M_P^", "M_I^")):
|
||||
report.append(f" {sym:<12} {exp}")
|
||||
report.append("")
|
||||
|
||||
|
||||
|
||||
report.append("任务网络效能计算公式")
|
||||
report.append("-" * 50)
|
||||
task_desc = {
|
||||
"defense": "综合防御 P_defense = α1·(∑f_IC/N_IC) + α2·(∑f_IA/N_IA) + α3·(∑L_IS/N_IS) + α4·(∑t_resp/N) + α5·(∑L_CC/N_CC)",
|
||||
"fire": "火力打击 P_fire = β1·(∑f_CS/N_CS) + β2·(∑f_IA/N_IA) + β3·(∑L_CO/N_CO) + β4·(∑p_core·f_CS/N_CS) + β5·(∑L_IS/N_IS)",
|
||||
"logistics": "后勤保障 P_logistics = γ1·(∑f_CPS/N_CPS) + γ2·(∑f_DP/N_DP) + γ3·(∑L_PD/N_PD) + γ4·(∑p_rel/N) + γ5·(∑f_IT/N_IT)",
|
||||
"medical": "医疗救援 P_medical = δ1·(∑f_CPS/N_CPS) + δ2·(∑t_resp/N) + δ3·(∑f_IT/N_IT) + δ4·(∑L_SF/N_SF) + δ5·(∑fresh_i/N)",
|
||||
"evacuation": "紧急疏散 P_evacuation = ε1·(∑f_CC/N_CC) + ε2·(∑f_IT/N_IT) + ε3·(∑L_CC/N_CC) + ε4·(∑f_CPS/N_CPS) + ε5·(∑t_resp/N)"
|
||||
}
|
||||
for task, form in task_desc.items():
|
||||
report.append(form)
|
||||
report.append("【符号说明】")
|
||||
for sym, exp in desc.items():
|
||||
if any(sym.startswith(x) for x in ["N_", "∑", "α", "β", "γ", "δ", "ε"]):
|
||||
report.append(f" {sym:<18} {exp}")
|
||||
report.append("")
|
||||
|
||||
# 2. 按关系类型统计
|
||||
report.append("3. 关系类型统计")
|
||||
report.append("-" * 40)
|
||||
|
||||
relation_type_stats = {}
|
||||
for (rel_type, head, tail), strength in all_relations.items():
|
||||
if rel_type not in relation_type_stats:
|
||||
relation_type_stats[rel_type] = {"all": [], "filtered": []}
|
||||
relation_type_stats[rel_type]["all"].append(strength)
|
||||
|
||||
if strength > threshold:
|
||||
relation_type_stats[rel_type]["filtered"].append(strength)
|
||||
|
||||
for rel_type in sorted(relation_type_stats.keys()):
|
||||
stats = relation_type_stats[rel_type]
|
||||
all_count = len(stats["all"])
|
||||
filtered_count = len(stats["filtered"])
|
||||
pass_rate = filtered_count / all_count if all_count > 0 else 0
|
||||
|
||||
report.append(f"{rel_type}关系:")
|
||||
report.append(f" 总数: {all_count}, 通过: {filtered_count}, 通过率: {pass_rate:.1%}")
|
||||
if stats["all"]:
|
||||
report.append(f" 平均强度: {np.mean(stats['all']):.3f}")
|
||||
if stats["filtered"]:
|
||||
report.append(f" 过滤后平均强度: {np.mean(stats['filtered']):.3f}")
|
||||
report.append("【被过滤掉的关系明细】")
|
||||
report.append("-" * 40)
|
||||
for rel, lst in relation_stats["filtered_out"].items():
|
||||
if not lst:
|
||||
continue
|
||||
report.append(f"{rel} 关系(强度 ≤ {threshold}):")
|
||||
for h, t, v in lst:
|
||||
report.append(f" {h} → {t} 强度={v:.3f}")
|
||||
report.append("") # 空行隔开
|
||||
|
||||
# 1. 取任务得分(排除 dimensions)
|
||||
task_scores = {task: score for task, score in network_results.items() if isinstance(score, float)}
|
||||
total = sum(task_scores.values()) or 1.0 # 防 0
|
||||
|
||||
# 2. 归一化 → 百分比
|
||||
task_pct = {task: (sc / total) * 100 for task, sc in task_scores.items()}
|
||||
|
||||
# 3. 输出
|
||||
report.append("4. 所属任务网络评估")
|
||||
report.append("-" * 40)
|
||||
task_name = {
|
||||
"defense": "综合防御",
|
||||
"fire": "火力打击",
|
||||
"logistics": "后勤保障",
|
||||
"medical": "医疗救援",
|
||||
"evacuation": "紧急疏散"
|
||||
}
|
||||
# 一行输出所有任务网络百分比
|
||||
line = " ".join(f"{task_name[task]} {pct:.1f}%" for task, pct in task_pct.items())
|
||||
report.append("任务网络概率:" + line)
|
||||
# 4. 最可能任务
|
||||
max_task = max(task_pct, key=task_pct.get)
|
||||
report.append(f"最有可能的任务网络:{task_name[max_task]} 可能性为:{task_pct[max_task]:.1f} %")
|
||||
|
||||
# 添加最终网络的五个维度评价
|
||||
report.append(f"5. 最终{task_name[max_task]}任务网络的五个维度评价")
|
||||
dimensions = network_results.get("dimensions", {})
|
||||
reference_value = 0.6 # 参考值
|
||||
|
||||
# 功能维度
|
||||
report.append("功能维度 (Function):")
|
||||
report.append(f"功能维度反映了网络中节点的功能匹配程度。得分: {dimensions.get('function', 0):.3f}")
|
||||
if dimensions.get('function', 0) > reference_value:
|
||||
report.append("高于参考值,表示节点在功能上高度互补,能够有效协同工作。")
|
||||
else:
|
||||
report.append("低于参考值,表示节点在功能上匹配程度较低,协同工作能力有待提升。")
|
||||
|
||||
# 边关系维度
|
||||
report.append("空间系维度 (Relationship):")
|
||||
report.append(f"空间维度反映了节点之间的连接强度。得分: {dimensions.get('relationship', 0):.3f}")
|
||||
if dimensions.get('relationship', 0) > reference_value:
|
||||
report.append("高于参考值,表示节点之间的连接紧密,信息传递高效。")
|
||||
else:
|
||||
report.append("低于参考值,表示节点之间的连接较弱,信息传递效率较低。")
|
||||
|
||||
# 性能维度
|
||||
report.append("性能维度 (Performance):")
|
||||
report.append(f"性能维度反映了节点的性能指标,如响应时间和可靠性。得分: {dimensions.get('performance', 0):.3f}")
|
||||
if dimensions.get('performance', 0) > reference_value:
|
||||
report.append("高于参考值,表示节点在性能上表现出色,能够稳定运行。")
|
||||
else:
|
||||
report.append("低于参考值,表示节点在性能上存在不足,稳定性有待提高。")
|
||||
|
||||
# 时间维度
|
||||
report.append("时间维度 (Temporal):")
|
||||
report.append(f"时间维度反映了节点的时间同步性和响应能力。得分: {dimensions.get('temporal', 0):.3f}")
|
||||
if dimensions.get('temporal', 0) > reference_value:
|
||||
report.append("高于参考值,表示节点在时间上高度同步,能够快速响应。")
|
||||
else:
|
||||
report.append("低于参考值,表示节点在时间同步性上存在偏差,响应速度较慢。")
|
||||
|
||||
# 交互维度
|
||||
report.append("交互维度 (Interaction):")
|
||||
report.append(f"交互维度反映了节点之间的协议、格式和安全兼容性。得分: {dimensions.get('interaction', 0):.3f}")
|
||||
if dimensions.get('interaction', 0) > reference_value:
|
||||
report.append("高于参考值,表示节点在交互上高度兼容,能够安全通信。")
|
||||
else:
|
||||
report.append("低于参考值,表示节点在交互上存在兼容性问题,通信安全性较低。")
|
||||
|
||||
return "\n".join(report)
|
||||
|
||||
|
||||
def save_filtered_triples(filtered_triples: List[Dict], filename: str = "filtered_triples.json"):
|
||||
"""
|
||||
保存过滤后的三元组到JSON文件
|
||||
"""
|
||||
project_root = Path(__file__).parent.parent # src -> 227
|
||||
output_path = project_root / "results" / filename
|
||||
|
||||
output_data = {"triples": filtered_triples}
|
||||
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(output_data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"过滤后的三元组已保存到 {filename}")
|
||||
|
||||
|
||||
def save_triples_and_task_probs(filtered_triples: list,
|
||||
network_results: dict,
|
||||
excel_name: str = "triples.xlsx",
|
||||
csv_name: str = "triples.csv") -> None:
|
||||
"""
|
||||
保存过滤后的三元组(csv + Excel 表1)+
|
||||
归一化任务网络概率(Excel 表2)
|
||||
"""
|
||||
# 1. 三元组 DataFrame(含 head_type / tail_type)
|
||||
triple_df = pd.DataFrame([
|
||||
{
|
||||
"head": t["head"],
|
||||
"head_type": t.get("head_type", ""),
|
||||
"relation": t["relation"],
|
||||
"tail": t["tail"],
|
||||
"tail_type": t.get("tail_type", ""),
|
||||
"strength": t["strength"]
|
||||
}
|
||||
for t in filtered_triples
|
||||
])
|
||||
|
||||
# 2. 任务网络概率 DataFrame
|
||||
task_scores = {task: score for task, score in network_results.items() if isinstance(score, float)}
|
||||
total = sum(task_scores.values()) or 1.0
|
||||
task_pct = {task: (sc / total) * 100 for task, sc in task_scores.items()}
|
||||
|
||||
task_map = {
|
||||
"defense": "1-综合防御任务网络",
|
||||
"fire": "2-火力打击任务网络",
|
||||
"logistics": "3-后勤保障任务网络",
|
||||
"medical": "4-医疗救援任务网络",
|
||||
"evacuation": "5-紧急疏散任务网络"
|
||||
}
|
||||
prob_df = pd.DataFrame([
|
||||
{"id": int(task_map[task].split('-')[0]),
|
||||
"任务": task_map[task].split('-')[1],
|
||||
"概率%": f"{pct:.1f}"}
|
||||
for task, pct in task_pct.items()
|
||||
])
|
||||
|
||||
# 3. 写文件
|
||||
project_root = Path(__file__).parent.parent # src -> 227
|
||||
output_path_csv = project_root / "results" / csv_name
|
||||
triple_df.to_csv(output_path_csv, index=False, encoding='utf-8-sig')
|
||||
|
||||
output_path = project_root / "results" / excel_name
|
||||
|
||||
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
|
||||
triple_df.to_excel(writer, sheet_name='triples', index=False)
|
||||
prob_df.to_excel(writer, sheet_name='TaskProbs', index=False)
|
||||
|
||||
print(f"已生成:{csv_name} | {excel_name}(含 TaskProbs 页)")
|
||||
|
||||
|
||||
def save_evaluation_report(report_content: str, filename: str = "evaluation_report.txt"):
|
||||
"""
|
||||
保存评价报告到TXT文件
|
||||
"""
|
||||
project_root = Path(__file__).parent.parent # src -> 227
|
||||
output_path = project_root / "results" / filename
|
||||
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(report_content)
|
||||
|
||||
print(f"评价报告已保存到 {filename}")
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
主函数:整合所有计算流程
|
||||
"""
|
||||
"""运行时交互版主函数"""
|
||||
import os
|
||||
from data_loader import load_data as load_data_source
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(script_dir)
|
||||
|
||||
config = load_config(project_root)
|
||||
source = config.get('source', 'json')
|
||||
print(f"数据源模式: {source}")
|
||||
|
||||
try:
|
||||
data, paths = load_data_source(config, project_root)
|
||||
except (FileNotFoundError, ValueError) as e:
|
||||
print(f"错误:{e}")
|
||||
exit(1)
|
||||
|
||||
os.makedirs(os.path.dirname(paths['report']), exist_ok=True)
|
||||
|
||||
cleaner = AdvancedDataCleaner(
|
||||
output_file=paths['output'],
|
||||
report_file=paths['report'],
|
||||
data=data,
|
||||
)
|
||||
cleaner.run()
|
||||
|
||||
formal_cfg = config.get("formal", {})
|
||||
if source == "db" and formal_cfg.get("enabled", False):
|
||||
print("\n=== 模块1.3:形式化入库流程 ===")
|
||||
try:
|
||||
formal_result = run_formalization_pipeline(config)
|
||||
if formal_result.get("ok"):
|
||||
print(f"形式化批次: {formal_result.get('batch_id')}")
|
||||
print(f"形式化统计: {formal_result.get('stats', {})}")
|
||||
else:
|
||||
print(f"形式化流程跳过: {formal_result.get('reason', 'unknown')}")
|
||||
except Exception as e:
|
||||
print(f"形式化流程执行失败: {e}")
|
||||
raise
|
||||
|
||||
|
||||
print("=== 网络关系与效能评估(交互填参版) ===")
|
||||
|
||||
# 1. 加载默认配置
|
||||
cfg = copy.deepcopy(DEFAULT_CFG)
|
||||
|
||||
# 交互式逐项覆盖
|
||||
def ask_one_weight(prompt: str, default: float):
|
||||
"""只问一个权重,回车=default"""
|
||||
while True:
|
||||
try:
|
||||
v = input(f"{prompt} (默认={default}):").strip()
|
||||
return default if v == "" else float(v)
|
||||
except ValueError:
|
||||
print("请输入 0-1 之间的数字!")
|
||||
|
||||
def ask_one_int(prompt: str, default: int):
|
||||
"""问整数距离"""
|
||||
while True:
|
||||
try:
|
||||
v = input(f"{prompt} (默认={default} km):").strip()
|
||||
return default if v == "" else int(v)
|
||||
except ValueError:
|
||||
print("请输入整数!")
|
||||
|
||||
def ask_one_float(prompt: str, default: float):
|
||||
"""问时间衰减系数"""
|
||||
while True:
|
||||
try:
|
||||
v = input(f"{prompt} (默认={default}):").strip()
|
||||
return default if v == "" else float(v)
|
||||
except ValueError:
|
||||
print("请输入数字!")
|
||||
|
||||
# print("\n========== ① 关系权重 (0-1) ==========")
|
||||
# rels = ["IS", "CC", "SF", "PD", "CO"]
|
||||
# rel_keys = ["w_f", "w_s", "w_t", "w_p", "w_i"]
|
||||
# rel_names = ["功能(w_f)", "空间(w_s)", "时间(w_t)", "性能(w_p)", "交互(w_i)"]
|
||||
# for rel in ["IS", "CC", "SF", "PD", "CO"]:
|
||||
# print(f"\n-- {rel} 关系 --w_f/w_s/w_t/w_p/w_i")
|
||||
# for k, name in zip(rel_keys, rel_names):
|
||||
# old = cfg["W_REL"][rel][k]
|
||||
# cfg["W_REL"][rel][k] = ask_one_weight(name, old)
|
||||
#
|
||||
# print("\n========== ② 网络权重 (0-1) ==========")
|
||||
# tasks = ["defense", "fire", "logistics", "medical", "evacuation"]
|
||||
# tfactor = ["w1", "w2", "w3", "w4", "w5"]
|
||||
# for task in tasks:
|
||||
# print(f"\n-- {task} -- w1/w2/w3/w4/w5")
|
||||
# for i, tf in enumerate(tfactor):
|
||||
# old = cfg["W_NET"][task][tf]
|
||||
# cfg["W_NET"][task][tf] = ask_one_weight(tf, old)
|
||||
#
|
||||
# print("\n========== ③ 特征距离 LAMBDA (km) ==========")
|
||||
# for rel in rels:
|
||||
# old = cfg["LAMBDA"][rel]
|
||||
# cfg["LAMBDA"][rel] = ask_one_float(f"{rel} 距离", old)
|
||||
#
|
||||
# print("\n========== ④ 时间衰减系数 ==========")
|
||||
# cfg["TIME"]["alpha"] = ask_one_float("alpha", cfg["TIME"]["alpha"])
|
||||
# cfg["TIME"]["beta"] = ask_one_float("beta", cfg["TIME"]["beta"])
|
||||
# cfg["TIME"]["gamma"] = ask_one_float("gamma", cfg["TIME"]["gamma"])
|
||||
#
|
||||
# print("\n========== ⑤ 其他 ==========")
|
||||
# cfg["PD_SGM"] = ask_one_float("平台共位阈值 PD_SGM", cfg["PD_SGM"])
|
||||
# cfg["THRESHOLD"] = ask_one_weight("关系过滤阈值", cfg["THRESHOLD"])
|
||||
#
|
||||
|
||||
print("开始网络关系计算")
|
||||
print("=" * 50)
|
||||
|
||||
|
||||
try:
|
||||
# 1. 加载数据
|
||||
print("步骤1: 加载数据...")
|
||||
nodes_data, triples_data = load_data(project_root)
|
||||
print(f"加载节点数: {len(nodes_data['nodes'])}")
|
||||
print(f"加载三元组数: {len(triples_data['triples'])}")
|
||||
|
||||
# 2. 计算并过滤关系强度
|
||||
print(f"\n步骤2: 计算关系强度并过滤(阈值={cfg['THRESHOLD']})")
|
||||
filtered_relations, filtered_triples, relation_stats, all_relations = calculate_and_filter_relations(
|
||||
nodes_data, triples_data, cfg["THRESHOLD"], cfg
|
||||
)
|
||||
|
||||
# 3. 评估任务网络效能(使用过滤后的关系)
|
||||
print("\n步骤3: 使用过滤后的关系评估任务网络效能...")
|
||||
network_results = evaluate_task_networks(nodes_data, filtered_relations, cfg)
|
||||
|
||||
# 4. 生成评价报告
|
||||
print("\n步骤4: 生成评价报告...")
|
||||
report_content = generate_evaluation_report(
|
||||
relation_stats, network_results, filtered_relations, all_relations, cfg["THRESHOLD"],cfg
|
||||
)
|
||||
|
||||
# 5. 保存结果
|
||||
print("\n步骤5: 保存结果文件...")
|
||||
save_triples_and_task_probs(filtered_triples, network_results)
|
||||
save_filtered_triples(filtered_triples)
|
||||
save_evaluation_report(report_content)
|
||||
|
||||
# 6. 在控制台显示关键信息
|
||||
print("\n" + "=" * 50)
|
||||
print("关键结果摘要:")
|
||||
print(f"- 过滤后保留的三元组: {len(filtered_triples)} 个")
|
||||
print("所有计算完成!")
|
||||
print("=" * 50)
|
||||
|
||||
except Exception as e:
|
||||
print(f"程序执行出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
113
final/src/triplet_cleaner.py
Normal file
113
final/src/triplet_cleaner.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
三元组清洗模块
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any, Set, Tuple
|
||||
|
||||
|
||||
def clean_triplets(
|
||||
triplets: List[Dict[str, Any]],
|
||||
valid_ids: Set[str] = None,
|
||||
deduplicate: bool = True,
|
||||
filter_orphans: bool = False,
|
||||
remove_null_core: bool = True,
|
||||
normalize_relation_type: bool = True,
|
||||
) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]:
|
||||
"""
|
||||
清洗三元组数据
|
||||
|
||||
:param triplets: 原始三元组列表
|
||||
:param valid_ids: 有效实体 ID 集合(用于 filter_orphans),None 表示不校验
|
||||
:param deduplicate: 是否按 (HEAD_ID, RELATION_TYPE, TAIL_ID) 去重
|
||||
:param filter_orphans: 是否过滤悬空引用(HEAD_ID/TAIL_ID 不在 valid_ids 中)
|
||||
:param remove_null_core: 是否移除 HEAD_ID/TAIL_ID/RELATION_TYPE 为空的记录
|
||||
:param normalize_relation_type: 是否规范化 RELATION_TYPE(去空格)
|
||||
:return: (清洗后列表, 清洗报告)
|
||||
"""
|
||||
report = {
|
||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"original_count": len(triplets),
|
||||
"deduplicate": {"enabled": deduplicate, "removed": 0},
|
||||
"filter_orphans": {"enabled": filter_orphans, "removed": 0},
|
||||
"remove_null_core": {"enabled": remove_null_core, "removed": 0},
|
||||
"normalize_relation_type": {"enabled": normalize_relation_type},
|
||||
"final_count": 0,
|
||||
}
|
||||
|
||||
result = list(triplets)
|
||||
|
||||
def _head(t): return _str(t.get("HEAD_ID") or t.get("head"))
|
||||
def _tail(t): return _str(t.get("TAIL_ID") or t.get("tail"))
|
||||
def _rel(t): return _str(t.get("RELATION_TYPE") or t.get("relation"))
|
||||
|
||||
if remove_null_core:
|
||||
before = len(result)
|
||||
result = [t for t in result if _not_empty(_head(t)) and _not_empty(_tail(t)) and _not_empty(_rel(t))]
|
||||
report["remove_null_core"]["removed"] = before - len(result)
|
||||
|
||||
if normalize_relation_type:
|
||||
for t in result:
|
||||
k = "RELATION_TYPE" if "RELATION_TYPE" in t else "relation"
|
||||
if isinstance(t.get(k), str):
|
||||
t[k] = t[k].strip()
|
||||
|
||||
if filter_orphans and valid_ids is not None:
|
||||
before = len(result)
|
||||
result = [t for t in result if _head(t) in valid_ids and _tail(t) in valid_ids]
|
||||
report["filter_orphans"]["removed"] = before - len(result)
|
||||
|
||||
if deduplicate:
|
||||
before = len(result)
|
||||
seen = set()
|
||||
unique = []
|
||||
for t in result:
|
||||
key = (_head(t), _rel(t), _tail(t))
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
unique.append(t)
|
||||
result = unique
|
||||
report["deduplicate"]["removed"] = before - len(result)
|
||||
|
||||
report["final_count"] = len(result)
|
||||
return result, report
|
||||
|
||||
|
||||
def _not_empty(v: Any) -> bool:
|
||||
if v is None:
|
||||
return False
|
||||
if isinstance(v, str) and not v.strip():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _str(v: Any) -> str:
|
||||
if v is None:
|
||||
return ""
|
||||
return str(v).strip()
|
||||
|
||||
|
||||
def transform_triplet_output_format(
|
||||
triplets: List[Dict[str, Any]],
|
||||
id_to_role: dict = None,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
将三元组转为输出格式:
|
||||
- HEAD_ID/head -> head, TAIL_ID/tail -> tail, RELATION_TYPE/relation -> relation
|
||||
- 新增 head_type、tail_type(从 id_to_role 查 ROLE_ID,已有则保留)
|
||||
"""
|
||||
id_to_role = id_to_role or {}
|
||||
result = []
|
||||
for t in triplets:
|
||||
head = _str(t.get("HEAD_ID") or t.get("head"))
|
||||
tail = _str(t.get("TAIL_ID") or t.get("tail"))
|
||||
rel = _str(t.get("RELATION_TYPE") or t.get("relation"))
|
||||
head_type = _str(t.get("head_type")) or id_to_role.get(head, "")
|
||||
tail_type = _str(t.get("tail_type")) or id_to_role.get(tail, "")
|
||||
result.append({
|
||||
"head": head,
|
||||
"head_type": head_type,
|
||||
"relation": rel,
|
||||
"tail": tail,
|
||||
"tail_type": tail_type,
|
||||
})
|
||||
return result
|
||||
Reference in New Issue
Block a user