新增修改

This commit is contained in:
2026-04-23 12:48:59 +08:00
parent df46812eff
commit 92b856b622
60 changed files with 18623 additions and 398 deletions

0
final/src/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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_vectorf_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
View 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.yamljson 优先,便于部署时只改 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
View 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
View 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()

View 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_orphansNone 表示不校验
: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