Files
DAT10/src/models/dat10_baseline_model.py
T
2026-10-06 13:37:13 +09:00

80 lines
3.4 KiB
Python

"""
DAT10: 오프라인 단말 간 1차 P2P/USB 재고 데이터 교환 및 베이스라인 탐지 모델
- P2P 재고 상태 교환 및 단순 LWW(Last-Write-Wins) 동기화
- 룰 기반 베이스라인 재고 급감/안전재고 미달 탐지
"""
from typing import Dict, List, Any
import datetime
class P2PInventoryBaselineSync:
def __init__(self, node_id: str):
self.node_id = node_id
self.local_state: Dict[str, Dict[str, Any]] = {}
# nsn_code -> {"stock": int, "lamport": int, "updated_at": str, "source_node": str}
def update_local_inventory(self, nsn_code: str, stock: int, lamport: int) -> Dict[str, Any]:
self.local_state[nsn_code] = {
"stock": stock,
"lamport": lamport,
"updated_at": datetime.datetime.utcnow().isoformat() + "Z",
"source_node": self.node_id
}
return self.local_state[nsn_code]
def merge_p2p_payload(self, incoming_state: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
"""P2P/USB를 통해 수신된 피어 노드의 상태를 LWW 규칙으로 1차 병합"""
merged_count = 0
conflicts = []
for nsn, remote_data in incoming_state.items():
if nsn not in self.local_state:
self.local_state[nsn] = remote_data
merged_count += 1
else:
local_data = self.local_state[nsn]
# Lamport Clock 비교
if remote_data["lamport"] > local_data["lamport"]:
self.local_state[nsn] = remote_data
merged_count += 1
elif remote_data["lamport"] == local_data["lamport"]:
# 타임스탬프 동률 시 노드 ID 사전식 순서
if remote_data["source_node"] > local_data["source_node"]:
self.local_state[nsn] = remote_data
merged_count += 1
elif remote_data["stock"] != local_data["stock"]:
conflicts.append({
"nsn_code": nsn,
"local_stock": local_data["stock"],
"remote_stock": remote_data["stock"],
"lamport": local_data["lamport"],
"peer_id": remote_data["source_node"]
})
return {
"merged_count": merged_count,
"conflicts_count": len(conflicts),
"conflicts": conflicts,
"current_state": self.local_state
}
class BaselineRuleDetector:
def __init__(self, safety_threshold: int = 10, drop_ratio_threshold: float = 0.5):
self.safety_threshold = safety_threshold
self.drop_ratio_threshold = drop_ratio_threshold
def evaluate_inventory_health(self, inventory_states: Dict[str, Dict[str, Any]]) -> List[Dict[str, Any]]:
alerts = []
for nsn, data in inventory_states.items():
stock = data.get("stock", 0)
if stock < self.safety_threshold:
alerts.append({
"nsn_code": nsn,
"alert_type": "SAFETY_STOCK_VIOLATION",
"severity": "WARNING",
"current_stock": stock,
"threshold": self.safety_threshold,
"reason": f"잔여 재고({stock})가 안전 재고 기준({self.safety_threshold}) 미만입니다."
})
return alerts