첫 커밋
This commit is contained in:
commit
c2fe648904
30 files changed
+3154
No files matched your search
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
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
|
||||
Reference in new issue
Block a user