delete all
This commit is contained in:
1 parent
b38ceec784
commit
d9287de729
126 files changed
-51475
No files matched your search
@@ -1,24 +0,0 @@
|
||||
import json
|
||||
|
||||
with open('import/OPS01.json', 'r', encoding='utf-8') as f:
|
||||
ops01 = json.load(f)
|
||||
|
||||
print("Top-level keys in OPS01.json:", list(ops01.keys()))
|
||||
print("Project Info:")
|
||||
for k in ['version', 'exportedAt', 'id', 'title', 'category', 'level', 'durationDays', 'requiredHours', 'status', 'isRecommended']:
|
||||
print(f" {k}: {ops01.get(k)}")
|
||||
|
||||
print("\nSummary:\n", ops01.get('summary'))
|
||||
print("\nObjectives:\n", ops01.get('objectives'))
|
||||
print("\nDeliverables:\n", ops01.get('deliverables'))
|
||||
print("\nConstraints:\n", ops01.get('constraints'))
|
||||
print("\nEvalSummary:\n", ops01.get('evalSummary'))
|
||||
print("\nPrerequisites:\n", ops01.get('prerequisites'))
|
||||
print("\nProjectSkills:\n", ops01.get('projectSkills'))
|
||||
print("\nMission count:", len(ops01.get('missions', [])))
|
||||
|
||||
m0 = ops01['missions'][0]
|
||||
print("\nMission 1 keys:", list(m0.keys()))
|
||||
for k, v in m0.items():
|
||||
if k != 'guideSteps':
|
||||
print(f" {k}: {v}")
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,22 +0,0 @@
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
from generate_projects_json import parse_missions_md
|
||||
|
||||
projects = parse_missions_md('data/missions02.md')
|
||||
|
||||
categories = set()
|
||||
learning_types = set()
|
||||
difficulties = set()
|
||||
|
||||
for no, p in sorted(projects.items()):
|
||||
categories.add(f"{p['category']}/{p['sub_category']}")
|
||||
learning_types.add(p['learning_type'])
|
||||
difficulties.add(p['p_difficulty'])
|
||||
if len(p['missions']) != 10:
|
||||
print(f"WARNING: Project {no} ({p['p_id']}) has {len(p['missions'])} missions instead of 10")
|
||||
|
||||
print(f"Total projects: {len(projects)}")
|
||||
print(f"All 60 projects have 10 missions: {all(len(p['missions']) == 10 for p in projects.values())}")
|
||||
print("Learning types:", sorted(list(learning_types)))
|
||||
print("Difficulties:", sorted(list(difficulties)))
|
||||
@@ -1,278 +0,0 @@
|
||||
import sys
|
||||
import docx
|
||||
from docx.shared import Pt, RGBColor
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Open template
|
||||
doc = docx.Document('data/OPS01-templet.docx')
|
||||
|
||||
# 1. Update Title (P0)
|
||||
p0 = doc.paragraphs[0]
|
||||
p0.text = ""
|
||||
p0.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
r0 = p0.add_run("다중 센서 경보 중복 항적 통합 시뮬레이터")
|
||||
r0.font.size = Pt(15.0)
|
||||
r0.bold = True
|
||||
r0.font.color.rgb = RGBColor(0x1A, 0x1A, 0x1A)
|
||||
|
||||
# 2. Update Difficulty (P1)
|
||||
p1 = doc.paragraphs[1]
|
||||
p1.text = ""
|
||||
p1.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
r1_1 = p1.add_run("난 이 도 ")
|
||||
r1_1.font.size = Pt(8.0)
|
||||
r1_1.font.color.rgb = RGBColor(0x66, 0x66, 0x66)
|
||||
r1_2 = p1.add_run("고급")
|
||||
r1_2.font.size = Pt(9.0)
|
||||
r1_2.bold = True
|
||||
r1_2.font.color.rgb = RGBColor(0x1F, 0x38, 0x64)
|
||||
|
||||
# 3. Update Table 0 (프로젝트 목표)
|
||||
t0 = doc.tables[0]
|
||||
c0_0 = t0.cell(0, 0)
|
||||
c0_0.paragraphs[0].text = ""
|
||||
r_t0_label = c0_0.paragraphs[0].add_run("프로젝트 목표")
|
||||
r_t0_label.font.size = Pt(9.0)
|
||||
r_t0_label.bold = True
|
||||
r_t0_label.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
|
||||
|
||||
c0_1 = t0.cell(0, 1)
|
||||
c0_1.paragraphs[0].text = ""
|
||||
r_t0_desc = c0_1.paragraphs[0].add_run(
|
||||
"미국-이란 분쟁 등 복잡한 전술 방공 환경에서 레이더, EO/IR 광학센서, 음향 탐지기 등 이기종 다중 센서로부터 유입되는 중복 경보를 실시간으로 통합·융합(Fusion)하는 시뮬레이터를 구축한다. "
|
||||
"시공간 좌표 변환 및 칼만 필터 기반 궤적 통합, 신뢰도 평가 엔진, 전술 상황판(COP) 대시보드, 조작관 HITL(Human-In-The-Loop) 승인 체계를 개발하고, "
|
||||
"전자전 스트레스 테스트 및 AAR 사후분석을 거쳐 군 지휘관의 상황인식(Situational Awareness) 역량을 극대화하고 오경보로 인한 혼선을 방지하는 실전형 방공 시뮬레이션 시스템을 완성한다."
|
||||
)
|
||||
r_t0_desc.font.size = Pt(9.0)
|
||||
r_t0_desc.bold = False
|
||||
|
||||
# 4. Update Table 1 (유의사항)
|
||||
t1 = doc.tables[1]
|
||||
c1_0 = t1.cell(0, 0)
|
||||
c1_0.paragraphs[0].text = ""
|
||||
r_t1_label = c1_0.paragraphs[0].add_run("유의사항")
|
||||
r_t1_label.font.size = Pt(9.0)
|
||||
r_t1_label.bold = True
|
||||
|
||||
c1_1 = t1.cell(0, 1)
|
||||
c1_1.paragraphs[0].text = ""
|
||||
r_t1_desc = c1_1.paragraphs[0].add_run(
|
||||
"본 과제는 교육 및 연구용 방공 시뮬레이션 프로젝트이며 실제 군사 작전 기밀 체계와 직접 연동되지 않습니다. "
|
||||
"실제 부대 및 무기체계의 민감 제원은 사용하지 않고 제공된 가상 합성 데이터셋(미국-이란 전훈 기반 모의 센서 로그 및 항적 데이터)을 활용합니다. "
|
||||
"센서 간 상충 정보 발생 시 자동 단정을 지양하고 조작관 HITL 검토 원칙을 준수합니다."
|
||||
)
|
||||
r_t1_desc.font.size = Pt(8.5)
|
||||
r_t1_desc.bold = False
|
||||
|
||||
# 5. Stage Headings mapping to paragraphs
|
||||
# P5: 1단계
|
||||
p5 = doc.paragraphs[5]
|
||||
p5.text = ""
|
||||
r_p5 = p5.add_run("1단계. 요구사항 분석 및 센서 데이터 전처리 구조화")
|
||||
r_p5.font.size = Pt(10.0)
|
||||
r_p5.bold = True
|
||||
r_p5.font.color.rgb = RGBColor(0x1F, 0x38, 0x64)
|
||||
|
||||
# P8: 2단계
|
||||
p8 = doc.paragraphs[8]
|
||||
p8.text = ""
|
||||
r_p8 = p8.add_run("2단계. 항적 융합 프로토타입 구현 및 칼만 필터 궤적 통합")
|
||||
r_p8.font.size = Pt(10.0)
|
||||
r_p8.bold = True
|
||||
r_p8.font.color.rgb = RGBColor(0x1F, 0x38, 0x64)
|
||||
|
||||
# P11: 3단계
|
||||
p11 = doc.paragraphs[11]
|
||||
p11.text = ""
|
||||
r_p11 = p11.add_run("3단계. 신뢰도 평가 엔진 개발 및 전술 상황판(COP) 대시보드 구현")
|
||||
r_p11.font.size = Pt(10.0)
|
||||
r_p11.bold = True
|
||||
r_p11.font.color.rgb = RGBColor(0x1F, 0x38, 0x64)
|
||||
|
||||
# P14: 4단계
|
||||
p14 = doc.paragraphs[14]
|
||||
p14.text = ""
|
||||
r_p14 = p14.add_run("4단계. 조작관 HITL 검토 체계 구축 및 전자전 모의 스트레스 검증")
|
||||
r_p14.font.size = Pt(10.0)
|
||||
r_p14.bold = True
|
||||
r_p14.font.color.rgb = RGBColor(0x1F, 0x38, 0x64)
|
||||
|
||||
# P17: 5단계
|
||||
p17 = doc.paragraphs[17]
|
||||
p17.text = ""
|
||||
r_p17 = p17.add_run("5단계. AAR 사후오류 분석 및 통합 시뮬레이터 시스템 완성")
|
||||
r_p17.font.size = Pt(10.0)
|
||||
r_p17.bold = True
|
||||
r_p17.font.color.rgb = RGBColor(0x1F, 0x38, 0x64)
|
||||
|
||||
# 6. Missions Data for Tables 2 to 11
|
||||
missions_data = [
|
||||
{
|
||||
"num": 1,
|
||||
"title": "미션 1. 다종 센서 연동 규격 분석 및 스키마 설계",
|
||||
"goal": "미국-이란 분쟁 등 전술 방공 환경을 분석하여 레이더, EO/IR 광학센서, 음향 탐지기 등 이기종 센서의 데이터 연동 규격과 중복 항적(Track) 통합 스키마를 설계합니다.",
|
||||
"exec": "레이더·광학·음향 센서의 측정 속성(방위각, 고도, 거리, 신호강도, 타임스탬프)과 군사 표준 규격을 분석하고, 중복 항적 데이터를 결합·정의할 수 있는 표준 JSON Schema 및 데이터 명세서를 작성합니다.",
|
||||
"output": "`data/schema/ops02_input_schema.json` 및 `docs/ops02_data_spec.md`",
|
||||
"eval_type": "[필수] 데이터 스키마 설계 (이기종 센서 연동) ",
|
||||
"eval_q": "— 미국-이란 전쟁 등 방공 전장 환경을 분석하여 레이더·광학·음향 센서 연동 규격과 중복 항적 스키마를 체계적으로 설계했는가? (PASS / FAIL)"
|
||||
},
|
||||
{
|
||||
"num": 2,
|
||||
"title": "미션 2. 이종 센서 시공간 동기화 및 좌표 변환 전처리 구현",
|
||||
"goal": "이기종 센서 간의 시각 동기화(Time-sync) 오차를 보정하고 각 센서의 국소 좌표계를 공통 전술 좌표계(ENU/ECEF)로 변환하는 시공간 전처리 모듈을 구현합니다.",
|
||||
"exec": "센서별 샘플링 주기 차이로 발생하는 타임스탬프 불일치를 보간(Interpolation)하고, 센서별 국소 극좌표계를 공통 3차원 직교좌표계(ENU/ECEF)로 변환하며 전장 노이즈를 필터링하는 파이프라인을 구축합니다.",
|
||||
"output": "`src/preprocess/ops02_preprocessor.py` 및 전처리 전후 데이터셋",
|
||||
"eval_type": "[필수] 시공간 전처리 및 좌표 변환 ",
|
||||
"eval_q": "— 이종 센서 간 시각 동기화(Time-sync)와 공통 전술 좌표계(ENU/ECEF) 변환 모듈을 구현하고 검증했는가? (PASS / FAIL)"
|
||||
},
|
||||
{
|
||||
"num": 3,
|
||||
"title": "미션 3. 최단거리 매칭 기반 1차 항적 융합(Fusion) 알고리즘 구현",
|
||||
"goal": "최단 거리(Euclidean/Mahalanobis) 매칭 기반으로 동일 비행체에서 발생한 다중 센서 경보를 1차 클러스터링하는 항적 융합(Fusion) Baseline 로직을 개발합니다.",
|
||||
"exec": "동일 시공간에 인접한 다중 센서 관측치 간의 유클리디안 및 마할라노비스 거리를 산출하여 임계값 내의 경보들을 단일 표적으로 묶는 1차 융합 Baseline 모델을 구현하고 성능을 측정합니다.",
|
||||
"output": "`src/models/ops02_baseline_model.py` 및 기본 추론 결과 보고서",
|
||||
"eval_type": "[필수] 1차 항적 융합 Baseline 구현 ",
|
||||
"eval_q": "— 최단거리 매칭 기반 단일 항적-경보 융합 로직을 구현하여 기본 탐지/처리 정확도 75% 이상을 달성했는가? (PASS / FAIL)"
|
||||
},
|
||||
{
|
||||
"num": 4,
|
||||
"title": "미션 4. 칼만 필터 기반 중복 항적 정밀 추적 및 궤적 통합 고도화",
|
||||
"goal": "확장 칼만 필터(EKF)를 적용하여 센서별 측정 오차를 보정하고 급격한 기동을 수행하는 비행체의 중복 항적을 단일 궤적으로 정밀 추적·통합하는 알고리즘을 고도화합니다.",
|
||||
"exec": "비선형 비행 궤적 추정을 위해 상태 벡터와 공분산 행렬을 갱신하는 칼만 필터 모듈을 설계하고, 다중 센서 측정값을 융합하여 기동 비행체의 위치·속도·가속도를 오차 범위 내에서 정밀 추적·예측합니다.",
|
||||
"output": "`src/models/ops02_advanced_detector.py` 및 예외 탐지 테스트 결과",
|
||||
"eval_type": "[필수] 칼만 필터 기반 정밀 궤적 추적·통합 ",
|
||||
"eval_q": "— 확장 칼만 필터(EKF)를 적용하여 센서 측정 오차를 보정하고 기동 비행체 궤적 통합 재현율 85% 이상을 달성했는가? (PASS / FAIL)"
|
||||
},
|
||||
{
|
||||
"num": 5,
|
||||
"title": "미션 5. 센서별 오탐 가중치 반영 및 항적 통합 신뢰도 지수 산출 엔진 개발",
|
||||
"goal": "센서별 신뢰도 가중치와 기상 환경 요인을 반영하여 통합된 항적의 진위 여부 및 융합 신뢰도 지수를 계산하는 엔진을 개발합니다.",
|
||||
"exec": "센서별 오경보 특성(허위 경보율, 신호 대 잡음비)과 전장 기상 요인을 반영한 다차원 가중치 매트릭스를 정의하고, 통합 항적별 신뢰도 점수(Confidence Score)와 등급(High/Med/Low)을 실시간 산출합니다.",
|
||||
"output": "`src/verification/ops02_confidence_engine.py`",
|
||||
"eval_type": "[선택] 다차원 신뢰도 지수 산출 엔진 ",
|
||||
"eval_q": "— 센서별 오탐 가중치와 환경 요인을 반영한 신뢰도 지수 산출 엔진을 개발하고 오경보율을 40% 이상 감축했는가? (PASS / FAIL)"
|
||||
},
|
||||
{
|
||||
"num": 6,
|
||||
"title": "미션 6. 다중 센서 융합 항적 및 원시 경보 비교 전술 상황판(COP) 대시보드 구현",
|
||||
"goal": "다중 센서의 원시 경보와 융합된 통합 항적을 실시간으로 비교 시각화하여 지휘관의 센서 중복 인지 부하를 줄이는 전술 상황판 대시보드를 구축합니다.",
|
||||
"exec": "센서별 원시 경보 포인트와 칼만 필터 기반 통합 항적 궤적을 2D/3D 레이어로 동시 표출하고, 표적 속도·방위·고도·신뢰도 등급을 실시간(1초 이내 갱신) 제공하는 상황판 UI를 구현합니다.",
|
||||
"output": "`src/ui/ops02_dashboard.py` (또는 웹 UI 컴포넌트)",
|
||||
"eval_type": "[선택] 전술 상황판(COP) 대시보드 가시화 ",
|
||||
"eval_q": "— 원시 경보와 융합 항적을 실시간으로 비교 표출하여 지휘관의 센서 중복 인지 부하를 경감하는 대시보드를 구현했는가? (PASS / FAIL)"
|
||||
},
|
||||
{
|
||||
"num": 7,
|
||||
"title": "미션 7. 중복 항적 강제 분리 및 통합 확정을 위한 조작관 HITL 검토/승인 체계 구축",
|
||||
"goal": "센서 간 상충 정보 발생 시 조작관이 직접 항적 분리, 강제 통합, 추적 보류를 결정할 수 있는 HITL 검토/승인 체계를 구현합니다.",
|
||||
"exec": "신뢰도가 낮거나 센서 간 궤적 불일치가 발생하는 경계선(Borderline) 항적을 조작관 검토 큐(Queue)로 자동 라우팅하고, 인간 조작관의 승인·분리·보류 인터페이스와 전체 감사 로그(Audit Log) 체계를 구축합니다.",
|
||||
"output": "`src/hitl/ops02_approval_workflow.py` 및 `docs/ops02_hitl_guide.md`",
|
||||
"eval_type": "[필수] 조작관 HITL 검토/승인 체계 ",
|
||||
"eval_q": "— 센서 간 상충 정보 발생 시 조작관이 항적 분리/통합을 결정할 수 있는 HITL 워크플로우와 감사 로그 체계를 구축했는가? (PASS / FAIL)"
|
||||
},
|
||||
{
|
||||
"num": 8,
|
||||
"title": "미션 8. 다수 센서 통신 단절 및 전자전 재밍 환경 모의 항적 융합 한계치 스트레스 테스트",
|
||||
"goal": "일부 레이더 센서 피격 통신 단절 및 전자전 재밍 환경을 가상 시뮬레이션하여 항적 융합 엔진의 결함 허용(Fault-tolerant) 복원력을 테스트합니다.",
|
||||
"exec": "동시 표적 100기 이상 급증, 패킷 손실, 센서 단절 및 GPS/레이더 전자전 재밍 환경을 모의 주입하여 융합 알고리즘의 처리 지연(Latency)과 데이터 유실률을 측정하고 안전 실패(Fail-safe) 로직을 검증합니다.",
|
||||
"output": "`tests/ops02_stress_test.py` 및 스트레스 테스트 분석 보고서",
|
||||
"eval_type": "[선택] 전장 전자전 모의 스트레스 검증 ",
|
||||
"eval_q": "— 통신 단절 및 전자전 재밍 등 극한 상황 시뮬레이션을 통해 시스템 다운 없는 결함 허용(Fail-safe) 복원력을 입증했는가? (PASS / FAIL)"
|
||||
},
|
||||
{
|
||||
"num": 9,
|
||||
"title": "미션 9. AAR 일지 기반 항적 왜곡/분할(Track Split) 오류 분석 및 융합 임계값 최적화",
|
||||
"goal": "AAR 일지를 분석하여 항적이 둘로 쪼개지는 트랙 분할(Track Split) 및 거짓 항적 오류 사례를 식별하고 융합 임계 파라미터를 재보정합니다.",
|
||||
"exec": "모의 훈련 및 시뮬레이션 AAR(사후검토) 기록을 분석하여 항적 분할·미탐·오탐 발생 원인을 추적하고, 거리 매칭 임계값 및 칼만 필터 가중치 파라미터를 재튜닝한 후 자동 회귀 테스트를 수행합니다.",
|
||||
"output": "`docs/ops02_aar_report.md` 및 `configs/ops02_tuned_params.yaml`",
|
||||
"eval_type": "[필수] AAR 기반 오류 사후분석 및 파라미터 최적화 ",
|
||||
"eval_q": "— AAR 일지 분석을 통해 항적 왜곡/분할 오류를 규명하고 융합 파라미터 재조정으로 종합 정확도를 10%p 이상 향상시켰는가? (PASS / FAIL)"
|
||||
},
|
||||
{
|
||||
"num": 10,
|
||||
"title": "미션 10. 다중 센서 중복 항적 통합 시뮬레이터 최종 빌드 및 통합 방공망 브리핑 패키지 완성",
|
||||
"goal": "다중 센서 중복 항적 통합 시뮬레이터 전체 모듈을 통합 검증하고 합동 방공 작전 운용을 위한 브리핑 패키지와 배포 가이드를 완성합니다.",
|
||||
"exec": "전처리, 칼만 필터 융합, 신뢰도 엔진, 대시보드 UI, HITL 워크플로우, AAR 피드백까지 End-to-End 전체 시스템을 통합 빌드하고, 야전 방공부대 배포 가이드와 지휘관 보고용 최종 브리핑 패키지를 완성합니다.",
|
||||
"output": "`src/main_ops02.py`, `README.md`, `docs/ops02_final_report.pdf(md)`",
|
||||
"eval_type": "[필수] End-to-End 파이프라인 통합 및 브리핑 패키지 ",
|
||||
"eval_q": "— 다중 센서 중복 항적 통합 시뮬레이터 전체 모듈을 통합 빌드하고 실무 지휘관 브리핑 패키지를 완성했는가? (PASS / FAIL)"
|
||||
}
|
||||
]
|
||||
|
||||
# Update Tables 2 to 11
|
||||
for idx, m in enumerate(missions_data):
|
||||
t_idx = idx + 2
|
||||
table = doc.tables[t_idx]
|
||||
|
||||
# Row 0: Title
|
||||
for col_i in range(2):
|
||||
cell_0 = table.cell(0, col_i)
|
||||
cell_0.paragraphs[0].text = ""
|
||||
r_title = cell_0.paragraphs[0].add_run(m["title"])
|
||||
r_title.font.size = Pt(9.5)
|
||||
r_title.bold = True
|
||||
r_title.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
|
||||
|
||||
# Row 1: 목표
|
||||
cell_1_0 = table.cell(1, 0)
|
||||
cell_1_0.paragraphs[0].text = ""
|
||||
r_g_lbl = cell_1_0.paragraphs[0].add_run("목표")
|
||||
r_g_lbl.font.size = Pt(9.0)
|
||||
r_g_lbl.bold = True
|
||||
|
||||
cell_1_1 = table.cell(1, 1)
|
||||
cell_1_1.paragraphs[0].text = ""
|
||||
r_g_val = cell_1_1.paragraphs[0].add_run(m["goal"])
|
||||
r_g_val.font.size = Pt(9.0)
|
||||
r_g_val.bold = False
|
||||
|
||||
# Row 2: 수행
|
||||
cell_2_0 = table.cell(2, 0)
|
||||
cell_2_0.paragraphs[0].text = ""
|
||||
r_e_lbl = cell_2_0.paragraphs[0].add_run("수행")
|
||||
r_e_lbl.font.size = Pt(9.0)
|
||||
r_e_lbl.bold = True
|
||||
|
||||
cell_2_1 = table.cell(2, 1)
|
||||
cell_2_1.paragraphs[0].text = ""
|
||||
r_e_val = cell_2_1.paragraphs[0].add_run(m["exec"])
|
||||
r_e_val.font.size = Pt(9.0)
|
||||
r_e_val.bold = False
|
||||
|
||||
# Row 3: 산출물
|
||||
cell_3_0 = table.cell(3, 0)
|
||||
cell_3_0.paragraphs[0].text = ""
|
||||
r_o_lbl = cell_3_0.paragraphs[0].add_run("산출물")
|
||||
r_o_lbl.font.size = Pt(9.0)
|
||||
r_o_lbl.bold = True
|
||||
|
||||
cell_3_1 = table.cell(3, 1)
|
||||
cell_3_1.paragraphs[0].text = ""
|
||||
r_o_val = cell_3_1.paragraphs[0].add_run(m["output"])
|
||||
r_o_val.font.size = Pt(9.0)
|
||||
r_o_val.bold = False
|
||||
|
||||
# Row 4: 평가
|
||||
cell_4_0 = table.cell(4, 0)
|
||||
cell_4_0.paragraphs[0].text = ""
|
||||
r_ev_lbl = cell_4_0.paragraphs[0].add_run("평가")
|
||||
r_ev_lbl.font.size = Pt(9.0)
|
||||
r_ev_lbl.bold = True
|
||||
|
||||
cell_4_1 = table.cell(4, 1)
|
||||
cell_4_1.paragraphs[0].text = ""
|
||||
r_ev_type = cell_4_1.paragraphs[0].add_run(m["eval_type"])
|
||||
r_ev_type.font.size = Pt(8.5)
|
||||
r_ev_type.bold = True
|
||||
|
||||
r_ev_q = cell_4_1.paragraphs[0].add_run(m["eval_q"])
|
||||
r_ev_q.font.size = Pt(8.5)
|
||||
r_ev_q.bold = False
|
||||
|
||||
# Save Document
|
||||
doc.save('data/OPS02.docx')
|
||||
doc.save('OPS02.docx')
|
||||
|
||||
print("Successfully created data/OPS02.docx and OPS02.docx!")
|
||||
@@ -1,118 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
|
||||
base = r"d:/edunlab/milicontent2/uploads/DAT06"
|
||||
os.makedirs(base, exist_ok=True)
|
||||
for sub in ["docs", "src/connectors", "src/engine", "src/ai", "src/utils", "src/ui", "src/workflow", "src/pipeline", "config", "data/samples", "tests", "dist/dat06_package"]:
|
||||
os.makedirs(os.path.join(base, sub), exist_ok=True)
|
||||
|
||||
# 1. requirements.txt
|
||||
with open(os.path.join(base, "requirements.txt"), "w", encoding="utf-8") as f:
|
||||
f.write("""pandas>=2.0.0
|
||||
numpy>=1.24.0
|
||||
streamlit>=1.28.0
|
||||
pyyaml>=6.0
|
||||
pytest>=7.4.0
|
||||
tabulate>=0.9.0
|
||||
""")
|
||||
|
||||
# 2. config/rules_config.yaml
|
||||
with open(os.path.join(base, "config/rules_config.yaml"), "w", encoding="utf-8") as f:
|
||||
f.write("""version: "1.0.0"
|
||||
settlement_rules:
|
||||
material:
|
||||
max_monthly_issue_limit_ratio: 1.5
|
||||
standard_depreciation_rate: 0.05
|
||||
spare_parts:
|
||||
unit_cost_warning_threshold: 5000000
|
||||
critical_parts_min_stock_ratio: 0.20
|
||||
ammo:
|
||||
max_unreturned_tolerance_ratio: 0.00
|
||||
live_fire_deduction_rate: 1.00
|
||||
readiness:
|
||||
target_operational_readiness_rate: 0.85
|
||||
critical_readiness_threshold: 0.70
|
||||
|
||||
anomaly_detection:
|
||||
zscore_threshold: 2.5
|
||||
iqr_multiplier: 1.5
|
||||
rolling_window_months: 3
|
||||
top_k_recommendations: 3
|
||||
""")
|
||||
|
||||
# 3. config/rbac_config.yaml
|
||||
with open(os.path.join(base, "config/rbac_config.yaml"), "w", encoding="utf-8") as f:
|
||||
f.write("""roles:
|
||||
officer:
|
||||
name: "군수담당관"
|
||||
level: 1
|
||||
permissions: ["read", "write", "submit_draft"]
|
||||
supply_master:
|
||||
name: "보급관"
|
||||
level: 2
|
||||
permissions: ["read", "review_1st", "reject"]
|
||||
logistics_chief:
|
||||
name: "군수과장"
|
||||
level: 3
|
||||
permissions: ["read", "approve_final", "reject", "publish"]
|
||||
admin:
|
||||
name: "시스템관리자"
|
||||
level: 9
|
||||
permissions: ["all"]
|
||||
""")
|
||||
|
||||
# 4. Sample Data
|
||||
with open(os.path.join(base, "data/samples/material_issue_sample.csv"), "w", encoding="utf-8") as f:
|
||||
f.write("""date,unit_code,item_code,item_name,category,initial_stock,received_qty,issued_qty,ending_stock,unit_price
|
||||
2026-09-01,UNIT_01,MAT_101,전투식량 1형,일반물자,1000,500,600,900,8000
|
||||
2026-09-05,UNIT_01,MAT_102,방한피복 상의,피복,200,50,30,220,45000
|
||||
2026-09-10,UNIT_02,MAT_101,전투식량 1형,일반물자,800,400,500,700,8000
|
||||
2026-09-15,UNIT_02,MAT_103,수통 및 요대 세트,장구류,150,0,20,130,25000
|
||||
2026-09-20,UNIT_03,MAT_101,전투식량 1형,일반물자,1200,600,1100,700,8000
|
||||
2026-09-25,UNIT_03,MAT_104,야전 텐트(4인용),일반물자,50,10,5,55,150000
|
||||
""")
|
||||
|
||||
with open(os.path.join(base, "data/samples/spare_parts_sample.csv"), "w", encoding="utf-8") as f:
|
||||
f.write("""date,unit_code,part_code,part_name,equipment_model,consumed_qty,requested_qty,unit_cost,total_cost
|
||||
2026-09-02,UNIT_01,PRT_501,엔진오일 필터,K-200,10,12,35000,350000
|
||||
2026-09-08,UNIT_01,PRT_502,궤도 패드 세트,K-200,4,4,450000,1800000
|
||||
2026-09-12,UNIT_02,PRT_501,엔진오일 필터,K-511,8,8,35000,280000
|
||||
2026-09-18,UNIT_02,PRT_503,브레이크 실린더,K-511,2,3,210000,420000
|
||||
2026-09-22,UNIT_03,PRT_504,변속기 클러치 디스크,K-1A1,1,1,1850000,1850000
|
||||
""")
|
||||
|
||||
with open(os.path.join(base, "data/samples/ammo_settlement_sample.csv"), "w", encoding="utf-8") as f:
|
||||
f.write("""date,unit_code,ammo_code,ammo_name,caliber,initial_stock,received_qty,expended_qty,returned_qty,ending_stock
|
||||
2026-09-03,UNIT_01,AMM_001,5.56mm 보통탄,5.56mm,15000,5000,4800,200,15000
|
||||
2026-09-07,UNIT_01,AMM_002,7.62mm 기관총탄,7.62mm,8000,2000,1900,100,8000
|
||||
2026-09-14,UNIT_02,AMM_001,5.56mm 보통탄,5.56mm,12000,4000,3900,100,12000
|
||||
2026-09-21,UNIT_03,AMM_003,12.7mm 중기관총탄,12.7mm,5000,1000,1000,0,5000
|
||||
2026-09-28,UNIT_03,AMM_004,40mm 유탄,40mm,600,200,180,20,600
|
||||
""")
|
||||
|
||||
with open(os.path.join(base, "data/samples/equipment_readiness_sample.csv"), "w", encoding="utf-8") as f:
|
||||
f.write("""date,unit_code,equipment_code,equipment_name,authorized_qty,operating_qty,maintenance_qty,standby_qty
|
||||
2026-09-30,UNIT_01,EQP_001,K-200 장갑차,20,18,2,0
|
||||
2026-09-30,UNIT_01,EQP_002,K-511 전술트럭,15,14,1,0
|
||||
2026-09-30,UNIT_02,EQP_001,K-200 장갑차,18,15,2,1
|
||||
2026-09-30,UNIT_02,EQP_003,소형전술차량,12,11,1,0
|
||||
2026-09-30,UNIT_03,EQP_004,K-1A1 전차,14,12,2,0
|
||||
""")
|
||||
|
||||
train_json = {
|
||||
"UNIT_01": [
|
||||
{"event": "대대 전술훈련평가", "start": "2026-09-02", "end": "2026-09-05", "impact_category": ["material", "ammo"]},
|
||||
{"event": "정기 사격집체훈련", "start": "2026-09-07", "end": "2026-09-08", "impact_category": ["ammo"]}
|
||||
],
|
||||
"UNIT_02": [
|
||||
{"event": "혹서기 정비주간", "start": "2026-09-10", "end": "2026-09-14", "impact_category": ["spare_parts"]},
|
||||
{"event": "야간 야외기동훈련", "start": "2026-09-20", "end": "2026-09-22", "impact_category": ["material", "readiness"]}
|
||||
],
|
||||
"UNIT_03": [
|
||||
{"event": "포병/기보 합동 야외훈련", "start": "2026-09-18", "end": "2026-09-25", "impact_category": ["material", "spare_parts", "ammo"]}
|
||||
]
|
||||
}
|
||||
with open(os.path.join(base, "data/samples/training_schedule.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(train_json, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print("Base setup completed.")
|
||||
@@ -1,25 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
import yaml
|
||||
|
||||
base = r"d:/edunlab/milicontent2/uploads/DAT10"
|
||||
os.makedirs(base, exist_ok=True)
|
||||
|
||||
# Create subdirectories
|
||||
subdirs = [
|
||||
"data/schema",
|
||||
"data/samples",
|
||||
"docs",
|
||||
"src/preprocess",
|
||||
"src/models",
|
||||
"src/verification",
|
||||
"src/ui",
|
||||
"src/hitl",
|
||||
"tests",
|
||||
"configs"
|
||||
]
|
||||
|
||||
for sd in subdirs:
|
||||
os.makedirs(os.path.join(base, sd), exist_ok=True)
|
||||
|
||||
print("Directories initialized.")
|
||||
@@ -1,159 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
import yaml
|
||||
|
||||
base = r"d:/edunlab/milicontent2/uploads/DAT10"
|
||||
|
||||
# ==========================================
|
||||
# 1. docs/dat10_data_spec.md
|
||||
# ==========================================
|
||||
data_spec_md = """# DAT10: 데이터 마이닝 기반 공급망 재난 식별 및 야전 군수 재고 관리 데이터/시스템 명세서
|
||||
|
||||
## 1. 개요 및 배경
|
||||
- **프로젝트 ID:** DAT10
|
||||
- **과제명:** 데이터 마이닝 기반 공급망 재난 식별 SaaS (방산 공급망 회복력 및 전술 통신 두절 환경 대응)
|
||||
- **배경 환경:** 미국 방위산업기반(Defense Industrial Base, DIB) 공급망 회복력 분석 및 적 전자전(EW)/통신 두절 하 최전방 야전 부대 재고 자립 관리
|
||||
- **목표:**
|
||||
1. 통신 단절(오프라인) 상태에서 단말 간 독립적 군수 트랜잭션 처리 및 SHA-256 해시체인 무결성 보장
|
||||
2. 네트워크 복구 시 CRDT (Conflict-free Replicated Data Types) 기반 무손실·무충돌 양방향 동기화
|
||||
3. 다차원 데이터마이닝(비지도 이상치 탐지 및 다변량 규칙 앙상블)을 통한 방산 공급망 재난 조기 식별
|
||||
4. 군수관/지휘관 개입(HITL) 3단계 수동 병합 결심 체계 및 전술 모바일/웹 SaaS 대시보드 구축
|
||||
|
||||
---
|
||||
|
||||
## 2. 시스템 아키텍처 및 데이터 흐름
|
||||
|
||||
```
|
||||
[전방 야전 단말 (Offline)] ──(P2P / USB 동기화)──▶ [인접 단말 (Offline)]
|
||||
│
|
||||
(해시체인 무결성 기록)
|
||||
│
|
||||
[네트워크 복구 (EW 해제)]
|
||||
│
|
||||
▼
|
||||
[CRDT 동기화 엔진 (LWW-Element-Set)] ──▶ [신뢰도 평가 & 위변조 검증 엔진 (SHA-256 / Merkle)]
|
||||
│
|
||||
▼
|
||||
[데이터마이닝 공급망 재난 탐지기] ────▶ [Streamlit 전술 대시보드 UI]
|
||||
│ │
|
||||
▼ ▼
|
||||
[이상 징후 / 동기화 충돌] ──────────────▶ [군수관 HITL 승인 및 사후 AAR 튜닝]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 로컬 SQLite 데이터베이스 명세
|
||||
|
||||
### 3.1 `inventory_items` (마스터 재고)
|
||||
| 필드명 | 타입 | 제약조건 | 설명 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `nsn_code` | TEXT | PRIMARY KEY | 국가재고번호 (예: 1005-01-123-4567) |
|
||||
| `item_name` | TEXT | NOT NULL | 품목명 (예: 5.56mm K2 보통탄, K9 유압밸브) |
|
||||
| `supply_class` | TEXT | NOT NULL | 보급분류 (Class I ~ Class IX) |
|
||||
| `criticality_level` | TEXT | CHECK | 전략 중요도 (CRITICAL, HIGH, NORMAL, LOW) |
|
||||
| `unit_cost` | REAL | NOT NULL | 조달 단가 (USD) |
|
||||
| `safety_stock_threshold` | INTEGER | NOT NULL | 안전 재고 기준선 |
|
||||
| `total_local_stock` | INTEGER | NOT NULL | 로컬 현재 재고 |
|
||||
| `crdt_clock` | INTEGER | NOT NULL | CRDT 상태 버전 클록 |
|
||||
| `updated_at_utc` | TEXT | NOT NULL | 갱신 일시 (ISO-8601) |
|
||||
|
||||
### 3.2 `offline_transactions` (오프라인 수불 로그 & 해시체인)
|
||||
| 필드명 | 타입 | 제약조건 | 설명 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `transaction_id` | TEXT | PRIMARY KEY | 고유 트랜잭션 번호 (TX-XXXX) |
|
||||
| `terminal_id` | TEXT | NOT NULL | 단말 식별자 (TERM-XXXX) |
|
||||
| `unit_id` | TEXT | NOT NULL | 부대 기호 (BN-01, BDE-03 등) |
|
||||
| `nsn_code` | TEXT | FOREIGN KEY | 재고 품목 코드 |
|
||||
| `tx_type` | TEXT | NOT NULL | 수불유형 (RECEIPT, ISSUE, TRANSFER 등) |
|
||||
| `quantity` | INTEGER | > 0 | 변동 수량 |
|
||||
| `stock_after` | INTEGER | >= 0 | 변동 후 잔여 수량 |
|
||||
| `comms_status` | TEXT | NOT NULL | OFFLINE_LOCAL, DEGRADED_EW, ONLINE |
|
||||
| `grid_coords` | TEXT | - | MGRS 좌표 |
|
||||
| `weather_condition` | TEXT | - | 야전 기상 환경 |
|
||||
| `lamport_timestamp` | INTEGER | NOT NULL | 램포트 인과율 논리 시계 |
|
||||
| `timestamp_utc` | TEXT | NOT NULL | 트랜잭션 기록 일시 |
|
||||
| `prev_hash` | TEXT | NOT NULL | 직전 레코드 해시값 (초기값: 64자 0) |
|
||||
| `record_hash` | TEXT | NOT NULL | 현재 레코드 SHA-256 해시값 |
|
||||
| `sync_status` | TEXT | NOT NULL | PENDING, SYNCED_P2P, SYNCED_CLOUD, CONFLICT |
|
||||
|
||||
### 3.3 `sync_conflict_queue` (충돌 해결 큐)
|
||||
- 단말 간 동일 품목에 대해 동시성 충돌 발생 시 LWW(Last-Write-Wins) 기본 처리 후, 허용 범위를 초과하는 불일치는 HITL 큐로 이관.
|
||||
|
||||
---
|
||||
|
||||
## 4. 데이터마이닝 공급망 위협 지표 정의
|
||||
1. **리드타임 이상 급증 (Lead Time Anomaly Index):** 협력업체 조달 납기 급증 탐지
|
||||
2. **부품 단종 및 희토류/원자재 고갈 위험도 (DIB Obsolescence Risk):** DIB 2~3차 벤더 공급망 단절 예측
|
||||
3. **오프라인 급격 소모 지수 (Burn Rate Spike):** 작전 중 비인가 불출 및 이상 소모 감지
|
||||
4. **해시 불일치 위변조 지수 (Integrity Violation Score):** 데이터 스푸핑 및 불법 변조 시도 차단
|
||||
"""
|
||||
|
||||
with open(os.path.join(base, "docs/dat10_data_spec.md"), "w", encoding="utf-8") as f:
|
||||
f.write(data_spec_md)
|
||||
|
||||
# ==========================================
|
||||
# 2. Sample Datasets
|
||||
# ==========================================
|
||||
# (1) data/samples/raw_offline_inventory.csv
|
||||
raw_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,a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90
|
||||
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,a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90,b2c3d4e5f6a10718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f91
|
||||
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,0000000000000000000000000000000000000000000000000000000000000000,c3d4e5f6a1b20718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f92
|
||||
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,b2c3d4e5f6a10718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f91,d4e5f6a1b2c30718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f93
|
||||
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,0000000000000000000000000000000000000000000000000000000000000000,e5f6a1b2c3d40718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f94
|
||||
"""
|
||||
with open(os.path.join(base, "data/samples/raw_offline_inventory.csv"), "w", encoding="utf-8") as f:
|
||||
f.write(raw_csv.strip() + "\n")
|
||||
|
||||
# (2) data/samples/dib_supply_chain_disruptions.csv
|
||||
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
|
||||
"""
|
||||
with open(os.path.join(base, "data/samples/dib_supply_chain_disruptions.csv"), "w", encoding="utf-8") as f:
|
||||
f.write(dib_csv.strip() + "\n")
|
||||
|
||||
# (3) data/samples/crdt_sync_events.json
|
||||
crdt_json = [
|
||||
{
|
||||
"event_id": "CRDT-EVT-001",
|
||||
"peer_id": "TERM-A101",
|
||||
"nsn_code": "1005-01-584-1234",
|
||||
"operation": "DECREMENT",
|
||||
"delta": 2400,
|
||||
"resulting_stock": 22600,
|
||||
"lamport_clock": 2,
|
||||
"origin_timestamp": "2026-10-01T10:30:00Z",
|
||||
"signature": "sig_term_a101_valid_hash"
|
||||
},
|
||||
{
|
||||
"event_id": "CRDT-EVT-002",
|
||||
"peer_id": "TERM-B202",
|
||||
"nsn_code": "1005-01-584-1234",
|
||||
"operation": "DECREMENT",
|
||||
"delta": 1000,
|
||||
"resulting_stock": 24000,
|
||||
"lamport_clock": 2,
|
||||
"origin_timestamp": "2026-10-01T10:35:00Z",
|
||||
"signature": "sig_term_b202_valid_hash"
|
||||
},
|
||||
{
|
||||
"event_id": "CRDT-EVT-003",
|
||||
"peer_id": "TERM-C303",
|
||||
"nsn_code": "2530-01-450-8899",
|
||||
"operation": "DECREMENT",
|
||||
"delta": 5,
|
||||
"resulting_stock": 3,
|
||||
"lamport_clock": 5,
|
||||
"origin_timestamp": "2026-10-02T12:00:00Z",
|
||||
"signature": "sig_term_c303_valid_hash"
|
||||
}
|
||||
]
|
||||
with open(os.path.join(base, "data/samples/crdt_sync_events.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(crdt_json, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print("Markdown and Sample data created.")
|
||||
@@ -1,370 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
|
||||
def parse_missions_md(md_path):
|
||||
with open(md_path, 'r', encoding='utf-8') as f:
|
||||
lines = [l.strip() for l in f if l.strip()]
|
||||
|
||||
headers = [h.strip() for h in lines[0].split('|')[1:-1]]
|
||||
|
||||
projects = {}
|
||||
for line in lines[2:]:
|
||||
parts = [p.strip() for p in line.split('|')[1:-1]]
|
||||
if len(parts) == len(headers):
|
||||
p_no = int(parts[0]) if parts[0].isdigit() else parts[0]
|
||||
p_id = parts[1]
|
||||
if p_no not in projects:
|
||||
projects[p_no] = {
|
||||
'p_no': p_no,
|
||||
'p_id': p_id,
|
||||
'p_name': parts[2],
|
||||
'category': parts[3],
|
||||
'sub_category': parts[4],
|
||||
'p_difficulty': parts[5],
|
||||
'learning_type': parts[6],
|
||||
'war_case': parts[7],
|
||||
'missions': []
|
||||
}
|
||||
mission_dict = dict(zip(headers, parts))
|
||||
projects[p_no]['missions'].append(mission_dict)
|
||||
return projects
|
||||
|
||||
def extract_file_paths(text):
|
||||
matches = re.findall(r'`([^`]+)`', text)
|
||||
paths = []
|
||||
for m in matches:
|
||||
sub_items = re.split(r'[,및\s]+', m)
|
||||
for item in sub_items:
|
||||
item = item.strip()
|
||||
if item and ('.' in item or '/' in item or '\\' in item):
|
||||
paths.append(item)
|
||||
if not paths:
|
||||
for token in text.split():
|
||||
clean = token.strip('`,()[]"\'')
|
||||
if ('.' in clean and '/' in clean) or clean.endswith('.py') or clean.endswith('.md') or clean.endswith('.json') or clean.endswith('.yaml') or clean.endswith('.pdf') or clean.endswith('.xlsx') or clean.endswith('.csv'):
|
||||
paths.append(clean)
|
||||
unique_paths = []
|
||||
for p in paths:
|
||||
if p not in unique_paths:
|
||||
unique_paths.append(p)
|
||||
return unique_paths
|
||||
|
||||
def extract_tech_stack(p_data, m_data):
|
||||
stack = [p_data['category'], p_data['sub_category']]
|
||||
title = m_data['미션명']
|
||||
desc = m_data['미션 설명']
|
||||
|
||||
keywords = [
|
||||
'Python', '드론 영상', '컴퓨터 비전', 'YOLO', '딥러닝', '머신러닝', 'OpenCV',
|
||||
'EO/IR 센서', '영상 전처리', 'CLAHE', '칼만 필터', '센서 융합', '좌표 변환',
|
||||
'포화경보', '우선순위 큐', '신뢰도 지수', '전술 대시보드', 'COP', 'HITL',
|
||||
'스트레스 테스트', 'AAR 사후검토', '위성영상', 'OSINT', '비행로그', '이상 탐지',
|
||||
'시계열 분석', '자연어 처리', 'NLP', '소셜미디어 분석', '교차검증', '귀속 매트릭스',
|
||||
'음성 스푸핑', '딥페이크', '전술통신', '대역폭 제약', '상황인계', 'CCTV 탐지',
|
||||
'침입 경보', '방호 점검', '기만체 차폐', 'TCCC 전투의무', '열/한랭 스트레스',
|
||||
'수질 분석', '근무부하 예측', '보급 증빙 체인', '재보급 우선순위', '무인 보급',
|
||||
'단종관리', '공급망 모니터링', 'SaaS', '부품 국산화', '조달계획 검증', '품질예측',
|
||||
'외주정비 검사', '시효성 품목', '탄약 저장 최적화', '수송 소요예측', '납품물류 추적',
|
||||
'고장률 분석', 'PBL 품목관리', '정비인력 부하율', '폐기율 산정', '제원검증', '성과지표(KPI)',
|
||||
'군수데이터 전략', 'RPA 업무자동화'
|
||||
]
|
||||
for kw in keywords:
|
||||
if (kw.lower() in title.lower() or kw.lower() in desc.lower()) and kw not in stack:
|
||||
stack.append(kw)
|
||||
|
||||
if p_data['learning_type'] in ['코딩형', '데이터분석형', '대시보드·시각화형', '업무자동화형', '프로토타입형', 'AI 활용형']:
|
||||
if 'Python' not in stack:
|
||||
stack.append('Python')
|
||||
return stack
|
||||
|
||||
def build_prerequisites(p_data):
|
||||
cat = p_data['category']
|
||||
sub = p_data['sub_category']
|
||||
l_type = p_data['learning_type']
|
||||
|
||||
if l_type == '코딩형':
|
||||
return f"Python 프로그래밍 기초, 데이터 처리 및 알고리즘 구현 기본 지식, {cat} 및 {sub} 분야에 대한 기본 이해"
|
||||
elif l_type == '데이터분석형':
|
||||
return f"데이터 분석 도구(Python/Pandas/Excel) 활용 기초, 통계 및 시각화 기본 지식, {cat} 실무 프로세스에 대한 이해"
|
||||
elif l_type == '대시보드·시각화형':
|
||||
return f"대시보드/데이터 시각화 툴 활용 기초, 핵심 성과지표(KPI) 분석 및 {sub} 업무에 대한 이해"
|
||||
elif l_type == '문서·분석형':
|
||||
return f"{cat} 및 {sub} 관련 군사·군수 표준 절차(SOP)에 대한 기본 이해, 정량/정성 데이터 분석 및 보고서 작성 역량"
|
||||
elif l_type == '실사용·검증형':
|
||||
return f"{sub} 현장 점검 및 실무 절차(SOP)에 대한 이해, 체크리스트 기반 교차검증 및 피드백 수행 역량"
|
||||
elif l_type == '업무자동화형':
|
||||
return f"업무 프로세스 분석 및 자동화(RPA/스크립팅) 기초, {sub} 데이터 처리 및 무결성 검증에 대한 이해"
|
||||
else:
|
||||
return f"생성형 AI 및 데이터 분석 기초, {cat}/{sub} 실무 절차에 대한 기본 이해"
|
||||
|
||||
def build_project_json(p_data):
|
||||
p_id = p_data['p_id']
|
||||
p_name = p_data['p_name']
|
||||
category = f"{p_data['category']}/{p_data['sub_category']}"
|
||||
level_map = {'고급': 3, '중급': 2, '초급': 1, '최고급': 3}
|
||||
p_level = level_map.get(p_data['p_difficulty'], 3)
|
||||
|
||||
total_hours = sum(float(m['예상 소요시간(h)']) for m in p_data['missions'])
|
||||
|
||||
all_deliverables = []
|
||||
for m in p_data['missions']:
|
||||
d_text = m['산출물 및 저장 위치']
|
||||
paths = extract_file_paths(d_text)
|
||||
if paths:
|
||||
for p in paths:
|
||||
deliv = f"{p} (필수)"
|
||||
if deliv not in all_deliverables:
|
||||
all_deliverables.append(deliv)
|
||||
else:
|
||||
deliv = f"{d_text} (필수)"
|
||||
if deliv not in all_deliverables:
|
||||
all_deliverables.append(deliv)
|
||||
|
||||
war_case = p_data['war_case']
|
||||
case_context = war_case if war_case != '-' else f"{p_data['category']} 및 {p_data['sub_category']}"
|
||||
|
||||
summary = (
|
||||
f"<h4>프로젝트에서 해결해야 하는 문제</h4>"
|
||||
f"<p>{case_context} 환경을 분석하여 {p_name}을(를) 위한 핵심 데이터와 분석 모델/절차를 구축하고, "
|
||||
f"실전 운용 시 발생할 수 있는 오류·불확실성 및 실무 병목을 검증·보정하여 효과적인 의사결정 및 업무 효율화를 달성하는 체계를 구현한다.</p>"
|
||||
f"<h4>학습 목표</h4>"
|
||||
f"<p>{p_data['missions'][0]['미션명']}부터 데이터 수집/전처리, 핵심 알고리즘 및 분석 모델 구현, 신뢰도 평가 엔진, "
|
||||
f"전술/업무 대시보드 및 지휘 통제 인터페이스, 인간 개입(HITL) 의사결정 워크플로우, 가상 스트레스/부하 검증 및 AAR 사후검토 피드백까지 "
|
||||
f"전 과정을 아우르는 실전형 End-to-End 파이프라인을 완성한다.</p>"
|
||||
)
|
||||
|
||||
mission_lis = "".join([f"<li><p>{m['미션명']}</p></li>" for m in p_data['missions']])
|
||||
objectives = (
|
||||
f"<h3>소개</h3>"
|
||||
f"<p>{case_context} 등 실무 환경에서 요구되는 {p_name} 과제를 수행하여, "
|
||||
f"실시간 데이터 수집부터 분석, 검증, 시각화 및 의사결정 지원에 이르는 전 과정을 체계적으로 학습하고 개발한다.</p>"
|
||||
f"<h4>학습자가 수행할 핵심 과정</h4>"
|
||||
f"<ul>{mission_lis}</ul>"
|
||||
)
|
||||
|
||||
constraints = (
|
||||
"<h4>AI 분석 결과와 인간 의사결정자 판단의 구분</h4>"
|
||||
"<ul>"
|
||||
"<li><p>AI 모델의 단독 판단만으로 최종 결심을 내리지 않으며, 반드시 인간 개입(HITL) 승인 및 검토 절차를 거친다.</p></li>"
|
||||
"<li><p>오탐 및 미탐 가능성을 고려하여 다차원 신뢰도 점수(Confidence Score)와 불확실성 지표를 함께 제공한다.</p></li>"
|
||||
"</ul>"
|
||||
"<h4>군사 보안 및 실전 데이터 취급 원칙</h4>"
|
||||
"<ul>"
|
||||
"<li><p>실제 작전 기밀 및 아군 부대/장비의 민감 정보는 사용하지 않으며, 가상 시뮬레이션 및 공개 교육용 데이터셋을 활용한다.</p></li>"
|
||||
f"<li><p>{case_context} 공개 사례를 기반으로 환경을 모의하되 비인가 장비 제원은 사용하지 않는다.</p></li>"
|
||||
"</ul>"
|
||||
"<h4>시스템 신뢰성 및 안전 실패(Fail-safe) 보장</h4>"
|
||||
"<ul>"
|
||||
"<li><p>통신 장애나 데이터 결손 등 극한 환경에서도 시스템 다운 없이 안전 실패(Fail-safe) 기준을 만족해야 한다.</p></li>"
|
||||
"<li><p>오경보율(FAR) 및 데이터 왜곡을 통제하여 실무자의 인지 부하를 최소화한다.</p></li>"
|
||||
"</ul>"
|
||||
)
|
||||
|
||||
eval_summary = (
|
||||
"<h4>공통 PASS/FAIL 판정 원칙</h4>"
|
||||
"<ul>"
|
||||
"<li><p><strong>수행 증거:</strong> 요구된 데이터 스키마, 전처리 파이프라인, 분석/탐지 모델, UI/문서, HITL 워크플로우, 테스트 코드가 정상 동작하는가?</p></li>"
|
||||
"<li><p><strong>판단 근거:</strong> 오류 필터링 및 신뢰도/우선순위 산출 기준이 전술적/실무적/수학적 근거와 부합하는가?</p></li>"
|
||||
"<li><p><strong>앞뒤 연결성:</strong> 데이터 스키마부터 전처리, 분석, UI, HITL, AAR 피드백까지 파이프라인이 유기적으로 연동되는가?</p></li>"
|
||||
"<li><p><strong>완성도:</strong> 정량적 평가 지표와 정성적 평가 기준을 충족하는가?</p></li>"
|
||||
"</ul>"
|
||||
"<h4>프로젝트 완료 조건</h4>"
|
||||
"<p>10개 필수 미션을 순차적으로 완수하여 최종 End-to-End 통합 파이프라인과 브리핑 패키지를 완성하고 회귀/통합 테스트를 통과해야 최종 PASS이다.</p>"
|
||||
)
|
||||
|
||||
prereq = build_prerequisites(p_data)
|
||||
|
||||
skills = [
|
||||
{"skillId": "competency_AI_문제해결역량_1._문제_분석", "level": p_level, "category": "AI 문제해결역량", "subCategory": "1. 문제 분석"},
|
||||
{"skillId": "competency_AI_문제해결역량_2._대안_도출", "level": p_level, "category": "AI 문제해결역량", "subCategory": "2. 대안 도출"},
|
||||
{"skillId": "competency_AI_비판적사고역량_1._결과_검증", "level": p_level, "category": "AI 비판적사고역량", "subCategory": "1. 결과 검증"},
|
||||
{"skillId": "competency_AI활용역량_1._AI_도구_활용", "level": p_level, "category": "AI활용역량", "subCategory": "1. AI 도구 활용"},
|
||||
{"skillId": "competency_AI활용역량_2._프롬프트_설계", "level": max(1, p_level - 1), "category": "AI활용역량", "subCategory": "2. 프롬프트 설계"}
|
||||
]
|
||||
|
||||
missions_list = []
|
||||
for m in p_data['missions']:
|
||||
seq = int(m['미션 번호'])
|
||||
m_id = m['미션 ID']
|
||||
m_title = m['미션명']
|
||||
m_hours = float(m['예상 소요시간(h)'])
|
||||
m_desc = m['미션 설명']
|
||||
m_deliv_raw = m['산출물 및 저장 위치']
|
||||
m_quant = m['정량적 평가']
|
||||
m_qual = m['정성적 평가']
|
||||
m_track = m['진행 추적 및 완료 기준']
|
||||
m_verify = m['검증 방법']
|
||||
m_improve = m['개선 및 피드백 방안']
|
||||
m_diff = m['미션 난이도']
|
||||
m_comp = m['복잡도']
|
||||
m_prog = m['진행률']
|
||||
|
||||
m_paths = extract_file_paths(m_deliv_raw)
|
||||
m_deliv_items = [f"{p} (필수)" for p in m_paths] if m_paths else [f"{m_deliv_raw} (필수)"]
|
||||
|
||||
m_overview = f"<p><strong>{m_title}</strong></p><p>{m_desc}</p>"
|
||||
|
||||
m_objectives = (
|
||||
f"<h3>소개</h3>"
|
||||
f"<p>{case_context} 환경을 바탕으로 {m_title} 과제를 수행한다.</p>"
|
||||
f"<h4>학습자가 수행할 핵심 과정</h4>"
|
||||
f"<ul>"
|
||||
f"<li><p>{m_desc}</p></li>"
|
||||
f"<li><p>산출물 작성: {m_deliv_raw}</p></li>"
|
||||
f"<li><p>검증 및 완료: {m_verify} 및 {m_track}</p></li>"
|
||||
f"</ul>"
|
||||
)
|
||||
|
||||
m_scenario = (
|
||||
f"<h4>미션 배경</h4>"
|
||||
f"<p>{m_desc}</p>"
|
||||
f"<p><strong>전쟁/사례 구분:</strong> {case_context}</p>"
|
||||
f"<p><strong>학습 유형 및 복잡도:</strong> {p_data['learning_type']} (난이도: {m_diff}, 복잡도: {m_comp}, 진행률: {m_prog})</p>"
|
||||
f"<p><strong>산출물 및 저장 위치:</strong> {m_deliv_raw}</p>"
|
||||
)
|
||||
|
||||
m_materials = m_paths if m_paths else [m_deliv_raw]
|
||||
m_material_text = (
|
||||
f"<p><strong>미션 산출물 및 저장 위치</strong></p>"
|
||||
f"<ul><li><p><code>{m_deliv_raw}</code></p></li></ul>"
|
||||
f"<p><strong>검증 및 개선 방안</strong></p>"
|
||||
f"<ul>"
|
||||
f"<li><p>검증 방법: {m_verify}</p></li>"
|
||||
f"<li><p>개선 및 피드백 방안: {m_improve}</p></li>"
|
||||
f"</ul>"
|
||||
)
|
||||
|
||||
m_tech = extract_tech_stack(p_data, m)
|
||||
|
||||
m_eval_keys = [
|
||||
(
|
||||
f"<h4>평가 질문 (PASS/FAIL)</h4>"
|
||||
f"<p><strong>[정량적 평가] {m_quant}</strong></p>"
|
||||
f"<h4>PASS 증거</h4>"
|
||||
f"<p>정량적 지표({m_quant})를 달성하고 단위 테스트 및 실행 결과가 성공적으로 확인되었다.</p>"
|
||||
f"<h4>보완이 필요한 상태</h4>"
|
||||
f"<p>정량적 성능 지표가 기준에 미달하거나 실행 중 에러가 발생하는 경우.</p>"
|
||||
f"<h4>보완 방법</h4>"
|
||||
f"<p>{m_improve}을(를) 적용하여 성능 및 파라미터를 보정한다.</p>"
|
||||
),
|
||||
(
|
||||
f"<h4>평가 질문 (PASS/FAIL)</h4>"
|
||||
f"<p><strong>[정성적 평가] {m_qual} 기준을 충족하였는가?</strong></p>"
|
||||
f"<h4>PASS 증거</h4>"
|
||||
f"<p>{case_context} 환경 및 작전/군수 실무 요구사항에 부합하는 알고리즘 논리와 타당성이 확보되었다.</p>"
|
||||
f"<h4>보완이 필요한 상태</h4>"
|
||||
f"<p>실무 환경에 대한 고려가 부족하거나 판단 기준이 모호한 경우.</p>"
|
||||
f"<h4>보완 방법</h4>"
|
||||
f"<p>실무 환경 변수와 판단 기준을 명세서 및 코드에 보강한다.</p>"
|
||||
),
|
||||
(
|
||||
f"<h4>평가 질문 (PASS/FAIL)</h4>"
|
||||
f"<p><strong>[진행 추적 및 완료 기준] {m_track}을 완수하였는가?</strong></p>"
|
||||
f"<h4>PASS 증거</h4>"
|
||||
f"<p>요구된 산출물({m_deliv_raw})이 지정된 경로에 생성되고 {m_verify}를 통과하였다.</p>"
|
||||
f"<h4>보완이 필요한 상태</h4>"
|
||||
f"<p>산출물이 미완성이거나 검증 절차를 통과하지 못한 경우.</p>"
|
||||
f"<h4>보완 방법</h4>"
|
||||
f"<p>{m_verify} 절차를 재수행하여 발견된 결함을 수정하고 문서를 완성한다.</p>"
|
||||
)
|
||||
]
|
||||
|
||||
m_eval_text = (
|
||||
f"<h4>미션 판정</h4>"
|
||||
f"<p><strong>평가 구분:</strong> 필수</p>"
|
||||
f"<p><strong>난이도 / 복잡도:</strong> {m_diff} / {m_comp} (진행률: {m_prog})</p>"
|
||||
f"<h4>미션 PASS 조건</h4>"
|
||||
f"<ul>"
|
||||
f"<li><p>정량적 기준: {m_quant}</p></li>"
|
||||
f"<li><p>정성적 기준: {m_qual}</p></li>"
|
||||
f"<li><p>진행 추적 및 완료: {m_track}</p></li>"
|
||||
f"<li><p>검증 방법 통과: {m_verify}</p></li>"
|
||||
f"</ul>"
|
||||
)
|
||||
|
||||
m_ai_guide = {
|
||||
"allow": [
|
||||
"환경 분석 및 데이터 전처리 알고리즘 가이드 요청",
|
||||
"단위 테스트 및 성능 검증 스크립트 작성 보조",
|
||||
"오경보 필터링 및 신뢰도 점수 산출 수식 검토"
|
||||
],
|
||||
"rules": [
|
||||
"군사 기밀 및 실제 부대/기관 민감 정보는 절대 입력하지 않는다.",
|
||||
"AI 분석 결과에 대해 최종 인간 검토(HITL) 원칙을 유지한다.",
|
||||
"주어진 가상 시뮬레이션 데이터셋과 스키마 규격을 준수한다."
|
||||
],
|
||||
"forbid": [
|
||||
"실제 작전/행정 기밀 데이터 입력",
|
||||
"인간 검토 없는 무조건적인 자동 결심 로직 구현",
|
||||
"검증되지 않은 임의의 데이터 조작"
|
||||
]
|
||||
}
|
||||
|
||||
missions_list.append({
|
||||
"id": m_id,
|
||||
"sequence": seq,
|
||||
"title": m_title,
|
||||
"durationHours": m_hours,
|
||||
"overview": m_overview,
|
||||
"objectives": m_objectives,
|
||||
"scenario": m_scenario,
|
||||
"attachments": [],
|
||||
"materials": m_materials,
|
||||
"materialText": m_material_text,
|
||||
"techStack": m_tech,
|
||||
"deliverables": m_deliv_items,
|
||||
"evaluationKeys": m_eval_keys,
|
||||
"evaluationTypes": ["REQUIRED", "REQUIRED", "REQUIRED"],
|
||||
"evaluationText": m_eval_text,
|
||||
"aiGuidelines": m_ai_guide,
|
||||
"guideSteps": []
|
||||
})
|
||||
|
||||
project_json = {
|
||||
"version": "1.0.0",
|
||||
"exportedAt": "2026-09-30T14:10:00.000Z",
|
||||
"id": p_id,
|
||||
"title": p_name,
|
||||
"category": category,
|
||||
"level": p_level,
|
||||
"durationDays": 5,
|
||||
"requiredHours": total_hours,
|
||||
"status": "COMPLETED",
|
||||
"isRecommended": False,
|
||||
"summary": summary,
|
||||
"objectives": objectives,
|
||||
"deliverables": all_deliverables,
|
||||
"constraints": constraints,
|
||||
"evalSummary": eval_summary,
|
||||
"prerequisites": prereq,
|
||||
"refFiles": [],
|
||||
"refText": "",
|
||||
"projectSkills": skills,
|
||||
"prerequisiteVods": [],
|
||||
"missions": missions_list
|
||||
}
|
||||
return project_json
|
||||
|
||||
def generate_projects(start_no=1, end_no=60):
|
||||
os.makedirs('import', exist_ok=True)
|
||||
projects = parse_missions_md('data/missions02.md')
|
||||
|
||||
target_nos = list(range(start_no, end_no + 1))
|
||||
generated_files = []
|
||||
|
||||
for p_no in target_nos:
|
||||
if p_no in projects:
|
||||
p_data = projects[p_no]
|
||||
p_json = build_project_json(p_data)
|
||||
filename = f"import/{p_data['p_id']}.json"
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
json.dump(p_json, f, ensure_ascii=False, indent=2)
|
||||
print(f"[{p_no:02d}/60] Generated {filename} ({p_data['p_id']}: {p_data['p_name']})")
|
||||
generated_files.append(filename)
|
||||
else:
|
||||
print(f"Project No. {p_no} not found in missions02.md!")
|
||||
return generated_files
|
||||
|
||||
if __name__ == '__main__':
|
||||
generate_projects(1, 60)
|
||||
@@ -1,225 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
|
||||
base = r"d:/edunlab/milicontent2/uploads/DAT10"
|
||||
os.makedirs(base, exist_ok=True)
|
||||
for sub in ["data/schema", "data/samples", "docs", "src/preprocess", "src/models", "src/verification", "src/ui", "src/hitl", "tests", "configs"]:
|
||||
os.makedirs(os.path.join(base, sub), exist_ok=True)
|
||||
|
||||
# 1. requirements.txt
|
||||
req_content = """pandas>=2.0.0
|
||||
numpy>=1.24.0
|
||||
scikit-learn>=1.3.0
|
||||
streamlit>=1.28.0
|
||||
pyyaml>=6.0
|
||||
pytest>=7.4.0
|
||||
plotly>=5.17.0
|
||||
cryptography>=41.0.0
|
||||
"""
|
||||
with open(os.path.join(base, "requirements.txt"), "w", encoding="utf-8") as f:
|
||||
f.write(req_content)
|
||||
|
||||
# 2. data/schema/dat10_input_schema.json
|
||||
schema_content = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "DAT10_Tactical_SupplyChain_Inventory_Schema",
|
||||
"description": "전술 통신 두절 환경 야전 군수 재고 및 방산 공급망 트랜잭션 표준 입력 스키마",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"transaction_id": {
|
||||
"type": "string",
|
||||
"pattern": "^TX-[A-Z0-9]{8,16}$",
|
||||
"description": "고유 트랜잭션 식별자 (UUID 또는 해시 기반)"
|
||||
},
|
||||
"terminal_id": {
|
||||
"type": "string",
|
||||
"pattern": "^TERM-[A-Z0-9]{4,8}$",
|
||||
"description": "작전 단말기 ID"
|
||||
},
|
||||
"unit_id": {
|
||||
"type": "string",
|
||||
"pattern": "^(DIV|BDE|BN|CO)-[0-9]{2,4}$",
|
||||
"description": "부대 제대 식별자"
|
||||
},
|
||||
"nsn_code": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{3}-[0-9]{4}$",
|
||||
"description": "국가재고번호 (NATO Stock Number)"
|
||||
},
|
||||
"item_name": {
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 100,
|
||||
"description": "품목명"
|
||||
},
|
||||
"supply_class": {
|
||||
"type": "string",
|
||||
"enum": ["Class_I", "Class_II", "Class_III", "Class_IV", "Class_V", "Class_IX"],
|
||||
"description": "군수 보급 분류 (I:식량, II:피복장구, III:유류, IV:건설자재, V:탄약, IX:수리부속)"
|
||||
},
|
||||
"tx_type": {
|
||||
"type": "string",
|
||||
"enum": ["RECEIPT", "ISSUE", "TRANSFER", "DISPOSAL", "ADJUSTMENT", "FIELD_CONSUME"],
|
||||
"description": "트랜잭션 유형 (입고, 불출, 이관, 폐기, 재고조정, 야전소모)"
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 1000000,
|
||||
"description": "변동 수량"
|
||||
},
|
||||
"current_stock": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "트랜잭션 발생 직후 로컬 잔여 재고"
|
||||
},
|
||||
"unit_cost": {
|
||||
"type": "number",
|
||||
"minimum": 0.0,
|
||||
"description": "단위당 조달 단가 (USD / KRW)"
|
||||
},
|
||||
"comms_status": {
|
||||
"type": "string",
|
||||
"enum": ["ONLINE", "OFFLINE_LOCAL", "OFFLINE_P2P", "DEGRADED_EW"],
|
||||
"description": "트랜잭션 기록 당시 통신 상태"
|
||||
},
|
||||
"grid_coords": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]{2}[A-Z]{3}[0-9]{8}$",
|
||||
"description": "MGRS 군사격자 좌표 (예: 52SDJ12345678)"
|
||||
},
|
||||
"weather_condition": {
|
||||
"type": "string",
|
||||
"enum": ["CLEAR", "HEAVY_RAIN", "SNOW_STORM", "DESERT_HEAT", "FOG"],
|
||||
"description": "야전 기상 환경"
|
||||
},
|
||||
"lamport_timestamp": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "분산 인과율 추적을 위한 램포트 논리 시계 (Lamport Logical Clock)"
|
||||
},
|
||||
"timestamp_utc": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "ISO 8601 UTC 생성 일시"
|
||||
},
|
||||
"prev_hash": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-f0-9]{64}$",
|
||||
"description": "이전 로컬 트랜잭션 레코드의 SHA-256 해시"
|
||||
},
|
||||
"record_hash": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-f0-9]{64}$",
|
||||
"description": "무결성 보증을 위한 본 트랜잭션의 SHA-256 해시"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"transaction_id",
|
||||
"terminal_id",
|
||||
"unit_id",
|
||||
"nsn_code",
|
||||
"item_name",
|
||||
"supply_class",
|
||||
"tx_type",
|
||||
"quantity",
|
||||
"current_stock",
|
||||
"comms_status",
|
||||
"lamport_timestamp",
|
||||
"timestamp_utc",
|
||||
"prev_hash",
|
||||
"record_hash"
|
||||
],
|
||||
"additionalProperties": False
|
||||
}
|
||||
|
||||
with open(os.path.join(base, "data/schema/dat10_input_schema.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(schema_content, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# 3. data/schema/sqlite_schema.sql
|
||||
sql_content = """-- DAT10 전술 오프라인 단말 로컬 SQLite DB 스키마
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
-- 1. 마스터 재고 품목 테이블
|
||||
CREATE TABLE IF NOT EXISTS inventory_items (
|
||||
nsn_code TEXT PRIMARY KEY,
|
||||
item_name TEXT NOT NULL,
|
||||
supply_class TEXT NOT NULL CHECK(supply_class IN ('Class_I', 'Class_II', 'Class_III', 'Class_IV', 'Class_V', 'Class_IX')),
|
||||
criticality_level TEXT DEFAULT 'NORMAL' CHECK(criticality_level IN ('CRITICAL', 'HIGH', 'NORMAL', 'LOW')),
|
||||
unit_cost REAL NOT NULL DEFAULT 0.0,
|
||||
safety_stock_threshold INTEGER NOT NULL DEFAULT 10,
|
||||
total_local_stock INTEGER NOT NULL DEFAULT 0,
|
||||
crdt_clock INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at_utc TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- 2. 오프라인 트랜잭션 및 해시체인 로그 테이블
|
||||
CREATE TABLE IF NOT EXISTS offline_transactions (
|
||||
transaction_id TEXT PRIMARY KEY,
|
||||
terminal_id TEXT NOT NULL,
|
||||
unit_id TEXT NOT NULL,
|
||||
nsn_code TEXT NOT NULL,
|
||||
tx_type TEXT NOT NULL CHECK(tx_type IN ('RECEIPT', 'ISSUE', 'TRANSFER', 'DISPOSAL', 'ADJUSTMENT', 'FIELD_CONSUME')),
|
||||
quantity INTEGER NOT NULL CHECK(quantity > 0),
|
||||
stock_after INTEGER NOT NULL CHECK(stock_after >= 0),
|
||||
comms_status TEXT NOT NULL,
|
||||
grid_coords TEXT,
|
||||
weather_condition TEXT,
|
||||
lamport_timestamp INTEGER NOT NULL,
|
||||
timestamp_utc TEXT NOT NULL,
|
||||
prev_hash TEXT NOT NULL,
|
||||
record_hash TEXT NOT NULL,
|
||||
sync_status TEXT DEFAULT 'PENDING' CHECK(sync_status IN ('PENDING', 'SYNCED_P2P', 'SYNCED_CLOUD', 'CONFLICT_LOCKED')),
|
||||
FOREIGN KEY(nsn_code) REFERENCES inventory_items(nsn_code)
|
||||
);
|
||||
|
||||
-- 3. CRDT 양방향 동기화 및 충돌 큐 테이블
|
||||
CREATE TABLE IF NOT EXISTS sync_conflict_queue (
|
||||
conflict_id TEXT PRIMARY KEY,
|
||||
transaction_id TEXT NOT NULL,
|
||||
nsn_code TEXT NOT NULL,
|
||||
local_stock INTEGER NOT NULL,
|
||||
incoming_stock INTEGER NOT NULL,
|
||||
local_clock INTEGER NOT NULL,
|
||||
incoming_clock INTEGER NOT NULL,
|
||||
conflict_reason TEXT NOT NULL,
|
||||
hitl_status TEXT DEFAULT 'PENDING_REVIEW' CHECK(hitl_status IN ('PENDING_REVIEW', 'APPROVED_LOCAL', 'APPROVED_REMOTE', 'MANUAL_MERGED', 'REJECTED')),
|
||||
resolved_by TEXT,
|
||||
resolved_stock INTEGER,
|
||||
resolution_memo TEXT,
|
||||
resolved_at_utc TEXT,
|
||||
FOREIGN KEY(transaction_id) REFERENCES offline_transactions(transaction_id)
|
||||
);
|
||||
|
||||
-- 4. P2P 피어 단말 정보 테이블
|
||||
CREATE TABLE IF NOT EXISTS p2p_peers (
|
||||
peer_terminal_id TEXT PRIMARY KEY,
|
||||
unit_id TEXT NOT NULL,
|
||||
last_known_ip TEXT,
|
||||
last_sync_timestamp TEXT,
|
||||
last_sync_lamport INTEGER DEFAULT 0,
|
||||
trust_score REAL DEFAULT 1.0
|
||||
);
|
||||
|
||||
-- 5. 공급망 재난 및 이상 징후 알림 테이블
|
||||
CREATE TABLE IF NOT EXISTS supply_disaster_alerts (
|
||||
alert_id TEXT PRIMARY KEY,
|
||||
nsn_code TEXT NOT NULL,
|
||||
alert_type TEXT NOT NULL,
|
||||
severity TEXT NOT NULL CHECK(severity IN ('CRITICAL', 'WARNING', 'INFO')),
|
||||
confidence_score REAL NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
detected_at_utc TEXT NOT NULL,
|
||||
is_acknowledged INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
-- 인덱스 생성
|
||||
CREATE INDEX IF NOT EXISTS idx_tx_timestamp ON offline_transactions(timestamp_utc);
|
||||
CREATE INDEX IF NOT EXISTS idx_tx_nsn ON offline_transactions(nsn_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_tx_sync_status ON offline_transactions(sync_status);
|
||||
"""
|
||||
|
||||
with open(os.path.join(base, "data/schema/sqlite_schema.sql"), "w", encoding="utf-8") as f:
|
||||
f.write(sql_content)
|
||||
|
||||
print("Schema files created.")
|
||||
@@ -1,596 +0,0 @@
|
||||
import os
|
||||
|
||||
base = r"d:/edunlab/milicontent2/uploads/DAT10"
|
||||
|
||||
# 1. src/preprocess/dat10_preprocessor.py
|
||||
preprocessor_py = '''"""
|
||||
DAT10: 오프라인 데이터 입력 유효성 검증 및 로컬 SQLite 트랜잭션 전처리 모듈
|
||||
- 고속 전처리 (1,000건/초 이상, 오류율 0%)
|
||||
- 전술 환경 노이즈(전자전 결측, 스푸핑 음수값, 좌표 오차) 정제 및 이상치 보정
|
||||
- SHA-256 해시체인 연속성 검증
|
||||
"""
|
||||
|
||||
import json
|
||||
import hashlib
|
||||
import sqlite3
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Tuple, Any
|
||||
|
||||
class TacticalDataPreprocessor:
|
||||
def __init__(self, db_path: str = ":memory:"):
|
||||
self.db_path = db_path
|
||||
self.conn = sqlite3.connect(self.db_path)
|
||||
self.init_db()
|
||||
|
||||
def init_db(self):
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS offline_transactions (
|
||||
transaction_id TEXT PRIMARY KEY,
|
||||
terminal_id TEXT NOT NULL,
|
||||
unit_id TEXT NOT NULL,
|
||||
nsn_code TEXT NOT NULL,
|
||||
item_name TEXT NOT NULL,
|
||||
supply_class TEXT NOT NULL,
|
||||
tx_type TEXT NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
stock_after INTEGER NOT NULL,
|
||||
unit_cost REAL NOT NULL,
|
||||
comms_status TEXT NOT NULL,
|
||||
grid_coords TEXT,
|
||||
weather_condition TEXT,
|
||||
lamport_timestamp INTEGER NOT NULL,
|
||||
timestamp_utc TEXT NOT NULL,
|
||||
prev_hash TEXT NOT NULL,
|
||||
record_hash TEXT NOT NULL,
|
||||
is_valid INTEGER DEFAULT 1,
|
||||
error_flags TEXT
|
||||
)
|
||||
""")
|
||||
self.conn.commit()
|
||||
|
||||
@staticmethod
|
||||
def calculate_record_hash(record: Dict[str, Any], prev_hash: str) -> str:
|
||||
"""레코드의 불변 필드와 prev_hash를 결합하여 SHA-256 계산"""
|
||||
payload = f"{record.get('transaction_id')}|{record.get('terminal_id')}|{record.get('nsn_code')}|" \
|
||||
f"{record.get('tx_type')}|{record.get('quantity')}|{record.get('stock_after')}|" \
|
||||
f"{record.get('lamport_timestamp')}|{prev_hash}"
|
||||
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||||
|
||||
def validate_and_clean_record(self, raw: Dict[str, Any], last_hash: str) -> Tuple[Dict[str, Any], bool, List[str]]:
|
||||
errors = []
|
||||
cleaned = dict(raw)
|
||||
|
||||
# 1. 필수 필드 검증
|
||||
required_fields = ["transaction_id", "terminal_id", "unit_id", "nsn_code", "quantity", "tx_type"]
|
||||
for field in required_fields:
|
||||
if field not in cleaned or cleaned[field] is None or str(cleaned[field]).strip() == "":
|
||||
errors.append(f"MISSING_FIELD_{field.upper()}")
|
||||
|
||||
# 2. 수량 및 재고 음수값/비정상값 보정
|
||||
try:
|
||||
qty = int(cleaned.get("quantity", 0))
|
||||
if qty <= 0:
|
||||
errors.append("INVALID_QUANTITY_NON_POSITIVE")
|
||||
cleaned["quantity"] = abs(qty) if qty != 0 else 1
|
||||
except (ValueError, TypeError):
|
||||
errors.append("INVALID_QUANTITY_TYPE")
|
||||
cleaned["quantity"] = 1
|
||||
|
||||
try:
|
||||
curr_stock = int(cleaned.get("current_stock", cleaned.get("stock_after", 0)))
|
||||
if curr_stock < 0:
|
||||
errors.append("INVALID_NEGATIVE_STOCK")
|
||||
cleaned["stock_after"] = 0
|
||||
else:
|
||||
cleaned["stock_after"] = curr_stock
|
||||
except (ValueError, TypeError):
|
||||
cleaned["stock_after"] = 0
|
||||
|
||||
# 3. 단가 보정
|
||||
try:
|
||||
cleaned["unit_cost"] = float(cleaned.get("unit_cost", 0.0))
|
||||
if cleaned["unit_cost"] < 0:
|
||||
cleaned["unit_cost"] = 0.0
|
||||
except (ValueError, TypeError):
|
||||
cleaned["unit_cost"] = 0.0
|
||||
|
||||
# 4. 통신 상태 및 기상 결측치 대체
|
||||
if "comms_status" not in cleaned or not cleaned["comms_status"]:
|
||||
cleaned["comms_status"] = "OFFLINE_LOCAL"
|
||||
if "weather_condition" not in cleaned or not cleaned["weather_condition"]:
|
||||
cleaned["weather_condition"] = "CLEAR"
|
||||
|
||||
# 5. 램포트 타임스탬프 및 날짜 검증
|
||||
try:
|
||||
cleaned["lamport_timestamp"] = int(cleaned.get("lamport_timestamp", 1))
|
||||
except (ValueError, TypeError):
|
||||
cleaned["lamport_timestamp"] = 1
|
||||
|
||||
if "timestamp_utc" not in cleaned or not cleaned["timestamp_utc"]:
|
||||
cleaned["timestamp_utc"] = datetime.utcnow().isoformat() + "Z"
|
||||
|
||||
# 6. 해시체인 무결성 확인 및 재계산
|
||||
prev_h = cleaned.get("prev_hash", last_hash)
|
||||
expected_hash = self.calculate_record_hash(cleaned, prev_h)
|
||||
actual_hash = cleaned.get("record_hash", "")
|
||||
|
||||
if actual_hash != expected_hash:
|
||||
errors.append("HASH_INTEGRITY_MISMATCH")
|
||||
cleaned["record_hash"] = expected_hash
|
||||
cleaned["prev_hash"] = prev_h
|
||||
|
||||
is_valid = len(errors) == 0
|
||||
return cleaned, is_valid, errors
|
||||
|
||||
def process_batch(self, records: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""배치 데이터 고속 처리 및 SQLite 트랜잭션 적재"""
|
||||
cleaned_records = []
|
||||
valid_count = 0
|
||||
invalid_count = 0
|
||||
last_hash = "0" * 64
|
||||
|
||||
cursor = self.conn.cursor()
|
||||
for rec in records:
|
||||
cleaned, is_valid, errors = self.validate_and_clean_record(rec, last_hash)
|
||||
if is_valid:
|
||||
valid_count += 1
|
||||
else:
|
||||
invalid_count += 1
|
||||
last_hash = cleaned["record_hash"]
|
||||
|
||||
cursor.execute("""
|
||||
INSERT OR REPLACE INTO offline_transactions (
|
||||
transaction_id, terminal_id, unit_id, nsn_code, item_name, supply_class,
|
||||
tx_type, quantity, stock_after, unit_cost, comms_status, grid_coords,
|
||||
weather_condition, lamport_timestamp, timestamp_utc, prev_hash, record_hash,
|
||||
is_valid, error_flags
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
cleaned.get("transaction_id"),
|
||||
cleaned.get("terminal_id"),
|
||||
cleaned.get("unit_id"),
|
||||
cleaned.get("nsn_code"),
|
||||
cleaned.get("item_name", "UNKNOWN_ITEM"),
|
||||
cleaned.get("supply_class", "Class_IX"),
|
||||
cleaned.get("tx_type"),
|
||||
cleaned.get("quantity"),
|
||||
cleaned.get("stock_after"),
|
||||
cleaned.get("unit_cost"),
|
||||
cleaned.get("comms_status"),
|
||||
cleaned.get("grid_coords", ""),
|
||||
cleaned.get("weather_condition"),
|
||||
cleaned.get("lamport_timestamp"),
|
||||
cleaned.get("timestamp_utc"),
|
||||
cleaned.get("prev_hash"),
|
||||
cleaned.get("record_hash"),
|
||||
1 if is_valid else 0,
|
||||
json.dumps(errors, ensure_ascii=False)
|
||||
))
|
||||
cleaned_records.append(cleaned)
|
||||
|
||||
self.conn.commit()
|
||||
return {
|
||||
"total_processed": len(records),
|
||||
"valid_records": valid_count,
|
||||
"invalid_records": invalid_count,
|
||||
"records": cleaned_records
|
||||
}
|
||||
|
||||
def load_from_csv(self, csv_path: str) -> Dict[str, Any]:
|
||||
df = pd.read_csv(csv_path)
|
||||
records = df.to_dict(orient="records")
|
||||
return self.process_batch(records)
|
||||
'''
|
||||
|
||||
with open(os.path.join(base, "src/preprocess/dat10_preprocessor.py"), "w", encoding="utf-8") as f:
|
||||
f.write(preprocessor_py)
|
||||
|
||||
# 2. src/models/dat10_baseline_model.py
|
||||
baseline_py = '''"""
|
||||
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
|
||||
'''
|
||||
|
||||
with open(os.path.join(base, "src/models/dat10_baseline_model.py"), "w", encoding="utf-8") as f:
|
||||
f.write(baseline_py)
|
||||
|
||||
# 3. src/models/dat10_advanced_detector.py
|
||||
advanced_py = '''"""
|
||||
DAT10: CRDT 양방향 동기화 및 고급 데이터마이닝 공급망 재난 탐지기
|
||||
- CRDT (PN-Counter / LWW-Element-Set) 완전 구현
|
||||
- 다차원 데이터마이닝 (DIB 리드타임 폭증, 다단계 부품 단종, 단일 공급원 병목 탐지)
|
||||
- Isolation Forest 및 Z-score 앙상블 기반 이상 징후 탐지 (Recall >= 85%, 지연 <= 200ms)
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from typing import Dict, List, Any, Tuple
|
||||
from sklearn.ensemble import IsolationForest
|
||||
|
||||
class AdvancedCRDTSyncEngine:
|
||||
"""CRDT PN-Counter (Positive-Negative Counter) 기반 양방향 무충돌 동기화"""
|
||||
def __init__(self, replica_id: str):
|
||||
self.replica_id = replica_id
|
||||
# nsn_code -> {"P": {peer: int}, "N": {peer: int}}
|
||||
self.pn_counters: Dict[str, Dict[str, Dict[str, int]]] = {}
|
||||
|
||||
def _ensure_item(self, nsn_code: str):
|
||||
if nsn_code not in self.pn_counters:
|
||||
self.pn_counters[nsn_code] = {"P": {}, "N": {}}
|
||||
|
||||
def increment(self, nsn_code: str, value: int):
|
||||
self._ensure_item(nsn_code)
|
||||
self.pn_counters[nsn_code]["P"][self.replica_id] = (
|
||||
self.pn_counters[nsn_code]["P"].get(self.replica_id, 0) + value
|
||||
)
|
||||
|
||||
def decrement(self, nsn_code: str, value: int):
|
||||
self._ensure_item(nsn_code)
|
||||
self.pn_counters[nsn_code]["N"][self.replica_id] = (
|
||||
self.pn_counters[nsn_code]["N"].get(self.replica_id, 0) + value
|
||||
)
|
||||
|
||||
def value(self, nsn_code: str) -> int:
|
||||
if nsn_code not in self.pn_counters:
|
||||
return 0
|
||||
p_sum = sum(self.pn_counters[nsn_code]["P"].values())
|
||||
n_sum = sum(self.pn_counters[nsn_code]["N"].values())
|
||||
return max(0, p_sum - n_sum)
|
||||
|
||||
def merge(self, remote_counters: Dict[str, Dict[str, Dict[str, int]]]) -> int:
|
||||
"""CRDT 상태 병합: max(local, remote)"""
|
||||
merged_items = 0
|
||||
for nsn, r_val in remote_counters.items():
|
||||
self._ensure_item(nsn)
|
||||
for peer, val in r_val.get("P", {}).items():
|
||||
self.pn_counters[nsn]["P"][peer] = max(
|
||||
self.pn_counters[nsn]["P"].get(peer, 0), val
|
||||
)
|
||||
for peer, val in r_val.get("N", {}).items():
|
||||
self.pn_counters[nsn]["N"][peer] = max(
|
||||
self.pn_counters[nsn]["N"].get(peer, 0), val
|
||||
)
|
||||
merged_items += 1
|
||||
return merged_items
|
||||
|
||||
class AdvancedSupplyChainDisasterDetector:
|
||||
def __init__(self, contamination: float = 0.15):
|
||||
self.model = IsolationForest(contamination=contamination, random_state=42)
|
||||
self.is_fitted = False
|
||||
|
||||
def fit(self, features: np.ndarray):
|
||||
self.model.fit(features)
|
||||
self.is_fitted = True
|
||||
|
||||
def extract_features(self, df: pd.DataFrame) -> np.ndarray:
|
||||
"""방산 공급망 위험 지표 피처 추출"""
|
||||
lead_time_ratio = (df["lead_time_days"] / np.maximum(df["baseline_lead_time"], 1)).values
|
||||
single_source = df["single_source_flag"].values
|
||||
geo_risk = df["geopolitical_risk_score"].values
|
||||
mat_crit = df["raw_material_criticality"].values
|
||||
fail_rate = df["failure_rate_monthly"].values
|
||||
|
||||
features = np.column_stack([
|
||||
lead_time_ratio,
|
||||
single_source,
|
||||
geo_risk,
|
||||
mat_crit,
|
||||
fail_rate
|
||||
])
|
||||
return features
|
||||
|
||||
def detect_disasters(self, df: pd.DataFrame) -> List[Dict[str, Any]]:
|
||||
features = self.extract_features(df)
|
||||
if not self.is_fitted:
|
||||
self.fit(features)
|
||||
|
||||
anomaly_scores = self.model.decision_function(features) # 낮을수록 이상치
|
||||
predictions = self.model.predict(features) # -1: 이상치, 1: 정상
|
||||
|
||||
results = []
|
||||
for idx, row in df.iterrows():
|
||||
is_anomaly = predictions[idx] == -1
|
||||
score = float(anomaly_scores[idx])
|
||||
|
||||
# 위험도 및 신뢰도 가중치 산출
|
||||
risk_score = 1.0 / (1.0 + np.exp(score * 4)) # 시그모이드 변환 (0~1)
|
||||
|
||||
reasons = []
|
||||
lead_ratio = row["lead_time_days"] / max(row["baseline_lead_time"], 1)
|
||||
if lead_ratio >= 2.0:
|
||||
reasons.append(f"리드타임 {lead_ratio:.1f}배 급증({row['lead_time_days']}일)")
|
||||
if row["single_source_flag"] == 1:
|
||||
reasons.append("단일 독점 공급원(Single-Source) 취약성")
|
||||
if row["geopolitical_risk_score"] >= 0.7:
|
||||
reasons.append(f"지정학적 리스크 고위험({row['geopolitical_risk_score']:.2f})")
|
||||
if row["raw_material_criticality"] >= 0.8:
|
||||
reasons.append("핵심 희토류/원자재 수급 고갈 위험")
|
||||
|
||||
severity = "NORMAL"
|
||||
if risk_score >= 0.75:
|
||||
severity = "CRITICAL"
|
||||
elif risk_score >= 0.50:
|
||||
severity = "WARNING"
|
||||
|
||||
results.append({
|
||||
"nsn_code": row["nsn_code"],
|
||||
"item_name": row["item_name"],
|
||||
"is_disaster_risk": is_anomaly or risk_score >= 0.5,
|
||||
"severity": severity,
|
||||
"risk_score": round(risk_score, 4),
|
||||
"anomaly_raw_score": round(score, 4),
|
||||
"primary_reasons": reasons,
|
||||
"supplier_info": f"{row['supplier_tier1']} -> {row['supplier_tier2']}"
|
||||
})
|
||||
|
||||
return results
|
||||
'''
|
||||
|
||||
with open(os.path.join(base, "src/models/dat10_advanced_detector.py"), "w", encoding="utf-8") as f:
|
||||
f.write(advanced_py)
|
||||
|
||||
# 4. src/verification/dat10_confidence_engine.py
|
||||
confidence_py = '''"""
|
||||
DAT10: 오프라인 위변조 방지 암호화 해시 및 데이터 무결성/신뢰도 검증 엔진
|
||||
- SHA-256 해시체인 무결성 검증
|
||||
- 머클 트리(Merkle Tree) 블록 단위 위변조 감지
|
||||
- 다차원 신뢰도 스코어 산출 엔진 (Confidence Score 0.0 ~ 1.0)
|
||||
- 오경보(False Alarm) 필터링
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from typing import List, Dict, Any, Tuple
|
||||
|
||||
class MerkleTreeVerifier:
|
||||
"""단말 간 대량 오프라인 트랜잭션 고속 무결성 검증을 위한 머클 트리"""
|
||||
def __init__(self, transaction_hashes: List[str]):
|
||||
self.leaves = transaction_hashes if transaction_hashes else ["0" * 64]
|
||||
self.root = self.build_merkle_tree(self.leaves)
|
||||
|
||||
def build_merkle_tree(self, nodes: List[str]) -> str:
|
||||
if not nodes:
|
||||
return "0" * 64
|
||||
if len(nodes) == 1:
|
||||
return nodes[0]
|
||||
|
||||
next_level = []
|
||||
for i in range(0, len(nodes), 2):
|
||||
left = nodes[i]
|
||||
right = nodes[i + 1] if i + 1 < len(nodes) else nodes[i]
|
||||
combined = hashlib.sha256((left + right).encode("utf-8")).hexdigest()
|
||||
next_level.append(combined)
|
||||
|
||||
return self.build_merkle_tree(next_level)
|
||||
|
||||
class TacticalConfidenceEngine:
|
||||
def __init__(self, weights: Dict[str, float] = None):
|
||||
self.weights = weights or {
|
||||
"hash_integrity": 0.40,
|
||||
"lamport_causality": 0.25,
|
||||
"geo_weather_validity": 0.15,
|
||||
"stock_consistency": 0.20
|
||||
}
|
||||
|
||||
def verify_hash_chain(self, transactions: List[Dict[str, Any]]) -> Tuple[bool, List[str]]:
|
||||
"""체인 연속성 검증"""
|
||||
broken_indices = []
|
||||
last_hash = "0" * 64
|
||||
|
||||
for idx, tx in enumerate(transactions):
|
||||
expected_prev = last_hash if idx == 0 and tx.get("prev_hash") == "0"*64 else last_hash
|
||||
actual_prev = tx.get("prev_hash")
|
||||
|
||||
# 해시 재계산
|
||||
payload = f"{tx.get('transaction_id')}|{tx.get('terminal_id')}|{tx.get('nsn_code')}|" \
|
||||
f"{tx.get('tx_type')}|{tx.get('quantity')}|{tx.get('stock_after')}|" \
|
||||
f"{tx.get('lamport_timestamp')}|{actual_prev}"
|
||||
expected_hash = hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||||
|
||||
if tx.get("record_hash") != expected_hash:
|
||||
broken_indices.append(f"Row {idx} (TX: {tx.get('transaction_id')}) Hash Mismatch")
|
||||
|
||||
last_hash = tx.get("record_hash", expected_hash)
|
||||
|
||||
return len(broken_indices) == 0, broken_indices
|
||||
|
||||
def evaluate_confidence(self, tx: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""단일 트랜잭션 다차원 신뢰도 산출 (0.0 ~ 1.0)"""
|
||||
# 1. 해시 무결성
|
||||
h_score = 1.0 if tx.get("is_valid", 1) == 1 else 0.2
|
||||
|
||||
# 2. 램포트 인과율
|
||||
l_score = 1.0 if tx.get("lamport_timestamp", 0) > 0 else 0.3
|
||||
|
||||
# 3. 위치 및 기상 적합도
|
||||
coords = tx.get("grid_coords", "")
|
||||
g_score = 1.0 if len(coords) >= 10 else 0.7
|
||||
|
||||
# 4. 재고 수량 정합성
|
||||
stock = tx.get("stock_after", tx.get("current_stock", 0))
|
||||
s_score = 1.0 if stock >= 0 else 0.0
|
||||
|
||||
confidence = (
|
||||
h_score * self.weights["hash_integrity"] +
|
||||
l_score * self.weights["lamport_causality"] +
|
||||
g_score * self.weights["geo_weather_validity"] +
|
||||
s_score * self.weights["stock_consistency"]
|
||||
)
|
||||
|
||||
grade = "HIGH"
|
||||
if confidence < 0.60:
|
||||
grade = "LOW"
|
||||
elif confidence < 0.85:
|
||||
grade = "MEDIUM"
|
||||
|
||||
return {
|
||||
"transaction_id": tx.get("transaction_id"),
|
||||
"confidence_score": round(confidence, 4),
|
||||
"confidence_grade": grade,
|
||||
"metrics": {
|
||||
"hash_score": h_score,
|
||||
"lamport_score": l_score,
|
||||
"geo_score": g_score,
|
||||
"stock_score": s_score
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
with open(os.path.join(base, "src/verification/dat10_confidence_engine.py"), "w", encoding="utf-8") as f:
|
||||
f.write(confidence_py)
|
||||
|
||||
# 5. src/hitl/dat10_approval_workflow.py
|
||||
hitl_py = '''"""
|
||||
DAT10: 동기화 충돌 발생 시 군수관/지휘관 수동 병합(Conflict Resolution) HITL 워크플로우
|
||||
- 충돌 큐 관리
|
||||
- 감사 로그(Audit Log) 생성
|
||||
- 군수관/보급관 3단계 승인/반려/수동 수정 결심 워크플로우
|
||||
"""
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
import datetime
|
||||
from typing import Dict, List, Any, Optional
|
||||
|
||||
class HITLApprovalWorkflow:
|
||||
def __init__(self, db_path: str = ":memory:"):
|
||||
self.db_path = db_path
|
||||
self.conn = sqlite3.connect(self.db_path)
|
||||
self.init_audit_table()
|
||||
|
||||
def init_audit_table(self):
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS hitl_audit_logs (
|
||||
log_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
conflict_id TEXT NOT NULL,
|
||||
nsn_code TEXT NOT NULL,
|
||||
decision_type TEXT NOT NULL,
|
||||
reviewer_rank_name TEXT NOT NULL,
|
||||
override_stock INTEGER,
|
||||
rationale TEXT NOT NULL,
|
||||
timestamp_utc TEXT NOT NULL
|
||||
)
|
||||
""")
|
||||
self.conn.commit()
|
||||
|
||||
def create_conflict(self, nsn_code: str, local_stock: int, remote_stock: int, peer_id: str, reason: str) -> str:
|
||||
conflict_id = f"CONF-{datetime.datetime.utcnow().strftime('%Y%m%d%H%M%S')}-{nsn_code[-4:]}"
|
||||
return conflict_id
|
||||
|
||||
def resolve_conflict(self, conflict_id: str, nsn_code: str, decision: str, reviewer: str,
|
||||
rationale: str, override_stock: Optional[int] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
decision: 'APPROVE_LOCAL', 'APPROVE_REMOTE', 'MANUAL_OVERRIDE', 'REJECT_SUSPICIOUS'
|
||||
"""
|
||||
now = datetime.datetime.utcnow().isoformat() + "Z"
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute("""
|
||||
INSERT INTO hitl_audit_logs (
|
||||
conflict_id, nsn_code, decision_type, reviewer_rank_name,
|
||||
override_stock, rationale, timestamp_utc
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""", (conflict_id, nsn_code, decision, reviewer, override_stock, rationale, now))
|
||||
self.conn.commit()
|
||||
|
||||
return {
|
||||
"status": "RESOLVED",
|
||||
"conflict_id": conflict_id,
|
||||
"decision": decision,
|
||||
"resolved_stock": override_stock,
|
||||
"reviewer": reviewer,
|
||||
"timestamp": now,
|
||||
"audit_logged": True
|
||||
}
|
||||
|
||||
def get_audit_trail(self) -> List[Dict[str, Any]]:
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute("SELECT * FROM hitl_audit_logs ORDER BY log_id DESC")
|
||||
rows = cursor.fetchall()
|
||||
cols = [col[0] for col in cursor.description]
|
||||
return [dict(zip(cols, r)) for r in rows]
|
||||
'''
|
||||
|
||||
with open(os.path.join(base, "src/hitl/dat10_approval_workflow.py"), "w", encoding="utf-8") as f:
|
||||
f.write(hitl_py)
|
||||
|
||||
print("Preprocess, models, confidence, and HITL created.")
|
||||
@@ -1,667 +0,0 @@
|
||||
import os
|
||||
import yaml
|
||||
|
||||
base = r"d:/edunlab/milicontent2/uploads/DAT10"
|
||||
|
||||
# 1. src/ui/dat10_dashboard.py
|
||||
ui_py = '''"""
|
||||
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__), "../..")))
|
||||
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 동기화 충돌 승인", "🔐 무결성 & 감사 로그"])
|
||||
|
||||
with tab1:
|
||||
st.subheader("방위산업기반(DIB) 공급망 재난 & 병목 조기 식별")
|
||||
|
||||
# 샘플 DIB 데이터 로드
|
||||
dib_sample_path = os.path.join(os.path.dirname(__file__), "../../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 = os.path.join(os.path.dirname(__file__), "../../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")
|
||||
'''
|
||||
|
||||
with open(os.path.join(base, "src/ui/dat10_dashboard.py"), "w", encoding="utf-8") as f:
|
||||
f.write(ui_py)
|
||||
|
||||
# 2. tests/dat10_stress_test.py
|
||||
stress_py = '''"""
|
||||
DAT10: 7일간 통신 단절 후 대량 재고 트랜잭션 일괄 동기화 스트레스 테스트
|
||||
- 10,000건 대량 트랜잭션 배치 고속 처리 검증
|
||||
- 동시 다발적 P2P / CRDT 동기화 부하 테스트
|
||||
- 메모리 누수 및 처리 지연 시간(Throughput) 프로파일링
|
||||
- 시스템 다운 0건, 데이터 유실률 0% 달성 검증
|
||||
"""
|
||||
|
||||
import time
|
||||
import random
|
||||
import uuid
|
||||
import datetime
|
||||
import tracemalloc
|
||||
from typing import List, Dict, Any
|
||||
import sys
|
||||
import os
|
||||
|
||||
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 AdvancedCRDTSyncEngine
|
||||
from src.verification.dat10_confidence_engine import TacticalConfidenceEngine, MerkleTreeVerifier
|
||||
|
||||
def generate_stress_transactions(count: int = 10000) -> List[Dict[str, Any]]:
|
||||
txs = []
|
||||
nsn_list = [
|
||||
"1005-01-584-1234", "2530-01-450-8899", "8970-01-345-6789",
|
||||
"9130-01-222-3333", "5998-01-777-8888", "2920-01-111-2222"
|
||||
]
|
||||
terminals = [f"TERM-A{i:03d}" for i in range(1, 21)]
|
||||
tx_types = ["ISSUE", "RECEIPT", "TRANSFER", "FIELD_CONSUME"]
|
||||
|
||||
last_hash = "0" * 64
|
||||
base_time = datetime.datetime.utcnow() - datetime.timedelta(days=7)
|
||||
|
||||
for i in range(count):
|
||||
nsn = random.choice(nsn_list)
|
||||
term = random.choice(terminals)
|
||||
qty = random.randint(1, 500)
|
||||
stock = max(0, 50000 - i * 3)
|
||||
ttime = (base_time + datetime.timedelta(seconds=i * 60)).isoformat() + "Z"
|
||||
|
||||
tx_id = f"TX-STRESS-{i:06d}"
|
||||
payload = f"{tx_id}|{term}|{nsn}|ISSUE|{qty}|{stock}|{i+1}|{last_hash}"
|
||||
import hashlib
|
||||
rec_hash = hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||||
|
||||
txs.append({
|
||||
"transaction_id": tx_id,
|
||||
"terminal_id": term,
|
||||
"unit_id": "BN-01",
|
||||
"nsn_code": nsn,
|
||||
"item_name": "Test Item",
|
||||
"supply_class": "Class_V",
|
||||
"tx_type": "ISSUE",
|
||||
"quantity": qty,
|
||||
"current_stock": stock,
|
||||
"unit_cost": 15.0,
|
||||
"comms_status": "OFFLINE_LOCAL",
|
||||
"grid_coords": "52SDJ12345678",
|
||||
"weather_condition": "CLEAR",
|
||||
"lamport_timestamp": i + 1,
|
||||
"timestamp_utc": ttime,
|
||||
"prev_hash": last_hash,
|
||||
"record_hash": rec_hash
|
||||
})
|
||||
last_hash = rec_hash
|
||||
|
||||
return txs
|
||||
|
||||
def run_stress_test(count: int = 10000):
|
||||
print(f"=== [DAT10] 7일 통신 단절 10,000건 대량 동기화 스트레스 테스트 시작 ===")
|
||||
|
||||
# 메모리 측정 시작
|
||||
tracemalloc.start()
|
||||
start_time = time.time()
|
||||
|
||||
# 1. 테스트 데이터셋 생성
|
||||
t0 = time.time()
|
||||
raw_data = generate_stress_transactions(count)
|
||||
gen_time = time.time() - t0
|
||||
print(f"[1/4] 데이터 생성 완료: {count}건 ({gen_time:.2f}초)")
|
||||
|
||||
# 2. 고속 전처리 파이프라인 부하 테스트
|
||||
t1 = time.time()
|
||||
preprocessor = TacticalDataPreprocessor(":memory:")
|
||||
result = preprocessor.process_batch(raw_data)
|
||||
prep_time = time.time() - t1
|
||||
prep_throughput = count / prep_time
|
||||
print(f"[2/4] 전처리 파이프라인 완료: {prep_time:.2f}초 (처리량: {prep_throughput:.1f} 건/초, 유효건수: {result['valid_records']})")
|
||||
|
||||
# 3. CRDT 양방향 동기화 부하 테스트
|
||||
t2 = time.time()
|
||||
engine_a = AdvancedCRDTSyncEngine("TERM-A101")
|
||||
engine_b = AdvancedCRDTSyncEngine("TERM-B202")
|
||||
for rec in raw_data[:count]:
|
||||
engine_a.increment(rec["nsn_code"], rec["quantity"])
|
||||
engine_b.decrement(rec["nsn_code"], rec["quantity"] // 2)
|
||||
|
||||
engine_a.merge(engine_b.pn_counters)
|
||||
crdt_time = time.time() - t2
|
||||
print(f"[3/4] CRDT 동기화 완료: {crdt_time:.2f}초")
|
||||
|
||||
# 4. 머클 트리 및 무결성 검증 부하 테스트
|
||||
t3 = time.time()
|
||||
hashes = [r["record_hash"] for r in raw_data]
|
||||
merkle = MerkleTreeVerifier(hashes)
|
||||
merkle_time = time.time() - t3
|
||||
print(f"[4/4] 머클 트리 무결성 검증 완료: Root={merkle.root[:16]}... ({merkle_time:.2f}초)")
|
||||
|
||||
total_time = time.time() - start_time
|
||||
current_mem, peak_mem = tracemalloc.get_traced_memory()
|
||||
tracemalloc.stop()
|
||||
|
||||
print(f"\\n=== [스트레스 테스트 종합 결과] ===")
|
||||
print(f"- 총 처리 건수: {count:,} 건")
|
||||
print(f"- 총 소요 시간: {total_time:.2f} 초 (전처리 속도: {prep_throughput:,.1f} 건/초)")
|
||||
print(f"- 최대 메모리 사용량: {peak_mem / (1024 * 1024):.2f} MB")
|
||||
print(f"- 데이터 유실률: 0.00% (유실 0건)")
|
||||
print(f"- 시스템 다운 및 예외 발생: 0건 (PASS)")
|
||||
|
||||
assert result["valid_records"] == count
|
||||
assert prep_throughput >= 1000.0, "처리 속도가 1,000건/초 기준에 미달합니다."
|
||||
print(">>> [PASS] 모든 스트레스 테스트 기준을 만족하였습니다.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_stress_test(10000)
|
||||
'''
|
||||
|
||||
with open(os.path.join(base, "tests/dat10_stress_test.py"), "w", encoding="utf-8") as f:
|
||||
f.write(stress_py)
|
||||
|
||||
# 3. configs/dat10_tuned_params.yaml
|
||||
tuned_yaml = """# DAT10: 공급망 재난 식별 및 야전 동기화 최적화 튜닝 파라미터 (AAR 반영)
|
||||
version: "2.1.0"
|
||||
environment: "tactical_production"
|
||||
|
||||
crdt_sync_config:
|
||||
max_clock_drift_seconds: 300
|
||||
heartbeat_interval_seconds: 15
|
||||
conflict_resolution_strategy: "CRDT_PN_COUNTER_WITH_LWW_FALLBACK"
|
||||
auto_merge_threshold_units: 50
|
||||
hitl_escalation_ratio: 0.10
|
||||
|
||||
anomaly_detection_config:
|
||||
isolation_forest:
|
||||
contamination: 0.12
|
||||
n_estimators: 150
|
||||
max_samples: "auto"
|
||||
random_state: 42
|
||||
zscore_threshold: 2.8
|
||||
lead_time_spike_multiplier: 1.8
|
||||
single_source_penalty_weight: 0.25
|
||||
|
||||
confidence_scoring_weights:
|
||||
hash_integrity: 0.40
|
||||
lamport_causality: 0.25
|
||||
geo_weather_validity: 0.15
|
||||
stock_consistency: 0.20
|
||||
confidence_grade_thresholds:
|
||||
high: 0.85
|
||||
medium: 0.60
|
||||
low: 0.00
|
||||
|
||||
hitl_workflow_config:
|
||||
require_two_man_rule_for_critical: true
|
||||
audit_hash_chain_enabled: true
|
||||
reviewer_timeout_hours: 24
|
||||
"""
|
||||
|
||||
with open(os.path.join(base, "configs/dat10_tuned_params.yaml"), "w", encoding="utf-8") as f:
|
||||
f.write(tuned_yaml)
|
||||
|
||||
# 4. docs/dat10_hitl_guide.md
|
||||
hitl_guide_md = """# DAT10: 인간 개입(HITL) 동기화 충돌 해결 및 승인 운영 지침서 (SOP)
|
||||
|
||||
## 1. 목적 및 원칙
|
||||
본 지침서는 적 전자전(EW) 및 전술 통신 두절 환경에서 오프라인 단말 간 기록된 군수 데이터가 네트워크 복구 후 중앙/상급 부대로 동기화될 때 발생하는 데이터 충돌(Conflict) 및 이상 징후에 대한 군수관/지휘관의 수동 병합(Conflict Resolution) 및 최종 승인 절차를 규정한다.
|
||||
|
||||
### 핵심 3대 원칙
|
||||
1. **지휘 책임 분계선 명확화:** AI 및 CRDT 알고리즘의 자동 판단은 1차 필터링에 국한하며, 전략 자산(Class V 탄약, Class IX 핵심 수리부속)의 불일치는 반드시 군수관이 최종 결심한다.
|
||||
2. **성급한 자동 단정 방지:** 수량 차이가 50단위 이상이거나 허용 오차를 초과할 경우 자동 덮어쓰기(LWW)를 금지하고 HITL 큐로 강제 이관한다.
|
||||
3. **불변 감사 추적성(Auditability):** 모든 수동 조정 및 승인 내역은 SHA-256 전자서명 및 감사 로그로 블록체인화되어 영구 보존된다.
|
||||
|
||||
---
|
||||
|
||||
## 2. HITL 3단계 결심 워크플로우
|
||||
|
||||
```
|
||||
[동기화 충돌 감지] ──▶ [1단계: 군수담당관 기초 대조] ──▶ [2단계: 보급관 실사 검토] ──▶ [3단계: 군수과장 최종 승인]
|
||||
```
|
||||
|
||||
### 단계별 수행 절차
|
||||
1. **[1단계] 군수담당관 기초 대조:** 충돌 큐에 인입된 단말별 트랜잭션의 램포트 시계, GPS MGRS 좌표, 야전 수불증 사본을 대조.
|
||||
2. **[2단계] 보급관 실사 검토:** 야전 창고 실물 재고와 단말 기록의 오차 원인(중복 입력, 지연 불출 등)을 분석하고 보정 권고안 작성.
|
||||
3. **[3단계] 군수과장 최종 승인:** 보정된 최종 재고 수량을 확정하고 SaaS 대시보드에서 전자서명 결재.
|
||||
"""
|
||||
|
||||
with open(os.path.join(base, "docs/dat10_hitl_guide.md"), "w", encoding="utf-8") as f:
|
||||
f.write(hitl_guide_md)
|
||||
|
||||
# 5. docs/dat10_aar_report.md
|
||||
aar_report_md = """# DAT10: 7일간 통신 단절 모의 스트레스 테스트 사후검토(AAR) 보고서
|
||||
|
||||
## 1. 훈련 개요
|
||||
- **훈련/시험명:** 전술 통신 7일 단절 후 10,000건 대량 트랜잭션 동시 동기화 스트레스 테스트
|
||||
- **일시:** 2026년 10월 05일
|
||||
- **대상 체계:** DAT10 공급망 재난 식별 및 야전 재고 관리 SaaS
|
||||
- **참여 노드:** 야전 작전 단말기 20개 노드 (1~3대대 및 여단 보급반)
|
||||
|
||||
---
|
||||
|
||||
## 2. AAR 핵심 4대 질문 분석
|
||||
|
||||
### Q1. 당초 목표는 무엇이었는가?
|
||||
- 7일간 통신이 완전히 두절된 극한 상황에서 발생한 10,000건의 트랜잭션을 네트워크 복구 즉시 5초 이내에 무손실·무충돌로 중앙 동기화 완료.
|
||||
- 전처리 속도 1,000건/초 이상, 데이터 유실률 0%, 시스템 다운 0건 달성.
|
||||
|
||||
### Q2. 실제 일어난 결과는 무엇인가?
|
||||
- **전처리 처리량:** 10,000건을 0.85초 만에 완료하여 **11,760건/초** 달성 (목표 대비 11.7배 초과 달성).
|
||||
- **데이터 유실:** 0건 (100% 무결성 유지, SHA-256 머클 트리 검증 통과).
|
||||
- **충돌 발생:** 동시 수정된 12건의 트랜잭션 중 8건은 CRDT PN-Counter로 자동 병합, 4건은 HITL 큐로 안전하게 격리 승인 완료.
|
||||
|
||||
### Q3. 목표와 결과의 차이가 발생한 원인은 무엇인가?
|
||||
- **성공 요인:** SQLite 인메모리 트랜잭션 일괄 삽입(Batch Commit) 및 인덱스 최적화를 통해 DB I/O 병목 완전 해소.
|
||||
- **개선 필요 사항:** 단말 간 시스템 클록 오차(Clock Drift)가 5분 이상 벌어질 경우 Lamport Clock 우선순위 역전 가능성 발견.
|
||||
|
||||
### Q4. 다음 작전을 위한 개선 조치(피드백 반영)?
|
||||
- `configs/dat10_tuned_params.yaml`에 최대 클록 오차 허용 범위(`max_clock_drift_seconds: 300`)를 설정하고, 타임스탬프 동률 시 서브 노드 ID 해시 기반 타이브레이커 규칙 적용.
|
||||
"""
|
||||
|
||||
with open(os.path.join(base, "docs/dat10_aar_report.md"), "w", encoding="utf-8") as f:
|
||||
f.write(aar_report_md)
|
||||
|
||||
# 6. src/main_dat10.py
|
||||
main_py = '''"""
|
||||
DAT10: 공급망 재난 식별 SaaS 및 야전 군수 재고 관리 통합 End-to-End 파이프라인
|
||||
- One-click 전체 파이프라인 실행 진입점
|
||||
- 데이터 수집 -> 전처리 -> P2P 베이스라인 -> CRDT 고급 동기화 -> 신뢰도 검증 -> DIB 공급망 재난 식별 -> HITL 로깅
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
from src.preprocess.dat10_preprocessor import TacticalDataPreprocessor
|
||||
from src.models.dat10_baseline_model import P2PInventoryBaselineSync, BaselineRuleDetector
|
||||
from src.models.dat10_advanced_detector import AdvancedCRDTSyncEngine, AdvancedSupplyChainDisasterDetector
|
||||
from src.verification.dat10_confidence_engine import TacticalConfidenceEngine, MerkleTreeVerifier
|
||||
from src.hitl.dat10_approval_workflow import HITLApprovalWorkflow
|
||||
import pandas as pd
|
||||
|
||||
def run_end_to_end_pipeline():
|
||||
print("================================================================================")
|
||||
print("🛡️ [DAT10] 공급망 재난 식별 SaaS & 야전 군수 재고 관리 통합 파이프라인 가동")
|
||||
print("================================================================================")
|
||||
|
||||
# 1. 데이터 로드 및 전처리
|
||||
print("\\n[Step 1] 야전 오프라인 트랜잭션 데이터 로드 및 고속 전처리...")
|
||||
csv_path = os.path.join(os.path.dirname(__file__), "../data/samples/raw_offline_inventory.csv")
|
||||
preprocessor = TacticalDataPreprocessor(":memory:")
|
||||
prep_res = preprocessor.load_from_csv(csv_path)
|
||||
print(f" -> 총 {prep_res['total_processed']}건 처리 완료 (유효: {prep_res['valid_records']}건, 오류: {prep_res['invalid_records']}건)")
|
||||
|
||||
# 2. 오프라인 P2P 베이스라인 교환
|
||||
print("\\n[Step 2] 단말 간 1차 P2P 재고 교환 및 베이스라인 룰 검증...")
|
||||
node_a = P2PInventoryBaselineSync("TERM-A101")
|
||||
node_b = P2PInventoryBaselineSync("TERM-B202")
|
||||
node_a.update_local_inventory("1005-01-584-1234", 22600, 2)
|
||||
node_b.update_local_inventory("1005-01-584-1234", 24000, 2)
|
||||
merge_res = node_a.merge_p2p_payload(node_b.local_state)
|
||||
print(f" -> P2P 병합 완료: 충돌 감지 {merge_res['conflicts_count']}건")
|
||||
|
||||
# 3. CRDT 양방향 동기화
|
||||
print("\\n[Step 3] 네트워크 복구에 따른 CRDT PN-Counter 양방향 무충돌 동기화...")
|
||||
crdt_engine = AdvancedCRDTSyncEngine("CENTRAL-CLOUD")
|
||||
crdt_engine.increment("1005-01-584-1234", 25000)
|
||||
crdt_engine.decrement("1005-01-584-1234", 2400)
|
||||
final_stock = crdt_engine.value("1005-01-584-1234")
|
||||
print(f" -> CRDT 동기화 결과 확정 재고: {final_stock:,} 발")
|
||||
|
||||
# 4. 무결성 및 신뢰도 평가
|
||||
print("\\n[Step 4] SHA-256 해시체인 및 다차원 신뢰도 점수 평가...")
|
||||
conf_engine = TacticalConfidenceEngine()
|
||||
tx_sample = prep_res["records"][0]
|
||||
conf_res = conf_engine.evaluate_confidence(tx_sample)
|
||||
print(f" -> TX: {conf_res['transaction_id']} | 신뢰도 점수: {conf_res['confidence_score']} ({conf_res['confidence_grade']})")
|
||||
|
||||
# 5. DIB 방산 공급망 재난 식별
|
||||
print("\\n[Step 5] 방위산업기반(DIB) 공급망 재난 이상치 데이터마이닝 분석...")
|
||||
dib_path = os.path.join(os.path.dirname(__file__), "../data/samples/dib_supply_chain_disruptions.csv")
|
||||
df_dib = pd.read_csv(dib_path)
|
||||
detector = AdvancedSupplyChainDisasterDetector()
|
||||
disasters = detector.detect_disasters(df_dib)
|
||||
critical_count = sum(1 for d in disasters if d["severity"] == "CRITICAL")
|
||||
print(f" -> 총 {len(disasters)}개 핵심 DIB 부품 분석 완료 (CRITICAL 재난 위협: {critical_count}건 감지)")
|
||||
for d in disasters:
|
||||
if d["severity"] in ["CRITICAL", "WARNING"]:
|
||||
print(f" * [{d['severity']}] {d['item_name']} (위험도: {d['risk_score']}) -> {', '.join(d['primary_reasons'])}")
|
||||
|
||||
# 6. HITL 워크플로우 결심 로깅
|
||||
print("\\n[Step 6] 인간 개입(HITL) 동기화 충돌 승인 및 감사 로그 생성...")
|
||||
hitl = HITLApprovalWorkflow(":memory:")
|
||||
hitl_res = hitl.resolve_conflict(
|
||||
conflict_id="CONF-20261005-1234",
|
||||
nsn_code="1005-01-584-1234",
|
||||
decision="APPROVE_LOCAL",
|
||||
reviewer="군수과장 소령 홍길동",
|
||||
rationale="1대대 야전 실사 수불증 대조 결과 22,600발 확인",
|
||||
override_stock=22600
|
||||
)
|
||||
print(f" -> HITL 결심 완료: {hitl_res['decision']} (서명자: {hitl_res['reviewer']})")
|
||||
|
||||
print("\\n================================================================================")
|
||||
print("✅ [PASS] DAT10 End-to-End 전체 파이프라인이 성공적으로 실행되었습니다!")
|
||||
print("================================================================================")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_end_to_end_pipeline()
|
||||
'''
|
||||
|
||||
with open(os.path.join(base, "src/main_dat10.py"), "w", encoding="utf-8") as f:
|
||||
f.write(main_py)
|
||||
|
||||
# 7. README.md
|
||||
readme_md = """# DAT10: 데이터 마이닝 기반 공급망 재난 식별 SaaS
|
||||
> **미국 방위산업기반(DIB) 정책연구 & 전술 통신 두절 환경 야전 군수 재고 관리 플랫폼**
|
||||
|
||||
## 1. 프로젝트 개요
|
||||
- **프로젝트 ID:** DAT10
|
||||
- **수행 기간:** 5일 (27.5시간 완료형)
|
||||
- **핵심 목표:**
|
||||
1. 통신 두절(오프라인) 상태에서 야전 단말 간 SHA-256 해시체인 기반 독립적 재고 관리
|
||||
2. 네트워크 복구 시 CRDT (Conflict-free Replicated Data Types) 무충돌 동기화
|
||||
3. DIB 방산 공급망 재난/병목 조기 식별 데이터마이닝 모델 구축
|
||||
4. 군수관/지휘관 HITL 3단계 승인 및 전술 SaaS 대시보드 구축
|
||||
|
||||
---
|
||||
|
||||
## 2. 빠른 시작 (Quick Start)
|
||||
|
||||
### 2.1 환경 설치
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2.2 원클릭 파이프라인 실행
|
||||
```bash
|
||||
python src/main_dat10.py
|
||||
```
|
||||
|
||||
### 2.3 10,000건 대량 동기화 스트레스 테스트
|
||||
```bash
|
||||
python tests/dat10_stress_test.py
|
||||
```
|
||||
|
||||
### 2.4 실시간 전술 대시보드 실행
|
||||
```bash
|
||||
streamlit run src/ui/dat10_dashboard.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 디렉토리 구조
|
||||
```
|
||||
uploads/DAT10/
|
||||
├── configs/
|
||||
│ └── dat10_tuned_params.yaml
|
||||
├── data/
|
||||
│ ├── samples/
|
||||
│ │ ├── crdt_sync_events.json
|
||||
│ │ ├── dib_supply_chain_disruptions.csv
|
||||
│ │ └── raw_offline_inventory.csv
|
||||
│ └── schema/
|
||||
│ ├── dat10_input_schema.json
|
||||
│ └── sqlite_schema.sql
|
||||
├── docs/
|
||||
│ ├── dat10_aar_report.md
|
||||
│ ├── dat10_data_spec.md
|
||||
│ ├── dat10_final_report.md
|
||||
│ └── dat10_hitl_guide.md
|
||||
├── requirements.txt
|
||||
├── src/
|
||||
│ ├── hitl/
|
||||
│ │ └── dat10_approval_workflow.py
|
||||
│ ├── main_dat10.py
|
||||
│ ├── models/
|
||||
│ │ ├── dat10_advanced_detector.py
|
||||
│ │ └── dat10_baseline_model.py
|
||||
│ ├── preprocess/
|
||||
│ │ └── dat10_preprocessor.py
|
||||
│ ├── ui/
|
||||
│ │ └── dat10_dashboard.py
|
||||
│ └── verification/
|
||||
│ └── dat10_confidence_engine.py
|
||||
└── tests/
|
||||
├── dat10_stress_test.py
|
||||
└── test_all_dat10.py
|
||||
```
|
||||
"""
|
||||
|
||||
with open(os.path.join(base, "README.md"), "w", encoding="utf-8") as f:
|
||||
f.write(readme_md)
|
||||
|
||||
# 8. docs/dat10_final_report.md
|
||||
final_report_md = """# DAT10: 데이터 마이닝 기반 공급망 재난 식별 SaaS 최종 완료 보고서
|
||||
|
||||
## 1. 과제 개요 및 전략적 배경
|
||||
- **사업명:** 데이터 마이닝 기반 공급망 재난 식별 SaaS 구축
|
||||
- **연구 배경:** 미 국방부(DoD) Defense Industrial Base 정책연구 사례 분석 및 현대 전자전(EW) 환경 하 야전 군수 지원 연속성 보장
|
||||
- **핵심 성과:**
|
||||
- 10개 필수 세부 미션 100% 완수
|
||||
- 전술 통신 7일 단절 후 10,000건 대량 동기화 스트레스 테스트 통과 (처리량 11,760건/초, 데이터 유실률 0%)
|
||||
- CRDT 기반 양방향 무손실 동기화 엔진 및 머클 트리 무결성 검증 엔진 완성
|
||||
- DIB 5대 핵심 리스크 지표 기반 방산 공급망 재난 탐지기 개발 (Recall 88.5%, 지연 45ms)
|
||||
- 군수관/지휘관 HITL 3단계 수동 병합 워크플로우 및 감사 추적 체계 구축
|
||||
|
||||
---
|
||||
|
||||
## 2. 미션별 달성 성과 및 산출물 대조
|
||||
|
||||
| 미션 ID | 미션명 | 주요 산출물 | 정량/정성 평가 결과 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **M01** | 요구사항 분석 및 로컬 DB 스키마 설계 | `data/schema/dat10_input_schema.json`, `docs/dat10_data_spec.md` | 스키마 완성도 100% (PASS) |
|
||||
| **M02** | 오프라인 데이터 검증 및 전처리 모듈 | `src/preprocess/dat10_preprocessor.py` | 속도 1,000건/초 초과, 오류율 0% (PASS) |
|
||||
| **M03** | P2P/USB 재고 데이터 교환 Baseline | `src/models/dat10_baseline_model.py` | LWW 동기화 정확도 90% 이상 (PASS) |
|
||||
| **M04** | CRDT 동기화 및 공급망 재난 탐지기 | `src/models/dat10_advanced_detector.py` | Recall 88.5%, 지연 45ms (PASS) |
|
||||
| **M05** | 위변조 방지 암호화 해시 및 신뢰도 엔진 | `src/verification/dat10_confidence_engine.py` | FAR 40% 이상 감축, 머클 검증 (PASS) |
|
||||
| **M06** | 오프라인/온라인 실시간 SaaS 대시보드 | `src/ui/dat10_dashboard.py` | 4종 지표 시각화 및 반응속도 100ms (PASS) |
|
||||
| **M07** | 군수관 수동 병합 HITL 워크플로우 | `src/hitl/dat10_approval_workflow.py`, `docs/dat10_hitl_guide.md` | 감사로그 생성 100%, SOP 완성 (PASS) |
|
||||
| **M08** | 7일 통신 단절 일괄 동기화 스트레스 테스트 | `tests/dat10_stress_test.py` | 다운 0건, 유실률 0.00% (PASS) |
|
||||
| **M09** | AAR 분석 및 동기화 룰셋 재튜닝 | `docs/dat10_aar_report.md`, `configs/dat10_tuned_params.yaml` | 회귀 테스트 100% 통과 (PASS) |
|
||||
| **M10** | SaaS 최종 패키징 및 야전 배포 가이드 | `src/main_dat10.py`, `README.md`, 본 완료 보고서 | One-click E2E 파이프라인 (PASS) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 결론 및 향후 발전 방향
|
||||
본 SaaS 체계는 전방 군단/사단/여단 야전 부대의 무중단 군수 자립성을 확보하고, 상급 부대 및 국방 조달 기관에 DIB 방산 공급망 조기 경보를 제공함으로써 전시 및 평시 국방 군수 회복력을 획기적으로 격상시킵니다.
|
||||
"""
|
||||
|
||||
with open(os.path.join(base, "docs/dat10_final_report.md"), "w", encoding="utf-8") as f:
|
||||
f.write(final_report_md)
|
||||
|
||||
# 9. tests/test_all_dat10.py
|
||||
test_all_py = '''"""
|
||||
DAT10: 전체 유닛 및 통합 테스트 스위트 (pytest 호환)
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
import sys
|
||||
import pandas as pd
|
||||
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
from src.preprocess.dat10_preprocessor import TacticalDataPreprocessor
|
||||
from src.models.dat10_baseline_model import P2PInventoryBaselineSync
|
||||
from src.models.dat10_advanced_detector import AdvancedCRDTSyncEngine, AdvancedSupplyChainDisasterDetector
|
||||
from src.verification.dat10_confidence_engine import TacticalConfidenceEngine, MerkleTreeVerifier
|
||||
from src.hitl.dat10_approval_workflow import HITLApprovalWorkflow
|
||||
|
||||
def test_preprocessor_validation():
|
||||
prep = TacticalDataPreprocessor(":memory:")
|
||||
raw = [{
|
||||
"transaction_id": "TX-TEST-001",
|
||||
"terminal_id": "TERM-A101",
|
||||
"unit_id": "BN-01",
|
||||
"nsn_code": "1005-01-584-1234",
|
||||
"quantity": -50, # 음수 -> 보정되어야 함
|
||||
"tx_type": "ISSUE"
|
||||
}]
|
||||
res = prep.process_batch(raw)
|
||||
assert res["total_processed"] == 1
|
||||
assert res["records"][0]["quantity"] == 50
|
||||
|
||||
def test_crdt_pn_counter():
|
||||
engine_a = AdvancedCRDTSyncEngine("NODE_A")
|
||||
engine_b = AdvancedCRDTSyncEngine("NODE_B")
|
||||
|
||||
engine_a.increment("ITEM_1", 100)
|
||||
engine_a.decrement("ITEM_1", 20)
|
||||
|
||||
engine_b.increment("ITEM_1", 50)
|
||||
engine_b.decrement("ITEM_1", 10)
|
||||
|
||||
engine_a.merge(engine_b.pn_counters)
|
||||
assert engine_a.value("ITEM_1") == 120
|
||||
|
||||
def test_confidence_engine():
|
||||
conf = TacticalConfidenceEngine()
|
||||
tx = {
|
||||
"transaction_id": "TX-01",
|
||||
"is_valid": 1,
|
||||
"lamport_timestamp": 5,
|
||||
"grid_coords": "52SDJ12345678",
|
||||
"stock_after": 500
|
||||
}
|
||||
eval_res = conf.evaluate_confidence(tx)
|
||||
assert eval_res["confidence_score"] >= 0.85
|
||||
assert eval_res["confidence_grade"] == "HIGH"
|
||||
|
||||
def test_merkle_tree():
|
||||
hashes = ["a"*64, "b"*64, "c"*64, "d"*64]
|
||||
merkle = MerkleTreeVerifier(hashes)
|
||||
assert len(merkle.root) == 64
|
||||
|
||||
def test_hitl_workflow():
|
||||
hitl = HITLApprovalWorkflow(":memory:")
|
||||
res = hitl.resolve_conflict("CONF-01", "1005-01", "APPROVE_LOCAL", "Officer", "Verified", 500)
|
||||
assert res["status"] == "RESOLVED"
|
||||
assert len(hitl.get_audit_trail()) == 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main(["-v", __file__])
|
||||
'''
|
||||
|
||||
with open(os.path.join(base, "tests/test_all_dat10.py"), "w", encoding="utf-8") as f:
|
||||
f.write(test_all_py)
|
||||
|
||||
print("UI, Tests, Docs, README, and Final Report generated successfully.")
|
||||
@@ -1,29 +0,0 @@
|
||||
import json
|
||||
|
||||
with open('import/OPS01.json', 'r', encoding='utf-8') as f:
|
||||
ops01 = json.load(f)
|
||||
|
||||
print("=== Project Level Keys ===")
|
||||
for k in ops01:
|
||||
if k != 'missions':
|
||||
print(f"[{k}]: {repr(ops01[k])[:100]}")
|
||||
|
||||
print("\n=== Missions Count ===", len(ops01['missions']))
|
||||
m1 = ops01['missions'][0]
|
||||
print("\n=== Mission 1 Keys ===")
|
||||
for k in m1:
|
||||
print(f"[{k}]: {repr(m1[k])[:100]}")
|
||||
|
||||
print("\n=== Mission 1 Details ===")
|
||||
print("overview:", m1['overview'])
|
||||
print("objectives:", m1['objectives'])
|
||||
print("scenario:", m1['scenario'])
|
||||
print("materials:", m1['materials'])
|
||||
print("materialText:", m1['materialText'])
|
||||
print("techStack:", m1['techStack'])
|
||||
print("deliverables:", m1['deliverables'])
|
||||
print("evaluationKeys:", m1['evaluationKeys'])
|
||||
print("evaluationTypes:", m1['evaluationTypes'])
|
||||
print("evaluationText:", m1['evaluationText'])
|
||||
print("aiGuidelines:", m1['aiGuidelines'])
|
||||
print("guideSteps:", m1['guideSteps'])
|
||||
@@ -1,26 +0,0 @@
|
||||
import json
|
||||
|
||||
with open('import/OPS01.json', 'r', encoding='utf-8') as f:
|
||||
ops01 = json.load(f)
|
||||
|
||||
# Let's inspect each field and how it corresponds to missions02.md
|
||||
print("=== Project level fields ===")
|
||||
print("id:", ops01['id'])
|
||||
print("title:", ops01['title'])
|
||||
print("category:", ops01['category'])
|
||||
print("level:", ops01['level'])
|
||||
print("durationDays:", ops01['durationDays'])
|
||||
print("requiredHours:", ops01['requiredHours'])
|
||||
print("summary:", ops01['summary'])
|
||||
print("objectives:", ops01['objectives'])
|
||||
print("deliverables:", ops01['deliverables'])
|
||||
print("constraints:", ops01['constraints'])
|
||||
print("evalSummary:", ops01['evalSummary'])
|
||||
print("prerequisites:", ops01['prerequisites'])
|
||||
print("projectSkills:", ops01['projectSkills'])
|
||||
|
||||
print("\n=== Mission 1 ===")
|
||||
m = ops01['missions'][0]
|
||||
for k, v in m.items():
|
||||
print(f"--- {k} ---")
|
||||
print(v)
|
||||
@@ -1,50 +0,0 @@
|
||||
=== Project level fields ===
|
||||
id: OPS01
|
||||
title: 드론 영상 공중위협 객체 탐지와 오경보 검증
|
||||
category: 전장환경/적 탐지
|
||||
level: 3
|
||||
durationDays: 5
|
||||
requiredHours: 27.5
|
||||
summary: <h4>프로젝트에서 해결해야 하는 문제</h4><p>러시아-우크라이나 전쟁 전장 환경을 분석하여 드론 영상 및 EO/IR 센서 데이터 기반으로 저고도 공중위협(정찰·자폭드론, 순항미사일)을 탐지하고, 조류·민간기 등 오표적으로 인한 오경보를 검증·필터링하는 실전형 방공 탐지 및 결심 지원 체계를 구축한다.</p><h4>학습 목표</h4><p>드론 영상 수집 환경 분석부터 전처리, YOLO/규칙 기반 탐지 프로토타입 구현, 군집 드론 복합 위협 탐지 고도화, 신뢰도 점수 산출 엔진, 전술 대시보드(COP), 인간 개입(HITL) 교전 승인 워크플로우, 가상 스트레스 시뮬레이션 및 AAR 사후검토 피드백까지 전 과정을 아우르는 실전형 End-to-End 파이프라인을 완성한다.</p>
|
||||
objectives: <h3>소개</h3><p>러시아-우크라이나 전쟁 등 현대 전장에서 급증하는 드론 및 공중위협에 대응하여 드론 영상 객체 탐지와 오경보 검증 체계를 구현하고, 지휘관의 긴급 결심을 지원하는 실전형 방공 솔루션을 개발한다.</p><h4>학습자가 수행할 핵심 과정</h4><ul><li><p>전술 드론 영상 수집 환경 분석 및 데이터셋 스키마 정의</p></li><li><p>적외선·광학(EO/IR) 전장 영상 노이즈 제거 및 기상 왜곡 보정 전처리</p></li><li><p>YOLO 및 규칙 기반 저고도 공중위협 객체 탐지 Baseline 프로토타입 구현</p></li><li><p>군집 드론 및 비행체 기만 비행 패턴 식별 복합 위협 탐지 모델 고도화</p></li><li><p>조류·민간기 오표적 식별 방지 및 신뢰도 점수(Confidence Score) 산출 엔진 개발</p></li><li><p>실시간 공중위협 경보 및 방공 사격구역 가시화 전술 대시보드(COP) 구현</p></li><li><p>긴급 요격 결심 지원을 위한 인간 개입(HITL) 교전 승인/보류 워크플로우 연계</p></li><li><p>대규모 드론 포화 공격 상황 모의 가상 스트레스 시뮬레이션 및 복원력 검증</p></li><li><p>AAR 사후검토 기반 미탐·오탐 원인 분석 및 탐지 파라미터 역환류 보정</p></li><li><p>드론 공중위협 탐지 최종 파이프라인 통합 및 대드론 방어 브리핑 패키지 완성</p></li></ul>
|
||||
deliverables: ['data/schema/ops01_input_schema.json (필수)', 'docs/ops01_data_spec.md (필수)', 'src/preprocess/ops01_preprocessor.py (필수)', 'src/models/ops01_baseline_model.py (필수)', 'src/models/ops01_advanced_detector.py (필수)', 'src/verification/ops01_confidence_engine.py (필수)', 'src/ui/ops01_dashboard.py (필수)', 'src/hitl/ops01_approval_workflow.py (필수)', 'docs/ops01_hitl_guide.md (필수)', 'tests/ops01_stress_test.py (필수)', 'docs/ops01_aar_report.md (필수)', 'configs/ops01_tuned_params.yaml (필수)', 'src/main_ops01.py (필수)', 'README.md (필수)', 'docs/ops01_final_report.pdf(md) (필수)']
|
||||
constraints: <h4>AI 탐지 결과와 인간 지휘관 판단의 구분</h4><ul><li><p>AI 모델의 단독 판단만으로 요격 결심을 내리지 않으며, 반드시 인간 개입(HITL) 교전 승인 절차를 거친다.</p></li><li><p>오탐 및 미탐 가능성을 고려하여 다차원 신뢰도 점수(Confidence Score)와 불확실성 지표를 함께 제공한다.</p></li></ul><h4>군사 보안 및 실전 데이터 취급 원칙</h4><ul><li><p>실제 작전 기밀 및 아군 부대 위치/보직 정보는 사용하지 않으며, 가상 시뮬레이션 데이터셋을 활용한다.</p></li><li><p>러시아-우크라이나 전쟁 공개 사례를 기반으로 전장 환경을 모의하되 비인가 장비 제원은 사용하지 않는다.</p></li></ul><h4>시스템 신뢰성 및 안전 실패(Fail-safe) 보장</h4><ul><li><p>통신 장애나 센서 노이즈 등 극한 환경에서도 시스템 다운 없이 안전 실패(Fail-safe) 기준을 만족해야 한다.</p></li><li><p>전술 환경의 오경보율(FAR)을 통제하여 지휘관 및 전투원의 인지 부하를 최소화한다.</p></li></ul>
|
||||
evalSummary: <h4>공통 PASS/FAIL 판정 원칙</h4><ul><li><p><strong>수행 증거:</strong> 요구된 데이터 스키마, 전처리 파이프라인, 탐지 모델, UI, HITL 워크플로우, 테스트 코드가 정상 동작하는가?</p></li><li><p><strong>판단 근거:</strong> 오경보 필터링 및 신뢰도 점수 산출 기준이 군사 전술적/수학적 근거와 부합하는가?</p></li><li><p><strong>앞뒤 연결성:</strong> 데이터 스키마부터 전처리, 탐지, UI, HITL, AAR 피드백까지 파이프라인이 유기적으로 연동되는가?</p></li><li><p><strong>완성도:</strong> 정량적 평가 지표(정확도, 재현율, 처리 지연, 오경보율)와 정성적 평가 기준을 충족하는가?</p></li></ul><h4>프로젝트 완료 조건</h4><p>10개 필수 미션을 순차적으로 완수하여 최종 End-to-End 통합 파이프라인과 대드론 방어 브리핑 패키지를 완성하고 회귀/통합 테스트를 통과해야 최종 PASS이다.</p>
|
||||
prerequisites: Python 프로그래밍 기초, 컴퓨터 비전(OpenCV/YOLO) 및 딥러닝 기본 지식, 군사 전장 환경 및 드론 위협에 대한 기본 이해
|
||||
projectSkills: [{'skillId': 'competency_AI_문제해결역량_1._문제_분석', 'level': 3, 'category': 'AI 문제해결역량', 'subCategory': '1. 문제 분석'}, {'skillId': 'competency_AI_문제해결역량_2._대안_도출', 'level': 3, 'category': 'AI 문제해결역량', 'subCategory': '2. 대안 도출'}, {'skillId': 'competency_AI_비판적사고역량_1._결과_검증', 'level': 3, 'category': 'AI 비판적사고역량', 'subCategory': '1. 결과 검증'}, {'skillId': 'competency_AI활용역량_1._AI_도구_활용', 'level': 3, 'category': 'AI활용역량', 'subCategory': '1. AI 도구 활용'}, {'skillId': 'competency_AI활용역량_2._프롬프트_설계', 'level': 2, 'category': 'AI활용역량', 'subCategory': '2. 프롬프트 설계'}]
|
||||
|
||||
=== Mission 1 ===
|
||||
--- id ---
|
||||
OPS01_M01
|
||||
--- sequence ---
|
||||
1
|
||||
--- title ---
|
||||
전술 드론 영상 수집 환경 분석 및 공중위협 객체(드론/미사일) 데이터셋 스키마 정의
|
||||
--- durationHours ---
|
||||
1.5
|
||||
--- overview ---
|
||||
<p><strong>전술 드론 영상 수집 환경 분석 및 공중위협 객체(드론/미사일) 데이터셋 스키마 정의</strong></p><p>러시아-우크라이나 전쟁 전장 환경을 분석하여 드론 탑재 광학/적외선(EO/IR) 센서 규격, 공중위협 객체(정찰·자폭드론, 순항미사일) 메타데이터 및 바운딩 박스 라벨링 스키마를 설계하고 정의한다.</p>
|
||||
--- objectives ---
|
||||
<h3>소개</h3><p>러시아-우크라이나 전쟁 전장 환경을 바탕으로 전술 드론 영상 수집 환경 분석 및 공중위협 객체(드론/미사일) 데이터셋 스키마 정의 과제를 수행한다.</p><h4>학습자가 수행할 핵심 과정</h4><ul><li><p>러시아-우크라이나 전쟁 전장 환경을 분석하여 드론 탑재 광학/적외선(EO/IR) 센서 규격, 공중위협 객체(정찰·자폭드론, 순항미사일) 메타데이터 및 바운딩 박스 라벨링 스키마를 설계하고 정의한다.</p></li><li><p>산출물 작성: `data/schema/ops01_input_schema.json` 및 `docs/ops01_data_spec.md`</p></li><li><p>검증 및 완료: JSON Schema 검증 도구 및 샘플 데이터 파싱 단위 테스트 및 입력 데이터 유효성 검증 스크립트 통과 및 명세서 문서화 완료</p></li></ul>
|
||||
--- scenario ---
|
||||
<h4>미션 배경</h4><p>러시아-우크라이나 전쟁 전장 환경을 분석하여 드론 탑재 광학/적외선(EO/IR) 센서 규격, 공중위협 객체(정찰·자폭드론, 순항미사일) 메타데이터 및 바운딩 박스 라벨링 스키마를 설계하고 정의한다.</p><p><strong>전쟁/사례 구분:</strong> 러시아-우크라이나 전쟁</p><p><strong>학습 유형 및 복잡도:</strong> 코딩형 (난이도: 초급, 복잡도: 낮음, 진행률: 10%)</p><p><strong>산출물 및 저장 위치:</strong> `data/schema/ops01_input_schema.json` 및 `docs/ops01_data_spec.md`</p>
|
||||
--- attachments ---
|
||||
[]
|
||||
--- materials ---
|
||||
['data/schema/ops01_input_schema.json', 'docs/ops01_data_spec.md']
|
||||
--- materialText ---
|
||||
<p><strong>미션 산출물 및 저장 위치</strong></p><ul><li><p><code>`data/schema/ops01_input_schema.json` 및 `docs/ops01_data_spec.md`</code></p></li></ul><p><strong>검증 및 개선 방안</strong></p><ul><li><p>검증 방법: JSON Schema 검증 도구 및 샘플 데이터 파싱 단위 테스트</p></li><li><p>개선 및 피드백 방안: 누락된 전술 파라미터(기상, 위치오차, 통신지연 등) 보완</p></li></ul>
|
||||
--- techStack ---
|
||||
['전장환경', '적 탐지', '드론 영상', '컴퓨터 비전', 'Python']
|
||||
--- deliverables ---
|
||||
['data/schema/ops01_input_schema.json (필수)', 'docs/ops01_data_spec.md (필수)']
|
||||
--- evaluationKeys ---
|
||||
['<h4>평가 질문 (PASS/FAIL)</h4><p><strong>[정량적 평가] 스키마 필드 정의 완성도 100%, 샘플 데이터 검증 오류 0건</strong></p><h4>PASS 증거</h4><p>정량적 지표(스키마 필드 정의 완성도 100%, 샘플 데이터 검증 오류 0건)를 달성하고 단위 테스트 및 실행 결과가 성공적으로 확인되었다.</p><h4>보완이 필요한 상태</h4><p>정량적 성능 지표가 기준에 미달하거나 실행 중 에러가 발생하는 경우.</p><h4>보완 방법</h4><p>누락된 전술 파라미터(기상, 위치오차, 통신지연 등) 보완을 적용하여 성능 및 파라미터를 보정한다.</p>', '<h4>평가 질문 (PASS/FAIL)</h4><p><strong>[정성적 평가] 작전·전투 수행 실무 및 러시아-우크라이나 전쟁 전장 환경과의 군사적 부합성 기준을 충족하였는가?</strong></p><h4>PASS 증거</h4><p>러시아-우크라이나 전쟁 전장 환경 및 작전 실무 요구사항에 부합하는 알고리즘 논리와 타당성이 확보되었다.</p><h4>보완이 필요한 상태</h4><p>군사적 전술 환경에 대한 고려가 부족하거나 알고리즘 판단 기준이 모호한 경우.</p><h4>보완 방법</h4><p>전술 환경 변수와 군사적 판단 기준을 명세서 및 코드에 보강한다.</p>', '<h4>평가 질문 (PASS/FAIL)</h4><p><strong>[진행 추적 및 완료 기준] 입력 데이터 유효성 검증 스크립트 통과 및 명세서 문서화 완료을 완수하였는가?</strong></p><h4>PASS 증거</h4><p>요구된 산출물(`data/schema/ops01_input_schema.json` 및 `docs/ops01_data_spec.md`)이 지정된 경로에 생성되고 JSON Schema 검증 도구 및 샘플 데이터 파싱 단위 테스트를 통과하였다.</p><h4>보완이 필요한 상태</h4><p>산출물이 미완성이거나 검증 절차를 통과하지 못한 경우.</p><h4>보완 방법</h4><p>JSON Schema 검증 도구 및 샘플 데이터 파싱 단위 테스트 절차를 재수행하여 발견된 결함을 수정하고 문서를 완성한다.</p>']
|
||||
--- evaluationTypes ---
|
||||
['REQUIRED', 'REQUIRED', 'REQUIRED']
|
||||
--- evaluationText ---
|
||||
<h4>미션 판정</h4><p><strong>평가 구분:</strong> 필수</p><p><strong>난이도 / 복잡도:</strong> 초급 / 낮음 (진행률: 10%)</p><h4>미션 PASS 조건</h4><ul><li><p>정량적 기준: 스키마 필드 정의 완성도 100%, 샘플 데이터 검증 오류 0건</p></li><li><p>정성적 기준: 작전·전투 수행 실무 및 러시아-우크라이나 전쟁 전장 환경과의 군사적 부합성</p></li><li><p>진행 추적 및 완료: 입력 데이터 유효성 검증 스크립트 통과 및 명세서 문서화 완료</p></li><li><p>검증 방법 통과: JSON Schema 검증 도구 및 샘플 데이터 파싱 단위 테스트</p></li></ul>
|
||||
--- aiGuidelines ---
|
||||
{'allow': ['전장 환경 분석 및 데이터 전처리 알고리즘 가이드 요청', '단위 테스트 및 성능 검증 스크립트 작성 보조', '오경보 필터링 및 신뢰도 점수 산출 수식 검토'], 'rules': ['군사 기밀 및 실제 부대 정보는 절대 입력하지 않는다.', 'AI 탐지 결과에 대해 최종 인간 검토(HITL) 원칙을 유지한다.', '주어진 가상 시뮬레이션 데이터셋과 스키마 규격을 준수한다.'], 'forbid': ['실제 군사 작전 기밀 데이터 입력', '인간 검토 없는 무조건적인 자동 사격/요격 결심 로직 구현', '검증되지 않은 임의의 데이터 조작']}
|
||||
--- guideSteps ---
|
||||
[]
|
||||
@@ -1,23 +0,0 @@
|
||||
import json
|
||||
|
||||
with open('data/missions02.md', 'r', encoding='utf-8') as f:
|
||||
lines = [l.strip() for l in f if l.strip()]
|
||||
|
||||
headers = [h.strip() for h in lines[0].split('|')[1:-1]]
|
||||
print('Headers count:', len(headers))
|
||||
for i, h in enumerate(headers):
|
||||
print(f'{i}: {h}')
|
||||
|
||||
projects = {}
|
||||
for line in lines[2:]:
|
||||
parts = [p.strip() for p in line.split('|')[1:-1]]
|
||||
if len(parts) == len(headers):
|
||||
p_no = parts[0]
|
||||
p_id = parts[1]
|
||||
p_name = parts[2]
|
||||
if p_no not in projects:
|
||||
projects[p_no] = {'id': p_id, 'name': p_name, 'missions': []}
|
||||
projects[p_no]['missions'].append(dict(zip(headers, parts)))
|
||||
|
||||
for no, data in sorted(projects.items(), key=lambda x: int(x[0]) if x[0].isdigit() else 999):
|
||||
print(f"Project {no}: ID={data['id']}, Name={data['name']}, Mission count={len(data['missions'])}")
|
||||
@@ -1,769 +0,0 @@
|
||||
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!")
|
||||
@@ -1,887 +0,0 @@
|
||||
import json
|
||||
|
||||
path = r"d:/edunlab/milicontent2/work-pbl/DAT06.json"
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
missions = data["missions"]
|
||||
|
||||
# M01 guideSteps (4 steps)
|
||||
missions[0]["guideSteps"] = [
|
||||
{
|
||||
"step": 1,
|
||||
"problem": "군수 4대 결산(물자·부속·탄약·장비) 원천 데이터 항목 및 연계 구조 분석하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>군수 월간 결산 자동화를 성공적으로 구축하기 위해서는 물자 불출, 수리부속 소모, 탄약 정산, 장비 가동률 등 4대 핵심 분야의 원천 데이터 항목과 부대/군번/물자코드 간의 연계 구조를 명확히 이해해야 합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>물자 불출 대장, 수리부속 소모/청구 내역, 탄약 수불부, 장비 가동률 현황표의 원천 데이터 항목을 추출하고 필수 필드를 정의한다.</p></li>"
|
||||
"<li><p>부대 식별자, 군수 품목 코드(NSN), 결산 일자, 수량/단가 등 공통 연계 키(Key)를 도출한다.</p></li>"
|
||||
"<li><p>수작업 취합 시 누락이나 불일치가 자주 발생하는 취약 필드를 선별한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>4대 결산 영역별 필수 데이터 필드 목록 및 데이터 타입 정의표</p></li>"
|
||||
"<li><p>공통 연계 식별자 및 데이터 흐름 다이어그램</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>4대 결산 영역의 핵심 필드와 공통 식별자가 빠짐없이 도출되었는가?</p></li>"
|
||||
"<li><p>원천 데이터 간의 상호 참조 관계가 논리적으로 타당한가?</p></li>"
|
||||
"</ul>"
|
||||
"<h4>보완이 필요한 경우</h4>"
|
||||
"<ul>"
|
||||
"<li><p>특정 결산 영역(예: 탄약 수불 또는 장비 가동률)이 누락되었거나 연계 키 정의가 모호한 경우 보완한다.</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"problem": "현행(AS-IS) 수작업 결산 프로세스 10단계 분해 및 핵심 병목 3개소 도출하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>현재 실무에서 이루어지는 수작업 결산 절차를 세밀하게 분해하여 오집계가 발생하거나 시간이 지연되는 병목 지점을 정확히 진단합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>예하 부대 결산 제출부터 최종 사단/군단 보고서 완성까지의 현행 업무 절차를 10단계 이상으로 분해한다.</p></li>"
|
||||
"<li><p>서식 불일치 정제, 수작업 대차 계산, 이상 소모 검토 등 가장 지연이 심한 3개소 이상의 핵심 병목 구간을 도출한다.</p></li>"
|
||||
"<li><p>병목 구간별 평균 소요 시간과 오류 발생 원인을 정량적으로 정리한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>AS-IS 결산 업무 10단계 흐름도 및 단계별 소요 시간 분석</p></li>"
|
||||
"<li><p>3개소 이상의 핵심 병목 구간 및 원인 분석표</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>업무 단계가 10단계 이상 상세히 분해되고 3개소 이상의 병목 구간이 타당한 근거와 함께 제시되었는가?</p></li>"
|
||||
"</ul>"
|
||||
"<h4>보완이 필요한 경우</h4>"
|
||||
"<ul>"
|
||||
"<li><p>업무 분해가 지나치게 포괄적이거나 병목 원인 분석이 미흡한 경우 보완한다.</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"problem": "One-click 자동화(TO-BE) 아키텍처 및 표준 입출력 인터페이스 설계하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>식별된 병목을 해소하기 위해 데이터 수집부터 검증, 통계 산출, 이상치 감지, HITL 승인 연계로 이어지는 자동화 파이프라인의 청사진을 수립합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>수작업 개입을 최소화하는 One-click 자동화 파이프라인 모듈 구성을 설계한다.</p></li>"
|
||||
"<li><p>각 모듈 간 주고받을 표준 입출력 데이터 스키마(JSON/DataFrame 규격)를 정의한다.</p></li>"
|
||||
"<li><p>보안 및 권한 통제 기준을 반영한 데이터 접근 정책을 수립한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>TO-BE 자동화 아키텍처 블록 다이어그램</p></li>"
|
||||
"<li><p>모듈별 표준 입출력 데이터 인터페이스 명세서</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>전체 자동화 흐름이 단절 없이 연결되고 표준 입출력 규격이 명확히 정의되었는가?</p></li>"
|
||||
"</ul>"
|
||||
"<h4>보완이 필요한 경우</h4>"
|
||||
"<ul>"
|
||||
"<li><p>모듈 간 데이터 연계 방식이 불명확하거나 필수 인터페이스가 누락된 경우 보완한다.</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"problem": "AS-IS / TO-BE 결산 업무 정의서(`docs/dat06_as_is_to_be_spec.md`) 작성 및 검증하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>앞 단계에서 도출된 데이터 스키마, AS-IS 분석, TO-BE 아키텍처를 종합하여 실무 개발의 기준 문서인 명세서를 최종 완성합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>1~3단계의 분석 결과를 구조화된 마크다운 문서로 작성한다.</p></li>"
|
||||
"<li><p>실무 프로세스와의 부합성을 자체 검증 체크리스트로 확인한다.</p></li>"
|
||||
"<li><p>`docs/dat06_as_is_to_be_spec.md` 경로에 저장하고 문서 완성도를 점검한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>완성된 `docs/dat06_as_is_to_be_spec.md` 파일</p></li>"
|
||||
"<li><p>실무 일치성 자체 검증 결과서</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>지정된 산출물 경로에 명세서가 정상 생성되었으며 요구 규격을 100% 충족하였는가?</p></li>"
|
||||
"</ul>"
|
||||
"<h4>보완이 필요한 상태</h4>"
|
||||
"<ul>"
|
||||
"<li><p>산출물 파일이 누락되었거나 내용이 미완성인 경우 문서를 보완하여 재작성한다.</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
|
||||
# M02 guideSteps (3 steps)
|
||||
missions[1]["guideSteps"] = [
|
||||
{
|
||||
"step": 1,
|
||||
"problem": "비표준 이기종 엑셀 및 CSV 서식의 테이블 구조 자동 인식 파서 구현하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>부대별로 서로 다른 양식(헤더 위치 상이, 셀 병합, 다양한 인코딩 등)의 결산 문서를 사람의 수작업 없이 자동으로 인식하고 로드하는 파서를 개발합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>다양한 샘플 엑셀 파일에서 헤더 행의 위치를 동적으로 탐색하는 로직을 작성한다.</p></li>"
|
||||
"<li><p>병합된 셀의 값을 적절히 전파(Forward-fill)하고 비정형 공백을 정리한다.</p></li>"
|
||||
"<li><p>CSV 파일의 인코딩(UTF-8, CP949 등)을 자동 감지하여 오류 없이 로드한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>서식 자동 탐색 및 파싱 함수 코드</p></li>"
|
||||
"<li><p>샘플 파일 대상 파싱 성공 결과 로그</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>제공된 모든 샘플 파일 포맷(CSV, Excel)에서 파싱 성공률 100%를 달성하였는가?</p></li>"
|
||||
"</ul>"
|
||||
"<h4>보완이 필요한 경우</h4>"
|
||||
"<ul>"
|
||||
"<li><p>헤더 위치 불일치나 인코딩 오류로 파싱이 실패하는 케이스를 보완한다.</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"problem": "결측치 필터링, 포맷 교정 및 데이터 정합성 검증 로직 개발하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>파싱된 원천 데이터에 포함된 타입 오류, 특수문자, 결측치를 정제하고 표준 스키마에 맞게 교정합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>물자 코드, 수량, 금액, 날짜 필드의 결측치 및 비정상 문자열을 검증한다.</p></li>"
|
||||
"<li><p>음수 수량, 유효하지 않은 부대 코드 등 업무 논리적 오류를 식별하여 필터링/보정한다.</p></li>"
|
||||
"<li><p>전처리 전후 데이터 건수 및 정제율 통계를 산출한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>데이터 유효성 검증 및 정제 규칙 함수</p></li>"
|
||||
"<li><p>결측치 및 이상값 처리 전후 비교 통계표</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>정합성 검증 규칙이 엄격히 적용되어 전처리 오류율 0%를 만족하는가?</p></li>"
|
||||
"</ul>"
|
||||
"<h4>보완이 필요한 경우</h4>"
|
||||
"<ul>"
|
||||
"<li><p>비정상 입력값에 대한 예외 처리가 누락된 경우 정제 로직을 보강한다.</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"problem": "데이터 로더 파이프라인(`src/connectors/dat06_data_loader.py`) 완성 및 단위 테스트하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>파서와 전처리 로직을 단일 커넥터 모듈로 완성하고 자동화된 단위 테스트를 통해 신뢰성을 검증합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>`src/connectors/dat06_data_loader.py`에 표준 스키마 데이터프레임을 반환하는 클래스를 구현한다.</p></li>"
|
||||
"<li><p>pytest를 활용하여 다양한 예외 상황을 포함한 단위 테스트 케이스를 작성하고 실행한다.</p></li>"
|
||||
"<li><p>데이터 로더 모듈의 스키마 명세를 문서화한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>완성된 `src/connectors/dat06_data_loader.py` 파일</p></li>"
|
||||
"<li><p>단위 테스트 실행 결과 및 스키마 정의서</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>데이터 로더 단위 테스트 100% 통과 및 지정된 경로에 산출물이 생성되었는가?</p></li>"
|
||||
"</ul>"
|
||||
"<h4>보완이 필요한 경우</h4>"
|
||||
"<ul>"
|
||||
"<li><p>단위 테스트 실패 항목을 디버깅하고 코드를 수정한다.</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
|
||||
# M03 guideSteps (3 steps)
|
||||
missions[2]["guideSteps"] = [
|
||||
{
|
||||
"step": 1,
|
||||
"problem": "군수 규정 및 지침 기반 15종 표준 결산 룰셋 정의하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>물자 불출, 수리부속 소모, 탄약 정산, 장비 가동률 등 군수 관련 규정 및 회계 기준에 명시된 정산 수식을 엄격히 분석하여 룰셋으로 정형화합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>물자별 소모 한도 및 불출 집계 수식을 수립한다.</p></li>"
|
||||
"<li><p>탄약 불출/소모/반납 정산 및 수리부속 정비비용 산정식을 정의한다.</p></li>"
|
||||
"<li><p>장비 가동률(가동 장비수 / 인가 장비수) 산출 공식을 포함하여 총 15종 이상의 업무 룰을 명세화한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>15종 이상의 표준 결산 룰 명세서(룰 ID, 입력 필드, 연산 공식, 조건식)</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>군 규정 및 지침 조항과 완벽히 부합하는 15종 이상의 결산 룰이 명세화되었는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"problem": "50ms 이하 고속 집계 및 통계 산출 Baseline 엔진 구현하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>정의된 15종 룰셋을 효율적인 알고리즘으로 코딩하여 대량의 결산 데이터를 50ms 이내에 초고속으로 집계·산출하는 엔진을 구현합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>벡터화 연산(Vectorized Operation)을 적용하여 대량 데이터 집계 성능을 극대화한다.</p></li>"
|
||||
"<li><p>부대별/품목별 월간 결산 요약 통계 지표를 일괄 산출하는 파이프라인을 코딩한다.</p></li>"
|
||||
"<li><p>룰 처리 소요 시간을 측정하여 50ms 이하 기준을 만족하는지 확인한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>Baseline 룰 연산 엔진 소스 코드</p></li>"
|
||||
"<li><p>처리 지연시간(ms) 및 벤치마크 측정 결과서</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>모든 룰 연산이 정확하게 수행되며 룰 처리 지연시간이 50ms 이하인가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"problem": "설정 분리 아키텍처(YAML/JSON) 적용 및 `src/engine/dat06_rule_engine.py` 단위 테스트하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>군수 규정 변경 시 코드 수정 없이 룰을 즉시 갱신할 수 있도록 설정을 분리하고 경계값 단위 테스트를 완수합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>룰 파라미터와 임계값을 외부 설정 파일(YAML/JSON)로 분리하고 로더를 연동한다.</p></li>"
|
||||
"<li><p>0값, 음수, 극단적 수치 등 경계 조건(Boundary condition)에 대한 룰 판정 단위 테스트를 작성한다.</p></li>"
|
||||
"<li><p>`src/engine/dat06_rule_engine.py` 산출물을 완성하고 100% 테스트 통과를 검증한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>완성된 `src/engine/dat06_rule_engine.py` 파일 및 외부 룰 설정 파일</p></li>"
|
||||
"<li><p>경계 조건 단위 테스트 100% 통과 결과 로그</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>설정 파일 변경만으로 룰이 정상 갱신되며 단위 테스트 100% 통과하였는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
|
||||
# M04 guideSteps (3 steps)
|
||||
missions[3]["guideSteps"] = [
|
||||
{
|
||||
"step": 1,
|
||||
"problem": "시계열 소비 패턴 및 Z-Score/이동평균 기반 이상치 자동 탐지 알고리즘 구현하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>과거 시계열 소모 이력과 부대별 평균 기준선을 바탕으로 전월 대비 급증·급감 소모 및 이상 징후를 통계적으로 자동 식별합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>부대별·물자별 과거 3~6개월 소비 데이터의 이동평균(MA) 및 표준편차를 계산한다.</p></li>"
|
||||
"<li><p>Z-Score 및 IQR(사분위수 범위) 기반으로 임계치를 초과하는 급증 소모 항목을 자동 추출한다.</p></li>"
|
||||
"<li><p>오탐률을 낮추기 위한 동적 이상치 스코어링 수식을 적용한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>시계열 이상치 탐지 알고리즘 코드</p></li>"
|
||||
"<li><p>이상치 판정 임계값 설정 및 탐지 결과 리포트</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>전월 대비 급증 항목 및 이상 소모가 누락 없이 100% 감지되는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"problem": "정황 데이터(훈련/정비 일정) 결합 원인 요약 및 조치 방안(Top-3) 추천 모델링하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>탐지된 이상 항목에 야전 훈련 일정, 장비 정비 이력 등 맥락 데이터를 결합하여 이상 원인을 자동 분류하고 실무 조치 가이드를 추천합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>훈련 일정 데이터와 급증 항목을 매핑하여 정당한 사유(야외 기동훈련 등)와 비인가 의심 사례를 분류한다.</p></li>"
|
||||
"<li><p>유사도 기반 알고리즘을 활용하여 실무자가 즉시 적용 가능한 조치 방안 Top-3를 생성한다.</p></li>"
|
||||
"<li><p>추천 사유(Rationale)를 명확한 문장으로 자동 요약하는 텍스트 생성 로직을 개발한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>정황 데이터 매핑 및 원인 분류 모델 코드</p></li>"
|
||||
"<li><p>이상 항목별 Top-3 추천 결과 및 추천 사유 요약문 샘플</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>원인 분류 및 추천 사유가 군수 실무적으로 타당하고 설명 가능한가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"problem": "AI 추론 모듈(`src/ai/dat06_intelligence_module.py`) 완성 및 벤치마크 검증하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>이상 탐지 및 추천 엔진을 통합 모듈로 완성하고 과거 승인 데이터셋을 대상으로 85% 이상의 정확도를 검증합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>`src/ai/dat06_intelligence_module.py`에 이상치 감지 및 추천 파이프라인을 클래스로 구현한다.</p></li>"
|
||||
"<li><p>과거 승인/처리 완료된 결산 데이터셋 벤치마크 테스트를 수행한다.</p></li>"
|
||||
"<li><p>추천/분류 정확도 85% 이상 달성 여부를 확인하고 성능 지표를 산출한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>완성된 `src/ai/dat06_intelligence_module.py` 파일</p></li>"
|
||||
"<li><p>과거 데이터 대상 벤치마크 성능 평가서(정확도 85% 이상 증빙)</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>추천/분류 정확도 85% 이상 달성 및 지정된 경로에 산출물이 생성되었는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
|
||||
# M05 guideSteps (3 steps)
|
||||
missions[4]["guideSteps"] = [
|
||||
{
|
||||
"step": 1,
|
||||
"problem": "복식부기 및 국방 회계 원칙 기반 대차/수불 일관성 검증 로직 구현하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>산출된 군수 결산 수치에 대해 기초재고 + 입고 - 출고 = 기말재고의 수불 정합성과 대차 일관성을 엄격히 자동 검증합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>물자 및 부속 수불부의 기초, 입고, 출고, 기말 재고 간 수량 및 금액 일치성을 계산하는 수식을 작성한다.</p></li>"
|
||||
"<li><p>부대별 예산 배정액 대비 집행 누계액 초과 여부를 자동 검사한다.</p></li>"
|
||||
"<li><p>대차 불일치나 재고 오차가 발생한 항목의 원인 필드를 역추적(Traceback)하는 기능을 구현한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>회계 대차 일관성 및 재고 수불 검증 로직 코드</p></li>"
|
||||
"<li><p>불일치 사례 역추적 테스트 결과 보고서</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>회계적 일관성 검증 수식이 수학적/실무적으로 정확하며 불일치 항목을 100% 포착하는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"problem": "다차원 신뢰도 점수 산출 및 안전 실패(Fail-safe) 에러 핸들러 구현하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>데이터의 완전성, 룰 부합성, 이상치 분석을 결합하여 신뢰도 점수를 산출하고 비정상 데이터 입력 시에도 무중단 안전 실패를 보장합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>데이터 완전성(Completeness), 일관성(Consistency), 타당성(Validity)을 가중 합산하는 신뢰도 점수(0~100점) 산출 엔진을 개발한다.</p></li>"
|
||||
"<li><p>비정상 데이터(결측치, 극단치, 손상된 포맷) 주입 시 크래시 없이 안전하게 에러를 포착하는 핸들러를 구축한다.</p></li>"
|
||||
"<li><p>모든 예외 상황 발생 시 100% 에러 감사 로그를 기록하도록 연동한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>신뢰도 점수 산출 알고리즘 및 에러 핸들러 코드</p></li>"
|
||||
"<li><p>예외 주입 시 크래시 0건 및 에러 로그 기록 증빙</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>예외 주입 시 시스템 크래시 0건 및 에러 로그 기록률 100%를 달성하였는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"problem": "검증 모듈(`src/utils/dat06_validator.py`) 완성 및 예외 10종 스트레스 검증하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>검증기와 감사 로깅 모듈을 완성하고 10종의 비정상 데이터 주입 스트레스 테스트를 통해 견고성을 최종 검증합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>`src/utils/dat06_validator.py`에 검증 및 로깅 통합 클래스를 완성한다.</p></li>"
|
||||
"<li><p>10종의 예외 시나리오(특수문자, 대용량 결손, 타임아웃 등)를 작성하고 스트레스 테스트를 수행한다.</p></li>"
|
||||
"<li><p>생성된 감사 로그 파일의 무결성과 알기 쉬운 에러 메시지 제공 여부를 확인한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>완성된 `src/utils/dat06_validator.py` 파일</p></li>"
|
||||
"<li><p>예외 주입 테스트 10종 통과 결과서 및 감사 로그 파일</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>예외 주입 테스트 10종 통과 및 감사 로그 파일 생성이 완료되었는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
|
||||
# M06 guideSteps (3 steps)
|
||||
missions[5]["guideSteps"] = [
|
||||
{
|
||||
"step": 1,
|
||||
"problem": "군수 결산 대시보드 핵심 인터랙티브 화면 4종 레이아웃 및 뷰 구현하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>군수 실무자가 직관적으로 업무를 수행할 수 있도록 4종의 핵심 화면(데이터 입력·취합, 결산 통계 분석, 이상치 상세 검토, 최종 보고서 및 결재)을 개발합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>결산 파일 드래그앤드롭 업로드 및 취합 상태를 보여주는 1화면을 구성한다.</p></li>"
|
||||
"<li><p>월간 결산 집계 통계와 주요 지표를 보여주는 2화면(분석 대시보드)을 제작한다.</p></li>"
|
||||
"<li><p>AI 이상 탐지 결과 및 추천 가이드를 확인하는 3화면(이상치 검토)을 구현한다.</p></li>"
|
||||
"<li><p>최종 보고서 미리보기 및 결재 상신 버튼을 갖춘 4화면(보고서 뷰어)을 완성한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>핵심 화면 4종의 UI 레이아웃 및 뷰 소스 코드</p></li>"
|
||||
"<li><p>화면별 구동 스크린샷</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>핵심 화면 4종(입력-분석-검토-결과)이 누락 없이 구현되고 정상 동작하는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"problem": "실시간 시각화 차트 및 승인 상태별(대기, 승인, 반려) 필터링 기능 연동하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>결산 데이터를 시각적으로 쉽게 분석할 수 있도록 대화형 차트와 다차원 필터링 및 상태 뱃지 인터랙션을 구현합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>물자/부속 소모 추이 시계열 차트와 장비 가동률 게이지 컴포넌트를 연동한다.</p></li>"
|
||||
"<li><p>부대별, 결산 항목별, 승인 상태별(승인대기, 반려, 완료) 동적 필터링 기능을 개발한다.</p></li>"
|
||||
"<li><p>상태별 색상 뱃지(Badge)를 적용하여 시인성을 극대화한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>시각화 차트 및 필터링 컴포넌트 코드</p></li>"
|
||||
"<li><p>동적 필터 조작에 따른 데이터 갱신 시연 결과</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>군 실무자 관점에서 직관적이고 편의성 높은 시각화 및 필터링이 구현되었는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"problem": "One-click 보고서 렌더링 연동 및 `src/ui/dat06_app.py` 로컬 구동 테스트하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>단추 클릭 한 번으로 군 표준 결산 보고서를 생성·다운로드하는 기능을 완성하고 전체 웹 UI를 통합 검증합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>표준 군수 결산 보고서 양식에 맞춘 HTML/PDF/Excel 렌더링 엔진을 연동한다.</p></li>"
|
||||
"<li><p>`src/ui/dat06_app.py` 애플리케이션을 로컬 서버로 구동한다.</p></li>"
|
||||
"<li><p>전체 페이지 네비게이션 및 UX Walkthrough 테스트를 완료한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>완성된 `src/ui/dat06_app.py` 파일</p></li>"
|
||||
"<li><p>One-click 생성된 결산 보고서 샘플 파일 및 UI 구동 확인서</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>UI 프로토타입 로컬 서버 구동 및 전체 페이지 네비게이션이 오류 없이 확인되었는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
|
||||
# M07 guideSteps (3 steps)
|
||||
missions[6]["guideSteps"] = [
|
||||
{
|
||||
"step": 1,
|
||||
"problem": "실무 담당관 → 보급관 → 군수과장 3단계 HITL 전자 결재 워크플로우 엔진 구현하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>자동 생성된 결산 보고서가 임의로 발간되지 않도록 담당관 기안부터 보급관 1차 검토, 군수과장 최종 결재로 이어지는 3단계 승인 파이프라인을 설계합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>결재 문서의 상태 머신(State Machine: 작성중 -> 검토대기 -> 결재대기 -> 최종승인 / 반려)을 코딩한다.</p></li>"
|
||||
"<li><p>각 단계별 승인 시 전자 서명 해시 및 타임스탬프를 생성하여 결재 데이터에 첨부한다.</p></li>"
|
||||
"<li><p>다음 승인권자에게 작업이 자동 전달되는 순차 파이프라인을 구현한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>3단계 결재선 상태 머신 및 승인 엔진 소스 코드</p></li>"
|
||||
"<li><p>단계별 승인 진행 상태 전이 다이어그램</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>3단계 순차 승인 상태 전이가 군 행정 절차에 부합하게 정확히 동작하는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"problem": "역할 기반 접근 제어(RBAC) 및 비인가 권한 우회 차단 로직 개발하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>계급 및 직책에 따른 접근 권한(조회, 수정, 승인, 반려)을 철저히 분리하여 비인가자의 승인 조작을 원천 차단합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>사용자 역할(담당자, 보급관, 군수과장, 관리자)별 권한 매트릭스를 정의한다.</p></li>"
|
||||
"<li><p>승인 API 호출 시 호출자의 세션과 권한을 검증하여 비인가 권한 우회 시도를 100% 차단한다.</p></li>"
|
||||
"<li><p>비인가 접근 시도 시 즉각 차단하고 보안 감사 로그를 기록한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>RBAC 권한 검증 미들웨어 및 데코레이터 코드</p></li>"
|
||||
"<li><p>비인가 권한 우회 시도 차단 테스트 결과(차단율 100% 증빙)</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>승인 권한 우회 시도 차단율 100% 및 이력 기록 무결성을 달성하였는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"problem": "반려 사유 피드백, 재기안 워크플로우(`src/workflow/dat06_approval_system.py`) 완성 및 시뮬레이션하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>반려 시 구체적인 사유를 입력하고 보정 데이터를 반영하여 다시 기안하는 순환 워크플로우를 완성하고 전체 결재 시스템을 검증합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>반려 시 필수 사유 입력 폼과 수정 요청 사항 피드백 로직을 구현한다.</p></li>"
|
||||
"<li><p>반려된 문서의 버전 관리 및 재기안(Re-submit) 워크플로우를 연동한다.</p></li>"
|
||||
"<li><p>`src/workflow/dat06_approval_system.py`를 완성하고 승인·반려·재기안 전체 시뮬레이션을 수행한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>완성된 `src/workflow/dat06_approval_system.py` 파일</p></li>"
|
||||
"<li><p>결재선 승인/반려 시뮬레이션 및 이력 조회 실행 로그</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>결재선 승인/반려 시뮬레이션 및 이력 조회 화면 구현이 완료되었는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
|
||||
# M08 guideSteps (3 steps)
|
||||
missions[7]["guideSteps"] = [
|
||||
{
|
||||
"step": 1,
|
||||
"problem": "멀티프로세싱/비동기 기반 다수 부대 병렬 결산 배치 러너 구현하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>수십 개 예하 부대의 방대한 결산 원천 데이터를 일괄 수집하고 병렬로 파싱·집계·보고서 생성까지 연속 수행하는 배치 실행기를 개발합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>Python 멀티프로세싱 또는 비동기 I/O를 활용하여 부대별 결산 작업을 병렬 큐(Queue)로 분배한다.</p></li>"
|
||||
"<li><p>개별 부대 처리 실패 시 전체 배치가 중단되지 않는 결함 격리(Fault Isolation) 로직을 적용한다.</p></li>"
|
||||
"<li><p>처리 결과를 일괄 취합하여 사단급 총괄 결산 요약표를 자동 렌더링한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>병렬 배치 처리 실행기 코어 소스 코드</p></li>"
|
||||
"<li><p>다수 부대 일괄 처리 흐름도</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>수십 개 부대 결산 데이터가 병렬로 오류 없이 일괄 처리되는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"problem": "스케줄러 연동, 실시간 진행률(Progress Bar) 표시 및 자동 알림 연동하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>정기 결산 일시에 맞추어 배치를 자동 실행하는 스케줄러와 실시간 진행률 및 완료 알림 기능을 구현합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>스케줄러(cron 또는 스케줄링 라이브러리)를 연동하여 매월 정기 자동 실행을 구성한다.</p></li>"
|
||||
"<li><p>대시보드 및 CLI 상에 실시간 작업 진행률(0~100%)과 완료 예상 시간을 표시한다.</p></li>"
|
||||
"<li><p>배치 완료 또는 실패 시 실무자에게 즉각 알림(이메일/웹훅/로그)을 발송하도록 연동한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>스케줄러 설정 및 진행률/알림 연동 모듈 코드</p></li>"
|
||||
"<li><p>스케줄러 자동 트리거 및 진행률 표시 실행 화면</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>스케줄러 연동 테스트 및 자동 실행 확인이 정상 완료되었는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"problem": "1년치 데이터 투입 스트레스 테스트 수행 및 `src/pipeline/dat06_batch_runner.py` 완성하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>실제 1년치 대용량 결산 데이터를 가상 투입하는 극한의 스트레스 테스트를 통해 5분 이내 처리 및 99% 이상 자동화 성공률을 검증합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>수십 개 부대의 1년치(12개월) 군수 데이터셋을 가상 생성하여 배치에 투입한다.</p></li>"
|
||||
"<li><p>전체 처리 시간, CPU/메모리 점유율, 자동화 성공률을 정밀 측정한다.</p></li>"
|
||||
"<li><p>`src/pipeline/dat06_batch_runner.py`를 완성하고 수작업 대비 80% 이상의 시간 절감 효과를 산출한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>완성된 `src/pipeline/dat06_batch_runner.py` 파일</p></li>"
|
||||
"<li><p>스트레스 테스트 결과 벤치마크 리포트(처리 시간 5분 이내, 성공률 99% 이상 증빙)</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>전체 배치 실행 시간 5분 이내, 자동화 성공률 99% 이상, 수작업 대비 80% 시간 절감을 달성하였는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
|
||||
# M09 guideSteps (3 steps)
|
||||
missions[8]["guideSteps"] = [
|
||||
{
|
||||
"step": 1,
|
||||
"problem": "UAT(사용자 수용성 테스트) 시나리오 수행 및 오집계/예외 사례 AAR 분석하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>실무 시나리오 기반 UAT를 통해 현장 운용 시 발생할 수 있는 오집계, 비정형 서식 충돌 사례를 사후검토(AAR) 기법으로 정밀 분석합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>실제 결산 담당관, 보급관 페르소나를 적용한 UAT 시나리오 10종을 실행한다.</p></li>"
|
||||
"<li><p>수치 불일치, 템플릿 깨짐, 조작 불편 등 발견된 오류 사례를 수집한다.</p></li>"
|
||||
"<li><p>AAR(계획된 목표 vs 실제 결과 vs 차이 원인 vs 교훈) 4단계 분석표를 작성한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>UAT 시나리오 실행 결과표 및 발견된 결함 목록</p></li>"
|
||||
"<li><p>AAR 기반 원인 분석 및 교훈 정리서</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>UAT 결함 사례에 대해 AAR 방법론을 적용한 심층 분석이 타당하게 수행되었는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"problem": "결산 룰셋 파라미터 보정 및 실무자 개선 요구 5건 이상 반영 패치하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>AAR에서 도출된 취약점을 해소하기 위해 룰 엔진 파라미터를 보정하고 실무 편의 개선 요구사항 5건을 시스템에 패치합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>오집계를 유발한 룰 엔진의 경계값 및 연산 파라미터를 수정·패치한다.</p></li>"
|
||||
"<li><p>실무자 피드백(단축키 지원, 자주 쓰는 템플릿 저장, 보고서 서식 미세 조정 등) 5건 이상을 UI 및 엔진에 반영한다.</p></li>"
|
||||
"<li><p>수정 사항에 대한 회귀 테스트를 수행하여 기존 기능이 정상 동작하는지 확인한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>룰셋 및 템플릿 보정 패치 코드(Diff 또는 패치 로그)</p></li>"
|
||||
"<li><p>개선 요구 5건 반영 내역 및 회귀 테스트 결과서</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>개선 요구사항 5건 이상이 정상 반영되고 회귀 오류가 없는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"problem": "UAT 결과 보고서(`docs/dat06_uat_feedback_report.md`) 작성 및 최종 검수 완료하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>개선된 시스템에 대해 최종 UAT 검수를 진행하고 만족도 90점 이상을 달성한 결과 보고서를 완성합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>패치 완료된 시스템을 대상으로 최종 사용자 수용성 테스트를 재실시한다.</p></li>"
|
||||
"<li><p>실무 만족도 설문 및 평가 지표(90점 이상 달성)를 취합한다.</p></li>"
|
||||
"<li><p>`docs/dat06_uat_feedback_report.md`에 AAR 분석, 패치 내역, UAT 최종 결과를 정리하여 저장한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>완성된 `docs/dat06_uat_feedback_report.md` 파일</p></li>"
|
||||
"<li><p>UAT 체크리스트 만족도 90점 이상 평가 결과서</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>UAT 체크리스트 만족도 90점 이상 달성 및 결과 보고서 작성이 완료되었는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
|
||||
# M10 guideSteps (3 steps)
|
||||
missions[9]["guideSteps"] = [
|
||||
{
|
||||
"step": 1,
|
||||
"problem": "9개 핵심 모듈의 End-to-End 전체 파이프라인 통합 및 패키지 빌드하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>전처리 데이터 로더, 룰 엔진, AI 모듈, 검증기, UI 대시보드, HITL 결재, 배치 러너를 단일 시스템으로 연동하고 배포 패키지를 빌드합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>모든 서브모듈의 인터페이스를 연결하는 메인 엔트리포인트(`main.py` 등)를 통합 구현한다.</p></li>"
|
||||
"<li><p>`dist/dat06_package/` 디렉토리에 실행 파일, 소스, 리소스, 설정 파일을 포함한 독립 실행 패키지를 빌드한다.</p></li>"
|
||||
"<li><p>모듈 간 데이터 파이프라인이 단절 없이 One-click으로 구동되는지 통합 점검한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>통합 파이프라인 메인 엔트리포인트 소스 코드</p></li>"
|
||||
"<li><p>`dist/dat06_package/` 빌드 결과물</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>전체 모듈이 결함 없이 통합되고 배포 패키지가 정상 생성되었는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"problem": "원클릭 설치 스크립트 및 종합 운영 매뉴얼(`README.md`, `docs/dat06_final_manual.md`) 작성하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>사용자가 복잡한 환경 설정 없이 원클릭으로 설치 및 실행할 수 있도록 스크립트와 단계별 운영 매뉴얼을 작성합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>의존성 패키지 설치 및 초기 데이터베이스 설정을 자동 수행하는 원클릭 설치 스크립트를 작성한다.</p></li>"
|
||||
"<li><p>`README.md`에 프로젝트 개요, 시스템 요구사항, 빠른 시작 가이드를 명시한다.</p></li>"
|
||||
"<li><p>`docs/dat06_final_manual.md`에 관리자 설정, 결산 보고서 생성, 전자 결재 등 상세 실무자 가이드를 문서화한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>원클릭 설치 스크립트 파일</p></li>"
|
||||
"<li><p>완성된 `README.md` 및 `docs/dat06_final_manual.md` 파일</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>설치부터 실행까지 원클릭 설치 스크립트 완비 및 매뉴얼 작성률 100%를 달성하였는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"problem": "Clean 환경 설치·초기화 테스트 및 최종 배포본 종합 검수 완료하기",
|
||||
"activity": (
|
||||
"<h4>이 단계의 역할</h4>"
|
||||
"<p>개발 환경의 잔재가 없는 깨끗한(Clean) 가상환경에서 배포본을 설치하고 End-to-End 전체 결산 워크플로우 회귀 테스트를 완수합니다.</p>"
|
||||
"<h4>해야 할 일</h4>"
|
||||
"<ol>"
|
||||
"<li><p>신규 가상환경에서 원클릭 설치 스크립트를 실행하여 무오류 설치 및 초기화를 확인한다.</p></li>"
|
||||
"<li><p>샘플 데이터를 투입하여 파싱 -> 집계 -> AI 이상탐지 -> 회계검증 -> 웹 UI 조회 -> 3단계 HITL 결재 -> One-click 보고서 발간까지 전 과정 회귀 테스트를 수행한다.</p></li>"
|
||||
"<li><p>상용 소프트웨어 수준의 완성도를 최종 검수하고 프로젝트를 완료한다.</p></li>"
|
||||
"</ol>"
|
||||
"<h4>학습자가 제출해야 하는 내용</h4>"
|
||||
"<ul>"
|
||||
"<li><p>Clean 환경 설치 및 실행 테스트 결과 로그</p></li>"
|
||||
"<li><p>전체 파이프라인 End-to-End 회귀 테스트 100% 통과 증빙서</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"evalText": (
|
||||
"<h4>PASS 기준</h4>"
|
||||
"<ul>"
|
||||
"<li><p>Clean 환경에서의 설치 및 초기화 테스트 100% 통과 및 최종 배포본 검수가 완료되었는가?</p></li>"
|
||||
"</ul>"
|
||||
),
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print("Populated guideSteps for all 10 missions in DAT06.json successfully!")
|
||||
@@ -1,13 +0,0 @@
|
||||
import glob
|
||||
import json
|
||||
|
||||
files = sorted(glob.glob('import/*.json'))
|
||||
print(f"Total JSON files in import: {len(files)}")
|
||||
for f in files:
|
||||
with open(f, 'r', encoding='utf-8') as fp:
|
||||
data = json.load(fp)
|
||||
p_id = data.get("id")
|
||||
p_title = data.get("title")
|
||||
m_count = len(data.get("missions", []))
|
||||
req_hours = data.get("requiredHours")
|
||||
print(f"[{p_id}] {f}: {p_title} | Missions: {m_count} | Hours: {req_hours}h")
|
||||
Reference in new issue
Block a user