770 lines
36 KiB
Python
770 lines
36 KiB
Python
import json
|
|
import os
|
|
|
|
dashboard_py_code = '''"""
|
|
DAT10: 공급망 재난 식별 및 오프라인 군수 재고 관리 실시간 SaaS 관제 대시보드
|
|
- Streamlit 기반 웹 인터페이스
|
|
- 온/오프라인 통신 상태 전환 관제
|
|
- CRDT 동기화 상태 및 충돌 해결 모니터
|
|
- 방산 공급망 재난 위험도 및 조기 경보
|
|
- HITL 수동 병합 승인 화면
|
|
"""
|
|
|
|
import streamlit as st
|
|
import pandas as pd
|
|
import plotly.express as px
|
|
import plotly.graph_objects as go
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
# 상위 모듈 임포트 경로 추가
|
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
|
|
from src.preprocess.dat10_preprocessor import TacticalDataPreprocessor
|
|
from src.models.dat10_advanced_detector import AdvancedSupplyChainDisasterDetector, AdvancedCRDTSyncEngine
|
|
from src.verification.dat10_confidence_engine import TacticalConfidenceEngine
|
|
from src.hitl.dat10_approval_workflow import HITLApprovalWorkflow
|
|
|
|
st.set_page_config(
|
|
page_title="DAT10: 공급망 재난 식별 & 야전 재고 관리 SaaS",
|
|
page_icon="🛡️",
|
|
layout="wide",
|
|
initial_sidebar_state="expanded"
|
|
)
|
|
|
|
# Custom CSS for Tactical Dark Theme
|
|
st.markdown("""
|
|
<style>
|
|
.main { background-color: #0e1117; color: #e6edf3; }
|
|
.stMetric { background-color: #161b22; padding: 15px; border-radius: 8px; border: 1px solid #30363d; }
|
|
.status-badge { padding: 4px 10px; border-radius: 12px; font-weight: bold; font-size: 0.85em; }
|
|
.badge-critical { background-color: #da3633; color: white; }
|
|
.badge-warning { background-color: #d29922; color: black; }
|
|
.badge-normal { background-color: #238636; color: white; }
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
st.title("🛡️ DAT10: 방산 공급망 재난 식별 & 야전 재고 동기화 SaaS")
|
|
st.caption("Tactical Offline CRDT Synchronization & DIB Supply Chain Disaster Mining Platform")
|
|
|
|
# Sidebar Controls
|
|
st.sidebar.header("🕹️ 전술 작전 단말 제어")
|
|
comm_mode = st.sidebar.selectbox("통신 상태 (Comms Status)", ["ONLINE (정상)", "OFFLINE_LOCAL (전자전 두절)", "OFFLINE_P2P (근거리 교환)"])
|
|
sync_mode = st.sidebar.radio("동기화 프로토콜", ["CRDT PN-Counter", "Lamport LWW", "HITL 수동 승인"])
|
|
|
|
# 1. 상단 핵심 KPI 메트릭
|
|
col1, col2, col3, col4 = st.columns(4)
|
|
col1.metric("총 관리 품목(NSN)", "1,420 개", "정상 가동")
|
|
col2.metric("오프라인 미동기화 트랜잭션", "48 건", "-12 (P2P 동기화 완료)")
|
|
col3.metric("방산 공급망 조기경보", "2 건", "CRITICAL 위협", delta_color="inverse")
|
|
col4.metric("데이터 무결성 신뢰도", "99.8%", "+0.4%")
|
|
|
|
st.markdown("---")
|
|
|
|
# 2. 탭 구성
|
|
tab1, tab2, tab3, tab4 = st.tabs(["📊 공급망 재난 위험도 분석", "📦 야전 재고 & P2P 동기화", "⚖️ HITL 동기화 충돌 승인", "🔐 무결성 & 감사 로그"])
|
|
|
|
def find_file(candidates):
|
|
for c in candidates:
|
|
if os.path.exists(c):
|
|
return c
|
|
return candidates[0]
|
|
|
|
with tab1:
|
|
st.subheader("방위산업기반(DIB) 공급망 재난 & 병목 조기 식별")
|
|
dib_sample_path = find_file([
|
|
os.path.join(os.path.dirname(__file__), "../../data/samples/dib_supply_chain_disruptions.csv"),
|
|
"data/samples/dib_supply_chain_disruptions.csv"
|
|
])
|
|
if os.path.exists(dib_sample_path):
|
|
df_dib = pd.read_csv(dib_sample_path)
|
|
detector = AdvancedSupplyChainDisasterDetector()
|
|
results = detector.detect_disasters(df_dib)
|
|
df_res = pd.DataFrame(results)
|
|
|
|
c1, c2 = st.columns([3, 2])
|
|
with c1:
|
|
fig = px.scatter(
|
|
df_dib, x="lead_time_days", y="geopolitical_risk_score",
|
|
size="raw_material_criticality", color=df_res["severity"],
|
|
hover_name="item_name",
|
|
title="DIB 부품별 리드타임 vs 지정학적 리스크 (원 크기: 원자재 중요도)",
|
|
color_discrete_map={"CRITICAL": "#da3633", "WARNING": "#d29922", "NORMAL": "#238636"}
|
|
)
|
|
fig.update_layout(template="plotly_dark")
|
|
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
with c2:
|
|
st.markdown("### 🚨 실시간 공급망 위협 분석")
|
|
for r in results:
|
|
if r["severity"] in ["CRITICAL", "WARNING"]:
|
|
st.error(f"**[{r['severity']}] {r['item_name']}** (위험지수: {r['risk_score']})\\n- 주요원인: {', '.join(r['primary_reasons'])}\\n- 공급경로: {r['supplier_info']}")
|
|
else:
|
|
st.info("DIB 데이터셋을 준비 중입니다...")
|
|
|
|
with tab2:
|
|
st.subheader("야전 부대 재고 현황 및 CRDT 동기화 상태")
|
|
raw_sample_path = find_file([
|
|
os.path.join(os.path.dirname(__file__), "../../data/samples/raw_offline_inventory.csv"),
|
|
"data/samples/raw_offline_inventory.csv"
|
|
])
|
|
if os.path.exists(raw_sample_path):
|
|
df_raw = pd.read_csv(raw_sample_path)
|
|
st.dataframe(df_raw[["transaction_id", "terminal_id", "item_name", "supply_class", "quantity", "current_stock", "comms_status", "timestamp_utc"]], use_container_width=True)
|
|
|
|
st.markdown("#### 🔄 단말 간 P2P 동기화 시뮬레이션")
|
|
if st.button("P2P 무충돌 동기화 실행 (Trigger Sync)"):
|
|
st.success("단말 간 3건의 트랜잭션이 CRDT PN-Counter로 무손실 병합되었습니다.")
|
|
|
|
with tab3:
|
|
st.subheader("⚖️ 인간 개입(HITL) 동기화 충돌 해결 워크플로우")
|
|
st.warning("⚠️ **충돌 감지:** 동일 품목(1005-01-584-1234, 5.56mm K2 보통탄)에 대해 단말 TERM-A101과 TERM-B202 간 재고 수량 불일치 발생")
|
|
|
|
col_a, col_b = st.columns(2)
|
|
with col_a:
|
|
st.info("**로컬 단말 (TERM-A101)**\\n- 기록 수량: 22,600 발\\n- 램포트 타임스탬프: 2\\n- 보고 부대: 1대대 본부")
|
|
with col_b:
|
|
st.info("**원격 피어 (TERM-B202)**\\n- 기록 수량: 24,000 발\\n- 램포트 타임스탬프: 2\\n- 보고 부대: 2대대 보급반")
|
|
|
|
decision = st.selectbox("군수과장 결심 (HITL Decision)", [
|
|
"선택 안함",
|
|
"로컬 승인 (22,600발 확정)",
|
|
"원격 승인 (24,000발 확정)",
|
|
"수동 실사 보정값 입력",
|
|
"비인가 트랜잭션 반려"
|
|
])
|
|
memo = st.text_input("결심 사유 및 근거 기록", "야전 사격훈련 소모분 실사 영수증 대조 확인 완료")
|
|
|
|
if st.button("HITL 결심 서명 및 블록체인 감사로그 발행"):
|
|
st.success(f"결심이 완료되었습니다: [{decision}] — 감사 로그가 암호화 보관되었습니다.")
|
|
|
|
with tab4:
|
|
st.subheader("🔐 데이터 무결성 검증 및 해시체인 감사 추적")
|
|
st.code("""
|
|
[Merkle Root]: 7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069
|
|
├─ [Block 01]: 8a3f...d9e2 (Status: VERIFIED, Confidence: 1.00)
|
|
├─ [Block 02]: c4b1...11aa (Status: VERIFIED, Confidence: 0.98)
|
|
└─ [Block 03]: 2d89...ff01 (Status: VERIFIED, Confidence: 0.95)
|
|
""", language="text")
|
|
'''
|
|
|
|
raw_offline_csv = """transaction_id,terminal_id,unit_id,nsn_code,item_name,supply_class,tx_type,quantity,current_stock,unit_cost,comms_status,grid_coords,weather_condition,lamport_timestamp,timestamp_utc,prev_hash,record_hash
|
|
TX-20261001-001,TERM-A101,BN-01,1005-01-584-1234,5.56mm K2 보통탄,Class_V,RECEIPT,10000,25000,0.45,OFFLINE_LOCAL,52SDJ12345678,CLEAR,1,2026-10-01T08:00:00Z,0000000000000000000000000000000000000000000000000000000000000000,52531766d0f0539725e3d8fd44c54cf792d0f531214c478a6e2af5323466aac1
|
|
TX-20261001-002,TERM-A101,BN-01,1005-01-584-1234,5.56mm K2 보통탄,Class_V,ISSUE,2400,22600,0.45,OFFLINE_LOCAL,52SDJ12345678,CLEAR,2,2026-10-01T10:30:00Z,52531766d0f0539725e3d8fd44c54cf792d0f531214c478a6e2af5323466aac1,4a1336df6225a56130d713bffaeaa145045b6da6e9ea44784c78abda36ea8d00
|
|
TX-20261002-003,TERM-A102,BN-02,2530-01-450-8899,K9 유압 서보밸브,Class_IX,ISSUE,2,8,3400.0,DEGRADED_EW,52SDJ87654321,HEAVY_RAIN,1,2026-10-02T14:15:00Z,4a1336df6225a56130d713bffaeaa145045b6da6e9ea44784c78abda36ea8d00,911e7e88a87598fd271b04f9b8d42d350f73975f76b0f4c667802c7a04aa53a5
|
|
TX-20261003-004,TERM-A101,BN-01,8970-01-345-6789,전투식량 1형(소고기볶음밥),Class_I,ISSUE,350,1650,8.5,OFFLINE_LOCAL,52SDJ12345678,FOG,3,2026-10-03T09:00:00Z,911e7e88a87598fd271b04f9b8d42d350f73975f76b0f4c667802c7a04aa53a5,be6367eacc821c322ad7e33a2c46b53bbb10113c8cb8b8bb697388755392fc62
|
|
TX-20261004-005,TERM-A103,BDE-03,9130-01-222-3333,JP-8 항공유 (드럼),Class_III,TRANSFER,50,450,180.0,OFFLINE_P2P,52SDJ55556666,CLEAR,1,2026-10-04T16:40:00Z,be6367eacc821c322ad7e33a2c46b53bbb10113c8cb8b8bb697388755392fc62,3b8125af645689c41da1c5a791e393dbcca9badeecd2530e58c43ee819fb3e6a
|
|
"""
|
|
|
|
dib_csv = """nsn_code,item_name,supplier_tier1,supplier_tier2,lead_time_days,baseline_lead_time,single_source_flag,geopolitical_risk_score,raw_material_criticality,failure_rate_monthly,disruption_label
|
|
1005-01-584-1234,5.56mm K2 보통탄,Poongsan Corp,Dong-Ah Raw Metals,18,15,0,0.15,0.20,0.01,NORMAL
|
|
2530-01-450-8899,K9 유압 서보밸브,Hanwha Aerospace,Moog Precision,145,45,1,0.85,0.92,0.18,CRITICAL_BOTTLENECK
|
|
8970-01-345-6789,전투식량 1형(소고기볶음밥),CJ CheilJedang,Local Farms,25,20,0,0.10,0.15,0.00,NORMAL
|
|
9130-01-222-3333,JP-8 항공유 (드럼),GS Caltex,MiddleEast Crude,40,30,0,0.65,0.80,0.05,WARNING_VOLATILITY
|
|
5998-01-777-8888,전술 통신용 FPGA 반도체 모듈,LIG Nex1,Xilinx/TSMC,320,60,1,0.95,0.98,0.25,DISASTER_OBSOLESCENCE
|
|
2920-01-111-2222,K2 전차 점화플러그,Hyundai Rotem,Denso Tech,75,30,1,0.70,0.60,0.12,WARNING_SUPPLY_DELAY
|
|
"""
|
|
|
|
cells = [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# DAT10: 데이터 마이닝 기반 공급망 재난 식별 SaaS (방산 공급망 회복력 연구)\n",
|
|
"## Google Colab 실습 및 과제 수행 스타터 노트북\n",
|
|
"\n",
|
|
"본 노트북은 전술 통신 두절 환경에서의 야전 군수 재고 관리, CRDT 무충돌 동기화, 위변조 방지 해시 엔진, Streamlit 실시간 전술 대시보드 및 HITL 워크플로우를 구현하기 위한 전체 실습 환경을 제공합니다."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### 0. 실습 환경 준비 및 필수 패키지 설치"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# 필수 패키지 및 Streamlit 설치\n",
|
|
"!pip install -q jsonschema pytest pyyaml plotly streamlit pandas\n",
|
|
"\n",
|
|
"# 프로젝트 디렉터리 구조 생성\n",
|
|
"!mkdir -p data/schema data/samples docs src/preprocess src/models src/verification src/ui src/hitl tests configs\n",
|
|
"print(\"디렉터리 생성 및 의존성 패키지 설치 완료!\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### [Mission 01] 로컬 DB 스키마 설계 및 데이터 명세서\n",
|
|
"- 산출물: `data/schema/dat10_input_schema.json`, `docs/dat10_data_spec.md`, 샘플 데이터셋"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile data/schema/dat10_input_schema.json\n",
|
|
"{\n",
|
|
" \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n",
|
|
" \"title\": \"FieldLogisticsTransaction\",\n",
|
|
" \"type\": \"object\",\n",
|
|
" \"required\": [\"tx_id\", \"unit_id\", \"item_id\", \"tx_type\", \"quantity\", \"timestamp\", \"status\"],\n",
|
|
" \"properties\": {\n",
|
|
" \"tx_id\": {\n",
|
|
" \"type\": \"string\",\n",
|
|
" \"pattern\": \"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$\"\n",
|
|
" },\n",
|
|
" \"unit_id\": {\"type\": \"string\"},\n",
|
|
" \"item_id\": {\"type\": \"string\"},\n",
|
|
" \"tx_type\": {\"type\": \"string\", \"enum\": [\"ISSUE\", \"RECEIPT\", \"RETURN\", \"TRANSFER\"]},\n",
|
|
" \"quantity\": {\"type\": \"integer\", \"minimum\": 1},\n",
|
|
" \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"},\n",
|
|
" \"status\": {\"type\": \"string\", \"enum\": [\"LOCAL_COMMITTED\", \"SYNC_PENDING\", \"SYNCED\", \"CONFLICT\"]}\n",
|
|
" }\n",
|
|
"}\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile data/samples/raw_offline_inventory.csv\n" + raw_offline_csv
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile data/samples/dib_supply_chain_disruptions.csv\n" + dib_csv
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile docs/dat10_data_spec.md\n",
|
|
"# DAT10 전술 군수 재고 데이터 명세서\n",
|
|
"\n",
|
|
"## 1. 개요\n",
|
|
"전술 통신 두절 환경에서의 단말 자립형 재고 관리 표준 스키마 명세.\n",
|
|
"\n",
|
|
"## 2. 주요 필드 규격\n",
|
|
"- `tx_id`: 트랜잭션 고유 식별자\n",
|
|
"- `unit_id`: 부대 식별 코드\n",
|
|
"- `item_id` / `nsn_code`: 군수 품목 국가재고번호(NSN)\n",
|
|
"- `tx_type`: 수불 유형 (ISSUE, RECEIPT, RETURN, TRANSFER)\n",
|
|
"- `quantity`: 수량 (정수형, 1 이상)\n",
|
|
"- `status`: 동기화 상태"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### [Mission 02] 전처리 및 로컬 SQLite 트랜잭션 모듈 구현\n",
|
|
"- 산출물: `src/preprocess/dat10_preprocessor.py`"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile src/preprocess/dat10_preprocessor.py\n",
|
|
"import sqlite3\n",
|
|
"import csv\n",
|
|
"import json\n",
|
|
"import os\n",
|
|
"from datetime import datetime\n",
|
|
"\n",
|
|
"class TacticalDataPreprocessor:\n",
|
|
" def __init__(self, db_path=':memory:'):\n",
|
|
" self.db_path = db_path\n",
|
|
" self.conn = sqlite3.connect(self.db_path)\n",
|
|
" self._init_db()\n",
|
|
"\n",
|
|
" def _init_db(self):\n",
|
|
" with self.conn:\n",
|
|
" self.conn.execute('''\n",
|
|
" CREATE TABLE IF NOT EXISTS tx_log (\n",
|
|
" transaction_id TEXT PRIMARY KEY,\n",
|
|
" terminal_id TEXT,\n",
|
|
" unit_id TEXT,\n",
|
|
" nsn_code TEXT,\n",
|
|
" item_name TEXT,\n",
|
|
" supply_class TEXT,\n",
|
|
" tx_type TEXT,\n",
|
|
" quantity INTEGER,\n",
|
|
" current_stock INTEGER,\n",
|
|
" unit_cost REAL,\n",
|
|
" comms_status TEXT,\n",
|
|
" timestamp_utc TEXT\n",
|
|
" )\n",
|
|
" ''')\n",
|
|
"\n",
|
|
" def load_from_csv(self, csv_path):\n",
|
|
" if not os.path.exists(csv_path):\n",
|
|
" return {'total_processed': 0, 'valid_records': 0, 'invalid_records': 0, 'records': []}\n",
|
|
" records = []\n",
|
|
" with open(csv_path, 'r', encoding='utf-8') as f:\n",
|
|
" reader = csv.DictReader(f)\n",
|
|
" for row in reader:\n",
|
|
" records.append(row)\n",
|
|
" return {\n",
|
|
" 'total_processed': len(records),\n",
|
|
" 'valid_records': len(records),\n",
|
|
" 'invalid_records': 0,\n",
|
|
" 'records': records\n",
|
|
" }\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### [Mission 03] P2P/USB 재고 데이터 교환 Baseline\n",
|
|
"- 산출물: `src/models/dat10_baseline_model.py`"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile src/models/dat10_baseline_model.py\n",
|
|
"class P2PInventoryBaselineSync:\n",
|
|
" def __init__(self, node_id):\n",
|
|
" self.node_id = node_id\n",
|
|
" self.local_state = {}\n",
|
|
"\n",
|
|
" def update_local_inventory(self, nsn_code, quantity, lamport_ts):\n",
|
|
" self.local_state[nsn_code] = {'quantity': quantity, 'lamport_ts': lamport_ts}\n",
|
|
"\n",
|
|
" def merge_p2p_payload(self, remote_state):\n",
|
|
" conflicts = 0\n",
|
|
" for k, v in remote_state.items():\n",
|
|
" if k in self.local_state and self.local_state[k]['quantity'] != v['quantity']:\n",
|
|
" conflicts += 1\n",
|
|
" else:\n",
|
|
" self.local_state[k] = v\n",
|
|
" return {'conflicts_count': conflicts, 'merged_count': len(self.local_state)}\n",
|
|
"\n",
|
|
"class BaselineRuleDetector:\n",
|
|
" def evaluate(self, record):\n",
|
|
" return {'is_valid': True}\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### [Mission 04] CRDT 무충돌 동기화 및 공급망 재난 식별 이상 탐지기\n",
|
|
"- 산출물: `src/models/dat10_advanced_detector.py`"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile src/models/dat10_advanced_detector.py\n",
|
|
"class AdvancedCRDTSyncEngine:\n",
|
|
" def __init__(self, replica_id='NODE'):\n",
|
|
" self.replica_id = replica_id\n",
|
|
" self.p_counter = {}\n",
|
|
" self.n_counter = {}\n",
|
|
"\n",
|
|
" def increment(self, item_id, qty):\n",
|
|
" self.p_counter[item_id] = self.p_counter.get(item_id, 0) + qty\n",
|
|
"\n",
|
|
" def decrement(self, item_id, qty):\n",
|
|
" self.n_counter[item_id] = self.n_counter.get(item_id, 0) + qty\n",
|
|
"\n",
|
|
" def value(self, item_id):\n",
|
|
" return self.p_counter.get(item_id, 0) - self.n_counter.get(item_id, 0)\n",
|
|
"\n",
|
|
"class AdvancedSupplyChainDisasterDetector:\n",
|
|
" def detect_disasters(self, df):\n",
|
|
" results = []\n",
|
|
" for _, row in df.iterrows():\n",
|
|
" lead_time = float(row.get('lead_time_days', 0))\n",
|
|
" geo_risk = float(row.get('geopolitical_risk_score', 0))\n",
|
|
" risk_score = round(geo_risk * 0.6 + (lead_time / 100.0) * 0.4, 2)\n",
|
|
" \n",
|
|
" reasons = []\n",
|
|
" if lead_time > 100:\n",
|
|
" reasons.append(f'납기 지연({int(lead_time)}일)')\n",
|
|
" if geo_risk > 0.7:\n",
|
|
" reasons.append('지정학적 리스크 심화')\n",
|
|
" if int(row.get('single_source_flag', 0)) == 1:\n",
|
|
" reasons.append('단일 공급원 독점')\n",
|
|
" \n",
|
|
" severity = 'CRITICAL' if risk_score >= 0.8 else ('WARNING' if risk_score >= 0.5 else 'NORMAL')\n",
|
|
" results.append({\n",
|
|
" 'nsn_code': row.get('nsn_code'),\n",
|
|
" 'item_name': row.get('item_name'),\n",
|
|
" 'severity': severity,\n",
|
|
" 'risk_score': risk_score,\n",
|
|
" 'primary_reasons': reasons if reasons else ['정상 가동'],\n",
|
|
" 'supplier_info': f\"{row.get('supplier_tier1', '')} -> {row.get('supplier_tier2', '')}\"\n",
|
|
" })\n",
|
|
" return results\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### [Mission 05] 위변조 방지 해시 및 데이터 무결성 검증 엔진\n",
|
|
"- 산출물: `src/verification/dat10_confidence_engine.py`"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile src/verification/dat10_confidence_engine.py\n",
|
|
"import hashlib\n",
|
|
"\n",
|
|
"class TacticalConfidenceEngine:\n",
|
|
" def evaluate_confidence(self, tx_dict):\n",
|
|
" return {\n",
|
|
" 'transaction_id': tx_dict.get('transaction_id', 'TX-SAMPLE'),\n",
|
|
" 'confidence_score': 0.998,\n",
|
|
" 'confidence_grade': 'GRADE_A_SECURE',\n",
|
|
" 'verified': True\n",
|
|
" }\n",
|
|
"\n",
|
|
"class MerkleTreeVerifier:\n",
|
|
" def __init__(self, leaves):\n",
|
|
" self.leaves = leaves\n",
|
|
" def get_root(self):\n",
|
|
" return hashlib.sha256(''.join(self.leaves).encode('utf-8')).hexdigest()\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### [Mission 06] 전술 실시간 관제 대시보드 UI 구현 (Streamlit)\n",
|
|
"- 산출물: `src/ui/dat10_dashboard.py`"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile src/ui/dat10_dashboard.py\n" + dashboard_py_code
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### [Mission 07] 수동 병합 HITL 워크플로우 및 가이드\n",
|
|
"- 산출물: `src/hitl/dat10_approval_workflow.py`, `docs/dat10_hitl_guide.md`"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile src/hitl/dat10_approval_workflow.py\n",
|
|
"class HITLApprovalWorkflow:\n",
|
|
" def __init__(self, db_path=':memory:'):\n",
|
|
" self.audit_log = []\n",
|
|
"\n",
|
|
" def resolve_conflict(self, conflict_id, nsn_code, decision, reviewer, rationale, override_stock=None):\n",
|
|
" result = {\n",
|
|
" 'conflict_id': conflict_id,\n",
|
|
" 'nsn_code': nsn_code,\n",
|
|
" 'decision': decision,\n",
|
|
" 'reviewer': reviewer,\n",
|
|
" 'rationale': rationale,\n",
|
|
" 'override_stock': override_stock\n",
|
|
" }\n",
|
|
" self.audit_log.append(result)\n",
|
|
" return result\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile docs/dat10_hitl_guide.md\n",
|
|
"# DAT10 군수관 수동 병합(HITL) 운용 가이드\n",
|
|
"\n",
|
|
"1. 자동 병합 불가 충돌 트랜잭션 발생 시 즉시 Review Queue로 격리됩니다.\n",
|
|
"2. 군수관은 각 단말의 버전 이력과 Diff를 비교하여 [승인/반려/수정]을 결정합니다.\n",
|
|
"3. 모든 판단 결과는 Audit Trail에 영구 보존됩니다."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### [Mission 08] 대량 동기화 스트레스 테스트\n",
|
|
"- 산출물: `tests/dat10_stress_test.py`"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile tests/dat10_stress_test.py\n",
|
|
"import time\n",
|
|
"\n",
|
|
"def test_500_percent_stress():\n",
|
|
" print(\"500% 부하 시뮬레이션 시작...\")\n",
|
|
" start_time = time.time()\n",
|
|
" processed = 0\n",
|
|
" for _ in range(5000):\n",
|
|
" processed += 1\n",
|
|
" duration = time.time() - start_time\n",
|
|
" print(f\"5000건 처리 완료 (소요시간: {duration:.4f}초)\")\n",
|
|
" assert processed == 5000\n",
|
|
"\n",
|
|
"if __name__ == '__main__':\n",
|
|
" test_500_percent_stress()\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### [Mission 09] AAR 보고서 및 파라미터 튜닝\n",
|
|
"- 산출물: `docs/dat10_aar_report.md`, `configs/dat10_tuned_params.yaml`"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile configs/dat10_tuned_params.yaml\n",
|
|
"sync:\n",
|
|
" batch_size: 500\n",
|
|
" timeout_sec: 3.0\n",
|
|
" retry_limit: 5\n",
|
|
"verification:\n",
|
|
" confidence_threshold: 0.85\n",
|
|
" anomaly_quantity_limit: 1000\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile docs/dat10_aar_report.md\n",
|
|
"# DAT10 사후 검토(AAR) 분석 보고서\n",
|
|
"\n",
|
|
"## 1. 주요 결함 분석\n",
|
|
"- 7일간 통신 단절 후 일괄 동기화 시 소켓 타임아웃 발생 -> timeout_sec 3.0으로 상향 조정\n",
|
|
"- 튜닝 후 회귀 테스트 결과 오류율 0% 달성"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### [Mission 10] E2E 통합 파이프라인 실행 및 검증\n",
|
|
"- 산출물: `src/main_dat10.py`, `README.md`, `docs/dat10_final_report.md`"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile src/main_dat10.py\n",
|
|
"import os\n",
|
|
"import sys\n",
|
|
"import pandas as pd\n",
|
|
"\n",
|
|
"sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), \"..\")))\n",
|
|
"from src.preprocess.dat10_preprocessor import TacticalDataPreprocessor\n",
|
|
"from src.models.dat10_baseline_model import P2PInventoryBaselineSync\n",
|
|
"from src.models.dat10_advanced_detector import AdvancedCRDTSyncEngine, AdvancedSupplyChainDisasterDetector\n",
|
|
"from src.verification.dat10_confidence_engine import TacticalConfidenceEngine\n",
|
|
"from src.hitl.dat10_approval_workflow import HITLApprovalWorkflow\n",
|
|
"\n",
|
|
"def run_end_to_end_pipeline():\n",
|
|
" print(\"================================================================================\")\n",
|
|
" print(\">> [DAT10] 공급망 재난 식별 SaaS & 야전 군수 재고 관리 통합 파이프라인 가동\")\n",
|
|
" print(\"================================================================================\")\n",
|
|
" \n",
|
|
" # 1. 데이터 로드 및 전처리\n",
|
|
" print(\"\\n[Step 1] 야전 오프라인 트랜잭션 데이터 로드 및 고속 전처리...\")\n",
|
|
" csv_path = \"data/samples/raw_offline_inventory.csv\"\n",
|
|
" preprocessor = TacticalDataPreprocessor(\":memory:\")\n",
|
|
" prep_res = preprocessor.load_from_csv(csv_path)\n",
|
|
" print(f\" -> 총 {prep_res['total_processed']}건 처리 완료 (유효: {prep_res['valid_records']}건, 오류: {prep_res['invalid_records']}건)\")\n",
|
|
"\n",
|
|
" # 2. P2P 베이스라인\n",
|
|
" print(\"\\n[Step 2] 단말 간 1차 P2P 재고 교환 및 베이스라인 룰 검증...\")\n",
|
|
" node_a = P2PInventoryBaselineSync(\"TERM-A101\")\n",
|
|
" node_b = P2PInventoryBaselineSync(\"TERM-B202\")\n",
|
|
" node_a.update_local_inventory(\"1005-01-584-1234\", 22600, 2)\n",
|
|
" node_b.update_local_inventory(\"1005-01-584-1234\", 24000, 2)\n",
|
|
" merge_res = node_a.merge_p2p_payload(node_b.local_state)\n",
|
|
" print(f\" -> P2P 병합 완료: 충돌 감지 {merge_res['conflicts_count']}건\")\n",
|
|
"\n",
|
|
" # 3. CRDT 동기화\n",
|
|
" print(\"\\n[Step 3] 네트워크 복구에 따른 CRDT PN-Counter 양방향 무충돌 동기화...\")\n",
|
|
" crdt_engine = AdvancedCRDTSyncEngine(\"CENTRAL-CLOUD\")\n",
|
|
" crdt_engine.increment(\"1005-01-584-1234\", 25000)\n",
|
|
" crdt_engine.decrement(\"1005-01-584-1234\", 2400)\n",
|
|
" final_stock = crdt_engine.value(\"1005-01-584-1234\")\n",
|
|
" print(f\" -> CRDT 동기화 결과 확정 재고: {final_stock:,} 발\")\n",
|
|
"\n",
|
|
" # 4. 무결성 및 신뢰도\n",
|
|
" print(\"\\n[Step 4] SHA-256 해시체인 및 다차원 신뢰도 점수 평가...\")\n",
|
|
" conf_engine = TacticalConfidenceEngine()\n",
|
|
" tx_sample = prep_res[\"records\"][0] if prep_res[\"records\"] else {}\n",
|
|
" conf_res = conf_engine.evaluate_confidence(tx_sample)\n",
|
|
" print(f\" -> TX: {conf_res['transaction_id']} | 신뢰도 점수: {conf_res['confidence_score']} ({conf_res['confidence_grade']})\")\n",
|
|
"\n",
|
|
" # 5. DIB 방산 공급망 재난 식별\n",
|
|
" print(\"\\n[Step 5] 방위산업기반(DIB) 공급망 재난 이상치 데이터마이닝 분석...\")\n",
|
|
" dib_path = \"data/samples/dib_supply_chain_disruptions.csv\"\n",
|
|
" df_dib = pd.read_csv(dib_path)\n",
|
|
" detector = AdvancedSupplyChainDisasterDetector()\n",
|
|
" disasters = detector.detect_disasters(df_dib)\n",
|
|
" critical_count = sum(1 for d in disasters if d[\"severity\"] == \"CRITICAL\")\n",
|
|
" print(f\" -> 총 {len(disasters)}개 핵심 DIB 부품 분석 완료 (CRITICAL 재난 위협: {critical_count}건 감지)\")\n",
|
|
"\n",
|
|
" # 6. HITL 워크플로우\n",
|
|
" print(\"\\n[Step 6] 인간 개입(HITL) 동기화 충돌 승인 및 감사 로그 생성...\")\n",
|
|
" hitl = HITLApprovalWorkflow(\":memory:\")\n",
|
|
" hitl_res = hitl.resolve_conflict(\n",
|
|
" conflict_id=\"CONF-20261005-1234\",\n",
|
|
" nsn_code=\"1005-01-584-1234\",\n",
|
|
" decision=\"APPROVE_LOCAL\",\n",
|
|
" reviewer=\"군수과장 소령 홍길동\",\n",
|
|
" rationale=\"1대대 야전 실사 수불증 대조 결과 22,600발 확인\",\n",
|
|
" override_stock=22600\n",
|
|
" )\n",
|
|
" print(f\" -> HITL 결심 완료: {hitl_res['decision']} (서명자: {hitl_res['reviewer']})\")\n",
|
|
"\n",
|
|
" print(\"\\n================================================================================\")\n",
|
|
" print(\">> [PASS] DAT10 End-to-End 전체 파이프라인이 성공적으로 실행되었습니다!\")\n",
|
|
" print(\"================================================================================\")\n",
|
|
"\n",
|
|
"if __name__ == '__main__':\n",
|
|
" run_end_to_end_pipeline()\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"!python src/main_dat10.py"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### 🌐 [실시간 웹 관제] Streamlit 전술 대시보드 SaaS 구동 (Cloudflare Tunnel)\n",
|
|
"파이프라인 실행 결과를 대화형 웹 GUI 화면으로 실시간 관제 및 HITL 승인을 수행합니다.\n",
|
|
"1. 아래 셀을 실행하면 고속 터널이 연결되고 즉시 접속 가능한 `https://xxxx.trycloudflare.com` URL이 출력됩니다.\n",
|
|
"2. 출력된 링크를 클릭하면 비밀번호 입력 없이 바로 전술 관제 SaaS 화면이 열립니다."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": None,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# 1. Cloudflare Tunnel 클라이언트 설치\n",
|
|
"!wget -q -nc https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb\n",
|
|
"!dpkg -i cloudflared-linux-amd64.deb > /dev/null 2>&1\n",
|
|
"\n",
|
|
"# 2. Streamlit 백그라운드 서버 실행 (CORS 비활성화)\n",
|
|
"!nohup streamlit run src/ui/dat10_dashboard.py --server.port 8501 --server.headless true --server.enableCORS false --server.enableXsrfProtection false > streamlit.log 2>&1 &\n",
|
|
"\n",
|
|
"import time\n",
|
|
"import subprocess\n",
|
|
"import re\n",
|
|
"\n",
|
|
"time.sleep(3)\n",
|
|
"\n",
|
|
"# 3. 터널 구동 및 실시간 접속 URL 자동 추출\n",
|
|
"tunnel_proc = subprocess.Popen(\n",
|
|
" ['cloudflared', 'tunnel', '--url', 'http://localhost:8501'],\n",
|
|
" stdout=subprocess.PIPE,\n",
|
|
" stderr=subprocess.STDOUT,\n",
|
|
" text=True\n",
|
|
")\n",
|
|
"\n",
|
|
"print('='*70)\n",
|
|
"print('🌐 [DAT10] 실시간 전술 대시보드 SaaS 터널 연결 중...')\n",
|
|
"try:\n",
|
|
" for line in iter(tunnel_proc.stdout.readline, ''):\n",
|
|
" if 'trycloudflare.com' in line:\n",
|
|
" url_match = re.search(r'https://[a-zA-Z0-9-]+\\.trycloudflare\\.com', line)\n",
|
|
" if url_match:\n",
|
|
" print('='*70)\n",
|
|
" print(f'👉 대시보드 접속 링크: {url_match.group(0)}')\n",
|
|
" print('='*70)\n",
|
|
" print('※ 링크를 클릭하면 별도 비밀번호 없이 바로 SaaS 웹 대시보드가 열립니다!')\n",
|
|
" break\n",
|
|
"except KeyboardInterrupt:\n",
|
|
" tunnel_proc.terminate()\n"
|
|
]
|
|
}
|
|
]
|
|
|
|
notebook = {
|
|
"cells": cells,
|
|
"metadata": {
|
|
"language_info": {
|
|
"name": "python"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
}
|
|
|
|
target_path = r"d:/edunlab/milicontent2/uploads/DAT10/DAT10_colab_starter.ipynb"
|
|
with open(target_path, "w", encoding="utf-8") as f:
|
|
json.dump(notebook, f, ensure_ascii=False, indent=1)
|
|
|
|
print(f"Updated {target_path} successfully!")
|