{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# ๐ฏ [OPS04] ์คํค๋ยท๋ฏธ์ฌ์ผ ํฌํ๊ฒฝ๋ณด ์ฐ์ ์์ ๋์๋ณด๋ ๋ชจ์ ๊ตฌํ\n",
"### Google Colab ์ค์ต ํ๊ฒฝ (์์ ยท์ ํฌ ์ํ / Level 3)\n",
"\n",
"[](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/OPS04/OPS04_solution.ipynb)\n",
"\n",
"---\n",
"\n",
"## ๐ 1. ํ๋ก์ ํธ ๊ฐ์ ๋ฐ ๋ฐฐ๊ฒฝ\n",
"\n",
"| ํญ๋ชฉ | ๋ด์ฉ |\n",
"| :--- | :--- |\n",
"| **๊ณผ์ ๋ถ๋ฅ** | ์์ ยท์ ํฌ ์ํ (Level 3) |\n",
"| **ํ์ต ์์ ์๊ฐ** | 10๊ฐ ๋ฏธ์
(5์ผ / ์ด 27.5์๊ฐ) |\n",
"| **ํ๊ฒฝ ์ฌ์** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n",
"\n",
"### ๐ ํ๋ก์ ํธ ๋ชฉํ\n",
"๋ฌ์์-์ฐํฌ๋ผ์ด๋ ์ ์์์๋ ์ ์ ์ํญ๋ฌด์ธ๊ธฐ์ ๊ณ ์ ์ํญํ ๋นํ์ฒด๊ฐ ์ฌ๋ฌ ๋ฐฉํฅ์์ ๋์์ ๋ค์ด์ค๋ ํฌํ ๊ณต๊ฒฉ์ด ๋ฐ๋ณต๋๋ฉด์, ์งง์ ์๊ฐ ์์ ์ด๋ค ํญ์ ์ ๋จผ์ ๋ณผ์ง ์ ํ๋ ๋ฐฉ๊ณต ์ํฉํ๋จ์ ๋ถ๋ด์ด ์ปค์ก๋ค. ๋ณธ ํ๋ก์ ํธ์์๋ ๊ฐ์ ๋ฐฉ์ด์ง์ญ๊ณผ ๊ฐ์ ์์ฐ, ์ถ์ ๋ฒ์ฃผ์ ๊ฐ์ ๋นํ์ฒด๋ฅผ ์ฝ๋๋ก ๋ง๋ค์ด ๊ฒฝ๋ณด ๋ฐ์ดํฐ ๊ท๊ฒฉ ์ ์, ๋ค์ค ์ผ์ ๋ฉ์์ง ์ ์ฒ๋ฆฌ, ๊ฑฐ๋ฆฌยท๋๋ฌ ์์์๊ฐ(TTI) ๊ธฐ๋ฐ Baseline ์์, ์์ฐ ๊ฐ์นยท์ํ ์ ์ฌ๋๋ฅผ ๊ฒฐํฉํ ๋ค์ฐจ์ ํ๊ฐ, ์ ๋ขฐ๋ ๋ฑ๊ธ๊ณผ ๊ถ๊ณ ๋์ ์์, ์งํ๊ด์ฉ ๋์๋ณด๋, ์ธ๊ฐ ๊ฐ์
(HITL) ์น์ธ ์ ์ฐจ, ํฌํ ๋ถํ ์ํ, AAR ์ฌ๋ณด์ , ์ต์ข
ํตํฉ๊น์ง ๋จ๊ณ๋ณ๋ก ๊ตฌํํ๋ค. ์ํ ์ ์๋ ์ค์ ๋ฌด๊ธฐ ์ ์์ด ์๋ ๊ฑฐ๋ฆฌยท๋๋ฌ์๊ฐยท์์ฐ ๊ฐ์น ๊ธฐ๋ฐ์ ์ถ์ ๋ชจ๋ธ๋ก๋ง ๊ณ์ฐํ๋ค. ์ต์ข
๋ชฉํ๋ ๊ธด๊ธ ํ์ ์ ๋์น์ง ์์ผ๋ฉด์ ๊ธฐ๋ง์ฒดยท์คํธํ์ ์ํ ์ค๊ฒฝ๋ณด๋ฅผ ์ค์ด๊ณ , ๋ชจ๋ ๋์ ์์๊ฐ '๊ถ๊ณ '๋ก์ ์งํ๊ด ์น์ธ์ ๊ฑฐ์น๋๋ก ์ค๊ณํ๋ ๊ฒ์ด๋ค.\n",
"\n",
"> **โ ๏ธ [๊ต์ก์ฉ ๊ฐ์ ๋ฐ์ดํฐ์
์์น]**\n",
"> - ๋ณธ ๋
ธํธ๋ถ์ ๋ชจ๋ ๋ฐ์ดํฐ(์ ์, ๋ก๊ทธ, ์๋๋ฆฌ์ค ๋ฑ)๋ ๋ณด์ ๊ท์ ์ ์ค์ํ์ฌ **์ฝ๋ ๋ด ๋์ ๊ธฐ๋ฐ ๊ฐ์ ๋ฐ์ดํฐ๋ก ์์ฑ**๋ฉ๋๋ค.\n",
"> - ์ค์ ๋ถ๋ ์์น, ์์ ์ธ๊ฐ, ์ค์ ์ ์ฅ ์ ๋ณด, ๊ตฐ์ฌ ๊ธฐ๋ฐ ๋ฐ์ดํฐ๋ ์ผ์ฒด ํฌํจ๋์ง ์์ต๋๋ค.\n",
"\n",
"---\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ============================================================================\n",
"# [Google Colab ํ๊ฒฝ ์ค์ ] ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น ๋ฐ ์ํฌํธ\n",
"# ============================================================================\n",
"!pip install -q numpy pandas matplotlib scipy scikit-learn\n",
"\n",
"import os\n",
"import sys\n",
"import time\n",
"import math\n",
"import json\n",
"import re\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import warnings\n",
"warnings.filterwarnings('ignore')\n",
"\n",
"# ๊ทธ๋ํ ์๋ฌธ ํฐํธ ๊ธฐ๋ณธ ์ค์ (ํ๊ฒฝ ๋
๋ฆฝ์ฑ ๋ณด์ฅ)\n",
"plt.rcParams['font.sans-serif'] = 'DejaVu Sans'\n",
"plt.rcParams['axes.unicode_minus'] = False\n",
"\n",
"print(f\"์คํ ํ๊ฒฝ ์ค๋น ์๋ฃ (Python {sys.version.split()[0]})\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
01. ์คํค๋ ์ํญ๋๋ก ๋ฐ ์ํญ๋ฏธ์ฌ์ผ ๋ณตํฉ ๋นํ ํน์ฑ ๋ถ์ ๋ฐ ํฌํ๊ฒฝ๋ณด ๋ฐ์ดํฐ ๊ท๊ฒฉ ์ ์\n",
"**์์์๊ฐ**: ์ฝ 1.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์คํค๋-136 ์ํญ๋๋ก ๋ฐ ์ํญ๋ฏธ์ฌ์ผ ๋์ ์นจํฌ ์ ์ ์ ๋ถ์ํ์ฌ ๋นํ ์๋, ๊ณ ๋, ํ๋ ์๋ ฅ, ํ์ ์ ๊ทผ ๋ฐฉํฅ ๋ฑ ํฌํ๊ฒฝ๋ณด ํต์ฌ ์์ฑ ๋ฐ์ดํฐ ๊ท๊ฒฉ์ ์ ์ํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **ํฌํ๊ฒฝ๋ณด ์ํฉ์์ ์ผ์๊ฐ ๋ณด๋ด๋ ํญ์ ๋ฉ์์ง์ ํต์ฌ ์์ฑ์ ์ ์ํ๊ณ , ์๋ชป๋ ๋ฉ์์ง๋ฅผ ๊ฑธ๋ฌ๋ด๋ ๊ฒ์ฆ๊ธฐ๋ฅผ ๋ง๋ ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **ํญ์ ID, ์ผ์, ์๊ฐ, ์์น, ๊ณ ๋, ์๋, ๋ฐฉ์, ์ ๊ทผ ๋ฐฉํฅ, ์ ํ ์ถ์ , ์ํ ๊ฐ์ค ๋ฑ๊ธ ํ๋๋ฅผ ์ ํ๊ณ ๊ธฐ์ยท์์น์ค์ฐจยทํต์ ์ง์ฐ์ ์ ์ ํ๋ผ๋ฏธํฐ๋ก ๋ณด์ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `data/schema/ops04_input_schema.json ๋ฐ docs/ops04_data_spec.md` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์์ ยท์ ํฌ ์ํ ์ค๋ฌด ๋ฐ ๋ฌ์์-์ฐํฌ๋ผ์ด๋ ์ ์ ์ ์ฅ ํ๊ฒฝ๊ณผ์ ๊ตฐ์ฌ์ ๋ถํฉ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์คํค๋ง ํ๋ ์ ์ ์์ฑ๋ 100%, ์ํ ๋ฐ์ดํฐ ๊ฒ์ฆ ์ค๋ฅ 0๊ฑด\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์
๋ ฅ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฆ ์คํฌ๋ฆฝํธ ํต๊ณผ ๋ฐ ๋ช
์ธ์ ๋ฌธ์ํ ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: JSON Schema ๊ฒ์ฆ ๋๊ตฌ ๋ฐ ์ํ ๋ฐ์ดํฐ ํ์ฑ ๋จ์ ํ
์คํธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"ํฌํ๊ฒฝ๋ณด ์
๋ ฅ ๋ฉ์์ง์ ๋ฐ์ดํฐ ๊ท๊ฒฉ(JSON ์คํค๋ง)์ ์ ์ํ๊ณ , ๊ฐ์ ์ํ 20๊ฑด ๊ฒ์ฆ๊ณผ ์ค๋ฅ 5์ข
์ฃผ์
๊ฒ์ถ๋ก ๊ฒ์ฆ๊ธฐ๋ฅผ ํ์ธํ ๋ค ๋ฐ์ดํฐ ๋ช
์ธ์๋ฅผ ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import json\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ๋ฐฉ์ด์ง์ญยท๊ฐ์ ๋นํ์ฒด ์์ฑ๊ธฐ ----------------\n",
"# ์ขํ๋ ๊ฐ์ ์งํ์๋ฅผ ์์ ์ผ๋ก ํ km ๋จ์ ํ๋ฉด์ด๋ค. ๋นํ์ฒด ์ ํ์ ์ถ์ ๋ฒ์ฃผ์ด๋ฉฐ\n",
"# ์๋ยท๊ณ ๋ยท๋ฐ์ฌ๊ฐ๋๋ ํ์ต์ฉ ์์ ๋ฒ์๋ก, ์ค์ ๋ฌด๊ธฐ ์ ์ยท์ฌ๊ฑฐ๋ฆฌยทํ๋์ ๋ฌด๊ดํ๋ค.\n",
"ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n",
" [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n",
" [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n",
" [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n",
"TYPES = { # ์ ํ: (์ํ ์ฌ๋ถ, ์๋ m/s ๋ฒ์, ๊ณ ๋ m ๋ฒ์, ์ถํ ๋น์จ)\n",
" \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ ์ ๋ฌด์ธ๊ธฐํ(๊ฐ์)\n",
" \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์ ์ํญํ(๊ฐ์)\n",
" \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋ง์ฒด(๊ฐ์)\n",
" \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํญ๊ธฐ(๊ฐ์)\n",
" \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ ๊ตฐ์ง\n",
"}\n",
"TTI_URGENT = 600 # ๊ธด๊ธ ํ์ ๊ธฐ์ค: ๊ฐ์ ์์ฐ ๋๋ฌ ์์์๊ฐ 600์ด ์ด๋ด\n",
"CPA_KM = 1.5 # ์์ฐ ์๊ณต ํต๊ณผ ํ์ ๋ฐ๊ฒฝ(๊ฐ์)\n",
"\n",
"\n",
"def make_raid(seed, n=80, spoof=0):\n",
" \"\"\"๊ฐ์ ํฌํ ์ํฉ 1ํ๋ถ(๋นํ์ฒด n๊ฐ + ์คํธํ ๊ฐ์ง ํญ์ spoof๊ฐ)์ ๋ง๋ ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" names = list(TYPES)\n",
" rows = []\n",
" for i in range(n + spoof):\n",
" typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n",
" threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n",
" brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n",
" x, y = dist * np.sin(brg), dist * np.cos(brg)\n",
" spd = rng.uniform(*sp); alt = rng.uniform(*al)\n",
" aim = int(rng.integers(0, len(ASSETS)))\n",
" if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ์ ์์ฐ ๋ฐฉํฅ(์์ ๋ฐฉ์ ์ค์ฐจ)\n",
" ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n",
" hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n",
" else:\n",
" hd = rng.uniform(0, 2 * np.pi); aim = -1\n",
" vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n",
" sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n",
" \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n",
" \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n",
" jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n",
" true_tti = np.inf\n",
" if threat:\n",
" d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n",
" true_tti = d_aim * 1000 / spd\n",
" rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n",
" \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n",
" \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n",
" \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n",
" \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n",
" df = pd.DataFrame(rows)\n",
" df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n",
" for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n",
" return df\n",
"\n",
"\n",
"def geometry(df):\n",
" \"\"\"๊ฐ ํญ์ ๊ณผ ๊ฐ์ ์์ฐ ์ฌ์ด ์ต๊ทผ์ ์ (CPA) ๊ฑฐ๋ฆฌ์ ๋๋ฌ ์์์๊ฐ(TTI, s)์ ๊ณ์ฐํ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 1] ์คํค๋ ์ํญ๋๋ก ๋ฐ ์ํญ๋ฏธ์ฌ์ผ ๋ณตํฉ ๋นํ ํน์ฑ ๋ถ์ ๋ฐ ํฌํ๊ฒฝ๋ณด ๋ฐ์ดํฐ ๊ท๊ฒฉ ์ ์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"SCHEMA = {\n",
" \"track_id\": {\"type\": str, \"rule\": \"T+์ซ์ 5์๋ฆฌ\", \"check\": lambda v: v[0] == \"T\" and v[1:].isdigit() and len(v) == 6},\n",
" \"sensor_id\": {\"type\": str, \"allowed\": [\"RDR-1\", \"RDR-2\", \"EO-1\", \"ACU-1\"]},\n",
" \"timestamp_ms\": {\"type\": int, \"range\": (0, 10 ** 13)},\n",
" \"x_km\": {\"type\": float, \"range\": (-100, 100)},\n",
" \"y_km\": {\"type\": float, \"range\": (-100, 100)},\n",
" \"alt_m\": {\"type\": float, \"range\": (0, 15000)},\n",
" \"speed_mps\": {\"type\": float, \"range\": (0, 400)},\n",
" \"heading_deg\": {\"type\": float, \"range\": (0, 360)},\n",
" \"approach_dir\": {\"type\": str, \"allowed\": [\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\"]},\n",
" \"class_hint\": {\"type\": str, \"allowed\": [\"uav_type\", \"fast_type\", \"unknown\", \"civil\", \"bird\"]},\n",
" \"threat_grade\": {\"type\": int, \"range\": (1, 3)}, # ์ํ ๊ฐ์ค ๋ฑ๊ธ(๊ฐ์, 1 ๋ฎ์ ~ 3 ๋์)\n",
" \"rcs\": {\"type\": float, \"range\": (0, 1)}, # ์ ๊ทํ ๋ฐ์ฌ๊ฐ๋(๊ฐ์)\n",
" \"weather\": {\"type\": str, \"allowed\": [\"clear\", \"rain\", \"fog\", \"snow\"]},\n",
" \"pos_error_m\": {\"type\": float, \"range\": (0, 2000)}, # ๋ณด์ ์ ์ ํ๋ผ๋ฏธํฐ: ์์น ์ค์ฐจ\n",
" \"comm_delay_ms\": {\"type\": int, \"range\": (0, 5000)}, # ๋ณด์ ์ ์ ํ๋ผ๋ฏธํฐ: ํต์ ์ง์ฐ\n",
"}\n",
"REQUIRED = list(SCHEMA)\n",
"\n",
"\n",
"def validate(msg):\n",
" # [TODO : ํ์ ์ค์ต 1] ์คํค๋ ์ํญ๋๋ก ๋ฐ ์ํญ๋ฏธ์ฌ์ผ ๋ณตํฉ ๋นํ ํน์ฑ ๋ถ์ ๋ฐ ํฌํ๊ฒฝ๋ณด ๋ฐ์ดํฐ ๊ท๊ฒฉ ์ ์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def to_message(r, rng):\n",
" hd = (np.degrees(np.arctan2(r.vx, r.vy)) + 360) % 360\n",
" frm = (hd + 180) % 360\n",
" hint = {\"uav_type\": \"uav_type\", \"fast_type\": \"fast_type\", \"civil\": \"civil\", \"bird\": \"bird\"}.get(r.type, \"unknown\")\n",
" return {\"track_id\": r.id, \"sensor_id\": str(rng.choice(SCHEMA[\"sensor_id\"][\"allowed\"])), \"timestamp_ms\": int(1.7e12 + rng.integers(0, 60000)),\n",
" \"x_km\": round(float(r.x), 2), \"y_km\": round(float(r.y), 2), \"alt_m\": round(float(r.alt), 0), \"speed_mps\": round(float(r.spd), 1),\n",
" \"heading_deg\": round(float(hd), 1), \"approach_dir\": [\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\"][int(((frm + 22.5) % 360) // 45)],\n",
" \"class_hint\": hint, \"threat_grade\": 3 if r.type == \"fast_type\" else (2 if r.threat else 1), \"rcs\": round(float(r.rcs), 2),\n",
" \"weather\": str(rng.choice([\"clear\", \"rain\", \"fog\", \"snow\"])), \"pos_error_m\": round(float(rng.uniform(20, 300)), 0),\n",
" \"comm_delay_ms\": int(rng.integers(50, 800))}\n",
"\n",
"\n",
"rng = np.random.default_rng(1)\n",
"raid = make_raid(11, 20)\n",
"samples = [to_message(r, rng) for r in raid.itertuples()]\n",
"normal_err = sum(len(validate(m)) for m in samples)\n",
"print(f\"[์ ์ ์ํ ๊ฒ์ฆ] {len(samples)}๊ฑด, ์ค๋ฅ {normal_err}๊ฑด\")\n",
"print(\"์ํ 1๊ฑด:\", json.dumps(samples[0], ensure_ascii=False)[:150], \"...\")\n",
"\n",
"bad = [dict(samples[0]) for _ in range(5)]\n",
"bad[0][\"speed_mps\"] = \"fast\" # ํ์ ์ค๋ฅ\n",
"bad[1][\"sensor_id\"] = \"RDR-9\" # ํ์ฉ๊ฐ ์ค๋ฅ\n",
"bad[2][\"alt_m\"] = -50.0 # ๋ฒ์ ์ค๋ฅ\n",
"del bad[3][\"comm_delay_ms\"] # ๋๋ฝ(ํต์ ์ง์ฐ)\n",
"bad[4][\"warhead_kg\"] = 50 # ์ ์๋์ง ์์ ํ๋(์ค์ ์ ์ ์
๋ ฅ ์ฐจ๋จ)\n",
"kinds = [\"ํ์\", \"ํ์ฉ๊ฐ\", \"๋ฒ์\", \"๋๋ฝ\", \"๋ฏธ์ ์ ํ๋\"]\n",
"print(\"\\n[์ค๋ฅ ์ฃผ์
๊ฒ์ถ]\")\n",
"detected = 0\n",
"for k, m in zip(kinds, bad):\n",
" e = validate(m); detected += bool(e)\n",
" print(f\" {k:7s} -> {'๊ฒ์ถ' if e else '๋ฏธ๊ฒ์ถ'}: {e[0] if e else ''}\")\n",
"\n",
"print(\"\\n[๋ฐ์ดํฐ ๋ช
์ธ ์์ฝ]\")\n",
"spec = pd.DataFrame([{\"ํ๋\": f, \"ํ์\": s[\"type\"].__name__, \"๊ท์น\": s.get(\"allowed\", s.get(\"range\", s.get(\"rule\")))} for f, s in SCHEMA.items()])\n",
"print(spec.to_string(index=False))\n",
"complete = sum(all(k in s for k in (\"type\",)) and any(k in s for k in (\"allowed\", \"range\", \"check\")) for s in SCHEMA.values()) / len(SCHEMA)\n",
"print(f\"\\n์คํค๋ง ํ๋ ์ ์ ์์ฑ๋ {complete:.0%} (๋ชฉํ 100%) -> {'๋ฌ์ฑ' if complete == 1 else '๋ฏธ๋ฌ'}\")\n",
"print(f\"์ํ ๊ฒ์ฆ ์ค๋ฅ {normal_err}๊ฑด (๋ชฉํ 0๊ฑด) -> {'๋ฌ์ฑ' if normal_err == 0 else '๋ฏธ๋ฌ'}, ์ฃผ์
์ค๋ฅ {detected}/5 ๊ฒ์ถ\")\n",
"print(\"์ฐธ๊ณ : ์ค์ ๋ฌด๊ธฐ ์ ์ยท์ฌ๊ฑฐ๋ฆฌ ํ๋๋ ๋์ง ์์ผ๋ฉฐ, ๋ฏธ์ ์ ํ๋๋ ๊ฒ์ฆ ๋จ๊ณ์์ ์ฐจ๋จํ๋ค.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
01 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[์ ์ ์ํ ๊ฒ์ฆ] 20๊ฑด, ์ค๋ฅ 0๊ฑด\n",
"์ํ 1๊ฑด: {\"track_id\": \"T11000\", \"sensor_id\": \"RDR-2\", \"timestamp_ms\": 1700000030709, \"x_km\": 0.17, \"y_km\": -38.48, \"alt_m\": 204.0, \"speed_mps\": 30.9, \"heading_ ...\n",
"\n",
"[์ค๋ฅ ์ฃผ์
๊ฒ์ถ]\n",
" ํ์ -> ๊ฒ์ถ: speed_mps: ํ์ ์ค๋ฅ(str)\n",
" ํ์ฉ๊ฐ -> ๊ฒ์ถ: sensor_id: ํ์ฉ๊ฐ ์๋(RDR-9)\n",
" ๋ฒ์ -> ๊ฒ์ถ: alt_m: ๋ฒ์ ์ด๊ณผ(-50.0)\n",
" ๋๋ฝ -> ๊ฒ์ถ: comm_delay_ms: ๋๋ฝ\n",
" ๋ฏธ์ ์ ํ๋ -> ๊ฒ์ถ: ์ ์๋์ง ์์ ํ๋: ['warhead_kg']\n",
"\n",
"[๋ฐ์ดํฐ ๋ช
์ธ ์์ฝ]\n",
" ํ๋ ํ์ ๊ท์น\n",
" track_id str T+์ซ์ 5์๋ฆฌ\n",
" sensor_id str [RDR-1, RDR-2, EO-1, ACU-1]\n",
" timestamp_ms int (0, 10000000000000)\n",
" x_km float (-100, 100)\n",
" y_km float (-100, 100)\n",
" alt_m float (0, 15000)\n",
" speed_mps float (0, 400)\n",
" heading_deg float (0, 360)\n",
" approach_dir str [N, NE, E, SE, S, SW, W, NW]\n",
" class_hint str [uav_type, fast_type, unknown, civil, bird]\n",
" threat_grade int (1, 3)\n",
" rcs float (0, 1)\n",
" weather str [clear, rain, fog, snow]\n",
" pos_error_m float (0, 2000)\n",
"comm_delay_ms int (0, 5000)\n",
"\n",
"์คํค๋ง ํ๋ ์ ์ ์์ฑ๋ 100% (๋ชฉํ 100%) -> ๋ฌ์ฑ\n",
"์ํ ๊ฒ์ฆ ์ค๋ฅ 0๊ฑด (๋ชฉํ 0๊ฑด) -> ๋ฌ์ฑ, ์ฃผ์
์ค๋ฅ 5/5 ๊ฒ์ถ\n",
"์ฐธ๊ณ : ์ค์ ๋ฌด๊ธฐ ์ ์ยท์ฌ๊ฑฐ๋ฆฌ ํ๋๋ ๋์ง ์์ผ๋ฉฐ, ๋ฏธ์ ์ ํ๋๋ ๊ฒ์ฆ ๋จ๊ณ์์ ์ฐจ๋จํ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
02. ๋ค๋ฐฉํฅ ๋์ ์นจํฌ ํญ์ ๋ฐ์ดํฐ ์ ์ ๋ฐ ์ผ์๋ณ ๊ฒฝ๋ณด ๋ฉ์์ง ํ์ ์ ์ฒ๋ฆฌ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋ค๋ฐฉํฅ์์ ๋์ ์ ์
๋๋ ๋๋์ ๊ฒฝ๋ณด ๋ฉ์์ง๋ฅผ ํ์์คํฌํ ์์ผ๋ก ์ ์ ํ๊ณ ์ผ์๋ณ ์ค๋ณต ๋ฉ์์ง๋ฅผ ํ์ํ๋ ์ค์๊ฐ ๋ฐ์ดํฐ ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ฌ๋ฌ ๋ฐฉํฅยท์ฌ๋ฌ ์ผ์์์ ๋์์ ๋ค์ด์ค๋ ๊ฒฝ๋ณด ๋ฉ์์ง๋ฅผ ์๊ฐ ์์ผ๋ก ์ ๋ ฌํ๊ณ ์ค๋ณตยท์ก์์ ๊ฑธ๋ฌ๋ด๋ ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ ๋ง๋ ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ผ์๋ณ ํ์ ์์ธ ๋ฉ์์ง๋ฅผ ๊ฐ๊ฐ ์ ๋ ฌํ ๋ค k-way ๋ณํฉ์ผ๋ก ํ์์คํฌํ ์์๋ฅผ ๋ง์ถ๋ค ๊ณผ์ ์ ์งํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/preprocess/ops04_preprocessor.py ๋ฐ ์ ์ฒ๋ฆฌ ์ ํ ๋ฐ์ดํฐ์
` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ ์ ํ๊ฒฝ ํน์ ์ ๋
ธ์ด์ฆ(์คํธํ, ๋ฐ์ฌํ, ์ผ์ ๋จ์ ๋ฑ) ์ ๊ฑฐ ํ๋น์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ๋ฐ์ดํฐ ์ ์ ์ฒ๋ฆฌ ์๋ 1,000๊ฑด/์ด ์ด์, ์ ์ฒ๋ฆฌ ์ค๋ฅ์จ 0%\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์ ์ ํ์ดํ๋ผ์ธ ์๋ํ ๋ฐ ๋จ์ ํ
์คํธ ์ผ์ด์ค 5์ข
ํต๊ณผ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: pytest ๊ธฐ๋ฐ ์ ์ฒ๋ฆฌ ์ ํฉ์ฑ ํ
์คํธ ๋ฐ ๊ฒฐ์ธก์น ๋ณด์ ์จ ํ์ธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๋ค๋ฐฉํฅ์์ ๋์์ ๋ค์ด์ค๋ ๊ฐ์ ์ผ์ ๊ฒฝ๋ณด ๋ฉ์์ง๋ฅผ ์ผ์๋ณ ํ์ ๋ฐ์ ํ์์คํฌํ ์์ผ๋ก ์ ๋ ฌํ๊ณ , ์ค๋ณตยท๋ฐ์ฌํ(์ ๋ น ํญ์ )ยท์คํธํยท์ผ์ ๋จ์ ์ ์ ์ ํ๋ ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ ๊ตฌํํด ์ฒ๋ฆฌ ์๋์ ์ค๋ฅ์จ์ ์ธก์ ํ๋ค.\"\"\"\n",
"import heapq\n",
"import time\n",
"from collections import defaultdict, deque\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ๋ฐฉ์ด์ง์ญยท๊ฐ์ ๋นํ์ฒด ์์ฑ๊ธฐ ----------------\n",
"# ์ขํ๋ ๊ฐ์ ์งํ์๋ฅผ ์์ ์ผ๋ก ํ km ๋จ์ ํ๋ฉด์ด๋ค. ๋นํ์ฒด ์ ํ์ ์ถ์ ๋ฒ์ฃผ์ด๋ฉฐ\n",
"# ์๋ยท๊ณ ๋ยท๋ฐ์ฌ๊ฐ๋๋ ํ์ต์ฉ ์์ ๋ฒ์๋ก, ์ค์ ๋ฌด๊ธฐ ์ ์ยท์ฌ๊ฑฐ๋ฆฌยทํ๋์ ๋ฌด๊ดํ๋ค.\n",
"ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n",
" [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n",
" [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n",
" [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n",
"TYPES = { # ์ ํ: (์ํ ์ฌ๋ถ, ์๋ m/s ๋ฒ์, ๊ณ ๋ m ๋ฒ์, ์ถํ ๋น์จ)\n",
" \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ ์ ๋ฌด์ธ๊ธฐํ(๊ฐ์)\n",
" \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์ ์ํญํ(๊ฐ์)\n",
" \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋ง์ฒด(๊ฐ์)\n",
" \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํญ๊ธฐ(๊ฐ์)\n",
" \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ ๊ตฐ์ง\n",
"}\n",
"TTI_URGENT = 600 # ๊ธด๊ธ ํ์ ๊ธฐ์ค: ๊ฐ์ ์์ฐ ๋๋ฌ ์์์๊ฐ 600์ด ์ด๋ด\n",
"CPA_KM = 1.5 # ์์ฐ ์๊ณต ํต๊ณผ ํ์ ๋ฐ๊ฒฝ(๊ฐ์)\n",
"\n",
"\n",
"def make_raid(seed, n=80, spoof=0):\n",
" \"\"\"๊ฐ์ ํฌํ ์ํฉ 1ํ๋ถ(๋นํ์ฒด n๊ฐ + ์คํธํ ๊ฐ์ง ํญ์ spoof๊ฐ)์ ๋ง๋ ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" names = list(TYPES)\n",
" rows = []\n",
" for i in range(n + spoof):\n",
" typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n",
" threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n",
" brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n",
" x, y = dist * np.sin(brg), dist * np.cos(brg)\n",
" spd = rng.uniform(*sp); alt = rng.uniform(*al)\n",
" aim = int(rng.integers(0, len(ASSETS)))\n",
" if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ์ ์์ฐ ๋ฐฉํฅ(์์ ๋ฐฉ์ ์ค์ฐจ)\n",
" ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n",
" hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n",
" else:\n",
" hd = rng.uniform(0, 2 * np.pi); aim = -1\n",
" vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n",
" sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n",
" \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n",
" \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n",
" jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n",
" true_tti = np.inf\n",
" if threat:\n",
" d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n",
" true_tti = d_aim * 1000 / spd\n",
" rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n",
" \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n",
" \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n",
" \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n",
" \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n",
" df = pd.DataFrame(rows)\n",
" df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n",
" for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n",
" return df\n",
"\n",
"\n",
"def geometry(df):\n",
" \"\"\"๊ฐ ํญ์ ๊ณผ ๊ฐ์ ์์ฐ ์ฌ์ด ์ต๊ทผ์ ์ (CPA) ๊ฑฐ๋ฆฌ์ ๋๋ฌ ์์์๊ฐ(TTI, s)์ ๊ณ์ฐํ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 2] ๋ค๋ฐฉํฅ ๋์ ์นจํฌ ํญ์ ๋ฐ์ดํฐ ์ ์ ๋ฐ ์ผ์๋ณ ๊ฒฝ๋ณด ๋ฉ์์ง ํ์ ์ ์ฒ๋ฆฌ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"SENSORS = [\"RDR-1\", \"RDR-2\", \"EO-1\", \"ACU-1\"]\n",
"\n",
"\n",
"def make_stream(seed=21, n=120, steps=30):\n",
" \"\"\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ์ผ์๋ณ ๋ฉ์์ง ์คํธ๋ฆผ. label: valid/dup/ghost/spoof (์ฑ์ ์ฉ ์ ๋ต)\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" raid = make_raid(seed, n, spoof=6)\n",
" queues = {s: [] for s in SENSORS}\n",
" mid = 0\n",
" for r in raid.itertuples():\n",
" sens = list(rng.choice(SENSORS, size=max(1, r.n_sensors), replace=False))\n",
" for k in range(steps):\n",
" if r.Index % 9 == 0 and 10 <= k < 15: # ์ผ๋ถ ํญ์ ์ ์ผ์ ๋จ์ ๊ตฌ๊ฐ\n",
" continue\n",
" t = k * 1000 + int(rng.integers(0, 300))\n",
" x, y = r.x + r.vx * k, r.y + r.vy * k\n",
" if r.type == \"spoof\": # ์คํธํ: ์์น๊ฐ ๋น์ ์์ ์ผ๋ก ๋์ฝ\n",
" x, y = x + rng.normal(0, 3), y + rng.normal(0, 3)\n",
" for s in sens:\n",
" m = {\"mid\": mid, \"sensor\": s, \"track\": r.id, \"ts\": t + int(rng.integers(0, 80)), \"x\": x, \"y\": y,\n",
" \"spd\": r.spd, \"snr\": rng.uniform(0.4, 1.0), \"label\": \"spoof\" if r.type == \"spoof\" else \"valid\"}\n",
" queues[s].append(m); mid += 1\n",
" if rng.random() < 0.08: # ์ค๋ณต ์ฌ์ ์ก\n",
" queues[s].append({**m, \"mid\": mid, \"label\": \"dup\"}); mid += 1\n",
" if s.startswith(\"RDR\") and rng.random() < 0.03: # ๋ฐ์ฌํ ์ ๋ น ํญ์ (์ฝํ ์ ํธ, ๋์นญ ์์น)\n",
" queues[s].append({**m, \"mid\": mid, \"track\": \"G\" + r.id[1:], \"x\": -x, \"snr\": rng.uniform(0.05, 0.2),\n",
" \"label\": \"ghost\"}); mid += 1\n",
" for s in SENSORS: # ์ ์ก ์ง์ฐ์ผ๋ก ์์๊ฐ ์์ฌ ๋์ฐฉ\n",
" q = queues[s]\n",
" idx = rng.permutation(len(q))[: len(q) // 10]\n",
" for a, b in zip(idx[::2], idx[1::2]):\n",
" q[a], q[b] = q[b], q[a]\n",
" return queues\n",
"\n",
"\n",
"def preprocess(queues, snr_min=0.25, jump_ratio=3.0, gap_ms=5000):\n",
" # [TODO : ํ์ ์ค์ต 2] ๋ค๋ฐฉํฅ ๋์ ์นจํฌ ํญ์ ๋ฐ์ดํฐ ์ ์ ๋ฐ ์ผ์๋ณ ๊ฒฝ๋ณด ๋ฉ์์ง ํ์ ์ ์ฒ๋ฆฌ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"queues = make_stream()\n",
"n_in = sum(len(q) for q in queues.values())\n",
"t0 = time.perf_counter()\n",
"out, stats, merged = preprocess(queues)\n",
"dt = time.perf_counter() - t0\n",
"rate = n_in / dt\n",
"\n",
"kept = {mid for f in out for mid in f[\"src\"]}\n",
"labels = {m[\"mid\"]: m[\"label\"] for m in merged}\n",
"wrong_drop = sum(1 for k, l in labels.items() if l == \"valid\" and k not in kept)\n",
"wrong_keep = sum(1 for k, l in labels.items() if l != \"valid\" and k in kept)\n",
"err = (wrong_drop + wrong_keep) / n_in\n",
"\n",
"print(f\"[์ ์ฒ๋ฆฌ ์ ] ๋ฉ์์ง {n_in}๊ฑด (์ผ์ {len(queues)}๊ฐ), ์ ํ๋ณ:\", pd.Series(labels).value_counts().to_dict())\n",
"print(f\"[์ ์ฒ๋ฆฌ ํ] ์ตํฉ ๋ณด๊ณ {len(out)}๊ฑด, ํญ์ {len({f['track'] for f in out})}๊ฐ\")\n",
"for k, v in stats.items():\n",
" print(f\" - {k}: {v}\")\n",
"ts = [m[\"ts\"] for m in merged]\n",
"print(f\" - ํ์์คํฌํ ์ ๋ ฌ ์๋ฐ: {sum(b < a for a, b in zip(ts, ts[1:]))}๊ฑด\")\n",
"multi = sum(len(f[\"sensors\"]) > 1 for f in out) / len(out)\n",
"print(f\" - ๋ค์ค ์ผ์ ์ตํฉ ๋น์จ: {multi:.1%}\")\n",
"print(f\"\\n์ฒ๋ฆฌ ์๊ฐ {dt * 1000:.1f} ms, ์ฒ๋ฆฌ ์๋ {rate:,.0f}๊ฑด/์ด (๋ชฉํ 1,000๊ฑด/์ด ์ด์) -> {'๋ฌ์ฑ' if rate >= 1000 else '๋ฏธ๋ฌ'}\")\n",
"print(f\"์ ์ ๋ฉ์์ง ์ค์ญ์ {wrong_drop}๊ฑด, ์ก์ ๋ฏธ์ ๊ฑฐ {wrong_keep}๊ฑด, ์ ์ฒ๋ฆฌ ์ค๋ฅ์จ {err:.2%} (๋ชฉํ 0%) -> {'๋ฌ์ฑ' if err == 0 else '๋ฏธ๋ฌ'}\")\n",
"print(\"\\n[์ ์ฒ๋ฆฌ ํ ๋ฐ์ดํฐ ์๋ถ๋ถ]\")\n",
"print(pd.DataFrame([{\"track\": f[\"track\"], \"sec\": f[\"sec\"], \"x_km\": round(f[\"x\"], 2), \"y_km\": round(f[\"y\"], 2), \"์ผ์์\": len(f[\"sensors\"])}\n",
" for f in sorted(out, key=lambda f: (f[\"sec\"], f[\"track\"]))[:6]]).to_string(index=False))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
02 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[์ ์ฒ๋ฆฌ ์ ] ๋ฉ์์ง 8100๊ฑด (์ผ์ 4๊ฐ), ์ ํ๋ณ: {'valid': 7145, 'dup': 612, 'spoof': 240, 'ghost': 103}\n",
"[์ ์ฒ๋ฆฌ ํ] ์ตํฉ ๋ณด๊ณ 3530๊ฑด, ํญ์ 120๊ฐ\n",
" - ๋ฐ์ฌํ ์ ๊ฑฐ: 103\n",
" - ์ค๋ณต ์ ๊ฑฐ: 612\n",
" - ์คํธํ ํญ์ ๊ฒฉ๋ฆฌ: 6\n",
" - ์ผ์ ๋จ์ ๊ตฌ๊ฐ ํ์: 14\n",
" - ํ์์คํฌํ ์ ๋ ฌ ์๋ฐ: 0๊ฑด\n",
" - ๋ค์ค ์ผ์ ์ตํฉ ๋น์จ: 71.8%\n",
"\n",
"์ฒ๋ฆฌ ์๊ฐ 49.3 ms, ์ฒ๋ฆฌ ์๋ 164,466๊ฑด/์ด (๋ชฉํ 1,000๊ฑด/์ด ์ด์) -> ๋ฌ์ฑ\n",
"์ ์ ๋ฉ์์ง ์ค์ญ์ 0๊ฑด, ์ก์ ๋ฏธ์ ๊ฑฐ 0๊ฑด, ์ ์ฒ๋ฆฌ ์ค๋ฅ์จ 0.00% (๋ชฉํ 0%) -> ๋ฌ์ฑ\n",
"\n",
"[์ ์ฒ๋ฆฌ ํ ๋ฐ์ดํฐ ์๋ถ๋ถ]\n",
" track sec x_km y_km ์ผ์์\n",
"T21000 0 -27.36 -34.88 3\n",
"T21001 0 50.43 13.12 2\n",
"T21002 0 0.03 20.05 3\n",
"T21003 0 -8.61 -25.31 3\n",
"T21004 0 -13.38 -24.24 1\n",
"T21005 0 50.83 -22.68 2\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
03. ์๋ยท๊ณ ๋ยท์ ๊ทผ ๋ฐฉ์๊ฐ ๊ธฐ๋ฐ 1์ฐจ ๊ณต์ค์ํ ์ฐ์ ์์ ์ฐ์ ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋ฐฉ์ด ๊ฑฐ์ ๊น์ง์ ๊ฑฐ๋ฆฌ, ์์ฌ ๋๋ฌ ์๊ฐ, ์ ๊ทผ ์๋๋ฅผ ๋ฐํ์ผ๋ก 1์ฐจ ์ํ ์์๋ฅผ ์ ๋ ฌํ๋ Baseline ๋ญํน ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋ฐฉ์ด ์์ฐ๊น์ง์ ๊ฑฐ๋ฆฌ์ ์์ฌ ๋๋ฌ ์์์๊ฐ(TTI)๋ง์ผ๋ก 1์ฐจ ์ํ ์์๋ฅผ ์ ๋ ฌํ๋ ํฌ๋ช
ํ Baseline์ ๋ง๋ ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฐ ํญ์ ์ ์ต๊ทผ์ ์ (CPA)์ด ๊ฐ์ ์์ฐ ๋ฐ๊ฒฝ 1.5 km ์์ ์ง๋๋์ง ๊ณ์ฐํ๊ณ , ํด๋น ์์ฐ๊น์ง์ ๋๋ฌ ์์์๊ฐ์ ๊ตฌํด ์งง์ ์์ผ๋ก ์ ๋ ฌ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/ops04_baseline_model.py ๋ฐ ๊ธฐ๋ณธ ์ถ๋ก ๊ฒฐ๊ณผ ๋ณด๊ณ ์` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์๊ณ ๋ฆฌ์ฆ์ ๋
ผ๋ฆฌ์ ํ๋ฆ ๋ฐ ๊ตฐ์ฌ์ ํ๋จ ๊ธฐ์ค์ ํฌ๋ช
์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ๊ธฐ๋ณธ ๋ฐ์ดํฐ์
๋์ ํ์ง/์ฒ๋ฆฌ ์ ํ๋ 75% ์ด์ ๋ฌ์ฑ\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ๋จ์ ๊ธฐ๋ฅ ๋์ ํ์ธ ๋ฐ ๊ธฐ๋ณธ ์๋๋ฆฌ์ค ์คํ ๋ก๊ทธ ์์ฑ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๊ธฐ์ค ๋ฒค์น๋งํฌ ๋ฐ์ดํฐ์
๋์ ์ถ๋ก ๊ฒฐ๊ณผ ์ค์ฐจ์จ ๋น๊ต\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๊ฐ์ ๋ฐฉ์ด์์ฐ๊น์ง์ ์ต๊ทผ์ ๊ฑฐ๋ฆฌ์ ์์ฌ ๋๋ฌ ์์์๊ฐ(TTI), ์ ๊ทผ ์๋๋ง์ผ๋ก 1์ฐจ ์ํ ์์๋ฅผ ์ ๋ ฌํ๋ Baseline ๋ญํน ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ๊ณ ๊ธด๊ธ ํ์ ์ ํ๋๋ฅผ ์ธก์ ํ๋ค.\"\"\"\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ๋ฐฉ์ด์ง์ญยท๊ฐ์ ๋นํ์ฒด ์์ฑ๊ธฐ ----------------\n",
"# ์ขํ๋ ๊ฐ์ ์งํ์๋ฅผ ์์ ์ผ๋ก ํ km ๋จ์ ํ๋ฉด์ด๋ค. ๋นํ์ฒด ์ ํ์ ์ถ์ ๋ฒ์ฃผ์ด๋ฉฐ\n",
"# ์๋ยท๊ณ ๋ยท๋ฐ์ฌ๊ฐ๋๋ ํ์ต์ฉ ์์ ๋ฒ์๋ก, ์ค์ ๋ฌด๊ธฐ ์ ์ยท์ฌ๊ฑฐ๋ฆฌยทํ๋์ ๋ฌด๊ดํ๋ค.\n",
"ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n",
" [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n",
" [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n",
" [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n",
"TYPES = { # ์ ํ: (์ํ ์ฌ๋ถ, ์๋ m/s ๋ฒ์, ๊ณ ๋ m ๋ฒ์, ์ถํ ๋น์จ)\n",
" \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ ์ ๋ฌด์ธ๊ธฐํ(๊ฐ์)\n",
" \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์ ์ํญํ(๊ฐ์)\n",
" \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋ง์ฒด(๊ฐ์)\n",
" \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํญ๊ธฐ(๊ฐ์)\n",
" \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ ๊ตฐ์ง\n",
"}\n",
"TTI_URGENT = 600 # ๊ธด๊ธ ํ์ ๊ธฐ์ค: ๊ฐ์ ์์ฐ ๋๋ฌ ์์์๊ฐ 600์ด ์ด๋ด\n",
"CPA_KM = 1.5 # ์์ฐ ์๊ณต ํต๊ณผ ํ์ ๋ฐ๊ฒฝ(๊ฐ์)\n",
"\n",
"\n",
"def make_raid(seed, n=80, spoof=0):\n",
" \"\"\"๊ฐ์ ํฌํ ์ํฉ 1ํ๋ถ(๋นํ์ฒด n๊ฐ + ์คํธํ ๊ฐ์ง ํญ์ spoof๊ฐ)์ ๋ง๋ ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" names = list(TYPES)\n",
" rows = []\n",
" for i in range(n + spoof):\n",
" typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n",
" threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n",
" brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n",
" x, y = dist * np.sin(brg), dist * np.cos(brg)\n",
" spd = rng.uniform(*sp); alt = rng.uniform(*al)\n",
" aim = int(rng.integers(0, len(ASSETS)))\n",
" if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ์ ์์ฐ ๋ฐฉํฅ(์์ ๋ฐฉ์ ์ค์ฐจ)\n",
" ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n",
" hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n",
" else:\n",
" hd = rng.uniform(0, 2 * np.pi); aim = -1\n",
" vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n",
" sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n",
" \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n",
" \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n",
" jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n",
" true_tti = np.inf\n",
" if threat:\n",
" d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n",
" true_tti = d_aim * 1000 / spd\n",
" rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n",
" \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n",
" \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n",
" \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n",
" \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n",
" df = pd.DataFrame(rows)\n",
" df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n",
" for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n",
" return df\n",
"\n",
"\n",
"def geometry(df):\n",
" \"\"\"๊ฐ ํญ์ ๊ณผ ๊ฐ์ ์์ฐ ์ฌ์ด ์ต๊ทผ์ ์ (CPA) ๊ฑฐ๋ฆฌ์ ๋๋ฌ ์์์๊ฐ(TTI, s)์ ๊ณ์ฐํ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 3] ์๋ยท๊ณ ๋ยท์ ๊ทผ ๋ฐฉ์๊ฐ ๊ธฐ๋ฐ 1์ฐจ ๊ณต์ค์ํ ์ฐ์ ์์ ์ฐ์ ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n",
"\n",
"\n",
"def features(df):\n",
" # [TODO : ํ์ ์ค์ต 3] ์๋ยท๊ณ ๋ยท์ ๊ทผ ๋ฐฉ์๊ฐ ๊ธฐ๋ฐ 1์ฐจ ๊ณต์ค์ํ ์ฐ์ ์์ ์ฐ์ ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def train_classifier(seeds=(101, 102, 103, 104)):\n",
" \"\"\"ํ์ต์ฉ ๊ฐ์ ์ํฉ(์ํ ์ํฉ๊ณผ ๋ค๋ฅธ ์๋)์ผ๋ก '์ํ ์ ์ฌ๋' ๋ถ๋ฅ๊ธฐ๋ฅผ ํ์ตํ๋ค.\"\"\"\n",
" tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n",
" clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n",
" clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n",
" return clf\n",
"\n",
"\n",
"def baseline_rank(df):\n",
" \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋๋ฌ์๊ฐ๋ง์ผ๋ก 1์ฐจ ์ํ ์ ์๋ฅผ ๋งค๊ธด๋ค(์์ฐ ๊ฐ์นยท์ ํ ํ๋ณ ์์).\"\"\"\n",
" _, tti = geometry(df)\n",
" t_min = tti.min(axis=1)\n",
" out = df[[\"id\"]].copy()\n",
" out[\"tti_s\"] = t_min\n",
" out[\"score\"] = np.exp(-t_min / 300)\n",
" out[\"urgent_pred\"] = t_min <= TTI_URGENT\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"\n",
"def advanced_rank(df, clf, w=None):\n",
" \"\"\"๊ณ ๋ํ: ์์ฐ ๊ฐ์น x ๋๋ฌ์๊ฐ x ์ํ ์ ์ฌ๋, ์ด๋ํ ๋ถ์ผ์น(์คํธํ ์์ฌ) ํญ์ ์ ๋ณ๋ ํ์ํ๋ค.\"\"\"\n",
" w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n",
" _, tti = geometry(df)\n",
" val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n",
" asset_score = val * np.exp(-tti / w[\"tau\"])\n",
" best = asset_score.argmax(axis=1)\n",
" p = clf.predict_proba(features(df)[FEATS])[:, 1]\n",
" spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n",
" out = df[[\"id\"]].copy()\n",
" out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n",
" out[\"tti_s\"] = tti[np.arange(len(df)), best]\n",
" out[\"p_threat\"] = p\n",
" out[\"spoof_flag\"] = spoof\n",
" out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n",
" out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"# ํ๋จ ๊ธฐ์ค(ํฌ๋ช
์ฑ): โ ๋นํ ๊ฒฝ๋ก๊ฐ ๊ฐ์ ์์ฐ ๋ฐ๊ฒฝ 1.5 km ์์ ์ง๋๋๊ฐ(CPA)\n",
"# โก ๊ทธ ์์ฐ๊น์ง ๋๋ฌ ์์์๊ฐ(TTI)์ด ์งง์๊ฐ โข TTI 600์ด ์ด๋ด๋ฉด '๊ธด๊ธ'\n",
"raid = make_raid(7, 80)\n",
"rank = baseline_rank(raid).merge(raid[[\"id\", \"type\", \"spd\", \"urgent\"]], on=\"id\")\n",
"_, tti = geometry(raid)\n",
"dist = np.hypot(raid[\"x\"], raid[\"y\"])\n",
"y, p = rank[\"urgent\"].to_numpy(), rank[\"urgent_pred\"].to_numpy()\n",
"acc = (y == p).mean()\n",
"print(f\"[Baseline 1์ฐจ ์ํ ์์] ๊ฐ์ ๋นํ์ฒด {len(raid)}๊ฐ, ์ค์ ๊ธด๊ธ {y.sum()}๊ฐ, ๊ธด๊ธ ํ์ {p.sum()}๊ฐ\")\n",
"top = rank.head(10).copy()\n",
"top[\"์์\"] = range(1, 11)\n",
"top[\"tti_s\"] = top[\"tti_s\"].round(0); top[\"spd\"] = top[\"spd\"].round(0)\n",
"print(top[[\"์์\", \"id\", \"tti_s\", \"spd\", \"type\", \"urgent\"]].rename(columns={\"tti_s\": \"๋๋ฌ์์(s)\", \"spd\": \"์๋(m/s)\", \"type\": \"์ค์ ์ ํ\", \"urgent\": \"์ค์ ๊ธด๊ธ\"}).to_string(index=False))\n",
"\n",
"print(\"\\n[์ ํ๋ณ ํ์ ๊ฒฐ๊ณผ] (๊ธด๊ธ ํ์ ์ / ์ ์ฒด)\")\n",
"for t, g in rank.groupby(\"type\"):\n",
" print(f\" {t:10s}: {int(g['urgent_pred'].sum())}/{len(g)} (์ค์ ๊ธด๊ธ {int(g['urgent'].sum())})\")\n",
"top10_true = set(raid.sort_values(\"true_prio\", ascending=False).head(10)[\"id\"])\n",
"overlap = len(top10_true & set(rank.head(10)[\"id\"])) / 10\n",
"print(f\"\\n๊ธด๊ธ ํ์ ์ ํ๋ {acc:.1%} (๋ชฉํ 75% ์ด์) -> {'๋ฌ์ฑ' if acc >= 0.75 else '๋ฏธ๋ฌ'}\")\n",
"print(f\"์์ 10๊ฐ ์์ ์ผ์น์จ {overlap:.0%}, ์ค๊ฒฝ๋ณด {int((p & ~y).sum())}๊ฑด(๊ธฐ๋ง์ฒดยท์กฐ๋ฅ ๋ฑ ์ ํ ํ๋ณ ์์์ด ์์ธ)\")\n",
"print(\"ํ๊ณ: ์์ฐ ๊ฐ์น์ ๋นํ์ฒด ์ ํ์ ๊ณ ๋ คํ์ง ์์ผ๋ฏ๋ก ๋ฏธ์
4์์ ๊ณ ๋ํํ๋ค. ์์๋ '๊ถ๊ณ '์ด๋ฉฐ ํ๋จ์ ์งํ๊ด์ด ํ๋ค.\")\n",
"\n",
"fig, ax = plt.subplots(1, 2, figsize=(12, 5))\n",
"ax[0].scatter(raid[\"x\"], raid[\"y\"], c=np.where(rank.set_index(\"id\").loc[raid[\"id\"], \"urgent_pred\"], \"red\", \"gray\"), s=18)\n",
"ax[0].scatter(ASSETS[\"x\"], ASSETS[\"y\"], marker=\"^\", s=120, c=\"blue\", label=\"virtual assets\")\n",
"ax[0].set_title(\"Baseline: red = urgent (TTI <= 600 s)\"); ax[0].set_xlabel(\"East (km)\"); ax[0].set_ylabel(\"North (km)\"); ax[0].legend()\n",
"ax[0].set_aspect(\"equal\")\n",
"t15 = rank.head(15)\n",
"ax[1].barh(t15[\"id\"][::-1], t15[\"tti_s\"][::-1], color=[\"red\" if u else \"gray\" for u in t15[\"urgent\"][::-1]])\n",
"ax[1].axvline(TTI_URGENT, ls=\"--\", c=\"k\"); ax[1].set_title(\"Top-15 by TTI (red = truly urgent)\"); ax[1].set_xlabel(\"TTI (s)\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
03 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[Baseline 1์ฐจ ์ํ ์์] ๊ฐ์ ๋นํ์ฒด 80๊ฐ, ์ค์ ๊ธด๊ธ 31๊ฐ, ๊ธด๊ธ ํ์ 39๊ฐ\n",
" ์์ id ๋๋ฌ์์(s) ์๋(m/s) ์ค์ ์ ํ ์ค์ ๊ธด๊ธ\n",
" 1 T07061 42.0 59.0 decoy False\n",
" 2 T07055 53.0 55.0 uav_type True\n",
" 3 T07048 68.0 221.0 fast_type True\n",
" 4 T07014 103.0 231.0 fast_type True\n",
" 5 T07004 152.0 224.0 fast_type True\n",
" 6 T07038 158.0 59.0 uav_type True\n",
" 7 T07019 162.0 54.0 uav_type True\n",
" 8 T07033 173.0 45.0 uav_type True\n",
" 9 T07065 176.0 154.0 fast_type True\n",
" 10 T07062 180.0 158.0 fast_type True\n",
"\n",
"[์ ํ๋ณ ํ์ ๊ฒฐ๊ณผ] (๊ธด๊ธ ํ์ ์ / ์ ์ฒด)\n",
" bird : 0/6 (์ค์ ๊ธด๊ธ 0)\n",
" civil : 1/4 (์ค์ ๊ธด๊ธ 0)\n",
" decoy : 7/16 (์ค์ ๊ธด๊ธ 0)\n",
" fast_type : 13/13 (์ค์ ๊ธด๊ธ 13)\n",
" uav_type : 18/41 (์ค์ ๊ธด๊ธ 18)\n",
"\n",
"๊ธด๊ธ ํ์ ์ ํ๋ 90.0% (๋ชฉํ 75% ์ด์) -> ๋ฌ์ฑ\n",
"์์ 10๊ฐ ์์ ์ผ์น์จ 80%, ์ค๊ฒฝ๋ณด 8๊ฑด(๊ธฐ๋ง์ฒดยท์กฐ๋ฅ ๋ฑ ์ ํ ํ๋ณ ์์์ด ์์ธ)\n",
"ํ๊ณ: ์์ฐ ๊ฐ์น์ ๋นํ์ฒด ์ ํ์ ๊ณ ๋ คํ์ง ์์ผ๋ฏ๋ก ๋ฏธ์
4์์ ๊ณ ๋ํํ๋ค. ์์๋ '๊ถ๊ณ '์ด๋ฉฐ ํ๋จ์ ์งํ๊ด์ด ํ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
04. ํต์ฌ ๋ฐฉ์ด์์ฐ(์งํ์ยท์ธํ๋ผ) ๊ฑฐ๋ฆฌ ๋ฐ ๋๋ฌ ์์ ์๊ฐ(TTI) ์ฐ๊ณ ๋ค์ฐจ์ ์ํ ํ๊ฐ ๊ณ ๋ํ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"ํต์ฌ ๋ฐฉ์ด์์ฐ(์งํ์, ๋ ์ด๋, ํ์ฝ์ฐฝ)์ ๋ฐฉํธ ๊ฐ์น์ ํ์ ๋๋ฌ ์์ ์๊ฐ(TTI)์ ๊ฒฐํฉํ์ฌ ๋ณตํฉ ์ํ ์ฐ์ ์์๋ฅผ ๋์ ์ผ๋ก ์ฌ๊ณ์ฐํ๋ ๋ชจ๋ธ์ ๊ณ ๋ํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์์ฐ ๋ฐฉํธ ๊ฐ์น, ์์ฐ๋ณ ๋๋ฌ ์์์๊ฐ, ์ํ ์ ์ฌ๋๋ฅผ ๊ฒฐํฉํด ์ฐ์ ์์๋ฅผ ๋์ ์ผ๋ก ๋ค์ ๊ณ์ฐํ๋ ๋ค์ฐจ์ ํ๊ฐ ๋ชจ๋ธ๋ก ๊ณ ๋ํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์์ฐ ๊ฐ์น x exp(-TTI/300)๋ก ์์ฐ๋ณ ์ํ์ ๊ตฌํ๊ณ , ๋ฐ์ฌ๊ฐ๋ยท์ ํธ ๋ณ๋ยท์ผ์ ํฉ์ยท๋ฏผ๊ฐ ์๋ต ์ ํธ๋ก ํ์ตํ ์ํ ์ ์ฌ๋ ๋ถ๋ฅ๊ธฐ๋ฅผ ๊ณฑ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/ops04_advanced_detector.py ๋ฐ ์์ธ ํ์ง ํ
์คํธ ๊ฒฐ๊ณผ` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ธฐ๋ง/๊ต๋ ๊ณต๊ฒฉ์ ๋ํ ๋ฐฉ์ด ๋ก์ง์ ํ์ค์ฑ ๋ฐ ์ ๋ขฐ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ๋ณตํฉ ์ํ ํ์ง ์ฌํ์จ(Recall) 85% ์ด์, ์ฒ๋ฆฌ ์ง์ฐ 200ms ์ดํ\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ๋ณตํฉ ์ํ ์๋๋ฆฌ์ค 10์ข
์ ๋ํ ํ์ง ํ
์คํธ ํต๊ณผ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ํฉ์ฑ ์ํ ๋ฐ์ดํฐ์
์ฃผ์
์ ์ด์์น ํ์ง ์ ํ๋ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๊ฐ์ ๋ฐฉ์ด์์ฐ์ ๋ฐฉํธ ๊ฐ์น์ ์์ฐ๋ณ ๋๋ฌ ์์์๊ฐ(TTI), ์ํ ์ ์ฌ๋ ๋ถ๋ฅ๊ธฐ๋ฅผ ๊ฒฐํฉํ ๋ค์ฐจ์ ์ํ ํ๊ฐ ๋ชจ๋ธ๋ก ์ฐ์ ์์๋ฅผ ๋์ ์ผ๋ก ์ฌ๊ณ์ฐํ๊ณ , ๊ธฐ๋ง์ฒดยท์คํธํ ํญ์ ์ ๋ํ ๋ฐฉ์ด ๋ก์ง๊ณผ ์ฌํ์จยท์ฒ๋ฆฌ ์ง์ฐ์ ๊ฒ์ฆํ๋ค.\"\"\"\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ๋ฐฉ์ด์ง์ญยท๊ฐ์ ๋นํ์ฒด ์์ฑ๊ธฐ ----------------\n",
"# ์ขํ๋ ๊ฐ์ ์งํ์๋ฅผ ์์ ์ผ๋ก ํ km ๋จ์ ํ๋ฉด์ด๋ค. ๋นํ์ฒด ์ ํ์ ์ถ์ ๋ฒ์ฃผ์ด๋ฉฐ\n",
"# ์๋ยท๊ณ ๋ยท๋ฐ์ฌ๊ฐ๋๋ ํ์ต์ฉ ์์ ๋ฒ์๋ก, ์ค์ ๋ฌด๊ธฐ ์ ์ยท์ฌ๊ฑฐ๋ฆฌยทํ๋์ ๋ฌด๊ดํ๋ค.\n",
"ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n",
" [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n",
" [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n",
" [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n",
"TYPES = { # ์ ํ: (์ํ ์ฌ๋ถ, ์๋ m/s ๋ฒ์, ๊ณ ๋ m ๋ฒ์, ์ถํ ๋น์จ)\n",
" \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ ์ ๋ฌด์ธ๊ธฐํ(๊ฐ์)\n",
" \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์ ์ํญํ(๊ฐ์)\n",
" \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋ง์ฒด(๊ฐ์)\n",
" \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํญ๊ธฐ(๊ฐ์)\n",
" \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ ๊ตฐ์ง\n",
"}\n",
"TTI_URGENT = 600 # ๊ธด๊ธ ํ์ ๊ธฐ์ค: ๊ฐ์ ์์ฐ ๋๋ฌ ์์์๊ฐ 600์ด ์ด๋ด\n",
"CPA_KM = 1.5 # ์์ฐ ์๊ณต ํต๊ณผ ํ์ ๋ฐ๊ฒฝ(๊ฐ์)\n",
"\n",
"\n",
"def make_raid(seed, n=80, spoof=0):\n",
" \"\"\"๊ฐ์ ํฌํ ์ํฉ 1ํ๋ถ(๋นํ์ฒด n๊ฐ + ์คํธํ ๊ฐ์ง ํญ์ spoof๊ฐ)์ ๋ง๋ ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" names = list(TYPES)\n",
" rows = []\n",
" for i in range(n + spoof):\n",
" typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n",
" threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n",
" brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n",
" x, y = dist * np.sin(brg), dist * np.cos(brg)\n",
" spd = rng.uniform(*sp); alt = rng.uniform(*al)\n",
" aim = int(rng.integers(0, len(ASSETS)))\n",
" if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ์ ์์ฐ ๋ฐฉํฅ(์์ ๋ฐฉ์ ์ค์ฐจ)\n",
" ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n",
" hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n",
" else:\n",
" hd = rng.uniform(0, 2 * np.pi); aim = -1\n",
" vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n",
" sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n",
" \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n",
" \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n",
" jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n",
" true_tti = np.inf\n",
" if threat:\n",
" d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n",
" true_tti = d_aim * 1000 / spd\n",
" rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n",
" \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n",
" \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n",
" \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n",
" \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n",
" df = pd.DataFrame(rows)\n",
" df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n",
" for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n",
" return df\n",
"\n",
"\n",
"def geometry(df):\n",
" \"\"\"๊ฐ ํญ์ ๊ณผ ๊ฐ์ ์์ฐ ์ฌ์ด ์ต๊ทผ์ ์ (CPA) ๊ฑฐ๋ฆฌ์ ๋๋ฌ ์์์๊ฐ(TTI, s)์ ๊ณ์ฐํ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 4] ํต์ฌ ๋ฐฉ์ด์์ฐ(์งํ์ยท์ธํ๋ผ) ๊ฑฐ๋ฆฌ ๋ฐ ๋๋ฌ ์์ ์๊ฐ(TTI) ์ฐ๊ณ ๋ค์ฐจ์ ์ํ ํ๊ฐ ๊ณ ๋ํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n",
"\n",
"\n",
"def features(df):\n",
" # [TODO : ํ์ ์ค์ต 4] ํต์ฌ ๋ฐฉ์ด์์ฐ(์งํ์ยท์ธํ๋ผ) ๊ฑฐ๋ฆฌ ๋ฐ ๋๋ฌ ์์ ์๊ฐ(TTI) ์ฐ๊ณ ๋ค์ฐจ์ ์ํ ํ๊ฐ ๊ณ ๋ํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def train_classifier(seeds=(101, 102, 103, 104)):\n",
" \"\"\"ํ์ต์ฉ ๊ฐ์ ์ํฉ(์ํ ์ํฉ๊ณผ ๋ค๋ฅธ ์๋)์ผ๋ก '์ํ ์ ์ฌ๋' ๋ถ๋ฅ๊ธฐ๋ฅผ ํ์ตํ๋ค.\"\"\"\n",
" tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n",
" clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n",
" clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n",
" return clf\n",
"\n",
"\n",
"def baseline_rank(df):\n",
" \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋๋ฌ์๊ฐ๋ง์ผ๋ก 1์ฐจ ์ํ ์ ์๋ฅผ ๋งค๊ธด๋ค(์์ฐ ๊ฐ์นยท์ ํ ํ๋ณ ์์).\"\"\"\n",
" _, tti = geometry(df)\n",
" t_min = tti.min(axis=1)\n",
" out = df[[\"id\"]].copy()\n",
" out[\"tti_s\"] = t_min\n",
" out[\"score\"] = np.exp(-t_min / 300)\n",
" out[\"urgent_pred\"] = t_min <= TTI_URGENT\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"\n",
"def advanced_rank(df, clf, w=None):\n",
" \"\"\"๊ณ ๋ํ: ์์ฐ ๊ฐ์น x ๋๋ฌ์๊ฐ x ์ํ ์ ์ฌ๋, ์ด๋ํ ๋ถ์ผ์น(์คํธํ ์์ฌ) ํญ์ ์ ๋ณ๋ ํ์ํ๋ค.\"\"\"\n",
" w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n",
" _, tti = geometry(df)\n",
" val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n",
" asset_score = val * np.exp(-tti / w[\"tau\"])\n",
" best = asset_score.argmax(axis=1)\n",
" p = clf.predict_proba(features(df)[FEATS])[:, 1]\n",
" spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n",
" out = df[[\"id\"]].copy()\n",
" out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n",
" out[\"tti_s\"] = tti[np.arange(len(df)), best]\n",
" out[\"p_threat\"] = p\n",
" out[\"spoof_flag\"] = spoof\n",
" out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n",
" out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"clf = train_classifier()\n",
"rows = []\n",
"for seed in (7, 8, 9): # ์ํ์ฉ ๊ฐ์ ์ํฉ 3๊ฐ(์คํธํ ํญ์ ํฌํจ)\n",
" raid = make_raid(seed, 80, spoof=6)\n",
" t0 = time.perf_counter()\n",
" adv = advanced_rank(raid, clf)\n",
" ms = (time.perf_counter() - t0) * 1000\n",
" base = baseline_rank(raid)\n",
" for name, r in ((\"Baseline\", base), (\"๊ณ ๋ํ\", adv)):\n",
" m = raid.merge(r[[\"id\", \"urgent_pred\"]], on=\"id\")\n",
" y, p = m[\"urgent\"].to_numpy(), m[\"urgent_pred\"].to_numpy()\n",
" rows.append({\"์ํฉ\": seed, \"๋ชจ๋ธ\": name, \"์ฌํ์จ\": (p & y).sum() / y.sum(), \"์ ๋ฐ๋\": (p & y).sum() / max(p.sum(), 1),\n",
" \"๊ธฐ๋งยท์คํธํ ์คํ\": int((p & m[\"type\"].isin([\"decoy\", \"spoof\"]).to_numpy()).sum()),\n",
" \"์ง์ฐ(ms)\": ms if name == \"๊ณ ๋ํ\" else np.nan})\n",
"res = pd.DataFrame(rows)\n",
"print(\"[๋ชจ๋ธ ๋น๊ต] ์ค์ ๊ธด๊ธ ํ์ ๊ธฐ์ค\")\n",
"print(res.round(3).fillna(\"-\").to_string(index=False))\n",
"\n",
"# ๋์ ์ฌ๊ณ์ฐ: 10์ด ๊ฒฝ๊ณผ ํ ์์น๋ฅผ ๊ฐฑ์ ํด ์์ ๋ณํ๋ฅผ ํ์ธ\n",
"raid = make_raid(7, 80, spoof=6)\n",
"r0 = advanced_rank(raid, clf).head(5)\n",
"moved = raid.copy(); moved[\"x\"] += moved[\"vx\"] * 10; moved[\"y\"] += moved[\"vy\"] * 10\n",
"r1 = advanced_rank(moved, clf).head(5)\n",
"print(\"\\n[๋์ ์ฌ๊ณ์ฐ] ์์ 5๊ฐ (ํ์ฌ -> 10์ด ํ)\")\n",
"for a, b in zip(r0.itertuples(), r1.itertuples()):\n",
" print(f\" {a.id} {a.asset} TTI {a.tti_s:5.0f}s ์ ์ {a.score:.2f} -> {b.id} {b.asset} TTI {b.tti_s:5.0f}s ์ ์ {b.score:.2f}\")\n",
"\n",
"adv_res = res[res[\"๋ชจ๋ธ\"] == \"๊ณ ๋ํ\"]\n",
"rec, lat = adv_res[\"์ฌํ์จ\"].mean(), adv_res[\"์ง์ฐ(ms)\"].max()\n",
"print(f\"\\n๋ณตํฉ ์ํ ์ฌํ์จ ํ๊ท {rec:.1%} (๋ชฉํ 85% ์ด์) -> {'๋ฌ์ฑ' if rec >= 0.85 else '๋ฏธ๋ฌ'}\")\n",
"print(f\"์ฒ๋ฆฌ ์ง์ฐ ์ต๋ {lat:.1f} ms (๋ชฉํ 200 ms ์ดํ) -> {'๋ฌ์ฑ' if lat <= 200 else '๋ฏธ๋ฌ'}\")\n",
"print(f\"์คํธํ ์์ฌ ํญ์ ๊ฒฉ๋ฆฌ {int(advanced_rank(raid, clf)['spoof_flag'].sum())}/6๊ฑด (์ด๋ํ ๋ถ์ผ์น = ๋ณด๊ณ ์๋ ๋๋น ์์น ๋์ฝ 2๋ฐฐ ์ด๊ณผ)\")\n",
"print(\"์ ์: ์ํ ์ ์ฌ๋๋ ๊ฐ์ ๋ฐ์ฌ๊ฐ๋ยท์ผ์ ํฉ์ยท๋ฏผ๊ฐ ์๋ต ์ ํธ ๋ฑ ์ถ์ ํน์ง ๊ธฐ๋ฐ์ด๋ฉฐ, ์ต์ข
์๋ณ์ ์ด์ฉ์ ํ์ธ์ด ํ์ํ๋ค.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
04 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๋ชจ๋ธ ๋น๊ต] ์ค์ ๊ธด๊ธ ํ์ ๊ธฐ์ค\n",
" ์ํฉ ๋ชจ๋ธ ์ฌํ์จ ์ ๋ฐ๋ ๊ธฐ๋งยท์คํธํ ์คํ ์ง์ฐ(ms)\n",
" 7 Baseline 1.000 0.721 11 -\n",
" 7 ๊ณ ๋ํ 0.968 1.000 0 7.677\n",
" 8 Baseline 1.000 0.861 3 -\n",
" 8 ๊ณ ๋ํ 1.000 1.000 0 6.501\n",
" 9 Baseline 1.000 0.703 9 -\n",
" 9 ๊ณ ๋ํ 1.000 0.963 0 6.603\n",
"\n",
"[๋์ ์ฌ๊ณ์ฐ] ์์ 5๊ฐ (ํ์ฌ -> 10์ด ํ)\n",
" T07055 A2 TTI 53s ์ ์ 0.60 -> T07055 A2 TTI 43s ์ ์ 0.62\n",
" T07014 A2 TTI 103s ์ ์ 0.57 -> T07014 A2 TTI 93s ์ ์ 0.59\n",
" T07033 A1 TTI 173s ์ ์ 0.56 -> T07033 A1 TTI 163s ์ ์ 0.58\n",
" T07004 A1 TTI 181s ์ ์ 0.55 -> T07004 A1 TTI 171s ์ ์ 0.57\n",
" T07053 A1 TTI 185s ์ ์ 0.54 -> T07053 A1 TTI 175s ์ ์ 0.56\n",
"\n",
"๋ณตํฉ ์ํ ์ฌํ์จ ํ๊ท 98.9% (๋ชฉํ 85% ์ด์) -> ๋ฌ์ฑ\n",
"์ฒ๋ฆฌ ์ง์ฐ ์ต๋ 7.7 ms (๋ชฉํ 200 ms ์ดํ) -> ๋ฌ์ฑ\n",
"์คํธํ ์์ฌ ํญ์ ๊ฒฉ๋ฆฌ 6/6๊ฑด (์ด๋ํ ๋ถ์ผ์น = ๋ณด๊ณ ์๋ ๋๋น ์์น ๋์ฝ 2๋ฐฐ ์ด๊ณผ)\n",
"์ ์: ์ํ ์ ์ฌ๋๋ ๊ฐ์ ๋ฐ์ฌ๊ฐ๋ยท์ผ์ ํฉ์ยท๋ฏผ๊ฐ ์๋ต ์ ํธ ๋ฑ ์ถ์ ํน์ง ๊ธฐ๋ฐ์ด๋ฉฐ, ์ต์ข
์๋ณ์ ์ด์ฉ์ ํ์ธ์ด ํ์ํ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
05. ๋ฐฉ๊ณต ์ ๋๋ฌด๊ธฐ ๊ต์ ์ฐ์ ์์ ๋ญํน ๋ฐ ๋ณตํฉ ์ ๋ขฐ๋ ์ง์ ์ฐ์ถ ๋ชจ๋ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋ฐฉ๊ณต ํฌ๋๋ณ ์์ฌ ์๊ฒฉ ๋ฏธ์ฌ์ผ ์๋ ๋ฐ ๊ต์ ๊ฐ๋ฅ ํ๋ฅ ์ ๋ฐ์ํ์ฌ ์ต์ ๋์ ์์ ๋ฐ ๋ณตํฉ ์ํ ์ ๋ขฐ๋ ์ง์๋ฅผ ์ฐ์ถํ๋ ์์ง์ ๊ฐ๋ฐํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋ณตํฉ ์ ๋ขฐ๋ ์ง์์ High/Med/Low ๋ฑ๊ธ์ผ๋ก ์ค๊ฒฝ๋ณด๋ฅผ ์ค์ด๊ณ , ๊ฐ์ ๋์ ์์ ์ํฉ์ ๋ฐ์ํ '๊ถ๊ณ ' ๋์ ์์๋ฅผ ๋ง๋ ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ํ ์ ์ฌ๋, ์ผ์ ํฉ์, ์ ํธ ์์ ์ฑ, ์ด๋ํ ์ผ๊ด์ฑ์ ๊ฐ์ค ํฉ์ฐํด ์ ๋ขฐ๋ ์ง์๋ฅผ ๋ง๋ค๊ณ ๋ฑ๊ธ ๊ธฐ์ค์ ์ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/verification/ops04_confidence_engine.py` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ ๋ขฐ๋ ๋ฑ๊ธ(High/Med/Low) ๋ถ์ฌ ๊ธฐ์ค์ ๊ตฐ์ฌ์ ํ๋น์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์ค๊ฒฝ๋ณด์จ(FAR) 40% ์ด์ ๊ฐ์ถ, ์ ๋ขฐ๋ ์ค์ฝ์ด ์ฐ์ถ ์ผ๊ด์ฑ 90% ์ด์\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์ค๊ฒฝ๋ณด ํํฐ๋ง ์ ํ ๋น๊ต ์งํ ์ฐ์ถ ๋ฐ ํตํฉ ํ
์คํธ ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ROC-AUC ๊ณก์ ๋ถ์ ๋ฐ ์๊ณ๊ฐ ๋ฏผ๊ฐ๋ ํ
์คํธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์ํ ์ ์ฌ๋ยท์ผ์ ํฉ์ยท์ ํธ ์์ ์ฑยท์ด๋ํ ์ผ๊ด์ฑ์ ๊ฒฐํฉํ ๋ณตํฉ ์ ๋ขฐ๋ ์ง์(High/Med/Low)๋ฅผ ์ฐ์ถํด ์ค๊ฒฝ๋ณด๋ฅผ ์ค์ด๊ณ , ๊ฐ์ ๋์ ์์์ ์์ฌ ์๋๊ณผ ๊ฐ์ ๋์ ์ฑ๊ณต ์ ์๋ฅผ ๋ฐ์ํ '๊ถ๊ณ ' ๋์ ์์๋ฅผ ๋ง๋ ๋ค(๋ชจ๋ ๊ถ๊ณ ๋ ์งํ๊ด ์น์ธ ๋๊ธฐ).\"\"\"\n",
"import numpy as np\n",
"import pandas as pd\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ๋ฐฉ์ด์ง์ญยท๊ฐ์ ๋นํ์ฒด ์์ฑ๊ธฐ ----------------\n",
"# ์ขํ๋ ๊ฐ์ ์งํ์๋ฅผ ์์ ์ผ๋ก ํ km ๋จ์ ํ๋ฉด์ด๋ค. ๋นํ์ฒด ์ ํ์ ์ถ์ ๋ฒ์ฃผ์ด๋ฉฐ\n",
"# ์๋ยท๊ณ ๋ยท๋ฐ์ฌ๊ฐ๋๋ ํ์ต์ฉ ์์ ๋ฒ์๋ก, ์ค์ ๋ฌด๊ธฐ ์ ์ยท์ฌ๊ฑฐ๋ฆฌยทํ๋์ ๋ฌด๊ดํ๋ค.\n",
"ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n",
" [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n",
" [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n",
" [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n",
"TYPES = { # ์ ํ: (์ํ ์ฌ๋ถ, ์๋ m/s ๋ฒ์, ๊ณ ๋ m ๋ฒ์, ์ถํ ๋น์จ)\n",
" \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ ์ ๋ฌด์ธ๊ธฐํ(๊ฐ์)\n",
" \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์ ์ํญํ(๊ฐ์)\n",
" \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋ง์ฒด(๊ฐ์)\n",
" \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํญ๊ธฐ(๊ฐ์)\n",
" \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ ๊ตฐ์ง\n",
"}\n",
"TTI_URGENT = 600 # ๊ธด๊ธ ํ์ ๊ธฐ์ค: ๊ฐ์ ์์ฐ ๋๋ฌ ์์์๊ฐ 600์ด ์ด๋ด\n",
"CPA_KM = 1.5 # ์์ฐ ์๊ณต ํต๊ณผ ํ์ ๋ฐ๊ฒฝ(๊ฐ์)\n",
"\n",
"\n",
"def make_raid(seed, n=80, spoof=0):\n",
" \"\"\"๊ฐ์ ํฌํ ์ํฉ 1ํ๋ถ(๋นํ์ฒด n๊ฐ + ์คํธํ ๊ฐ์ง ํญ์ spoof๊ฐ)์ ๋ง๋ ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" names = list(TYPES)\n",
" rows = []\n",
" for i in range(n + spoof):\n",
" typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n",
" threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n",
" brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n",
" x, y = dist * np.sin(brg), dist * np.cos(brg)\n",
" spd = rng.uniform(*sp); alt = rng.uniform(*al)\n",
" aim = int(rng.integers(0, len(ASSETS)))\n",
" if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ์ ์์ฐ ๋ฐฉํฅ(์์ ๋ฐฉ์ ์ค์ฐจ)\n",
" ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n",
" hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n",
" else:\n",
" hd = rng.uniform(0, 2 * np.pi); aim = -1\n",
" vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n",
" sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n",
" \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n",
" \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n",
" jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n",
" true_tti = np.inf\n",
" if threat:\n",
" d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n",
" true_tti = d_aim * 1000 / spd\n",
" rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n",
" \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n",
" \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n",
" \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n",
" \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n",
" df = pd.DataFrame(rows)\n",
" df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n",
" for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n",
" return df\n",
"\n",
"\n",
"def geometry(df):\n",
" \"\"\"๊ฐ ํญ์ ๊ณผ ๊ฐ์ ์์ฐ ์ฌ์ด ์ต๊ทผ์ ์ (CPA) ๊ฑฐ๋ฆฌ์ ๋๋ฌ ์์์๊ฐ(TTI, s)์ ๊ณ์ฐํ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 5] ๋ฐฉ๊ณต ์ ๋๋ฌด๊ธฐ ๊ต์ ์ฐ์ ์์ ๋ญํน ๋ฐ ๋ณตํฉ ์ ๋ขฐ๋ ์ง์ ์ฐ์ถ ๋ชจ๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n",
"\n",
"\n",
"def features(df):\n",
" # [TODO : ํ์ ์ค์ต 5] ๋ฐฉ๊ณต ์ ๋๋ฌด๊ธฐ ๊ต์ ์ฐ์ ์์ ๋ญํน ๋ฐ ๋ณตํฉ ์ ๋ขฐ๋ ์ง์ ์ฐ์ถ ๋ชจ๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def train_classifier(seeds=(101, 102, 103, 104)):\n",
" \"\"\"ํ์ต์ฉ ๊ฐ์ ์ํฉ(์ํ ์ํฉ๊ณผ ๋ค๋ฅธ ์๋)์ผ๋ก '์ํ ์ ์ฌ๋' ๋ถ๋ฅ๊ธฐ๋ฅผ ํ์ตํ๋ค.\"\"\"\n",
" tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n",
" clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n",
" clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n",
" return clf\n",
"\n",
"\n",
"def baseline_rank(df):\n",
" \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋๋ฌ์๊ฐ๋ง์ผ๋ก 1์ฐจ ์ํ ์ ์๋ฅผ ๋งค๊ธด๋ค(์์ฐ ๊ฐ์นยท์ ํ ํ๋ณ ์์).\"\"\"\n",
" _, tti = geometry(df)\n",
" t_min = tti.min(axis=1)\n",
" out = df[[\"id\"]].copy()\n",
" out[\"tti_s\"] = t_min\n",
" out[\"score\"] = np.exp(-t_min / 300)\n",
" out[\"urgent_pred\"] = t_min <= TTI_URGENT\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"\n",
"def advanced_rank(df, clf, w=None):\n",
" \"\"\"๊ณ ๋ํ: ์์ฐ ๊ฐ์น x ๋๋ฌ์๊ฐ x ์ํ ์ ์ฌ๋, ์ด๋ํ ๋ถ์ผ์น(์คํธํ ์์ฌ) ํญ์ ์ ๋ณ๋ ํ์ํ๋ค.\"\"\"\n",
" w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n",
" _, tti = geometry(df)\n",
" val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n",
" asset_score = val * np.exp(-tti / w[\"tau\"])\n",
" best = asset_score.argmax(axis=1)\n",
" p = clf.predict_proba(features(df)[FEATS])[:, 1]\n",
" spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n",
" out = df[[\"id\"]].copy()\n",
" out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n",
" out[\"tti_s\"] = tti[np.arange(len(df)), best]\n",
" out[\"p_threat\"] = p\n",
" out[\"spoof_flag\"] = spoof\n",
" out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n",
" out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"GRADE = [(0.70, \"High\"), (0.45, \"Med\"), (0.0, \"Low\")]\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ๋์ ์์: ์์ฌ ์๋, ๋ด๋น ์์ฐ, ์ ํ๋ณ '๊ฐ์ ๋์ ์ฑ๊ณต ์ ์'(์ถ์๊ฐ, ์ค์ ๋ฌด๊ธฐ ์ฑ๋ฅ ์๋)\n",
"RESOURCES = pd.DataFrame([[\"R1\", 6, [\"A1\", \"A2\"], {\"uav_type\": 0.8, \"fast_type\": 0.5}],\n",
" [\"R2\", 4, [\"A1\", \"A3\"], {\"uav_type\": 0.7, \"fast_type\": 0.7}],\n",
" [\"R3\", 8, [\"A2\", \"A3\", \"A4\"], {\"uav_type\": 0.6, \"fast_type\": 0.3}]],\n",
" columns=[\"res\", \"remain\", \"covers\", \"score_by_type\"])\n",
"\n",
"\n",
"def confidence(df, clf):\n",
" p = clf.predict_proba(features(df)[FEATS])[:, 1]\n",
" agree = df[\"n_sensors\"].to_numpy() / 3\n",
" stable = 1 - np.clip(df[\"rcs_var\"].to_numpy() / 0.4, 0, 1)\n",
" kin = np.clip(2 - df[\"max_jump\"].to_numpy() / df[\"spd\"].to_numpy(), 0, 1)\n",
" conf = 0.5 * p + 0.2 * agree + 0.15 * stable + 0.15 * kin\n",
" grade = np.select([conf >= GRADE[0][0], conf >= GRADE[1][0]], [\"High\", \"Med\"], \"Low\")\n",
" return conf, grade\n",
"\n",
"\n",
"clf = train_classifier()\n",
"raid = make_raid(8, 80, spoof=6)\n",
"base = raid.merge(baseline_rank(raid)[[\"id\", \"urgent_pred\"]], on=\"id\")\n",
"adv = raid.merge(advanced_rank(raid, clf), on=\"id\")\n",
"conf, grade = confidence(raid, clf)\n",
"adv[\"conf\"] = conf[raid.set_index(\"id\").index.get_indexer(adv[\"id\"])]\n",
"adv[\"grade\"] = grade[raid.set_index(\"id\").index.get_indexer(adv[\"id\"])]\n",
"alert = adv[\"urgent_pred\"] & (adv[\"grade\"] != \"Low\")\n",
"\n",
"far_b = (base[\"urgent_pred\"] & ~base[\"urgent\"]).sum() / base[\"urgent_pred\"].sum()\n",
"far_n = (alert & ~adv[\"urgent\"]).sum() / max(alert.sum(), 1)\n",
"cut = (far_b - far_n) / far_b if far_b > 0 else 1.0\n",
"print(f\"[์ค๊ฒฝ๋ณด์จ] Baseline {far_b:.1%} -> ์ ๋ขฐ๋ ์์ง {far_n:.1%}, ๊ฐ์ถ๋ฅ {cut:.0%} (๋ชฉํ 40% ์ด์) -> {'๋ฌ์ฑ' if cut >= 0.4 else '๋ฏธ๋ฌ'}\")\n",
"print(\"[๋ฑ๊ธ ๊ธฐ์ค] High: ์ง์ 0.70 ์ด์(๋ณต์ ์ผ์ยท์์ ์ ํธ), Med: 0.45~0.70(์ถ๊ฐ ํ์ธ ํ์), Low: 0.45 ๋ฏธ๋ง(๊ฒฝ๋ณด ๋ณด๋ฅ)\")\n",
"print(pd.crosstab(adv[\"type\"], adv[\"grade\"]).to_string())\n",
"\n",
"# ์ผ๊ด์ฑ: ์ธก์ ์ก์(๋ฐ์ฌ๊ฐ๋ยท์๋ ยฑ5%)์ ๋ฃ์ด 20ํ ๋ฐ๋ณตํ ๋ ๋ฑ๊ธ์ด ์ ์ง๋๋ ๋น์จ\n",
"rng = np.random.default_rng(0)\n",
"same = []\n",
"for _ in range(20):\n",
" noisy = raid.copy()\n",
" noisy[\"rcs\"] *= rng.normal(1, 0.05, len(raid)); noisy[\"spd\"] *= rng.normal(1, 0.05, len(raid))\n",
" same.append((confidence(noisy, clf)[1] == grade).mean())\n",
"cons = float(np.mean(same))\n",
"print(f\"\\n์ ๋ขฐ๋ ๋ฑ๊ธ ์ผ๊ด์ฑ {cons:.1%} (๋ชฉํ 90% ์ด์) -> {'๋ฌ์ฑ' if cons >= 0.9 else '๋ฏธ๋ฌ'}\")\n",
"\n",
"# ๊ถ๊ณ ๋์ ์์: ์ ์ ์์ผ๋ก ๋ด๋น ์์ ์ค (๊ฐ์ ๋์ ์ ์ x ์์ฌ ๋น์จ)์ด ๊ฐ์ฅ ํฐ ์์์ '๊ถ๊ณ '\n",
"remain = dict(zip(RESOURCES[\"res\"], RESOURCES[\"remain\"]))\n",
"recs = []\n",
"for r in adv[alert].sort_values(\"score\", ascending=False).itertuples():\n",
" cand = [(row.score_by_type.get(r.type, 0.4) * remain[row.res] / row.remain, row.res) for row in RESOURCES.itertuples()\n",
" if r.asset in row.covers and remain[row.res] > 0]\n",
" res = max(cand)[1] if cand else \"์์๋ถ์กฑ-์๊ธ๋ณด๊ณ \"\n",
" if cand:\n",
" remain[res] -= 1\n",
" recs.append({\"๊ถ๊ณ ์์\": len(recs) + 1, \"ํ์ \": r.id, \"์งํฅ์์ฐ\": r.asset, \"๋๋ฌ์์(s)\": int(r.tti_s), \"์ ๋ขฐ๋\": round(r.conf, 2),\n",
" \"๋ฑ๊ธ\": r.grade, \"๊ถ๊ณ ์์\": res, \"์ํ\": \"์น์ธ ๋๊ธฐ\"})\n",
"rec = pd.DataFrame(recs)\n",
"print(f\"\\n[๊ถ๊ณ ๋์ ์์] ๊ฒฝ๋ณด {len(rec)}๊ฑด ์ค ์์ 10๊ฑด (์๋ ์คํ ์์, ์งํ๊ด ์น์ธ ํ์)\")\n",
"print(rec.head(10).to_string(index=False))\n",
"print(\"์์ฌ ์๋(๊ฐ์):\", remain, f\"| ์์ ๋ถ์กฑ์ผ๋ก ์๊ธ ๋ณด๊ณ ๊ถ๊ณ {int((rec['๊ถ๊ณ ์์'] == '์์๋ถ์กฑ-์๊ธ๋ณด๊ณ ').sum())}๊ฑด\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
05 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[์ค๊ฒฝ๋ณด์จ] Baseline 13.9% -> ์ ๋ขฐ๋ ์์ง 0.0%, ๊ฐ์ถ๋ฅ 100% (๋ชฉํ 40% ์ด์) -> ๋ฌ์ฑ\n",
"[๋ฑ๊ธ ๊ธฐ์ค] High: ์ง์ 0.70 ์ด์(๋ณต์ ์ผ์ยท์์ ์ ํธ), Med: 0.45~0.70(์ถ๊ฐ ํ์ธ ํ์), Low: 0.45 ๋ฏธ๋ง(๊ฒฝ๋ณด ๋ณด๋ฅ)\n",
"grade High Low Med\n",
"type \n",
"bird 0 8 1\n",
"civil 0 0 10\n",
"decoy 1 7 2\n",
"fast_type 15 0 0\n",
"spoof 0 6 0\n",
"uav_type 35 0 1\n",
"\n",
"์ ๋ขฐ๋ ๋ฑ๊ธ ์ผ๊ด์ฑ 99.4% (๋ชฉํ 90% ์ด์) -> ๋ฌ์ฑ\n",
"\n",
"[๊ถ๊ณ ๋์ ์์] ๊ฒฝ๋ณด 31๊ฑด ์ค ์์ 10๊ฑด (์๋ ์คํ ์์, ์งํ๊ด ์น์ธ ํ์)\n",
" ๊ถ๊ณ ์์ ํ์ ์งํฅ์์ฐ ๋๋ฌ์์(s) ์ ๋ขฐ๋ ๋ฑ๊ธ ๊ถ๊ณ ์์ ์ํ\n",
" 1 T08023 A1 163 0.96 High R2 ์น์ธ ๋๊ธฐ\n",
" 2 T08044 A2 99 0.90 High R1 ์น์ธ ๋๊ธฐ\n",
" 3 T08034 A1 170 0.94 High R2 ์น์ธ ๋๊ธฐ\n",
" 4 T08015 A3 69 0.94 High R2 ์น์ธ ๋๊ธฐ\n",
" 5 T08036 A1 192 0.96 High R1 ์น์ธ ๋๊ธฐ\n",
" 6 T08027 A2 107 0.89 High R3 ์น์ธ ๋๊ธฐ\n",
" 7 T08058 A2 143 0.97 High R1 ์น์ธ ๋๊ธฐ\n",
" 8 T08006 A2 145 0.87 High R3 ์น์ธ ๋๊ธฐ\n",
" 9 T08068 A3 118 0.98 High R3 ์น์ธ ๋๊ธฐ\n",
" 10 T08003 A2 153 0.90 High R1 ์น์ธ ๋๊ธฐ\n",
"์์ฌ ์๋(๊ฐ์): {'R1': 0, 'R2': 0, 'R3': 0} | ์์ ๋ถ์กฑ์ผ๋ก ์๊ธ ๋ณด๊ณ ๊ถ๊ณ 13๊ฑด\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
06. ๋ฐฉ๊ณต ์งํ๊ด ๊ฒฐ์ฌ ์ง์์ ์ํ ์ค์๊ฐ ํฌํ๊ฒฝ๋ณด ์ฐ์ ์์ ๋์๋ณด๋ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"ํฌํ ๊ณต๊ฒฉ ์ํฉ์์ ๊ฐ์ฅ ๋จผ์ ์๊ฒฉํด์ผ ํ ํ์ ๋ชฉ๋ก๊ณผ ๋ฐฉ๊ณต๋ง ๋์ ํํฉ์ ์ง๊ด์ ์ผ๋ก ํ์ถํ๋ ํฌํ๊ฒฝ๋ณด ์ฐ์ ์์ ๋์๋ณด๋๋ฅผ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **ํฌํ ์ํฉ์์ ๋จผ์ ํ์ธํ ํ์ ๊ณผ ๋์ ์์ ํํฉ์ ํ ํ๋ฉด์ ๋ณด์ฌ ์ฃผ๋ ์งํ๊ด์ฉ ๋์๋ณด๋๋ฅผ ๋ง๋ ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฐ์ ๋ฐฉ์ด์ง์ญ ์ง๋์ ํญ์ ์ ์ฐ์ ์์ ์(P1 180์ด ์ด๋ด, P2 600์ด ์ด๋ด, ์คํธํ ์์ฌ)์ผ๋ก ํ์ํ๊ณ , ๋๋ฌ์๊ฐ ๋ถํฌ์ ๊ฐ์ ๋์ ์์ ์์ฌ ํํฉ์ ๊ณ ์ ๋ฐฐ์น ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/ui/ops04_dashboard.py (๋๋ ์น UI ์ปดํฌ๋ํธ)` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ ์ ์์ ์ํฉํ์ผ๋ก์์ ์ง๊ด์ฑ ๋ฐ ์ธ์ง ๋ถํ ๊ฐ์๋\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ํ๋ฉด ๊ฐฑ์ ์ฃผ๊ธฐ 1์ด ์ด๋ด, UI ์๋ต์ฑ 100ms ์ด๋ด\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์ฃผ์ ์งํ ์๊ฐํ ์ปดํฌ๋ํธ 4์ข
์ด์ ๋ ๋๋ง ๋ฐ ์ค์๊ฐ ๋ฐ์ดํฐ ์ฐ๋\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ์ฌ์ฉ์ ์ธํฐ๋์
ํ
์คํธ ๋ฐ ๋ชจ์ ๋ฐ์ดํฐ ์คํธ๋ฆผ ๋ ๋๋ง ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๋ฐฉ๊ณต ์งํ๊ด ๊ฒฐ์ฌ ์ง์์ฉ ํฌํ๊ฒฝ๋ณด ์ฐ์ ์์ ๋์๋ณด๋๋ฅผ ๊ตฌํํ๋ค. ๊ฐ์ ๋ฐฉ์ด์ง์ญ ์ง๋์ ํ์ ํญ์ ๊ณผ ์ฐ์ ์์๋ฅผ, ์์ ๊ถ๊ณ ์ฐ์ ์์ ๋ชฉ๋กยท๋๋ฌ์๊ฐ ๋ถํฌยท๊ฐ์ ๋์ ์์ ํํฉ์ ํ์ํ๊ณ 1์ด ์ฃผ๊ธฐ ๊ฐฑ์ ์ฒ๋ฆฌ์๊ฐ๊ณผ ์กฐํ ์๋ต์๊ฐ์ ์ธก์ ํ๋ค.\"\"\"\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ๋ฐฉ์ด์ง์ญยท๊ฐ์ ๋นํ์ฒด ์์ฑ๊ธฐ ----------------\n",
"# ์ขํ๋ ๊ฐ์ ์งํ์๋ฅผ ์์ ์ผ๋ก ํ km ๋จ์ ํ๋ฉด์ด๋ค. ๋นํ์ฒด ์ ํ์ ์ถ์ ๋ฒ์ฃผ์ด๋ฉฐ\n",
"# ์๋ยท๊ณ ๋ยท๋ฐ์ฌ๊ฐ๋๋ ํ์ต์ฉ ์์ ๋ฒ์๋ก, ์ค์ ๋ฌด๊ธฐ ์ ์ยท์ฌ๊ฑฐ๋ฆฌยทํ๋์ ๋ฌด๊ดํ๋ค.\n",
"ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n",
" [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n",
" [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n",
" [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n",
"TYPES = { # ์ ํ: (์ํ ์ฌ๋ถ, ์๋ m/s ๋ฒ์, ๊ณ ๋ m ๋ฒ์, ์ถํ ๋น์จ)\n",
" \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ ์ ๋ฌด์ธ๊ธฐํ(๊ฐ์)\n",
" \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์ ์ํญํ(๊ฐ์)\n",
" \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋ง์ฒด(๊ฐ์)\n",
" \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํญ๊ธฐ(๊ฐ์)\n",
" \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ ๊ตฐ์ง\n",
"}\n",
"TTI_URGENT = 600 # ๊ธด๊ธ ํ์ ๊ธฐ์ค: ๊ฐ์ ์์ฐ ๋๋ฌ ์์์๊ฐ 600์ด ์ด๋ด\n",
"CPA_KM = 1.5 # ์์ฐ ์๊ณต ํต๊ณผ ํ์ ๋ฐ๊ฒฝ(๊ฐ์)\n",
"\n",
"\n",
"def make_raid(seed, n=80, spoof=0):\n",
" \"\"\"๊ฐ์ ํฌํ ์ํฉ 1ํ๋ถ(๋นํ์ฒด n๊ฐ + ์คํธํ ๊ฐ์ง ํญ์ spoof๊ฐ)์ ๋ง๋ ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" names = list(TYPES)\n",
" rows = []\n",
" for i in range(n + spoof):\n",
" typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n",
" threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n",
" brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n",
" x, y = dist * np.sin(brg), dist * np.cos(brg)\n",
" spd = rng.uniform(*sp); alt = rng.uniform(*al)\n",
" aim = int(rng.integers(0, len(ASSETS)))\n",
" if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ์ ์์ฐ ๋ฐฉํฅ(์์ ๋ฐฉ์ ์ค์ฐจ)\n",
" ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n",
" hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n",
" else:\n",
" hd = rng.uniform(0, 2 * np.pi); aim = -1\n",
" vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n",
" sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n",
" \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n",
" \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n",
" jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n",
" true_tti = np.inf\n",
" if threat:\n",
" d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n",
" true_tti = d_aim * 1000 / spd\n",
" rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n",
" \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n",
" \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n",
" \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n",
" \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n",
" df = pd.DataFrame(rows)\n",
" df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n",
" for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n",
" return df\n",
"\n",
"\n",
"def geometry(df):\n",
" \"\"\"๊ฐ ํญ์ ๊ณผ ๊ฐ์ ์์ฐ ์ฌ์ด ์ต๊ทผ์ ์ (CPA) ๊ฑฐ๋ฆฌ์ ๋๋ฌ ์์์๊ฐ(TTI, s)์ ๊ณ์ฐํ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 6] ๋ฐฉ๊ณต ์งํ๊ด ๊ฒฐ์ฌ ์ง์์ ์ํ ์ค์๊ฐ ํฌํ๊ฒฝ๋ณด ์ฐ์ ์์ ๋์๋ณด๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n",
"\n",
"\n",
"def features(df):\n",
" # [TODO : ํ์ ์ค์ต 6] ๋ฐฉ๊ณต ์งํ๊ด ๊ฒฐ์ฌ ์ง์์ ์ํ ์ค์๊ฐ ํฌํ๊ฒฝ๋ณด ์ฐ์ ์์ ๋์๋ณด๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def train_classifier(seeds=(101, 102, 103, 104)):\n",
" \"\"\"ํ์ต์ฉ ๊ฐ์ ์ํฉ(์ํ ์ํฉ๊ณผ ๋ค๋ฅธ ์๋)์ผ๋ก '์ํ ์ ์ฌ๋' ๋ถ๋ฅ๊ธฐ๋ฅผ ํ์ตํ๋ค.\"\"\"\n",
" tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n",
" clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n",
" clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n",
" return clf\n",
"\n",
"\n",
"def baseline_rank(df):\n",
" \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋๋ฌ์๊ฐ๋ง์ผ๋ก 1์ฐจ ์ํ ์ ์๋ฅผ ๋งค๊ธด๋ค(์์ฐ ๊ฐ์นยท์ ํ ํ๋ณ ์์).\"\"\"\n",
" _, tti = geometry(df)\n",
" t_min = tti.min(axis=1)\n",
" out = df[[\"id\"]].copy()\n",
" out[\"tti_s\"] = t_min\n",
" out[\"score\"] = np.exp(-t_min / 300)\n",
" out[\"urgent_pred\"] = t_min <= TTI_URGENT\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"\n",
"def advanced_rank(df, clf, w=None):\n",
" \"\"\"๊ณ ๋ํ: ์์ฐ ๊ฐ์น x ๋๋ฌ์๊ฐ x ์ํ ์ ์ฌ๋, ์ด๋ํ ๋ถ์ผ์น(์คํธํ ์์ฌ) ํญ์ ์ ๋ณ๋ ํ์ํ๋ค.\"\"\"\n",
" w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n",
" _, tti = geometry(df)\n",
" val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n",
" asset_score = val * np.exp(-tti / w[\"tau\"])\n",
" best = asset_score.argmax(axis=1)\n",
" p = clf.predict_proba(features(df)[FEATS])[:, 1]\n",
" spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n",
" out = df[[\"id\"]].copy()\n",
" out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n",
" out[\"tti_s\"] = tti[np.arange(len(df)), best]\n",
" out[\"p_threat\"] = p\n",
" out[\"spoof_flag\"] = spoof\n",
" out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n",
" out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"clf = train_classifier()\n",
"raid = make_raid(9, 100, spoof=6)\n",
"RES = {\"R1\": [6, 6], \"R2\": [4, 4], \"R3\": [8, 8]} # [์์ฌ, ์ต์ด] ๊ฐ์ ๋์ ์์\n",
"\n",
"\n",
"def refresh(df):\n",
" r = advanced_rank(df, clf).merge(df[[\"id\", \"x\", \"y\", \"vx\", \"vy\"]], on=\"id\")\n",
" r[\"prio\"] = np.where(r[\"urgent_pred\"] & (r[\"tti_s\"] <= 180), \"P1\", np.where(r[\"urgent_pred\"], \"P2\", np.where(r[\"spoof_flag\"], \"SPOOF\", \"P3\")))\n",
" return r\n",
"\n",
"\n",
"# 1) 1์ด ์ฃผ๊ธฐ ๊ฐฑ์ 10ํ ๋ชจ์: ๊ฐฑ์ ์ฒ๋ฆฌ์๊ฐ๊ณผ ์กฐํ(ํํฐยท์ ๋ ฌ) ์๋ต์๊ฐ ์ธก์ \n",
"upd, qry = [], []\n",
"for k in range(10):\n",
" t0 = time.perf_counter(); board = refresh(raid); upd.append((time.perf_counter() - t0) * 1000)\n",
" t0 = time.perf_counter(); view = board[board[\"prio\"].isin([\"P1\", \"P2\"])].sort_values(\"tti_s\").head(12); qry.append((time.perf_counter() - t0) * 1000)\n",
" raid = raid.assign(x=raid[\"x\"] + raid[\"vx\"], y=raid[\"y\"] + raid[\"vy\"])\n",
" raid = raid[np.hypot(raid[\"x\"], raid[\"y\"]) > 0.5].reset_index(drop=True) # ๋๋ฌ ์ฒ๋ฆฌ๋ ํญ์ ์ ๊ฑฐ(๊ฐ์)\n",
"print(f\"๊ฐฑ์ ์ฒ๋ฆฌ์๊ฐ ํ๊ท {np.mean(upd):.1f} ms / ์ต๋ {np.max(upd):.1f} ms (๋ชฉํ: ๊ฐฑ์ ์ฃผ๊ธฐ 1์ด ์ด๋ด) -> {'๋ฌ์ฑ' if max(upd) < 1000 else '๋ฏธ๋ฌ'}\")\n",
"print(f\"์กฐํ ์๋ต์๊ฐ ์ต๋ {np.max(qry):.2f} ms (๋ชฉํ 100 ms ์ด๋ด) -> {'๋ฌ์ฑ' if max(qry) < 100 else '๋ฏธ๋ฌ'}\")\n",
"cnt = board[\"prio\"].value_counts()\n",
"print(f\"ํ์ฌ ํ์: P1(180์ด ์ด๋ด) {cnt.get('P1', 0)}๊ฑด, P2(600์ด ์ด๋ด) {cnt.get('P2', 0)}๊ฑด, ์คํธํ ์์ฌ {cnt.get('SPOOF', 0)}๊ฑด, ๊ธฐํ {cnt.get('P3', 0)}๊ฑด\")\n",
"print(\"\\n[๊ถ๊ณ ์ฐ์ ์์ ๋ชฉ๋ก] (P1ยทP2, ๋๋ฌ ์์ ์)\")\n",
"print(view[[\"id\", \"prio\", \"asset\", \"tti_s\", \"p_threat\"]].round({\"tti_s\": 0, \"p_threat\": 2}).rename(\n",
" columns={\"id\": \"ํ์ \", \"prio\": \"๋ฑ๊ธ\", \"asset\": \"์งํฅ์์ฐ\", \"tti_s\": \"๋๋ฌ์์(s)\", \"p_threat\": \"์ํ์ ์ฌ๋\"}).to_string(index=False))\n",
"\n",
"# 2) ๋์๋ณด๋(์ด๋์ด ๋ฐฐ๊ฒฝ, P1์ ๋นจ๊ฐ ๊ตต๊ฒ, ์ ๋ณด๋ 4๊ฐ ์์ญ์ผ๋ก ๊ณ ์ ๋ฐฐ์นํด ์ธ์ง ๋ถํ๋ฅผ ์ค์ธ๋ค)\n",
"plt.style.use(\"dark_background\")\n",
"fig = plt.figure(figsize=(14, 7))\n",
"gs = fig.add_gridspec(2, 3)\n",
"a = fig.add_subplot(gs[:, :2])\n",
"col = {\"P1\": \"red\", \"P2\": \"orange\", \"P3\": \"gray\", \"SPOOF\": \"magenta\"}\n",
"for p, g in board.groupby(\"prio\"):\n",
" a.quiver(g[\"x\"], g[\"y\"], g[\"vx\"], g[\"vy\"], color=col[p], scale=3, width=0.003, label=f\"{p} ({len(g)})\")\n",
"a.scatter(ASSETS[\"x\"], ASSETS[\"y\"], marker=\"^\", s=150, c=\"cyan\")\n",
"for r in ASSETS.itertuples():\n",
" a.text(r.x + 0.8, r.y + 0.8, f\"{r.asset} v={r.value}\", color=\"cyan\", fontsize=9)\n",
"for r in board[board[\"prio\"] == \"P1\"].itertuples():\n",
" a.text(r.x, r.y, r.id[-3:], color=\"red\", fontsize=8, fontweight=\"bold\")\n",
"a.set_xlim(-60, 60); a.set_ylim(-60, 60); a.set_aspect(\"equal\"); a.grid(alpha=0.2); a.legend(loc=\"lower left\")\n",
"a.set_title(\"Saturation alert picture (virtual area)\"); a.set_xlabel(\"East (km)\"); a.set_ylabel(\"North (km)\")\n",
"b = fig.add_subplot(gs[0, 2])\n",
"urg = board[board[\"urgent_pred\"]]\n",
"b.hist(urg[\"tti_s\"], bins=np.arange(0, 660, 60), color=\"orange\"); b.axvline(180, c=\"red\", ls=\"--\")\n",
"b.set_title(\"Urgent tracks by TTI (s)\")\n",
"c = fig.add_subplot(gs[1, 2])\n",
"c.barh(list(RES), [v[1] for v in RES.values()], color=\"dimgray\", label=\"initial\")\n",
"c.barh(list(RES), [v[0] for v in RES.values()], color=\"lime\", label=\"remaining\")\n",
"c.set_title(\"Virtual response resources\"); c.legend(fontsize=8)\n",
"plt.suptitle(\"OPS04 dashboard - recommendations only, commander approval required (training data)\")\n",
"plt.tight_layout(); plt.show()\n",
"print(\"\\n๋์๋ณด๋์ ์์๋ ๊ถ๊ณ ์ ๋ณด์ด๋ฉฐ, ๋์ ์ฌ๋ถ๋ ๋ฏธ์
7์ ์งํ๊ด ์น์ธ ์ ์ฐจ์ ๊ต์ ์์นยท์งํ๊ณํต์ ๋ฐ๋ฅธ๋ค.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
06 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"๊ฐฑ์ ์ฒ๋ฆฌ์๊ฐ ํ๊ท 9.4 ms / ์ต๋ 10.9 ms (๋ชฉํ: ๊ฐฑ์ ์ฃผ๊ธฐ 1์ด ์ด๋ด) -> ๋ฌ์ฑ\n",
"์กฐํ ์๋ต์๊ฐ ์ต๋ 1.64 ms (๋ชฉํ 100 ms ์ด๋ด) -> ๋ฌ์ฑ\n",
"ํ์ฌ ํ์: P1(180์ด ์ด๋ด) 13๊ฑด, P2(600์ด ์ด๋ด) 20๊ฑด, ์คํธํ ์์ฌ 6๊ฑด, ๊ธฐํ 67๊ฑด\n",
"\n",
"[๊ถ๊ณ ์ฐ์ ์์ ๋ชฉ๋ก] (P1ยทP2, ๋๋ฌ ์์ ์)\n",
" ํ์ ๋ฑ๊ธ ์งํฅ์์ฐ ๋๋ฌ์์(s) ์ํ์ ์ฌ๋\n",
"T09027 P1 A3 0.0 1.00\n",
"T09017 P1 A4 70.0 1.00\n",
"T09072 P1 A1 76.0 1.00\n",
"T09047 P1 A2 78.0 0.99\n",
"T09038 P1 A3 85.0 0.96\n",
"T09069 P1 A4 97.0 1.00\n",
"T09049 P1 A3 136.0 0.99\n",
"T09023 P1 A4 147.0 1.00\n",
"T09092 P1 A3 149.0 0.97\n",
"T09006 P1 A1 155.0 0.93\n",
"T09089 P1 A2 160.0 1.00\n",
"T09026 P1 A2 167.0 1.00\n",
"\n",
"๋์๋ณด๋์ ์์๋ ๊ถ๊ณ ์ ๋ณด์ด๋ฉฐ, ๋์ ์ฌ๋ถ๋ ๋ฏธ์
7์ ์งํ๊ด ์น์ธ ์ ์ฐจ์ ๊ต์ ์์นยท์งํ๊ณํต์ ๋ฐ๋ฅธ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
07. ์๋ ํ ๋น๋ ๊ต์ ์์ ์ฌ์กฐ์ ๋ฐ ๊ธด๊ธ ์๋ ์ ํ์ ์ํ HITL ์น์ธ ์ ์ฐจ ์ค๊ณ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์๋ ๊ณ์ฐ๋ ์๊ฒฉ ์ฐ์ ์์๋ฅผ ๋ฐฉ๊ณต ์งํ๊ด์ด ์ ์ ์ ํ๋จ์ ๋ฐ๋ผ ๊ธด๊ธ ์๋ ๋ณ๊ฒฝํ๊ณ ์น์ธํ๋ HITL ๊ต์ ํต์ ์ํฌํ๋ก์ฐ๋ฅผ ์ฐ๊ณํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์๋ ๊ณ์ฐ๋ ๋์ ์์๋ฅผ ์งํ๊ด์ด ์น์ธยท๋ณด๋ฅยท๋ฐ๋ คยท์์ ๋ณ๊ฒฝํ๊ณ , ๊ธด๊ธ ์ ์๋ ํต์ ๋ก ์ ํํ๋ HITL ์ ์ฐจ๋ฅผ ๋ง๋ ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **AI๋ ๊ถ๊ณ ๋ง ํ๊ณ , ์ด์ฉ์๋ ๋ณด๋ฅยท๋ฐ๋ ค, ์งํ๊ด์ ์น์ธยท์์ ๋ณ๊ฒฝยท์๋ ์ ํ ๊ถํ์ ๊ฐ๋๋ก ์ญํ ์ ๋๋๋ค ๊ณผ์ ์ ์งํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/hitl/ops04_approval_workflow.py ๋ฐ docs/ops04_hitl_guide.md` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์งํ ์ฑ
์ ๋ถ๊ณ์ ๋ช
ํํ ๋ฐ ์ฑ๊ธํ ์๋ ๋จ์ ๋ฐฉ์ง ์ฒด๊ณ ๋ถํฉ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์ธ๊ฐ ๊ฒํ ํ(Queue) ๋๊ธฐ์๊ฐ ๋ฐ ์น์ธ/๋ฐ๋ ค ์ฒ๋ฆฌ ๋ก๊น
์ฑ๊ณต๋ฅ 100%\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์น์ธ/๋ฐ๋ ค ์ธํฐํ์ด์ค ๋์ ๋ฐ ๊ฐ์ฌ ๋ก๊ทธ(Audit Log) ์์ฑ ๊ฒ์ฆ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๋ชจ์ ์กฐ์์ ์น์ธ/๋ฐ๋ ค ์๋ฎฌ๋ ์ด์
๋ฐ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# [TODO : ํ์ ์ค์ต 7] ์๋ ํ ๋น๋ ๊ต์ ์์ ์ฌ์กฐ์ ๋ฐ ๊ธด๊ธ ์๋ ์ ํ์ ์ํ HITL ์น์ธ ์ ์ฐจ ์ค๊ณ\n",
"# ๋ฏธ์
์ง์นจ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค์ ํ์ธํ๊ณ ์ฝ๋๋ฅผ ์คํ ๋ฐ ํ๋ํ์ธ์.\n",
"\"\"\"์๋ ๊ณ์ฐ๋ ๋์ ์ฐ์ ์์๋ฅผ ๋ฐฉ๊ณต ์งํ๊ด์ด ์น์ธยท๋ณด๋ฅยท๋ฐ๋ คํ๊ฑฐ๋ ์๋์ผ๋ก ์์๋ฅผ ๋ฐ๊พธ๊ณ , ๊ธด๊ธ ์ ์๋ ํต์ ๋ชจ๋๋ก ์ ํํ๋ ์ธ๊ฐ ๊ฐ์
(HITL) ์น์ธ ์ํฌํ๋ก์ฐ๋ฅผ ๊ตฌํํ๋ค. ์๋ ์น์ธ์ ์์ผ๋ฉฐ, ๋ชจ๋ ์๋(๊ฑฐ๋ถ๋ ์๋ ํฌํจ)๋ ์ญํ ยท๊ทผ๊ฑฐ์ ํจ๊ป ํด์ ์ฒด์ธ ๊ฐ์ฌ ๋ก๊ทธ์ ๊ธฐ๋ก๋๋ค.\"\"\"\n",
"import hashlib\n",
"import json\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"ROLES = {\"CDR-01\": \"commander\", \"OPR-07\": \"operator\", \"OPR-09\": \"operator\"} # ๊ฐ์ ์ด์ฉ์\n",
"PERM = {\"APPROVE\": {\"commander\"}, \"REORDER\": {\"commander\"}, \"MODE\": {\"commander\"},\n",
" \"HOLD\": {\"commander\", \"operator\"}, \"REJECT\": {\"commander\", \"operator\"}}\n",
"HOLD_LIMIT_S = 30\n",
"\n",
"\n",
"class FakeClock:\n",
" t = 0.0\n",
"\n",
" def __call__(self):\n",
" return self.t\n",
"\n",
"\n",
"class HitlBoard:\n",
" def __init__(self, clock):\n",
" self.clock, self.items, self.log, self.mode = clock, {}, [], \"RECOMMEND\"\n",
" self.attempts = 0\n",
"\n",
" def _record(self, body):\n",
" prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n",
" body = {**body, \"ts\": self.clock(), \"mode\": self.mode}\n",
" h = hashlib.sha256((prev + json.dumps(body, ensure_ascii=False, sort_keys=True)).encode()).hexdigest()\n",
" self.log.append({**body, \"prev\": prev, \"hash\": h})\n",
"\n",
" def verify(self):\n",
" prev = \"GENESIS\"\n",
" for n, r in enumerate(self.log, 1):\n",
" body = {k: v for k, v in r.items() if k not in (\"prev\", \"hash\")}\n",
" if r[\"prev\"] != prev or r[\"hash\"] != hashlib.sha256((prev + json.dumps(body, ensure_ascii=False, sort_keys=True)).encode()).hexdigest():\n",
" return False, n\n",
" prev = r[\"hash\"]\n",
" return True, -1\n",
"\n",
" def submit(self, tid, ai_rank, tti, grade):\n",
" self.items[tid] = {\"id\": tid, \"ai_rank\": ai_rank, \"rank\": ai_rank, \"tti\": tti, \"grade\": grade, \"status\": \"PENDING\",\n",
" \"t_in\": self.clock(), \"t_out\": None, \"hold_at\": None, \"escalated\": False}\n",
" self._record({\"event\": \"SUBMIT\", \"id\": tid, \"ai_rank\": ai_rank, \"by\": \"AI\"})\n",
"\n",
" def act(self, op, action, tid=None, reason=\"\", new_rank=None):\n",
" \"\"\"๋ชจ๋ ์๋๋ ์ฑ๊ณตยท๊ฑฐ๋ถ์ ๋ฌด๊ดํ๊ฒ ๊ฐ์ฌ ๋ก๊ทธ์ ๋จ๊ธด๋ค.\"\"\"\n",
" self.attempts += 1\n",
" err = None\n",
" role = ROLES.get(op)\n",
" it = self.items.get(tid)\n",
" if role is None or role not in PERM.get(action, set()):\n",
" err = f\"๊ถํ ์์({op}:{action})\"\n",
" elif len(reason.strip()) < 6:\n",
" err = \"ํ๋จ ๊ทผ๊ฑฐ 6์ ์ด์ ํ์\"\n",
" elif action != \"MODE\" and (it is None or it[\"status\"] in (\"APPROVED\", \"REJECTED\")):\n",
" err = \"๋์ ์์ ๋๋ ์ด๋ฏธ ์ข
๊ฒฐ\"\n",
" elif action == \"APPROVE\" and it[\"grade\"] == \"Low\":\n",
" err = \"Low ๋ฑ๊ธ์ ์น์ธ ์ ์ถ๊ฐ ํ์ธ(HOLD) ํ์\"\n",
" if err:\n",
" self._record({\"event\": \"REFUSED\", \"action\": action, \"id\": tid, \"operator\": op, \"reason\": reason, \"error\": err})\n",
" return err\n",
" if action == \"MODE\":\n",
" self.mode = \"MANUAL\" if self.mode == \"RECOMMEND\" else \"RECOMMEND\"\n",
" elif action == \"REORDER\":\n",
" it[\"rank\"] = new_rank\n",
" else:\n",
" it[\"status\"] = {\"APPROVE\": \"APPROVED\", \"HOLD\": \"HOLD\", \"REJECT\": \"REJECTED\"}[action]\n",
" it[\"hold_at\"] = self.clock() if action == \"HOLD\" else None\n",
" if action != \"HOLD\":\n",
" it[\"t_out\"] = self.clock()\n",
" self._record({\"event\": action, \"id\": tid, \"operator\": op, \"role\": role, \"reason\": reason, \"new_rank\": new_rank})\n",
" return \"OK\"\n",
"\n",
" def tick(self):\n",
" \"\"\"๋ณด๋ฅ ๊ธฐํ ์ด๊ณผ ์ ์๋ ์น์ธํ์ง ์๊ณ ์งํ๊ด์๊ฒ ์์ (ESCALATE)๋ง ํ๋ค.\"\"\"\n",
" for it in self.items.values():\n",
" if it[\"status\"] == \"HOLD\" and not it[\"escalated\"] and self.clock() - it[\"hold_at\"] > HOLD_LIMIT_S:\n",
" it[\"escalated\"] = True\n",
" self._record({\"event\": \"ESCALATE\", \"id\": it[\"id\"], \"by\": \"SYSTEM\"})\n",
"\n",
" def queue(self):\n",
" todo = [i for i in self.items.values() if i[\"status\"] in (\"PENDING\", \"HOLD\")]\n",
" return sorted(todo, key=lambda i: (not i[\"escalated\"], i[\"rank\"]))\n",
"\n",
"\n",
"clk = FakeClock()\n",
"hb = HitlBoard(clk)\n",
"rng = np.random.default_rng(5)\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฏธ์
5 ๊ถ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ํ 12๊ฑด\n",
"for k in range(12):\n",
" hb.submit(f\"T09{k:03d}\", k + 1, int(rng.integers(60, 600)), [\"High\", \"High\", \"Med\", \"Low\"][k % 4])\n",
"scenario = [ # (๊ฒฝ๊ณผ s, ์ด์ฉ์, ์กฐ์น, ํ์ , ๊ทผ๊ฑฐ, ์ ์์)\n",
" (2, \"OPR-07\", \"APPROVE\", \"T09000\", \"๋๋ฌ ์๋ฐ ํ์ \", None), # ๊ถํ ์์ -> ๊ฑฐ๋ถ\n",
" (3, \"CDR-01\", \"APPROVE\", \"T09000\", \"๋ณต์ ์ผ์ ํ์ธ, ์งํ์ ์งํฅ\", None),\n",
" (5, \"CDR-01\", \"REORDER\", \"T09005\", \"ํ์ฝ๊ณ ์งํฅ ํ์ธ, ์ฐ์ ๋์ ํ์\", 2),\n",
" (6, \"OPR-09\", \"HOLD\", \"T09003\", \"๋จ์ผ ์ผ์, ์ก์ ํ์ธ ์์ฒญ\", None),\n",
" (7, \"CDR-01\", \"APPROVE\", \"T09003\", \"๊ธํจ\", None), # ๊ทผ๊ฑฐ ๋ถ์กฑ -> ๊ฑฐ๋ถ\n",
" (8, \"OPR-07\", \"REJECT\", \"T09007\", \"๋ฏผ๊ฐ ์๋ต ์ ํธ ํ์ธ, ์ค๊ฒฝ๋ณด\", None),\n",
" (10, \"CDR-01\", \"MODE\", None, \"ํต์ ์ง์ฐ์ผ๋ก ๊ธด๊ธ ์๋ ํต์ ์ ํ\", None),\n",
" (12, \"CDR-01\", \"APPROVE\", \"T09001\", \"์๋ ํต์ ์ค ์งํ๊ด ์ง์ ํ๋จ\", None),\n",
" (15, \"CDR-01\", \"APPROVE\", \"T09011\", \"Low ๋ฑ๊ธ์ด๋ ๋๋ฌ ์๋ฐ ํ๋จ\", None), # Low ์น์ธ -> ๊ฑฐ๋ถ\n",
" (20, \"CDR-01\", \"APPROVE\", \"T09001\", \"์ค๋ณต ์น์ธ ์๋ ํ์ธ์ฉ\", None), # ์ด๋ฏธ ์ข
๊ฒฐ -> ๊ฑฐ๋ถ\n",
" (25, \"CDR-01\", \"MODE\", None, \"ํต์ ๋ณต๊ตฌ, ๊ถ๊ณ ๋ชจ๋ ๋ณต๊ท\", None),\n",
"]\n",
"for t, op, a, tid, rsn, nr in scenario:\n",
" clk.t = t; hb.tick()\n",
" res = hb.act(op, a, tid, rsn, nr)\n",
" print(f\"[t={t:>3}s] {op} {a:8s} {tid or '-':7s} -> {res}\")\n",
"clk.t = 45; hb.tick()\n",
"\n",
"print(\"\\n[๊ฒํ ๋๊ธฐ์ด] (์์ ์ฐ์ , ๊ทธ๋ค์ ์์)\")\n",
"print(pd.DataFrame(hb.queue())[[\"id\", \"ai_rank\", \"rank\", \"grade\", \"status\", \"escalated\"]].head(6).to_string(index=False))\n",
"auto = sum(1 for r in hb.log if r[\"event\"] == \"APPROVE\" and r.get(\"operator\") in (None, \"AI\", \"SYSTEM\"))\n",
"logged = sum(1 for r in hb.log if r[\"event\"] not in (\"SUBMIT\", \"ESCALATE\"))\n",
"ok, _ = hb.verify()\n",
"done = [i for i in hb.items.values() if i[\"t_out\"] is not None]\n",
"waits = [i[\"t_out\"] - i[\"t_in\"] for i in done]\n",
"print(f\"\\n์กฐ์น ์๋ {hb.attempts}๊ฑด ์ค ๋ก๊ทธ ๊ธฐ๋ก {logged}๊ฑด -> ๋ก๊น
์ฑ๊ณต๋ฅ {logged / hb.attempts:.0%} (๋ชฉํ 100%) -> {'๋ฌ์ฑ' if logged == hb.attempts else '๋ฏธ๋ฌ'}\")\n",
"print(f\"์ข
๊ฒฐ {len(done)}๊ฑด ๋๊ธฐ์๊ฐ ํ๊ท {np.mean(waits):.1f}s / ์ต๋ {max(waits):.0f}s, ๋ฏธ๊ฒฐ {len(hb.queue())}๊ฑด, ์๋ ์น์ธ {auto}๊ฑด\")\n",
"print(f\"๊ฐ์ฌ ๋ก๊ทธ ๋ฌด๊ฒฐ์ฑ: {'์ ์' if ok else 'ํผ์'} ({len(hb.log)}๊ฑด)\")\n",
"hb.log[3][\"reason\"] = \"์ฌํ ์์ \"\n",
"ok2, n2 = hb.verify()\n",
"print(f\"์ยท๋ณ์กฐ ์ํ(4๋ฒ์งธ ๊ธฐ๋ก ๊ทผ๊ฑฐ ์์ ) -> {'๊ฒ์ถ(' + str(n2) + '๋ฒ ๊ธฐ๋ก)' if not ok2 else '๋ฏธ๊ฒ์ถ'}\")\n",
"print(\"\\n[์งํ ์ฑ
์ ๋ถ๊ณ์ ] AI: ์์ '๊ถ๊ณ '๋ง / ์ด์ฉ์: ๋ณด๋ฅยท๋ฐ๋ ค / ์งํ๊ด: ์น์ธยท์์ ๋ณ๊ฒฝยท์๋ ์ ํ\")\n",
"print(\"์น์ธ์ ์งํ๊ณํต์ ๋์ ์ธ๊ฐ๋ฅผ ์์ฒญํ๋ ๋จ๊ณ์ด๋ฉฐ, ์ค์ ๊ต์ ์ ๊ต์ ์์น๊ณผ ์งํ๊ณํต์ ๋ฐ๋ฅธ๋ค.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
07 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[t= 2s] OPR-07 APPROVE T09000 -> ๊ถํ ์์(OPR-07:APPROVE)\n",
"[t= 3s] CDR-01 APPROVE T09000 -> OK\n",
"[t= 5s] CDR-01 REORDER T09005 -> OK\n",
"[t= 6s] OPR-09 HOLD T09003 -> OK\n",
"[t= 7s] CDR-01 APPROVE T09003 -> ํ๋จ ๊ทผ๊ฑฐ 6์ ์ด์ ํ์\n",
"[t= 8s] OPR-07 REJECT T09007 -> OK\n",
"[t= 10s] CDR-01 MODE - -> OK\n",
"[t= 12s] CDR-01 APPROVE T09001 -> OK\n",
"[t= 15s] CDR-01 APPROVE T09011 -> Low ๋ฑ๊ธ์ ์น์ธ ์ ์ถ๊ฐ ํ์ธ(HOLD) ํ์\n",
"[t= 20s] CDR-01 APPROVE T09001 -> ๋์ ์์ ๋๋ ์ด๋ฏธ ์ข
๊ฒฐ\n",
"[t= 25s] CDR-01 MODE - -> OK\n",
"\n",
"[๊ฒํ ๋๊ธฐ์ด] (์์ ์ฐ์ , ๊ทธ๋ค์ ์์)\n",
" id ai_rank rank grade status escalated\n",
"T09003 4 4 Low HOLD True\n",
"T09005 6 2 High PENDING False\n",
"T09002 3 3 Med PENDING False\n",
"T09004 5 5 High PENDING False\n",
"T09006 7 7 Med PENDING False\n",
"T09008 9 9 High PENDING False\n",
"\n",
"์กฐ์น ์๋ 11๊ฑด ์ค ๋ก๊ทธ ๊ธฐ๋ก 11๊ฑด -> ๋ก๊น
์ฑ๊ณต๋ฅ 100% (๋ชฉํ 100%) -> ๋ฌ์ฑ\n",
"์ข
๊ฒฐ 3๊ฑด ๋๊ธฐ์๊ฐ ํ๊ท 7.7s / ์ต๋ 12s, ๋ฏธ๊ฒฐ 9๊ฑด, ์๋ ์น์ธ 0๊ฑด\n",
"๊ฐ์ฌ ๋ก๊ทธ ๋ฌด๊ฒฐ์ฑ: ์ ์ (24๊ฑด)\n",
"์ยท๋ณ์กฐ ์ํ(4๋ฒ์งธ ๊ธฐ๋ก ๊ทผ๊ฑฐ ์์ ) -> ๊ฒ์ถ(4๋ฒ ๊ธฐ๋ก)\n",
"\n",
"[์งํ ์ฑ
์ ๋ถ๊ณ์ ] AI: ์์ '๊ถ๊ณ '๋ง / ์ด์ฉ์: ๋ณด๋ฅยท๋ฐ๋ ค / ์งํ๊ด: ์น์ธยท์์ ๋ณ๊ฒฝยท์๋ ์ ํ\n",
"์น์ธ์ ์งํ๊ณํต์ ๋์ ์ธ๊ฐ๋ฅผ ์์ฒญํ๋ ๋จ๊ณ์ด๋ฉฐ, ์ค์ ๊ต์ ์ ๊ต์ ์์น๊ณผ ์งํ๊ณํต์ ๋ฐ๋ฅธ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
08. 100๊ธฐ ์ด์ ๋์ ํฌํ ๊ณต๊ฒฉ ๊ฐ์ ์ ์ฅ ๋ชจ์ ๋ถํ ํ
์คํธ ๋ฐ ์ฒ๋ฆฌ ์ง์ฐ ๊ฒ์ฆ\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์๋ฐฑ ๋ฐ์ ๋๋ก /๋ฏธ์ฌ์ผ ๋์ ์ ์
๋ชจ์ ์คํธ๋ ์ค ํ
์คํธ๋ฅผ ์ํํ์ฌ ์์คํ
๋ณ๋ชฉ ๊ตฌ๊ฐ์ ํ๋กํ์ผ๋งํ๊ณ ์ค์๊ฐ ์ฒ๋ฆฌ ์ฑ๋ฅ์ ๊ฒ์ฆํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **100๊ธฐ ์ด์์ด ๋์์ ๋ค์ด์ค๋ ํฌํ ์ํฉ์์ ์์คํ
์ด ๋ฉ์ถ๊ฑฐ๋ ๋ฐ์ดํฐ๋ฅผ ์์ง ์๋์ง ๊ฒ์ฆํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **ํ์ 100๊ธฐ์์ 600๊ธฐ(500% ์ฆ๊ฐ)๊น์ง ๋จ๊ณ์ ์ผ๋ก ๋๋ฆฌ๋ฉฐ ์์ ๋ฉ์์งยท์ค๋ณตยท์์ ๋ค์์ ์ฃผ์
ํ์ฌ ์ฒ๋ฆฌ ์ง์ฐ ๋ฐ ๋ณต์๋ ฅ์ ์ํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `tests/ops04_stress_test.py ๋ฐ ์คํธ๋ ์ค ํ
์คํธ ๋ถ์ ๋ณด๊ณ ์` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ทน๋จ ์ํฉ์์์ ์์ ์คํจ(Fail-safe) ๋ฐ ๊ธฐ๋ฅ ์ถ์ ์๋ ์ ์ ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ๋์ ํธ๋ํฝ 500% ์ฆ๊ฐ ์ ์์คํ
๋ค์ด 0๊ฑด, ๋ฐ์ดํฐ ์ ์ค๋ฅ 0%\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ๋๋ ์๋ฎฌ๋ ์ด์
๋ถํ ์ฃผ์
ํ
์คํธ ํต๊ณผ ๋ฐ ๋ณ๋ชฉ ๊ตฌ๊ฐ ํ๋กํ์ผ๋ง ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๋ถํ ํ
์คํธ ๋๊ตฌ๋ฅผ ํตํ ์ฒ๋ฆฌ๋(Throughput) ๋ฐ ๋ฉ๋ชจ๋ฆฌ ๋์ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"ํ์ 100๊ธฐ ๊ธฐ์ค์์ ์ต๋ 600๊ธฐ(500% ์ฆ๊ฐ)๊น์ง ๊ฐ์ ๋นํ์ฒด๊ฐ ๋์์ ์ ์
๋๋ ํฌํ ๊ณต๊ฒฉ ์ํฉ์ ์์ ๋ฉ์์งยท์ค๋ณตยท์์ ๋ค์์์ ์ฃผ์
ํด ๋ถํ ์ํ์ ์ํํ๊ณ , ๋จ๊ณ๋ณ ์ฒ๋ฆฌ ์ง์ฐยท์์คํ
๋ค์ดยท๋ฐ์ดํฐ ์ ์ค๊ณผ ์๊ฐ ์์ฐ ์ด๊ณผ ์ ๊ธฐ๋ฅ ์ถ์(Fail-safe) ๋์์ ๊ฒ์ฆํ๋ค.\"\"\"\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ๋ฐฉ์ด์ง์ญยท๊ฐ์ ๋นํ์ฒด ์์ฑ๊ธฐ ----------------\n",
"# ์ขํ๋ ๊ฐ์ ์งํ์๋ฅผ ์์ ์ผ๋ก ํ km ๋จ์ ํ๋ฉด์ด๋ค. ๋นํ์ฒด ์ ํ์ ์ถ์ ๋ฒ์ฃผ์ด๋ฉฐ\n",
"# ์๋ยท๊ณ ๋ยท๋ฐ์ฌ๊ฐ๋๋ ํ์ต์ฉ ์์ ๋ฒ์๋ก, ์ค์ ๋ฌด๊ธฐ ์ ์ยท์ฌ๊ฑฐ๋ฆฌยทํ๋์ ๋ฌด๊ดํ๋ค.\n",
"ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n",
" [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n",
" [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n",
" [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n",
"TYPES = { # ์ ํ: (์ํ ์ฌ๋ถ, ์๋ m/s ๋ฒ์, ๊ณ ๋ m ๋ฒ์, ์ถํ ๋น์จ)\n",
" \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ ์ ๋ฌด์ธ๊ธฐํ(๊ฐ์)\n",
" \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์ ์ํญํ(๊ฐ์)\n",
" \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋ง์ฒด(๊ฐ์)\n",
" \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํญ๊ธฐ(๊ฐ์)\n",
" \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ ๊ตฐ์ง\n",
"}\n",
"TTI_URGENT = 600 # ๊ธด๊ธ ํ์ ๊ธฐ์ค: ๊ฐ์ ์์ฐ ๋๋ฌ ์์์๊ฐ 600์ด ์ด๋ด\n",
"CPA_KM = 1.5 # ์์ฐ ์๊ณต ํต๊ณผ ํ์ ๋ฐ๊ฒฝ(๊ฐ์)\n",
"\n",
"\n",
"def make_raid(seed, n=80, spoof=0):\n",
" \"\"\"๊ฐ์ ํฌํ ์ํฉ 1ํ๋ถ(๋นํ์ฒด n๊ฐ + ์คํธํ ๊ฐ์ง ํญ์ spoof๊ฐ)์ ๋ง๋ ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" names = list(TYPES)\n",
" rows = []\n",
" for i in range(n + spoof):\n",
" typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n",
" threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n",
" brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n",
" x, y = dist * np.sin(brg), dist * np.cos(brg)\n",
" spd = rng.uniform(*sp); alt = rng.uniform(*al)\n",
" aim = int(rng.integers(0, len(ASSETS)))\n",
" if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ์ ์์ฐ ๋ฐฉํฅ(์์ ๋ฐฉ์ ์ค์ฐจ)\n",
" ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n",
" hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n",
" else:\n",
" hd = rng.uniform(0, 2 * np.pi); aim = -1\n",
" vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n",
" sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n",
" \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n",
" \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n",
" jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n",
" true_tti = np.inf\n",
" if threat:\n",
" d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n",
" true_tti = d_aim * 1000 / spd\n",
" rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n",
" \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n",
" \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n",
" \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n",
" \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n",
" df = pd.DataFrame(rows)\n",
" df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n",
" for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n",
" return df\n",
"\n",
"\n",
"def geometry(df):\n",
" \"\"\"๊ฐ ํญ์ ๊ณผ ๊ฐ์ ์์ฐ ์ฌ์ด ์ต๊ทผ์ ์ (CPA) ๊ฑฐ๋ฆฌ์ ๋๋ฌ ์์์๊ฐ(TTI, s)์ ๊ณ์ฐํ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 8] 100๊ธฐ ์ด์ ๋์ ํฌํ ๊ณต๊ฒฉ ๊ฐ์ ์ ์ฅ ๋ชจ์ ๋ถํ ํ
์คํธ ๋ฐ ์ฒ๋ฆฌ ์ง์ฐ ๊ฒ์ฆ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n",
"\n",
"\n",
"def features(df):\n",
" # [TODO : ํ์ ์ค์ต 8] 100๊ธฐ ์ด์ ๋์ ํฌํ ๊ณต๊ฒฉ ๊ฐ์ ์ ์ฅ ๋ชจ์ ๋ถํ ํ
์คํธ ๋ฐ ์ฒ๋ฆฌ ์ง์ฐ ๊ฒ์ฆ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def train_classifier(seeds=(101, 102, 103, 104)):\n",
" \"\"\"ํ์ต์ฉ ๊ฐ์ ์ํฉ(์ํ ์ํฉ๊ณผ ๋ค๋ฅธ ์๋)์ผ๋ก '์ํ ์ ์ฌ๋' ๋ถ๋ฅ๊ธฐ๋ฅผ ํ์ตํ๋ค.\"\"\"\n",
" tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n",
" clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n",
" clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n",
" return clf\n",
"\n",
"\n",
"def baseline_rank(df):\n",
" \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋๋ฌ์๊ฐ๋ง์ผ๋ก 1์ฐจ ์ํ ์ ์๋ฅผ ๋งค๊ธด๋ค(์์ฐ ๊ฐ์นยท์ ํ ํ๋ณ ์์).\"\"\"\n",
" _, tti = geometry(df)\n",
" t_min = tti.min(axis=1)\n",
" out = df[[\"id\"]].copy()\n",
" out[\"tti_s\"] = t_min\n",
" out[\"score\"] = np.exp(-t_min / 300)\n",
" out[\"urgent_pred\"] = t_min <= TTI_URGENT\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"\n",
"def advanced_rank(df, clf, w=None):\n",
" \"\"\"๊ณ ๋ํ: ์์ฐ ๊ฐ์น x ๋๋ฌ์๊ฐ x ์ํ ์ ์ฌ๋, ์ด๋ํ ๋ถ์ผ์น(์คํธํ ์์ฌ) ํญ์ ์ ๋ณ๋ ํ์ํ๋ค.\"\"\"\n",
" w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n",
" _, tti = geometry(df)\n",
" val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n",
" asset_score = val * np.exp(-tti / w[\"tau\"])\n",
" best = asset_score.argmax(axis=1)\n",
" p = clf.predict_proba(features(df)[FEATS])[:, 1]\n",
" spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n",
" out = df[[\"id\"]].copy()\n",
" out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n",
" out[\"tti_s\"] = tti[np.arange(len(df)), best]\n",
" out[\"p_threat\"] = p\n",
" out[\"spoof_flag\"] = spoof\n",
" out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n",
" out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"BUDGET_MS = 200 # 1ํ ์ฒ๋ฆฌ ์๊ฐ ์์ฐ\n",
"clf = train_classifier()\n",
"\n",
"\n",
"def inject(df, rng):\n",
" \"\"\"์์(NaNยท์์ ์๋) 5%, ์ค๋ณต 10%, ์์ ๋ค์๊ธฐ\"\"\"\n",
" d = df.copy()\n",
" bad = rng.random(len(d)) < 0.05\n",
" d.loc[bad, \"spd\"] = rng.choice([np.nan, -1.0], bad.sum())\n",
" dup = d.sample(frac=0.10, random_state=int(rng.integers(1e6)))\n",
" return pd.concat([d, dup]).sample(frac=1, random_state=1).reset_index(drop=True)\n",
"\n",
"\n",
"def process(msgs, degrade=False):\n",
" \"\"\"๊ฒ์ฆ -> ๊ฒฉ๋ฆฌ -> ์ค๋ณต ์ ๊ฑฐ -> ์์ ์ฐ์ . ์ด๋ค ๋ฉ์์ง๋ ์กฐ์ฉํ ๋ฒ๋ฆฌ์ง ์๊ณ ๊ฒฐ๊ณผ/๊ฒฉ๋ฆฌ/์ค๋ณต ์ค ํ๋๋ก ๊ณ์ํ๋ค.\"\"\"\n",
" ok = msgs[\"spd\"].notna() & (msgs[\"spd\"] > 0) & msgs[[\"x\", \"y\"]].notna().all(axis=1)\n",
" quarantine = msgs[~ok]\n",
" good = msgs[ok]\n",
" uniq = good.drop_duplicates(\"id\")\n",
" if degrade: # ๊ธฐ๋ฅ ์ถ์: ๋ถ๋ฅ๊ธฐ ์๋ต, ๋๋ฌ์๊ฐ ์ ์ ๋ ฌ๋ง ์ํ(๊ฒฝ๋ณด ๋๋ฝ ๋ฐฉ์ง ์ฐ์ )\n",
" ranked = baseline_rank(uniq)\n",
" else:\n",
" ranked = advanced_rank(uniq, clf)\n",
" return ranked, len(quarantine), len(good) - len(uniq)\n",
"\n",
"\n",
"def run_stage(msgs, budget_ms):\n",
" \"\"\"์๊ฐ ์์ฐ์ ๋์ผ๋ฉด ๊ธฐ๋ฅ ์ถ์ ๋ชจ๋๋ก ๋ค์ ์ฒ๋ฆฌํ๋ค(Fail-safe).\"\"\"\n",
" t0 = time.perf_counter()\n",
" out = process(msgs)\n",
" ms = (time.perf_counter() - t0) * 1000\n",
" if ms <= budget_ms:\n",
" return out, ms, False\n",
" t0 = time.perf_counter()\n",
" out = process(msgs, degrade=True)\n",
" return out, (time.perf_counter() - t0) * 1000, True\n",
"\n",
"\n",
"rows = []\n",
"rng = np.random.default_rng(8)\n",
"for n in (100, 200, 300, 450, 600):\n",
" raid = make_raid(30 + n, n, spoof=n // 20)\n",
" msgs = inject(raid, rng)\n",
" crash = 0\n",
" try:\n",
" (ranked, nq, ndup), ms, degraded = run_stage(msgs, BUDGET_MS)\n",
" except Exception: # ๋ค์ด ๊ณ์(๋ฐ์ ์์๋ ๋ค์ ๋จ๊ณ ๊ณ์)\n",
" crash, ranked, nq, ndup, ms, degraded = 1, pd.DataFrame(columns=[\"id\"]), 0, 0, np.nan, False\n",
" accounted = len(ranked) + nq + ndup\n",
" expect_ids = set(msgs.loc[msgs[\"spd\"].notna() & (msgs[\"spd\"] > 0), \"id\"])\n",
" lost = len(expect_ids - set(ranked[\"id\"]))\n",
" rows.append({\"๋นํ์ฒด\": n, \"์ฆ๊ฐ์จ\": f\"{(n / 100 - 1):.0%}\", \"๋ฉ์์ง\": len(msgs), \"์ฒ๋ฆฌ(ms)\": round(ms, 1), \"๊ฒฉ๋ฆฌ\": nq, \"์ค๋ณต\": ndup,\n",
" \"๊ณ์๋๋ฝ\": len(msgs) - accounted, \"์ ์ค\": lost, \"๋ค์ด\": crash, \"๊ธฐ๋ฅ์ถ์\": degraded})\n",
"res = pd.DataFrame(rows)\n",
"print(\"[๋ถํ ๋จ๊ณ๋ณ ๊ฒฐ๊ณผ]\")\n",
"print(res.to_string(index=False))\n",
"\n",
"# Fail-safe ํ์ธ: ์์ฐ์ 0.1 ms๋ก ๊ฐ์ ํด ๊ธฐ๋ฅ ์ถ์ ๊ฒฝ๋ก๊ฐ ์ค์ ๋ก ๋์ํ๋์ง ์ํ\n",
"raid = make_raid(99, 600, spoof=30); msgs = inject(raid, rng)\n",
"(r_deg, nq, _), ms, degraded = run_stage(msgs, 0.1)\n",
"valid = set(msgs.loc[msgs[\"spd\"].notna() & (msgs[\"spd\"] > 0), \"id\"])\n",
"urgent_ids = set(raid.loc[raid[\"urgent\"], \"id\"]) & valid\n",
"miss = len(urgent_ids - set(r_deg.loc[r_deg[\"urgent_pred\"], \"id\"]))\n",
"print(f\"\\n[Fail-safe ์ํ] ์์ฐ 0.1 ms ๊ฐ์ -> ๊ธฐ๋ฅ ์ถ์ {'์ ํ' if degraded else '๋ฏธ์ ํ'}, ๊ธด๊ธ ํ์ {len(urgent_ids)}๊ฑด ์ค ๋๋ฝ {miss}๊ฑด\")\n",
"print(f\" ๊ธฐ๋ฅ ์ถ์ ๋ชจ๋๋ ๋๋ฌ์๊ฐ ๊ธฐ์ค ๊ฒฝ๋ณด๋ฅผ ์ ์งํ๊ณ (๋๋ฝ ๋ฐฉ์ง ์ฐ์ ), ๋์ด๋ ์ค๊ฒฝ๋ณด์ ๊ฒฉ๋ฆฌ ๋ฉ์์ง {nq}๊ฑด์ ์ด์ฉ์ ํ์ธ์ผ๋ก ์ฒ๋ฆฌํ๋ค.\")\n",
"print(\"๋ณ๋ชฉ ๊ตฌ๊ฐ: ๋ถ๋ฅ๊ธฐ ์ถ๋ก ยท์์ฐ๋ณ CPA ํ๋ ฌ ๊ณ์ฐ(ํญ์ ์ x ์์ฐ ์)์ ๋น๋ก, 600๊ธฐ์์๋ ์์ฐ ๋ด\")\n",
"\n",
"down, loss = int(res[\"๋ค์ด\"].sum()), int(res[\"์ ์ค\"].sum() + res[\"๊ณ์๋๋ฝ\"].sum())\n",
"print(f\"\\nํธ๋ํฝ 500% ์ฆ๊ฐ ์ ์์คํ
๋ค์ด {down}๊ฑด (๋ชฉํ 0๊ฑด) -> {'๋ฌ์ฑ' if down == 0 else '๋ฏธ๋ฌ'}\")\n",
"print(f\"๋ฐ์ดํฐ ์ ์ค {loss}๊ฑด, ์ ์ค๋ฅ {loss / res['๋ฉ์์ง'].sum():.2%} (๋ชฉํ 0%) -> {'๋ฌ์ฑ' if loss == 0 else '๋ฏธ๋ฌ'}\")\n",
"\n",
"plt.figure(figsize=(7, 4))\n",
"plt.plot(res[\"๋นํ์ฒด\"], res[\"์ฒ๋ฆฌ(ms)\"], \"o-\", label=\"processing time\")\n",
"plt.axhline(200, ls=\"--\", c=\"r\", label=\"budget 200 ms\")\n",
"plt.xlabel(\"Simultaneous tracks\"); plt.ylabel(\"ms\"); plt.title(\"Stress test: latency vs load (virtual)\"); plt.legend(); plt.grid(alpha=.3)\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
08 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๋ถํ ๋จ๊ณ๋ณ ๊ฒฐ๊ณผ]\n",
" ๋นํ์ฒด ์ฆ๊ฐ์จ ๋ฉ์์ง ์ฒ๋ฆฌ(ms) ๊ฒฉ๋ฆฌ ์ค๋ณต ๊ณ์๋๋ฝ ์ ์ค ๋ค์ด ๊ธฐ๋ฅ์ถ์\n",
" 100 0% 115 9.8 2 10 0 0 0 False\n",
" 200 100% 231 9.8 9 20 0 0 0 False\n",
" 300 200% 347 11.8 9 31 0 0 0 False\n",
" 450 350% 519 14.8 27 46 0 0 0 False\n",
" 600 500% 693 10.9 28 60 0 0 0 False\n",
"\n",
"[Fail-safe ์ํ] ์์ฐ 0.1 ms ๊ฐ์ -> ๊ธฐ๋ฅ ์ถ์ ์ ํ, ๊ธด๊ธ ํ์ 197๊ฑด ์ค ๋๋ฝ 0๊ฑด\n",
" ๊ธฐ๋ฅ ์ถ์ ๋ชจ๋๋ ๋๋ฌ์๊ฐ ๊ธฐ์ค ๊ฒฝ๋ณด๋ฅผ ์ ์งํ๊ณ (๋๋ฝ ๋ฐฉ์ง ์ฐ์ ), ๋์ด๋ ์ค๊ฒฝ๋ณด์ ๊ฒฉ๋ฆฌ ๋ฉ์์ง 23๊ฑด์ ์ด์ฉ์ ํ์ธ์ผ๋ก ์ฒ๋ฆฌํ๋ค.\n",
"๋ณ๋ชฉ ๊ตฌ๊ฐ: ๋ถ๋ฅ๊ธฐ ์ถ๋ก ยท์์ฐ๋ณ CPA ํ๋ ฌ ๊ณ์ฐ(ํญ์ ์ x ์์ฐ ์)์ ๋น๋ก, 600๊ธฐ์์๋ ์์ฐ ๋ด\n",
"\n",
"ํธ๋ํฝ 500% ์ฆ๊ฐ ์ ์์คํ
๋ค์ด 0๊ฑด (๋ชฉํ 0๊ฑด) -> ๋ฌ์ฑ\n",
"๋ฐ์ดํฐ ์ ์ค 0๊ฑด, ์ ์ค๋ฅ 0.00% (๋ชฉํ 0%) -> ๋ฌ์ฑ\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
09. AAR ๊ธฐ๋ฐ ๋ฐฉ๊ณต ์๊ฒฉ ์์ ํ์ ์ค๋ฅ ๋ถ์ ๋ฐ ์ํ ๊ฐ์ค์น ๋งคํธ๋ฆญ์ค ์ฌ๋ณด์ \n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"AAR ์ฌํ๊ฒํ ๋ฅผ ํตํด ์๊ฒฉ ์์ ์ฐ์ ์ง์ฐ ๋ฐ ๊ฐ์น ํ๊ฐ ์ค๋ฅ ์ฌ๋ก๋ฅผ ๋ถ์ํ๊ณ ์ํ ๊ฐ์ค์น ๋งคํธ๋ฆญ์ค๋ฅผ ์ญํ๋ฅ ์์ ํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **AAR๋ก ์์ ํ์ ์ค๋ฅ์ ์์ธ์ ๋ถ๋ฅํ๊ณ ์ํ ๊ฐ์ค์น ๋งคํธ๋ฆญ์ค๋ฅผ ์ฌ๋ณด์ ํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๋๋ฌ์๊ฐ๋ง ๋ฐ์ํ ์ด๊ธฐ ๊ฐ์ค์น๋ก ๊ฐ์ ์ํฉ์ ํ์ ํด ์ค๋ฅ๋ฅผ '๋น์ํ ๊ณผ๋ ํ์ , ์คํธํ ๋ฏธ๊ฒฉ๋ฆฌ, ๋ฑ๊ธ ๊ฒฝ๊ณ ์ค์ฐจ'๋ก ๋ถ๋ฅ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `docs/ops04_aar_report.md ๋ฐ configs/ops04_tuned_params.yaml` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ค๋ฅ ์์ธ ๊ท๋ช
์ ๊ฐ๊ด์ฑ ๋ฐ ๊ฐ์ ์กฐ์น์ ๋
ผ๋ฆฌ์ ํ๋น์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: AAR ํผ๋๋ฐฑ ์ ์ฉ ํ ์ข
ํฉ ์ ํ๋ 10%p ์ด์ ํฅ์, ์ฌ๋ฐ ์ค๋ฅ 0๊ฑด\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: AAR ๋ณด๊ณ ์ ์์ฑ ๋ฐ ํ๋ผ๋ฏธํฐ ํ๋ ์ ํ ํ๊ท ํ
์คํธ ํต๊ณผ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ํ๊ท ํ
์คํธ(Regression Test) ์ค์ํธ ์ ์ฒด ์๋ ์คํ ๋ฐ ๋น๊ต\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"AAR(์ฌํ๊ฒํ )๋ก ์ด๊ธฐ ์ํ ๊ฐ์ค์น ๋งคํธ๋ฆญ์ค์ ์ฐ์ ์์ ํ์ ์ค๋ฅ๋ฅผ ์ ํ๋ณ๋ก ๋ถ์ํ๊ณ , ํ์ต์ฉ ๊ฐ์ ์ํฉ์์ ๊ฐ์ค์น๋ฅผ ์ฌ๋ณด์ ํ ๋ค ๋ณ๋ ์ํ ์ํฉ์์ ์ ํ๋ ํฅ์๊ณผ ๊ฐ์ ์ค๋ฅ์ ์ฌ๋ฐ ์ฌ๋ถ๋ฅผ ํ์ธํด ์กฐ์ ํ๋ผ๋ฏธํฐ๋ฅผ YAML ๋ฌธ์์ด๋ก ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import itertools\n",
"import numpy as np\n",
"import pandas as pd\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ๋ฐฉ์ด์ง์ญยท๊ฐ์ ๋นํ์ฒด ์์ฑ๊ธฐ ----------------\n",
"# ์ขํ๋ ๊ฐ์ ์งํ์๋ฅผ ์์ ์ผ๋ก ํ km ๋จ์ ํ๋ฉด์ด๋ค. ๋นํ์ฒด ์ ํ์ ์ถ์ ๋ฒ์ฃผ์ด๋ฉฐ\n",
"# ์๋ยท๊ณ ๋ยท๋ฐ์ฌ๊ฐ๋๋ ํ์ต์ฉ ์์ ๋ฒ์๋ก, ์ค์ ๋ฌด๊ธฐ ์ ์ยท์ฌ๊ฑฐ๋ฆฌยทํ๋์ ๋ฌด๊ดํ๋ค.\n",
"ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n",
" [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n",
" [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n",
" [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n",
"TYPES = { # ์ ํ: (์ํ ์ฌ๋ถ, ์๋ m/s ๋ฒ์, ๊ณ ๋ m ๋ฒ์, ์ถํ ๋น์จ)\n",
" \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ ์ ๋ฌด์ธ๊ธฐํ(๊ฐ์)\n",
" \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์ ์ํญํ(๊ฐ์)\n",
" \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋ง์ฒด(๊ฐ์)\n",
" \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํญ๊ธฐ(๊ฐ์)\n",
" \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ ๊ตฐ์ง\n",
"}\n",
"TTI_URGENT = 600 # ๊ธด๊ธ ํ์ ๊ธฐ์ค: ๊ฐ์ ์์ฐ ๋๋ฌ ์์์๊ฐ 600์ด ์ด๋ด\n",
"CPA_KM = 1.5 # ์์ฐ ์๊ณต ํต๊ณผ ํ์ ๋ฐ๊ฒฝ(๊ฐ์)\n",
"\n",
"\n",
"def make_raid(seed, n=80, spoof=0):\n",
" \"\"\"๊ฐ์ ํฌํ ์ํฉ 1ํ๋ถ(๋นํ์ฒด n๊ฐ + ์คํธํ ๊ฐ์ง ํญ์ spoof๊ฐ)์ ๋ง๋ ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" names = list(TYPES)\n",
" rows = []\n",
" for i in range(n + spoof):\n",
" typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n",
" threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n",
" brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n",
" x, y = dist * np.sin(brg), dist * np.cos(brg)\n",
" spd = rng.uniform(*sp); alt = rng.uniform(*al)\n",
" aim = int(rng.integers(0, len(ASSETS)))\n",
" if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ์ ์์ฐ ๋ฐฉํฅ(์์ ๋ฐฉ์ ์ค์ฐจ)\n",
" ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n",
" hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n",
" else:\n",
" hd = rng.uniform(0, 2 * np.pi); aim = -1\n",
" vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n",
" sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n",
" \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n",
" \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n",
" jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n",
" true_tti = np.inf\n",
" if threat:\n",
" d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n",
" true_tti = d_aim * 1000 / spd\n",
" rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n",
" \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n",
" \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n",
" \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n",
" \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n",
" df = pd.DataFrame(rows)\n",
" df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n",
" for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n",
" return df\n",
"\n",
"\n",
"def geometry(df):\n",
" \"\"\"๊ฐ ํญ์ ๊ณผ ๊ฐ์ ์์ฐ ์ฌ์ด ์ต๊ทผ์ ์ (CPA) ๊ฑฐ๋ฆฌ์ ๋๋ฌ ์์์๊ฐ(TTI, s)์ ๊ณ์ฐํ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 9] AAR ๊ธฐ๋ฐ ๋ฐฉ๊ณต ์๊ฒฉ ์์ ํ์ ์ค๋ฅ ๋ถ์ ๋ฐ ์ํ ๊ฐ์ค์น ๋งคํธ๋ฆญ์ค ์ฌ๋ณด์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n",
"\n",
"\n",
"def features(df):\n",
" # [TODO : ํ์ ์ค์ต 9] AAR ๊ธฐ๋ฐ ๋ฐฉ๊ณต ์๊ฒฉ ์์ ํ์ ์ค๋ฅ ๋ถ์ ๋ฐ ์ํ ๊ฐ์ค์น ๋งคํธ๋ฆญ์ค ์ฌ๋ณด์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def train_classifier(seeds=(101, 102, 103, 104)):\n",
" \"\"\"ํ์ต์ฉ ๊ฐ์ ์ํฉ(์ํ ์ํฉ๊ณผ ๋ค๋ฅธ ์๋)์ผ๋ก '์ํ ์ ์ฌ๋' ๋ถ๋ฅ๊ธฐ๋ฅผ ํ์ตํ๋ค.\"\"\"\n",
" tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n",
" clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n",
" clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n",
" return clf\n",
"\n",
"\n",
"def baseline_rank(df):\n",
" \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋๋ฌ์๊ฐ๋ง์ผ๋ก 1์ฐจ ์ํ ์ ์๋ฅผ ๋งค๊ธด๋ค(์์ฐ ๊ฐ์นยท์ ํ ํ๋ณ ์์).\"\"\"\n",
" _, tti = geometry(df)\n",
" t_min = tti.min(axis=1)\n",
" out = df[[\"id\"]].copy()\n",
" out[\"tti_s\"] = t_min\n",
" out[\"score\"] = np.exp(-t_min / 300)\n",
" out[\"urgent_pred\"] = t_min <= TTI_URGENT\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"\n",
"def advanced_rank(df, clf, w=None):\n",
" \"\"\"๊ณ ๋ํ: ์์ฐ ๊ฐ์น x ๋๋ฌ์๊ฐ x ์ํ ์ ์ฌ๋, ์ด๋ํ ๋ถ์ผ์น(์คํธํ ์์ฌ) ํญ์ ์ ๋ณ๋ ํ์ํ๋ค.\"\"\"\n",
" w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n",
" _, tti = geometry(df)\n",
" val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n",
" asset_score = val * np.exp(-tti / w[\"tau\"])\n",
" best = asset_score.argmax(axis=1)\n",
" p = clf.predict_proba(features(df)[FEATS])[:, 1]\n",
" spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n",
" out = df[[\"id\"]].copy()\n",
" out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n",
" out[\"tti_s\"] = tti[np.arange(len(df)), best]\n",
" out[\"p_threat\"] = p\n",
" out[\"spoof_flag\"] = spoof\n",
" out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n",
" out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"LEVELS = [\"๊ธด๊ธ\", \"์ฃผ์\", \"์ผ๋ฐ\"]\n",
"\n",
"\n",
"def truth(df):\n",
" return np.select([df[\"threat\"] & (df[\"true_tti\"] <= 300), df[\"threat\"] & (df[\"true_tti\"] <= 900)], [\"๊ธด๊ธ\", \"์ฃผ์\"], \"์ผ๋ฐ\")\n",
"\n",
"\n",
"def judge(df, clf, W):\n",
" \"\"\"์ํ ์ง์ = exp(-TTI/300) x ์ํ์ ์ฌ๋^w_cls x (1 - w_spoof x ์คํธํ์์ฌ) -> ์๊ณ๊ฐ์ผ๋ก 3๋จ๊ณ ํ์ \"\"\"\n",
" _, tti = geometry(df)\n",
" t = tti.min(axis=1)\n",
" p = clf.predict_proba(features(df)[FEATS])[:, 1]\n",
" spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > 2.0\n",
" idx = np.exp(-t / 300) * p ** W[\"w_cls\"] * (1 - W[\"w_spoof\"] * spoof)\n",
" return np.select([idx >= W[\"th_urgent\"], idx >= W[\"th_watch\"]], [\"๊ธด๊ธ\", \"์ฃผ์\"], \"์ผ๋ฐ\")\n",
"\n",
"\n",
"def acc(df, clf, W):\n",
" return (judge(df, clf, W) == truth(df)).mean()\n",
"\n",
"\n",
"clf = train_classifier()\n",
"W0 = {\"w_cls\": 0.0, \"w_spoof\": 0.0, \"th_urgent\": 0.37, \"th_watch\": 0.05} # ์ด๊ธฐ: ๋๋ฌ์๊ฐ๋ง ๋ฐ์\n",
"aar = make_raid(7, 80, spoof=6) # AAR ๋์ ์ํฉ\n",
"pred0, y = judge(aar, clf, W0), truth(aar)\n",
"err = aar[pred0 != y].assign(pred=pred0[pred0 != y], true=y[pred0 != y])\n",
"\n",
"\n",
"def cause(r):\n",
" if r.type == \"spoof\":\n",
" return \"์คํธํ ํญ์ ๋ฏธ๊ฒฉ๋ฆฌ\"\n",
" if r.type in (\"decoy\", \"bird\", \"civil\"):\n",
" return \"๋น์ํ(๊ธฐ๋ง์ฒดยท์กฐ๋ฅยท๋ฏผํญ๊ธฐ) ๊ณผ๋ ํ์ \"\n",
" return \"๋ฑ๊ธ ๊ฒฝ๊ณ ์ค์ฐจ(๋๋ฌ์๊ฐ ์ถ์ )\"\n",
"\n",
"\n",
"err[\"์์ธ\"] = [cause(r) for r in err.itertuples()]\n",
"print(f\"[AAR] ์ด๊ธฐ ๊ฐ์ค์น ์ ํ๋ {acc(aar, clf, W0):.1%}, ์ค๋ฅ {len(err)}๊ฑด\")\n",
"print(err[\"์์ธ\"].value_counts().to_string())\n",
"print(err[[\"id\", \"type\", \"true\", \"pred\", \"์์ธ\"]].head(5).to_string(index=False))\n",
"\n",
"# ์ฌ๋ณด์ : ํ์ต์ฉ ๊ฐ์ ์ํฉ 3๊ฐ(์ํ๊ณผ ๋ค๋ฅธ ์๋)์์ ๊ฒฉ์ ํ์\n",
"train = [make_raid(s, 80, spoof=6) for s in (201, 202, 203)]\n",
"grid = itertools.product([0.0, 0.5, 1.0, 2.0, 4.0], [1.0, 0.0], [0.30, 0.37, 0.45], [0.03, 0.05, 0.08])\n",
"cands = [{\"w_cls\": a, \"w_spoof\": b, \"th_urgent\": c, \"th_watch\": d} for a, b, c, d in grid]\n",
"aar_idx = [i for i in np.where(pred0 != y)[0] if cause(aar.iloc[i]) != \"๋ฑ๊ธ ๊ฒฝ๊ณ ์ค์ฐจ(๋๋ฌ์๊ฐ ์ถ์ )\"]\n",
"\n",
"\n",
"def recurrence(W):\n",
" p = judge(aar, clf, W)\n",
" return int(sum(p[i] != y[i] for i in aar_idx))\n",
"\n",
"\n",
"score = {i: np.mean([acc(r, clf, W) for r in train]) for i, W in enumerate(cands)}\n",
"top = max(score.values())\n",
"# ์ญํ๋ฅ: ํ์ต ์ ํ๋ ์ต๊ณ ์น์ 0.5%p ์ด๋ด์ธ ํ๋ณด ์ค AAR ์ง์ ์ค๋ฅ ์ฌ๋ฐ์ด ๊ฐ์ฅ ์ ์ ๊ฐ์ค์น๋ฅผ ์ฑํ\n",
"best = min((W for i, W in enumerate(cands) if score[i] >= top - 0.005), key=lambda W: (recurrence(W), -np.mean([acc(r, clf, W) for r in train])))\n",
"tests = [make_raid(s, 80, spoof=6) for s in (8, 9, 10)]\n",
"a0 = np.mean([acc(r, clf, W0) for r in tests]); a1 = np.mean([acc(r, clf, best) for r in tests])\n",
"pred1 = judge(aar, clf, best)\n",
"recur = recurrence(best)\n",
"boundary_left = int(sum((pred1[i] != y[i]) for i in np.where(pred0 != y)[0]))\n",
"print(\"\\n[์ฌ๋ณด์ ๊ฒฐ๊ณผ] (์ํ ์ํฉ 3๊ฐ ํ๊ท , ํ์ต์ ์ฐ์ง ์์ ์ํฉ)\")\n",
"print(f\" ์ข
ํฉ ์ ํ๋ {a0:.1%} -> {a1:.1%} ({(a1 - a0) * 100:+.1f}%p, ๋ชฉํ +10%p ์ด์) -> {'๋ฌ์ฑ' if a1 - a0 >= 0.10 else '๋ฏธ๋ฌ'}\")\n",
"print(f\" AAR ๋์ ์ํฉ ์ ํ๋ {acc(aar, clf, W0):.1%} -> {acc(aar, clf, best):.1%}\")\n",
"print(f\" AAR ์ง์ ์ค๋ฅ(๊ธฐ๋งยท์คํธํยท๋น์ํ) ์ฌ๋ฐ {recur}๊ฑด (๋ชฉํ 0๊ฑด) -> {'๋ฌ์ฑ' if recur == 0 else '๋ฏธ๋ฌ'}, ๋ฑ๊ธ ๊ฒฝ๊ณ ์ค๋ฅ ํฌํจ ์์ฌ {boundary_left}๊ฑด\")\n",
"for i in aar_idx:\n",
" if pred1[i] != y[i]:\n",
" r = aar.iloc[i]\n",
" print(f\" ์ฌ๋ฐ ์ฌ๋ก {r.id}({r.type}): ์์ฐ ๊ทผ์ (๋๋ฌ {geometry(aar.iloc[[i]])[1].min():.0f}s)์ผ๋ก ์ง์๊ฐ '์ฃผ์' ์๊ณ๋ฅผ ๋์ -> ๊ทผ๊ฑฐ๋ฆฌ ๋ฏธํ์ธ ํญ์ ์ ์ด์ฉ์ ์ก์ยท์ถ๊ฐ ์ผ์ ํ์ธ์ผ๋ก ์ฒ๋ฆฌ\")\n",
"print(pd.crosstab(pd.Series(truth(tests[0]), name=\"true\"), pd.Series(judge(tests[0], clf, best), name=\"pred\")).to_string())\n",
"print(\"\\n# configs/ops04_tuned_params.yaml\")\n",
"print(f\"\"\"threat_matrix:\n",
" w_cls: {best['w_cls']} # ์ํ ์ ์ฌ๋ ๋ฐ์ ์ง์ (์ด๊ธฐ 0.0)\n",
" w_spoof: {best['w_spoof']} # ์คํธํ ์์ฌ ๊ฐ์ (์ด๊ธฐ 0.0)\n",
" th_urgent: {best['th_urgent']} # ๊ธด๊ธ ์๊ณ (exp(-TTI/300) ๊ธฐ์ค)\n",
" th_watch: {best['th_watch']} # ์ฃผ์ ์๊ณ\n",
"evaluation:\n",
" baseline_acc: {a0:.3f}\n",
" tuned_acc: {a1:.3f}\n",
"note: \"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ์ถ์ ์ํ ์ง์ ๋งค๊ฐ๋ณ์์ด๋ฉฐ ์ค์ ๋ฌด๊ธฐ ์ ์๊ณผ ๋ฌด๊ด\"\n",
"\"\"\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
09 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[AAR] ์ด๊ธฐ ๊ฐ์ค์น ์ ํ๋ 75.6%, ์ค๋ฅ 21๊ฑด\n",
"์์ธ\n",
"๋น์ํ(๊ธฐ๋ง์ฒดยท์กฐ๋ฅยท๋ฏผํญ๊ธฐ) ๊ณผ๋ ํ์ 13\n",
"์คํธํ ํญ์ ๋ฏธ๊ฒฉ๋ฆฌ 5\n",
"๋ฑ๊ธ ๊ฒฝ๊ณ ์ค์ฐจ(๋๋ฌ์๊ฐ ์ถ์ ) 3\n",
" id type true pred ์์ธ\n",
"T07002 uav_type ์ฃผ์ ๊ธด๊ธ ๋ฑ๊ธ ๊ฒฝ๊ณ ์ค์ฐจ(๋๋ฌ์๊ฐ ์ถ์ )\n",
"T07005 uav_type ์ฃผ์ ๊ธด๊ธ ๋ฑ๊ธ ๊ฒฝ๊ณ ์ค์ฐจ(๋๋ฌ์๊ฐ ์ถ์ )\n",
"T07010 decoy ์ผ๋ฐ ์ฃผ์ ๋น์ํ(๊ธฐ๋ง์ฒดยท์กฐ๋ฅยท๋ฏผํญ๊ธฐ) ๊ณผ๋ ํ์ \n",
"T07020 decoy ์ผ๋ฐ ์ฃผ์ ๋น์ํ(๊ธฐ๋ง์ฒดยท์กฐ๋ฅยท๋ฏผํญ๊ธฐ) ๊ณผ๋ ํ์ \n",
"T07021 decoy ์ผ๋ฐ ์ฃผ์ ๋น์ํ(๊ธฐ๋ง์ฒดยท์กฐ๋ฅยท๋ฏผํญ๊ธฐ) ๊ณผ๋ ํ์ \n",
"\n",
"[์ฌ๋ณด์ ๊ฒฐ๊ณผ] (์ํ ์ํฉ 3๊ฐ ํ๊ท , ํ์ต์ ์ฐ์ง ์์ ์ํฉ)\n",
" ์ข
ํฉ ์ ํ๋ 84.9% -> 95.3% (+10.5%p, ๋ชฉํ +10%p ์ด์) -> ๋ฌ์ฑ\n",
" AAR ๋์ ์ํฉ ์ ํ๋ 75.6% -> 95.3%\n",
" AAR ์ง์ ์ค๋ฅ(๊ธฐ๋งยท์คํธํยท๋น์ํ) ์ฌ๋ฐ 1๊ฑด (๋ชฉํ 0๊ฑด) -> ๋ฏธ๋ฌ, ๋ฑ๊ธ ๊ฒฝ๊ณ ์ค๋ฅ ํฌํจ ์์ฌ 3๊ฑด\n",
" ์ฌ๋ฐ ์ฌ๋ก T07061(decoy): ์์ฐ ๊ทผ์ (๋๋ฌ 42s)์ผ๋ก ์ง์๊ฐ '์ฃผ์' ์๊ณ๋ฅผ ๋์ -> ๊ทผ๊ฑฐ๋ฆฌ ๋ฏธํ์ธ ํญ์ ์ ์ด์ฉ์ ์ก์ยท์ถ๊ฐ ์ผ์ ํ์ธ์ผ๋ก ์ฒ๋ฆฌ\n",
"pred ๊ธด๊ธ ์ผ๋ฐ ์ฃผ์\n",
"true \n",
"๊ธด๊ธ 18 0 2\n",
"์ผ๋ฐ 0 46 1\n",
"์ฃผ์ 2 0 17\n",
"\n",
"# configs/ops04_tuned_params.yaml\n",
"threat_matrix:\n",
" w_cls: 1.0 # ์ํ ์ ์ฌ๋ ๋ฐ์ ์ง์ (์ด๊ธฐ 0.0)\n",
" w_spoof: 1.0 # ์คํธํ ์์ฌ ๊ฐ์ (์ด๊ธฐ 0.0)\n",
" th_urgent: 0.37 # ๊ธด๊ธ ์๊ณ (exp(-TTI/300) ๊ธฐ์ค)\n",
" th_watch: 0.05 # ์ฃผ์ ์๊ณ\n",
"evaluation:\n",
" baseline_acc: 0.849\n",
" tuned_acc: 0.953\n",
"note: \"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ์ถ์ ์ํ ์ง์ ๋งค๊ฐ๋ณ์์ด๋ฉฐ ์ค์ ๋ฌด๊ธฐ ์ ์๊ณผ ๋ฌด๊ด\"\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
10. ์คํค๋ยท๋ฏธ์ฌ์ผ ํฌํ๊ฒฝ๋ณด ์ฐ์ ์์ ์์คํ
์ต์ข
ํตํฉ ๋ฐ ๋ฐฉ๊ณต์์ ๋ธ๋ฆฌํ ํจํค์ง ์์ฑ\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์คํค๋ยท๋ฏธ์ฌ์ผ ํฌํ๊ฒฝ๋ณด ์ฐ์ ์์ ์์คํ
์ ์ต์ข
ํตํฉํ๊ณ ์งํ๊ด ๊ฒฐ์ฌ ์ง์์ฉ ์ค์ ๋ธ๋ฆฌํ ํจํค์ง์ ์ด์ ๋งค๋ด์ผ์ ์์ฑํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ ์ฒด ํ์ดํ๋ผ์ธ์ ํตํฉํด ์คํยท์์ฒด ์ ๊ฒํ๊ณ ์งํ๊ด ๊ฒฐ์ฌ ์ง์์ฉ ๋ธ๋ฆฌํ ํจํค์ง๋ฅผ ์์ฑํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฒ์ฆยท์ ์ฒ๋ฆฌ, ๋ค์ฐจ์ ํ๊ฐ(์ฌ๋ณด์ ๊ฐ), ์ ๋ขฐ๋ ๋ฑ๊ธ, ๊ถ๊ณ ์์, ์น์ธ ๋๊ธฐ์ด์ ํ ํ์ผ๋ก ๋ฌถ์ด ๊ฐ์ ์ํฉ 5๊ฐ๋ฅผ ์ฒ๋ฆฌ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/main_ops04.py, README.md, docs/ops04_final_report.pdf(md)` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ตฐ ์ค๋ฌด ์ฆ์ ๋์
๊ฐ๋ฅ์ฑ, ์ด์ฉ ์ ๋ขฐ๋ ๋ฐ ์ ๋ต์ ๊ธฐ๋ํจ๊ณผ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: End-to-End ํตํฉ ํ์ดํ๋ผ์ธ ์คํ ์ฑ๊ณต๋ฅ 100%, ํ
์คํธ ์ปค๋ฒ๋ฆฌ์ง 85% ์ด์\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์ํด๋ฆญ ์คํ ์คํฌ๋ฆฝํธ ๋์ ํ์ธ ๋ฐ ์ต์ข
๋ฐํ ์ฐ์ถ๋ฌผ ์์ฑ ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ์ต์ข
๊ฒ์ฆ ์์ํ(ํ๊ฐ๊ด) ๋ชจ์ ์์ฐ ๋ฐ ์ ์ฒด ๊ธฐ๋ฅ ํตํฉ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๋ฉ์์ง ๊ฒ์ฆยท์ ์ฒ๋ฆฌ, ๋ค์ฐจ์ ์ํ ํ๊ฐ(์ฌ๋ณด์ ๊ฐ์ค์น), ์ ๋ขฐ๋ ๋ฑ๊ธ, ๊ถ๊ณ ๋์ ์์, ์งํ๊ด ์น์ธ ๋๊ธฐ์ด, ๋์๋ณด๋๊น์ง ์ ์ฒด ํ์ดํ๋ผ์ธ์ ํ ํ์ผ๋ก ํตํฉ ์คํํ๊ณ , ์์ฒด ์ ๊ฒ(ํจ์ ์ปค๋ฒ๋ฆฌ์ง ํฌํจ) ๊ฒฐ๊ณผ์ ๋ฐฉ๊ณต์์ ๋ธ๋ฆฌํ ์์ฝ์ ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import hashlib\n",
"import json\n",
"import sys\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ๋ฐฉ์ด์ง์ญยท๊ฐ์ ๋นํ์ฒด ์์ฑ๊ธฐ ----------------\n",
"# ์ขํ๋ ๊ฐ์ ์งํ์๋ฅผ ์์ ์ผ๋ก ํ km ๋จ์ ํ๋ฉด์ด๋ค. ๋นํ์ฒด ์ ํ์ ์ถ์ ๋ฒ์ฃผ์ด๋ฉฐ\n",
"# ์๋ยท๊ณ ๋ยท๋ฐ์ฌ๊ฐ๋๋ ํ์ต์ฉ ์์ ๋ฒ์๋ก, ์ค์ ๋ฌด๊ธฐ ์ ์ยท์ฌ๊ฑฐ๋ฆฌยทํ๋์ ๋ฌด๊ดํ๋ค.\n",
"ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n",
" [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n",
" [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n",
" [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n",
"TYPES = { # ์ ํ: (์ํ ์ฌ๋ถ, ์๋ m/s ๋ฒ์, ๊ณ ๋ m ๋ฒ์, ์ถํ ๋น์จ)\n",
" \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ ์ ๋ฌด์ธ๊ธฐํ(๊ฐ์)\n",
" \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์ ์ํญํ(๊ฐ์)\n",
" \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋ง์ฒด(๊ฐ์)\n",
" \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํญ๊ธฐ(๊ฐ์)\n",
" \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ ๊ตฐ์ง\n",
"}\n",
"TTI_URGENT = 600 # ๊ธด๊ธ ํ์ ๊ธฐ์ค: ๊ฐ์ ์์ฐ ๋๋ฌ ์์์๊ฐ 600์ด ์ด๋ด\n",
"CPA_KM = 1.5 # ์์ฐ ์๊ณต ํต๊ณผ ํ์ ๋ฐ๊ฒฝ(๊ฐ์)\n",
"\n",
"\n",
"def make_raid(seed, n=80, spoof=0):\n",
" \"\"\"๊ฐ์ ํฌํ ์ํฉ 1ํ๋ถ(๋นํ์ฒด n๊ฐ + ์คํธํ ๊ฐ์ง ํญ์ spoof๊ฐ)์ ๋ง๋ ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" names = list(TYPES)\n",
" rows = []\n",
" for i in range(n + spoof):\n",
" typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n",
" threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n",
" brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n",
" x, y = dist * np.sin(brg), dist * np.cos(brg)\n",
" spd = rng.uniform(*sp); alt = rng.uniform(*al)\n",
" aim = int(rng.integers(0, len(ASSETS)))\n",
" if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ์ ์์ฐ ๋ฐฉํฅ(์์ ๋ฐฉ์ ์ค์ฐจ)\n",
" ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n",
" hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n",
" else:\n",
" hd = rng.uniform(0, 2 * np.pi); aim = -1\n",
" vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n",
" sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n",
" \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n",
" \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n",
" jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n",
" true_tti = np.inf\n",
" if threat:\n",
" d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n",
" true_tti = d_aim * 1000 / spd\n",
" rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n",
" \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n",
" \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n",
" \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n",
" \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n",
" df = pd.DataFrame(rows)\n",
" df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n",
" for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n",
" return df\n",
"\n",
"\n",
"def geometry(df):\n",
" \"\"\"๊ฐ ํญ์ ๊ณผ ๊ฐ์ ์์ฐ ์ฌ์ด ์ต๊ทผ์ ์ (CPA) ๊ฑฐ๋ฆฌ์ ๋๋ฌ ์์์๊ฐ(TTI, s)์ ๊ณ์ฐํ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 10] ์คํค๋ยท๋ฏธ์ฌ์ผ ํฌํ๊ฒฝ๋ณด ์ฐ์ ์์ ์์คํ
์ต์ข
ํตํฉ ๋ฐ ๋ฐฉ๊ณต์์ ๋ธ๋ฆฌํ ํจํค์ง ์์ฑ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n",
"\n",
"\n",
"def features(df):\n",
" # [TODO : ํ์ ์ค์ต 10] ์คํค๋ยท๋ฏธ์ฌ์ผ ํฌํ๊ฒฝ๋ณด ์ฐ์ ์์ ์์คํ
์ต์ข
ํตํฉ ๋ฐ ๋ฐฉ๊ณต์์ ๋ธ๋ฆฌํ ํจํค์ง ์์ฑ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def train_classifier(seeds=(101, 102, 103, 104)):\n",
" \"\"\"ํ์ต์ฉ ๊ฐ์ ์ํฉ(์ํ ์ํฉ๊ณผ ๋ค๋ฅธ ์๋)์ผ๋ก '์ํ ์ ์ฌ๋' ๋ถ๋ฅ๊ธฐ๋ฅผ ํ์ตํ๋ค.\"\"\"\n",
" tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n",
" clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n",
" clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n",
" return clf\n",
"\n",
"\n",
"def baseline_rank(df):\n",
" \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋๋ฌ์๊ฐ๋ง์ผ๋ก 1์ฐจ ์ํ ์ ์๋ฅผ ๋งค๊ธด๋ค(์์ฐ ๊ฐ์นยท์ ํ ํ๋ณ ์์).\"\"\"\n",
" _, tti = geometry(df)\n",
" t_min = tti.min(axis=1)\n",
" out = df[[\"id\"]].copy()\n",
" out[\"tti_s\"] = t_min\n",
" out[\"score\"] = np.exp(-t_min / 300)\n",
" out[\"urgent_pred\"] = t_min <= TTI_URGENT\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"\n",
"def advanced_rank(df, clf, w=None):\n",
" \"\"\"๊ณ ๋ํ: ์์ฐ ๊ฐ์น x ๋๋ฌ์๊ฐ x ์ํ ์ ์ฌ๋, ์ด๋ํ ๋ถ์ผ์น(์คํธํ ์์ฌ) ํญ์ ์ ๋ณ๋ ํ์ํ๋ค.\"\"\"\n",
" w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n",
" _, tti = geometry(df)\n",
" val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n",
" asset_score = val * np.exp(-tti / w[\"tau\"])\n",
" best = asset_score.argmax(axis=1)\n",
" p = clf.predict_proba(features(df)[FEATS])[:, 1]\n",
" spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n",
" out = df[[\"id\"]].copy()\n",
" out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n",
" out[\"tti_s\"] = tti[np.arange(len(df)), best]\n",
" out[\"p_threat\"] = p\n",
" out[\"spoof_flag\"] = spoof\n",
" out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n",
" out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n",
" return out.sort_values(\"score\", ascending=False)\n",
"\n",
"CONFIG = {\"w_cls\": 1.0, \"w_spoof\": 1.0, \"th_urgent\": 0.37, \"th_watch\": 0.05, \"budget_ms\": 200} # ๋ฏธ์
9 ์ฌ๋ณด์ ๊ฐ\n",
"\n",
"\n",
"def validate(df):\n",
" ok = df[\"spd\"].between(0.1, 400) & df[\"alt\"].between(0, 15000) & df[[\"x\", \"y\"]].abs().le(100).all(axis=1)\n",
" return df[ok], df[~ok]\n",
"\n",
"\n",
"def preprocess(df):\n",
" good, bad = validate(df)\n",
" return good.drop_duplicates(\"id\").reset_index(drop=True), bad\n",
"\n",
"\n",
"def assess(df, clf):\n",
" _, tti = geometry(df)\n",
" best = (ASSETS[\"value\"].to_numpy()[None] * np.exp(-tti / 300)).argmax(axis=1)\n",
" t = tti[np.arange(len(df)), best]\n",
" p = clf.predict_proba(features(df)[FEATS])[:, 1]\n",
" spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > 2.0\n",
" idx = np.exp(-t / 300) * p ** CONFIG[\"w_cls\"] * (1 - CONFIG[\"w_spoof\"] * spoof)\n",
" lvl = np.select([idx >= CONFIG[\"th_urgent\"], idx >= CONFIG[\"th_watch\"]], [\"๊ธด๊ธ\", \"์ฃผ์\"], \"์ผ๋ฐ\")\n",
" conf = 0.5 * p + 0.2 * df[\"n_sensors\"].to_numpy() / 3 + 0.15 * (1 - np.clip(df[\"rcs_var\"].to_numpy() / 0.4, 0, 1)) + 0.15 * (~spoof)\n",
" grade = np.select([conf >= 0.7, conf >= 0.45], [\"High\", \"Med\"], \"Low\")\n",
" return df.assign(asset=ASSETS[\"asset\"].to_numpy()[best], tti_s=t, p=p, spoof_flag=spoof, index_=idx, level=lvl, conf=conf, grade=grade)\n",
"\n",
"\n",
"def recommend(a):\n",
" rec = a[a[\"level\"] != \"์ผ๋ฐ\"].sort_values(\"index_\", ascending=False).reset_index(drop=True)\n",
" rec[\"๊ถ๊ณ ์์\"] = np.arange(1, len(rec) + 1)\n",
" rec[\"์ํ\"] = \"์น์ธ ๋๊ธฐ\" # ์๋ ์คํยท์๋ ์น์ธ ์์\n",
" return rec\n",
"\n",
"\n",
"class Queue:\n",
" def __init__(self):\n",
" self.items, self.log = {}, []\n",
"\n",
" def submit(self, r):\n",
" self.items[r.id] = \"PENDING\"\n",
" prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n",
" body = json.dumps({\"id\": r.id, \"rank\": int(r.๊ถ๊ณ ์์), \"by\": \"AI\"}, sort_keys=True)\n",
" self.log.append({\"body\": body, \"hash\": hashlib.sha256((prev + body).encode()).hexdigest()})\n",
"\n",
"\n",
"def run(seed, clf):\n",
" raw = make_raid(seed, 120, spoof=8)\n",
" rng = np.random.default_rng(seed)\n",
" raw.loc[rng.choice(len(raw), 4, replace=False), \"spd\"] = np.nan # ์์ ๋ฉ์์ง ์ฃผ์
\n",
" raw = pd.concat([raw, raw.sample(10, random_state=seed)]) # ์ค๋ณต ์ฃผ์
\n",
" t0 = time.perf_counter()\n",
" df, bad = preprocess(raw)\n",
" a = assess(df, clf)\n",
" rec = recommend(a)\n",
" q = Queue()\n",
" for r in rec.itertuples():\n",
" q.submit(r)\n",
" return {\"raw\": raw, \"df\": df, \"bad\": bad, \"a\": a, \"rec\": rec, \"q\": q, \"ms\": (time.perf_counter() - t0) * 1000}\n",
"\n",
"\n",
"# ---------------- 1. ํตํฉ ์คํ + ํจ์ ์ปค๋ฒ๋ฆฌ์ง ์ธก์ ----------------\n",
"called = set()\n",
"mine = {\"make_raid\", \"geometry\", \"features\", \"train_classifier\", \"baseline_rank\", \"advanced_rank\", \"validate\", \"preprocess\",\n",
" \"assess\", \"recommend\", \"submit\", \"run\"}\n",
"sys.setprofile(lambda f, e, a: called.add(f.f_code.co_name) if e == \"call\" and f.f_code.co_name in mine else None)\n",
"t_start = time.perf_counter()\n",
"clf = train_classifier()\n",
"results, fails = [], 0\n",
"for seed in (8, 9, 10, 11, 12):\n",
" try:\n",
" results.append(run(seed, clf))\n",
" except Exception as e:\n",
" fails += 1; print(\"์คํ ์คํจ:\", seed, e)\n",
"base_chk = baseline_rank(results[0][\"df\"]) # ๋น๊ต ๊ธฐ์ค(Baseline) ๊ฒฝ๋ก๋ ์ ๊ฒ์ ํฌํจ\n",
"adv_chk = advanced_rank(results[0][\"df\"], clf)\n",
"sys.setprofile(None)\n",
"cov = len(called & mine) / len(mine)\n",
"\n",
"# ---------------- 2. ์ฑ๋ฅ ์ง๊ณ ----------------\n",
"acc, rec_u, far = [], [], []\n",
"for R in results:\n",
" a = R[\"a\"]\n",
" y = np.select([a[\"threat\"] & (a[\"true_tti\"] <= 300), a[\"threat\"] & (a[\"true_tti\"] <= 900)], [\"๊ธด๊ธ\", \"์ฃผ์\"], \"์ผ๋ฐ\")\n",
" acc.append((a[\"level\"] == y).mean())\n",
" rec_u.append(((a[\"level\"] != \"์ผ๋ฐ\") & (y == \"๊ธด๊ธ\")).sum() / max((y == \"๊ธด๊ธ\").sum(), 1))\n",
" far.append((~a[\"threat\"] & (a[\"level\"] != \"์ผ๋ฐ\")).sum() / max((a[\"level\"] != \"์ผ๋ฐ\").sum(), 1))\n",
"R = results[0]\n",
"print(f\"[ํตํฉ ์คํ] ์ํฉ {len(results) + fails}๊ฐ ์ค ์ฑ๊ณต {len(results)}๊ฐ, ํ๊ท ์ฒ๋ฆฌ {np.mean([r['ms'] for r in results]):.1f} ms\")\n",
"print(f\" ์ํฉ 1: ์
๋ ฅ {len(R['raw'])}๊ฑด -> ์์ ๊ฒฉ๋ฆฌ {len(R['bad'])}๊ฑด, ์ค๋ณต ์ ๊ฑฐ {len(R['raw']) - len(R['bad']) - len(R['df'])}๊ฑด, ํ๊ฐ {len(R['df'])}๊ฑด\")\n",
"print(f\" ์ฐ์ ์์ ํ์ ์ ํ๋ {np.mean(acc):.1%}, ๊ธด๊ธ ํ์ ๊ฒฝ๋ณด ์ฌํ์จ {np.mean(rec_u):.1%}, ๋น์ํ ๊ฒฝ๋ณด ๋น์จ {np.mean(far):.1%}\")\n",
"print(\"\\n[์ํฉ 1 ๊ถ๊ณ ๋์ ์์ ์์ 5] (๋ชจ๋ ์น์ธ ๋๊ธฐ)\")\n",
"print(R[\"rec\"].head(5)[[\"๊ถ๊ณ ์์\", \"id\", \"asset\", \"tti_s\", \"level\", \"grade\", \"์ํ\"]].round({\"tti_s\": 0}).to_string(index=False))\n",
"\n",
"# ---------------- 3. ์์ฒด ์ ๊ฒ ----------------\n",
"chk = {\n",
" \"E2E ํ์ดํ๋ผ์ธ ์คํ ์ฑ๊ณต๋ฅ 100%\": fails == 0,\n",
" \"์์ ๋ฉ์์ง ์ ๋ ๊ฒฉ๋ฆฌ\": all(r[\"bad\"][\"spd\"].isna().all() and r[\"bad\"][\"id\"].nunique() == 4 for r in results),\n",
" \"์คํธํ ์์ฌ ํญ์ ๊ฒฝ๋ณด 0๊ฑด\": all(((r[\"a\"][\"spoof_flag\"]) & (r[\"a\"][\"level\"] != \"์ผ๋ฐ\")).sum() == 0 for r in results),\n",
" \"์๋ ์น์ธ 0๊ฑด(์ ๋ถ ์น์ธ ๋๊ธฐ)\": all(set(r[\"q\"].items.values()) <= {\"PENDING\"} for r in results),\n",
" \"๊ธด๊ธ ํ์ ๊ฒฝ๋ณด ์ฌํ์จ 90% ์ด์\": np.mean(rec_u) >= 0.9,\n",
" \"์ฒ๋ฆฌ ์๊ฐ ์์ฐ ์ด๋ด\": max(r[\"ms\"] for r in results) <= CONFIG[\"budget_ms\"],\n",
" \"ํจ์ ์ปค๋ฒ๋ฆฌ์ง 85% ์ด์\": cov >= 0.85,\n",
"}\n",
"passed = sum(chk.values())\n",
"for k, v in chk.items():\n",
" print(f\"[{'PASS' if v else 'FAIL'}] {k}\")\n",
"print(f\"์์ฒด ์ ๊ฒ {passed}/{len(chk)} ํต๊ณผ, ํจ์ ์ปค๋ฒ๋ฆฌ์ง {cov:.0%} (๋ชฉํ 85% ์ด์) -> {'๋ฌ์ฑ' if cov >= 0.85 else '๋ฏธ๋ฌ'}\")\n",
"\n",
"# ---------------- 4. ๋ธ๋ฆฌํ ์ํฉํ ----------------\n",
"a = R[\"a\"]\n",
"fig, ax = plt.subplots(1, 2, figsize=(12, 5))\n",
"col = {\"๊ธด๊ธ\": \"red\", \"์ฃผ์\": \"orange\", \"์ผ๋ฐ\": \"lightgray\"}\n",
"for lv, g in a.groupby(\"level\"):\n",
" ax[0].scatter(g[\"x\"], g[\"y\"], s=14, c=col[lv], label={\"๊ธด๊ธ\": \"urgent\", \"์ฃผ์\": \"watch\", \"์ผ๋ฐ\": \"normal\"}[lv])\n",
"ax[0].scatter(ASSETS[\"x\"], ASSETS[\"y\"], marker=\"^\", s=120, c=\"blue\", label=\"virtual assets\")\n",
"ax[0].set_aspect(\"equal\"); ax[0].legend(fontsize=8); ax[0].set_title(\"Integrated picture (scenario 1)\")\n",
"ax[1].axis(\"off\")\n",
"ax[1].text(0, 0.9, f\"Accuracy {np.mean(acc):.1%}\\nUrgent recall {np.mean(rec_u):.1%}\\nPending recs {len(R['rec'])}\\n\"\n",
" f\"Auto-approved 0\\nSelf-check {passed}/{len(chk)}\\nCoverage {cov:.0%}\", family=\"monospace\", fontsize=13, va=\"top\")\n",
"plt.suptitle(\"OPS04 saturation alert system - training data only\"); plt.tight_layout(); plt.show()\n",
"\n",
"# ---------------- 5. ๋ฐฉ๊ณต์์ ๋ธ๋ฆฌํ ์์ฝ ----------------\n",
"lv = R[\"a\"][\"level\"].value_counts()\n",
"print(f\"\"\"\n",
"[ํฌํ๊ฒฝ๋ณด ์ฐ์ ์์ ๋ธ๋ฆฌํ ์์ฝ - ๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"1. ์ํฉ: ๊ฐ์ ๋นํ์ฒด {len(R['df'])}๊ฑด ํ๊ฐ, ๊ธด๊ธ {lv.get('๊ธด๊ธ', 0)} / ์ฃผ์ {lv.get('์ฃผ์', 0)} / ์ผ๋ฐ {lv.get('์ผ๋ฐ', 0)}\n",
"2. ์ฑ๋ฅ: ํ์ ์ ํ๋ {np.mean(acc):.1%}, ๊ธด๊ธ ์ฌํ์จ {np.mean(rec_u):.1%}, ์ฒ๋ฆฌ {R['ms']:.1f} ms, ์ ์ฒด ์์ {time.perf_counter() - t_start:.1f} s\n",
"3. ๊ถ๊ณ : ๋์ ๊ถ๊ณ {len(R['rec'])}๊ฑด ๋ชจ๋ ์งํ๊ด ์น์ธ ๋๊ธฐ, ์๋ ์น์ธ 0๊ฑด\n",
"4. ์ด์ฉ ์ ์: ์์๋ ๊ถ๊ณ ์ด๋ฉฐ ๋์ ์ฌ๋ถ๋ ์งํ๊ด ์น์ธ๊ณผ ๊ต์ ์์นยท์งํ๊ณํต์ ๋ฐ๋ฅธ๋ค.\n",
"5. ํ๊ณ: ์ถ์ ์ํ ์ง์์ ๊ฐ์ ๋ฐ์ดํฐ ๊ธฐ๋ฐ ๊ฒฐ๊ณผ๋ก, ์ค์ ์ด์ฉ ์ ์๋ ์ค์ ์ผ์ ์๋ฃ๋ก ์ฌ๊ฒ์ฆํด์ผ ํ๋ค.\"\"\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
10 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[ํตํฉ ์คํ] ์ํฉ 5๊ฐ ์ค ์ฑ๊ณต 5๊ฐ, ํ๊ท ์ฒ๋ฆฌ 31.0 ms\n",
" ์ํฉ 1: ์
๋ ฅ 138๊ฑด -> ์์ ๊ฒฉ๋ฆฌ 4๊ฑด, ์ค๋ณต ์ ๊ฑฐ 10๊ฑด, ํ๊ฐ 124๊ฑด\n",
" ์ฐ์ ์์ ํ์ ์ ํ๋ 94.5%, ๊ธด๊ธ ํ์ ๊ฒฝ๋ณด ์ฌํ์จ 100.0%, ๋น์ํ ๊ฒฝ๋ณด ๋น์จ 3.3%\n",
"\n",
"[์ํฉ 1 ๊ถ๊ณ ๋์ ์์ ์์ 5] (๋ชจ๋ ์น์ธ ๋๊ธฐ)\n",
" ๊ถ๊ณ ์์ id asset tti_s level grade ์ํ\n",
" 1 T08115 A3 11.0 ๊ธด๊ธ High ์น์ธ ๋๊ธฐ\n",
" 2 T08017 A4 54.0 ๊ธด๊ธ High ์น์ธ ๋๊ธฐ\n",
" 3 T08094 A3 60.0 ๊ธด๊ธ High ์น์ธ ๋๊ธฐ\n",
" 4 T08119 A4 64.0 ๊ธด๊ธ High ์น์ธ ๋๊ธฐ\n",
" 5 T08015 A3 69.0 ๊ธด๊ธ High ์น์ธ ๋๊ธฐ\n",
"[PASS] E2E ํ์ดํ๋ผ์ธ ์คํ ์ฑ๊ณต๋ฅ 100%\n",
"[PASS] ์์ ๋ฉ์์ง ์ ๋ ๊ฒฉ๋ฆฌ\n",
"[PASS] ์คํธํ ์์ฌ ํญ์ ๊ฒฝ๋ณด 0๊ฑด\n",
"[PASS] ์๋ ์น์ธ 0๊ฑด(์ ๋ถ ์น์ธ ๋๊ธฐ)\n",
"[PASS] ๊ธด๊ธ ํ์ ๊ฒฝ๋ณด ์ฌํ์จ 90% ์ด์\n",
"[PASS] ์ฒ๋ฆฌ ์๊ฐ ์์ฐ ์ด๋ด\n",
"[PASS] ํจ์ ์ปค๋ฒ๋ฆฌ์ง 85% ์ด์\n",
"์์ฒด ์ ๊ฒ 7/7 ํต๊ณผ, ํจ์ ์ปค๋ฒ๋ฆฌ์ง 100% (๋ชฉํ 85% ์ด์) -> ๋ฌ์ฑ\n",
"\n",
"[ํฌํ๊ฒฝ๋ณด ์ฐ์ ์์ ๋ธ๋ฆฌํ ์์ฝ - ๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"1. ์ํฉ: ๊ฐ์ ๋นํ์ฒด 124๊ฑด ํ๊ฐ, ๊ธด๊ธ 30 / ์ฃผ์ 30 / ์ผ๋ฐ 64\n",
"2. ์ฑ๋ฅ: ํ์ ์ ํ๋ 94.5%, ๊ธด๊ธ ์ฌํ์จ 100.0%, ์ฒ๋ฆฌ 29.1 ms, ์ ์ฒด ์์ 1.7 s\n",
"3. ๊ถ๊ณ : ๋์ ๊ถ๊ณ 60๊ฑด ๋ชจ๋ ์งํ๊ด ์น์ธ ๋๊ธฐ, ์๋ ์น์ธ 0๊ฑด\n",
"4. ์ด์ฉ ์ ์: ์์๋ ๊ถ๊ณ ์ด๋ฉฐ ๋์ ์ฌ๋ถ๋ ์งํ๊ด ์น์ธ๊ณผ ๊ต์ ์์นยท์งํ๊ณํต์ ๋ฐ๋ฅธ๋ค.\n",
"5. ํ๊ณ: ์ถ์ ์ํ ์ง์์ ๊ฐ์ ๋ฐ์ดํฐ ๊ธฐ๋ฐ ๊ฒฐ๊ณผ๋ก, ์ค์ ์ด์ฉ ์ ์๋ ์ค์ ์ผ์ ์๋ฃ๋ก ์ฌ๊ฒ์ฆํด์ผ ํ๋ค.\n",
"```\n",
" \n"
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}