{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# ๐ฏ [QLT05] ์ธ์ฃผ์ ๋น ํ์ง๊ฒ์ฌ ์ค์ ยท๋ณ๋ชฉ ๋ถ์\n",
"### Google Colab ์ค์ต ํ๊ฒฝ (ํ์งยท๊ท๊ฒฉยท๊ฒ์ฌ / Level 2)\n",
"\n",
"[](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/QLT05/QLT05_solution.ipynb)\n",
"\n",
"---\n",
"\n",
"## ๐ 1. ํ๋ก์ ํธ ๊ฐ์ ๋ฐ ๋ฐฐ๊ฒฝ\n",
"\n",
"| ํญ๋ชฉ | ๋ด์ฉ |\n",
"| :--- | :--- |\n",
"| **๊ณผ์ ๋ถ๋ฅ** | ํ์งยท๊ท๊ฒฉยท๊ฒ์ฌ (Level 2) |\n",
"| **ํ์ต ์์ ์๊ฐ** | 10๊ฐ ๋ฏธ์
(5์ผ / ์ด 28.5์๊ฐ) |\n",
"| **ํ๊ฒฝ ์ฌ์** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n",
"\n",
"### ๐ ํ๋ก์ ํธ ๋ชฉํ\n",
"ํด์ธ ์ฌ๋ก์์ ๊ตญ๋ฐฉ ๊ฐ์ฌ๊ธฐ๊ด์ด ์ธ์ฃผ์ ๋น ๊ฒ์ฌ์ค์ ์ ๋ถ์ํ ๊ฒฐ๊ณผ, ๊ฒ์ฌ ๋๊ธฐ๊ฐ ๊ธธ์ด์ง๋ ๋ณ๋ชฉ๊ณผ ์ ๋น ์งํ ์ฌ๋ฐํ๋ ๊ฒฐํจ์ด ํ์ง ๋ฌธ์ ์ ์ฃผ์ ๋จ์๋ก ์ง์ ๋ ๋ฐ ์๋ค. ์ผ์ ์์๋ ์ฅ๋น ๊ฒฐํจ์ด ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR)์ ๋น์ ํ ์์ ๋ก ์์ด์ง๋ง ๋ถ์, ์น๋ช
๋, ์์ธ์ด ์ฌ๋๋ง๋ค ๋ค๋ฅด๊ฒ ์ ํ ๋ค๋ฐ ๊ฒฐํจ๊ณผ ์ฑ
์ ์์ฌ๋ฅผ ๊ฐ๋ ค๋ด๊ธฐ ์ด๋ ต๋ค. ๋ณธ ํ๋ก์ ํธ๋ ์ธ์ฃผ์ ๋น ํ์ง๊ฒ์ฌ ์ค์ ๊ณผ QDR ํ
์คํธ๋ฅผ ์ฐ๊ฒฐํด ๋ณ๋ชฉ๊ณผ ํ์ง ์ทจ์ฝ์ ์ ๋ฐ์ดํฐ๋ก ์ฐพ๋ ๊ฒ์ ๋ชฉํ๋ก, ๊ต์ก์ฉ ๊ฐ์ QDR์ ๊ฐ์ ์ธ์ฃผ์ ๋น ์
์ฒด ์๋ฃ๋ฅผ ์ฝ๋๋ก ๋ง๋ค์ด ๋ถ์ํ๋ค. QDR ๋ฐ์ดํฐ ์คํค๋ง ์ ์, ์์ ํ
์คํธ ์ ์ ์ ๋ถ์ยท๊ฒฐํจ ๋ผ๋ฒจ๋ง, ๋น๋ Baseline๊ณผ ํ๋ ํ ยท์๊ด ๋ถ์, ํ ํฝ ๋ชจ๋ธ๋ง๊ณผ ์๊ฐ ๊ฑด์ ์์ธก, ์น๋ช
๋ ๊ธฐ๋ฐ ์ทจ์ฝ์ฑ ์ง์, ์ค๋ช
๊ฐ๋ฅํ ๋ฐ์ ํํฉ ํ๋ฉด, ํ์ง๊ด ์น์ธ(HITL) ๊ธฐ๋ฐ ํ์ ๊ตฌ์ยท์ค๊ณ ๋ณ๊ฒฝ ์๋ฎฌ๋ ์ดํฐ, 10๋
์น ๋์ฉ๋ ์ผ๊ด ๋ถ์, 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. ์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR, Quality Deficiency Report) ๋ฐ์ดํฐ ์คํค๋ง ๋ถ์\n",
"**์์์๊ฐ**: ์ฝ 1.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR) ๋ฐ์ดํฐ ์คํค๋ง๋ฅผ ๋ถ์ํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR)์ ๋ณด๊ณ ์ ๋ณด, ๊ฒฐํจ ์์ ํ
์คํธ, ์ธ์ฃผ์ ๋นยท๊ฒ์ฌ ์ค์ , ์กฐ์น ๊ฒฐ๊ณผ ๋ฐ์ดํฐ ์คํค๋ง์ ๋ฐ์ดํฐ ๋์
๋๋ฆฌ, ๊ฒฐํจ ๋ถ์ ์
๋ฌด ํ๋ก์ธ์ค ๋งต์ ์ ์ํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฒฐํจ ๋ฐ๊ฒฌ๋ถํฐ QDR ์์ฑ, ์ ์ยท๊ฒํ , ๊ฒ์ฌยทํ์ , ์์ธ ๋ถ์, ์กฐ์น, ์ค์ ์ง๊ณ, ๋ค๋ฐ ๊ฒฐํจ ์๋ณ, ์ฌํ๊ฒํ ๊น์ง ์
๋ฌด ํ๋ฆ์ ๋จ๊ณ๋ณ๋ก ์ ๋ฆฌํ๊ณ ๋ณด๊ณ ยทํ
์คํธยท๊ฒ์ฌยท๊ฒฐ๊ณผ ๋ฌถ์์ผ๋ก ํผ์ฒ 20๊ฐ ์ด์ ์ ์ํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `docs/qlt05_logistics_domain_spec.md ๋ฐ ๋ฐ์ดํฐ ๋์
๋๋ฆฌ` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ตฐ์ ์ค๋ฌด ์
๋ฌด ํ๋ก์ธ์ค์์ ์ผ์น์ฑ ๋ฐ ๋ถ์ ๋ชฉํ์ ๋ช
ํ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์
๋ฌด ํ๋ก์ธ์ค ๋งต ์์ฑ ์๋ฃ, ํ์ ํผ์ฒ(Feature) ๋ชฉ๋ก 20๊ฐ ์ด์ ์ ์\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ๋๋ฉ์ธ ๋ถ์์ ๋ฐ ๋ฐ์ดํฐ ์๊ตฌ๋ช
์ธ์ ์น์ธ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๊ตฐ์ ๋ด๋น ์ค๋ฌด์ ์ธํฐ๋ทฐ ๋ฐ ๊ธฐ์กด ์ ์ฐ์ฒด๊ณ ๋ฐ์ดํฐ ๊ตฌ์กฐ ๋์กฐ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR) ๋ฐ์ดํฐ(๋ณด๊ณ ์ ๋ณด, ๊ฒฐํจ ์์ ํ
์คํธ, ์ธ์ฃผ์ ๋นยท๊ฒ์ฌ ์ค์ , ์กฐ์น ๊ฒฐ๊ณผ)์ ์คํค๋งยท๋ฐ์ดํฐ ๋์
๋๋ฆฌ(ํผ์ฒ 20๊ฐ ์ด์)์ ๊ฒฐํจ ๋ถ์ ์
๋ฌด ํ๋ก์ธ์ค ๋งต์ ์ ์ํ๊ณ , ๊ฐ์ QDR ํ๋ณธ์ ๊ฒ์ฆํ๋ฉฐ ์ค๋ฅ ์ฃผ์
์ผ๋ก ๊ฒ์ถ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ค.\"\"\"\n",
"import json\n",
"import re\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR) ์์ฑ๊ธฐ (์ค์ ๋ถ๋ยท์ฅ๋นยท์
์ฒดยท์ ๋น ์ค์ ์์)\n",
"# ๊ฐ์ ์ฅ๋น ๋ถ์ 12์ข
, ๊ฒฐํจ ์ ํ 8์ข
, ์น๋ช
๋(Critical/Major/Minor), ์์ธ 5์ข
, ๊ฐ์ ์ธ์ฃผ์ ๋น ์
์ฒด 4๊ณณ + ๋ถ๋์ ๋น.\n",
"# QDR 1๊ฑด๋ง๋ค ํ๊ตญ์ด ์์ ํ
์คํธ๋ฅผ ๋ง๋ค๊ณ , ์๋ณ ๊ฑด์๋ ๊ณ์ ยท์ถ์ธยทํ๋ จ ๊ฐ๋(usage)ยท์ผ๋ถ ์ ์กฐ ๋ถ๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ๋ฅธ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"PARTS = [ # (์ด๋ฆ, ๋ณ์นญ, ์ ๊ธฐ์ค ๊ฑด์, ๊ณ์ ์งํญ, ์ ์ ์, (Critical, Major ๋น์จ), ๊ฐ์ ๋จ๊ฐ์ง์, ์ ์กฐ์ฌ, ์ฃผ์ ๊ฒฐํจ ๊ฐ์ค์น)\n",
" (\"์์ง ์ฐ๋ฃ๊ณํต\", [\"์ฐ๋ฃ๊ณํต\", \"์ฐ๋ฃ ๋ผ์ธ\", \"์ฐ๋ฃ ํํ\"], 26, 0.15, 7, (0.10, 0.35), 6.0, \"MK1\", \"๋์ :4 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:2 ์ค์๋:2\"),\n",
" (\"๋ณ์๊ธฐ\", [\"๋ณ์๊ธฐ\", \"T/M\", \"๋ฏธ์
\"], 22, 0.10, 8, (0.12, 0.38), 9.0, \"MK1\", \"๋ง๋ชจ:4 ๋์ :2 ๊ณผ์ด:3 ์ค์๋:1\"),\n",
" (\"์ ๋ ์ฅ์น\", [\"์ ๋ ์ฅ์น\", \"๋ธ๋ ์ดํฌ\", \"์ ๋ ๋ผ์ธ\"], 20, 0.20, 12, (0.26, 0.40), 5.0, \"MK2\", \"๋ง๋ชจ:4 ๊ท ์ด:2 ๋์ :2 ์ฒด๊ฒฐ๋ถ๋:2\"),\n",
" (\"์กฐํฅ ์ฅ์น\", [\"์กฐํฅ ์ฅ์น\", \"ํธ๋ค ์ฐ๋๋ถ\", \"์กฐํฅ ๋ก๋\"], 14, 0.10, 6, (0.30, 0.40), 5.0, \"MK2\", \"์ฒด๊ฒฐ๋ถ๋:4 ๊ท ์ด:2 ๋ง๋ชจ:3 ์ค์๋:1\"),\n",
" (\"๋๊ฐ ๊ณํต\", [\"๋๊ฐ ๊ณํต\", \"๋ผ๋์์ดํฐ\", \"๋๊ฐ์ ๋ผ์ธ\"], 24, 0.30, 7, (0.05, 0.30), 3.0, \"MK3\", \"๋์ :4 ๊ณผ์ด:4 ๋ถ์:2\"),\n",
" (\"์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ\", [\"๋ฐฐํฐ๋ฆฌ\", \"์ ๊ธฐ ๊ณํต\", \"๋ฐ์ ๊ธฐ\"], 28, 0.35, 1, (0.04, 0.28), 2.5, \"MK3\", \"์ ์ด๋ถ๋:4 ๋ถ์:2 ๊ณผ์ด:2 ์ค์๋:2\"),\n",
" (\"ํต์ ๋ชจ๋\", [\"ํต์ ๋ชจ๋\", \"๋ฌด์ ๊ธฐ ์ฐ๋๋ถ\", \"์ํ
๋ ์ ์๋ถ\"], 16, 0.05, 4, (0.03, 0.25), 4.0, \"MK4\", \"์ ์ด๋ถ๋:5 ์ค์๋:4 ๋ถ์:1\"),\n",
" (\"์ ์ ๊ณํต\", [\"์ ์ ๊ณํต\", \"์ ์ ํธ์ค\", \"์ ์ ์ค๋ฆฐ๋\"], 18, 0.10, 9, (0.10, 0.35), 4.5, \"MK2\", \"๋์ :5 ๊ท ์ด:2 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"ํ๊ฐ ์ฅ์น\", [\"ํ๊ฐ ์ฅ์น\", \"์์คํ์
\", \"์์ถฉ๊ธฐ\"], 15, 0.08, 5, (0.06, 0.30), 3.5, \"MK4\", \"๋ง๋ชจ:4 ๊ท ์ด:3 ์ฒด๊ฒฐ๋ถ๋:2 ๋ถ์:1\"),\n",
" (\"์ฐจ์ฒด ์ฉ์ ๋ถ\", [\"์ฐจ์ฒด ์ฉ์ ๋ถ\", \"ํ๋ ์ ์ฉ์ ๋ถ\", \"์ฐจ์ฒด ์ด์๋ถ\"], 10, 0.05, 10, (0.28, 0.40), 7.0, \"MK1\", \"๊ท ์ด:6 ๋ถ์:3 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ดํยท์กฐ์ค ์ฅ๋น\", [\"๊ดํ ์ฅ๋น\", \"์กฐ์ค๊ฒฝ\", \"๊ด์ธก ์ฅ๋น\"], 8, 0.05, 3, (0.03, 0.22), 8.0, \"MK4\", \"์ค์๋:4 ์ ์ด๋ถ๋:3 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ณต๊ธฐ ์ ํ ํํฐ\", [\"๊ณต๊ธฐ ํํฐ\", \"ํก๊ธฐ ํํฐ\", \"์ ํ ์ฅ์น\"], 12, 0.25, 4, (0.01, 0.12), 1.0, \"MK3\", \"๋ง๋ชจ:5 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:3\")]\n",
"NP = len(PARTS)\n",
"DEFECTS = { # ๊ฒฐํจ ์ ํ: ์ฆ์ ํํ(๋์์ด)\n",
" \"๊ท ์ด\": [\"๊ท ์ด์ด ๋ฐ๊ฒฌ๋จ\", \"ํฌ๋์ด ํ์ธ๋จ\", \"๊ฐ๋ผ์ง์ด ์์\"], \"๋์ \": [\"๋์ ๊ฐ ๋ฐ์ํจ\", \"์ค์ผ์ด ์์ด ๋์ด\", \"๋์ค ํ์ ์ด ์์\"],\n",
" \"๋ง๋ชจ\": [\"๋ง๋ชจ๊ฐ ์ฌํจ\", \"๊ณผ๋ํ ๋ง๋ชจ๊ฐ ํ์ธ๋จ\", \"๋ณ์ ํ์ ์ด ์์\"], \"๋ถ์\": [\"๋ถ์์ด ์งํ๋จ\", \"๋
น์ด ๋ฐ์ํจ\", \"์ฐํ ํ์ ์ด ์์\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋์ด ๋ฐ์ํจ\", \"์ปค๋ฅํฐ ๋จ์ ์ด ์์ฌ๋จ\", \"์ ํธ๊ฐ ๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ ํ๋ฆผ์ด ํ์ธ๋จ\", \"์ฒด๊ฒฐ ์ํ๊ฐ ๋ถ๋ํจ\", \"๊ณ ์ ๋ถ๊ฐ ํ๊ฑฐ์\"],\n",
" \"๊ณผ์ด\": [\"๊ณผ์ด ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํจ\", \"์จ๋๊ฐ ๊ณผ๋ํ๊ฒ ์์นํจ\", \"์ด์ด ์ฌํ๊ฒ ๋จ\"], \"์ค์๋\": [\"์ค์๋์ด ๋ฐ๋ณต๋จ\", \"์๋์ด ๋ถ์์ ํจ\", \"๊ฐํ์ ์ผ๋ก ๋ฉ์ถค\"]}\n",
"DNAMES = list(DEFECTS)\n",
"SEV = [\"Minor\", \"Major\", \"Critical\"]\n",
"SEV_CUE = {0: [\"๊ฒฝ๋ฏธํ ์์ค์\", \"์ด์ฉ์๋ ์ง์ฅ์ด ์์\", \"์ก์ ์ ๊ฒ์ ์ฌ์ํ ์ด์์\", \"๊ธฐ๋ฅ์๋ ์ํฅ์ด ์์\"],\n",
" 1: [\"์ฑ๋ฅ ์ ํ๊ฐ ์์\", \"์กฐ์ํ ์ ๋น๊ฐ ํ์ํจ\", \"์ถ๋ ฅ์ด ๋จ์ด์ง๋ ์ํ์\", \"๊ธฐ๋ฅ์ด ๋ถ๋ถ์ ์ผ๋ก ์ ํ๋จ\"],\n",
" 2: [\"์ฆ์ ์ด์ฉ ์ค์งํจ\", \"์์ ์ ์ํ์ด ์์ด ์ฌ์ฉ ๋ถ๊ฐ\", \"์๋ ์ ์ง๋ก ์๋ฌด ์ํ์ด ์ด๋ ค์\", \"์ธ๋ช
์์ ์ํ์ด ์ฐ๋ ค๋จ\"]}\n",
"CAUSES = [\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\", \"์ด์ฉ ๋ถ์ฃผ์\", \"์ค๊ณ ์ทจ์ฝ\", \"์์ฐ ๋
ธํ\"]\n",
"CAUSE_CUE = {\n",
" \"์ ์กฐ ๋ถ๋\": [\"์ ํ ์ธ์ ์งํ ๋ฐ์ํจ\", \"์ถ๊ณ ์ํ์์ ์ด๋ฏธ ์ด์์ด ์์์\", \"์ธ์ ํ ์ผ๋ง ์ง๋์ง ์์ ๋ฐ์ํจ\"],\n",
" \"์ ๋น ํ์ง\": [\"์ธ์ฃผ ์ ๋น ํ ์ผ๋ง ๋์ง ์์ ์ฌ๋ฐํจ\", \"์ ๋น ํ ์ฌ์กฐ๋ฆฝ ์ํ๊ฐ ๋ถ๋ํจ\", \"์ ๋น ์งํ ๊ฐ์ ๋ถ์์์ ๋ค์ ๋ฐ์ํจ\"],\n",
" \"์ด์ฉ ๋ถ์ฃผ์\": [\"ํ์ง ๊ธฐ๋ ์ค ์ถฉ๊ฒฉ ์ดํ ๋ฐ์ํจ\", \"๊ณผ๋ถํ ์ด์ฉ ํ ๋ฐ์ํจ\", \"์ ๊ฒ ์์ด ์ฐ์ ์ด์ฉํ ๋ค ๋ฐ์ํจ\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์์ด ๋ค๋ฅธ ์ฐจ๋์์๋ ๋ฐ๋ณต๋จ\", \"๊ตฌ์กฐ์ ์ผ๋ก ์ทจ์ฝํ ๋ถ์๋ก ๋ณด์\", \"์ฌ๋ฌ ๋ถ๋์์ ๊ฐ์ ํํ๋ก ๋ณด๊ณ ๋จ\"],\n",
" \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ์ด ๋๋ํจ\", \"์ฅ๊ธฐ๊ฐ ์ฌ์ฉ์ผ๋ก ๋
ธํ๋จ\", \"์๋ช
์ฃผ๊ธฐ ๋ง๊ธฐ์ ๋ฐ์ํจ\"]}\n",
"VENDORS = [\"์ธ์ฃผA\", \"์ธ์ฃผB\", \"์ธ์ฃผC\", \"์ธ์ฃผD\", \"๋ถ๋์ ๋น\"]\n",
"ACTIONS = [\"์ ๋น์ ์
๊ณ ๋ฅผ ์์ฒญํจ\", \"ํ์ฅ ์์ ์กฐ์น ํ ์ด์ฉ ์ค์\", \"๋ถํ ๊ตํ์ ์์ฒญํจ\", \"์ฌ๊ฒ์ฌ๋ฅผ ์์ฒญํจ\", \"์์ธ ํ์ธ์ด ํ์ํจ\"]\n",
"MAKER_OF = [p[7] for p in PARTS]\n",
"PRICE = np.array([p[6] for p in PARTS])\n",
"\n",
"\n",
"def usage_index(months, seed=0):\n",
" \"\"\"์๋ณ ํ๋ จ ๊ฐ๋(๋ฏธ๋ฆฌ ์ ์ ์๋ ๊ณํ ๊ฐ): ์ฐ 1ํ ์ ์ ์ ๊ฐ๋ ๊ณ์ + ์์ ์ก์\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 1] ์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR, Quality Deficiency Report) ๋ฐ์ดํฐ ์คํค๋ง ๋ถ์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_qdr(months=36, seed=1, scale=1.0, surge=True):\n",
" \"\"\"๋ฐํ: QDR DataFrame(๊ฑด๋น 1ํ), ์๋ณ usage, ๊ธ์ฆ ์ด๋ฒคํธ ๋ชฉ๋ก\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" use = usage_index(months, seed)\n",
" events = []\n",
" if surge:\n",
" for _ in range(max(2, months // 12 * 2)):\n",
" events.append((int(rng.integers(NP)), int(rng.integers(6, months))))\n",
" rows = []\n",
" wdef = [np.array([float(x.split(\":\")[1]) for x in p[8].split()]) for p in PARTS]\n",
" ddef = [[x.split(\":\")[0] for x in p[8].split()] for p in PARTS]\n",
" n = 0\n",
" for m in range(months):\n",
" for pi, p in enumerate(PARTS):\n",
" lam = p[2] * scale * (1 + 0.010 * m) * use[m] * (1 + p[3] * np.cos(2 * np.pi * (m % 12 - p[4]) / 12))\n",
" ev = (pi, m) in events\n",
" k = rng.poisson(lam * (2.6 if ev else 1.0))\n",
" for _ in range(k):\n",
" rows.append((n, m, pi, ev)); n += 1\n",
" N = len(rows)\n",
" A = np.array(rows)\n",
" month, part, ev = A[:, 1], A[:, 2], A[:, 3].astype(bool)\n",
" defect = np.array([rng.choice(ddef[i], p=wdef[i] / wdef[i].sum()) for i in part])\n",
" cr = np.array([PARTS[i][5][0] for i in part]); ma = np.array([PARTS[i][5][1] for i in part])\n",
" u = rng.random(N)\n",
" sev = np.where(u < cr, 2, np.where(u < cr + ma, 1, 0))\n",
" vendor = rng.choice(VENDORS, N, p=[0.25, 0.2, 0.2, 0.15, 0.2])\n",
" # ์์ธ: ๊ธฐ๋ณธ ๋น์จ์ ๋ถ์(์ ์กฐ์ฌ)ยท์
์ฒดยท์ด๋ฒคํธ ํจ๊ณผ๋ฅผ ๊ณฑํ๋ค\n",
" base = np.array([0.22, 0.20, 0.20, 0.16, 0.22])\n",
" P = np.tile(base, (N, 1))\n",
" P[vendor == \"์ธ์ฃผB\", 1] *= 2.6\n",
" P[ev, 0] *= 5.0\n",
" design = np.isin(part, [2, 3, 7]); P[design, 3] *= 2.2\n",
" P[np.isin(part, [4, 5, 11]), 0] *= 2.0 # ์ ์กฐ์ฌ MK3 ๋ฉํ ๋ถ์๋ ์ ์กฐ ๋ถ๋ ๋น์ค์ด ๋๋ค\n",
" P[defect == \"๋ง๋ชจ\", 4] *= 1.6; P[defect == \"๋์ \", 4] *= 1.3\n",
" P /= P.sum(axis=1, keepdims=True)\n",
" cause = np.array([CAUSES[rng.choice(5, p=P[i])] for i in range(N)])\n",
" since = np.where(cause == \"์ ๋น ํ์ง\", rng.exponential(35, N), rng.exponential(160, N)).clip(1, 720).round().astype(int)\n",
" wait = (rng.lognormal(1.3, 0.5, N) * np.where(vendor == \"์ธ์ฃผB\", 1.7, np.where(vendor == \"๋ถ๋์ ๋น\", 0.6, 1.0)) * (0.7 + 0.5 * use[month])).round(1)\n",
" reins = rng.random(N) < np.where(cause == \"์ ๋น ํ์ง\", 0.6, 0.08)\n",
" texts = []\n",
" for i in range(N):\n",
" pt = PARTS[part[i]]\n",
" cl = [f\"{rng.choice(pt[1])}์์ {rng.choice(DEFECTS[defect[i]])}\"]\n",
" if rng.random() < 0.7: cl.append(rng.choice(SEV_CUE[sev[i]]))\n",
" if rng.random() < 0.65: # ์์ธ ๋จ์: 8%๋ ์ค์ ์์ธ๊ณผ ๋ค๋ฅธ ๋จ์๊ฐ ์ ํ๋ค(๋ณด๊ณ ์์ ์ถ์ )\n",
" cc = cause[i] if rng.random() > 0.08 else rng.choice(CAUSES)\n",
" cl.append(rng.choice(CAUSE_CUE[cc]))\n",
" cl.append(rng.choice(ACTIONS))\n",
" if rng.random() < 0.5: cl.insert(0, f\"VU-{rng.integers(1, 31):02d} ์์\")\n",
" texts.append(\". \".join(cl) + \".\")\n",
" df = pd.DataFrame({\"qdr_id\": [f\"Q{seed}{i:06d}\" for i in range(N)], \"month\": month, \"unit\": [f\"VU-{x:02d}\" for x in rng.integers(1, 31, N)],\n",
" \"part\": part, \"defect\": defect, \"sev\": sev, \"cause\": cause, \"vendor\": vendor, \"maker\": [MAKER_OF[i] for i in part],\n",
" \"since_maint\": since, \"inspect_wait\": wait, \"reinspect\": reins, \"event\": ev, \"text\": texts})\n",
" df[\"cost\"] = PRICE[df[\"part\"]] * np.array([1.0, 2.0, 4.0])[df[\"sev\"]] # [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์๋ฆฌยท์กฐ์น ๋น์ฉ์ง์\n",
" return df, use, events\n",
"\n",
"# 1) ์
๋ฌด ํ๋ก์ธ์ค ๋งต: ๊ฒฐํจ ๋ฐ์๋ถํฐ ์์ธ ํ๋ฅ๊น์ง (๊ฐ ๋จ๊ณ์์ ์๊ธฐ๋ ๋ฐ์ดํฐ)\n",
"PROCESS = [(\"๊ฒฐํจ ๋ฐ๊ฒฌ\", \"๋ฐ๊ฒฌ ๋ถ๋, ์ฅ๋น, ๋ถ์, ์์ ํ
์คํธ\"), (\"QDR ์์ฑ\", \"QDR ID, ์์ฑ์ผ, ์น๋ช
๋(Critical/Major/Minor)\"),\n",
" (\"์ ์ยท๊ฒํ \", \"ํ
์คํธ ์ ์ , ๋ถ์ยท๊ฒฐํจ ์ ํ ๋ผ๋ฒจ\"), (\"๊ฒ์ฌยทํ์ \", \"๊ฒ์ฌ ๋๊ธฐ์ผ, ์ฌ๊ฒ์ฌ ์ฌ๋ถ, ์ธ์ฃผ์ ๋น ์
์ฒด\"),\n",
" (\"์์ธ ๋ถ์\", \"์์ธ ๊ตฌ๋ถ(์ ์กฐยท์ ๋นยท์ด์ฉยท์ค๊ณยท๋
ธํ), ์ ์กฐ์ฌ\"), (\"์กฐ์น\", \"์๋ฆฌยท๊ตํยทํ์ ๊ตฌ์ ์ฌ๋ถ, ์กฐ์น ๋น์ฉ\"),\n",
" (\"์ค์ ์ง๊ณ\", \"๋ถ์๋ณยท์๋ณ ๊ฑด์, ์ทจ์ฝ์ฑ ์ง์\"), (\"๋ค๋ฐ ๊ฒฐํจ ์๋ณ\", \"ํ๋ ํ (ABC), ์ด์ ์ ํธ\"), (\"์ฌํ๊ฒํ (AAR)\", \"์์ธ ์ค๋ถ๋ฅ ์ฌ๋ก, ๋ฃฐ ์กฐ์ ์ด๋ ฅ\")]\n",
"\n",
"# 2) ์คํค๋ง(๋ฐ์ดํฐ ๋์
๋๋ฆฌ): ๊ทธ๋ฃน / ํ๋ / ์๋ฃํ / ๊ท์น / ์ค๋ช
\n",
"PART_NAMES = [p[0] for p in PARTS]\n",
"S = {\n",
" \"report\": {\n",
" \"qdr_id\": (str, {\"pattern\": r\"^Q\\d{7,8}$\"}, \"QDR ID\"),\n",
" \"month\": (int, {\"min\": 0, \"max\": 143}, \"๋ณด๊ณ ์(๋ชจ์ ์์ ๊ธฐ์ค ๊ฒฝ๊ณผ ์)\"),\n",
" \"unit\": (str, {\"pattern\": r\"^VU-\\d{2}$\"}, \"๋ณด๊ณ ๋ถ๋ ์ฝ๋(๊ฐ์)\"),\n",
" \"equip_code\": (str, {\"pattern\": r\"^VX-\\d{4}-\\d{4}$\"}, \"์ฅ๋น ์๋ณ์ฝ๋(๊ตญ๋ฐฉ ํ์ค ์ฝ๋ ํ์์ ๋ชจ์ฌํ ๊ฐ์ ์ฝ๋)\"),\n",
" \"fsc_group\": (str, {\"pattern\": r\"^\\d{4}$\"}, \"๋ณด๊ธ ๋ถ๋ฅ๊ตฐ(๊ฐ์ 4์๋ฆฌ)\"),\n",
" \"part\": (str, {\"enum\": PART_NAMES}, \"๊ฒฐํจ ๋ถ์(์ฅ๋น ๊ณํต)\"),\n",
" \"severity\": (str, {\"enum\": SEV}, \"๊ฒฐํจ ์น๋ช
๋\"),\n",
" },\n",
" \"text\": {\n",
" \"text\": (str, {\"min_len\": 5}, \"๊ฒฐํจ ์์ ํ
์คํธ(๋น์ ํ)\"),\n",
" \"defect\": (str, {\"enum\": DNAMES + [\"๋ฏธ๋ถ๋ฅ\"]}, \"๊ฒฐํจ ์ ํ ๋ผ๋ฒจ\"),\n",
" \"n_tokens\": (int, {\"min\": 1, \"max\": 200}, \"์ ์ ํ ํ ํฐ ์\"),\n",
" \"part_label\": (str, {\"enum\": PART_NAMES + [\"๋ฏธ๋ถ๋ฅ\"]}, \"์์ ์์ ์ถ์ถํ ๋ถ์ ๋ผ๋ฒจ\"),\n",
" },\n",
" \"inspection\": {\n",
" \"vendor\": (str, {\"enum\": VENDORS}, \"์ง์ ์ ๋น ์ํ์ฒ(์ธ์ฃผ ์
์ฒด ๋๋ ๋ถ๋์ ๋น)\"),\n",
" \"since_maint\": (int, {\"min\": 0, \"max\": 1500}, \"์ง์ ์ ๋น ํ ๊ฒฝ๊ณผ์ผ\"),\n",
" \"inspect_wait\": (float, {\"min\": 0, \"max\": 60}, \"๊ฒ์ฌ ๋๊ธฐ์ผ(๊ฒ์ฌ ๋ณ๋ชฉ ์งํ)\"),\n",
" \"reinspect\": (bool, {}, \"์ฌ๊ฒ์ฌ ์ฌ๋ถ\"),\n",
" },\n",
" \"result\": {\n",
" \"cause\": (str, {\"enum\": CAUSES}, \"์์ธ ๊ตฌ๋ถ - ์์ธก๋์ 1\"),\n",
" \"maker\": (str, {\"pattern\": r\"^MK\\d$\"}, \"์ ์กฐ์ฌ ์ฝ๋(๊ฐ์)\"),\n",
" \"cost\": (float, {\"min\": 0, \"max\": 100}, \"์กฐ์น ๋น์ฉ์ง์ - ์์ธก๋์ 2\"),\n",
" \"monthly_count\": (int, {\"min\": 0, \"max\": 500}, \"๋ถ์๋ณ ์๊ฐ QDR ๊ฑด์ - ์์ธก๋์ 3\"),\n",
" \"vuln_index\": (float, {\"min\": 0, \"max\": 1000}, \"์น๋ช
๋ ๊ฐ์ค ์ทจ์ฝ์ฑ ์ง์ - ์์ธก๋์ 4\"),\n",
" \"event_flag\": (bool, {}, \"์ ์กฐ LOT ๋ถ๋ ๋ฑ ๊ธ์ฆ ์ด๋ฒคํธ ์ ์ฌ๋ถ\"),\n",
" \"hitl_status\": (str, {\"enum\": [\"PENDING\", \"APPROVED\", \"REJECTED\", \"NO_RESPONSE\"]}, \"ํ์ง๊ด ๊ฒํ ์ํ\"),\n",
" },\n",
"}\n",
"\n",
"\n",
"def check(name, v, typ, rule):\n",
" # [TODO : ํ์ ์ค์ต 1] ์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR, Quality Deficiency Report) ๋ฐ์ดํฐ ์คํค๋ง ๋ถ์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def validate(rec):\n",
" errs = []\n",
" for g, fields in S.items():\n",
" for k, (typ, rule, _) in fields.items():\n",
" if k not in rec.get(g, {}):\n",
" errs.append(f\"{g}.{k}: ํ์ ํ๋ ๋๋ฝ\")\n",
" else:\n",
" errs += check(f\"{g}.{k}\", rec[g][k], typ, rule)\n",
" return errs\n",
"\n",
"\n",
"# 3) [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์์ฑ ๋ฐ์ดํฐ์์ 20๊ฑด์ ๋ ์ฝ๋ ํํ๋ก ๋ง๋ค์ด ๊ฒ์ฆ\n",
"df, use, events = make_qdr(36, seed=1)\n",
"rng = np.random.default_rng(7)\n",
"mc = df.groupby([\"part\", \"month\"]).size()\n",
"recs = []\n",
"for r in df.sample(20, random_state=1).itertuples():\n",
" t = r.text\n",
" recs.append({\n",
" \"report\": {\"qdr_id\": r.qdr_id, \"month\": int(r.month), \"unit\": r.unit, \"equip_code\": f\"VX-{rng.integers(1000, 9999)}-{rng.integers(1000, 9999)}\",\n",
" \"fsc_group\": f\"{rng.integers(1000, 9999)}\", \"part\": PART_NAMES[r.part], \"severity\": SEV[r.sev]},\n",
" \"text\": {\"text\": t, \"defect\": r.defect, \"n_tokens\": max(len(t.split()), 1), \"part_label\": PART_NAMES[r.part]},\n",
" \"inspection\": {\"vendor\": r.vendor, \"since_maint\": int(r.since_maint), \"inspect_wait\": float(r.inspect_wait), \"reinspect\": bool(r.reinspect)},\n",
" \"result\": {\"cause\": r.cause, \"maker\": r.maker, \"cost\": float(r.cost), \"monthly_count\": int(mc[(r.part, r.month)]),\n",
" \"vuln_index\": float(sum(np.array([1, 3, 9])[df.loc[(df.part == r.part) & (df.month == r.month), \"sev\"]])), \"event_flag\": bool(r.event), \"hitl_status\": \"PENDING\"}})\n",
"ok = sum(not validate(r) for r in recs)\n",
"print(f\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ QDR {len(df)}๊ฑด ์์ฑ, ํ๋ณธ 20๊ฑด ์ค ๊ฒ์ฆ ํต๊ณผ {ok}๊ฑด, ์ค๋ฅ {len(recs) - ok}๊ฑด\")\n",
"print(f\"์ ์ฒด ๋ฐ์ดํฐ ๋ถ์ยท์น๋ช
๋ยท์์ธ ํ์ฉ๊ฐ ๊ฒ์ฆ: {'ํต๊ณผ' if df['part'].between(0, NP - 1).all() and df['sev'].isin([0, 1, 2]).all() and df['cause'].isin(CAUSES).all() else '์คํจ'}\")\n",
"\n",
"# 4) ์ค๋ฅ ์ฃผ์
์ํ\n",
"bad = json.loads(json.dumps(recs[0]))\n",
"bad[\"report\"][\"qdr_id\"] = \"QDR-7\" # ID ํ์ ์ค๋ฅ\n",
"bad[\"report\"][\"severity\"] = \"Severe\" # ํ์ฉ๋์ง ์์ ์น๋ช
๋\n",
"bad[\"text\"][\"text\"] = \"๊ณ ์ฅ\" # ์์ ๋๋ฌด ์งง์\n",
"del bad[\"inspection\"][\"vendor\"] # ํ์ ํ๋ ๋๋ฝ\n",
"bad[\"inspection\"][\"inspect_wait\"] = \"3์ผ\" # ์๋ฃํ ์ค๋ฅ\n",
"bad[\"result\"][\"cost\"] = 250.0 # ๋ฒ์ ์ด๊ณผ\n",
"print(\"\\n[์ค๋ฅ ์ฃผ์
๊ฒฐ๊ณผ]\")\n",
"for e in validate(bad):\n",
" print(\" -\", e)\n",
"\n",
"# 5) ํ๋ก์ธ์ค ๋งต๊ณผ ๋ฐ์ดํฐ ๋์
๋๋ฆฌ\n",
"print(\"\\n[์
๋ฌด ํ๋ก์ธ์ค ๋งต]\")\n",
"print(\" -> \".join(f\"{i}.{p}\" for i, (p, _) in enumerate(PROCESS, 1)))\n",
"for p, d in PROCESS:\n",
" print(f\" {p:10s}: {d}\")\n",
"rows = [(g, k, t.__name__, str(r)[:34] if r else \"-\", d) for g, f in S.items() for k, (t, r, d) in f.items()]\n",
"dd = pd.DataFrame(rows, columns=[\"๊ทธ๋ฃน\", \"ํ๋\", \"์๋ฃํ\", \"๊ท์น\", \"์ค๋ช
\"])\n",
"print(f\"\\n[๋ฐ์ดํฐ ๋์
๋๋ฆฌ] ํผ์ฒ {len(dd)}๊ฐ (๋ชฉํ 20๊ฐ ์ด์: {'๋ฌ์ฑ' if len(dd) >= 20 else '๋ฏธ๋ฌ'})\")\n",
"print(dd[[\"๊ทธ๋ฃน\", \"ํ๋\", \"์๋ฃํ\", \"์ค๋ช
\"]].to_string(index=False))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
01 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ QDR 9221๊ฑด ์์ฑ, ํ๋ณธ 20๊ฑด ์ค ๊ฒ์ฆ ํต๊ณผ 20๊ฑด, ์ค๋ฅ 0๊ฑด\n",
"์ ์ฒด ๋ฐ์ดํฐ ๋ถ์ยท์น๋ช
๋ยท์์ธ ํ์ฉ๊ฐ ๊ฒ์ฆ: ํต๊ณผ\n",
"\n",
"[์ค๋ฅ ์ฃผ์
๊ฒฐ๊ณผ]\n",
" - report.qdr_id: ํ์ ๋ถ์ผ์น 'QDR-7'\n",
" - report.severity: ํ์ฉ๋์ง ์์ ๊ฐ 'Severe'\n",
" - text.text: ์์ ์ด ๋๋ฌด ์งง์\n",
" - inspection.vendor: ํ์ ํ๋ ๋๋ฝ\n",
" - inspection.inspect_wait: ์๋ฃํ ์ค๋ฅ(str)\n",
" - result.cost: ์ต๋๊ฐ 100 ์ด๊ณผ\n",
"\n",
"[์
๋ฌด ํ๋ก์ธ์ค ๋งต]\n",
"1.๊ฒฐํจ ๋ฐ๊ฒฌ -> 2.QDR ์์ฑ -> 3.์ ์ยท๊ฒํ -> 4.๊ฒ์ฌยทํ์ -> 5.์์ธ ๋ถ์ -> 6.์กฐ์น -> 7.์ค์ ์ง๊ณ -> 8.๋ค๋ฐ ๊ฒฐํจ ์๋ณ -> 9.์ฌํ๊ฒํ (AAR)\n",
" ๊ฒฐํจ ๋ฐ๊ฒฌ : ๋ฐ๊ฒฌ ๋ถ๋, ์ฅ๋น, ๋ถ์, ์์ ํ
์คํธ\n",
" QDR ์์ฑ : QDR ID, ์์ฑ์ผ, ์น๋ช
๋(Critical/Major/Minor)\n",
" ์ ์ยท๊ฒํ : ํ
์คํธ ์ ์ , ๋ถ์ยท๊ฒฐํจ ์ ํ ๋ผ๋ฒจ\n",
" ๊ฒ์ฌยทํ์ : ๊ฒ์ฌ ๋๊ธฐ์ผ, ์ฌ๊ฒ์ฌ ์ฌ๋ถ, ์ธ์ฃผ์ ๋น ์
์ฒด\n",
" ์์ธ ๋ถ์ : ์์ธ ๊ตฌ๋ถ(์ ์กฐยท์ ๋นยท์ด์ฉยท์ค๊ณยท๋
ธํ), ์ ์กฐ์ฌ\n",
" ์กฐ์น : ์๋ฆฌยท๊ตํยทํ์ ๊ตฌ์ ์ฌ๋ถ, ์กฐ์น ๋น์ฉ\n",
" ์ค์ ์ง๊ณ : ๋ถ์๋ณยท์๋ณ ๊ฑด์, ์ทจ์ฝ์ฑ ์ง์\n",
" ๋ค๋ฐ ๊ฒฐํจ ์๋ณ : ํ๋ ํ (ABC), ์ด์ ์ ํธ\n",
" ์ฌํ๊ฒํ (AAR) : ์์ธ ์ค๋ถ๋ฅ ์ฌ๋ก, ๋ฃฐ ์กฐ์ ์ด๋ ฅ\n",
"\n",
"[๋ฐ์ดํฐ ๋์
๋๋ฆฌ] ํผ์ฒ 22๊ฐ (๋ชฉํ 20๊ฐ ์ด์: ๋ฌ์ฑ)\n",
" ๊ทธ๋ฃน ํ๋ ์๋ฃํ ์ค๋ช
\n",
" report qdr_id str QDR ID\n",
" report month int ๋ณด๊ณ ์(๋ชจ์ ์์ ๊ธฐ์ค ๊ฒฝ๊ณผ ์)\n",
" report unit str ๋ณด๊ณ ๋ถ๋ ์ฝ๋(๊ฐ์)\n",
" report equip_code str ์ฅ๋น ์๋ณ์ฝ๋(๊ตญ๋ฐฉ ํ์ค ์ฝ๋ ํ์์ ๋ชจ์ฌํ ๊ฐ์ ์ฝ๋)\n",
" report fsc_group str ๋ณด๊ธ ๋ถ๋ฅ๊ตฐ(๊ฐ์ 4์๋ฆฌ)\n",
" report part str ๊ฒฐํจ ๋ถ์(์ฅ๋น ๊ณํต)\n",
" report severity str ๊ฒฐํจ ์น๋ช
๋\n",
" text text str ๊ฒฐํจ ์์ ํ
์คํธ(๋น์ ํ)\n",
" text defect str ๊ฒฐํจ ์ ํ ๋ผ๋ฒจ\n",
" text n_tokens int ์ ์ ํ ํ ํฐ ์\n",
" text part_label str ์์ ์์ ์ถ์ถํ ๋ถ์ ๋ผ๋ฒจ\n",
"inspection vendor str ์ง์ ์ ๋น ์ํ์ฒ(์ธ์ฃผ ์
์ฒด ๋๋ ๋ถ๋์ ๋น)\n",
"inspection since_maint int ์ง์ ์ ๋น ํ ๊ฒฝ๊ณผ์ผ\n",
"inspection inspect_wait float ๊ฒ์ฌ ๋๊ธฐ์ผ(๊ฒ์ฌ ๋ณ๋ชฉ ์งํ)\n",
"inspection reinspect bool ์ฌ๊ฒ์ฌ ์ฌ๋ถ\n",
"... (์ดํ ์๋ต)\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
02. ๋น์ ํ ๊ฒฐํจ ์์ ํ
์คํธ ์ ์ , ์ฅ๋น ๋ถ์๋ณ/๊ฒฐํจ ์ ํ๋ณ ๋ผ๋ฒจ๋ง ์ ์ฒ๋ฆฌ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋น์ ํ ๊ฒฐํจ ์์ ํ
์คํธ ์ ์ ๋ฐ ๋ถ์๋ณ/์ ํ๋ณ ๋ผ๋ฒจ๋ง ์ ์ฒ๋ฆฌ ๋ชจ๋์ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋น์ ํ ๊ฒฐํจ ์์ ํ
์คํธ๋ฅผ ์ ์ ํ๊ณ ์ฅ๋น ๋ถ์๋ณยท๊ฒฐํจ ์ ํ๋ณ๋ก ๋ผ๋ฒจ๋งํ๋ ์ ์ฒ๋ฆฌ ๋ชจ๋์ ๊ตฌํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ค๋ณต QDR ์ ๊ฑฐ, ๋ถ๋ ์ฝ๋์ ์ผ์ ํ๊ธฐ ํต์ผ, ์ ๊ฐยทํน์๋ฌธ์ยท๊ณต๋ฐฑ ์ ๊ทํ, ์ฝ์ด(T/M ๋ฑ) ํ์ฅ, ์ฌ์ ๊ธฐ๋ฐ ์คํ ๊ต์ , ์์ ๋๊ธฐ์ผ ๊ฒฐ์ธก ์ฒ๋ฆฌ, KNN ๊ฒฐ์ธก ๋ณด์ , ๋ถ์ยท๊ฒฐํจ ์ ํ ํค์๋ ๋ผ๋ฒจ๋ง ๋ฑ ์ ์ ๊ท์น 10์ข
์ ์ ์ฉ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/etl/qlt05_data_cleansing.py ๋ฐ ์ ์ ํ์ง ๋ณด๊ณ ์` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ตฐ์ ํน์ ์์ธ(๋น๊ณํ ์ ๋น, ๊ธด๊ธ ๋ถ์ถ ๋ฑ) ๋ฐ์ดํฐ ๋ณด์กด์ ํ๋น์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ๋ฐ์ดํฐ ์ ์ ๊ท์น 10์ข
์ ์ฉ, ๋ฐ์ดํฐ ์ ํฉ์ฑ ๊ฒ์ฆ ํต๊ณผ์จ 100%\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ETL ํ์ดํ๋ผ์ธ ๊ตฌํ ๋ฐ ์ ์ ๋ ๋ง์คํฐ ๋ฐ์ดํฐ์
๊ตฌ์ถ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ์ ํ ๋ฐ์ดํฐ ํต๊ณ๋(๊ธฐ์ ํต๊ณ) ๋น๊ต ๋ฐ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"ํ๊ธฐ ๋ถ์ผ์นยท์ค๋ณตยท์ ๊ฐยทํน์๋ฌธ์ยท์ฝ์ดยท์คํยท๋ถ๊ฐ๋ฅ๊ฐยท๊ฒฐ์ธก์ด ์์ธ ๊ฐ์ QDR ์์ฒ ์๋ฃ์ ์ ์ ๊ท์น 10์ข
(ํ
์คํธ ์ ๊ทํ, ์ฝ์ด ํ์ฅ, ์คํ ๊ต์ , KNN ๊ฒฐ์ธก ๋ณด์ , ๋ถ์ยท๊ฒฐํจ ์ ํ ํค์๋ ๋ผ๋ฒจ๋ง ํฌํจ)์ ์ ์ฉํ๊ณ , ๊ธด๊ธ ๋ณด๊ณ ๋ ๋ณด์กดํ๋ฉฐ ์ ํฉ์ฑ ๊ฒ์ฆ ํต๊ณผ์จ๊ณผ ๋ผ๋ฒจ๋ง ์ ํ๋, ์ ์ ์ ํ ํต๊ณ๋ฅผ ํ์ธํ๋ค.\"\"\"\n",
"import re\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"from sklearn.impute import KNNImputer\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR) ์์ฑ๊ธฐ (์ค์ ๋ถ๋ยท์ฅ๋นยท์
์ฒดยท์ ๋น ์ค์ ์์)\n",
"# ๊ฐ์ ์ฅ๋น ๋ถ์ 12์ข
, ๊ฒฐํจ ์ ํ 8์ข
, ์น๋ช
๋(Critical/Major/Minor), ์์ธ 5์ข
, ๊ฐ์ ์ธ์ฃผ์ ๋น ์
์ฒด 4๊ณณ + ๋ถ๋์ ๋น.\n",
"# QDR 1๊ฑด๋ง๋ค ํ๊ตญ์ด ์์ ํ
์คํธ๋ฅผ ๋ง๋ค๊ณ , ์๋ณ ๊ฑด์๋ ๊ณ์ ยท์ถ์ธยทํ๋ จ ๊ฐ๋(usage)ยท์ผ๋ถ ์ ์กฐ ๋ถ๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ๋ฅธ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"PARTS = [ # (์ด๋ฆ, ๋ณ์นญ, ์ ๊ธฐ์ค ๊ฑด์, ๊ณ์ ์งํญ, ์ ์ ์, (Critical, Major ๋น์จ), ๊ฐ์ ๋จ๊ฐ์ง์, ์ ์กฐ์ฌ, ์ฃผ์ ๊ฒฐํจ ๊ฐ์ค์น)\n",
" (\"์์ง ์ฐ๋ฃ๊ณํต\", [\"์ฐ๋ฃ๊ณํต\", \"์ฐ๋ฃ ๋ผ์ธ\", \"์ฐ๋ฃ ํํ\"], 26, 0.15, 7, (0.10, 0.35), 6.0, \"MK1\", \"๋์ :4 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:2 ์ค์๋:2\"),\n",
" (\"๋ณ์๊ธฐ\", [\"๋ณ์๊ธฐ\", \"T/M\", \"๋ฏธ์
\"], 22, 0.10, 8, (0.12, 0.38), 9.0, \"MK1\", \"๋ง๋ชจ:4 ๋์ :2 ๊ณผ์ด:3 ์ค์๋:1\"),\n",
" (\"์ ๋ ์ฅ์น\", [\"์ ๋ ์ฅ์น\", \"๋ธ๋ ์ดํฌ\", \"์ ๋ ๋ผ์ธ\"], 20, 0.20, 12, (0.26, 0.40), 5.0, \"MK2\", \"๋ง๋ชจ:4 ๊ท ์ด:2 ๋์ :2 ์ฒด๊ฒฐ๋ถ๋:2\"),\n",
" (\"์กฐํฅ ์ฅ์น\", [\"์กฐํฅ ์ฅ์น\", \"ํธ๋ค ์ฐ๋๋ถ\", \"์กฐํฅ ๋ก๋\"], 14, 0.10, 6, (0.30, 0.40), 5.0, \"MK2\", \"์ฒด๊ฒฐ๋ถ๋:4 ๊ท ์ด:2 ๋ง๋ชจ:3 ์ค์๋:1\"),\n",
" (\"๋๊ฐ ๊ณํต\", [\"๋๊ฐ ๊ณํต\", \"๋ผ๋์์ดํฐ\", \"๋๊ฐ์ ๋ผ์ธ\"], 24, 0.30, 7, (0.05, 0.30), 3.0, \"MK3\", \"๋์ :4 ๊ณผ์ด:4 ๋ถ์:2\"),\n",
" (\"์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ\", [\"๋ฐฐํฐ๋ฆฌ\", \"์ ๊ธฐ ๊ณํต\", \"๋ฐ์ ๊ธฐ\"], 28, 0.35, 1, (0.04, 0.28), 2.5, \"MK3\", \"์ ์ด๋ถ๋:4 ๋ถ์:2 ๊ณผ์ด:2 ์ค์๋:2\"),\n",
" (\"ํต์ ๋ชจ๋\", [\"ํต์ ๋ชจ๋\", \"๋ฌด์ ๊ธฐ ์ฐ๋๋ถ\", \"์ํ
๋ ์ ์๋ถ\"], 16, 0.05, 4, (0.03, 0.25), 4.0, \"MK4\", \"์ ์ด๋ถ๋:5 ์ค์๋:4 ๋ถ์:1\"),\n",
" (\"์ ์ ๊ณํต\", [\"์ ์ ๊ณํต\", \"์ ์ ํธ์ค\", \"์ ์ ์ค๋ฆฐ๋\"], 18, 0.10, 9, (0.10, 0.35), 4.5, \"MK2\", \"๋์ :5 ๊ท ์ด:2 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"ํ๊ฐ ์ฅ์น\", [\"ํ๊ฐ ์ฅ์น\", \"์์คํ์
\", \"์์ถฉ๊ธฐ\"], 15, 0.08, 5, (0.06, 0.30), 3.5, \"MK4\", \"๋ง๋ชจ:4 ๊ท ์ด:3 ์ฒด๊ฒฐ๋ถ๋:2 ๋ถ์:1\"),\n",
" (\"์ฐจ์ฒด ์ฉ์ ๋ถ\", [\"์ฐจ์ฒด ์ฉ์ ๋ถ\", \"ํ๋ ์ ์ฉ์ ๋ถ\", \"์ฐจ์ฒด ์ด์๋ถ\"], 10, 0.05, 10, (0.28, 0.40), 7.0, \"MK1\", \"๊ท ์ด:6 ๋ถ์:3 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ดํยท์กฐ์ค ์ฅ๋น\", [\"๊ดํ ์ฅ๋น\", \"์กฐ์ค๊ฒฝ\", \"๊ด์ธก ์ฅ๋น\"], 8, 0.05, 3, (0.03, 0.22), 8.0, \"MK4\", \"์ค์๋:4 ์ ์ด๋ถ๋:3 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ณต๊ธฐ ์ ํ ํํฐ\", [\"๊ณต๊ธฐ ํํฐ\", \"ํก๊ธฐ ํํฐ\", \"์ ํ ์ฅ์น\"], 12, 0.25, 4, (0.01, 0.12), 1.0, \"MK3\", \"๋ง๋ชจ:5 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:3\")]\n",
"NP = len(PARTS)\n",
"DEFECTS = { # ๊ฒฐํจ ์ ํ: ์ฆ์ ํํ(๋์์ด)\n",
" \"๊ท ์ด\": [\"๊ท ์ด์ด ๋ฐ๊ฒฌ๋จ\", \"ํฌ๋์ด ํ์ธ๋จ\", \"๊ฐ๋ผ์ง์ด ์์\"], \"๋์ \": [\"๋์ ๊ฐ ๋ฐ์ํจ\", \"์ค์ผ์ด ์์ด ๋์ด\", \"๋์ค ํ์ ์ด ์์\"],\n",
" \"๋ง๋ชจ\": [\"๋ง๋ชจ๊ฐ ์ฌํจ\", \"๊ณผ๋ํ ๋ง๋ชจ๊ฐ ํ์ธ๋จ\", \"๋ณ์ ํ์ ์ด ์์\"], \"๋ถ์\": [\"๋ถ์์ด ์งํ๋จ\", \"๋
น์ด ๋ฐ์ํจ\", \"์ฐํ ํ์ ์ด ์์\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋์ด ๋ฐ์ํจ\", \"์ปค๋ฅํฐ ๋จ์ ์ด ์์ฌ๋จ\", \"์ ํธ๊ฐ ๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ ํ๋ฆผ์ด ํ์ธ๋จ\", \"์ฒด๊ฒฐ ์ํ๊ฐ ๋ถ๋ํจ\", \"๊ณ ์ ๋ถ๊ฐ ํ๊ฑฐ์\"],\n",
" \"๊ณผ์ด\": [\"๊ณผ์ด ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํจ\", \"์จ๋๊ฐ ๊ณผ๋ํ๊ฒ ์์นํจ\", \"์ด์ด ์ฌํ๊ฒ ๋จ\"], \"์ค์๋\": [\"์ค์๋์ด ๋ฐ๋ณต๋จ\", \"์๋์ด ๋ถ์์ ํจ\", \"๊ฐํ์ ์ผ๋ก ๋ฉ์ถค\"]}\n",
"DNAMES = list(DEFECTS)\n",
"SEV = [\"Minor\", \"Major\", \"Critical\"]\n",
"SEV_CUE = {0: [\"๊ฒฝ๋ฏธํ ์์ค์\", \"์ด์ฉ์๋ ์ง์ฅ์ด ์์\", \"์ก์ ์ ๊ฒ์ ์ฌ์ํ ์ด์์\", \"๊ธฐ๋ฅ์๋ ์ํฅ์ด ์์\"],\n",
" 1: [\"์ฑ๋ฅ ์ ํ๊ฐ ์์\", \"์กฐ์ํ ์ ๋น๊ฐ ํ์ํจ\", \"์ถ๋ ฅ์ด ๋จ์ด์ง๋ ์ํ์\", \"๊ธฐ๋ฅ์ด ๋ถ๋ถ์ ์ผ๋ก ์ ํ๋จ\"],\n",
" 2: [\"์ฆ์ ์ด์ฉ ์ค์งํจ\", \"์์ ์ ์ํ์ด ์์ด ์ฌ์ฉ ๋ถ๊ฐ\", \"์๋ ์ ์ง๋ก ์๋ฌด ์ํ์ด ์ด๋ ค์\", \"์ธ๋ช
์์ ์ํ์ด ์ฐ๋ ค๋จ\"]}\n",
"CAUSES = [\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\", \"์ด์ฉ ๋ถ์ฃผ์\", \"์ค๊ณ ์ทจ์ฝ\", \"์์ฐ ๋
ธํ\"]\n",
"CAUSE_CUE = {\n",
" \"์ ์กฐ ๋ถ๋\": [\"์ ํ ์ธ์ ์งํ ๋ฐ์ํจ\", \"์ถ๊ณ ์ํ์์ ์ด๋ฏธ ์ด์์ด ์์์\", \"์ธ์ ํ ์ผ๋ง ์ง๋์ง ์์ ๋ฐ์ํจ\"],\n",
" \"์ ๋น ํ์ง\": [\"์ธ์ฃผ ์ ๋น ํ ์ผ๋ง ๋์ง ์์ ์ฌ๋ฐํจ\", \"์ ๋น ํ ์ฌ์กฐ๋ฆฝ ์ํ๊ฐ ๋ถ๋ํจ\", \"์ ๋น ์งํ ๊ฐ์ ๋ถ์์์ ๋ค์ ๋ฐ์ํจ\"],\n",
" \"์ด์ฉ ๋ถ์ฃผ์\": [\"ํ์ง ๊ธฐ๋ ์ค ์ถฉ๊ฒฉ ์ดํ ๋ฐ์ํจ\", \"๊ณผ๋ถํ ์ด์ฉ ํ ๋ฐ์ํจ\", \"์ ๊ฒ ์์ด ์ฐ์ ์ด์ฉํ ๋ค ๋ฐ์ํจ\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์์ด ๋ค๋ฅธ ์ฐจ๋์์๋ ๋ฐ๋ณต๋จ\", \"๊ตฌ์กฐ์ ์ผ๋ก ์ทจ์ฝํ ๋ถ์๋ก ๋ณด์\", \"์ฌ๋ฌ ๋ถ๋์์ ๊ฐ์ ํํ๋ก ๋ณด๊ณ ๋จ\"],\n",
" \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ์ด ๋๋ํจ\", \"์ฅ๊ธฐ๊ฐ ์ฌ์ฉ์ผ๋ก ๋
ธํ๋จ\", \"์๋ช
์ฃผ๊ธฐ ๋ง๊ธฐ์ ๋ฐ์ํจ\"]}\n",
"VENDORS = [\"์ธ์ฃผA\", \"์ธ์ฃผB\", \"์ธ์ฃผC\", \"์ธ์ฃผD\", \"๋ถ๋์ ๋น\"]\n",
"ACTIONS = [\"์ ๋น์ ์
๊ณ ๋ฅผ ์์ฒญํจ\", \"ํ์ฅ ์์ ์กฐ์น ํ ์ด์ฉ ์ค์\", \"๋ถํ ๊ตํ์ ์์ฒญํจ\", \"์ฌ๊ฒ์ฌ๋ฅผ ์์ฒญํจ\", \"์์ธ ํ์ธ์ด ํ์ํจ\"]\n",
"MAKER_OF = [p[7] for p in PARTS]\n",
"PRICE = np.array([p[6] for p in PARTS])\n",
"\n",
"\n",
"def usage_index(months, seed=0):\n",
" \"\"\"์๋ณ ํ๋ จ ๊ฐ๋(๋ฏธ๋ฆฌ ์ ์ ์๋ ๊ณํ ๊ฐ): ์ฐ 1ํ ์ ์ ์ ๊ฐ๋ ๊ณ์ + ์์ ์ก์\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 2] ๋น์ ํ ๊ฒฐํจ ์์ ํ
์คํธ ์ ์ , ์ฅ๋น ๋ถ์๋ณ/๊ฒฐํจ ์ ํ๋ณ ๋ผ๋ฒจ๋ง ์ ์ฒ๋ฆฌ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_qdr(months=36, seed=1, scale=1.0, surge=True):\n",
" \"\"\"๋ฐํ: QDR DataFrame(๊ฑด๋น 1ํ), ์๋ณ usage, ๊ธ์ฆ ์ด๋ฒคํธ ๋ชฉ๋ก\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" use = usage_index(months, seed)\n",
" events = []\n",
" if surge:\n",
" for _ in range(max(2, months // 12 * 2)):\n",
" events.append((int(rng.integers(NP)), int(rng.integers(6, months))))\n",
" rows = []\n",
" wdef = [np.array([float(x.split(\":\")[1]) for x in p[8].split()]) for p in PARTS]\n",
" ddef = [[x.split(\":\")[0] for x in p[8].split()] for p in PARTS]\n",
" n = 0\n",
" for m in range(months):\n",
" for pi, p in enumerate(PARTS):\n",
" lam = p[2] * scale * (1 + 0.010 * m) * use[m] * (1 + p[3] * np.cos(2 * np.pi * (m % 12 - p[4]) / 12))\n",
" ev = (pi, m) in events\n",
" k = rng.poisson(lam * (2.6 if ev else 1.0))\n",
" for _ in range(k):\n",
" rows.append((n, m, pi, ev)); n += 1\n",
" N = len(rows)\n",
" A = np.array(rows)\n",
" month, part, ev = A[:, 1], A[:, 2], A[:, 3].astype(bool)\n",
" defect = np.array([rng.choice(ddef[i], p=wdef[i] / wdef[i].sum()) for i in part])\n",
" cr = np.array([PARTS[i][5][0] for i in part]); ma = np.array([PARTS[i][5][1] for i in part])\n",
" u = rng.random(N)\n",
" sev = np.where(u < cr, 2, np.where(u < cr + ma, 1, 0))\n",
" vendor = rng.choice(VENDORS, N, p=[0.25, 0.2, 0.2, 0.15, 0.2])\n",
" # ์์ธ: ๊ธฐ๋ณธ ๋น์จ์ ๋ถ์(์ ์กฐ์ฌ)ยท์
์ฒดยท์ด๋ฒคํธ ํจ๊ณผ๋ฅผ ๊ณฑํ๋ค\n",
" base = np.array([0.22, 0.20, 0.20, 0.16, 0.22])\n",
" P = np.tile(base, (N, 1))\n",
" P[vendor == \"์ธ์ฃผB\", 1] *= 2.6\n",
" P[ev, 0] *= 5.0\n",
" design = np.isin(part, [2, 3, 7]); P[design, 3] *= 2.2\n",
" P[np.isin(part, [4, 5, 11]), 0] *= 2.0 # ์ ์กฐ์ฌ MK3 ๋ฉํ ๋ถ์๋ ์ ์กฐ ๋ถ๋ ๋น์ค์ด ๋๋ค\n",
" P[defect == \"๋ง๋ชจ\", 4] *= 1.6; P[defect == \"๋์ \", 4] *= 1.3\n",
" P /= P.sum(axis=1, keepdims=True)\n",
" cause = np.array([CAUSES[rng.choice(5, p=P[i])] for i in range(N)])\n",
" since = np.where(cause == \"์ ๋น ํ์ง\", rng.exponential(35, N), rng.exponential(160, N)).clip(1, 720).round().astype(int)\n",
" wait = (rng.lognormal(1.3, 0.5, N) * np.where(vendor == \"์ธ์ฃผB\", 1.7, np.where(vendor == \"๋ถ๋์ ๋น\", 0.6, 1.0)) * (0.7 + 0.5 * use[month])).round(1)\n",
" reins = rng.random(N) < np.where(cause == \"์ ๋น ํ์ง\", 0.6, 0.08)\n",
" texts = []\n",
" for i in range(N):\n",
" pt = PARTS[part[i]]\n",
" cl = [f\"{rng.choice(pt[1])}์์ {rng.choice(DEFECTS[defect[i]])}\"]\n",
" if rng.random() < 0.7: cl.append(rng.choice(SEV_CUE[sev[i]]))\n",
" if rng.random() < 0.65: # ์์ธ ๋จ์: 8%๋ ์ค์ ์์ธ๊ณผ ๋ค๋ฅธ ๋จ์๊ฐ ์ ํ๋ค(๋ณด๊ณ ์์ ์ถ์ )\n",
" cc = cause[i] if rng.random() > 0.08 else rng.choice(CAUSES)\n",
" cl.append(rng.choice(CAUSE_CUE[cc]))\n",
" cl.append(rng.choice(ACTIONS))\n",
" if rng.random() < 0.5: cl.insert(0, f\"VU-{rng.integers(1, 31):02d} ์์\")\n",
" texts.append(\". \".join(cl) + \".\")\n",
" df = pd.DataFrame({\"qdr_id\": [f\"Q{seed}{i:06d}\" for i in range(N)], \"month\": month, \"unit\": [f\"VU-{x:02d}\" for x in rng.integers(1, 31, N)],\n",
" \"part\": part, \"defect\": defect, \"sev\": sev, \"cause\": cause, \"vendor\": vendor, \"maker\": [MAKER_OF[i] for i in part],\n",
" \"since_maint\": since, \"inspect_wait\": wait, \"reinspect\": reins, \"event\": ev, \"text\": texts})\n",
" df[\"cost\"] = PRICE[df[\"part\"]] * np.array([1.0, 2.0, 4.0])[df[\"sev\"]] # [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์๋ฆฌยท์กฐ์น ๋น์ฉ์ง์\n",
" return df, use, events\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# ๊ณตํต ํ
์คํธ ์กฐ๊ฐ: ์ ๊ทํ, ๊ฐ์ด ํ ํฐํ(์กฐ์ฌ ์ ๊ฑฐ), ๋ถ์ยท๊ฒฐํจยท์น๋ช
๋ยท์์ธ ํค์๋ ๋ผ๋ฒจ๋ง\n",
"# ---------------------------------------------------------------------------\n",
"TYPO = {\"๋์ณ\": \"๋์ \", \"๊ท ๋ ฌ\": \"๊ท ์ด\", \"์ ์ถ ๋ถ๋\": \"์ ์ด ๋ถ๋\", \"๋ถ์ฉ\": \"๋ถ์\", \"๋ง๋ฌ\": \"๋ง๋ชจ\", \"๊ณผ๋ ฌ\": \"๊ณผ์ด\", \"์ค์๋ฅ\": \"์ค์๋\", \"์ฌ๋\": \"์ ๋\"}\n",
"ABBR = {\"T/M\": \"๋ณ์๊ธฐ\", \"BAT\": \"๋ฐฐํฐ๋ฆฌ\"}\n",
"PART_KW = sorted([(a, i) for i, p in enumerate(PARTS) for a in p[1]], key=lambda x: -len(x[0]))\n",
"DEF_KW = {\"๊ท ์ด\": [\"๊ท ์ด\", \"ํฌ๋\", \"๊ฐ๋ผ\"], \"๋์ \": [\"๋์ \", \"์์ด\", \"๋์ค\"], \"๋ง๋ชจ\": [\"๋ง๋ชจ\", \"๋ณ์\"], \"๋ถ์\": [\"๋ถ์\", \"๋
น์ด\", \"์ฐํ\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋\", \"๋จ์ \", \"๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ\", \"์ฒด๊ฒฐ\", \"ํ๊ฑฐ\"], \"๊ณผ์ด\": [\"๊ณผ์ด\", \"์จ๋\", \"์ด์ด\"], \"์ค์๋\": [\"์ค์๋\", \"๋ถ์์ \", \"๋ฉ์ถค\"]}\n",
"SEV_KW = [(2, [\"์ฆ์ ์ด์ฉ ์ค์ง\", \"์ฌ์ฉ ๋ถ๊ฐ\", \"์ํ\"]), (1, [\"์ฑ๋ฅ ์ ํ\", \"์กฐ์ํ\"]), (0, [\"๊ฒฝ๋ฏธ\", \"์ง์ฅ์ด ์\"])]\n",
"CAUSE_KW = {\"์ ์กฐ ๋ถ๋\": [\"์ ํ\", \"์ถ๊ณ \", \"์ธ์\"], \"์ ๋น ํ์ง\": [\"์ ๋น ํ\", \"์ฌ์กฐ๋ฆฝ\", \"์ ๋น ์งํ\"], \"์ด์ฉ ๋ถ์ฃผ์\": [\"์ถฉ๊ฒฉ\", \"๊ณผ๋ถํ\", \"์ ๊ฒ ์์ด\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์\", \"๊ตฌ์กฐ์ \", \"์ฌ๋ฌ ๋ถ๋\"], \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ\", \"๋
ธํ\", \"์๋ช
\"]}\n",
"PARTICLES = (\"์์\", \"์ผ๋ก\", \"์ด\", \"๊ฐ\", \"์\", \"๋\", \"์\", \"๋ฅผ\", \"์\", \"์\", \"๋ก\", \"์\", \"๊ณผ\", \"๋\", \"ํจ\", \"์\", \"๋จ\")\n",
"\n",
"\n",
"def norm(t):\n",
" \"\"\"์ ๊ทํ: ์ ๊ฐ->๋ฐ๊ฐ, ํน์๋ฌธ์ยท์ค๋ณต ๊ณต๋ฐฑ ์ ๊ฑฐ, ์ฝ์ด ํ์ฅ, ์คํ ๊ต์ \"\"\"\n",
" # [TODO : ํ์ ์ค์ต 2] ๋น์ ํ ๊ฒฐํจ ์์ ํ
์คํธ ์ ์ , ์ฅ๋น ๋ถ์๋ณ/๊ฒฐํจ ์ ํ๋ณ ๋ผ๋ฒจ๋ง ์ ์ฒ๋ฆฌ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tok(t):\n",
" out = []\n",
" for w in re.split(r\"[\\s.,]+\", norm(t)):\n",
" if not w or re.fullmatch(r\"[\\d\\-A-Za-z]+\", w):\n",
" continue\n",
" for p in PARTICLES:\n",
" if len(w) > len(p) + 1 and w.endswith(p):\n",
" w = w[:-len(p)]\n",
" break\n",
" out.append(w)\n",
" return out\n",
"\n",
"\n",
"def label_part(t):\n",
" for a, i in PART_KW:\n",
" if a in t:\n",
" return i\n",
" return -1\n",
"\n",
"\n",
"def label_defect(t):\n",
" for d, ks in DEF_KW.items():\n",
" if any(k in t for k in ks):\n",
" return d\n",
" return \"๋ฏธ๋ถ๋ฅ\"\n",
"\n",
"\n",
"def label_sev(t):\n",
" for s, ks in SEV_KW:\n",
" if any(k in t for k in ks):\n",
" return s\n",
" return -1\n",
"\n",
"\n",
"def label_cause(t):\n",
" for c, ks in CAUSE_KW.items():\n",
" if any(k in t for k in ks):\n",
" return c\n",
" return \"๋ฏธ์\"\n",
"\n",
"# 1) [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์์ฒ ์๋ฃ: ์์ฑ ์๋ฃ์ ์ผ๋ถ๋ฌ ์ค๋ฅ๋ฅผ ์๋๋ค\n",
"df0, use, events = make_qdr(36, seed=1)\n",
"rng = np.random.default_rng(2)\n",
"raw = df0[[\"qdr_id\", \"month\", \"unit\", \"vendor\", \"since_maint\", \"inspect_wait\", \"text\", \"part\", \"defect\"]].copy()\n",
"raw[\"date\"] = [f\"2024-{m % 12 + 1:02d}-{rng.integers(1, 28):02d}\" for m in raw[\"month\"]]\n",
"raw[\"urgent\"] = False\n",
"n = len(raw)\n",
"urg = rng.choice(n, 40, replace=False)\n",
"raw.loc[urg, \"inspect_wait\"] = 0.0; raw.loc[urg, \"urgent\"] = True # ๊ธด๊ธ ๋ณด๊ณ : ๋๊ธฐ์ผ 0์ ์ ์ ์์ธ\n",
"raw.loc[urg, \"text\"] = \"๊ธด๊ธ ๋ณด๊ณ . \" + raw.loc[urg, \"text\"]\n",
"\n",
"\n",
"def pick(k):\n",
" return rng.choice(n, k, replace=False)\n",
"\n",
"\n",
"i2 = pick(300)\n",
"raw.loc[i2, \"unit\"] = [u.lower() if j % 3 == 0 else u.replace(\"-\", \"\") if j % 3 == 1 else \"VU-\" + str(int(u[-2:])) for j, u in enumerate(raw.loc[i2, \"unit\"])]\n",
"i3 = pick(400); raw.loc[i3, \"date\"] = [d.replace(\"-\", \".\") if j % 2 else d[2:].replace(\"-\", \"/\") for j, d in enumerate(raw.loc[i3, \"date\"])]\n",
"i4 = pick(600); raw.loc[i4, \"text\"] = [t.replace(\". \", \". \").replace(\"์์\", \"์์ \").replace(\"ํจ.\", \"ํจ!!\") + \" ##\" if j % 2 else unicodedata.normalize(\"NFKC\", t).replace(\".\", \"๏ผ\") for j, t in enumerate(raw.loc[i4, \"text\"])]\n",
"i5 = pick(300); raw.loc[i5, \"text\"] = [t.replace(\"๋ณ์๊ธฐ\", \"T/M\").replace(\"๋ฐฐํฐ๋ฆฌ\", \"BAT\") for t in raw.loc[i5, \"text\"]]\n",
"i6 = rng.choice(np.where(raw[\"text\"].str.contains(\"๋์ |๊ท ์ด|๋ถ์|์ ๋|๊ณผ์ด\"))[0], 400, replace=False); raw.loc[i6, \"text\"] = [t.replace(\"๋์ \", \"๋์ณ\").replace(\"๊ท ์ด\", \"๊ท ๋ ฌ\").replace(\"๋ถ์\", \"๋ถ์ฉ\").replace(\"์ ๋\", \"์ฌ๋\").replace(\"๊ณผ์ด\", \"๊ณผ๋ ฌ\") for t in raw.loc[i6, \"text\"]]\n",
"i7 = pick(80); raw.loc[i7, \"inspect_wait\"] = -1.0\n",
"i8 = pick(400); raw.loc[i8, \"since_maint\"] = np.nan\n",
"dup = raw.sample(150, random_state=3); raw = pd.concat([raw, dup], ignore_index=True)\n",
"print(f\"์์ฒ ์๋ฃ {len(raw)}ํ (์ ์ {n}ํ + ๊ธด๊ธ ๋ณด๊ณ {len(urg)}๊ฑด + ์ค๋ณต {len(dup)}ํ, ์ค๋ฅ ์ฃผ์
{len(i2) + len(i3) + len(i4) + len(i5) + len(i6) + len(i7) + len(i8)}ํ)\")\n",
"\n",
"# 2) ์ ์ ๊ท์น 10์ข
\n",
"log = []\n",
"\n",
"\n",
"def rule(name, cnt):\n",
" log.append((name, int(cnt)))\n",
"\n",
"\n",
"df = raw.copy()\n",
"before_len = len(df)\n",
"df = df.drop_duplicates(\"qdr_id\").reset_index(drop=True); rule(\"R1 ์ค๋ณต QDR ์ ๊ฑฐ\", before_len - len(df))\n",
"fix = lambda u: \"VU-%02d\" % int(re.sub(r\"\\D\", \"\", u))\n",
"bad = ~df[\"unit\"].str.fullmatch(r\"VU-\\d{2}\"); df[\"unit\"] = df[\"unit\"].map(fix); rule(\"R2 ๋ถ๋ ์ฝ๋ ํ๊ธฐ ํต์ผ\", bad.sum())\n",
"bad = ~df[\"date\"].str.fullmatch(r\"\\d{4}-\\d{2}-\\d{2}\")\n",
"df[\"date\"] = [pd.to_datetime(d.replace(\".\", \"-\"), format=\"%Y-%m-%d\" if len(d) == 10 else \"%y-%m-%d\", errors=\"coerce\") if \"/\" not in d else pd.to_datetime(d, format=\"%y/%m/%d\") for d in df[\"date\"]]\n",
"rule(\"R3 ์ผ์ ํ์ ํต์ผ\", bad.sum())\n",
"t0 = df[\"text\"].copy()\n",
"df[\"text\"] = df[\"text\"].map(lambda t: re.sub(r\"\\s+\", \" \", re.sub(r\"[!#~*@^]+\", \" \", unicodedata.normalize(\"NFKC\", t))).strip()); rule(\"R4 ํ
์คํธ ์ ๊ทํ(์ ๊ฐยทํน์๋ฌธ์ยท๊ณต๋ฐฑ)\", (df[\"text\"] != t0).sum())\n",
"c = df[\"text\"].str.contains(\"T/M|BAT\"); df[\"text\"] = df[\"text\"].map(norm); rule(\"R5 ์ฝ์ด ํ์ฅ(T/M, BAT)\", c.sum())\n",
"c = t0.map(lambda t: any(k in t for k in TYPO))\n",
"rule(\"R6 ์คํ ๊ต์ (์ฌ์ ๊ธฐ๋ฐ)\", c.sum())\n",
"bad = df[\"inspect_wait\"] < 0; df.loc[bad, \"inspect_wait\"] = np.nan; rule(\"R7 ๋ถ๊ฐ๋ฅ๊ฐ(์์ ๋๊ธฐ์ผ) ๊ฒฐ์ธก ์ฒ๋ฆฌ\", bad.sum())\n",
"miss = df[\"since_maint\"].isna() | df[\"inspect_wait\"].isna()\n",
"X = pd.DataFrame({\"part\": df[\"part\"], \"vendor\": df[\"vendor\"].astype(\"category\").cat.codes * 5, \"wait\": df[\"inspect_wait\"], \"since\": df[\"since_maint\"], \"urgent\": df[\"urgent\"] * 20})\n",
"df[[\"inspect_wait\", \"since_maint\"]] = KNNImputer(n_neighbors=5).fit_transform(X)[:, 2:4]; rule(\"R8 ๋๊ธฐ์ผยท์ ๋นํ๊ฒฝ๊ณผ์ผ KNN ๋ณด์ \", miss.sum())\n",
"df[\"part_label\"] = df[\"text\"].map(label_part); df[\"defect_label\"] = df[\"text\"].map(label_defect); rule(\"R9 ๋ถ์ยท๊ฒฐํจ ์ ํ ํค์๋ ๋ผ๋ฒจ๋ง\", len(df))\n",
"flag = (df[\"inspect_wait\"] > 40) | ((df[\"inspect_wait\"] == 0) & ~df[\"urgent\"])\n",
"df[\"flag\"] = flag\n",
"rule(\"R10 ๊ธด๊ธ ๋ณด๊ณ ๋ณด์กด(์ผ๋ฐ ์ด์๊ฐ์ ํ์๋ง)\", int(df[\"urgent\"].sum()))\n",
"print(\"\\n[์ ์ฉ ๊ท์น๊ณผ ์ฒ๋ฆฌ ๊ฑด์]\")\n",
"for nm, c in log:\n",
" print(f\" {nm:40s} {c:6d}๊ฑด\")\n",
"\n",
"# 3) ์ ํฉ์ฑ ๊ฒ์ฆ + ๋ผ๋ฒจ๋ง ์ ํ๋\n",
"truth = df.merge(df0[[\"qdr_id\", \"part\", \"defect\"]], on=\"qdr_id\", suffixes=(\"\", \"_t\"))\n",
"rawlab = raw.drop_duplicates(\"qdr_id\").assign(pl=lambda d: d[\"text\"].map(label_part), dl=lambda d: d[\"text\"].map(label_defect))\n",
"rawacc_p, rawacc_d = (rawlab[\"pl\"] == rawlab[\"part\"]).mean(), (rawlab[\"dl\"] == rawlab[\"defect\"]).mean()\n",
"acc_p, acc_d = (truth[\"part_label\"] == truth[\"part_t\"]).mean(), (truth[\"defect_label\"] == truth[\"defect_t\"]).mean()\n",
"checks = {\n",
" \"R1 ID ์ ์ผ\": df[\"qdr_id\"].is_unique, \"R2 ๋ถ๋ ์ฝ๋ ํ์\": df[\"unit\"].str.fullmatch(r\"VU-\\d{2}\").all(), \"R3 ์ผ์ ๋ณํ\": df[\"date\"].notna().all(),\n",
" \"R4 ํน์๋ฌธ์ยท์ด์ค ๊ณต๋ฐฑ ์์\": not df[\"text\"].str.contains(r\"[!#]|\\s\\s|๏ผ\").any(), \"R5 ์ฝ์ด ์์\": not df[\"text\"].str.contains(\"T/M|BAT\").any(),\n",
" \"R6 ์คํ ์์\": not df[\"text\"].map(lambda t: any(k in t for k in TYPO)).any(), \"R7 ์์ ๋๊ธฐ์ผ ์์\": (df[\"inspect_wait\"] >= 0).all(),\n",
" \"R8 ๊ฒฐ์ธก ์์\": df[[\"inspect_wait\", \"since_maint\"]].notna().all().all(), \"R9 ๋ถ์ ๋ผ๋ฒจ ๋ถ์ฌ์จ 100%\": (df[\"part_label\"] >= 0).all(),\n",
" \"R10 ๊ธด๊ธ ๋ณด๊ณ 40๊ฑด ๋ณด์กด\": int(df[\"urgent\"].sum()) == 40}\n",
"npass = sum(checks.values())\n",
"print(f\"\\n์ ํฉ์ฑ ๊ฒ์ฆ {npass}/10 ํต๊ณผ, ํต๊ณผ์จ {npass / 10:.0%} (๋ชฉํ 100%: {'๋ฌ์ฑ' if npass == 10 else '๋ฏธ๋ฌ'})\")\n",
"print(f\"๋ผ๋ฒจ๋ง ์ ํ๋: ๋ถ์ {rawacc_p:.1%} -> {acc_p:.1%}, ๊ฒฐํจ ์ ํ {rawacc_d:.1%} -> {acc_d:.1%} (์ ์ ์ -> ํ)\")\n",
"print(f\"๊ธด๊ธ ๋ณด๊ณ {int(df['urgent'].sum())}๊ฑด ๋ณด์กด, ์ผ๋ฐ ์ด์ ํ์ {int((df['flag'] & ~df['urgent']).sum())}๊ฑด\")\n",
"\n",
"# 4) ์ ์ ์ ํ ๊ธฐ์ ํต๊ณ\n",
"def stats(d, after):\n",
" return {\"ํ ์\": len(d), \"๋ถ๋ ํ๊ธฐ ์ข
๋ฅ ์\": d[\"unit\"].nunique(), \"์์ ํ๊ท ๊ธธ์ด(์)\": round(d[\"text\"].str.len().mean(), 1),\n",
" \"๋๊ธฐ์ผ ์ต์\": round(d[\"inspect_wait\"].min(), 1), \"๋๊ธฐ์ผ ๊ฒฐ์ธก ์\": int(d[\"inspect_wait\"].isna().sum()), \"์ ๋นํ๊ฒฝ๊ณผ์ผ ๊ฒฐ์ธก ์\": int(d[\"since_maint\"].isna().sum())}\n",
"print(\"\\n[์ ์ ์ ํ ๊ธฐ์ ํต๊ณ]\")\n",
"print(pd.DataFrame({\"์ ์ ์ \": stats(raw, False), \"์ ์ ํ\": stats(df, True)}).to_string())\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
02 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"์์ฒ ์๋ฃ 9371ํ (์ ์ 9221ํ + ๊ธด๊ธ ๋ณด๊ณ 40๊ฑด + ์ค๋ณต 150ํ, ์ค๋ฅ ์ฃผ์
2480ํ)\n",
"\n",
"[์ ์ฉ ๊ท์น๊ณผ ์ฒ๋ฆฌ ๊ฑด์]\n",
" R1 ์ค๋ณต QDR ์ ๊ฑฐ 150๊ฑด\n",
" R2 ๋ถ๋ ์ฝ๋ ํ๊ธฐ ํต์ผ 238๊ฑด\n",
" R3 ์ผ์ ํ์ ํต์ผ 400๊ฑด\n",
" R4 ํ
์คํธ ์ ๊ทํ(์ ๊ฐยทํน์๋ฌธ์ยท๊ณต๋ฐฑ) 600๊ฑด\n",
" R5 ์ฝ์ด ํ์ฅ(T/M, BAT) 294๊ฑด\n",
" R6 ์คํ ๊ต์ (์ฌ์ ๊ธฐ๋ฐ) 400๊ฑด\n",
" R7 ๋ถ๊ฐ๋ฅ๊ฐ(์์ ๋๊ธฐ์ผ) ๊ฒฐ์ธก ์ฒ๋ฆฌ 80๊ฑด\n",
" R8 ๋๊ธฐ์ผยท์ ๋นํ๊ฒฝ๊ณผ์ผ KNN ๋ณด์ 474๊ฑด\n",
" R9 ๋ถ์ยท๊ฒฐํจ ์ ํ ํค์๋ ๋ผ๋ฒจ๋ง 9221๊ฑด\n",
" R10 ๊ธด๊ธ ๋ณด๊ณ ๋ณด์กด(์ผ๋ฐ ์ด์๊ฐ์ ํ์๋ง) 40๊ฑด\n",
"\n",
"์ ํฉ์ฑ ๊ฒ์ฆ 10/10 ํต๊ณผ, ํต๊ณผ์จ 100% (๋ชฉํ 100%: ๋ฌ์ฑ)\n",
"๋ผ๋ฒจ๋ง ์ ํ๋: ๋ถ์ 98.5% -> 100.0%, ๊ฒฐํจ ์ ํ 96.8% -> 100.0% (์ ์ ์ -> ํ)\n",
"๊ธด๊ธ ๋ณด๊ณ 40๊ฑด ๋ณด์กด, ์ผ๋ฐ ์ด์ ํ์ 1๊ฑด\n",
"\n",
"[์ ์ ์ ํ ๊ธฐ์ ํต๊ณ]\n",
" ์ ์ ์ ์ ์ ํ\n",
"ํ ์ 9371.0 9221.0\n",
"๋ถ๋ ํ๊ธฐ ์ข
๋ฅ ์ 98.0 30.0\n",
"์์ ํ๊ท ๊ธธ์ด(์) 56.4 56.1\n",
"๋๊ธฐ์ผ ์ต์ -1.0 0.0\n",
"๋๊ธฐ์ผ ๊ฒฐ์ธก ์ 0.0 0.0\n",
"์ ๋นํ๊ฒฝ๊ณผ์ผ ๊ฒฐ์ธก ์ 406.0 0.0\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
03. ๋น๋ ๋ถ์ ๊ธฐ๋ฐ 1์ฐจ ๋ค๋ฐ ๊ฒฐํจ ๋ถํ ๋์ถ Baseline ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋น๋ ๋ถ์ ๊ธฐ๋ฐ ๋ค๋ฐ ๊ฒฐํจ ๋ถํ ๋์ถ Baseline ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋น๋ ๋ถ์ ๊ธฐ๋ฐ 1์ฐจ ๋ค๋ฐ ๊ฒฐํจ ๋ถ์ ๋์ถ Baseline์ ๊ตฌํํ๊ณ , ํ์์ ๋ถ์์ผ๋ก ๋น๋ ๊ธฐ์ค์ ํ๊ณ์ ์ธ์ฃผ์ ๋น ๊ฒ์ฌ ๋ณ๋ชฉ, ํต์ฌ ๊ด๋ฆฌ ๋์์ ์ฐพ๋๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **ํ์ต ๊ธฐ๊ฐ ๊ฑด์ ์์ 5๊ฐ ๋ถ์๋ฅผ ๋ค๋ฐ ๊ฒฐํจ ๋ถ์๋ก ์ง์ ํด ๊ฒ์ฆ ๊ธฐ๊ฐ ์ ์ค๋ฅ ๊ณผ ์น๋ช
๋ ๊ฐ์ค ์ง์ ์์์์ ์ฐจ์ด ํ์ธํ๊ณ ์ ํจ์ฑ์ ์ ๊ฒํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `notebooks/qlt05_eda_analysis.ipynb ๋ฐ EDA ์์ฝ ๋ฆฌํฌํธ` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ตฐ์ ์์ฌ๊ฒฐ์ ์ ์ง๊ด์ ์ธ ์ธ์ฌ์ดํธ ์ ๊ณต ์์ค\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: EDA ์๊ฐํ ์ฐจํธ 8์ข
์ด์ ์์ฑ, ์ ์๋ฏธํ ์๊ด๊ด๊ณ ํผ์ฒ 5๊ฐ ์ด์ ๋์ถ\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: EDA ๋
ธํธ๋ถ ์คํ ์๋ฃ ๋ฐ ํต์ฌ ๋ฐ๊ฒฌ์ (Key Findings) ๋ฌธ์ํ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ํต๊ณ์ ๊ฐ์ค ๊ฒ์ (p-value, ์๊ด๊ณ์)์ ํตํ ์ ์์ฑ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๋น๋ ๋ถ์ ๊ธฐ๋ฐ 1์ฐจ ๋ค๋ฐ ๊ฒฐํจ ๋ถ์ ๋์ถ Baseline(ํ์ต ๊ธฐ๊ฐ ๊ฑด์ ์์ 5๊ฐ ๋ถ์)์ ์ ์ค๋ฅ ๊ณผ ํ๊ณ(์น๋ช
๋ยท๋น์ฉ ์์์์ ์ฐจ์ด)๋ฅผ ํ์ธํ๊ณ , ํ์์ ๋ถ์(EDA ์ฐจํธ 8์ข
, ์๊ฐ QDR ๊ฑด์์ ํผ์ฒ์ ์๊ด๊ณ์ยทp-value, ์ธ์ฃผ์ ๋น ์
์ฒด๋ณ ๊ฒ์ฌ ๋๊ธฐยท์์ธ, ๊ฑด์ยท๋น์ฉ ํ๋ ํ ABC ๋ถ๋ฅ)์ผ๋ก ํต์ฌ ๊ด๋ฆฌ ๋์์ ์๋ณํ๋ค.\"\"\"\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from scipy.stats import spearmanr\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR) ์์ฑ๊ธฐ (์ค์ ๋ถ๋ยท์ฅ๋นยท์
์ฒดยท์ ๋น ์ค์ ์์)\n",
"# ๊ฐ์ ์ฅ๋น ๋ถ์ 12์ข
, ๊ฒฐํจ ์ ํ 8์ข
, ์น๋ช
๋(Critical/Major/Minor), ์์ธ 5์ข
, ๊ฐ์ ์ธ์ฃผ์ ๋น ์
์ฒด 4๊ณณ + ๋ถ๋์ ๋น.\n",
"# QDR 1๊ฑด๋ง๋ค ํ๊ตญ์ด ์์ ํ
์คํธ๋ฅผ ๋ง๋ค๊ณ , ์๋ณ ๊ฑด์๋ ๊ณ์ ยท์ถ์ธยทํ๋ จ ๊ฐ๋(usage)ยท์ผ๋ถ ์ ์กฐ ๋ถ๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ๋ฅธ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"PARTS = [ # (์ด๋ฆ, ๋ณ์นญ, ์ ๊ธฐ์ค ๊ฑด์, ๊ณ์ ์งํญ, ์ ์ ์, (Critical, Major ๋น์จ), ๊ฐ์ ๋จ๊ฐ์ง์, ์ ์กฐ์ฌ, ์ฃผ์ ๊ฒฐํจ ๊ฐ์ค์น)\n",
" (\"์์ง ์ฐ๋ฃ๊ณํต\", [\"์ฐ๋ฃ๊ณํต\", \"์ฐ๋ฃ ๋ผ์ธ\", \"์ฐ๋ฃ ํํ\"], 26, 0.15, 7, (0.10, 0.35), 6.0, \"MK1\", \"๋์ :4 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:2 ์ค์๋:2\"),\n",
" (\"๋ณ์๊ธฐ\", [\"๋ณ์๊ธฐ\", \"T/M\", \"๋ฏธ์
\"], 22, 0.10, 8, (0.12, 0.38), 9.0, \"MK1\", \"๋ง๋ชจ:4 ๋์ :2 ๊ณผ์ด:3 ์ค์๋:1\"),\n",
" (\"์ ๋ ์ฅ์น\", [\"์ ๋ ์ฅ์น\", \"๋ธ๋ ์ดํฌ\", \"์ ๋ ๋ผ์ธ\"], 20, 0.20, 12, (0.26, 0.40), 5.0, \"MK2\", \"๋ง๋ชจ:4 ๊ท ์ด:2 ๋์ :2 ์ฒด๊ฒฐ๋ถ๋:2\"),\n",
" (\"์กฐํฅ ์ฅ์น\", [\"์กฐํฅ ์ฅ์น\", \"ํธ๋ค ์ฐ๋๋ถ\", \"์กฐํฅ ๋ก๋\"], 14, 0.10, 6, (0.30, 0.40), 5.0, \"MK2\", \"์ฒด๊ฒฐ๋ถ๋:4 ๊ท ์ด:2 ๋ง๋ชจ:3 ์ค์๋:1\"),\n",
" (\"๋๊ฐ ๊ณํต\", [\"๋๊ฐ ๊ณํต\", \"๋ผ๋์์ดํฐ\", \"๋๊ฐ์ ๋ผ์ธ\"], 24, 0.30, 7, (0.05, 0.30), 3.0, \"MK3\", \"๋์ :4 ๊ณผ์ด:4 ๋ถ์:2\"),\n",
" (\"์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ\", [\"๋ฐฐํฐ๋ฆฌ\", \"์ ๊ธฐ ๊ณํต\", \"๋ฐ์ ๊ธฐ\"], 28, 0.35, 1, (0.04, 0.28), 2.5, \"MK3\", \"์ ์ด๋ถ๋:4 ๋ถ์:2 ๊ณผ์ด:2 ์ค์๋:2\"),\n",
" (\"ํต์ ๋ชจ๋\", [\"ํต์ ๋ชจ๋\", \"๋ฌด์ ๊ธฐ ์ฐ๋๋ถ\", \"์ํ
๋ ์ ์๋ถ\"], 16, 0.05, 4, (0.03, 0.25), 4.0, \"MK4\", \"์ ์ด๋ถ๋:5 ์ค์๋:4 ๋ถ์:1\"),\n",
" (\"์ ์ ๊ณํต\", [\"์ ์ ๊ณํต\", \"์ ์ ํธ์ค\", \"์ ์ ์ค๋ฆฐ๋\"], 18, 0.10, 9, (0.10, 0.35), 4.5, \"MK2\", \"๋์ :5 ๊ท ์ด:2 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"ํ๊ฐ ์ฅ์น\", [\"ํ๊ฐ ์ฅ์น\", \"์์คํ์
\", \"์์ถฉ๊ธฐ\"], 15, 0.08, 5, (0.06, 0.30), 3.5, \"MK4\", \"๋ง๋ชจ:4 ๊ท ์ด:3 ์ฒด๊ฒฐ๋ถ๋:2 ๋ถ์:1\"),\n",
" (\"์ฐจ์ฒด ์ฉ์ ๋ถ\", [\"์ฐจ์ฒด ์ฉ์ ๋ถ\", \"ํ๋ ์ ์ฉ์ ๋ถ\", \"์ฐจ์ฒด ์ด์๋ถ\"], 10, 0.05, 10, (0.28, 0.40), 7.0, \"MK1\", \"๊ท ์ด:6 ๋ถ์:3 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ดํยท์กฐ์ค ์ฅ๋น\", [\"๊ดํ ์ฅ๋น\", \"์กฐ์ค๊ฒฝ\", \"๊ด์ธก ์ฅ๋น\"], 8, 0.05, 3, (0.03, 0.22), 8.0, \"MK4\", \"์ค์๋:4 ์ ์ด๋ถ๋:3 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ณต๊ธฐ ์ ํ ํํฐ\", [\"๊ณต๊ธฐ ํํฐ\", \"ํก๊ธฐ ํํฐ\", \"์ ํ ์ฅ์น\"], 12, 0.25, 4, (0.01, 0.12), 1.0, \"MK3\", \"๋ง๋ชจ:5 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:3\")]\n",
"NP = len(PARTS)\n",
"DEFECTS = { # ๊ฒฐํจ ์ ํ: ์ฆ์ ํํ(๋์์ด)\n",
" \"๊ท ์ด\": [\"๊ท ์ด์ด ๋ฐ๊ฒฌ๋จ\", \"ํฌ๋์ด ํ์ธ๋จ\", \"๊ฐ๋ผ์ง์ด ์์\"], \"๋์ \": [\"๋์ ๊ฐ ๋ฐ์ํจ\", \"์ค์ผ์ด ์์ด ๋์ด\", \"๋์ค ํ์ ์ด ์์\"],\n",
" \"๋ง๋ชจ\": [\"๋ง๋ชจ๊ฐ ์ฌํจ\", \"๊ณผ๋ํ ๋ง๋ชจ๊ฐ ํ์ธ๋จ\", \"๋ณ์ ํ์ ์ด ์์\"], \"๋ถ์\": [\"๋ถ์์ด ์งํ๋จ\", \"๋
น์ด ๋ฐ์ํจ\", \"์ฐํ ํ์ ์ด ์์\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋์ด ๋ฐ์ํจ\", \"์ปค๋ฅํฐ ๋จ์ ์ด ์์ฌ๋จ\", \"์ ํธ๊ฐ ๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ ํ๋ฆผ์ด ํ์ธ๋จ\", \"์ฒด๊ฒฐ ์ํ๊ฐ ๋ถ๋ํจ\", \"๊ณ ์ ๋ถ๊ฐ ํ๊ฑฐ์\"],\n",
" \"๊ณผ์ด\": [\"๊ณผ์ด ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํจ\", \"์จ๋๊ฐ ๊ณผ๋ํ๊ฒ ์์นํจ\", \"์ด์ด ์ฌํ๊ฒ ๋จ\"], \"์ค์๋\": [\"์ค์๋์ด ๋ฐ๋ณต๋จ\", \"์๋์ด ๋ถ์์ ํจ\", \"๊ฐํ์ ์ผ๋ก ๋ฉ์ถค\"]}\n",
"DNAMES = list(DEFECTS)\n",
"SEV = [\"Minor\", \"Major\", \"Critical\"]\n",
"SEV_CUE = {0: [\"๊ฒฝ๋ฏธํ ์์ค์\", \"์ด์ฉ์๋ ์ง์ฅ์ด ์์\", \"์ก์ ์ ๊ฒ์ ์ฌ์ํ ์ด์์\", \"๊ธฐ๋ฅ์๋ ์ํฅ์ด ์์\"],\n",
" 1: [\"์ฑ๋ฅ ์ ํ๊ฐ ์์\", \"์กฐ์ํ ์ ๋น๊ฐ ํ์ํจ\", \"์ถ๋ ฅ์ด ๋จ์ด์ง๋ ์ํ์\", \"๊ธฐ๋ฅ์ด ๋ถ๋ถ์ ์ผ๋ก ์ ํ๋จ\"],\n",
" 2: [\"์ฆ์ ์ด์ฉ ์ค์งํจ\", \"์์ ์ ์ํ์ด ์์ด ์ฌ์ฉ ๋ถ๊ฐ\", \"์๋ ์ ์ง๋ก ์๋ฌด ์ํ์ด ์ด๋ ค์\", \"์ธ๋ช
์์ ์ํ์ด ์ฐ๋ ค๋จ\"]}\n",
"CAUSES = [\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\", \"์ด์ฉ ๋ถ์ฃผ์\", \"์ค๊ณ ์ทจ์ฝ\", \"์์ฐ ๋
ธํ\"]\n",
"CAUSE_CUE = {\n",
" \"์ ์กฐ ๋ถ๋\": [\"์ ํ ์ธ์ ์งํ ๋ฐ์ํจ\", \"์ถ๊ณ ์ํ์์ ์ด๋ฏธ ์ด์์ด ์์์\", \"์ธ์ ํ ์ผ๋ง ์ง๋์ง ์์ ๋ฐ์ํจ\"],\n",
" \"์ ๋น ํ์ง\": [\"์ธ์ฃผ ์ ๋น ํ ์ผ๋ง ๋์ง ์์ ์ฌ๋ฐํจ\", \"์ ๋น ํ ์ฌ์กฐ๋ฆฝ ์ํ๊ฐ ๋ถ๋ํจ\", \"์ ๋น ์งํ ๊ฐ์ ๋ถ์์์ ๋ค์ ๋ฐ์ํจ\"],\n",
" \"์ด์ฉ ๋ถ์ฃผ์\": [\"ํ์ง ๊ธฐ๋ ์ค ์ถฉ๊ฒฉ ์ดํ ๋ฐ์ํจ\", \"๊ณผ๋ถํ ์ด์ฉ ํ ๋ฐ์ํจ\", \"์ ๊ฒ ์์ด ์ฐ์ ์ด์ฉํ ๋ค ๋ฐ์ํจ\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์์ด ๋ค๋ฅธ ์ฐจ๋์์๋ ๋ฐ๋ณต๋จ\", \"๊ตฌ์กฐ์ ์ผ๋ก ์ทจ์ฝํ ๋ถ์๋ก ๋ณด์\", \"์ฌ๋ฌ ๋ถ๋์์ ๊ฐ์ ํํ๋ก ๋ณด๊ณ ๋จ\"],\n",
" \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ์ด ๋๋ํจ\", \"์ฅ๊ธฐ๊ฐ ์ฌ์ฉ์ผ๋ก ๋
ธํ๋จ\", \"์๋ช
์ฃผ๊ธฐ ๋ง๊ธฐ์ ๋ฐ์ํจ\"]}\n",
"VENDORS = [\"์ธ์ฃผA\", \"์ธ์ฃผB\", \"์ธ์ฃผC\", \"์ธ์ฃผD\", \"๋ถ๋์ ๋น\"]\n",
"ACTIONS = [\"์ ๋น์ ์
๊ณ ๋ฅผ ์์ฒญํจ\", \"ํ์ฅ ์์ ์กฐ์น ํ ์ด์ฉ ์ค์\", \"๋ถํ ๊ตํ์ ์์ฒญํจ\", \"์ฌ๊ฒ์ฌ๋ฅผ ์์ฒญํจ\", \"์์ธ ํ์ธ์ด ํ์ํจ\"]\n",
"MAKER_OF = [p[7] for p in PARTS]\n",
"PRICE = np.array([p[6] for p in PARTS])\n",
"\n",
"\n",
"def usage_index(months, seed=0):\n",
" \"\"\"์๋ณ ํ๋ จ ๊ฐ๋(๋ฏธ๋ฆฌ ์ ์ ์๋ ๊ณํ ๊ฐ): ์ฐ 1ํ ์ ์ ์ ๊ฐ๋ ๊ณ์ + ์์ ์ก์\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 3] ๋น๋ ๋ถ์ ๊ธฐ๋ฐ 1์ฐจ ๋ค๋ฐ ๊ฒฐํจ ๋ถํ ๋์ถ Baseline ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_qdr(months=36, seed=1, scale=1.0, surge=True):\n",
" \"\"\"๋ฐํ: QDR DataFrame(๊ฑด๋น 1ํ), ์๋ณ usage, ๊ธ์ฆ ์ด๋ฒคํธ ๋ชฉ๋ก\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" use = usage_index(months, seed)\n",
" events = []\n",
" if surge:\n",
" for _ in range(max(2, months // 12 * 2)):\n",
" events.append((int(rng.integers(NP)), int(rng.integers(6, months))))\n",
" rows = []\n",
" wdef = [np.array([float(x.split(\":\")[1]) for x in p[8].split()]) for p in PARTS]\n",
" ddef = [[x.split(\":\")[0] for x in p[8].split()] for p in PARTS]\n",
" n = 0\n",
" for m in range(months):\n",
" for pi, p in enumerate(PARTS):\n",
" lam = p[2] * scale * (1 + 0.010 * m) * use[m] * (1 + p[3] * np.cos(2 * np.pi * (m % 12 - p[4]) / 12))\n",
" ev = (pi, m) in events\n",
" k = rng.poisson(lam * (2.6 if ev else 1.0))\n",
" for _ in range(k):\n",
" rows.append((n, m, pi, ev)); n += 1\n",
" N = len(rows)\n",
" A = np.array(rows)\n",
" month, part, ev = A[:, 1], A[:, 2], A[:, 3].astype(bool)\n",
" defect = np.array([rng.choice(ddef[i], p=wdef[i] / wdef[i].sum()) for i in part])\n",
" cr = np.array([PARTS[i][5][0] for i in part]); ma = np.array([PARTS[i][5][1] for i in part])\n",
" u = rng.random(N)\n",
" sev = np.where(u < cr, 2, np.where(u < cr + ma, 1, 0))\n",
" vendor = rng.choice(VENDORS, N, p=[0.25, 0.2, 0.2, 0.15, 0.2])\n",
" # ์์ธ: ๊ธฐ๋ณธ ๋น์จ์ ๋ถ์(์ ์กฐ์ฌ)ยท์
์ฒดยท์ด๋ฒคํธ ํจ๊ณผ๋ฅผ ๊ณฑํ๋ค\n",
" base = np.array([0.22, 0.20, 0.20, 0.16, 0.22])\n",
" P = np.tile(base, (N, 1))\n",
" P[vendor == \"์ธ์ฃผB\", 1] *= 2.6\n",
" P[ev, 0] *= 5.0\n",
" design = np.isin(part, [2, 3, 7]); P[design, 3] *= 2.2\n",
" P[np.isin(part, [4, 5, 11]), 0] *= 2.0 # ์ ์กฐ์ฌ MK3 ๋ฉํ ๋ถ์๋ ์ ์กฐ ๋ถ๋ ๋น์ค์ด ๋๋ค\n",
" P[defect == \"๋ง๋ชจ\", 4] *= 1.6; P[defect == \"๋์ \", 4] *= 1.3\n",
" P /= P.sum(axis=1, keepdims=True)\n",
" cause = np.array([CAUSES[rng.choice(5, p=P[i])] for i in range(N)])\n",
" since = np.where(cause == \"์ ๋น ํ์ง\", rng.exponential(35, N), rng.exponential(160, N)).clip(1, 720).round().astype(int)\n",
" wait = (rng.lognormal(1.3, 0.5, N) * np.where(vendor == \"์ธ์ฃผB\", 1.7, np.where(vendor == \"๋ถ๋์ ๋น\", 0.6, 1.0)) * (0.7 + 0.5 * use[month])).round(1)\n",
" reins = rng.random(N) < np.where(cause == \"์ ๋น ํ์ง\", 0.6, 0.08)\n",
" texts = []\n",
" for i in range(N):\n",
" pt = PARTS[part[i]]\n",
" cl = [f\"{rng.choice(pt[1])}์์ {rng.choice(DEFECTS[defect[i]])}\"]\n",
" if rng.random() < 0.7: cl.append(rng.choice(SEV_CUE[sev[i]]))\n",
" if rng.random() < 0.65: # ์์ธ ๋จ์: 8%๋ ์ค์ ์์ธ๊ณผ ๋ค๋ฅธ ๋จ์๊ฐ ์ ํ๋ค(๋ณด๊ณ ์์ ์ถ์ )\n",
" cc = cause[i] if rng.random() > 0.08 else rng.choice(CAUSES)\n",
" cl.append(rng.choice(CAUSE_CUE[cc]))\n",
" cl.append(rng.choice(ACTIONS))\n",
" if rng.random() < 0.5: cl.insert(0, f\"VU-{rng.integers(1, 31):02d} ์์\")\n",
" texts.append(\". \".join(cl) + \".\")\n",
" df = pd.DataFrame({\"qdr_id\": [f\"Q{seed}{i:06d}\" for i in range(N)], \"month\": month, \"unit\": [f\"VU-{x:02d}\" for x in rng.integers(1, 31, N)],\n",
" \"part\": part, \"defect\": defect, \"sev\": sev, \"cause\": cause, \"vendor\": vendor, \"maker\": [MAKER_OF[i] for i in part],\n",
" \"since_maint\": since, \"inspect_wait\": wait, \"reinspect\": reins, \"event\": ev, \"text\": texts})\n",
" df[\"cost\"] = PRICE[df[\"part\"]] * np.array([1.0, 2.0, 4.0])[df[\"sev\"]] # [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์๋ฆฌยท์กฐ์น ๋น์ฉ์ง์\n",
" return df, use, events\n",
"\n",
"df, use, events = make_qdr(36, seed=1)\n",
"names = [p[0] for p in PARTS]\n",
"tr, va = df[df[\"month\"] < 24], df[df[\"month\"] >= 24]\n",
"print(f\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] QDR {len(df)}๊ฑด, ๋ถ์ {NP}์ข
, 36๊ฐ์ (ํ์ต 24๊ฐ์ {len(tr)}๊ฑด, ๊ฒ์ฆ 12๊ฐ์ {len(va)}๊ฑด)\")\n",
"\n",
"# 1) Baseline: ํ์ต ๊ธฐ๊ฐ ๊ฑด์ ์์ 5๊ฐ ๋ถ์๋ฅผ ๋ค๋ฐ ๊ฒฐํจ ๋ถ์๋ก ์ง์ \n",
"cnt_tr = tr.groupby(\"part\").size().sort_values(ascending=False)\n",
"top_base = list(cnt_tr.index[:5])\n",
"cnt_va = va.groupby(\"part\").size().sort_values(ascending=False)\n",
"vuln = lambda d: d.groupby(\"part\")[\"sev\"].apply(lambda s: np.array([1, 3, 9])[s].sum()).sort_values(ascending=False)\n",
"v_va = vuln(va)\n",
"monthly_hit = np.mean([len(set(top_base) & set(g.groupby(\"part\").size().sort_values(ascending=False).index[:5])) / 5 for _, g in va.groupby(\"month\")])\n",
"print(f\"Baseline ์์ 5 ๋ถ์: {', '.join(names[i] for i in top_base)}\")\n",
"print(f\" ๊ฒ์ฆ ๊ธฐ๊ฐ ๊ฑด์ ์์ 5์ ์ผ์น {len(set(top_base) & set(cnt_va.index[:5]))}/5, ์๋ณ ํ๊ท ์ ์ค๋ฅ {monthly_hit:.0%}\")\n",
"print(f\" ์น๋ช
๋ ๊ฐ์ค ์ทจ์ฝ์ฑ ์ง์ ์์ 5์ ์ผ์น {len(set(top_base) & set(v_va.index[:5]))}/5 -> ๊ฑด์๋ง ๋ณด๋ฉด ๋์น๋ ๋ถ์: \" + \", \".join(names[i] for i in set(v_va.index[:5]) - set(top_base)))\n",
"crit = va[va[\"sev\"] == 2]\n",
"miss = (~crit[\"part\"].isin(top_base)).mean()\n",
"print(f\" Critical ๊ฒฐํจ {len(crit)}๊ฑด ์ค Baseline ๋์ ๋ฐ ๋ถ์ ๋น์ค {miss:.0%}\")\n",
"\n",
"# 2) ํ๋ ํ (ABC): ๊ฑด์์ ์กฐ์น ๋น์ฉ\n",
"par = pd.DataFrame({\"๋ถ์\": [names[i] for i in cnt_tr.index], \"๊ฑด์\": cnt_tr.values}).set_index(\"๋ถ์\")\n",
"par[\"๋น์ฉ์ง์\"] = [df.loc[df[\"part\"] == i, \"cost\"].sum() for i in cnt_tr.index]\n",
"pc = par.sort_values(\"๋น์ฉ์ง์\", ascending=False)\n",
"pc[\"๋์ ๋น์จ\"] = pc[\"๋น์ฉ์ง์\"].cumsum() / pc[\"๋น์ฉ์ง์\"].sum()\n",
"pc[\"๋ฑ๊ธ\"] = np.where(pc[\"๋์ ๋น์จ\"] <= 0.7, \"A\", np.where(pc[\"๋์ ๋น์จ\"] <= 0.9, \"B\", \"C\")); pc.iloc[0, 3] = \"A\"\n",
"print(\"\\n[ํ๋ ํ ] ์กฐ์น ๋น์ฉ ๊ธฐ์ค(๊ฐ์ ๋จ๊ฐ์ง์)\")\n",
"print(pc.assign(๋น์ฉ์ง์=lambda d: d[\"๋น์ฉ์ง์\"].round(0), ๋์ ๋น์จ=lambda d: d[\"๋์ ๋น์จ\"].map(\"{:.0%}\".format)).head(6).to_string())\n",
"top20 = pc.head(int(np.ceil(NP * 0.2)))\n",
"print(f\"์์ 20% ๋ถ์({', '.join(top20.index)})๊ฐ ์ ์ฒด ์กฐ์น ๋น์ฉ์ {top20['๋น์ฉ์ง์'].sum() / pc['๋น์ฉ์ง์'].sum():.0%}, A๋ฑ๊ธ {int((pc['๋ฑ๊ธ'] == 'A').sum())}์ข
\")\n",
"\n",
"# 3) ์๊ด๋ถ์: ์๊ฐ QDR ๊ฑด์์ ์๋ณ ํผ์ฒ\n",
"mon = df.groupby(\"month\").agg(n=(\"qdr_id\", \"size\"), wait=(\"inspect_wait\", \"mean\"), re=(\"reinspect\", \"mean\"), crit=(\"sev\", lambda s: (s == 2).mean()),\n",
" ven_b=(\"vendor\", lambda s: (s == \"์ธ์ฃผB\").mean()), short=(\"since_maint\", lambda s: (s < 60).mean()),\n",
" mfg=(\"cause\", lambda s: (s == \"์ ์กฐ ๋ถ๋\").mean()), cost=(\"cost\", \"mean\"), units=(\"unit\", \"nunique\"),\n",
" short_n=(\"since_maint\", lambda s: (s < 60).sum()))\n",
"mon[\"usage\"] = use; mon[\"trend\"] = np.arange(36); mon[\"season\"] = np.cos(2 * np.pi * (mon[\"trend\"] % 12 - 7) / 12); mon[\"events\"] = [sum(1 for e in events if e[1] == m) for m in range(36)]\n",
"rows = []\n",
"for c, lab in [(\"usage\", \"ํ๋ จ ๊ฐ๋\"), (\"trend\", \"๊ฒฝ๊ณผ ์(์ถ์ธ)\"), (\"season\", \"๊ณ์ ์์ธ\"), (\"wait\", \"ํ๊ท ๊ฒ์ฌ ๋๊ธฐ์ผ\"), (\"re\", \"์ฌ๊ฒ์ฌ์จ\"), (\"short\", \"์ ๋น ํ 60์ผ ์ด๋ด ๋น์ค\"),\n",
" (\"mfg\", \"์ ์กฐ ๋ถ๋ ๋น์ค\"), (\"cost\", \"๊ฑด๋น ํ๊ท ์กฐ์น ๋น์ฉ\"), (\"ven_b\", \"์ธ์ฃผB ๋น์ค\"), (\"crit\", \"Critical ๋น์ค\"), (\"units\", \"๋ณด๊ณ ๋ถ๋ ์\"), (\"short_n\", \"์ ๋น ํ 60์ผ ์ด๋ด ๊ฑด์\")]:\n",
" r, p = spearmanr(mon[c], mon[\"n\"]); rows.append((lab, r, p))\n",
"cor = pd.DataFrame(rows, columns=[\"ํผ์ฒ\", \"rho\", \"p\"]); cor[\"์ ์\"] = (cor[\"p\"] < 0.05) & (cor[\"rho\"].abs() >= 0.3)\n",
"print(\"\\n[์๊ด๋ถ์] ํผ์ฒ | ์คํผ์ด๋ง rho | p-value | ์ ์(p<0.05, |rho|>=0.3)\")\n",
"for x in cor.sort_values(\"rho\", key=abs, ascending=False).head(7).itertuples():\n",
" print(f\" {x.ํผ์ฒ:16s} {x.rho:+.2f} {x.p:.4f} {'์ ์' if x.์ ์ else '-'}\")\n",
"nsig = int(cor[\"์ ์\"].sum())\n",
"\n",
"# 4) ์ธ์ฃผ์ ๋น ์
์ฒด๋ณ ๊ฒ์ฌ ๋ณ๋ชฉ๊ณผ ์์ธ\n",
"ven = df.groupby(\"vendor\").agg(๊ฑด์=(\"qdr_id\", \"size\"), ๋๊ธฐ์ผ=(\"inspect_wait\", \"mean\"), ์ฌ๊ฒ์ฌ์จ=(\"reinspect\", \"mean\"), ์ ๋นํ์ง์์ธ=(\"cause\", lambda s: (s == \"์ ๋น ํ์ง\").mean()))\n",
"print(\"\\n[์
์ฒด๋ณ ๊ฒ์ฌ ์ค์ ] ์ง์ ์ ๋น ์ํ์ฒ ๊ธฐ์ค\")\n",
"print(ven.round(2).to_string())\n",
"b, o = df[df[\"vendor\"] == \"์ธ์ฃผB\"], df[~df[\"vendor\"].isin([\"์ธ์ฃผB\"])]\n",
"print(f\"์ธ์ฃผB: ๋๊ธฐ์ผ {b['inspect_wait'].mean():.1f}์ผ(ํ ์ํ์ฒ {o['inspect_wait'].mean():.1f}์ผ), '์ ๋น ํ์ง' ์์ธ {np.mean(b['cause'] == '์ ๋น ํ์ง'):.0%}(ํ {np.mean(o['cause'] == '์ ๋น ํ์ง'):.0%}), ์ ๋น ํ 60์ผ ์ด๋ด ์ฌ๋ฐ {np.mean(b['since_maint'] < 60):.0%}\")\n",
"\n",
"# 5) EDA ์ฐจํธ 8์ข
\n",
"fig, ax = plt.subplots(2, 4, figsize=(16, 7))\n",
"H = df.pivot_table(index=\"part\", columns=\"month\", values=\"qdr_id\", aggfunc=\"size\", fill_value=0)\n",
"ax[0, 0].imshow(H.values, aspect=\"auto\", cmap=\"Reds\"); ax[0, 0].set_title(\"1 QDR heatmap (part x month)\")\n",
"ax[0, 1].plot(mon[\"n\"].values, label=\"QDR\"); ax[0, 1].plot(mon[\"usage\"].values * mon[\"n\"].mean(), label=\"usage x mean\"); ax[0, 1].legend(fontsize=6); ax[0, 1].set_title(\"2 Monthly QDR vs usage\")\n",
"ax[0, 2].bar(range(NP), pc[\"๋น์ฉ์ง์\"].values); ax[0, 2].plot(range(NP), pc[\"๋์ ๋น์จ\"].values * pc[\"๋น์ฉ์ง์\"].max(), \"r-o\"); ax[0, 2].set_title(\"3 Pareto of repair cost\")\n",
"sv = pd.crosstab(df[\"part\"], df[\"sev\"], normalize=\"index\"); ax[0, 3].bar(range(NP), sv[0], label=\"Minor\"); ax[0, 3].bar(range(NP), sv[1], bottom=sv[0], label=\"Major\"); ax[0, 3].bar(range(NP), sv[2], bottom=sv[0] + sv[1], label=\"Critical\"); ax[0, 3].legend(fontsize=6); ax[0, 3].set_title(\"4 Severity share by part\")\n",
"ax[1, 0].boxplot([df.loc[df[\"vendor\"] == v, \"inspect_wait\"] for v in VENDORS], labels=[v[-1] for v in VENDORS]); ax[1, 0].set_title(\"5 Inspection wait by vendor\")\n",
"cv = pd.crosstab(df[\"vendor\"], df[\"cause\"], normalize=\"index\"); ax[1, 1].imshow(cv.values, cmap=\"Blues\"); ax[1, 1].set_title(\"6 Cause share by vendor\")\n",
"ax[1, 2].hist([df.loc[df[\"cause\"] == c, \"since_maint\"].clip(0, 400) for c in CAUSES[:2]], bins=20, label=[\"mfg\", \"maint\"]); ax[1, 2].legend(fontsize=6); ax[1, 2].set_title(\"7 Days since maintenance\")\n",
"ax[1, 3].scatter(mon[\"wait\"], mon[\"n\"], s=12); ax[1, 3].set_title(\"8 Wait vs monthly QDR\")\n",
"plt.tight_layout(); plt.show()\n",
"\n",
"print(f\"\\nEDA ์ฐจํธ 8์ข
์์ฑ(๋ชฉํ 8์ข
์ด์: ๋ฌ์ฑ), ์ ์ํ ํผ์ฒ {nsig}๊ฐ (๋ชฉํ 5๊ฐ ์ด์: {'๋ฌ์ฑ' if nsig >= 5 else '๋ฏธ๋ฌ'})\")\n",
"print(\"ํต์ฌ ๋ฐ๊ฒฌ: 1) ๊ฑด์ ์์๋ง ๋ณด๋ฉด ์กฐํฅ ์ฅ์น์ฒ๋ผ ์น๋ช
๋ ๋์ ๋ถ์๋ฅผ ๋์นจ 2) ์ธ์ฃผB๋ ๊ฒ์ฌ ๋๊ธฐ๊ฐ ๊ธธ๊ณ ์ ๋น ํ ์กฐ๊ธฐ ์ฌ๋ฐ ๋น์ค์ด ๋์ 3) ์๋ณ QDR๋ ํ๋ จ ๊ฐ๋์ ๊ณ์ ์ ์ฐ๋\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
03 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ต์ก์ฉ๊ฐ์์๋ฃ] QDR 9221๊ฑด, ๋ถ์ 12์ข
, 36๊ฐ์ (ํ์ต 24๊ฐ์ 5906๊ฑด, ๊ฒ์ฆ 12๊ฐ์ 3315๊ฑด)\n",
"Baseline ์์ 5 ๋ถ์: ์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ, ์์ง ์ฐ๋ฃ๊ณํต, ๋๊ฐ ๊ณํต, ์ ๋ ์ฅ์น, ๋ณ์๊ธฐ\n",
" ๊ฒ์ฆ ๊ธฐ๊ฐ ๊ฑด์ ์์ 5์ ์ผ์น 5/5, ์๋ณ ํ๊ท ์ ์ค๋ฅ 80%\n",
" ์น๋ช
๋ ๊ฐ์ค ์ทจ์ฝ์ฑ ์ง์ ์์ 5์ ์ผ์น 4/5 -> ๊ฑด์๋ง ๋ณด๋ฉด ๋์น๋ ๋ถ์: ์กฐํฅ ์ฅ์น\n",
" Critical ๊ฒฐํจ 366๊ฑด ์ค Baseline ๋์ ๋ฐ ๋ถ์ ๋น์ค 46%\n",
"\n",
"[ํ๋ ํ ] ์กฐ์น ๋น์ฉ ๊ธฐ์ค(๊ฐ์ ๋จ๊ฐ์ง์)\n",
" ๊ฑด์ ๋น์ฉ์ง์ ๋์ ๋น์จ ๋ฑ๊ธ\n",
"๋ถ์ \n",
"๋ณ์๊ธฐ 558 13464.0 18% A\n",
"์์ง ์ฐ๋ฃ๊ณํต 770 11550.0 33% A\n",
"์ ๋ ์ฅ์น 596 10220.0 47% A\n",
"์กฐํฅ ์ฅ์น 397 7155.0 56% A\n",
"์ฐจ์ฒด ์ฉ์ ๋ถ 277 6776.0 65% A\n",
"์ ์ ๊ณํต 462 5607.0 73% B\n",
"์์ 20% ๋ถ์(๋ณ์๊ธฐ, ์์ง ์ฐ๋ฃ๊ณํต, ์ ๋ ์ฅ์น)๊ฐ ์ ์ฒด ์กฐ์น ๋น์ฉ์ 47%, A๋ฑ๊ธ 5์ข
\n",
"\n",
"[์๊ด๋ถ์] ํผ์ฒ | ์คํผ์ด๋ง rho | p-value | ์ ์(p<0.05, |rho|>=0.3)\n",
" ์ ๋น ํ 60์ผ ์ด๋ด ๊ฑด์ +0.94 0.0000 ์ ์\n",
" ํ๊ท ๊ฒ์ฌ ๋๊ธฐ์ผ +0.83 0.0000 ์ ์\n",
" ํ๋ จ ๊ฐ๋ +0.82 0.0000 ์ ์\n",
" ๊ณ์ ์์ธ +0.78 0.0000 ์ ์\n",
" ๊ฒฝ๊ณผ ์(์ถ์ธ) +0.39 0.0200 ์ ์\n",
" ์ธ์ฃผB ๋น์ค -0.22 0.1928 -\n",
" Critical ๋น์ค +0.16 0.3583 -\n",
"\n",
"[์
์ฒด๋ณ ๊ฒ์ฌ ์ค์ ] ์ง์ ์ ๋น ์ํ์ฒ ๊ธฐ์ค\n",
" ๊ฑด์ ๋๊ธฐ์ผ ์ฌ๊ฒ์ฌ์จ ์ ๋นํ์ง์์ธ\n",
"vendor \n",
"๋ถ๋์ ๋น 1811 3.11 0.18 0.18\n",
"์ธ์ฃผA 2231 5.00 0.17 0.18\n",
"์ธ์ฃผB 1916 8.44 0.27 0.36\n",
"์ธ์ฃผC 1864 5.08 0.16 0.17\n",
"์ธ์ฃผD 1399 4.98 0.17 0.17\n",
"์ธ์ฃผB: ๋๊ธฐ์ผ 8.4์ผ(ํ ์ํ์ฒ 4.5์ผ), '์ ๋น ํ์ง' ์์ธ 36%(ํ 18%), ์ ๋น ํ 60์ผ ์ด๋ด ์ฌ๋ฐ 49%\n",
"\n",
"EDA ์ฐจํธ 8์ข
์์ฑ(๋ชฉํ 8์ข
์ด์: ๋ฌ์ฑ), ์ ์ํ ํผ์ฒ 5๊ฐ (๋ชฉํ 5๊ฐ ์ด์: ๋ฌ์ฑ)\n",
"ํต์ฌ ๋ฐ๊ฒฌ: 1) ๊ฑด์ ์์๋ง ๋ณด๋ฉด ์กฐํฅ ์ฅ์น์ฒ๋ผ ์น๋ช
๋ ๋์ ๋ถ์๋ฅผ ๋์นจ 2) ์ธ์ฃผB๋ ๊ฒ์ฌ ๋๊ธฐ๊ฐ ๊ธธ๊ณ ์ ๋น ํ ์กฐ๊ธฐ ์ฌ๋ฐ ๋น์ค์ด ๋์ 3) ์๋ณ QDR๋ ํ๋ จ ๊ฐ๋์ ๊ณ์ ์ ์ฐ๋\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
04. ํ
์คํธ ์๋ฒ ๋ฉ ๋ฐ ํ ํฝ ๋ชจ๋ธ๋ง ๊ธฐ๋ฐ ์ฅ๋น ๊ตฌ์กฐ์ ํ์ง ์ทจ์ฝ์ ์๋ ๊ตฐ์งํ ๋ชจ๋ธ ๊ณ ๋ํ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"ํ ํฝ ๋ชจ๋ธ๋ง ๊ธฐ๋ฐ ์ฅ๋น ๊ตฌ์กฐ์ ํ์ง ์ทจ์ฝ์ ์๋ ๊ตฐ์งํ ๋ชจ๋ธ์ ๊ณ ๋ํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **ํ
์คํธ ์๋ฒ ๋ฉ(TF-IDF)๊ณผ ํ ํฝ ๋ชจ๋ธ๋ง(NMF)์ผ๋ก ์ฅ๋น ๊ตฌ์กฐ์ ํ์ง ์ทจ์ฝ์ ์ ์๋ ๊ตฐ์งํํ๊ณ , ๋ถ์๋ณ ์๊ฐ QDR ๊ฑด์ ๊ธฐ์ค ๋ชจ๋ธ์ ํ์ ํผ์ฒ๋ก ๊ณ ๋ํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฒฐํจ ์์ ์ ํต์ฌ ๋ฌธ์ฅ์ TF-IDF๋ก ๋ฒกํฐํํ๊ณ NMF ํ ํฝ 36๊ฐ๋ก ๊ตฐ์งํํด ๋ถ์ยท๊ฒฐํจ ๋ผ๋ฒจ๊ณผ์ ์ผ์น๋(NMI) ํ์ธํ๊ณ ์ ํจ์ฑ์ ์ ๊ฒํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/features/qlt05_feature_engineering.py ๋ฐ ๊ธฐ์ค ๋ชจ๋ธ` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ตฐ์ ์ค๋ฌด์์ ๋ฉ๋ ๊ฐ๋ฅํ ํผ์ฒ ์ค๊ณ์ ๋
ผ๋ฆฌ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ํ์ ํผ์ฒ 10๊ฐ ์ด์ ์์ฑ, ๊ธฐ์ค ๋ชจ๋ธ MAPE 25% ์ดํ ๋ฌ์ฑ\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ํผ์ฒ ํ์ดํ๋ผ์ธ ๋ฐ ๋ฒ ์ด์ค๋ผ์ธ ๋ชจ๋ธ ์ฑ๋ฅ ์งํ ๋์ถ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๊ต์ฐจ ๊ฒ์ฆ(Time-series Cross Validation)์ ํตํ ๊ณผ์ ํฉ ๋ฐฉ์ง\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"QDR ์์ ํ
์คํธ๋ฅผ TF-IDF๋ก ๋ฒกํฐํํ๊ณ NMF ํ ํฝ ๋ชจ๋ธ๋ง์ผ๋ก ์ฅ๋น ๊ตฌ์กฐ์ ํ์ง ์ทจ์ฝ์ ์ ์๋ ๊ตฐ์งํํด ๋ถ์ยท๊ฒฐํจ ๋ผ๋ฒจ๊ณผ์ ์ผ์น๋(NMI)์ ์ทจ์ฝ ๊ตฐ์ง(์น๋ช
๋ยท์ค๊ณ ์ทจ์ฝ ๋น์ค)์ ํ์ธํ๊ณ , ํ ํฝ ๋น์ค ๋ฑ ํ์ ํผ์ฒ 14๊ฐ๋ก ๋ถ์๋ณ ์๊ฐ QDR ๊ฑด์ ๊ธฐ์ค ๋ชจ๋ธ์ ๋ง๋ค์ด ์๊ณ์ด ๊ต์ฐจ๊ฒ์ฆ MAPE๋ฅผ ํ๊ฐํ๊ณ ์์ด ์ค์๋๋ก ํผ์ฒ๋ฅผ ์ค์ธ๋ค.\"\"\"\n",
"import re\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"from sklearn.decomposition import NMF\n",
"from sklearn.ensemble import GradientBoostingRegressor\n",
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"from sklearn.inspection import permutation_importance\n",
"from sklearn.metrics import normalized_mutual_info_score\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR) ์์ฑ๊ธฐ (์ค์ ๋ถ๋ยท์ฅ๋นยท์
์ฒดยท์ ๋น ์ค์ ์์)\n",
"# ๊ฐ์ ์ฅ๋น ๋ถ์ 12์ข
, ๊ฒฐํจ ์ ํ 8์ข
, ์น๋ช
๋(Critical/Major/Minor), ์์ธ 5์ข
, ๊ฐ์ ์ธ์ฃผ์ ๋น ์
์ฒด 4๊ณณ + ๋ถ๋์ ๋น.\n",
"# QDR 1๊ฑด๋ง๋ค ํ๊ตญ์ด ์์ ํ
์คํธ๋ฅผ ๋ง๋ค๊ณ , ์๋ณ ๊ฑด์๋ ๊ณ์ ยท์ถ์ธยทํ๋ จ ๊ฐ๋(usage)ยท์ผ๋ถ ์ ์กฐ ๋ถ๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ๋ฅธ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"PARTS = [ # (์ด๋ฆ, ๋ณ์นญ, ์ ๊ธฐ์ค ๊ฑด์, ๊ณ์ ์งํญ, ์ ์ ์, (Critical, Major ๋น์จ), ๊ฐ์ ๋จ๊ฐ์ง์, ์ ์กฐ์ฌ, ์ฃผ์ ๊ฒฐํจ ๊ฐ์ค์น)\n",
" (\"์์ง ์ฐ๋ฃ๊ณํต\", [\"์ฐ๋ฃ๊ณํต\", \"์ฐ๋ฃ ๋ผ์ธ\", \"์ฐ๋ฃ ํํ\"], 26, 0.15, 7, (0.10, 0.35), 6.0, \"MK1\", \"๋์ :4 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:2 ์ค์๋:2\"),\n",
" (\"๋ณ์๊ธฐ\", [\"๋ณ์๊ธฐ\", \"T/M\", \"๋ฏธ์
\"], 22, 0.10, 8, (0.12, 0.38), 9.0, \"MK1\", \"๋ง๋ชจ:4 ๋์ :2 ๊ณผ์ด:3 ์ค์๋:1\"),\n",
" (\"์ ๋ ์ฅ์น\", [\"์ ๋ ์ฅ์น\", \"๋ธ๋ ์ดํฌ\", \"์ ๋ ๋ผ์ธ\"], 20, 0.20, 12, (0.26, 0.40), 5.0, \"MK2\", \"๋ง๋ชจ:4 ๊ท ์ด:2 ๋์ :2 ์ฒด๊ฒฐ๋ถ๋:2\"),\n",
" (\"์กฐํฅ ์ฅ์น\", [\"์กฐํฅ ์ฅ์น\", \"ํธ๋ค ์ฐ๋๋ถ\", \"์กฐํฅ ๋ก๋\"], 14, 0.10, 6, (0.30, 0.40), 5.0, \"MK2\", \"์ฒด๊ฒฐ๋ถ๋:4 ๊ท ์ด:2 ๋ง๋ชจ:3 ์ค์๋:1\"),\n",
" (\"๋๊ฐ ๊ณํต\", [\"๋๊ฐ ๊ณํต\", \"๋ผ๋์์ดํฐ\", \"๋๊ฐ์ ๋ผ์ธ\"], 24, 0.30, 7, (0.05, 0.30), 3.0, \"MK3\", \"๋์ :4 ๊ณผ์ด:4 ๋ถ์:2\"),\n",
" (\"์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ\", [\"๋ฐฐํฐ๋ฆฌ\", \"์ ๊ธฐ ๊ณํต\", \"๋ฐ์ ๊ธฐ\"], 28, 0.35, 1, (0.04, 0.28), 2.5, \"MK3\", \"์ ์ด๋ถ๋:4 ๋ถ์:2 ๊ณผ์ด:2 ์ค์๋:2\"),\n",
" (\"ํต์ ๋ชจ๋\", [\"ํต์ ๋ชจ๋\", \"๋ฌด์ ๊ธฐ ์ฐ๋๋ถ\", \"์ํ
๋ ์ ์๋ถ\"], 16, 0.05, 4, (0.03, 0.25), 4.0, \"MK4\", \"์ ์ด๋ถ๋:5 ์ค์๋:4 ๋ถ์:1\"),\n",
" (\"์ ์ ๊ณํต\", [\"์ ์ ๊ณํต\", \"์ ์ ํธ์ค\", \"์ ์ ์ค๋ฆฐ๋\"], 18, 0.10, 9, (0.10, 0.35), 4.5, \"MK2\", \"๋์ :5 ๊ท ์ด:2 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"ํ๊ฐ ์ฅ์น\", [\"ํ๊ฐ ์ฅ์น\", \"์์คํ์
\", \"์์ถฉ๊ธฐ\"], 15, 0.08, 5, (0.06, 0.30), 3.5, \"MK4\", \"๋ง๋ชจ:4 ๊ท ์ด:3 ์ฒด๊ฒฐ๋ถ๋:2 ๋ถ์:1\"),\n",
" (\"์ฐจ์ฒด ์ฉ์ ๋ถ\", [\"์ฐจ์ฒด ์ฉ์ ๋ถ\", \"ํ๋ ์ ์ฉ์ ๋ถ\", \"์ฐจ์ฒด ์ด์๋ถ\"], 10, 0.05, 10, (0.28, 0.40), 7.0, \"MK1\", \"๊ท ์ด:6 ๋ถ์:3 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ดํยท์กฐ์ค ์ฅ๋น\", [\"๊ดํ ์ฅ๋น\", \"์กฐ์ค๊ฒฝ\", \"๊ด์ธก ์ฅ๋น\"], 8, 0.05, 3, (0.03, 0.22), 8.0, \"MK4\", \"์ค์๋:4 ์ ์ด๋ถ๋:3 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ณต๊ธฐ ์ ํ ํํฐ\", [\"๊ณต๊ธฐ ํํฐ\", \"ํก๊ธฐ ํํฐ\", \"์ ํ ์ฅ์น\"], 12, 0.25, 4, (0.01, 0.12), 1.0, \"MK3\", \"๋ง๋ชจ:5 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:3\")]\n",
"NP = len(PARTS)\n",
"DEFECTS = { # ๊ฒฐํจ ์ ํ: ์ฆ์ ํํ(๋์์ด)\n",
" \"๊ท ์ด\": [\"๊ท ์ด์ด ๋ฐ๊ฒฌ๋จ\", \"ํฌ๋์ด ํ์ธ๋จ\", \"๊ฐ๋ผ์ง์ด ์์\"], \"๋์ \": [\"๋์ ๊ฐ ๋ฐ์ํจ\", \"์ค์ผ์ด ์์ด ๋์ด\", \"๋์ค ํ์ ์ด ์์\"],\n",
" \"๋ง๋ชจ\": [\"๋ง๋ชจ๊ฐ ์ฌํจ\", \"๊ณผ๋ํ ๋ง๋ชจ๊ฐ ํ์ธ๋จ\", \"๋ณ์ ํ์ ์ด ์์\"], \"๋ถ์\": [\"๋ถ์์ด ์งํ๋จ\", \"๋
น์ด ๋ฐ์ํจ\", \"์ฐํ ํ์ ์ด ์์\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋์ด ๋ฐ์ํจ\", \"์ปค๋ฅํฐ ๋จ์ ์ด ์์ฌ๋จ\", \"์ ํธ๊ฐ ๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ ํ๋ฆผ์ด ํ์ธ๋จ\", \"์ฒด๊ฒฐ ์ํ๊ฐ ๋ถ๋ํจ\", \"๊ณ ์ ๋ถ๊ฐ ํ๊ฑฐ์\"],\n",
" \"๊ณผ์ด\": [\"๊ณผ์ด ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํจ\", \"์จ๋๊ฐ ๊ณผ๋ํ๊ฒ ์์นํจ\", \"์ด์ด ์ฌํ๊ฒ ๋จ\"], \"์ค์๋\": [\"์ค์๋์ด ๋ฐ๋ณต๋จ\", \"์๋์ด ๋ถ์์ ํจ\", \"๊ฐํ์ ์ผ๋ก ๋ฉ์ถค\"]}\n",
"DNAMES = list(DEFECTS)\n",
"SEV = [\"Minor\", \"Major\", \"Critical\"]\n",
"SEV_CUE = {0: [\"๊ฒฝ๋ฏธํ ์์ค์\", \"์ด์ฉ์๋ ์ง์ฅ์ด ์์\", \"์ก์ ์ ๊ฒ์ ์ฌ์ํ ์ด์์\", \"๊ธฐ๋ฅ์๋ ์ํฅ์ด ์์\"],\n",
" 1: [\"์ฑ๋ฅ ์ ํ๊ฐ ์์\", \"์กฐ์ํ ์ ๋น๊ฐ ํ์ํจ\", \"์ถ๋ ฅ์ด ๋จ์ด์ง๋ ์ํ์\", \"๊ธฐ๋ฅ์ด ๋ถ๋ถ์ ์ผ๋ก ์ ํ๋จ\"],\n",
" 2: [\"์ฆ์ ์ด์ฉ ์ค์งํจ\", \"์์ ์ ์ํ์ด ์์ด ์ฌ์ฉ ๋ถ๊ฐ\", \"์๋ ์ ์ง๋ก ์๋ฌด ์ํ์ด ์ด๋ ค์\", \"์ธ๋ช
์์ ์ํ์ด ์ฐ๋ ค๋จ\"]}\n",
"CAUSES = [\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\", \"์ด์ฉ ๋ถ์ฃผ์\", \"์ค๊ณ ์ทจ์ฝ\", \"์์ฐ ๋
ธํ\"]\n",
"CAUSE_CUE = {\n",
" \"์ ์กฐ ๋ถ๋\": [\"์ ํ ์ธ์ ์งํ ๋ฐ์ํจ\", \"์ถ๊ณ ์ํ์์ ์ด๋ฏธ ์ด์์ด ์์์\", \"์ธ์ ํ ์ผ๋ง ์ง๋์ง ์์ ๋ฐ์ํจ\"],\n",
" \"์ ๋น ํ์ง\": [\"์ธ์ฃผ ์ ๋น ํ ์ผ๋ง ๋์ง ์์ ์ฌ๋ฐํจ\", \"์ ๋น ํ ์ฌ์กฐ๋ฆฝ ์ํ๊ฐ ๋ถ๋ํจ\", \"์ ๋น ์งํ ๊ฐ์ ๋ถ์์์ ๋ค์ ๋ฐ์ํจ\"],\n",
" \"์ด์ฉ ๋ถ์ฃผ์\": [\"ํ์ง ๊ธฐ๋ ์ค ์ถฉ๊ฒฉ ์ดํ ๋ฐ์ํจ\", \"๊ณผ๋ถํ ์ด์ฉ ํ ๋ฐ์ํจ\", \"์ ๊ฒ ์์ด ์ฐ์ ์ด์ฉํ ๋ค ๋ฐ์ํจ\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์์ด ๋ค๋ฅธ ์ฐจ๋์์๋ ๋ฐ๋ณต๋จ\", \"๊ตฌ์กฐ์ ์ผ๋ก ์ทจ์ฝํ ๋ถ์๋ก ๋ณด์\", \"์ฌ๋ฌ ๋ถ๋์์ ๊ฐ์ ํํ๋ก ๋ณด๊ณ ๋จ\"],\n",
" \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ์ด ๋๋ํจ\", \"์ฅ๊ธฐ๊ฐ ์ฌ์ฉ์ผ๋ก ๋
ธํ๋จ\", \"์๋ช
์ฃผ๊ธฐ ๋ง๊ธฐ์ ๋ฐ์ํจ\"]}\n",
"VENDORS = [\"์ธ์ฃผA\", \"์ธ์ฃผB\", \"์ธ์ฃผC\", \"์ธ์ฃผD\", \"๋ถ๋์ ๋น\"]\n",
"ACTIONS = [\"์ ๋น์ ์
๊ณ ๋ฅผ ์์ฒญํจ\", \"ํ์ฅ ์์ ์กฐ์น ํ ์ด์ฉ ์ค์\", \"๋ถํ ๊ตํ์ ์์ฒญํจ\", \"์ฌ๊ฒ์ฌ๋ฅผ ์์ฒญํจ\", \"์์ธ ํ์ธ์ด ํ์ํจ\"]\n",
"MAKER_OF = [p[7] for p in PARTS]\n",
"PRICE = np.array([p[6] for p in PARTS])\n",
"\n",
"\n",
"def usage_index(months, seed=0):\n",
" \"\"\"์๋ณ ํ๋ จ ๊ฐ๋(๋ฏธ๋ฆฌ ์ ์ ์๋ ๊ณํ ๊ฐ): ์ฐ 1ํ ์ ์ ์ ๊ฐ๋ ๊ณ์ + ์์ ์ก์\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 4] ํ
์คํธ ์๋ฒ ๋ฉ ๋ฐ ํ ํฝ ๋ชจ๋ธ๋ง ๊ธฐ๋ฐ ์ฅ๋น ๊ตฌ์กฐ์ ํ์ง ์ทจ์ฝ์ ์๋ ๊ตฐ์งํ ๋ชจ๋ธ ๊ณ ๋ํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_qdr(months=36, seed=1, scale=1.0, surge=True):\n",
" \"\"\"๋ฐํ: QDR DataFrame(๊ฑด๋น 1ํ), ์๋ณ usage, ๊ธ์ฆ ์ด๋ฒคํธ ๋ชฉ๋ก\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" use = usage_index(months, seed)\n",
" events = []\n",
" if surge:\n",
" for _ in range(max(2, months // 12 * 2)):\n",
" events.append((int(rng.integers(NP)), int(rng.integers(6, months))))\n",
" rows = []\n",
" wdef = [np.array([float(x.split(\":\")[1]) for x in p[8].split()]) for p in PARTS]\n",
" ddef = [[x.split(\":\")[0] for x in p[8].split()] for p in PARTS]\n",
" n = 0\n",
" for m in range(months):\n",
" for pi, p in enumerate(PARTS):\n",
" lam = p[2] * scale * (1 + 0.010 * m) * use[m] * (1 + p[3] * np.cos(2 * np.pi * (m % 12 - p[4]) / 12))\n",
" ev = (pi, m) in events\n",
" k = rng.poisson(lam * (2.6 if ev else 1.0))\n",
" for _ in range(k):\n",
" rows.append((n, m, pi, ev)); n += 1\n",
" N = len(rows)\n",
" A = np.array(rows)\n",
" month, part, ev = A[:, 1], A[:, 2], A[:, 3].astype(bool)\n",
" defect = np.array([rng.choice(ddef[i], p=wdef[i] / wdef[i].sum()) for i in part])\n",
" cr = np.array([PARTS[i][5][0] for i in part]); ma = np.array([PARTS[i][5][1] for i in part])\n",
" u = rng.random(N)\n",
" sev = np.where(u < cr, 2, np.where(u < cr + ma, 1, 0))\n",
" vendor = rng.choice(VENDORS, N, p=[0.25, 0.2, 0.2, 0.15, 0.2])\n",
" # ์์ธ: ๊ธฐ๋ณธ ๋น์จ์ ๋ถ์(์ ์กฐ์ฌ)ยท์
์ฒดยท์ด๋ฒคํธ ํจ๊ณผ๋ฅผ ๊ณฑํ๋ค\n",
" base = np.array([0.22, 0.20, 0.20, 0.16, 0.22])\n",
" P = np.tile(base, (N, 1))\n",
" P[vendor == \"์ธ์ฃผB\", 1] *= 2.6\n",
" P[ev, 0] *= 5.0\n",
" design = np.isin(part, [2, 3, 7]); P[design, 3] *= 2.2\n",
" P[np.isin(part, [4, 5, 11]), 0] *= 2.0 # ์ ์กฐ์ฌ MK3 ๋ฉํ ๋ถ์๋ ์ ์กฐ ๋ถ๋ ๋น์ค์ด ๋๋ค\n",
" P[defect == \"๋ง๋ชจ\", 4] *= 1.6; P[defect == \"๋์ \", 4] *= 1.3\n",
" P /= P.sum(axis=1, keepdims=True)\n",
" cause = np.array([CAUSES[rng.choice(5, p=P[i])] for i in range(N)])\n",
" since = np.where(cause == \"์ ๋น ํ์ง\", rng.exponential(35, N), rng.exponential(160, N)).clip(1, 720).round().astype(int)\n",
" wait = (rng.lognormal(1.3, 0.5, N) * np.where(vendor == \"์ธ์ฃผB\", 1.7, np.where(vendor == \"๋ถ๋์ ๋น\", 0.6, 1.0)) * (0.7 + 0.5 * use[month])).round(1)\n",
" reins = rng.random(N) < np.where(cause == \"์ ๋น ํ์ง\", 0.6, 0.08)\n",
" texts = []\n",
" for i in range(N):\n",
" pt = PARTS[part[i]]\n",
" cl = [f\"{rng.choice(pt[1])}์์ {rng.choice(DEFECTS[defect[i]])}\"]\n",
" if rng.random() < 0.7: cl.append(rng.choice(SEV_CUE[sev[i]]))\n",
" if rng.random() < 0.65: # ์์ธ ๋จ์: 8%๋ ์ค์ ์์ธ๊ณผ ๋ค๋ฅธ ๋จ์๊ฐ ์ ํ๋ค(๋ณด๊ณ ์์ ์ถ์ )\n",
" cc = cause[i] if rng.random() > 0.08 else rng.choice(CAUSES)\n",
" cl.append(rng.choice(CAUSE_CUE[cc]))\n",
" cl.append(rng.choice(ACTIONS))\n",
" if rng.random() < 0.5: cl.insert(0, f\"VU-{rng.integers(1, 31):02d} ์์\")\n",
" texts.append(\". \".join(cl) + \".\")\n",
" df = pd.DataFrame({\"qdr_id\": [f\"Q{seed}{i:06d}\" for i in range(N)], \"month\": month, \"unit\": [f\"VU-{x:02d}\" for x in rng.integers(1, 31, N)],\n",
" \"part\": part, \"defect\": defect, \"sev\": sev, \"cause\": cause, \"vendor\": vendor, \"maker\": [MAKER_OF[i] for i in part],\n",
" \"since_maint\": since, \"inspect_wait\": wait, \"reinspect\": reins, \"event\": ev, \"text\": texts})\n",
" df[\"cost\"] = PRICE[df[\"part\"]] * np.array([1.0, 2.0, 4.0])[df[\"sev\"]] # [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์๋ฆฌยท์กฐ์น ๋น์ฉ์ง์\n",
" return df, use, events\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# ๊ณตํต ํ
์คํธ ์กฐ๊ฐ: ์ ๊ทํ, ๊ฐ์ด ํ ํฐํ(์กฐ์ฌ ์ ๊ฑฐ), ๋ถ์ยท๊ฒฐํจยท์น๋ช
๋ยท์์ธ ํค์๋ ๋ผ๋ฒจ๋ง\n",
"# ---------------------------------------------------------------------------\n",
"TYPO = {\"๋์ณ\": \"๋์ \", \"๊ท ๋ ฌ\": \"๊ท ์ด\", \"์ ์ถ ๋ถ๋\": \"์ ์ด ๋ถ๋\", \"๋ถ์ฉ\": \"๋ถ์\", \"๋ง๋ฌ\": \"๋ง๋ชจ\", \"๊ณผ๋ ฌ\": \"๊ณผ์ด\", \"์ค์๋ฅ\": \"์ค์๋\", \"์ฌ๋\": \"์ ๋\"}\n",
"ABBR = {\"T/M\": \"๋ณ์๊ธฐ\", \"BAT\": \"๋ฐฐํฐ๋ฆฌ\"}\n",
"PART_KW = sorted([(a, i) for i, p in enumerate(PARTS) for a in p[1]], key=lambda x: -len(x[0]))\n",
"DEF_KW = {\"๊ท ์ด\": [\"๊ท ์ด\", \"ํฌ๋\", \"๊ฐ๋ผ\"], \"๋์ \": [\"๋์ \", \"์์ด\", \"๋์ค\"], \"๋ง๋ชจ\": [\"๋ง๋ชจ\", \"๋ณ์\"], \"๋ถ์\": [\"๋ถ์\", \"๋
น์ด\", \"์ฐํ\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋\", \"๋จ์ \", \"๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ\", \"์ฒด๊ฒฐ\", \"ํ๊ฑฐ\"], \"๊ณผ์ด\": [\"๊ณผ์ด\", \"์จ๋\", \"์ด์ด\"], \"์ค์๋\": [\"์ค์๋\", \"๋ถ์์ \", \"๋ฉ์ถค\"]}\n",
"SEV_KW = [(2, [\"์ฆ์ ์ด์ฉ ์ค์ง\", \"์ฌ์ฉ ๋ถ๊ฐ\", \"์ํ\"]), (1, [\"์ฑ๋ฅ ์ ํ\", \"์กฐ์ํ\"]), (0, [\"๊ฒฝ๋ฏธ\", \"์ง์ฅ์ด ์\"])]\n",
"CAUSE_KW = {\"์ ์กฐ ๋ถ๋\": [\"์ ํ\", \"์ถ๊ณ \", \"์ธ์\"], \"์ ๋น ํ์ง\": [\"์ ๋น ํ\", \"์ฌ์กฐ๋ฆฝ\", \"์ ๋น ์งํ\"], \"์ด์ฉ ๋ถ์ฃผ์\": [\"์ถฉ๊ฒฉ\", \"๊ณผ๋ถํ\", \"์ ๊ฒ ์์ด\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์\", \"๊ตฌ์กฐ์ \", \"์ฌ๋ฌ ๋ถ๋\"], \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ\", \"๋
ธํ\", \"์๋ช
\"]}\n",
"PARTICLES = (\"์์\", \"์ผ๋ก\", \"์ด\", \"๊ฐ\", \"์\", \"๋\", \"์\", \"๋ฅผ\", \"์\", \"์\", \"๋ก\", \"์\", \"๊ณผ\", \"๋\", \"ํจ\", \"์\", \"๋จ\")\n",
"\n",
"\n",
"def norm(t):\n",
" \"\"\"์ ๊ทํ: ์ ๊ฐ->๋ฐ๊ฐ, ํน์๋ฌธ์ยท์ค๋ณต ๊ณต๋ฐฑ ์ ๊ฑฐ, ์ฝ์ด ํ์ฅ, ์คํ ๊ต์ \"\"\"\n",
" # [TODO : ํ์ ์ค์ต 4] ํ
์คํธ ์๋ฒ ๋ฉ ๋ฐ ํ ํฝ ๋ชจ๋ธ๋ง ๊ธฐ๋ฐ ์ฅ๋น ๊ตฌ์กฐ์ ํ์ง ์ทจ์ฝ์ ์๋ ๊ตฐ์งํ ๋ชจ๋ธ ๊ณ ๋ํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tok(t):\n",
" out = []\n",
" for w in re.split(r\"[\\s.,]+\", norm(t)):\n",
" if not w or re.fullmatch(r\"[\\d\\-A-Za-z]+\", w):\n",
" continue\n",
" for p in PARTICLES:\n",
" if len(w) > len(p) + 1 and w.endswith(p):\n",
" w = w[:-len(p)]\n",
" break\n",
" out.append(w)\n",
" return out\n",
"\n",
"\n",
"def label_part(t):\n",
" for a, i in PART_KW:\n",
" if a in t:\n",
" return i\n",
" return -1\n",
"\n",
"\n",
"def label_defect(t):\n",
" for d, ks in DEF_KW.items():\n",
" if any(k in t for k in ks):\n",
" return d\n",
" return \"๋ฏธ๋ถ๋ฅ\"\n",
"\n",
"\n",
"def label_sev(t):\n",
" for s, ks in SEV_KW:\n",
" if any(k in t for k in ks):\n",
" return s\n",
" return -1\n",
"\n",
"\n",
"def label_cause(t):\n",
" for c, ks in CAUSE_KW.items():\n",
" if any(k in t for k in ks):\n",
" return c\n",
" return \"๋ฏธ์\"\n",
"\n",
"df, use, events = make_qdr(36, seed=1, scale=3.0)\n",
"names = [p[0] for p in PARTS]\n",
"print(f\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] QDR {len(df)}๊ฑด, 36๊ฐ์\")\n",
"print(\"(ํ ํฝ ๋ชจ๋ธ์ ๊ฒฐํจ ์์ ํต์ฌ ๋ฌธ์ฅ '๋ถ์์์ ์ฆ์'์ ์ ์ฉํ๊ณ , ์น๋ช
๋ยท์์ธ์ ๊ตฐ์ง ํด์์ ์ฌ์ฉ)\")\n",
"\n",
"# 1) ํ
์คํธ ์๋ฒ ๋ฉ(TF-IDF) + NMF ํ ํฝ ๋ชจ๋ธ๋ง\n",
"STOP = set(w for a in ACTIONS for w in tok(a)) | {\"์์\"}\n",
"vec = TfidfVectorizer(tokenizer=lambda s: [w for w in tok(s) if w not in STOP], lowercase=False, token_pattern=None, min_df=5)\n",
"df[\"core\"] = df[\"text\"].map(lambda s: [x for x in s.split(\". \") if \"์์\" in x][0]) # ๊ฒฐํจ ์์ ํต์ฌ ๋ฌธ์ฅ(๋ถ์+์ฆ์)\n",
"X = vec.fit_transform(df[\"core\"])\n",
"K = 36\n",
"nmf = NMF(n_components=K, init=\"nndsvd\", random_state=0, max_iter=400).fit(X)\n",
"Wd = nmf.transform(X)\n",
"df[\"topic\"] = Wd.argmax(axis=1)\n",
"vocab = np.array(vec.get_feature_names_out())\n",
"nmi_p = normalized_mutual_info_score(df[\"part\"], df[\"topic\"]); nmi_d = normalized_mutual_info_score(df[\"defect\"], df[\"topic\"]); nmi_pd = normalized_mutual_info_score(df[\"part\"].astype(str) + df[\"defect\"], df[\"topic\"])\n",
"print(f\"TF-IDF ์ดํ {X.shape[1]}๊ฐ, ํ ํฝ {K}๊ฐ: ๊ตฐ์ง-๋ผ๋ฒจ ์ผ์น๋(NMI) ๋ถ์ {nmi_p:.2f}, ๊ฒฐํจ ์ ํ {nmi_d:.2f}, ๋ถ์+๊ฒฐํจ {nmi_pd:.2f}\")\n",
"base_acc = np.mean([label_part(t) == p for t, p in zip(df[\"text\"], df[\"part\"])])\n",
"print(f\" ์ฐธ๊ณ : ํค์๋ ๊ท์น ๋ถ์ ๋ผ๋ฒจ ์ ํ๋ {base_acc:.1%}\")\n",
"g = df.groupby(\"topic\").agg(๊ฑด์=(\"qdr_id\", \"size\"), critical=(\"sev\", lambda s: (s == 2).mean()), ์ค๊ณ์ทจ์ฝ=(\"cause\", lambda s: (s == \"์ค๊ณ ์ทจ์ฝ\").mean()),\n",
" ์ฃผ๋ถ์=(\"part\", lambda s: names[s.mode()[0]]), ์ฃผ๊ฒฐํจ=(\"defect\", lambda s: s.mode()[0]))\n",
"g[\"ํต์ฌ์ด\"] = [\", \".join(vocab[np.argsort(-nmf.components_[k])[:4]]) for k in g.index]\n",
"g[\"์ทจ์ฝ์ง์\"] = g[\"๊ฑด์\"] * (g[\"critical\"] * 9 + (1 - g[\"critical\"]) * 1.5)\n",
"print(\"\\n[์ทจ์ฝ ๊ตฐ์ง ์์ 5] ํ ํฝ | ๊ฑด์ | Critical ๋น์ค | ์ฃผ ๋ถ์ | ์ฃผ ๊ฒฐํจ | ํต์ฌ์ด\")\n",
"for k, r in g.sort_values(\"์ทจ์ฝ์ง์\", ascending=False).head(5).iterrows():\n",
" print(f\" T{k:02d} {r['๊ฑด์']:4d} {r['critical']:.0%} {r['์ฃผ๋ถ์']} / {r['์ฃผ๊ฒฐํจ']} [{r['ํต์ฌ์ด']}]\")\n",
"struct = list(g[\"์ค๊ณ์ทจ์ฝ\"].sort_values(ascending=False).index[:8])\n",
"print(f\"์ค๊ณ ์ทจ์ฝ ๋น์ค ์์ ํ ํฝ {len(struct)}๊ฐ(๊ตฌ์กฐ์ ์ทจ์ฝ ํ๋ณด): \" + \", \".join(f\"T{k:02d}\" for k in struct) + f\" -> ํด๋น ํ ํฝ QDR์ ์ค๊ณ ์ทจ์ฝ ๋น์ค {df.loc[df['topic'].isin(struct), 'cause'].eq('์ค๊ณ ์ทจ์ฝ').mean():.0%} (์ ์ฒด {df['cause'].eq('์ค๊ณ ์ทจ์ฝ').mean():.0%})\")\n",
"\n",
"# 2) ๋ถ์๋ณ ์๊ฐ QDR ๊ฑด์ ๊ธฐ์ค ๋ชจ๋ธ: ํ์ ํผ์ฒ 13๊ฐ\n",
"top8 = list(df.groupby(\"part\").size().sort_values(ascending=False).index[:8])\n",
"mon = df.groupby([\"part\", \"month\"]).agg(n=(\"qdr_id\", \"size\"), crit=(\"sev\", lambda s: (s == 2).mean()), wait=(\"inspect_wait\", \"mean\"), vb=(\"vendor\", lambda s: (s == \"์ธ์ฃผB\").mean()),\n",
" re=(\"reinspect\", \"mean\"), st=(\"topic\", lambda s: s.isin(struct).mean())).reset_index()\n",
"rows = []\n",
"for p in top8:\n",
" d = mon[mon[\"part\"] == p].set_index(\"month\").reindex(range(36)).fillna(0)\n",
" f = pd.DataFrame({\"part\": p, \"month\": range(36), \"n\": d[\"n\"].values})\n",
" f[\"lag1\"], f[\"lag2\"] = f[\"n\"].shift(1), f[\"n\"].shift(2)\n",
" f[\"roll3\"] = f[\"n\"].shift(1).rolling(3).mean()\n",
" f[\"exp_count\"] = (f[\"n\"] / use).shift(1).rolling(6, min_periods=3).mean() * use # ์ต๊ทผ 6๊ฐ์ ํ๊ท ๋ฐ์๋ฅ x ์ด๋ฒ ๋ฌ ํ๋ จ ๊ฐ๋\n",
" f[\"usage\"], f[\"usage_lag1\"] = use, np.r_[use[0], use[:-1]]\n",
" f[\"season_c\"], f[\"season_s\"] = np.cos(2 * np.pi * f[\"month\"] / 12), np.sin(2 * np.pi * f[\"month\"] / 12)\n",
" f[\"trend\"] = f[\"month\"]\n",
" for c in (\"crit\", \"wait\", \"vb\", \"re\", \"st\"):\n",
" f[c + \"_lag1\"] = d[c].shift(1).values\n",
" rows.append(f)\n",
"F = pd.concat(rows, ignore_index=True).dropna()\n",
"DER = [\"lag1\", \"lag2\", \"roll3\", \"exp_count\", \"usage\", \"usage_lag1\", \"season_c\", \"season_s\", \"trend\", \"crit_lag1\", \"wait_lag1\", \"vb_lag1\", \"re_lag1\", \"st_lag1\"]\n",
"print(f\"\\n์๊ฐ ๊ฑด์ ์์ธก: ํ์ ํผ์ฒ {len(DER)}๊ฐ (๋ชฉํ 10๊ฐ ์ด์: {'๋ฌ์ฑ' if len(DER) >= 10 else '๋ฏธ๋ฌ'}), ๋์ {len(top8)}๊ฐ ๋ถ์ x {F['month'].nunique()}๊ฐ์\")\n",
"\n",
"\n",
"def fit(d, cols):\n",
" return GradientBoostingRegressor(n_estimators=120, max_depth=3, learning_rate=0.08, subsample=0.8, random_state=0).fit(d[cols], d[\"n\"])\n",
"\n",
"\n",
"mape = lambda y, p: float(np.mean(np.abs(y - p) / y))\n",
"folds, naive = [], []\n",
"for c in (17, 23, 29): # ํ์ฅ ์๋: ๊ณผ๊ฑฐ๋ก๋ง ํ์ต, ๋ค 6๊ฐ์ ๊ฒ์ฆ\n",
" trn, val = F[F[\"month\"] <= c], F[(F[\"month\"] > c) & (F[\"month\"] <= c + 6)]\n",
" folds.append(mape(val[\"n\"], fit(trn, DER).predict(val[DER]))); naive.append(mape(val[\"n\"], val[\"lag1\"]))\n",
"print(f\"์๊ณ์ด ๊ต์ฐจ๊ฒ์ฆ ํด๋๋ณ MAPE: {', '.join(f'{x:.1%}' for x in folds)} -> ํ๊ท {np.mean(folds):.1%} (๋ชฉํ 25% ์ดํ: {'๋ฌ์ฑ' if np.mean(folds) <= 0.25 else '๋ฏธ๋ฌ'}), ์ ์๊ฐ ๊ธฐ์ค ํ๊ท {np.mean(naive):.1%}\")\n",
"\n",
"# 3) ํผ์ฒ ์ค์๋๋ก ์ฐจ์ ์ถ์\n",
"trn, val = F[F[\"month\"] <= 29], F[F[\"month\"] > 29]\n",
"full = fit(trn, DER)\n",
"pi = permutation_importance(full, val[DER], val[\"n\"], n_repeats=5, random_state=0, scoring=\"neg_mean_absolute_error\")\n",
"imp = pd.Series(pi.importances_mean, DER).sort_values(ascending=False)\n",
"print(\"ํผ์ฒ ์ค์๋ ์์ 5: \" + \", \".join(f\"{k}({v:.2f})\" for k, v in imp.head(5).items()))\n",
"top = list(imp.index[:7])\n",
"print(f\"์ฐจ์ ์ถ์ {len(DER)}๊ฐ -> {len(top)}๊ฐ: ๊ฒ์ฆ MAPE {mape(val['n'], full.predict(val[DER])):.1%} -> {mape(val['n'], fit(trn, top).predict(val[top])):.1%}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
04 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ต์ก์ฉ๊ฐ์์๋ฃ] QDR 27675๊ฑด, 36๊ฐ์\n",
"(ํ ํฝ ๋ชจ๋ธ์ ๊ฒฐํจ ์์ ํต์ฌ ๋ฌธ์ฅ '๋ถ์์์ ์ฆ์'์ ์ ์ฉํ๊ณ , ์น๋ช
๋ยท์์ธ์ ๊ตฐ์ง ํด์์ ์ฌ์ฉ)\n",
"TF-IDF ์ดํ 90๊ฐ, ํ ํฝ 36๊ฐ: ๊ตฐ์ง-๋ผ๋ฒจ ์ผ์น๋(NMI) ๋ถ์ 0.45, ๊ฒฐํจ ์ ํ 0.60, ๋ถ์+๊ฒฐํจ 0.63\n",
" ์ฐธ๊ณ : ํค์๋ ๊ท์น ๋ถ์ ๋ผ๋ฒจ ์ ํ๋ 100.0%\n",
"\n",
"[์ทจ์ฝ ๊ตฐ์ง ์์ 5] ํ ํฝ | ๊ฑด์ | Critical ๋น์ค | ์ฃผ ๋ถ์ | ์ฃผ ๊ฒฐํจ | ํต์ฌ์ด\n",
" T03 1711 10% ์์ง ์ฐ๋ฃ๊ณํต / ๋์ [๋์ด, ์์ด, ์ค์ผ, ๋ผ๋์์ดํฐ]\n",
" T24 1631 11% ๋ณ์๊ธฐ / ๋ง๋ชจ [๋ณ์, ํ์ , ์์, ์ฐํ]\n",
" T28 935 30% ์กฐํฅ ์ฅ์น / ์ฒด๊ฒฐ๋ถ๋ [์กฐํฅ, ๋ก๋, ์ฅ์น, ํฌ๋]\n",
" T02 1430 12% ๋๊ฐ ๊ณํต / ๋์ [๋์ , ๋ฐ์, ๋
น์ด, ๋ผ๋์์ดํฐ]\n",
" T25 1009 18% ์กฐํฅ ์ฅ์น / ์ ์ด๋ถ๋ [์ฐ๋๋ถ, ํธ๋ค, ๋ฌด์ ๊ธฐ, ์ ์ด]\n",
"์ค๊ณ ์ทจ์ฝ ๋น์ค ์์ ํ ํฝ 8๊ฐ(๊ตฌ์กฐ์ ์ทจ์ฝ ํ๋ณด): T05, T28, T35, T13, T11, T25, T30, T02 -> ํด๋น ํ ํฝ QDR์ ์ค๊ณ ์ทจ์ฝ ๋น์ค 21% (์ ์ฒด 17%)\n",
"\n",
"์๊ฐ ๊ฑด์ ์์ธก: ํ์ ํผ์ฒ 14๊ฐ (๋ชฉํ 10๊ฐ ์ด์: ๋ฌ์ฑ), ๋์ 8๊ฐ ๋ถ์ x 33๊ฐ์\n",
"์๊ณ์ด ๊ต์ฐจ๊ฒ์ฆ ํด๋๋ณ MAPE: 27.8%, 13.9%, 16.9% -> ํ๊ท 19.5% (๋ชฉํ 25% ์ดํ: ๋ฌ์ฑ), ์ ์๊ฐ ๊ธฐ์ค ํ๊ท 18.9%\n",
"ํผ์ฒ ์ค์๋ ์์ 5: lag1(6.77), usage(1.86), exp_count(1.78), st_lag1(0.81), lag2(0.68)\n",
"์ฐจ์ ์ถ์ 14๊ฐ -> 7๊ฐ: ๊ฒ์ฆ MAPE 16.9% -> 15.3%\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
05. ๊ฒฐํจ ์น๋ช
๋(Critical/Major/Minor) ๊ธฐ๋ฐ ์ข
ํฉ ํ์ง ์ทจ์ฝ์ฑ ์ง์ ์ฐ์ถ ์์ง ๊ฐ๋ฐ\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ฒฐํจ ์น๋ช
๋ ๊ธฐ๋ฐ ์ข
ํฉ ํ์ง ์ทจ์ฝ์ฑ ์ง์ ์ฐ์ถ ์์ง์ ๊ฐ๋ฐํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๊ฒฐํจ ์น๋ช
๋(Critical/Major/Minor)๋ฅผ ๋ถ๋ฅํ๊ณ ์น๋ช
๋ ๊ฐ์ค ์ข
ํฉ ํ์ง ์ทจ์ฝ์ฑ ์ง์๋ฅผ ๋ถ์๋ณ๋ก ์ฐ์ถยท์์ธกํ๋ ์์ง์ ๊ฐ๋ฐํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **ํค์๋ ๊ท์น๊ณผ TF-IDF ๋ก์ง์คํฑ ํ๊ท์ ์น๋ช
๋ ๋ถ๋ฅ ์ ํ๋์ Critical ์ฌํ์จ์ ๋น๊ตํ๊ณ ์ ๋ขฐ๋ ๊ตฌ๊ฐ๋ณ ์ค์ ์ ํ๋๋ฅผ ์ ๊ฒ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/qlt05_advanced_model.py` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๋น์ ํ/๋ถ๊ท์น ์์์ ๋ํ ๋ชจ๋ธ์ ์ ์๋ ฅ ๋ฐ ์์ธก ์์ ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ๊ธฐ์ค ๋ชจ๋ธ ๋๋น ์ค์ฐจ์จ(MAE/RMSE) 20% ์ด์ ๊ฐ์ , R2 Score 0.85 ์ด์\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ๋ชจ๋ธ ํ์ดํผํ๋ผ๋ฏธํฐ ํ๋ ์๋ฃ ๋ฐ ์ต์ ๋ชจ๋ธ ๊ฐ์ค์น ์ ์ฅ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: Hold-out ํ
์คํธ ๋ฐ์ดํฐ์
๋์ ์ต์ข
์ฑ๋ฅ ํ๊ฐ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"QDR ์์ ยท๋ถ์ยท๊ฒฐํจ ์ ๋ณด๋ก ๊ฐ๋ณ ๊ฒฐํจ์ ์น๋ช
๋(Critical/Major/Minor)๋ฅผ ๋ถ๋ฅํ๋ ๋ชจ๋ธ(ํค์๋ ๊ท์น ๊ธฐ์ค ๋๋น)๊ณผ ์ ๋ขฐ๋ ๋ณด์ ์ ํ์ธํ๊ณ , ์น๋ช
๋ ๊ฐ์ค ๋ถ์๋ณ ์๊ฐ ์ข
ํฉ ํ์ง ์ทจ์ฝ์ฑ ์ง์๋ฅผ ์์๋ธ(๋ฆฟ์ง์ ๊ทธ๋๋์ธํธ ๋ถ์คํ
ยท๋๋ค ํฌ๋ ์คํธ ์์ฐจ ๋ณด์ ์ ๋ํ ๋ชจ๋ธ)๋ก ์์ธกํด ํ์ต ๊ธฐ๊ฐ ํ๊ท ๊ธฐ์ค ๋ชจ๋ธ๊ณผ MAEยทRMSEยทR2๋ฅผ ๋น๊ตํ๋ ์ทจ์ฝ์ฑ ์ง์ ์ฐ์ถ ์์ง์ ๊ตฌํํ๋ค.\"\"\"\n",
"import re\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"import scipy.sparse as sp\n",
"from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor\n",
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"from sklearn.linear_model import LogisticRegression, Ridge\n",
"from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR) ์์ฑ๊ธฐ (์ค์ ๋ถ๋ยท์ฅ๋นยท์
์ฒดยท์ ๋น ์ค์ ์์)\n",
"# ๊ฐ์ ์ฅ๋น ๋ถ์ 12์ข
, ๊ฒฐํจ ์ ํ 8์ข
, ์น๋ช
๋(Critical/Major/Minor), ์์ธ 5์ข
, ๊ฐ์ ์ธ์ฃผ์ ๋น ์
์ฒด 4๊ณณ + ๋ถ๋์ ๋น.\n",
"# QDR 1๊ฑด๋ง๋ค ํ๊ตญ์ด ์์ ํ
์คํธ๋ฅผ ๋ง๋ค๊ณ , ์๋ณ ๊ฑด์๋ ๊ณ์ ยท์ถ์ธยทํ๋ จ ๊ฐ๋(usage)ยท์ผ๋ถ ์ ์กฐ ๋ถ๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ๋ฅธ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"PARTS = [ # (์ด๋ฆ, ๋ณ์นญ, ์ ๊ธฐ์ค ๊ฑด์, ๊ณ์ ์งํญ, ์ ์ ์, (Critical, Major ๋น์จ), ๊ฐ์ ๋จ๊ฐ์ง์, ์ ์กฐ์ฌ, ์ฃผ์ ๊ฒฐํจ ๊ฐ์ค์น)\n",
" (\"์์ง ์ฐ๋ฃ๊ณํต\", [\"์ฐ๋ฃ๊ณํต\", \"์ฐ๋ฃ ๋ผ์ธ\", \"์ฐ๋ฃ ํํ\"], 26, 0.15, 7, (0.10, 0.35), 6.0, \"MK1\", \"๋์ :4 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:2 ์ค์๋:2\"),\n",
" (\"๋ณ์๊ธฐ\", [\"๋ณ์๊ธฐ\", \"T/M\", \"๋ฏธ์
\"], 22, 0.10, 8, (0.12, 0.38), 9.0, \"MK1\", \"๋ง๋ชจ:4 ๋์ :2 ๊ณผ์ด:3 ์ค์๋:1\"),\n",
" (\"์ ๋ ์ฅ์น\", [\"์ ๋ ์ฅ์น\", \"๋ธ๋ ์ดํฌ\", \"์ ๋ ๋ผ์ธ\"], 20, 0.20, 12, (0.26, 0.40), 5.0, \"MK2\", \"๋ง๋ชจ:4 ๊ท ์ด:2 ๋์ :2 ์ฒด๊ฒฐ๋ถ๋:2\"),\n",
" (\"์กฐํฅ ์ฅ์น\", [\"์กฐํฅ ์ฅ์น\", \"ํธ๋ค ์ฐ๋๋ถ\", \"์กฐํฅ ๋ก๋\"], 14, 0.10, 6, (0.30, 0.40), 5.0, \"MK2\", \"์ฒด๊ฒฐ๋ถ๋:4 ๊ท ์ด:2 ๋ง๋ชจ:3 ์ค์๋:1\"),\n",
" (\"๋๊ฐ ๊ณํต\", [\"๋๊ฐ ๊ณํต\", \"๋ผ๋์์ดํฐ\", \"๋๊ฐ์ ๋ผ์ธ\"], 24, 0.30, 7, (0.05, 0.30), 3.0, \"MK3\", \"๋์ :4 ๊ณผ์ด:4 ๋ถ์:2\"),\n",
" (\"์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ\", [\"๋ฐฐํฐ๋ฆฌ\", \"์ ๊ธฐ ๊ณํต\", \"๋ฐ์ ๊ธฐ\"], 28, 0.35, 1, (0.04, 0.28), 2.5, \"MK3\", \"์ ์ด๋ถ๋:4 ๋ถ์:2 ๊ณผ์ด:2 ์ค์๋:2\"),\n",
" (\"ํต์ ๋ชจ๋\", [\"ํต์ ๋ชจ๋\", \"๋ฌด์ ๊ธฐ ์ฐ๋๋ถ\", \"์ํ
๋ ์ ์๋ถ\"], 16, 0.05, 4, (0.03, 0.25), 4.0, \"MK4\", \"์ ์ด๋ถ๋:5 ์ค์๋:4 ๋ถ์:1\"),\n",
" (\"์ ์ ๊ณํต\", [\"์ ์ ๊ณํต\", \"์ ์ ํธ์ค\", \"์ ์ ์ค๋ฆฐ๋\"], 18, 0.10, 9, (0.10, 0.35), 4.5, \"MK2\", \"๋์ :5 ๊ท ์ด:2 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"ํ๊ฐ ์ฅ์น\", [\"ํ๊ฐ ์ฅ์น\", \"์์คํ์
\", \"์์ถฉ๊ธฐ\"], 15, 0.08, 5, (0.06, 0.30), 3.5, \"MK4\", \"๋ง๋ชจ:4 ๊ท ์ด:3 ์ฒด๊ฒฐ๋ถ๋:2 ๋ถ์:1\"),\n",
" (\"์ฐจ์ฒด ์ฉ์ ๋ถ\", [\"์ฐจ์ฒด ์ฉ์ ๋ถ\", \"ํ๋ ์ ์ฉ์ ๋ถ\", \"์ฐจ์ฒด ์ด์๋ถ\"], 10, 0.05, 10, (0.28, 0.40), 7.0, \"MK1\", \"๊ท ์ด:6 ๋ถ์:3 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ดํยท์กฐ์ค ์ฅ๋น\", [\"๊ดํ ์ฅ๋น\", \"์กฐ์ค๊ฒฝ\", \"๊ด์ธก ์ฅ๋น\"], 8, 0.05, 3, (0.03, 0.22), 8.0, \"MK4\", \"์ค์๋:4 ์ ์ด๋ถ๋:3 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ณต๊ธฐ ์ ํ ํํฐ\", [\"๊ณต๊ธฐ ํํฐ\", \"ํก๊ธฐ ํํฐ\", \"์ ํ ์ฅ์น\"], 12, 0.25, 4, (0.01, 0.12), 1.0, \"MK3\", \"๋ง๋ชจ:5 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:3\")]\n",
"NP = len(PARTS)\n",
"DEFECTS = { # ๊ฒฐํจ ์ ํ: ์ฆ์ ํํ(๋์์ด)\n",
" \"๊ท ์ด\": [\"๊ท ์ด์ด ๋ฐ๊ฒฌ๋จ\", \"ํฌ๋์ด ํ์ธ๋จ\", \"๊ฐ๋ผ์ง์ด ์์\"], \"๋์ \": [\"๋์ ๊ฐ ๋ฐ์ํจ\", \"์ค์ผ์ด ์์ด ๋์ด\", \"๋์ค ํ์ ์ด ์์\"],\n",
" \"๋ง๋ชจ\": [\"๋ง๋ชจ๊ฐ ์ฌํจ\", \"๊ณผ๋ํ ๋ง๋ชจ๊ฐ ํ์ธ๋จ\", \"๋ณ์ ํ์ ์ด ์์\"], \"๋ถ์\": [\"๋ถ์์ด ์งํ๋จ\", \"๋
น์ด ๋ฐ์ํจ\", \"์ฐํ ํ์ ์ด ์์\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋์ด ๋ฐ์ํจ\", \"์ปค๋ฅํฐ ๋จ์ ์ด ์์ฌ๋จ\", \"์ ํธ๊ฐ ๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ ํ๋ฆผ์ด ํ์ธ๋จ\", \"์ฒด๊ฒฐ ์ํ๊ฐ ๋ถ๋ํจ\", \"๊ณ ์ ๋ถ๊ฐ ํ๊ฑฐ์\"],\n",
" \"๊ณผ์ด\": [\"๊ณผ์ด ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํจ\", \"์จ๋๊ฐ ๊ณผ๋ํ๊ฒ ์์นํจ\", \"์ด์ด ์ฌํ๊ฒ ๋จ\"], \"์ค์๋\": [\"์ค์๋์ด ๋ฐ๋ณต๋จ\", \"์๋์ด ๋ถ์์ ํจ\", \"๊ฐํ์ ์ผ๋ก ๋ฉ์ถค\"]}\n",
"DNAMES = list(DEFECTS)\n",
"SEV = [\"Minor\", \"Major\", \"Critical\"]\n",
"SEV_CUE = {0: [\"๊ฒฝ๋ฏธํ ์์ค์\", \"์ด์ฉ์๋ ์ง์ฅ์ด ์์\", \"์ก์ ์ ๊ฒ์ ์ฌ์ํ ์ด์์\", \"๊ธฐ๋ฅ์๋ ์ํฅ์ด ์์\"],\n",
" 1: [\"์ฑ๋ฅ ์ ํ๊ฐ ์์\", \"์กฐ์ํ ์ ๋น๊ฐ ํ์ํจ\", \"์ถ๋ ฅ์ด ๋จ์ด์ง๋ ์ํ์\", \"๊ธฐ๋ฅ์ด ๋ถ๋ถ์ ์ผ๋ก ์ ํ๋จ\"],\n",
" 2: [\"์ฆ์ ์ด์ฉ ์ค์งํจ\", \"์์ ์ ์ํ์ด ์์ด ์ฌ์ฉ ๋ถ๊ฐ\", \"์๋ ์ ์ง๋ก ์๋ฌด ์ํ์ด ์ด๋ ค์\", \"์ธ๋ช
์์ ์ํ์ด ์ฐ๋ ค๋จ\"]}\n",
"CAUSES = [\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\", \"์ด์ฉ ๋ถ์ฃผ์\", \"์ค๊ณ ์ทจ์ฝ\", \"์์ฐ ๋
ธํ\"]\n",
"CAUSE_CUE = {\n",
" \"์ ์กฐ ๋ถ๋\": [\"์ ํ ์ธ์ ์งํ ๋ฐ์ํจ\", \"์ถ๊ณ ์ํ์์ ์ด๋ฏธ ์ด์์ด ์์์\", \"์ธ์ ํ ์ผ๋ง ์ง๋์ง ์์ ๋ฐ์ํจ\"],\n",
" \"์ ๋น ํ์ง\": [\"์ธ์ฃผ ์ ๋น ํ ์ผ๋ง ๋์ง ์์ ์ฌ๋ฐํจ\", \"์ ๋น ํ ์ฌ์กฐ๋ฆฝ ์ํ๊ฐ ๋ถ๋ํจ\", \"์ ๋น ์งํ ๊ฐ์ ๋ถ์์์ ๋ค์ ๋ฐ์ํจ\"],\n",
" \"์ด์ฉ ๋ถ์ฃผ์\": [\"ํ์ง ๊ธฐ๋ ์ค ์ถฉ๊ฒฉ ์ดํ ๋ฐ์ํจ\", \"๊ณผ๋ถํ ์ด์ฉ ํ ๋ฐ์ํจ\", \"์ ๊ฒ ์์ด ์ฐ์ ์ด์ฉํ ๋ค ๋ฐ์ํจ\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์์ด ๋ค๋ฅธ ์ฐจ๋์์๋ ๋ฐ๋ณต๋จ\", \"๊ตฌ์กฐ์ ์ผ๋ก ์ทจ์ฝํ ๋ถ์๋ก ๋ณด์\", \"์ฌ๋ฌ ๋ถ๋์์ ๊ฐ์ ํํ๋ก ๋ณด๊ณ ๋จ\"],\n",
" \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ์ด ๋๋ํจ\", \"์ฅ๊ธฐ๊ฐ ์ฌ์ฉ์ผ๋ก ๋
ธํ๋จ\", \"์๋ช
์ฃผ๊ธฐ ๋ง๊ธฐ์ ๋ฐ์ํจ\"]}\n",
"VENDORS = [\"์ธ์ฃผA\", \"์ธ์ฃผB\", \"์ธ์ฃผC\", \"์ธ์ฃผD\", \"๋ถ๋์ ๋น\"]\n",
"ACTIONS = [\"์ ๋น์ ์
๊ณ ๋ฅผ ์์ฒญํจ\", \"ํ์ฅ ์์ ์กฐ์น ํ ์ด์ฉ ์ค์\", \"๋ถํ ๊ตํ์ ์์ฒญํจ\", \"์ฌ๊ฒ์ฌ๋ฅผ ์์ฒญํจ\", \"์์ธ ํ์ธ์ด ํ์ํจ\"]\n",
"MAKER_OF = [p[7] for p in PARTS]\n",
"PRICE = np.array([p[6] for p in PARTS])\n",
"\n",
"\n",
"def usage_index(months, seed=0):\n",
" \"\"\"์๋ณ ํ๋ จ ๊ฐ๋(๋ฏธ๋ฆฌ ์ ์ ์๋ ๊ณํ ๊ฐ): ์ฐ 1ํ ์ ์ ์ ๊ฐ๋ ๊ณ์ + ์์ ์ก์\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 5] ๊ฒฐํจ ์น๋ช
๋(Critical/Major/Minor) ๊ธฐ๋ฐ ์ข
ํฉ ํ์ง ์ทจ์ฝ์ฑ ์ง์ ์ฐ์ถ ์์ง ๊ฐ๋ฐ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_qdr(months=36, seed=1, scale=1.0, surge=True):\n",
" \"\"\"๋ฐํ: QDR DataFrame(๊ฑด๋น 1ํ), ์๋ณ usage, ๊ธ์ฆ ์ด๋ฒคํธ ๋ชฉ๋ก\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" use = usage_index(months, seed)\n",
" events = []\n",
" if surge:\n",
" for _ in range(max(2, months // 12 * 2)):\n",
" events.append((int(rng.integers(NP)), int(rng.integers(6, months))))\n",
" rows = []\n",
" wdef = [np.array([float(x.split(\":\")[1]) for x in p[8].split()]) for p in PARTS]\n",
" ddef = [[x.split(\":\")[0] for x in p[8].split()] for p in PARTS]\n",
" n = 0\n",
" for m in range(months):\n",
" for pi, p in enumerate(PARTS):\n",
" lam = p[2] * scale * (1 + 0.010 * m) * use[m] * (1 + p[3] * np.cos(2 * np.pi * (m % 12 - p[4]) / 12))\n",
" ev = (pi, m) in events\n",
" k = rng.poisson(lam * (2.6 if ev else 1.0))\n",
" for _ in range(k):\n",
" rows.append((n, m, pi, ev)); n += 1\n",
" N = len(rows)\n",
" A = np.array(rows)\n",
" month, part, ev = A[:, 1], A[:, 2], A[:, 3].astype(bool)\n",
" defect = np.array([rng.choice(ddef[i], p=wdef[i] / wdef[i].sum()) for i in part])\n",
" cr = np.array([PARTS[i][5][0] for i in part]); ma = np.array([PARTS[i][5][1] for i in part])\n",
" u = rng.random(N)\n",
" sev = np.where(u < cr, 2, np.where(u < cr + ma, 1, 0))\n",
" vendor = rng.choice(VENDORS, N, p=[0.25, 0.2, 0.2, 0.15, 0.2])\n",
" # ์์ธ: ๊ธฐ๋ณธ ๋น์จ์ ๋ถ์(์ ์กฐ์ฌ)ยท์
์ฒดยท์ด๋ฒคํธ ํจ๊ณผ๋ฅผ ๊ณฑํ๋ค\n",
" base = np.array([0.22, 0.20, 0.20, 0.16, 0.22])\n",
" P = np.tile(base, (N, 1))\n",
" P[vendor == \"์ธ์ฃผB\", 1] *= 2.6\n",
" P[ev, 0] *= 5.0\n",
" design = np.isin(part, [2, 3, 7]); P[design, 3] *= 2.2\n",
" P[np.isin(part, [4, 5, 11]), 0] *= 2.0 # ์ ์กฐ์ฌ MK3 ๋ฉํ ๋ถ์๋ ์ ์กฐ ๋ถ๋ ๋น์ค์ด ๋๋ค\n",
" P[defect == \"๋ง๋ชจ\", 4] *= 1.6; P[defect == \"๋์ \", 4] *= 1.3\n",
" P /= P.sum(axis=1, keepdims=True)\n",
" cause = np.array([CAUSES[rng.choice(5, p=P[i])] for i in range(N)])\n",
" since = np.where(cause == \"์ ๋น ํ์ง\", rng.exponential(35, N), rng.exponential(160, N)).clip(1, 720).round().astype(int)\n",
" wait = (rng.lognormal(1.3, 0.5, N) * np.where(vendor == \"์ธ์ฃผB\", 1.7, np.where(vendor == \"๋ถ๋์ ๋น\", 0.6, 1.0)) * (0.7 + 0.5 * use[month])).round(1)\n",
" reins = rng.random(N) < np.where(cause == \"์ ๋น ํ์ง\", 0.6, 0.08)\n",
" texts = []\n",
" for i in range(N):\n",
" pt = PARTS[part[i]]\n",
" cl = [f\"{rng.choice(pt[1])}์์ {rng.choice(DEFECTS[defect[i]])}\"]\n",
" if rng.random() < 0.7: cl.append(rng.choice(SEV_CUE[sev[i]]))\n",
" if rng.random() < 0.65: # ์์ธ ๋จ์: 8%๋ ์ค์ ์์ธ๊ณผ ๋ค๋ฅธ ๋จ์๊ฐ ์ ํ๋ค(๋ณด๊ณ ์์ ์ถ์ )\n",
" cc = cause[i] if rng.random() > 0.08 else rng.choice(CAUSES)\n",
" cl.append(rng.choice(CAUSE_CUE[cc]))\n",
" cl.append(rng.choice(ACTIONS))\n",
" if rng.random() < 0.5: cl.insert(0, f\"VU-{rng.integers(1, 31):02d} ์์\")\n",
" texts.append(\". \".join(cl) + \".\")\n",
" df = pd.DataFrame({\"qdr_id\": [f\"Q{seed}{i:06d}\" for i in range(N)], \"month\": month, \"unit\": [f\"VU-{x:02d}\" for x in rng.integers(1, 31, N)],\n",
" \"part\": part, \"defect\": defect, \"sev\": sev, \"cause\": cause, \"vendor\": vendor, \"maker\": [MAKER_OF[i] for i in part],\n",
" \"since_maint\": since, \"inspect_wait\": wait, \"reinspect\": reins, \"event\": ev, \"text\": texts})\n",
" df[\"cost\"] = PRICE[df[\"part\"]] * np.array([1.0, 2.0, 4.0])[df[\"sev\"]] # [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์๋ฆฌยท์กฐ์น ๋น์ฉ์ง์\n",
" return df, use, events\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# ๊ณตํต ํ
์คํธ ์กฐ๊ฐ: ์ ๊ทํ, ๊ฐ์ด ํ ํฐํ(์กฐ์ฌ ์ ๊ฑฐ), ๋ถ์ยท๊ฒฐํจยท์น๋ช
๋ยท์์ธ ํค์๋ ๋ผ๋ฒจ๋ง\n",
"# ---------------------------------------------------------------------------\n",
"TYPO = {\"๋์ณ\": \"๋์ \", \"๊ท ๋ ฌ\": \"๊ท ์ด\", \"์ ์ถ ๋ถ๋\": \"์ ์ด ๋ถ๋\", \"๋ถ์ฉ\": \"๋ถ์\", \"๋ง๋ฌ\": \"๋ง๋ชจ\", \"๊ณผ๋ ฌ\": \"๊ณผ์ด\", \"์ค์๋ฅ\": \"์ค์๋\", \"์ฌ๋\": \"์ ๋\"}\n",
"ABBR = {\"T/M\": \"๋ณ์๊ธฐ\", \"BAT\": \"๋ฐฐํฐ๋ฆฌ\"}\n",
"PART_KW = sorted([(a, i) for i, p in enumerate(PARTS) for a in p[1]], key=lambda x: -len(x[0]))\n",
"DEF_KW = {\"๊ท ์ด\": [\"๊ท ์ด\", \"ํฌ๋\", \"๊ฐ๋ผ\"], \"๋์ \": [\"๋์ \", \"์์ด\", \"๋์ค\"], \"๋ง๋ชจ\": [\"๋ง๋ชจ\", \"๋ณ์\"], \"๋ถ์\": [\"๋ถ์\", \"๋
น์ด\", \"์ฐํ\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋\", \"๋จ์ \", \"๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ\", \"์ฒด๊ฒฐ\", \"ํ๊ฑฐ\"], \"๊ณผ์ด\": [\"๊ณผ์ด\", \"์จ๋\", \"์ด์ด\"], \"์ค์๋\": [\"์ค์๋\", \"๋ถ์์ \", \"๋ฉ์ถค\"]}\n",
"SEV_KW = [(2, [\"์ฆ์ ์ด์ฉ ์ค์ง\", \"์ฌ์ฉ ๋ถ๊ฐ\", \"์ํ\"]), (1, [\"์ฑ๋ฅ ์ ํ\", \"์กฐ์ํ\"]), (0, [\"๊ฒฝ๋ฏธ\", \"์ง์ฅ์ด ์\"])]\n",
"CAUSE_KW = {\"์ ์กฐ ๋ถ๋\": [\"์ ํ\", \"์ถ๊ณ \", \"์ธ์\"], \"์ ๋น ํ์ง\": [\"์ ๋น ํ\", \"์ฌ์กฐ๋ฆฝ\", \"์ ๋น ์งํ\"], \"์ด์ฉ ๋ถ์ฃผ์\": [\"์ถฉ๊ฒฉ\", \"๊ณผ๋ถํ\", \"์ ๊ฒ ์์ด\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์\", \"๊ตฌ์กฐ์ \", \"์ฌ๋ฌ ๋ถ๋\"], \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ\", \"๋
ธํ\", \"์๋ช
\"]}\n",
"PARTICLES = (\"์์\", \"์ผ๋ก\", \"์ด\", \"๊ฐ\", \"์\", \"๋\", \"์\", \"๋ฅผ\", \"์\", \"์\", \"๋ก\", \"์\", \"๊ณผ\", \"๋\", \"ํจ\", \"์\", \"๋จ\")\n",
"\n",
"\n",
"def norm(t):\n",
" \"\"\"์ ๊ทํ: ์ ๊ฐ->๋ฐ๊ฐ, ํน์๋ฌธ์ยท์ค๋ณต ๊ณต๋ฐฑ ์ ๊ฑฐ, ์ฝ์ด ํ์ฅ, ์คํ ๊ต์ \"\"\"\n",
" # [TODO : ํ์ ์ค์ต 5] ๊ฒฐํจ ์น๋ช
๋(Critical/Major/Minor) ๊ธฐ๋ฐ ์ข
ํฉ ํ์ง ์ทจ์ฝ์ฑ ์ง์ ์ฐ์ถ ์์ง ๊ฐ๋ฐ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tok(t):\n",
" out = []\n",
" for w in re.split(r\"[\\s.,]+\", norm(t)):\n",
" if not w or re.fullmatch(r\"[\\d\\-A-Za-z]+\", w):\n",
" continue\n",
" for p in PARTICLES:\n",
" if len(w) > len(p) + 1 and w.endswith(p):\n",
" w = w[:-len(p)]\n",
" break\n",
" out.append(w)\n",
" return out\n",
"\n",
"\n",
"def label_part(t):\n",
" for a, i in PART_KW:\n",
" if a in t:\n",
" return i\n",
" return -1\n",
"\n",
"\n",
"def label_defect(t):\n",
" for d, ks in DEF_KW.items():\n",
" if any(k in t for k in ks):\n",
" return d\n",
" return \"๋ฏธ๋ถ๋ฅ\"\n",
"\n",
"\n",
"def label_sev(t):\n",
" for s, ks in SEV_KW:\n",
" if any(k in t for k in ks):\n",
" return s\n",
" return -1\n",
"\n",
"\n",
"def label_cause(t):\n",
" for c, ks in CAUSE_KW.items():\n",
" if any(k in t for k in ks):\n",
" return c\n",
" return \"๋ฏธ์\"\n",
"\n",
"df, use, events = make_qdr(48, seed=1, scale=4.0)\n",
"names = [p[0] for p in PARTS]\n",
"W_SEV = np.array([1, 3, 9]) # ์ทจ์ฝ์ฑ ์ง์ ๊ฐ์ค์น(Minor 1, Major 3, Critical 9)\n",
"tr, va = df[df[\"month\"] < 36], df[df[\"month\"] >= 36]\n",
"print(f\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] QDR {len(df)}๊ฑด (ํ์ต 36๊ฐ์ {len(tr)}๊ฑด, ๊ฒ์ฆ 12๊ฐ์ {len(va)}๊ฑด)\")\n",
"\n",
"# 1) ๊ฐ๋ณ QDR ์น๋ช
๋ ๋ถ๋ฅ: ํค์๋ ๊ท์น vs ํ
์คํธ+๋ถ์ยท๊ฒฐํจ ๋ก์ง์คํฑ ํ๊ท\n",
"rule = va[\"text\"].map(label_sev).replace(-1, 0).values # ์น๋ช
๋ ๋จ์๊ฐ ์์ผ๋ฉด Minor๋ก ๊ฐ์ฃผ\n",
"vec = TfidfVectorizer(tokenizer=tok, lowercase=False, token_pattern=None, min_df=5)\n",
"Xtr, Xva = vec.fit_transform(tr[\"text\"]), vec.transform(va[\"text\"])\n",
"oh = lambda d: sp.csr_matrix(np.hstack([np.eye(NP)[d[\"part\"]], pd.get_dummies(d[\"defect\"]).reindex(columns=DNAMES, fill_value=0).values.astype(float)]))\n",
"clf = LogisticRegression(C=5, max_iter=500).fit(sp.hstack([Xtr, oh(tr)]), tr[\"sev\"])\n",
"P = clf.predict_proba(sp.hstack([Xva, oh(va)]))\n",
"pred = P.argmax(axis=1)\n",
"acc_r, acc_m = (rule == va[\"sev\"].values).mean(), (pred == va[\"sev\"].values).mean()\n",
"rec = lambda p: ((p == 2) & (va[\"sev\"].values == 2)).sum() / (va[\"sev\"].values == 2).sum()\n",
"print(f\"์น๋ช
๋ ๋ถ๋ฅ ์ ํ๋: ํค์๋ ๊ท์น {acc_r:.1%} -> ํ
์คํธ ๋ชจ๋ธ {acc_m:.1%}, Critical ์ฌํ์จ {rec(rule):.1%} -> {rec(pred):.1%}\")\n",
"conf = P.max(axis=1)\n",
"print(\"[์ ๋ขฐ๋ ๋ณด์ ] ๊ตฌ๊ฐ | ๊ฑด์ | ํ๊ท ์ ๋ขฐ๋ | ์ค์ ์ ํ๋\")\n",
"for a, b in [(0, 0.6), (0.6, 0.8), (0.8, 0.95), (0.95, 1.01)]:\n",
" m = (conf >= a) & (conf < b)\n",
" print(f\" {a:.2f}~{min(b, 1):.2f} {m.sum():5d} {conf[m].mean():.2f} {(pred[m] == va['sev'].values[m]).mean():.2f}\")\n",
"\n",
"# 2) ์๊ฐ ๋ถ์๋ณ ์ทจ์ฝ์ฑ ์ง์ ์์ธก\n",
"g = df.assign(v=W_SEV[df[\"sev\"]]).groupby([\"part\", \"month\"]).agg(n=(\"v\", \"size\"), v=(\"v\", \"sum\")).reset_index()\n",
"rows = []\n",
"for p in range(NP):\n",
" d = g[g[\"part\"] == p].set_index(\"month\").reindex(range(48)).fillna(0)\n",
" f = pd.DataFrame({\"part\": p, \"month\": range(48), \"y\": d[\"v\"].values, \"n\": d[\"n\"].values})\n",
" f[\"lag1\"], f[\"lag2\"], f[\"lag12\"] = f[\"y\"].shift(1), f[\"y\"].shift(2), (f[\"y\"] / use).shift(12) * use # lag12: ์๋
๊ฐ์ ๋ฌ ๋ฐ์๋ฅ x ์ด๋ฒ ๋ฌ ํ๋ จ ๊ฐ๋\n",
" f[\"exp_v\"] = (f[\"y\"] / use).shift(1).rolling(6, min_periods=3).mean() * use # ์ต๊ทผ 6๊ฐ์ ๋ฐ์๋ฅ x ์ด๋ฒ ๋ฌ ํ๋ จ ๊ฐ๋\n",
" f[\"usage\"], f[\"season_c\"], f[\"season_s\"], f[\"trend\"] = use, np.cos(2 * np.pi * f[\"month\"] / 12), np.sin(2 * np.pi * f[\"month\"] / 12), f[\"month\"]\n",
" f[\"price\"], f[\"crit_rate\"] = PRICE[p], PARTS[p][5][0]\n",
" rows.append(f)\n",
"F = pd.concat(rows, ignore_index=True).dropna()\n",
"FE = [\"lag1\", \"lag2\", \"lag12\", \"exp_v\", \"usage\", \"season_c\", \"season_s\", \"trend\", \"price\", \"crit_rate\"]\n",
"Ftr, Fva = F[F[\"month\"] < 42], F[F[\"month\"] >= 42]\n",
"base = Ftr.groupby(\"part\")[\"y\"].mean()\n",
"pb = Fva[\"part\"].map(base).values\n",
"\n",
"\n",
"def members(d_tr, depth, alpha):\n",
" \"\"\"๋ฆฟ์ง(์ ํ ์ถ์ธยท๊ณ์ )๋ก ๋จผ์ ๋ง์ถ๊ณ , ๊ทธ๋๋์ธํธ ๋ถ์คํ
ยท๋๋ค ํฌ๋ ์คํธ๋ ๋ฆฟ์ง ์์ฐจ๋ฅผ ๋ณด์ ํ๋๋ก ํ์ตํ๋ค.\"\"\"\n",
" rd = make_pipeline(StandardScaler(), Ridge(alpha=alpha)).fit(d_tr[FE], d_tr[\"y\"])\n",
" res = d_tr[\"y\"] - rd.predict(d_tr[FE])\n",
" gb = GradientBoostingRegressor(n_estimators=100, max_depth=depth, learning_rate=0.05, subsample=0.8, random_state=0).fit(d_tr[FE], res)\n",
" rf = RandomForestRegressor(n_estimators=40, max_depth=6, min_samples_leaf=5, n_jobs=-1, random_state=0).fit(d_tr[FE], res)\n",
" return [rd, gb, rf]\n",
"\n",
"\n",
"def blend(ms, d, w):\n",
" return ms[0].predict(d[FE]) + w[0] * ms[1].predict(d[FE]) + w[1] * ms[2].predict(d[FE])\n",
"\n",
"\n",
"GRID_W = [(a / 2, b / 2) for a in range(3) for b in range(3)] # ์์ฐจ ๋ณด์ ๊ฐ์ค์น ๊ฒฉ์(GBR, RF) 0, 0.5, 1\n",
"best, solo = (1e9,), {}\n",
"folds = [(Ftr[Ftr[\"month\"] < c], Ftr[(Ftr[\"month\"] >= c) & (Ftr[\"month\"] < c + 6)]) for c in (24, 30, 36)] # ํ์ฅ ์๋ ๋ด๋ถ ๊ฒ์ฆ 3ํด๋\n",
"for dp in (2, 3):\n",
" for al in (1, 10):\n",
" fitted = [(members(a_, dp, al), v) for a_, v in folds]\n",
" for w in GRID_W:\n",
" e_ = np.mean([mean_absolute_error(v[\"y\"], blend(ms, v, w)) for ms, v in fitted])\n",
" solo[(dp, al, w)] = e_\n",
" if e_ < best[0]:\n",
" best = (e_, dp, al, w)\n",
"_, dp, al, wt = best\n",
"if solo[(dp, al, (0.0, 0.0))] * 0.98 < best[0]: # ์์ฐจ ๋ณด์ ์ ๋ด๋ถ ๊ฒ์ฆ ๊ฐ์ ์ด 2% ๋ฏธ๋ง์ด๋ฉด ๋จ์ํ ๋ชจ๋ธ(๋ฆฟ์ง)์ ์ฑํ(๊ณผ์ ํฉ ๋ฐฉ์ง)\n",
" wt = (0.0, 0.0)\n",
"models = members(Ftr, dp, al)\n",
"pe = blend(models, Fva, wt)\n",
"ev = np.array([(int(a), int(b)) in set(events) for a, b in zip(Fva[\"part\"], Fva[\"month\"])]) # ์ ์กฐ LOT ๋ถ๋ ๋ฑ ๊ธ์ฆ ์ด๋ฒคํธ ์(๋ณ๋ ๊ฒฝ๋ณด ๋์)\n",
"keep = ~ev\n",
"y = Fva[\"y\"].values\n",
"met = lambda p, m=keep: (mean_absolute_error(y[m], p[m]), mean_squared_error(y[m], p[m]) ** 0.5, r2_score(y[m], p[m]))\n",
"mb, me = met(pb), met(pe)\n",
"print(f\"\\n[์ทจ์ฝ์ฑ ์ง์ ์์ธก] ํ์ต ์ 12~41, Hold-out ์ 42~47 ({len(Fva)}๊ฑด ์ค ๊ธ์ฆ ์ด๋ฒคํธ ์ {int(ev.sum())}๊ฑด ์ ์ธ ํ๊ฐ), ์ ํ GBR depth={dp}, ridge alpha={al}, ์์ฐจ ๋ณด์ ๊ฐ์ค์น(GBR, RF)={wt}\")\n",
"print(\"๋ชจ๋ธ | MAE | RMSE | R2\")\n",
"print(f\" ๊ธฐ์ค(ํ์ต ๊ธฐ๊ฐ ๋ถ์๋ณ ํ๊ท ) {mb[0]:7.1f} {mb[1]:7.1f} {mb[2]:.3f}\")\n",
"print(f\" ์ ์๊ฐ(์ฐธ๊ณ ) {met(Fva['lag1'].values)[0]:7.1f} {met(Fva['lag1'].values)[1]:7.1f} {met(Fva['lag1'].values)[2]:.3f}\")\n",
"x = met(models[0].predict(Fva[FE]))\n",
"print(f\" ๋ฆฟ์ง ๋จ๋
{x[0]:7.1f} {x[1]:7.1f} {x[2]:.3f}\")\n",
"print(f\" ๊ณ ๋ํ(์ ํ ๋ชจ๋ธ) {me[0]:7.1f} {me[1]:7.1f} {me[2]:.3f}\")\n",
"imp_mae, imp_rmse = 1 - me[0] / mb[0], 1 - me[1] / mb[1]\n",
"print(f\" (์ฐธ๊ณ ) ์ด๋ฒคํธ ์ ํฌํจ ์ ์ฒด: ๊ธฐ์ค MAE {met(pb, np.ones(len(y), bool))[0]:.1f} -> ์์๋ธ {met(pe, np.ones(len(y), bool))[0]:.1f}, R2 {met(pe, np.ones(len(y), bool))[2]:.3f}\")\n",
"print(f\"๊ฐ์ ์จ MAE {imp_mae:.1%}, RMSE {imp_rmse:.1%} (๋ชฉํ 20% ์ด์: {'๋ฌ์ฑ' if min(imp_mae, imp_rmse) >= 0.2 else '๋ฏธ๋ฌ'}), R2 {me[2]:.3f} (๋ชฉํ 0.85 ์ด์: {'๋ฌ์ฑ' if me[2] >= 0.85 else '๋ฏธ๋ฌ'})\")\n",
"\n",
"# 3) ์ข
ํฉ ํ์ง ์ทจ์ฝ์ฑ ์ง์ ์์(๋ค์ ๋ฌ ์์ธก ์์ 5 ๋ถ์) ์นด๋\n",
"last = Fva[Fva[\"month\"] == 47].assign(pred=pe[Fva[\"month\"].values == 47]).sort_values(\"pred\", ascending=False)\n",
"print(\"\\n[์ทจ์ฝ์ฑ ์ง์ ์นด๋] ๋ง์ง๋ง ๋ฌ ์์ธก ์์ 5 ๋ถ์ | ์์ธก | ์ค์ \")\n",
"for r in last.head(5).itertuples():\n",
" print(f\" {names[r.part]:10s} ์์ธก {r.pred:6.0f} ์ค์ {r.y:6.0f} {'(์ฐ์ ์ ๊ฒ ๊ถ๊ณ )' if r.pred >= last['pred'].median() * 1.5 else ''}\")\n",
"print(f\"์์ธก ์์ 5์ ์ค์ ์์ 5 ์ผ์น {len(set(last.head(5)['part']) & set(last.sort_values('y', ascending=False).head(5)['part']))}/5\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
05 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ต์ก์ฉ๊ฐ์์๋ฃ] QDR 51700๊ฑด (ํ์ต 36๊ฐ์ 36834๊ฑด, ๊ฒ์ฆ 12๊ฐ์ 14866๊ฑด)\n",
"์น๋ช
๋ ๋ถ๋ฅ ์ ํ๋: ํค์๋ ๊ท์น 72.0% -> ํ
์คํธ ๋ชจ๋ธ 87.2%, Critical ์ฌํ์จ 34.7% -> 69.0%\n",
"[์ ๋ขฐ๋ ๋ณด์ ] ๊ตฌ๊ฐ | ๊ฑด์ | ํ๊ท ์ ๋ขฐ๋ | ์ค์ ์ ํ๋\n",
" 0.00~0.60 2394 0.48 0.46\n",
" 0.60~0.80 1827 0.68 0.68\n",
" 0.80~0.95 264 0.86 0.89\n",
" 0.95~1.00 10381 1.00 1.00\n",
"\n",
"[์ทจ์ฝ์ฑ ์ง์ ์์ธก] ํ์ต ์ 12~41, Hold-out ์ 42~47 (72๊ฑด ์ค ๊ธ์ฆ ์ด๋ฒคํธ ์ 1๊ฑด ์ ์ธ ํ๊ฐ), ์ ํ GBR depth=3, ridge alpha=10, ์์ฐจ ๋ณด์ ๊ฐ์ค์น(GBR, RF)=(0.0, 0.0)\n",
"๋ชจ๋ธ | MAE | RMSE | R2\n",
" ๊ธฐ์ค(ํ์ต ๊ธฐ๊ฐ ๋ถ์๋ณ ํ๊ท ) 63.5 88.5 0.582\n",
" ์ ์๊ฐ(์ฐธ๊ณ ) 51.1 74.9 0.700\n",
" ๋ฆฟ์ง ๋จ๋
39.3 51.8 0.857\n",
" ๊ณ ๋ํ(์ ํ ๋ชจ๋ธ) 39.3 51.8 0.857\n",
" (์ฐธ๊ณ ) ์ด๋ฒคํธ ์ ํฌํจ ์ ์ฒด: ๊ธฐ์ค MAE 68.6 -> ์์๋ธ 43.9, R2 0.777\n",
"๊ฐ์ ์จ MAE 38.0%, RMSE 41.4% (๋ชฉํ 20% ์ด์: ๋ฌ์ฑ), R2 0.857 (๋ชฉํ 0.85 ์ด์: ๋ฌ์ฑ)\n",
"\n",
"[์ทจ์ฝ์ฑ ์ง์ ์นด๋] ๋ง์ง๋ง ๋ฌ ์์ธก ์์ 5 ๋ถ์ | ์์ธก | ์ค์ \n",
" ์ ๋ ์ฅ์น ์์ธก 409 ์ค์ 412 (์ฐ์ ์ ๊ฒ ๊ถ๊ณ )\n",
" ์ฐจ์ฒด ์ฉ์ ๋ถ ์์ธก 315 ์ค์ 171 \n",
" ์์ง ์ฐ๋ฃ๊ณํต ์์ธก 304 ์ค์ 237 \n",
" ๋ณ์๊ธฐ ์์ธก 298 ์ค์ 303 \n",
" ์กฐํฅ ์ฅ์น ์์ธก 272 ์ค์ 314 \n",
"์์ธก ์์ 5์ ์ค์ ์์ 5 ์ผ์น 3/5\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
06. ์ผ์ QDR ๋ฐ์ ํํฉ ๋ฐ ์ฅ๋น ๋ถ์๋ณ ๊ฒฐํจ ์ง์ค๋ ํํธ๋งต ๋์๋ณด๋ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์ผ์ QDR ๋ฐ์ ํํฉ ๋ฐ ์ฅ๋น ๋ถ์๋ณ ๊ฒฐํจ ํํธ๋งต ๋์๋ณด๋๋ฅผ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ผ์ QDR ๋ฐ์ ํํฉ๊ณผ ๋ถ์๋ณ ๊ฒฐํจ ์ง์ค๋๋ฅผ ํํธ๋งตยท๊ฒฝ๋ณด๋ก ๊ตฌ์ฑํ๊ณ , ์์ธก ๊ฑด๋ง๋ค ๊ธฐ์ฌ๋ ์์ 3๊ฐ ์ธ์์ ์์ฐ์ด ์์ฝ์ ์๋ ์ฐ์ถํ๋ ์ค๋ช
๊ฐ๋ฅ์ฑ(XAI) ๋ชจ๋์ ๊ตฌํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์น๋ช
๋ ๋ถ๋ฅ ๋ชจ๋ธ์ ์์ ๋จ์ดยท๋ถ์ยท๊ฒฐํจ ์ ํ ๊ธฐ์ฌ๋๋ฅผ ๊ณ์ฐํด ๊ฑด๋ง๋ค ์์ 3๊ฐ ๊ทผ๊ฑฐ๋ฅผ ๋ฝ๊ณ , 1์์ ๊ทผ๊ฑฐ๋ฅผ ์ ๊ฑฐํ์ ๋ ํ๋ฅ ์ด ๋ด๋ ค๊ฐ๋์ง๋ก ์ ํจ์ฑ์ ์ ๊ฒ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/explainability/qlt05_xai_explainer.py ๋ฐ XAI ๋ฆฌํฌํธ` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ตฐ์ ์ฅ๊ต/๊ตฐ๋ฌด์์ด ์ ๋ขฐํ๊ณ ์กฐ์นํ ์ ์๋ ์ค๋ช
์ ๋ช
ํ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์ฃผ์ ์์ธก ๊ฑด๋น ๊ธฐ์ฌ๋ ์์ 3๊ฐ ์ธ์ ์๋ ์ฐ์ถ๋ฅ 100%\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: SHAP summary plot ๋ฐ waterfall plot ์์ฑ ํ์ดํ๋ผ์ธ ์์ฑ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ์ค๋ฌด์ ๋์ ์ค๋ช
๊ทผ๊ฑฐ ์ ์ฉ์ฑ ํ๊ฐ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์ผ์ QDR ๋ฐ์ ํํฉ์ ๋ถ์๋ณยท์๋ณ ํํธ๋งต๊ณผ ์ง์ค๋ ๊ฒฝ๋ณด๋ก ๊ตฌ์ฑํ๊ณ , ์น๋ช
๋ ๋ถ๋ฅ ๋ชจ๋ธ์ ์์ธก๋ง๋ค ๊ธฐ์ฌ๋ ์์ 3๊ฐ ์ธ์(์์ ๋จ์ดยท๋ถ์ยท๊ฒฐํจ ์ ํ)๋ฅผ ์๋ ์ฐ์ถํ๋ฉฐ(๊ทผ๊ฑฐ ์ ํจ์ฑ ๊ฒ์ฆ), ์ ๋นยทํ์ง ๋ด๋น์๊ฐ ์ฝ์ ์ ์๋ ์์ฐ์ด ์์ฝ ๋ฌธ์ฅ์ ์์ฑํ๋ ์ค๋ช
๊ฐ๋ฅ์ฑ(XAI) ๋ชจ๋์ ๊ตฌํํ๋ค.\"\"\"\n",
"import re\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"import scipy.sparse as sp\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"from sklearn.linear_model import LogisticRegression\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR) ์์ฑ๊ธฐ (์ค์ ๋ถ๋ยท์ฅ๋นยท์
์ฒดยท์ ๋น ์ค์ ์์)\n",
"# ๊ฐ์ ์ฅ๋น ๋ถ์ 12์ข
, ๊ฒฐํจ ์ ํ 8์ข
, ์น๋ช
๋(Critical/Major/Minor), ์์ธ 5์ข
, ๊ฐ์ ์ธ์ฃผ์ ๋น ์
์ฒด 4๊ณณ + ๋ถ๋์ ๋น.\n",
"# QDR 1๊ฑด๋ง๋ค ํ๊ตญ์ด ์์ ํ
์คํธ๋ฅผ ๋ง๋ค๊ณ , ์๋ณ ๊ฑด์๋ ๊ณ์ ยท์ถ์ธยทํ๋ จ ๊ฐ๋(usage)ยท์ผ๋ถ ์ ์กฐ ๋ถ๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ๋ฅธ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"PARTS = [ # (์ด๋ฆ, ๋ณ์นญ, ์ ๊ธฐ์ค ๊ฑด์, ๊ณ์ ์งํญ, ์ ์ ์, (Critical, Major ๋น์จ), ๊ฐ์ ๋จ๊ฐ์ง์, ์ ์กฐ์ฌ, ์ฃผ์ ๊ฒฐํจ ๊ฐ์ค์น)\n",
" (\"์์ง ์ฐ๋ฃ๊ณํต\", [\"์ฐ๋ฃ๊ณํต\", \"์ฐ๋ฃ ๋ผ์ธ\", \"์ฐ๋ฃ ํํ\"], 26, 0.15, 7, (0.10, 0.35), 6.0, \"MK1\", \"๋์ :4 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:2 ์ค์๋:2\"),\n",
" (\"๋ณ์๊ธฐ\", [\"๋ณ์๊ธฐ\", \"T/M\", \"๋ฏธ์
\"], 22, 0.10, 8, (0.12, 0.38), 9.0, \"MK1\", \"๋ง๋ชจ:4 ๋์ :2 ๊ณผ์ด:3 ์ค์๋:1\"),\n",
" (\"์ ๋ ์ฅ์น\", [\"์ ๋ ์ฅ์น\", \"๋ธ๋ ์ดํฌ\", \"์ ๋ ๋ผ์ธ\"], 20, 0.20, 12, (0.26, 0.40), 5.0, \"MK2\", \"๋ง๋ชจ:4 ๊ท ์ด:2 ๋์ :2 ์ฒด๊ฒฐ๋ถ๋:2\"),\n",
" (\"์กฐํฅ ์ฅ์น\", [\"์กฐํฅ ์ฅ์น\", \"ํธ๋ค ์ฐ๋๋ถ\", \"์กฐํฅ ๋ก๋\"], 14, 0.10, 6, (0.30, 0.40), 5.0, \"MK2\", \"์ฒด๊ฒฐ๋ถ๋:4 ๊ท ์ด:2 ๋ง๋ชจ:3 ์ค์๋:1\"),\n",
" (\"๋๊ฐ ๊ณํต\", [\"๋๊ฐ ๊ณํต\", \"๋ผ๋์์ดํฐ\", \"๋๊ฐ์ ๋ผ์ธ\"], 24, 0.30, 7, (0.05, 0.30), 3.0, \"MK3\", \"๋์ :4 ๊ณผ์ด:4 ๋ถ์:2\"),\n",
" (\"์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ\", [\"๋ฐฐํฐ๋ฆฌ\", \"์ ๊ธฐ ๊ณํต\", \"๋ฐ์ ๊ธฐ\"], 28, 0.35, 1, (0.04, 0.28), 2.5, \"MK3\", \"์ ์ด๋ถ๋:4 ๋ถ์:2 ๊ณผ์ด:2 ์ค์๋:2\"),\n",
" (\"ํต์ ๋ชจ๋\", [\"ํต์ ๋ชจ๋\", \"๋ฌด์ ๊ธฐ ์ฐ๋๋ถ\", \"์ํ
๋ ์ ์๋ถ\"], 16, 0.05, 4, (0.03, 0.25), 4.0, \"MK4\", \"์ ์ด๋ถ๋:5 ์ค์๋:4 ๋ถ์:1\"),\n",
" (\"์ ์ ๊ณํต\", [\"์ ์ ๊ณํต\", \"์ ์ ํธ์ค\", \"์ ์ ์ค๋ฆฐ๋\"], 18, 0.10, 9, (0.10, 0.35), 4.5, \"MK2\", \"๋์ :5 ๊ท ์ด:2 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"ํ๊ฐ ์ฅ์น\", [\"ํ๊ฐ ์ฅ์น\", \"์์คํ์
\", \"์์ถฉ๊ธฐ\"], 15, 0.08, 5, (0.06, 0.30), 3.5, \"MK4\", \"๋ง๋ชจ:4 ๊ท ์ด:3 ์ฒด๊ฒฐ๋ถ๋:2 ๋ถ์:1\"),\n",
" (\"์ฐจ์ฒด ์ฉ์ ๋ถ\", [\"์ฐจ์ฒด ์ฉ์ ๋ถ\", \"ํ๋ ์ ์ฉ์ ๋ถ\", \"์ฐจ์ฒด ์ด์๋ถ\"], 10, 0.05, 10, (0.28, 0.40), 7.0, \"MK1\", \"๊ท ์ด:6 ๋ถ์:3 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ดํยท์กฐ์ค ์ฅ๋น\", [\"๊ดํ ์ฅ๋น\", \"์กฐ์ค๊ฒฝ\", \"๊ด์ธก ์ฅ๋น\"], 8, 0.05, 3, (0.03, 0.22), 8.0, \"MK4\", \"์ค์๋:4 ์ ์ด๋ถ๋:3 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ณต๊ธฐ ์ ํ ํํฐ\", [\"๊ณต๊ธฐ ํํฐ\", \"ํก๊ธฐ ํํฐ\", \"์ ํ ์ฅ์น\"], 12, 0.25, 4, (0.01, 0.12), 1.0, \"MK3\", \"๋ง๋ชจ:5 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:3\")]\n",
"NP = len(PARTS)\n",
"DEFECTS = { # ๊ฒฐํจ ์ ํ: ์ฆ์ ํํ(๋์์ด)\n",
" \"๊ท ์ด\": [\"๊ท ์ด์ด ๋ฐ๊ฒฌ๋จ\", \"ํฌ๋์ด ํ์ธ๋จ\", \"๊ฐ๋ผ์ง์ด ์์\"], \"๋์ \": [\"๋์ ๊ฐ ๋ฐ์ํจ\", \"์ค์ผ์ด ์์ด ๋์ด\", \"๋์ค ํ์ ์ด ์์\"],\n",
" \"๋ง๋ชจ\": [\"๋ง๋ชจ๊ฐ ์ฌํจ\", \"๊ณผ๋ํ ๋ง๋ชจ๊ฐ ํ์ธ๋จ\", \"๋ณ์ ํ์ ์ด ์์\"], \"๋ถ์\": [\"๋ถ์์ด ์งํ๋จ\", \"๋
น์ด ๋ฐ์ํจ\", \"์ฐํ ํ์ ์ด ์์\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋์ด ๋ฐ์ํจ\", \"์ปค๋ฅํฐ ๋จ์ ์ด ์์ฌ๋จ\", \"์ ํธ๊ฐ ๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ ํ๋ฆผ์ด ํ์ธ๋จ\", \"์ฒด๊ฒฐ ์ํ๊ฐ ๋ถ๋ํจ\", \"๊ณ ์ ๋ถ๊ฐ ํ๊ฑฐ์\"],\n",
" \"๊ณผ์ด\": [\"๊ณผ์ด ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํจ\", \"์จ๋๊ฐ ๊ณผ๋ํ๊ฒ ์์นํจ\", \"์ด์ด ์ฌํ๊ฒ ๋จ\"], \"์ค์๋\": [\"์ค์๋์ด ๋ฐ๋ณต๋จ\", \"์๋์ด ๋ถ์์ ํจ\", \"๊ฐํ์ ์ผ๋ก ๋ฉ์ถค\"]}\n",
"DNAMES = list(DEFECTS)\n",
"SEV = [\"Minor\", \"Major\", \"Critical\"]\n",
"SEV_CUE = {0: [\"๊ฒฝ๋ฏธํ ์์ค์\", \"์ด์ฉ์๋ ์ง์ฅ์ด ์์\", \"์ก์ ์ ๊ฒ์ ์ฌ์ํ ์ด์์\", \"๊ธฐ๋ฅ์๋ ์ํฅ์ด ์์\"],\n",
" 1: [\"์ฑ๋ฅ ์ ํ๊ฐ ์์\", \"์กฐ์ํ ์ ๋น๊ฐ ํ์ํจ\", \"์ถ๋ ฅ์ด ๋จ์ด์ง๋ ์ํ์\", \"๊ธฐ๋ฅ์ด ๋ถ๋ถ์ ์ผ๋ก ์ ํ๋จ\"],\n",
" 2: [\"์ฆ์ ์ด์ฉ ์ค์งํจ\", \"์์ ์ ์ํ์ด ์์ด ์ฌ์ฉ ๋ถ๊ฐ\", \"์๋ ์ ์ง๋ก ์๋ฌด ์ํ์ด ์ด๋ ค์\", \"์ธ๋ช
์์ ์ํ์ด ์ฐ๋ ค๋จ\"]}\n",
"CAUSES = [\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\", \"์ด์ฉ ๋ถ์ฃผ์\", \"์ค๊ณ ์ทจ์ฝ\", \"์์ฐ ๋
ธํ\"]\n",
"CAUSE_CUE = {\n",
" \"์ ์กฐ ๋ถ๋\": [\"์ ํ ์ธ์ ์งํ ๋ฐ์ํจ\", \"์ถ๊ณ ์ํ์์ ์ด๋ฏธ ์ด์์ด ์์์\", \"์ธ์ ํ ์ผ๋ง ์ง๋์ง ์์ ๋ฐ์ํจ\"],\n",
" \"์ ๋น ํ์ง\": [\"์ธ์ฃผ ์ ๋น ํ ์ผ๋ง ๋์ง ์์ ์ฌ๋ฐํจ\", \"์ ๋น ํ ์ฌ์กฐ๋ฆฝ ์ํ๊ฐ ๋ถ๋ํจ\", \"์ ๋น ์งํ ๊ฐ์ ๋ถ์์์ ๋ค์ ๋ฐ์ํจ\"],\n",
" \"์ด์ฉ ๋ถ์ฃผ์\": [\"ํ์ง ๊ธฐ๋ ์ค ์ถฉ๊ฒฉ ์ดํ ๋ฐ์ํจ\", \"๊ณผ๋ถํ ์ด์ฉ ํ ๋ฐ์ํจ\", \"์ ๊ฒ ์์ด ์ฐ์ ์ด์ฉํ ๋ค ๋ฐ์ํจ\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์์ด ๋ค๋ฅธ ์ฐจ๋์์๋ ๋ฐ๋ณต๋จ\", \"๊ตฌ์กฐ์ ์ผ๋ก ์ทจ์ฝํ ๋ถ์๋ก ๋ณด์\", \"์ฌ๋ฌ ๋ถ๋์์ ๊ฐ์ ํํ๋ก ๋ณด๊ณ ๋จ\"],\n",
" \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ์ด ๋๋ํจ\", \"์ฅ๊ธฐ๊ฐ ์ฌ์ฉ์ผ๋ก ๋
ธํ๋จ\", \"์๋ช
์ฃผ๊ธฐ ๋ง๊ธฐ์ ๋ฐ์ํจ\"]}\n",
"VENDORS = [\"์ธ์ฃผA\", \"์ธ์ฃผB\", \"์ธ์ฃผC\", \"์ธ์ฃผD\", \"๋ถ๋์ ๋น\"]\n",
"ACTIONS = [\"์ ๋น์ ์
๊ณ ๋ฅผ ์์ฒญํจ\", \"ํ์ฅ ์์ ์กฐ์น ํ ์ด์ฉ ์ค์\", \"๋ถํ ๊ตํ์ ์์ฒญํจ\", \"์ฌ๊ฒ์ฌ๋ฅผ ์์ฒญํจ\", \"์์ธ ํ์ธ์ด ํ์ํจ\"]\n",
"MAKER_OF = [p[7] for p in PARTS]\n",
"PRICE = np.array([p[6] for p in PARTS])\n",
"\n",
"\n",
"def usage_index(months, seed=0):\n",
" \"\"\"์๋ณ ํ๋ จ ๊ฐ๋(๋ฏธ๋ฆฌ ์ ์ ์๋ ๊ณํ ๊ฐ): ์ฐ 1ํ ์ ์ ์ ๊ฐ๋ ๊ณ์ + ์์ ์ก์\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 6] ์ผ์ QDR ๋ฐ์ ํํฉ ๋ฐ ์ฅ๋น ๋ถ์๋ณ ๊ฒฐํจ ์ง์ค๋ ํํธ๋งต ๋์๋ณด๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_qdr(months=36, seed=1, scale=1.0, surge=True):\n",
" \"\"\"๋ฐํ: QDR DataFrame(๊ฑด๋น 1ํ), ์๋ณ usage, ๊ธ์ฆ ์ด๋ฒคํธ ๋ชฉ๋ก\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" use = usage_index(months, seed)\n",
" events = []\n",
" if surge:\n",
" for _ in range(max(2, months // 12 * 2)):\n",
" events.append((int(rng.integers(NP)), int(rng.integers(6, months))))\n",
" rows = []\n",
" wdef = [np.array([float(x.split(\":\")[1]) for x in p[8].split()]) for p in PARTS]\n",
" ddef = [[x.split(\":\")[0] for x in p[8].split()] for p in PARTS]\n",
" n = 0\n",
" for m in range(months):\n",
" for pi, p in enumerate(PARTS):\n",
" lam = p[2] * scale * (1 + 0.010 * m) * use[m] * (1 + p[3] * np.cos(2 * np.pi * (m % 12 - p[4]) / 12))\n",
" ev = (pi, m) in events\n",
" k = rng.poisson(lam * (2.6 if ev else 1.0))\n",
" for _ in range(k):\n",
" rows.append((n, m, pi, ev)); n += 1\n",
" N = len(rows)\n",
" A = np.array(rows)\n",
" month, part, ev = A[:, 1], A[:, 2], A[:, 3].astype(bool)\n",
" defect = np.array([rng.choice(ddef[i], p=wdef[i] / wdef[i].sum()) for i in part])\n",
" cr = np.array([PARTS[i][5][0] for i in part]); ma = np.array([PARTS[i][5][1] for i in part])\n",
" u = rng.random(N)\n",
" sev = np.where(u < cr, 2, np.where(u < cr + ma, 1, 0))\n",
" vendor = rng.choice(VENDORS, N, p=[0.25, 0.2, 0.2, 0.15, 0.2])\n",
" # ์์ธ: ๊ธฐ๋ณธ ๋น์จ์ ๋ถ์(์ ์กฐ์ฌ)ยท์
์ฒดยท์ด๋ฒคํธ ํจ๊ณผ๋ฅผ ๊ณฑํ๋ค\n",
" base = np.array([0.22, 0.20, 0.20, 0.16, 0.22])\n",
" P = np.tile(base, (N, 1))\n",
" P[vendor == \"์ธ์ฃผB\", 1] *= 2.6\n",
" P[ev, 0] *= 5.0\n",
" design = np.isin(part, [2, 3, 7]); P[design, 3] *= 2.2\n",
" P[np.isin(part, [4, 5, 11]), 0] *= 2.0 # ์ ์กฐ์ฌ MK3 ๋ฉํ ๋ถ์๋ ์ ์กฐ ๋ถ๋ ๋น์ค์ด ๋๋ค\n",
" P[defect == \"๋ง๋ชจ\", 4] *= 1.6; P[defect == \"๋์ \", 4] *= 1.3\n",
" P /= P.sum(axis=1, keepdims=True)\n",
" cause = np.array([CAUSES[rng.choice(5, p=P[i])] for i in range(N)])\n",
" since = np.where(cause == \"์ ๋น ํ์ง\", rng.exponential(35, N), rng.exponential(160, N)).clip(1, 720).round().astype(int)\n",
" wait = (rng.lognormal(1.3, 0.5, N) * np.where(vendor == \"์ธ์ฃผB\", 1.7, np.where(vendor == \"๋ถ๋์ ๋น\", 0.6, 1.0)) * (0.7 + 0.5 * use[month])).round(1)\n",
" reins = rng.random(N) < np.where(cause == \"์ ๋น ํ์ง\", 0.6, 0.08)\n",
" texts = []\n",
" for i in range(N):\n",
" pt = PARTS[part[i]]\n",
" cl = [f\"{rng.choice(pt[1])}์์ {rng.choice(DEFECTS[defect[i]])}\"]\n",
" if rng.random() < 0.7: cl.append(rng.choice(SEV_CUE[sev[i]]))\n",
" if rng.random() < 0.65: # ์์ธ ๋จ์: 8%๋ ์ค์ ์์ธ๊ณผ ๋ค๋ฅธ ๋จ์๊ฐ ์ ํ๋ค(๋ณด๊ณ ์์ ์ถ์ )\n",
" cc = cause[i] if rng.random() > 0.08 else rng.choice(CAUSES)\n",
" cl.append(rng.choice(CAUSE_CUE[cc]))\n",
" cl.append(rng.choice(ACTIONS))\n",
" if rng.random() < 0.5: cl.insert(0, f\"VU-{rng.integers(1, 31):02d} ์์\")\n",
" texts.append(\". \".join(cl) + \".\")\n",
" df = pd.DataFrame({\"qdr_id\": [f\"Q{seed}{i:06d}\" for i in range(N)], \"month\": month, \"unit\": [f\"VU-{x:02d}\" for x in rng.integers(1, 31, N)],\n",
" \"part\": part, \"defect\": defect, \"sev\": sev, \"cause\": cause, \"vendor\": vendor, \"maker\": [MAKER_OF[i] for i in part],\n",
" \"since_maint\": since, \"inspect_wait\": wait, \"reinspect\": reins, \"event\": ev, \"text\": texts})\n",
" df[\"cost\"] = PRICE[df[\"part\"]] * np.array([1.0, 2.0, 4.0])[df[\"sev\"]] # [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์๋ฆฌยท์กฐ์น ๋น์ฉ์ง์\n",
" return df, use, events\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# ๊ณตํต ํ
์คํธ ์กฐ๊ฐ: ์ ๊ทํ, ๊ฐ์ด ํ ํฐํ(์กฐ์ฌ ์ ๊ฑฐ), ๋ถ์ยท๊ฒฐํจยท์น๋ช
๋ยท์์ธ ํค์๋ ๋ผ๋ฒจ๋ง\n",
"# ---------------------------------------------------------------------------\n",
"TYPO = {\"๋์ณ\": \"๋์ \", \"๊ท ๋ ฌ\": \"๊ท ์ด\", \"์ ์ถ ๋ถ๋\": \"์ ์ด ๋ถ๋\", \"๋ถ์ฉ\": \"๋ถ์\", \"๋ง๋ฌ\": \"๋ง๋ชจ\", \"๊ณผ๋ ฌ\": \"๊ณผ์ด\", \"์ค์๋ฅ\": \"์ค์๋\", \"์ฌ๋\": \"์ ๋\"}\n",
"ABBR = {\"T/M\": \"๋ณ์๊ธฐ\", \"BAT\": \"๋ฐฐํฐ๋ฆฌ\"}\n",
"PART_KW = sorted([(a, i) for i, p in enumerate(PARTS) for a in p[1]], key=lambda x: -len(x[0]))\n",
"DEF_KW = {\"๊ท ์ด\": [\"๊ท ์ด\", \"ํฌ๋\", \"๊ฐ๋ผ\"], \"๋์ \": [\"๋์ \", \"์์ด\", \"๋์ค\"], \"๋ง๋ชจ\": [\"๋ง๋ชจ\", \"๋ณ์\"], \"๋ถ์\": [\"๋ถ์\", \"๋
น์ด\", \"์ฐํ\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋\", \"๋จ์ \", \"๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ\", \"์ฒด๊ฒฐ\", \"ํ๊ฑฐ\"], \"๊ณผ์ด\": [\"๊ณผ์ด\", \"์จ๋\", \"์ด์ด\"], \"์ค์๋\": [\"์ค์๋\", \"๋ถ์์ \", \"๋ฉ์ถค\"]}\n",
"SEV_KW = [(2, [\"์ฆ์ ์ด์ฉ ์ค์ง\", \"์ฌ์ฉ ๋ถ๊ฐ\", \"์ํ\"]), (1, [\"์ฑ๋ฅ ์ ํ\", \"์กฐ์ํ\"]), (0, [\"๊ฒฝ๋ฏธ\", \"์ง์ฅ์ด ์\"])]\n",
"CAUSE_KW = {\"์ ์กฐ ๋ถ๋\": [\"์ ํ\", \"์ถ๊ณ \", \"์ธ์\"], \"์ ๋น ํ์ง\": [\"์ ๋น ํ\", \"์ฌ์กฐ๋ฆฝ\", \"์ ๋น ์งํ\"], \"์ด์ฉ ๋ถ์ฃผ์\": [\"์ถฉ๊ฒฉ\", \"๊ณผ๋ถํ\", \"์ ๊ฒ ์์ด\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์\", \"๊ตฌ์กฐ์ \", \"์ฌ๋ฌ ๋ถ๋\"], \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ\", \"๋
ธํ\", \"์๋ช
\"]}\n",
"PARTICLES = (\"์์\", \"์ผ๋ก\", \"์ด\", \"๊ฐ\", \"์\", \"๋\", \"์\", \"๋ฅผ\", \"์\", \"์\", \"๋ก\", \"์\", \"๊ณผ\", \"๋\", \"ํจ\", \"์\", \"๋จ\")\n",
"\n",
"\n",
"def norm(t):\n",
" \"\"\"์ ๊ทํ: ์ ๊ฐ->๋ฐ๊ฐ, ํน์๋ฌธ์ยท์ค๋ณต ๊ณต๋ฐฑ ์ ๊ฑฐ, ์ฝ์ด ํ์ฅ, ์คํ ๊ต์ \"\"\"\n",
" # [TODO : ํ์ ์ค์ต 6] ์ผ์ QDR ๋ฐ์ ํํฉ ๋ฐ ์ฅ๋น ๋ถ์๋ณ ๊ฒฐํจ ์ง์ค๋ ํํธ๋งต ๋์๋ณด๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tok(t):\n",
" out = []\n",
" for w in re.split(r\"[\\s.,]+\", norm(t)):\n",
" if not w or re.fullmatch(r\"[\\d\\-A-Za-z]+\", w):\n",
" continue\n",
" for p in PARTICLES:\n",
" if len(w) > len(p) + 1 and w.endswith(p):\n",
" w = w[:-len(p)]\n",
" break\n",
" out.append(w)\n",
" return out\n",
"\n",
"\n",
"def label_part(t):\n",
" for a, i in PART_KW:\n",
" if a in t:\n",
" return i\n",
" return -1\n",
"\n",
"\n",
"def label_defect(t):\n",
" for d, ks in DEF_KW.items():\n",
" if any(k in t for k in ks):\n",
" return d\n",
" return \"๋ฏธ๋ถ๋ฅ\"\n",
"\n",
"\n",
"def label_sev(t):\n",
" for s, ks in SEV_KW:\n",
" if any(k in t for k in ks):\n",
" return s\n",
" return -1\n",
"\n",
"\n",
"def label_cause(t):\n",
" for c, ks in CAUSE_KW.items():\n",
" if any(k in t for k in ks):\n",
" return c\n",
" return \"๋ฏธ์\"\n",
"\n",
"df, use, events = make_qdr(36, seed=1, scale=2.0)\n",
"names = [p[0] for p in PARTS]\n",
"tr, va = df[df[\"month\"] < 24].reset_index(drop=True), df[df[\"month\"] >= 24].reset_index(drop=True)\n",
"vec = TfidfVectorizer(tokenizer=tok, lowercase=False, token_pattern=None, min_df=5)\n",
"oh = lambda d: sp.csr_matrix(np.hstack([np.eye(NP)[d[\"part\"]], pd.get_dummies(d[\"defect\"]).reindex(columns=DNAMES, fill_value=0).values.astype(float)]))\n",
"Xtr, Xva = sp.hstack([vec.fit_transform(tr[\"text\"]), oh(tr)]).tocsr(), sp.hstack([vec.transform(va[\"text\"]), oh(va)]).tocsr()\n",
"fnames = list(vec.get_feature_names_out()) + [f\"๋ถ์:{n}\" for n in names] + [f\"๊ฒฐํจ:{d}\" for d in DNAMES]\n",
"clf = LogisticRegression(C=5, max_iter=500).fit(Xtr, tr[\"sev\"])\n",
"P = clf.predict_proba(Xva); pred = P.argmax(axis=1)\n",
"print(f\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] QDR {len(df)}๊ฑด, ๊ฒ์ฆ {len(va)}๊ฑด ์น๋ช
๋ ๋ถ๋ฅ ์ ํ๋ {(pred == va['sev'].values).mean():.1%}\")\n",
"\n",
"# 1) ๊ฑด๋ง๋ค ๊ธฐ์ฌ๋ ์์ 3๊ฐ ์ธ์ (๊ธฐ์ฌ๋ = ํน์ฑ๊ฐ x ์์ธก ํด๋์ค ๊ณ์)\n",
"co = clf.coef_\n",
"top3 = []\n",
"for i in range(Xva.shape[0]):\n",
" row = Xva.getrow(i)\n",
" contrib = row.data * co[pred[i], row.indices]\n",
" order = np.argsort(-contrib)[:3]\n",
" top3.append([(row.indices[j], contrib[j]) for j in order])\n",
"ok = sum(len(t) == 3 for t in top3)\n",
"print(f\"์์ธก {len(top3)}๊ฑด ์ค ๊ธฐ์ฌ๋ ์์ 3๊ฐ ์ธ์ ์ฐ์ถ {ok}๊ฑด (์ฐ์ถ๋ฅ {ok / len(top3):.0%}, ๋ชฉํ 100%: {'๋ฌ์ฑ' if ok == len(top3) else '๋ฏธ๋ฌ'})\")\n",
"\n",
"# 2) ๊ทผ๊ฑฐ ์ ํจ์ฑ: 1์์ ์ธ์๋ฅผ ์ ๊ฑฐํ๋ฉด ์์ธก ํด๋์ค ํ๋ฅ ์ด ๋ฎ์์ง๋๊ฐ (ํ๋ณธ 2,000๊ฑด)\n",
"idx = np.random.default_rng(0).choice(len(top3), 2000, replace=False)\n",
"X2 = Xva[idx].tolil()\n",
"for r, i in enumerate(idx):\n",
" X2[r, top3[i][0][0]] = 0\n",
"P2 = clf.predict_proba(X2.tocsr())\n",
"drop = P2[np.arange(len(idx)), pred[idx]] < P[idx, pred[idx]]\n",
"print(f\"๊ทผ๊ฑฐ ์ ํจ์ฑ: 1์์ ์ธ์๋ฅผ ์ ๊ฑฐํ๋ฉด ์์ธก ํ๋ฅ ์ด ๋ฎ์์ง๋ ๋น์จ {drop.mean():.1%} (ํ๊ท {P[idx, pred[idx]].mean():.2f} -> {P2[np.arange(len(idx)), pred[idx]].mean():.2f})\")\n",
"\n",
"\n",
"def summarize(i):\n",
" d = va.iloc[i]\n",
" f = [fnames[j] for j, _ in top3[i]]\n",
" return f\"{names[d['part']]}์ {d['defect']} ๊ฒฐํจ, ์น๋ช
๋ {SEV[pred[i]]} ๊ฐ๋ฅ์ฑ {P[i, pred[i]]:.0%}. ์ฃผ์ ๊ทผ๊ฑฐ: \" + \", \".join(f\"'{x}'\" for x in f) + \".\"\n",
"\n",
"\n",
"# 3) ์ ๊ฒ ์นด๋ (์น๋ช
๋๋ณ 1๊ฑด)\n",
"print(\"\\n[์ ๊ฒ ์นด๋] ์น๋ช
๋ | ์ค์ | ์ค๋ช
\")\n",
"for s in (2, 1, 0):\n",
" i = int(np.where((pred == s) & (va[\"sev\"].values == s) & (P.max(axis=1) > 0.9))[0][0])\n",
" print(f\" ์์ธก {SEV[s]:8s} (์ค์ {SEV[va.iloc[i]['sev']]})\")\n",
" print(\" \" + summarize(i))\n",
" print(\" ๊ธฐ์ฌ๋: \" + \" / \".join(f\"{fnames[j]}({c:+.1f})\" for j, c in top3[i]))\n",
"\n",
"# 4) ๋ฐ์ ํํฉ ํํธ๋งต(์ต๊ทผ 6๊ฐ์ x ๋ถ์)๊ณผ ์ง์ค๋ ๊ฒฝ๋ณด: ์ ๊ฑด์ / ํ์ต ๊ธฐ๊ฐ ์ํ๊ท > 1.4 ์ด๊ณ Critical ๊ฑด์ด ์์ผ๋ฉด ๊ฒฝ๋ณด\n",
"H = df[df[\"month\"] >= 30].pivot_table(index=\"part\", columns=\"month\", values=\"qdr_id\", aggfunc=\"size\", fill_value=0)\n",
"base = tr.groupby(\"part\").size() / 24\n",
"ratio = H.div(base, axis=0)\n",
"alerts = [(int(p), int(m), ratio.loc[p, m]) for p in H.index for m in H.columns if ratio.loc[p, m] > 1.4 and ((df[\"part\"] == p) & (df[\"month\"] == m) & (df[\"sev\"] == 2)).any()]\n",
"print(\"\\n[QDR ๋ฐ์ ํํธ๋งต] ๋ถ์ | ์ 30~35 ๊ฑด์ | ํ์ต ์ํ๊ท ๋๋น ์ต๋ ๋ฐฐ์\")\n",
"for p in H.sum(axis=1).sort_values(ascending=False).index[:6]:\n",
" print(f\" {names[p]:10s} \" + \" \".join(f\"{v:4d}\" for v in H.loc[p]) + f\" x{ratio.loc[p].max():.2f}\")\n",
"print(f\"์ง์ค๋ ๊ฒฝ๋ณด {len(alerts)}๊ฑด: \" + \", \".join(f\"{names[p]}({m}์, x{r:.1f})\" for p, m, r in sorted(alerts, key=lambda a: -a[2])[:4]))\n",
"\n",
"fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n",
"im = ax[0].imshow(ratio.values, aspect=\"auto\", cmap=\"Reds\"); ax[0].set_yticks(range(NP)); ax[0].set_yticklabels(range(NP)); ax[0].set_title(\"QDR concentration (x baseline)\"); ax[0].set_xlabel(\"month\"); plt.colorbar(im, ax=ax[0])\n",
"ax[1].hist(P.max(axis=1), bins=20); ax[1].set_title(\"Prediction confidence\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
06 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ต์ก์ฉ๊ฐ์์๋ฃ] QDR 18434๊ฑด, ๊ฒ์ฆ 6823๊ฑด ์น๋ช
๋ ๋ถ๋ฅ ์ ํ๋ 87.6%\n",
"์์ธก 6823๊ฑด ์ค ๊ธฐ์ฌ๋ ์์ 3๊ฐ ์ธ์ ์ฐ์ถ 6823๊ฑด (์ฐ์ถ๋ฅ 100%, ๋ชฉํ 100%: ๋ฌ์ฑ)\n",
"๊ทผ๊ฑฐ ์ ํจ์ฑ: 1์์ ์ธ์๋ฅผ ์ ๊ฑฐํ๋ฉด ์์ธก ํ๋ฅ ์ด ๋ฎ์์ง๋ ๋น์จ 100.0% (ํ๊ท 0.87 -> 0.80)\n",
"\n",
"[์ ๊ฒ ์นด๋] ์น๋ช
๋ | ์ค์ | ์ค๋ช
\n",
" ์์ธก Critical (์ค์ Critical)\n",
" ์์ง ์ฐ๋ฃ๊ณํต์ ๋ง๋ชจ ๊ฒฐํจ, ์น๋ช
๋ Critical ๊ฐ๋ฅ์ฑ 99%. ์ฃผ์ ๊ทผ๊ฑฐ: '์ค์ง', '์ฆ์', '์ด์ฉ'.\n",
" ๊ธฐ์ฌ๋: ์ค์ง(+2.1) / ์ฆ์(+2.1) / ์ด์ฉ(+0.3)\n",
" ์์ธก Major (์ค์ Major)\n",
" ์์ง ์ฐ๋ฃ๊ณํต์ ์ฒด๊ฒฐ๋ถ๋ ๊ฒฐํจ, ์น๋ช
๋ Major ๊ฐ๋ฅ์ฑ 99%. ์ฃผ์ ๊ทผ๊ฑฐ: '๊ธฐ๋ฅ', '๋ถ๋ถ์ ', '์ ํ'.\n",
" ๊ธฐ์ฌ๋: ๊ธฐ๋ฅ(+1.3) / ๋ถ๋ถ์ (+1.3) / ์ ํ(+1.3)\n",
" ์์ธก Minor (์ค์ Minor)\n",
" ์์ง ์ฐ๋ฃ๊ณํต์ ์ฒด๊ฒฐ๋ถ๋ ๊ฒฐํจ, ์น๋ช
๋ Minor ๊ฐ๋ฅ์ฑ 100%. ์ฃผ์ ๊ทผ๊ฑฐ: '์์', '๊ธฐ๋ฅ์', '์ํฅ'.\n",
" ๊ธฐ์ฌ๋: ์์(+1.7) / ๊ธฐ๋ฅ์(+1.4) / ์ํฅ(+1.4)\n",
"\n",
"[QDR ๋ฐ์ ํํธ๋งต] ๋ถ์ | ์ 30~35 ๊ฑด์ | ํ์ต ์ํ๊ท ๋๋น ์ต๋ ๋ฐฐ์\n",
" ๋๊ฐ ๊ณํต 124 87 99 67 56 38 x2.27\n",
" ์์ง ์ฐ๋ฃ๊ณํต 99 89 87 75 58 49 x1.61\n",
" ์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ 70 58 67 65 71 71 x1.14\n",
" ์ ๋ ์ฅ์น 53 50 71 55 66 62 x1.50\n",
" ๋ณ์๊ธฐ 67 69 60 58 44 45 x1.42\n",
" ์ ์ ๊ณํต 54 60 59 63 41 42 x1.57\n",
"์ง์ค๋ ๊ฒฝ๋ณด 19๊ฑด: ์ฐจ์ฒด ์ฉ์ ๋ถ(34์, x3.0), ๋๊ฐ ๊ณํต(30์, x2.3), ๋๊ฐ ๊ณํต(32์, x1.8), ์กฐํฅ ์ฅ์น(31์, x1.7)\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
07. ์ ์กฐ์ฌ ํ์ ๊ตฌ์ ๋ฐ ์ค๊ณ ๋ณ๊ฒฝ ์์ ์ ๊ธฐ๋ฅผ ์ํ ํ์ง๊ด HITL ๊ฒํ ๋ชจ๋ ์ค๊ณ\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์ค๊ณ ๋ณ๊ฒฝ ์์ ์ ๊ธฐ๋ฅผ ์ํ ํ์ง๊ด HITL ๊ฒํ ๋ชจ๋์ ์ค๊ณํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ ์กฐ์ฌ ํ์ ๊ตฌ์๊ณผ ์ค๊ณ ๋ณ๊ฒฝ ์์ ์ ๊ธฐ(์ธ์ฃผ์ ๋น ์์ ์๊ตฌ ํฌํจ)๋ฅผ ์ํด ํ์ง๊ด์ด ๊ทผ๊ฑฐ๋ฅผ ๊ฒํ ยท์น์ธํ๋ HITL ๋ชจ๋์ ์ค๊ณํ๊ณ ํจ๊ณผ๋ฅผ ์๋ฎฌ๋ ์ด์
์ผ๋ก ๊ฒ์ฆํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ต๊ทผ 12๊ฐ์ QDR์ ๋ถ์๋ณ ์์ธ ๋น์ค์ผ๋ก ํ์ ๊ตฌ์, ์ค๊ณ ๋ณ๊ฒฝ, ์ธ์ฃผ์ ๋น ์์ ํ๋ณด๋ฅผ ์ ์ํ๊ณ ์น์ธ ๋๊ธฐ์ด์ ์ฌ๋ฆฐ๋ค ๊ณผ์ ์ ์งํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/optimization/qlt05_simulator.py` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ํ์ค์ ๊ตฐ์ ์ ์ฝ์กฐ๊ฑด ๋ฐ์๋ ๋ฐ ์๋๋ฆฌ์ค๋ณ ๋์ ์ ์ ์ ์ ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์ต์ ํ ์ ์ฉ ์ ๋น์ฉ 15% ์ ๊ฐ ๋๋ ์ฌ๊ณ ํ์ ์จ 20% ํฅ์\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์ ํ/์ ์ ๊ณํ๋ฒ(LP/MIP) ์ต์ ํ ํด ๋์ถ ๋ฐ ์๋ฎฌ๋ ์ด์
์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๊ณผ๊ฑฐ ์ค์ ๋ฐ์ดํฐ ๋๋น ์ต์ ํ ๊ฒฐ๊ณผ ๋น๊ต ๋ฐฑํ
์คํ
\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์ต๊ทผ 12๊ฐ์ QDR ๊ทผ๊ฑฐ๋ก ๋ถ์๋ณ ์ ์กฐ์ฌ ํ์ ๊ตฌ์(์ ์กฐ ๋ถ๋ ์ฐ์ธ)๊ณผ ์ค๊ณ ๋ณ๊ฒฝ ์์ ์ ๊ธฐ(์ค๊ณ ์ทจ์ฝ ์ฐ์ธ) ํ๋ณด๋ฅผ ์ ์ํ๋ ์๋ ํ์ ํ์ง ์๊ณ ํ์ง๊ด ๊ฒํ ๋๊ธฐ์ด์ ์ฌ๋ ค ์น์ธยท๋ฐ๋ คยท๋ฌด์๋ต(์กฐ์น ๋ณด๋ฅ)์ ์ฌ์ ์ ํจ๊ป ํด์ ์ฒด์ธ ๊ฐ์ฌ ๋ก๊ทธ์ ๋จ๊ธฐ๋ HITL ๋ชจ๋์ ์๋ฎฌ๋ ์ด์
ํ๊ณ , ๊ฐ๋ณ ์ฌํ ์๋ฆฌ ๋๋น ๋น์ฉ ์ ๊ฐ๋ฅ ๊ณผ ์น์ธ ์ง์ฐยท์ฒ๋ฆฌ ์ฉ๋ยท์ ์ ์ ํ ๋ฏผ๊ฐ๋๋ฅผ ๋ถ์ํ๋ค.\"\"\"\n",
"import hashlib\n",
"import json\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR) ์์ฑ๊ธฐ (์ค์ ๋ถ๋ยท์ฅ๋นยท์
์ฒดยท์ ๋น ์ค์ ์์)\n",
"# ๊ฐ์ ์ฅ๋น ๋ถ์ 12์ข
, ๊ฒฐํจ ์ ํ 8์ข
, ์น๋ช
๋(Critical/Major/Minor), ์์ธ 5์ข
, ๊ฐ์ ์ธ์ฃผ์ ๋น ์
์ฒด 4๊ณณ + ๋ถ๋์ ๋น.\n",
"# QDR 1๊ฑด๋ง๋ค ํ๊ตญ์ด ์์ ํ
์คํธ๋ฅผ ๋ง๋ค๊ณ , ์๋ณ ๊ฑด์๋ ๊ณ์ ยท์ถ์ธยทํ๋ จ ๊ฐ๋(usage)ยท์ผ๋ถ ์ ์กฐ ๋ถ๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ๋ฅธ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"PARTS = [ # (์ด๋ฆ, ๋ณ์นญ, ์ ๊ธฐ์ค ๊ฑด์, ๊ณ์ ์งํญ, ์ ์ ์, (Critical, Major ๋น์จ), ๊ฐ์ ๋จ๊ฐ์ง์, ์ ์กฐ์ฌ, ์ฃผ์ ๊ฒฐํจ ๊ฐ์ค์น)\n",
" (\"์์ง ์ฐ๋ฃ๊ณํต\", [\"์ฐ๋ฃ๊ณํต\", \"์ฐ๋ฃ ๋ผ์ธ\", \"์ฐ๋ฃ ํํ\"], 26, 0.15, 7, (0.10, 0.35), 6.0, \"MK1\", \"๋์ :4 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:2 ์ค์๋:2\"),\n",
" (\"๋ณ์๊ธฐ\", [\"๋ณ์๊ธฐ\", \"T/M\", \"๋ฏธ์
\"], 22, 0.10, 8, (0.12, 0.38), 9.0, \"MK1\", \"๋ง๋ชจ:4 ๋์ :2 ๊ณผ์ด:3 ์ค์๋:1\"),\n",
" (\"์ ๋ ์ฅ์น\", [\"์ ๋ ์ฅ์น\", \"๋ธ๋ ์ดํฌ\", \"์ ๋ ๋ผ์ธ\"], 20, 0.20, 12, (0.26, 0.40), 5.0, \"MK2\", \"๋ง๋ชจ:4 ๊ท ์ด:2 ๋์ :2 ์ฒด๊ฒฐ๋ถ๋:2\"),\n",
" (\"์กฐํฅ ์ฅ์น\", [\"์กฐํฅ ์ฅ์น\", \"ํธ๋ค ์ฐ๋๋ถ\", \"์กฐํฅ ๋ก๋\"], 14, 0.10, 6, (0.30, 0.40), 5.0, \"MK2\", \"์ฒด๊ฒฐ๋ถ๋:4 ๊ท ์ด:2 ๋ง๋ชจ:3 ์ค์๋:1\"),\n",
" (\"๋๊ฐ ๊ณํต\", [\"๋๊ฐ ๊ณํต\", \"๋ผ๋์์ดํฐ\", \"๋๊ฐ์ ๋ผ์ธ\"], 24, 0.30, 7, (0.05, 0.30), 3.0, \"MK3\", \"๋์ :4 ๊ณผ์ด:4 ๋ถ์:2\"),\n",
" (\"์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ\", [\"๋ฐฐํฐ๋ฆฌ\", \"์ ๊ธฐ ๊ณํต\", \"๋ฐ์ ๊ธฐ\"], 28, 0.35, 1, (0.04, 0.28), 2.5, \"MK3\", \"์ ์ด๋ถ๋:4 ๋ถ์:2 ๊ณผ์ด:2 ์ค์๋:2\"),\n",
" (\"ํต์ ๋ชจ๋\", [\"ํต์ ๋ชจ๋\", \"๋ฌด์ ๊ธฐ ์ฐ๋๋ถ\", \"์ํ
๋ ์ ์๋ถ\"], 16, 0.05, 4, (0.03, 0.25), 4.0, \"MK4\", \"์ ์ด๋ถ๋:5 ์ค์๋:4 ๋ถ์:1\"),\n",
" (\"์ ์ ๊ณํต\", [\"์ ์ ๊ณํต\", \"์ ์ ํธ์ค\", \"์ ์ ์ค๋ฆฐ๋\"], 18, 0.10, 9, (0.10, 0.35), 4.5, \"MK2\", \"๋์ :5 ๊ท ์ด:2 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"ํ๊ฐ ์ฅ์น\", [\"ํ๊ฐ ์ฅ์น\", \"์์คํ์
\", \"์์ถฉ๊ธฐ\"], 15, 0.08, 5, (0.06, 0.30), 3.5, \"MK4\", \"๋ง๋ชจ:4 ๊ท ์ด:3 ์ฒด๊ฒฐ๋ถ๋:2 ๋ถ์:1\"),\n",
" (\"์ฐจ์ฒด ์ฉ์ ๋ถ\", [\"์ฐจ์ฒด ์ฉ์ ๋ถ\", \"ํ๋ ์ ์ฉ์ ๋ถ\", \"์ฐจ์ฒด ์ด์๋ถ\"], 10, 0.05, 10, (0.28, 0.40), 7.0, \"MK1\", \"๊ท ์ด:6 ๋ถ์:3 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ดํยท์กฐ์ค ์ฅ๋น\", [\"๊ดํ ์ฅ๋น\", \"์กฐ์ค๊ฒฝ\", \"๊ด์ธก ์ฅ๋น\"], 8, 0.05, 3, (0.03, 0.22), 8.0, \"MK4\", \"์ค์๋:4 ์ ์ด๋ถ๋:3 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ณต๊ธฐ ์ ํ ํํฐ\", [\"๊ณต๊ธฐ ํํฐ\", \"ํก๊ธฐ ํํฐ\", \"์ ํ ์ฅ์น\"], 12, 0.25, 4, (0.01, 0.12), 1.0, \"MK3\", \"๋ง๋ชจ:5 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:3\")]\n",
"NP = len(PARTS)\n",
"DEFECTS = { # ๊ฒฐํจ ์ ํ: ์ฆ์ ํํ(๋์์ด)\n",
" \"๊ท ์ด\": [\"๊ท ์ด์ด ๋ฐ๊ฒฌ๋จ\", \"ํฌ๋์ด ํ์ธ๋จ\", \"๊ฐ๋ผ์ง์ด ์์\"], \"๋์ \": [\"๋์ ๊ฐ ๋ฐ์ํจ\", \"์ค์ผ์ด ์์ด ๋์ด\", \"๋์ค ํ์ ์ด ์์\"],\n",
" \"๋ง๋ชจ\": [\"๋ง๋ชจ๊ฐ ์ฌํจ\", \"๊ณผ๋ํ ๋ง๋ชจ๊ฐ ํ์ธ๋จ\", \"๋ณ์ ํ์ ์ด ์์\"], \"๋ถ์\": [\"๋ถ์์ด ์งํ๋จ\", \"๋
น์ด ๋ฐ์ํจ\", \"์ฐํ ํ์ ์ด ์์\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋์ด ๋ฐ์ํจ\", \"์ปค๋ฅํฐ ๋จ์ ์ด ์์ฌ๋จ\", \"์ ํธ๊ฐ ๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ ํ๋ฆผ์ด ํ์ธ๋จ\", \"์ฒด๊ฒฐ ์ํ๊ฐ ๋ถ๋ํจ\", \"๊ณ ์ ๋ถ๊ฐ ํ๊ฑฐ์\"],\n",
" \"๊ณผ์ด\": [\"๊ณผ์ด ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํจ\", \"์จ๋๊ฐ ๊ณผ๋ํ๊ฒ ์์นํจ\", \"์ด์ด ์ฌํ๊ฒ ๋จ\"], \"์ค์๋\": [\"์ค์๋์ด ๋ฐ๋ณต๋จ\", \"์๋์ด ๋ถ์์ ํจ\", \"๊ฐํ์ ์ผ๋ก ๋ฉ์ถค\"]}\n",
"DNAMES = list(DEFECTS)\n",
"SEV = [\"Minor\", \"Major\", \"Critical\"]\n",
"SEV_CUE = {0: [\"๊ฒฝ๋ฏธํ ์์ค์\", \"์ด์ฉ์๋ ์ง์ฅ์ด ์์\", \"์ก์ ์ ๊ฒ์ ์ฌ์ํ ์ด์์\", \"๊ธฐ๋ฅ์๋ ์ํฅ์ด ์์\"],\n",
" 1: [\"์ฑ๋ฅ ์ ํ๊ฐ ์์\", \"์กฐ์ํ ์ ๋น๊ฐ ํ์ํจ\", \"์ถ๋ ฅ์ด ๋จ์ด์ง๋ ์ํ์\", \"๊ธฐ๋ฅ์ด ๋ถ๋ถ์ ์ผ๋ก ์ ํ๋จ\"],\n",
" 2: [\"์ฆ์ ์ด์ฉ ์ค์งํจ\", \"์์ ์ ์ํ์ด ์์ด ์ฌ์ฉ ๋ถ๊ฐ\", \"์๋ ์ ์ง๋ก ์๋ฌด ์ํ์ด ์ด๋ ค์\", \"์ธ๋ช
์์ ์ํ์ด ์ฐ๋ ค๋จ\"]}\n",
"CAUSES = [\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\", \"์ด์ฉ ๋ถ์ฃผ์\", \"์ค๊ณ ์ทจ์ฝ\", \"์์ฐ ๋
ธํ\"]\n",
"CAUSE_CUE = {\n",
" \"์ ์กฐ ๋ถ๋\": [\"์ ํ ์ธ์ ์งํ ๋ฐ์ํจ\", \"์ถ๊ณ ์ํ์์ ์ด๋ฏธ ์ด์์ด ์์์\", \"์ธ์ ํ ์ผ๋ง ์ง๋์ง ์์ ๋ฐ์ํจ\"],\n",
" \"์ ๋น ํ์ง\": [\"์ธ์ฃผ ์ ๋น ํ ์ผ๋ง ๋์ง ์์ ์ฌ๋ฐํจ\", \"์ ๋น ํ ์ฌ์กฐ๋ฆฝ ์ํ๊ฐ ๋ถ๋ํจ\", \"์ ๋น ์งํ ๊ฐ์ ๋ถ์์์ ๋ค์ ๋ฐ์ํจ\"],\n",
" \"์ด์ฉ ๋ถ์ฃผ์\": [\"ํ์ง ๊ธฐ๋ ์ค ์ถฉ๊ฒฉ ์ดํ ๋ฐ์ํจ\", \"๊ณผ๋ถํ ์ด์ฉ ํ ๋ฐ์ํจ\", \"์ ๊ฒ ์์ด ์ฐ์ ์ด์ฉํ ๋ค ๋ฐ์ํจ\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์์ด ๋ค๋ฅธ ์ฐจ๋์์๋ ๋ฐ๋ณต๋จ\", \"๊ตฌ์กฐ์ ์ผ๋ก ์ทจ์ฝํ ๋ถ์๋ก ๋ณด์\", \"์ฌ๋ฌ ๋ถ๋์์ ๊ฐ์ ํํ๋ก ๋ณด๊ณ ๋จ\"],\n",
" \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ์ด ๋๋ํจ\", \"์ฅ๊ธฐ๊ฐ ์ฌ์ฉ์ผ๋ก ๋
ธํ๋จ\", \"์๋ช
์ฃผ๊ธฐ ๋ง๊ธฐ์ ๋ฐ์ํจ\"]}\n",
"VENDORS = [\"์ธ์ฃผA\", \"์ธ์ฃผB\", \"์ธ์ฃผC\", \"์ธ์ฃผD\", \"๋ถ๋์ ๋น\"]\n",
"ACTIONS = [\"์ ๋น์ ์
๊ณ ๋ฅผ ์์ฒญํจ\", \"ํ์ฅ ์์ ์กฐ์น ํ ์ด์ฉ ์ค์\", \"๋ถํ ๊ตํ์ ์์ฒญํจ\", \"์ฌ๊ฒ์ฌ๋ฅผ ์์ฒญํจ\", \"์์ธ ํ์ธ์ด ํ์ํจ\"]\n",
"MAKER_OF = [p[7] for p in PARTS]\n",
"PRICE = np.array([p[6] for p in PARTS])\n",
"\n",
"\n",
"def usage_index(months, seed=0):\n",
" \"\"\"์๋ณ ํ๋ จ ๊ฐ๋(๋ฏธ๋ฆฌ ์ ์ ์๋ ๊ณํ ๊ฐ): ์ฐ 1ํ ์ ์ ์ ๊ฐ๋ ๊ณ์ + ์์ ์ก์\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 7] ์ ์กฐ์ฌ ํ์ ๊ตฌ์ ๋ฐ ์ค๊ณ ๋ณ๊ฒฝ ์์ ์ ๊ธฐ๋ฅผ ์ํ ํ์ง๊ด HITL ๊ฒํ ๋ชจ๋ ์ค๊ณ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_qdr(months=36, seed=1, scale=1.0, surge=True):\n",
" \"\"\"๋ฐํ: QDR DataFrame(๊ฑด๋น 1ํ), ์๋ณ usage, ๊ธ์ฆ ์ด๋ฒคํธ ๋ชฉ๋ก\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" use = usage_index(months, seed)\n",
" events = []\n",
" if surge:\n",
" for _ in range(max(2, months // 12 * 2)):\n",
" events.append((int(rng.integers(NP)), int(rng.integers(6, months))))\n",
" rows = []\n",
" wdef = [np.array([float(x.split(\":\")[1]) for x in p[8].split()]) for p in PARTS]\n",
" ddef = [[x.split(\":\")[0] for x in p[8].split()] for p in PARTS]\n",
" n = 0\n",
" for m in range(months):\n",
" for pi, p in enumerate(PARTS):\n",
" lam = p[2] * scale * (1 + 0.010 * m) * use[m] * (1 + p[3] * np.cos(2 * np.pi * (m % 12 - p[4]) / 12))\n",
" ev = (pi, m) in events\n",
" k = rng.poisson(lam * (2.6 if ev else 1.0))\n",
" for _ in range(k):\n",
" rows.append((n, m, pi, ev)); n += 1\n",
" N = len(rows)\n",
" A = np.array(rows)\n",
" month, part, ev = A[:, 1], A[:, 2], A[:, 3].astype(bool)\n",
" defect = np.array([rng.choice(ddef[i], p=wdef[i] / wdef[i].sum()) for i in part])\n",
" cr = np.array([PARTS[i][5][0] for i in part]); ma = np.array([PARTS[i][5][1] for i in part])\n",
" u = rng.random(N)\n",
" sev = np.where(u < cr, 2, np.where(u < cr + ma, 1, 0))\n",
" vendor = rng.choice(VENDORS, N, p=[0.25, 0.2, 0.2, 0.15, 0.2])\n",
" # ์์ธ: ๊ธฐ๋ณธ ๋น์จ์ ๋ถ์(์ ์กฐ์ฌ)ยท์
์ฒดยท์ด๋ฒคํธ ํจ๊ณผ๋ฅผ ๊ณฑํ๋ค\n",
" base = np.array([0.22, 0.20, 0.20, 0.16, 0.22])\n",
" P = np.tile(base, (N, 1))\n",
" P[vendor == \"์ธ์ฃผB\", 1] *= 2.6\n",
" P[ev, 0] *= 5.0\n",
" design = np.isin(part, [2, 3, 7]); P[design, 3] *= 2.2\n",
" P[np.isin(part, [4, 5, 11]), 0] *= 2.0 # ์ ์กฐ์ฌ MK3 ๋ฉํ ๋ถ์๋ ์ ์กฐ ๋ถ๋ ๋น์ค์ด ๋๋ค\n",
" P[defect == \"๋ง๋ชจ\", 4] *= 1.6; P[defect == \"๋์ \", 4] *= 1.3\n",
" P /= P.sum(axis=1, keepdims=True)\n",
" cause = np.array([CAUSES[rng.choice(5, p=P[i])] for i in range(N)])\n",
" since = np.where(cause == \"์ ๋น ํ์ง\", rng.exponential(35, N), rng.exponential(160, N)).clip(1, 720).round().astype(int)\n",
" wait = (rng.lognormal(1.3, 0.5, N) * np.where(vendor == \"์ธ์ฃผB\", 1.7, np.where(vendor == \"๋ถ๋์ ๋น\", 0.6, 1.0)) * (0.7 + 0.5 * use[month])).round(1)\n",
" reins = rng.random(N) < np.where(cause == \"์ ๋น ํ์ง\", 0.6, 0.08)\n",
" texts = []\n",
" for i in range(N):\n",
" pt = PARTS[part[i]]\n",
" cl = [f\"{rng.choice(pt[1])}์์ {rng.choice(DEFECTS[defect[i]])}\"]\n",
" if rng.random() < 0.7: cl.append(rng.choice(SEV_CUE[sev[i]]))\n",
" if rng.random() < 0.65: # ์์ธ ๋จ์: 8%๋ ์ค์ ์์ธ๊ณผ ๋ค๋ฅธ ๋จ์๊ฐ ์ ํ๋ค(๋ณด๊ณ ์์ ์ถ์ )\n",
" cc = cause[i] if rng.random() > 0.08 else rng.choice(CAUSES)\n",
" cl.append(rng.choice(CAUSE_CUE[cc]))\n",
" cl.append(rng.choice(ACTIONS))\n",
" if rng.random() < 0.5: cl.insert(0, f\"VU-{rng.integers(1, 31):02d} ์์\")\n",
" texts.append(\". \".join(cl) + \".\")\n",
" df = pd.DataFrame({\"qdr_id\": [f\"Q{seed}{i:06d}\" for i in range(N)], \"month\": month, \"unit\": [f\"VU-{x:02d}\" for x in rng.integers(1, 31, N)],\n",
" \"part\": part, \"defect\": defect, \"sev\": sev, \"cause\": cause, \"vendor\": vendor, \"maker\": [MAKER_OF[i] for i in part],\n",
" \"since_maint\": since, \"inspect_wait\": wait, \"reinspect\": reins, \"event\": ev, \"text\": texts})\n",
" df[\"cost\"] = PRICE[df[\"part\"]] * np.array([1.0, 2.0, 4.0])[df[\"sev\"]] # [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์๋ฆฌยท์กฐ์น ๋น์ฉ์ง์\n",
" return df, use, events\n",
"\n",
"df, use, events = make_qdr(48, seed=1, scale=1.5)\n",
"names = [p[0] for p in PARTS]\n",
"D0 = 24 # ๊ฒํ ๊ธฐ์ค ์: ์ง์ 12๊ฐ์(12~23) ๊ทผ๊ฑฐ๋ก ์ ์, ์ดํ 24๊ฐ์(24~47) ํจ๊ณผ ํ๊ฐ\n",
"CLAIM_FIXED, CHANGE_FIXED, FIX_VENDOR = 15.0, 100.0, 50.0 # [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํ์ ๊ตฌ์ ์ฒญ๊ตฌ ์ฒ๋ฆฌ๋น, ์ค๊ณ ๋ณ๊ฒฝ ์์ ์ ๊ธฐยท๊ฒํ ๋น์ฉ์ง์\n",
"RECOVER, REDUCE, LAG, W_WAIT, REINSPECT = 0.8, 0.8, 3, 0.8, 6.0 # ํ์ ๊ตฌ์ ํ์์จ, ์ค๊ณ ๋ณ๊ฒฝ ํ ํด๋น ์์ธ QDR ๊ฐ์์จ, ์ค๊ณ ๋ณ๊ฒฝ ๋ฐ์ ์ง์ฐ(๊ฐ์)\n",
"\n",
"\n",
"class Audit:\n",
" def __init__(self):\n",
" self.log = []\n",
"\n",
" def add(self, **body):\n",
" prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n",
" body = {\"seq\": len(self.log), **body}\n",
" self.log.append({**body, \"prev\": prev, \"hash\": hashlib.sha256((prev + json.dumps(body, sort_keys=True, ensure_ascii=False)).encode()).hexdigest()})\n",
"\n",
" def verify(self):\n",
" prev = \"GENESIS\"\n",
" for e in self.log:\n",
" b = {k: v for k, v in e.items() if k not in (\"prev\", \"hash\")}\n",
" if e[\"prev\"] != prev or hashlib.sha256((prev + json.dumps(b, sort_keys=True, ensure_ascii=False)).encode()).hexdigest() != e[\"hash\"]:\n",
" return e[\"seq\"]\n",
" prev = e[\"hash\"]\n",
" return None\n",
"\n",
"\n",
"def proposals(d):\n",
" \"\"\"๋ถ์๋ณ๋ก ๊ทผ๊ฑฐ(๊ฑด์ยท์น๋ช
๋ยท์์ธ ๋น์ค)๋ฅผ ์ง๊ณํด ์ ์ ์ ํ์ ์ ํ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 7] ์ ์กฐ์ฌ ํ์ ๊ตฌ์ ๋ฐ ์ค๊ณ ๋ณ๊ฒฝ ์์ ์ ๊ธฐ๋ฅผ ์ํ ํ์ง๊ด HITL ๊ฒํ ๋ชจ๋ ์ค๊ณ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def reviewer(pr, rng, second=False):\n",
" \"\"\"[๋ชจ์ ํ์ง๊ด] ๊ทผ๊ฑฐ๊ฐ ์ฝํ๊ฑฐ๋ ์ด์ฉยท๋
ธํ ์์ธ์ด ์ฐ์ธํ๋ฉด ๋ฐ๋ ค, 10%๋ ๊ธฐํ ๋ด ๋ฌด์๋ต(์ฌ์์ ์์๋ ์๋ต).\"\"\"\n",
" if not second and rng.random() < 0.10:\n",
" return \"๋ฌด์๋ต\", \"๊ธฐํ ๋ด ์๋ต ์์ -> ์กฐ์น ๋ณด๋ฅ(๊ธฐ๋ณธ ๊ณํ ์ ์ง)\"\n",
" if pr[\"noise\"] > 0.46:\n",
" return \"๋ฐ๋ ค\", f\"์ด์ฉยท๋
ธํ ์์ธ {pr['noise']:.0%}๋ก ์ ์กฐ์ฌ ์ฑ
์ ๊ทผ๊ฑฐ ๋ถ์กฑ\"\n",
" if pr[\"share\"] < 0.235 and pr[\"kind\"] != \"์ธ์ฃผ์ ๋น ์์ \":\n",
" return \"๋ฐ๋ ค\", f\"์์ธ ๋น์ค {pr['share']:.0%}๋ก ํ๊ท ์์ค, ์ถ๊ฐ ๊ด์ฐฐ\"\n",
" if pr[\"n\"] < 120 and pr[\"crit\"] < 0.10:\n",
" return \"๋ฐ๋ ค\", f\"๊ทผ๊ฑฐ {pr['n']}๊ฑด, Critical {pr['crit']:.0%}๋ก ์ถ๊ฐ ๊ด์ฐฐ\"\n",
" return \"์น์ธ\", f\"{pr['kind']} ๊ทผ๊ฑฐ {pr['n']}๊ฑด, ์์ธ ๋น์ค {pr['share']:.0%}, Critical {pr['crit']:.0%}\"\n",
"\n",
"\n",
"def effect(d, pr, delay, surge):\n",
" \"\"\"์น์ธ๋ ์ ์์ ์ดํ 12๊ฐ์ ํจ๊ณผ(๋น์ฉ์ง์ ์ด๋): ํ์ ๊ตฌ์์ ์ ์กฐ ๋ถ๋ QDR ๋น์ฉ์ ์ผ๋ถ ํ์, ์ค๊ณ ๋ณ๊ฒฝ์ ์ค๊ณ ์ทจ์ฝ QDR ๊ฐ์\"\"\"\n",
" f = d[(d[\"month\"] >= D0) & (d[\"part\"] == pr[\"part\"])] if pr[\"part\"] >= 0 else d[(d[\"month\"] >= D0) & (d[\"vendor\"] == pr[\"vendor\"])]\n",
" start = D0 + delay\n",
" if pr[\"kind\"] == \"์ธ์ฃผ์ ๋น ์์ \":\n",
" g = f[(f[\"cause\"] == \"์ ๋น ํ์ง\") & (f[\"month\"] >= start + 2)]\n",
" return 0.7 * (g[\"cost\"] + W_WAIT * g[\"inspect_wait\"] + REINSPECT * g[\"reinspect\"]).sum() * surge - FIX_VENDOR\n",
" if pr[\"kind\"] == \"ํ์ ๊ตฌ์\":\n",
" c = f[(f[\"cause\"] == \"์ ์กฐ ๋ถ๋\") & (f[\"month\"] >= start)][\"cost\"].sum() * surge\n",
" return RECOVER * c - CLAIM_FIXED\n",
" c = f[(f[\"cause\"] == \"์ค๊ณ ์ทจ์ฝ\") & (f[\"month\"] >= start + LAG)][\"cost\"].sum() * surge\n",
" return REDUCE * c - CHANGE_FIXED\n",
"\n",
"\n",
"def run(mode, delay=1, surge=1.0, cap=99, seed=0, audit=None, resubmit=True):\n",
" rng = np.random.default_rng(seed)\n",
" props = sorted(proposals(df), key=lambda x: -x[\"n\"] * (1 + 3 * x[\"crit\"]))\n",
" extra = 0\n",
" gain, dec, n_ok = 0.0, {\"์น์ธ\": 0, \"๋ฐ๋ ค\": 0, \"๋ฌด์๋ต\": 0}, 0\n",
" for pr in props:\n",
" if mode == \"auto\":\n",
" d_, why = \"์น์ธ\", \"๋น๊ต์ฉ ์ ๋ถ ์ ์ฉ\"\n",
" else:\n",
" audit.add(event=\"PROPOSE\", target=(names[pr[\"part\"]] if pr[\"part\"] >= 0 else pr[\"vendor\"]) + \"/\" + pr[\"kind\"], status=\"PENDING\")\n",
" d_, why = reviewer(pr, rng)\n",
" if d_ == \"์น์ธ\" and n_ok >= cap:\n",
" d_, why = \"๋ฌด์๋ต\", \"์ ์ฒ๋ฆฌ ์ฉ๋ ์ด๊ณผ -> ๋ณด๋ฅ\"\n",
" tg = (names[pr[\"part\"]] if pr[\"part\"] >= 0 else pr[\"vendor\"]) + \"/\" + pr[\"kind\"]\n",
" audit.add(event=\"DECIDE\", target=tg, decision=d_, reason=why, by=\"ํ์ง๊ด(๋ชจ์)\")\n",
" extra = 0\n",
" if d_ == \"๋ฌด์๋ต\" and resubmit: # ๋ฌด์๋ต์ ๊ธฐ๋ณธ ๊ณํ ์ ์ง(์กฐ์น ๋ณด๋ฅ), ๋ค์ ๋ถ๊ธฐ ๊ฒํ ํ์์ ์ฌ์์ \n",
" audit.add(event=\"RESUBMIT\", target=tg, status=\"PENDING\", note=\"๋ถ๊ธฐ ๊ฒํ ํ์ ์ฌ์์ (์๋ ์น์ธ ์๋)\")\n",
" d_, why = reviewer(pr, rng, second=True)\n",
" audit.add(event=\"DECIDE\", target=tg, decision=d_, reason=why + \"(์ฌ์์ )\", by=\"ํ์ง๊ด(๋ชจ์)\")\n",
" extra = 3\n",
" dec[d_] += 1\n",
" if d_ == \"์น์ธ\":\n",
" n_ok += 1; gain += effect(df, pr, (delay + (extra if mode != \"auto\" else 0)) if mode != \"auto\" else 0, surge)\n",
" e = df[df[\"month\"] >= D0]\n",
" base = (e[\"cost\"].sum() + W_WAIT * e[\"inspect_wait\"].sum() + REINSPECT * e[\"reinspect\"].sum()) * surge\n",
" return gain, base, dec, len(props)\n",
"\n",
"\n",
"audit = Audit()\n",
"g_h, base, dec, n_prop = run(\"hitl\", seed=3, audit=audit)\n",
"g_a, _, _, _ = run(\"auto\")\n",
"print(f\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] QDR {len(df)}๊ฑด, ๊ฒํ ๊ธฐ์ค ์ {D0}, ํ๋ณด ์ ์ {n_prop}๊ฑด(ํ์ ๊ตฌ์ยท์ค๊ณ ๋ณ๊ฒฝยท์ธ์ฃผ์ ๋น ์์ )\")\n",
"print(\"\\n[๋น์ฉ ๋น๊ต] ๋ฐฉ์ | ์ดํ 24๊ฐ์ ๋น์ฉ์ง์ | ์ ๊ฐ์ก\")\n",
"for nm, g in [(\"๊ฐ๋ณ ์ฌํ ์๋ฆฌ(๊ธฐ๋ณธ ๊ณํ)\", 0), (\"์ ์ + ํ์ง๊ด ์น์ธ\", g_h), (\"์ ์ ์ ๋ถ ์ ์ฉ(๋น๊ต์ฉ)\", g_a)]:\n",
" print(f\" {nm:22s} {base - g:9.0f} {g:8.0f}\")\n",
"cut = g_h / base\n",
"print(f\"HITL ์ ์ฉ ๋น์ฉ ์ ๊ฐ๋ฅ {cut:.1%} (๋ชฉํ 15% ์ด์: {'๋ฌ์ฑ' if cut >= 0.15 else '๋ฏธ๋ฌ'}), ๋น๊ต์ฉ ์ ๋ถ ์ ์ฉ {g_a / base:.1%}\")\n",
"print(f\"์ต์ข
๊ฒฐ์ {sum(dec.values())}๊ฑด: \" + \", \".join(f\"{k} {v}๊ฑด\" for k, v in dec.items()))\n",
"tgt = {e[\"target\"] for e in audit.log if e[\"event\"] == \"PROPOSE\"}; dec_t = {e[\"target\"] for e in audit.log if e[\"event\"] == \"DECIDE\"}\n",
"n_re = sum(e[\"event\"] == \"RESUBMIT\" for e in audit.log)\n",
"print(f\"์ ์ {len(tgt)}๊ฑด ์ค ๊ฒฐ์ ๊ธฐ๋ก {len(tgt & dec_t)}๊ฑด(๋ก๊น
๋ฅ {len(tgt & dec_t) / len(tgt):.0%}), ์๋ ์น์ธ 0๊ฑด, ๋ฌด์๋ต {n_re}๊ฑด์ ์กฐ์น ๋ณด๋ฅ ํ ๋ถ๊ธฐ ํ์ ์ฌ์์ \")\n",
"rej = [e[\"reason\"] for e in audit.log if e.get(\"decision\") == \"๋ฐ๋ ค\"]\n",
"print(\"๋ฐ๋ ค ์ฌ์ ์:\", rej[0] if rej else \"-\")\n",
"print(f\"๊ฐ์ฌ ๋ก๊ทธ ๋ฌด๊ฒฐ์ฑ: {'์ ์' if audit.verify() is None else '์ด์'}\", end=\", \")\n",
"audit.log[3][\"reason\"] = \"์ฌํ ์์ \"\n",
"print(f\"์ฌํ ๋ณ์กฐ ์ seq {audit.verify()}์์ ํ์ง\")\n",
"\n",
"print(\"\\n[๋ฏผ๊ฐ๋ 1] ์น์ธ๊น์ง ์ง์ฐ(๊ฐ์) -> ์ ๊ฐ๋ฅ \")\n",
"for dl in (0, 1, 3, 6, 9):\n",
" g, b, _, _ = run(\"hitl\", delay=dl, seed=3, audit=Audit())\n",
" print(f\" {dl}๊ฐ์: {g / b:.1%}\")\n",
"print(\"[๋ฏผ๊ฐ๋ 2] ํ์ง๊ด ์ฒ๋ฆฌ ์ฉ๋(์น์ธ ๊ฐ๋ฅ ๊ฑด์) -> ์ ๊ฐ๋ฅ \")\n",
"for cp in (2, 5, 8, 99):\n",
" g, b, _, _ = run(\"hitl\", cap=cp, seed=3, audit=Audit())\n",
" print(f\" ์ต๋ {cp if cp < 99 else '๋ฌด์ ํ'}๊ฑด: {g / b:.1%}\")\n",
"print(\"[๋ฏผ๊ฐ๋ 3] ์ ์ ์ ํ ๊ฐ์ (QDR ๋น์ฉ 1.5๋ฐฐ ๊ธ์ฆ) -> ์ ๊ฐ๋ฅ \")\n",
"for sg in (1.0, 1.5, 2.0):\n",
" g, b, _, _ = run(\"hitl\", surge=sg, seed=3, audit=Audit())\n",
" print(f\" x{sg}: {g / b:.1%}\")\n",
"\n",
"plt.figure(figsize=(6, 4))\n",
"plt.bar([\"baseline\", \"HITL\", \"auto(ref)\"], [base, base - g_h, base - g_a]); plt.ylabel(\"cost index (24 months)\"); plt.title(\"Quality review HITL simulator\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
07 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ต์ก์ฉ๊ฐ์์๋ฃ] QDR 19316๊ฑด, ๊ฒํ ๊ธฐ์ค ์ 24, ํ๋ณด ์ ์ 13๊ฑด(ํ์ ๊ตฌ์ยท์ค๊ณ ๋ณ๊ฒฝยท์ธ์ฃผ์ ๋น ์์ )\n",
"\n",
"[๋น์ฉ ๋น๊ต] ๋ฐฉ์ | ์ดํ 24๊ฐ์ ๋น์ฉ์ง์ | ์ ๊ฐ์ก\n",
" ๊ฐ๋ณ ์ฌํ ์๋ฆฌ(๊ธฐ๋ณธ ๊ณํ) 144670 0\n",
" ์ ์ + ํ์ง๊ด ์น์ธ 122929 21741\n",
" ์ ์ ์ ๋ถ ์ ์ฉ(๋น๊ต์ฉ) 119586 25084\n",
"HITL ์ ์ฉ ๋น์ฉ ์ ๊ฐ๋ฅ 15.0% (๋ชฉํ 15% ์ด์: ๋ฌ์ฑ), ๋น๊ต์ฉ ์ ๋ถ ์ ์ฉ 17.3%\n",
"์ต์ข
๊ฒฐ์ 13๊ฑด: ์น์ธ 12๊ฑด, ๋ฐ๋ ค 1๊ฑด, ๋ฌด์๋ต 0๊ฑด\n",
"์ ์ 13๊ฑด ์ค ๊ฒฐ์ ๊ธฐ๋ก 13๊ฑด(๋ก๊น
๋ฅ 100%), ์๋ ์น์ธ 0๊ฑด, ๋ฌด์๋ต 2๊ฑด์ ์กฐ์น ๋ณด๋ฅ ํ ๋ถ๊ธฐ ํ์ ์ฌ์์ \n",
"๋ฐ๋ ค ์ฌ์ ์: ์์ธ ๋น์ค 23%๋ก ํ๊ท ์์ค, ์ถ๊ฐ ๊ด์ฐฐ\n",
"๊ฐ์ฌ ๋ก๊ทธ ๋ฌด๊ฒฐ์ฑ: ์ ์, ์ฌํ ๋ณ์กฐ ์ seq 3์์ ํ์ง\n",
"\n",
"[๋ฏผ๊ฐ๋ 1] ์น์ธ๊น์ง ์ง์ฐ(๊ฐ์) -> ์ ๊ฐ๋ฅ \n",
" 0๊ฐ์: 15.9%\n",
" 1๊ฐ์: 15.0%\n",
" 3๊ฐ์: 13.6%\n",
" 6๊ฐ์: 11.4%\n",
" 9๊ฐ์: 9.2%\n",
"[๋ฏผ๊ฐ๋ 2] ํ์ง๊ด ์ฒ๋ฆฌ ์ฉ๋(์น์ธ ๊ฐ๋ฅ ๊ฑด์) -> ์ ๊ฐ๋ฅ \n",
" ์ต๋ 2๊ฑด: 14.2%\n",
" ์ต๋ 5๊ฑด: 14.4%\n",
" ์ต๋ 8๊ฑด: 14.8%\n",
" ์ต๋ ๋ฌด์ ํ๊ฑด: 15.0%\n",
"[๋ฏผ๊ฐ๋ 3] ์ ์ ์ ํ ๊ฐ์ (QDR ๋น์ฉ 1.5๋ฐฐ ๊ธ์ฆ) -> ์ ๊ฐ๋ฅ \n",
" x1.0: 15.0%\n",
" x1.5: 15.1%\n",
" x2.0: 15.2%\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
08. 10๋
์น ๋์ฉ๋ ์ผ์ QDR ํ
์คํธ ๋ฐ์ดํฐ ์ผ๊ด ๋ถ์ ์คํธ๋ ์ค ํ
์คํธ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋์ฉ๋ ์ผ์ QDR ํ
์คํธ ๋ฐ์ดํฐ ์ผ๊ด ๋ถ์ ์คํธ๋ ์ค ํ
์คํธ๋ฅผ ์ํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **10๋
(120๊ฐ์)์น ๋์ฉ๋ ์ผ์ QDR ํ
์คํธ๋ฅผ ์ผ๊ด ๋ถ์ํ๋ ์คํธ๋ ์ค ์ํ์ ์ํํด ์ฒ๋ฆฌ ์๊ฐ, ํ๋ฉด ๊ตฌ์ฑ ์๊ฐ, ๊ฒฝ๋ณด ์ ํ๋๋ฅผ ํ์ธํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฐ์ QDR ์ฝ 6๋ง ๊ฑด์ ๋น ์์ , ์ธ์ฝ๋ฉ ๊นจ์ง, ๊ณผ๋ ๊ธธ์ด ๋ ์ฝ๋๋ฅผ ์์ด 1~10๋
๋จ์๋ก ์ฃผ์
ํด ๊ฒฉ๋ฆฌยท์ ์ค ๊ฑด์์ ๋ถ์ ์๊ฐ์ ์ผ๋ค ๊ณผ์ ์ ์งํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/dashboard/qlt05_app.py (Streamlit/Dash ๋ฑ)` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์์ฌ๊ฒฐ์ ์ง์์ ์ต์ ํ๋ ์๊ฐ์ ์๊ณ ๋ฐ ์ฌ์ฉ์ ๊ฒฝํ(UX)\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ๋์๋ณด๋ ๋ก๋ฉ ์๊ฐ 2์ด ์ด๋ด, ๊ฒฝ๋ณด ๋ฐ๋ น ์ ํ๋ 95% ์ด์\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์น ๊ธฐ๋ฐ ๋ถ์ ๋์๋ณด๋ ๊ตฌ๋ ๋ฐ ์ค์๊ฐ ํํฐ๋ง ๊ธฐ๋ฅ ๊ฒ์ฆ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ์ค์ ๊ตฐ์ ๋ฐ์ดํฐ ์ฐ๋ ํ๋ฉด ํ
์คํธ ๋ฐ UI ๋์ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"10๋
(120๊ฐ์)์น ๋์ฉ๋ ์ผ์ QDR ํ
์คํธ๋ฅผ ํ๊บผ๋ฒ์ ์ฃผ์
ํด ์ ๊ทํยท๋ถ์/๊ฒฐํจ/์น๋ช
๋ ๋ผ๋ฒจ๋งยท์๋ณ ์ง๊ณยท์ง์ค๋ ๊ฒฝ๋ณด๋ฅผ ์ผ๊ด ๋ถ์ํ๊ณ , ๊ท๋ชจ๋ณ ์ฒ๋ฆฌ ์๊ฐ, ๋ถ๋ ๋ ์ฝ๋(๋น ์์ ยท์ธ์ฝ๋ฉ ๊นจ์งยท๊ณผ๋ ๊ธธ์ด) ๊ฒฉ๋ฆฌ์ ์ ์ค 0๊ฑด, ๋์๋ณด๋ ํ๋ฉด ๊ตฌ์ฑยทํํฐ ์๋ต ์๊ฐ, ๊ธ์ฆ ์ด๋ฒคํธ์ ๋ํ ๊ฒฝ๋ณด ์ ํ๋๋ฅผ ํ์ธํ๋ ์คํธ๋ ์ค ์ํ์ ์ํํ๋ค.\"\"\"\n",
"import re\n",
"import time\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR) ์์ฑ๊ธฐ (์ค์ ๋ถ๋ยท์ฅ๋นยท์
์ฒดยท์ ๋น ์ค์ ์์)\n",
"# ๊ฐ์ ์ฅ๋น ๋ถ์ 12์ข
, ๊ฒฐํจ ์ ํ 8์ข
, ์น๋ช
๋(Critical/Major/Minor), ์์ธ 5์ข
, ๊ฐ์ ์ธ์ฃผ์ ๋น ์
์ฒด 4๊ณณ + ๋ถ๋์ ๋น.\n",
"# QDR 1๊ฑด๋ง๋ค ํ๊ตญ์ด ์์ ํ
์คํธ๋ฅผ ๋ง๋ค๊ณ , ์๋ณ ๊ฑด์๋ ๊ณ์ ยท์ถ์ธยทํ๋ จ ๊ฐ๋(usage)ยท์ผ๋ถ ์ ์กฐ ๋ถ๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ๋ฅธ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"PARTS = [ # (์ด๋ฆ, ๋ณ์นญ, ์ ๊ธฐ์ค ๊ฑด์, ๊ณ์ ์งํญ, ์ ์ ์, (Critical, Major ๋น์จ), ๊ฐ์ ๋จ๊ฐ์ง์, ์ ์กฐ์ฌ, ์ฃผ์ ๊ฒฐํจ ๊ฐ์ค์น)\n",
" (\"์์ง ์ฐ๋ฃ๊ณํต\", [\"์ฐ๋ฃ๊ณํต\", \"์ฐ๋ฃ ๋ผ์ธ\", \"์ฐ๋ฃ ํํ\"], 26, 0.15, 7, (0.10, 0.35), 6.0, \"MK1\", \"๋์ :4 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:2 ์ค์๋:2\"),\n",
" (\"๋ณ์๊ธฐ\", [\"๋ณ์๊ธฐ\", \"T/M\", \"๋ฏธ์
\"], 22, 0.10, 8, (0.12, 0.38), 9.0, \"MK1\", \"๋ง๋ชจ:4 ๋์ :2 ๊ณผ์ด:3 ์ค์๋:1\"),\n",
" (\"์ ๋ ์ฅ์น\", [\"์ ๋ ์ฅ์น\", \"๋ธ๋ ์ดํฌ\", \"์ ๋ ๋ผ์ธ\"], 20, 0.20, 12, (0.26, 0.40), 5.0, \"MK2\", \"๋ง๋ชจ:4 ๊ท ์ด:2 ๋์ :2 ์ฒด๊ฒฐ๋ถ๋:2\"),\n",
" (\"์กฐํฅ ์ฅ์น\", [\"์กฐํฅ ์ฅ์น\", \"ํธ๋ค ์ฐ๋๋ถ\", \"์กฐํฅ ๋ก๋\"], 14, 0.10, 6, (0.30, 0.40), 5.0, \"MK2\", \"์ฒด๊ฒฐ๋ถ๋:4 ๊ท ์ด:2 ๋ง๋ชจ:3 ์ค์๋:1\"),\n",
" (\"๋๊ฐ ๊ณํต\", [\"๋๊ฐ ๊ณํต\", \"๋ผ๋์์ดํฐ\", \"๋๊ฐ์ ๋ผ์ธ\"], 24, 0.30, 7, (0.05, 0.30), 3.0, \"MK3\", \"๋์ :4 ๊ณผ์ด:4 ๋ถ์:2\"),\n",
" (\"์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ\", [\"๋ฐฐํฐ๋ฆฌ\", \"์ ๊ธฐ ๊ณํต\", \"๋ฐ์ ๊ธฐ\"], 28, 0.35, 1, (0.04, 0.28), 2.5, \"MK3\", \"์ ์ด๋ถ๋:4 ๋ถ์:2 ๊ณผ์ด:2 ์ค์๋:2\"),\n",
" (\"ํต์ ๋ชจ๋\", [\"ํต์ ๋ชจ๋\", \"๋ฌด์ ๊ธฐ ์ฐ๋๋ถ\", \"์ํ
๋ ์ ์๋ถ\"], 16, 0.05, 4, (0.03, 0.25), 4.0, \"MK4\", \"์ ์ด๋ถ๋:5 ์ค์๋:4 ๋ถ์:1\"),\n",
" (\"์ ์ ๊ณํต\", [\"์ ์ ๊ณํต\", \"์ ์ ํธ์ค\", \"์ ์ ์ค๋ฆฐ๋\"], 18, 0.10, 9, (0.10, 0.35), 4.5, \"MK2\", \"๋์ :5 ๊ท ์ด:2 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"ํ๊ฐ ์ฅ์น\", [\"ํ๊ฐ ์ฅ์น\", \"์์คํ์
\", \"์์ถฉ๊ธฐ\"], 15, 0.08, 5, (0.06, 0.30), 3.5, \"MK4\", \"๋ง๋ชจ:4 ๊ท ์ด:3 ์ฒด๊ฒฐ๋ถ๋:2 ๋ถ์:1\"),\n",
" (\"์ฐจ์ฒด ์ฉ์ ๋ถ\", [\"์ฐจ์ฒด ์ฉ์ ๋ถ\", \"ํ๋ ์ ์ฉ์ ๋ถ\", \"์ฐจ์ฒด ์ด์๋ถ\"], 10, 0.05, 10, (0.28, 0.40), 7.0, \"MK1\", \"๊ท ์ด:6 ๋ถ์:3 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ดํยท์กฐ์ค ์ฅ๋น\", [\"๊ดํ ์ฅ๋น\", \"์กฐ์ค๊ฒฝ\", \"๊ด์ธก ์ฅ๋น\"], 8, 0.05, 3, (0.03, 0.22), 8.0, \"MK4\", \"์ค์๋:4 ์ ์ด๋ถ๋:3 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ณต๊ธฐ ์ ํ ํํฐ\", [\"๊ณต๊ธฐ ํํฐ\", \"ํก๊ธฐ ํํฐ\", \"์ ํ ์ฅ์น\"], 12, 0.25, 4, (0.01, 0.12), 1.0, \"MK3\", \"๋ง๋ชจ:5 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:3\")]\n",
"NP = len(PARTS)\n",
"DEFECTS = { # ๊ฒฐํจ ์ ํ: ์ฆ์ ํํ(๋์์ด)\n",
" \"๊ท ์ด\": [\"๊ท ์ด์ด ๋ฐ๊ฒฌ๋จ\", \"ํฌ๋์ด ํ์ธ๋จ\", \"๊ฐ๋ผ์ง์ด ์์\"], \"๋์ \": [\"๋์ ๊ฐ ๋ฐ์ํจ\", \"์ค์ผ์ด ์์ด ๋์ด\", \"๋์ค ํ์ ์ด ์์\"],\n",
" \"๋ง๋ชจ\": [\"๋ง๋ชจ๊ฐ ์ฌํจ\", \"๊ณผ๋ํ ๋ง๋ชจ๊ฐ ํ์ธ๋จ\", \"๋ณ์ ํ์ ์ด ์์\"], \"๋ถ์\": [\"๋ถ์์ด ์งํ๋จ\", \"๋
น์ด ๋ฐ์ํจ\", \"์ฐํ ํ์ ์ด ์์\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋์ด ๋ฐ์ํจ\", \"์ปค๋ฅํฐ ๋จ์ ์ด ์์ฌ๋จ\", \"์ ํธ๊ฐ ๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ ํ๋ฆผ์ด ํ์ธ๋จ\", \"์ฒด๊ฒฐ ์ํ๊ฐ ๋ถ๋ํจ\", \"๊ณ ์ ๋ถ๊ฐ ํ๊ฑฐ์\"],\n",
" \"๊ณผ์ด\": [\"๊ณผ์ด ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํจ\", \"์จ๋๊ฐ ๊ณผ๋ํ๊ฒ ์์นํจ\", \"์ด์ด ์ฌํ๊ฒ ๋จ\"], \"์ค์๋\": [\"์ค์๋์ด ๋ฐ๋ณต๋จ\", \"์๋์ด ๋ถ์์ ํจ\", \"๊ฐํ์ ์ผ๋ก ๋ฉ์ถค\"]}\n",
"DNAMES = list(DEFECTS)\n",
"SEV = [\"Minor\", \"Major\", \"Critical\"]\n",
"SEV_CUE = {0: [\"๊ฒฝ๋ฏธํ ์์ค์\", \"์ด์ฉ์๋ ์ง์ฅ์ด ์์\", \"์ก์ ์ ๊ฒ์ ์ฌ์ํ ์ด์์\", \"๊ธฐ๋ฅ์๋ ์ํฅ์ด ์์\"],\n",
" 1: [\"์ฑ๋ฅ ์ ํ๊ฐ ์์\", \"์กฐ์ํ ์ ๋น๊ฐ ํ์ํจ\", \"์ถ๋ ฅ์ด ๋จ์ด์ง๋ ์ํ์\", \"๊ธฐ๋ฅ์ด ๋ถ๋ถ์ ์ผ๋ก ์ ํ๋จ\"],\n",
" 2: [\"์ฆ์ ์ด์ฉ ์ค์งํจ\", \"์์ ์ ์ํ์ด ์์ด ์ฌ์ฉ ๋ถ๊ฐ\", \"์๋ ์ ์ง๋ก ์๋ฌด ์ํ์ด ์ด๋ ค์\", \"์ธ๋ช
์์ ์ํ์ด ์ฐ๋ ค๋จ\"]}\n",
"CAUSES = [\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\", \"์ด์ฉ ๋ถ์ฃผ์\", \"์ค๊ณ ์ทจ์ฝ\", \"์์ฐ ๋
ธํ\"]\n",
"CAUSE_CUE = {\n",
" \"์ ์กฐ ๋ถ๋\": [\"์ ํ ์ธ์ ์งํ ๋ฐ์ํจ\", \"์ถ๊ณ ์ํ์์ ์ด๋ฏธ ์ด์์ด ์์์\", \"์ธ์ ํ ์ผ๋ง ์ง๋์ง ์์ ๋ฐ์ํจ\"],\n",
" \"์ ๋น ํ์ง\": [\"์ธ์ฃผ ์ ๋น ํ ์ผ๋ง ๋์ง ์์ ์ฌ๋ฐํจ\", \"์ ๋น ํ ์ฌ์กฐ๋ฆฝ ์ํ๊ฐ ๋ถ๋ํจ\", \"์ ๋น ์งํ ๊ฐ์ ๋ถ์์์ ๋ค์ ๋ฐ์ํจ\"],\n",
" \"์ด์ฉ ๋ถ์ฃผ์\": [\"ํ์ง ๊ธฐ๋ ์ค ์ถฉ๊ฒฉ ์ดํ ๋ฐ์ํจ\", \"๊ณผ๋ถํ ์ด์ฉ ํ ๋ฐ์ํจ\", \"์ ๊ฒ ์์ด ์ฐ์ ์ด์ฉํ ๋ค ๋ฐ์ํจ\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์์ด ๋ค๋ฅธ ์ฐจ๋์์๋ ๋ฐ๋ณต๋จ\", \"๊ตฌ์กฐ์ ์ผ๋ก ์ทจ์ฝํ ๋ถ์๋ก ๋ณด์\", \"์ฌ๋ฌ ๋ถ๋์์ ๊ฐ์ ํํ๋ก ๋ณด๊ณ ๋จ\"],\n",
" \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ์ด ๋๋ํจ\", \"์ฅ๊ธฐ๊ฐ ์ฌ์ฉ์ผ๋ก ๋
ธํ๋จ\", \"์๋ช
์ฃผ๊ธฐ ๋ง๊ธฐ์ ๋ฐ์ํจ\"]}\n",
"VENDORS = [\"์ธ์ฃผA\", \"์ธ์ฃผB\", \"์ธ์ฃผC\", \"์ธ์ฃผD\", \"๋ถ๋์ ๋น\"]\n",
"ACTIONS = [\"์ ๋น์ ์
๊ณ ๋ฅผ ์์ฒญํจ\", \"ํ์ฅ ์์ ์กฐ์น ํ ์ด์ฉ ์ค์\", \"๋ถํ ๊ตํ์ ์์ฒญํจ\", \"์ฌ๊ฒ์ฌ๋ฅผ ์์ฒญํจ\", \"์์ธ ํ์ธ์ด ํ์ํจ\"]\n",
"MAKER_OF = [p[7] for p in PARTS]\n",
"PRICE = np.array([p[6] for p in PARTS])\n",
"\n",
"\n",
"def usage_index(months, seed=0):\n",
" \"\"\"์๋ณ ํ๋ จ ๊ฐ๋(๋ฏธ๋ฆฌ ์ ์ ์๋ ๊ณํ ๊ฐ): ์ฐ 1ํ ์ ์ ์ ๊ฐ๋ ๊ณ์ + ์์ ์ก์\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 8] 10๋
์น ๋์ฉ๋ ์ผ์ QDR ํ
์คํธ ๋ฐ์ดํฐ ์ผ๊ด ๋ถ์ ์คํธ๋ ์ค ํ
์คํธ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_qdr(months=36, seed=1, scale=1.0, surge=True):\n",
" \"\"\"๋ฐํ: QDR DataFrame(๊ฑด๋น 1ํ), ์๋ณ usage, ๊ธ์ฆ ์ด๋ฒคํธ ๋ชฉ๋ก\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" use = usage_index(months, seed)\n",
" events = []\n",
" if surge:\n",
" for _ in range(max(2, months // 12 * 2)):\n",
" events.append((int(rng.integers(NP)), int(rng.integers(6, months))))\n",
" rows = []\n",
" wdef = [np.array([float(x.split(\":\")[1]) for x in p[8].split()]) for p in PARTS]\n",
" ddef = [[x.split(\":\")[0] for x in p[8].split()] for p in PARTS]\n",
" n = 0\n",
" for m in range(months):\n",
" for pi, p in enumerate(PARTS):\n",
" lam = p[2] * scale * (1 + 0.010 * m) * use[m] * (1 + p[3] * np.cos(2 * np.pi * (m % 12 - p[4]) / 12))\n",
" ev = (pi, m) in events\n",
" k = rng.poisson(lam * (2.6 if ev else 1.0))\n",
" for _ in range(k):\n",
" rows.append((n, m, pi, ev)); n += 1\n",
" N = len(rows)\n",
" A = np.array(rows)\n",
" month, part, ev = A[:, 1], A[:, 2], A[:, 3].astype(bool)\n",
" defect = np.array([rng.choice(ddef[i], p=wdef[i] / wdef[i].sum()) for i in part])\n",
" cr = np.array([PARTS[i][5][0] for i in part]); ma = np.array([PARTS[i][5][1] for i in part])\n",
" u = rng.random(N)\n",
" sev = np.where(u < cr, 2, np.where(u < cr + ma, 1, 0))\n",
" vendor = rng.choice(VENDORS, N, p=[0.25, 0.2, 0.2, 0.15, 0.2])\n",
" # ์์ธ: ๊ธฐ๋ณธ ๋น์จ์ ๋ถ์(์ ์กฐ์ฌ)ยท์
์ฒดยท์ด๋ฒคํธ ํจ๊ณผ๋ฅผ ๊ณฑํ๋ค\n",
" base = np.array([0.22, 0.20, 0.20, 0.16, 0.22])\n",
" P = np.tile(base, (N, 1))\n",
" P[vendor == \"์ธ์ฃผB\", 1] *= 2.6\n",
" P[ev, 0] *= 5.0\n",
" design = np.isin(part, [2, 3, 7]); P[design, 3] *= 2.2\n",
" P[np.isin(part, [4, 5, 11]), 0] *= 2.0 # ์ ์กฐ์ฌ MK3 ๋ฉํ ๋ถ์๋ ์ ์กฐ ๋ถ๋ ๋น์ค์ด ๋๋ค\n",
" P[defect == \"๋ง๋ชจ\", 4] *= 1.6; P[defect == \"๋์ \", 4] *= 1.3\n",
" P /= P.sum(axis=1, keepdims=True)\n",
" cause = np.array([CAUSES[rng.choice(5, p=P[i])] for i in range(N)])\n",
" since = np.where(cause == \"์ ๋น ํ์ง\", rng.exponential(35, N), rng.exponential(160, N)).clip(1, 720).round().astype(int)\n",
" wait = (rng.lognormal(1.3, 0.5, N) * np.where(vendor == \"์ธ์ฃผB\", 1.7, np.where(vendor == \"๋ถ๋์ ๋น\", 0.6, 1.0)) * (0.7 + 0.5 * use[month])).round(1)\n",
" reins = rng.random(N) < np.where(cause == \"์ ๋น ํ์ง\", 0.6, 0.08)\n",
" texts = []\n",
" for i in range(N):\n",
" pt = PARTS[part[i]]\n",
" cl = [f\"{rng.choice(pt[1])}์์ {rng.choice(DEFECTS[defect[i]])}\"]\n",
" if rng.random() < 0.7: cl.append(rng.choice(SEV_CUE[sev[i]]))\n",
" if rng.random() < 0.65: # ์์ธ ๋จ์: 8%๋ ์ค์ ์์ธ๊ณผ ๋ค๋ฅธ ๋จ์๊ฐ ์ ํ๋ค(๋ณด๊ณ ์์ ์ถ์ )\n",
" cc = cause[i] if rng.random() > 0.08 else rng.choice(CAUSES)\n",
" cl.append(rng.choice(CAUSE_CUE[cc]))\n",
" cl.append(rng.choice(ACTIONS))\n",
" if rng.random() < 0.5: cl.insert(0, f\"VU-{rng.integers(1, 31):02d} ์์\")\n",
" texts.append(\". \".join(cl) + \".\")\n",
" df = pd.DataFrame({\"qdr_id\": [f\"Q{seed}{i:06d}\" for i in range(N)], \"month\": month, \"unit\": [f\"VU-{x:02d}\" for x in rng.integers(1, 31, N)],\n",
" \"part\": part, \"defect\": defect, \"sev\": sev, \"cause\": cause, \"vendor\": vendor, \"maker\": [MAKER_OF[i] for i in part],\n",
" \"since_maint\": since, \"inspect_wait\": wait, \"reinspect\": reins, \"event\": ev, \"text\": texts})\n",
" df[\"cost\"] = PRICE[df[\"part\"]] * np.array([1.0, 2.0, 4.0])[df[\"sev\"]] # [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์๋ฆฌยท์กฐ์น ๋น์ฉ์ง์\n",
" return df, use, events\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# ๊ณตํต ํ
์คํธ ์กฐ๊ฐ: ์ ๊ทํ, ๊ฐ์ด ํ ํฐํ(์กฐ์ฌ ์ ๊ฑฐ), ๋ถ์ยท๊ฒฐํจยท์น๋ช
๋ยท์์ธ ํค์๋ ๋ผ๋ฒจ๋ง\n",
"# ---------------------------------------------------------------------------\n",
"TYPO = {\"๋์ณ\": \"๋์ \", \"๊ท ๋ ฌ\": \"๊ท ์ด\", \"์ ์ถ ๋ถ๋\": \"์ ์ด ๋ถ๋\", \"๋ถ์ฉ\": \"๋ถ์\", \"๋ง๋ฌ\": \"๋ง๋ชจ\", \"๊ณผ๋ ฌ\": \"๊ณผ์ด\", \"์ค์๋ฅ\": \"์ค์๋\", \"์ฌ๋\": \"์ ๋\"}\n",
"ABBR = {\"T/M\": \"๋ณ์๊ธฐ\", \"BAT\": \"๋ฐฐํฐ๋ฆฌ\"}\n",
"PART_KW = sorted([(a, i) for i, p in enumerate(PARTS) for a in p[1]], key=lambda x: -len(x[0]))\n",
"DEF_KW = {\"๊ท ์ด\": [\"๊ท ์ด\", \"ํฌ๋\", \"๊ฐ๋ผ\"], \"๋์ \": [\"๋์ \", \"์์ด\", \"๋์ค\"], \"๋ง๋ชจ\": [\"๋ง๋ชจ\", \"๋ณ์\"], \"๋ถ์\": [\"๋ถ์\", \"๋
น์ด\", \"์ฐํ\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋\", \"๋จ์ \", \"๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ\", \"์ฒด๊ฒฐ\", \"ํ๊ฑฐ\"], \"๊ณผ์ด\": [\"๊ณผ์ด\", \"์จ๋\", \"์ด์ด\"], \"์ค์๋\": [\"์ค์๋\", \"๋ถ์์ \", \"๋ฉ์ถค\"]}\n",
"SEV_KW = [(2, [\"์ฆ์ ์ด์ฉ ์ค์ง\", \"์ฌ์ฉ ๋ถ๊ฐ\", \"์ํ\"]), (1, [\"์ฑ๋ฅ ์ ํ\", \"์กฐ์ํ\"]), (0, [\"๊ฒฝ๋ฏธ\", \"์ง์ฅ์ด ์\"])]\n",
"CAUSE_KW = {\"์ ์กฐ ๋ถ๋\": [\"์ ํ\", \"์ถ๊ณ \", \"์ธ์\"], \"์ ๋น ํ์ง\": [\"์ ๋น ํ\", \"์ฌ์กฐ๋ฆฝ\", \"์ ๋น ์งํ\"], \"์ด์ฉ ๋ถ์ฃผ์\": [\"์ถฉ๊ฒฉ\", \"๊ณผ๋ถํ\", \"์ ๊ฒ ์์ด\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์\", \"๊ตฌ์กฐ์ \", \"์ฌ๋ฌ ๋ถ๋\"], \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ\", \"๋
ธํ\", \"์๋ช
\"]}\n",
"PARTICLES = (\"์์\", \"์ผ๋ก\", \"์ด\", \"๊ฐ\", \"์\", \"๋\", \"์\", \"๋ฅผ\", \"์\", \"์\", \"๋ก\", \"์\", \"๊ณผ\", \"๋\", \"ํจ\", \"์\", \"๋จ\")\n",
"\n",
"\n",
"def norm(t):\n",
" \"\"\"์ ๊ทํ: ์ ๊ฐ->๋ฐ๊ฐ, ํน์๋ฌธ์ยท์ค๋ณต ๊ณต๋ฐฑ ์ ๊ฑฐ, ์ฝ์ด ํ์ฅ, ์คํ ๊ต์ \"\"\"\n",
" # [TODO : ํ์ ์ค์ต 8] 10๋
์น ๋์ฉ๋ ์ผ์ QDR ํ
์คํธ ๋ฐ์ดํฐ ์ผ๊ด ๋ถ์ ์คํธ๋ ์ค ํ
์คํธ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tok(t):\n",
" out = []\n",
" for w in re.split(r\"[\\s.,]+\", norm(t)):\n",
" if not w or re.fullmatch(r\"[\\d\\-A-Za-z]+\", w):\n",
" continue\n",
" for p in PARTICLES:\n",
" if len(w) > len(p) + 1 and w.endswith(p):\n",
" w = w[:-len(p)]\n",
" break\n",
" out.append(w)\n",
" return out\n",
"\n",
"\n",
"def label_part(t):\n",
" for a, i in PART_KW:\n",
" if a in t:\n",
" return i\n",
" return -1\n",
"\n",
"\n",
"def label_defect(t):\n",
" for d, ks in DEF_KW.items():\n",
" if any(k in t for k in ks):\n",
" return d\n",
" return \"๋ฏธ๋ถ๋ฅ\"\n",
"\n",
"\n",
"def label_sev(t):\n",
" for s, ks in SEV_KW:\n",
" if any(k in t for k in ks):\n",
" return s\n",
" return -1\n",
"\n",
"\n",
"def label_cause(t):\n",
" for c, ks in CAUSE_KW.items():\n",
" if any(k in t for k in ks):\n",
" return c\n",
" return \"๋ฏธ์\"\n",
"\n",
"names = [p[0] for p in PARTS]\n",
"t0 = time.perf_counter()\n",
"full, use, events = make_qdr(120, seed=3, scale=1.5)\n",
"print(f\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] 10๋
(120๊ฐ์) QDR {len(full)}๊ฑด ์์ฑ({time.perf_counter() - t0:.1f}์ด, ๋ถ์ ์๊ฐ์๋ ํฌํจํ์ง ์์), ๋ถ์ {NP}์ข
, ๊ธ์ฆ ์ด๋ฒคํธ {len(events)}๊ฑด\")\n",
"rng = np.random.default_rng(0)\n",
"RE_SPEC, RE_SPACE = re.compile(r\"[!#~*@^]+\"), re.compile(r\"\\s+\")\n",
"PART_RE = [(re.compile(\"|\".join(map(re.escape, p[1]))), i) for i, p in enumerate(PARTS)]\n",
"DEF_RE = [(d, re.compile(\"|\".join(ks))) for d, ks in DEF_KW.items()]\n",
"\n",
"\n",
"def analyze(d):\n",
" \"\"\"์ผ๊ด ๋ถ์: ๊ฒ์ฆ(๊ฒฉ๋ฆฌ) -> ์ ๊ทํ -> ๋ถ์ยท๊ฒฐํจยท์น๋ช
๋ ๋ผ๋ฒจ๋ง. ๋ฐํ: (์ ์ ๊ฒฐ๊ณผ, ๊ฒฉ๋ฆฌ ๊ฑด์)\"\"\"\n",
" ok = d[\"text\"].str.len().between(5, 400) & ~d[\"text\"].str.contains(\"๏ฟฝ\", regex=False)\n",
" g = d[ok].copy()\n",
" g[\"t\"] = g[\"text\"].str.replace(RE_SPEC, \" \", regex=True).str.replace(RE_SPACE, \" \", regex=True).str.strip()\n",
" g[\"p_lab\"] = -1\n",
" for rx, i in PART_RE:\n",
" g.loc[(g[\"p_lab\"] == -1) & g[\"t\"].str.contains(rx), \"p_lab\"] = i\n",
" g[\"d_lab\"] = \"๋ฏธ๋ถ๋ฅ\"\n",
" for dn, rx in DEF_RE:\n",
" g.loc[(g[\"d_lab\"] == \"๋ฏธ๋ถ๋ฅ\") & g[\"t\"].str.contains(rx), \"d_lab\"] = dn\n",
" g[\"s_lab\"] = np.where(g[\"t\"].str.contains(\"์ฆ์ ์ด์ฉ ์ค์ง|์ฌ์ฉ ๋ถ๊ฐ|์ํ|์๋ฌด ์ํ์ด ์ด๋ ค์|์ํ\"), 2, np.where(g[\"t\"].str.contains(\"์ฑ๋ฅ ์ ํ|์กฐ์ํ|์ถ๋ ฅ์ด|๋ถ๋ถ์ ์ผ๋ก\"), 1, 0))\n",
" return g, int((~ok).sum())\n",
"\n",
"\n",
"def alarms(g, months):\n",
" \"\"\"์ยท๋ถ์ ์ง๊ณ ํ ์ง์ 12๊ฐ์ ๋ฐ์๋ฅ (ํ๋ จ ๊ฐ๋ ๋ณด์ ) ๋๋น ๊ธ์ฆ ๊ฒฝ๋ณด\"\"\"\n",
" M = g.pivot_table(index=\"p_lab\", columns=\"month\", values=\"qdr_id\", aggfunc=\"size\", fill_value=0).reindex(index=range(NP), columns=range(months), fill_value=0)\n",
" rate = (M / use[:months]).T.rolling(12, min_periods=6).mean().shift(1).T\n",
" exp = rate * use[:months]\n",
" z = (M - exp) / np.sqrt(exp.clip(lower=1))\n",
" return M, (M / exp.clip(lower=1) > 1.9) & (z > 4)\n",
"\n",
"\n",
"def make_input(n_months, bad=0.01):\n",
" d = full[full[\"month\"] < n_months].copy()\n",
" k = int(len(d) * bad)\n",
" idx = rng.choice(len(d), k, replace=False)\n",
" d.iloc[idx[: k // 3], d.columns.get_loc(\"text\")] = \"\"\n",
" d.iloc[idx[k // 3: 2 * k // 3], d.columns.get_loc(\"text\")] = \"๏ฟฝ๏ฟฝ๏ฟฝ ์ค๋ฅ\"\n",
" d.iloc[idx[2 * k // 3:], d.columns.get_loc(\"text\")] = \"๊ฐ\" * 900\n",
" return d, k\n",
"\n",
"\n",
"print(\"\\n[๊ธฐ๊ฐ๋ณ ์ผ๊ด ๋ถ์] ๊ธฐ๊ฐ | ์
๋ ฅ | ์ ์ | ๊ฒฉ๋ฆฌ | ์ ์ค | ๋ถ์ ์๊ฐ | ์ฒ๋ฆฌ๋\")\n",
"res = {}\n",
"for months in (12, 36, 72, 120):\n",
" d, k = make_input(months)\n",
" t = time.perf_counter()\n",
" g, q = analyze(d)\n",
" M, A = alarms(g, months)\n",
" sec = time.perf_counter() - t\n",
" res[months] = (g, M, A, sec)\n",
" print(f\" {months // 12:2d}๋
{len(d):7d} {len(g):7d} {q:5d} {len(d) - len(g) - q:3d} {sec:6.2f}์ด {len(d) / sec:8.0f}๊ฑด/์ด\")\n",
"g, M, A, sec = res[120]\n",
"\n",
"# ๋ผ๋ฒจ๋ง ํ์ง๊ณผ ๊ฒฝ๋ณด ์ ํ๋\n",
"tr_ = full.set_index(\"qdr_id\").loc[g[\"qdr_id\"]]\n",
"print(f\"\\n๋ผ๋ฒจ๋ง ์ ํ๋(10๋
์น): ๋ถ์ {(g['p_lab'].values == tr_['part'].values).mean():.1%}, ๊ฒฐํจ ์ ํ {(g['d_lab'].values == tr_['defect'].values).mean():.1%}, ์น๋ช
๋ {(g['s_lab'].values == tr_['sev'].values).mean():.1%} (๋จ์ ์๋ ๊ฑด์ Minor ์ฒ๋ฆฌ)\")\n",
"truth = np.zeros((NP, 120), bool)\n",
"for p, m in events:\n",
" truth[p, m] = True\n",
"valid = ~np.isnan(A.values.astype(float)) & (np.arange(120) >= 12)[None, :]\n",
"al = A.values & valid\n",
"tp, fp, fn = (al & truth).sum(), (al & ~truth).sum(), (~al & truth & valid).sum()\n",
"acc = ((al == truth) & valid).sum() / valid.sum()\n",
"print(f\"๊ธ์ฆ ๊ฒฝ๋ณด(12๊ฐ์ ์ดํ {int(valid.sum())}๊ฐ ๋ถ์-์ ์
): ๊ฒฝ๋ณด ์ ํ๋ {acc:.1%} (๋ชฉํ 95% ์ด์: {'๋ฌ์ฑ' if acc >= 0.95 else '๋ฏธ๋ฌ'}), ์ฌํ์จ {tp / max(tp + fn, 1):.0%}, ์ ๋ฐ๋ {tp / max(tp + fp, 1):.0%}, ์ค์ ์ด๋ฒคํธ {int((truth & valid).sum())}๊ฑด\")\n",
"\n",
"# ๋์๋ณด๋ ํ๋ฉด ๊ตฌ์ฑ: 10๋
์น ์ง๊ณํ์์ ํํธ๋งตยท์์ 5 ๋ถ์ยท๊ฒฝ๋ณด ๋ชฉ๋ก์ ๋ง๋๋ ์๊ฐ, ํํฐ(๋ถ์ยท๊ธฐ๊ฐ) ์๋ต ์๊ฐ\n",
"t = time.perf_counter()\n",
"heat = M.values.reshape(NP, 10, 12).sum(axis=2)\n",
"top5 = M.sum(axis=1).sort_values(ascending=False).head(5)\n",
"alert_list = [(names[p], m) for p, m in zip(*np.where(al))]\n",
"load = time.perf_counter() - t\n",
"t = time.perf_counter()\n",
"for p in range(NP):\n",
" sub = g[(g[\"p_lab\"] == p) & (g[\"month\"] >= 84)]\n",
" _ = sub.groupby(\"d_lab\").size()\n",
"flt = (time.perf_counter() - t) / NP\n",
"print(f\"ํ๋ฉด ๊ตฌ์ฑ(์ฐ๋๋ณ ํํธ๋งตยท์์ 5ยท๊ฒฝ๋ณด {len(alert_list)}๊ฑด) {load * 1000:.0f}ms, ํํฐ 1ํ ์๋ต {flt * 1000:.0f}ms (๋ชฉํ 2์ด ์ด๋ด: {'๋ฌ์ฑ' if max(load, flt) <= 2 else '๋ฏธ๋ฌ'})\")\n",
"print(\"์์ 5 ๋ถ์: \" + \", \".join(f\"{names[p]} {v}\" for p, v in top5.items()))\n",
"print(\"์ด์ฉ ๊ธฐ์ค: ์ผ๊ด ๋ถ์์ ์ผ๊ฐ ๋ฐฐ์น๋ก ์ง๊ณํ๋ฅผ ๊ฐฑ์ ํ๊ณ ํ๋ฉด์ ์ง๊ณํ๋ง ์ฝ๋๋ค. ์ฒ๋ฆฌ๊ฐ ๋๋ ค์ง๋ฉด ์ต๊ทผ 36๊ฐ์๋ง ์ฌ๊ณ์ฐํ๊ณ ๊ฒฉ๋ฆฌ์จ์ด 5%๋ฅผ ๋์ผ๋ฉด ์์ ์
๋ ฅ ์ฒด๊ณ๋ฅผ ์ ๊ฒํ๋ค.\")\n",
"\n",
"plt.figure(figsize=(6, 4))\n",
"ms = sorted(res)\n",
"plt.plot([m // 12 for m in ms], [res[m][3] for m in ms], marker=\"o\"); plt.xlabel(\"years of QDR\"); plt.ylabel(\"analysis seconds\"); plt.title(\"Bulk analysis time\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
08 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ต์ก์ฉ๊ฐ์์๋ฃ] 10๋
(120๊ฐ์) QDR 62293๊ฑด ์์ฑ(6.5์ด, ๋ถ์ ์๊ฐ์๋ ํฌํจํ์ง ์์), ๋ถ์ 12์ข
, ๊ธ์ฆ ์ด๋ฒคํธ 20๊ฑด\n",
"\n",
"[๊ธฐ๊ฐ๋ณ ์ผ๊ด ๋ถ์] ๊ธฐ๊ฐ | ์
๋ ฅ | ์ ์ | ๊ฒฉ๋ฆฌ | ์ ์ค | ๋ถ์ ์๊ฐ | ์ฒ๋ฆฌ๋\n",
" 1๋
4177 4136 41 0 0.14์ด 30805๊ฑด/์ด\n",
" 3๋
14068 13928 140 0 0.36์ด 39039๊ฑด/์ด\n",
" 6๋
32040 31720 320 0 0.77์ด 41671๊ฑด/์ด\n",
" 10๋
62293 61671 622 0 1.78์ด 34950๊ฑด/์ด\n",
"\n",
"๋ผ๋ฒจ๋ง ์ ํ๋(10๋
์น): ๋ถ์ 100.0%, ๊ฒฐํจ ์ ํ 100.0%, ์น๋ช
๋ 87.0% (๋จ์ ์๋ ๊ฑด์ Minor ์ฒ๋ฆฌ)\n",
"๊ธ์ฆ ๊ฒฝ๋ณด(12๊ฐ์ ์ดํ 1296๊ฐ ๋ถ์-์ ์
): ๊ฒฝ๋ณด ์ ํ๋ 99.8% (๋ชฉํ 95% ์ด์: ๋ฌ์ฑ), ์ฌํ์จ 90%, ์ ๋ฐ๋ 100%, ์ค์ ์ด๋ฒคํธ 20๊ฑด\n",
"ํ๋ฉด ๊ตฌ์ฑ(์ฐ๋๋ณ ํํธ๋งตยท์์ 5ยท๊ฒฝ๋ณด 18๊ฑด) 1ms, ํํฐ 1ํ ์๋ต 4ms (๋ชฉํ 2์ด ์ด๋ด: ๋ฌ์ฑ)\n",
"์์ 5 ๋ถ์: ์์ง ์ฐ๋ฃ๊ณํต 7848, ์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ 7816, ๋๊ฐ ๊ณํต 7095, ๋ณ์๊ธฐ 6301, ์ ๋ ์ฅ์น 5667\n",
"์ด์ฉ ๊ธฐ์ค: ์ผ๊ด ๋ถ์์ ์ผ๊ฐ ๋ฐฐ์น๋ก ์ง๊ณํ๋ฅผ ๊ฐฑ์ ํ๊ณ ํ๋ฉด์ ์ง๊ณํ๋ง ์ฝ๋๋ค. ์ฒ๋ฆฌ๊ฐ ๋๋ ค์ง๋ฉด ์ต๊ทผ 36๊ฐ์๋ง ์ฌ๊ณ์ฐํ๊ณ ๊ฒฉ๋ฆฌ์จ์ด 5%๋ฅผ ๋์ผ๋ฉด ์์ ์
๋ ฅ ์ฒด๊ณ๋ฅผ ์ ๊ฒํ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
09. AAR ๊ธฐ๋ฐ ๊ฒฐํจ ์์ธ ์ค๋ถ๋ฅ ๋ถ์ ๋ฐ ํ
์คํธ ๋ง์ด๋ ๋ฃฐ์
์ฌํ๋\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"AAR ๊ธฐ๋ฐ ๊ฒฐํจ ์์ธ ์ค๋ถ๋ฅ๋ฅผ ๋ถ์ํ๊ณ ๋ง์ด๋ ๋ฃฐ์
์ ์ฌํ๋ํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **AAR ๊ธฐ๋ฐ์ผ๋ก ๊ฒฐํจ ์์ธ ์ค๋ถ๋ฅ ์ฌ๋ก๋ฅผ ๋ถ์ํ๊ณ ํ
์คํธ ๋ง์ด๋ ๋ฃฐ์
์ ๋ณด์กฐ ๊ท์น์ผ๋ก ์ฌํ๋ํด ํจ๊ณผ๋ฅผ ์งํ๋ก ์ฐ์ถํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **AAR ๊ธฐ๊ฐ ์์ธ ๋ถ๋ฅ ์ค๋ฅ๋ฅผ ์์ธ ๋จ์ ์์๊ณผ ๋จ์-์ค์ ์์ธ ๋ถ์ผ์น๋ก ๋๋๊ณ ๋ํ ์ฌ๋ก ์ ๋ฆฌํ๊ณ ๊ท๊ฒฉํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `reports/qlt05_roi_analysis.md` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ถ์ ์ฐ์์ ๊ฐ๊ด์ฑ ๋ฐ ๊ตฐ ์ฌ์ /๊ตฐ์ ๊ธฐํ ๋ถ์ ์ค๋๋ ฅ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ๋น์ฉ ์ ๊ฐ์ก, ์๊ฐ ๋จ์ถ๋ฅ ๋ฑ 3๋ ์ ๋ ํจ๊ณผ ์งํ ์ฐ์ถ\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ROI ๋ถ์ ๋ณด๊ณ ์ ์์ฑ ๋ฐ ์ฃผ์ ์งํ ๋ฏผ๊ฐ๋ ๋ถ์ ์๋ก\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ํ๊ณ/๊ธฐํ ๊ธฐ์ค ๋ชจ๋ธ๊ณผ์ ๋น๊ต ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"AAR ๊ธฐ๊ฐ(12~23๊ฐ์) QDR์ ๊ฒฐํจ ์์ธ ๋ถ๋ฅ ์ค๋ฅ๋ฅผ ์์ธ๋ณ๋ก ๋ถ์ํ๊ณ , ํค์๋ ๊ธฐ๋ฐ ํ
์คํธ ๋ง์ด๋ ๋ฃฐ์
์ ์ธ์ฃผ ์ ๋น ํ ๊ฒฝ๊ณผ์ผยท์
์ฒดยท๊ฒฐํจ ์ ํ ๋ฑ ๋ณด์กฐ ๊ท์น(๊ฒฐ์ ํธ๋ฆฌ๋ก ๋์ถ)์ ๋ํด ์ฌํ๋ํ ๋ค, ๊ฒ์ฆ ๊ธฐ๊ฐ(24~35๊ฐ์)์์ ์ ํ๋์ ์ค๋ถ๋ฅ ๋น์ฉ ๋ณํ๋ฅผ ํ์ธํ๊ณ 3๋ ์ ๋ ํจ๊ณผ ์งํ(๋น์ฉ ์ ๊ฐ, ํ์ธ ์๊ฐ ๋จ์ถ, ์ ์กฐยท์ ๋น ์ฑ
์ ๊ฑด ์๋ณ ์ฆ๊ฐ)์ ๋์
๋ก๋๋งต์ ์ฐ์ถํ๋ค.\"\"\"\n",
"import re\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"from sklearn.tree import DecisionTreeClassifier, export_text\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR) ์์ฑ๊ธฐ (์ค์ ๋ถ๋ยท์ฅ๋นยท์
์ฒดยท์ ๋น ์ค์ ์์)\n",
"# ๊ฐ์ ์ฅ๋น ๋ถ์ 12์ข
, ๊ฒฐํจ ์ ํ 8์ข
, ์น๋ช
๋(Critical/Major/Minor), ์์ธ 5์ข
, ๊ฐ์ ์ธ์ฃผ์ ๋น ์
์ฒด 4๊ณณ + ๋ถ๋์ ๋น.\n",
"# QDR 1๊ฑด๋ง๋ค ํ๊ตญ์ด ์์ ํ
์คํธ๋ฅผ ๋ง๋ค๊ณ , ์๋ณ ๊ฑด์๋ ๊ณ์ ยท์ถ์ธยทํ๋ จ ๊ฐ๋(usage)ยท์ผ๋ถ ์ ์กฐ ๋ถ๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ๋ฅธ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"PARTS = [ # (์ด๋ฆ, ๋ณ์นญ, ์ ๊ธฐ์ค ๊ฑด์, ๊ณ์ ์งํญ, ์ ์ ์, (Critical, Major ๋น์จ), ๊ฐ์ ๋จ๊ฐ์ง์, ์ ์กฐ์ฌ, ์ฃผ์ ๊ฒฐํจ ๊ฐ์ค์น)\n",
" (\"์์ง ์ฐ๋ฃ๊ณํต\", [\"์ฐ๋ฃ๊ณํต\", \"์ฐ๋ฃ ๋ผ์ธ\", \"์ฐ๋ฃ ํํ\"], 26, 0.15, 7, (0.10, 0.35), 6.0, \"MK1\", \"๋์ :4 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:2 ์ค์๋:2\"),\n",
" (\"๋ณ์๊ธฐ\", [\"๋ณ์๊ธฐ\", \"T/M\", \"๋ฏธ์
\"], 22, 0.10, 8, (0.12, 0.38), 9.0, \"MK1\", \"๋ง๋ชจ:4 ๋์ :2 ๊ณผ์ด:3 ์ค์๋:1\"),\n",
" (\"์ ๋ ์ฅ์น\", [\"์ ๋ ์ฅ์น\", \"๋ธ๋ ์ดํฌ\", \"์ ๋ ๋ผ์ธ\"], 20, 0.20, 12, (0.26, 0.40), 5.0, \"MK2\", \"๋ง๋ชจ:4 ๊ท ์ด:2 ๋์ :2 ์ฒด๊ฒฐ๋ถ๋:2\"),\n",
" (\"์กฐํฅ ์ฅ์น\", [\"์กฐํฅ ์ฅ์น\", \"ํธ๋ค ์ฐ๋๋ถ\", \"์กฐํฅ ๋ก๋\"], 14, 0.10, 6, (0.30, 0.40), 5.0, \"MK2\", \"์ฒด๊ฒฐ๋ถ๋:4 ๊ท ์ด:2 ๋ง๋ชจ:3 ์ค์๋:1\"),\n",
" (\"๋๊ฐ ๊ณํต\", [\"๋๊ฐ ๊ณํต\", \"๋ผ๋์์ดํฐ\", \"๋๊ฐ์ ๋ผ์ธ\"], 24, 0.30, 7, (0.05, 0.30), 3.0, \"MK3\", \"๋์ :4 ๊ณผ์ด:4 ๋ถ์:2\"),\n",
" (\"์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ\", [\"๋ฐฐํฐ๋ฆฌ\", \"์ ๊ธฐ ๊ณํต\", \"๋ฐ์ ๊ธฐ\"], 28, 0.35, 1, (0.04, 0.28), 2.5, \"MK3\", \"์ ์ด๋ถ๋:4 ๋ถ์:2 ๊ณผ์ด:2 ์ค์๋:2\"),\n",
" (\"ํต์ ๋ชจ๋\", [\"ํต์ ๋ชจ๋\", \"๋ฌด์ ๊ธฐ ์ฐ๋๋ถ\", \"์ํ
๋ ์ ์๋ถ\"], 16, 0.05, 4, (0.03, 0.25), 4.0, \"MK4\", \"์ ์ด๋ถ๋:5 ์ค์๋:4 ๋ถ์:1\"),\n",
" (\"์ ์ ๊ณํต\", [\"์ ์ ๊ณํต\", \"์ ์ ํธ์ค\", \"์ ์ ์ค๋ฆฐ๋\"], 18, 0.10, 9, (0.10, 0.35), 4.5, \"MK2\", \"๋์ :5 ๊ท ์ด:2 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"ํ๊ฐ ์ฅ์น\", [\"ํ๊ฐ ์ฅ์น\", \"์์คํ์
\", \"์์ถฉ๊ธฐ\"], 15, 0.08, 5, (0.06, 0.30), 3.5, \"MK4\", \"๋ง๋ชจ:4 ๊ท ์ด:3 ์ฒด๊ฒฐ๋ถ๋:2 ๋ถ์:1\"),\n",
" (\"์ฐจ์ฒด ์ฉ์ ๋ถ\", [\"์ฐจ์ฒด ์ฉ์ ๋ถ\", \"ํ๋ ์ ์ฉ์ ๋ถ\", \"์ฐจ์ฒด ์ด์๋ถ\"], 10, 0.05, 10, (0.28, 0.40), 7.0, \"MK1\", \"๊ท ์ด:6 ๋ถ์:3 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ดํยท์กฐ์ค ์ฅ๋น\", [\"๊ดํ ์ฅ๋น\", \"์กฐ์ค๊ฒฝ\", \"๊ด์ธก ์ฅ๋น\"], 8, 0.05, 3, (0.03, 0.22), 8.0, \"MK4\", \"์ค์๋:4 ์ ์ด๋ถ๋:3 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ณต๊ธฐ ์ ํ ํํฐ\", [\"๊ณต๊ธฐ ํํฐ\", \"ํก๊ธฐ ํํฐ\", \"์ ํ ์ฅ์น\"], 12, 0.25, 4, (0.01, 0.12), 1.0, \"MK3\", \"๋ง๋ชจ:5 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:3\")]\n",
"NP = len(PARTS)\n",
"DEFECTS = { # ๊ฒฐํจ ์ ํ: ์ฆ์ ํํ(๋์์ด)\n",
" \"๊ท ์ด\": [\"๊ท ์ด์ด ๋ฐ๊ฒฌ๋จ\", \"ํฌ๋์ด ํ์ธ๋จ\", \"๊ฐ๋ผ์ง์ด ์์\"], \"๋์ \": [\"๋์ ๊ฐ ๋ฐ์ํจ\", \"์ค์ผ์ด ์์ด ๋์ด\", \"๋์ค ํ์ ์ด ์์\"],\n",
" \"๋ง๋ชจ\": [\"๋ง๋ชจ๊ฐ ์ฌํจ\", \"๊ณผ๋ํ ๋ง๋ชจ๊ฐ ํ์ธ๋จ\", \"๋ณ์ ํ์ ์ด ์์\"], \"๋ถ์\": [\"๋ถ์์ด ์งํ๋จ\", \"๋
น์ด ๋ฐ์ํจ\", \"์ฐํ ํ์ ์ด ์์\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋์ด ๋ฐ์ํจ\", \"์ปค๋ฅํฐ ๋จ์ ์ด ์์ฌ๋จ\", \"์ ํธ๊ฐ ๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ ํ๋ฆผ์ด ํ์ธ๋จ\", \"์ฒด๊ฒฐ ์ํ๊ฐ ๋ถ๋ํจ\", \"๊ณ ์ ๋ถ๊ฐ ํ๊ฑฐ์\"],\n",
" \"๊ณผ์ด\": [\"๊ณผ์ด ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํจ\", \"์จ๋๊ฐ ๊ณผ๋ํ๊ฒ ์์นํจ\", \"์ด์ด ์ฌํ๊ฒ ๋จ\"], \"์ค์๋\": [\"์ค์๋์ด ๋ฐ๋ณต๋จ\", \"์๋์ด ๋ถ์์ ํจ\", \"๊ฐํ์ ์ผ๋ก ๋ฉ์ถค\"]}\n",
"DNAMES = list(DEFECTS)\n",
"SEV = [\"Minor\", \"Major\", \"Critical\"]\n",
"SEV_CUE = {0: [\"๊ฒฝ๋ฏธํ ์์ค์\", \"์ด์ฉ์๋ ์ง์ฅ์ด ์์\", \"์ก์ ์ ๊ฒ์ ์ฌ์ํ ์ด์์\", \"๊ธฐ๋ฅ์๋ ์ํฅ์ด ์์\"],\n",
" 1: [\"์ฑ๋ฅ ์ ํ๊ฐ ์์\", \"์กฐ์ํ ์ ๋น๊ฐ ํ์ํจ\", \"์ถ๋ ฅ์ด ๋จ์ด์ง๋ ์ํ์\", \"๊ธฐ๋ฅ์ด ๋ถ๋ถ์ ์ผ๋ก ์ ํ๋จ\"],\n",
" 2: [\"์ฆ์ ์ด์ฉ ์ค์งํจ\", \"์์ ์ ์ํ์ด ์์ด ์ฌ์ฉ ๋ถ๊ฐ\", \"์๋ ์ ์ง๋ก ์๋ฌด ์ํ์ด ์ด๋ ค์\", \"์ธ๋ช
์์ ์ํ์ด ์ฐ๋ ค๋จ\"]}\n",
"CAUSES = [\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\", \"์ด์ฉ ๋ถ์ฃผ์\", \"์ค๊ณ ์ทจ์ฝ\", \"์์ฐ ๋
ธํ\"]\n",
"CAUSE_CUE = {\n",
" \"์ ์กฐ ๋ถ๋\": [\"์ ํ ์ธ์ ์งํ ๋ฐ์ํจ\", \"์ถ๊ณ ์ํ์์ ์ด๋ฏธ ์ด์์ด ์์์\", \"์ธ์ ํ ์ผ๋ง ์ง๋์ง ์์ ๋ฐ์ํจ\"],\n",
" \"์ ๋น ํ์ง\": [\"์ธ์ฃผ ์ ๋น ํ ์ผ๋ง ๋์ง ์์ ์ฌ๋ฐํจ\", \"์ ๋น ํ ์ฌ์กฐ๋ฆฝ ์ํ๊ฐ ๋ถ๋ํจ\", \"์ ๋น ์งํ ๊ฐ์ ๋ถ์์์ ๋ค์ ๋ฐ์ํจ\"],\n",
" \"์ด์ฉ ๋ถ์ฃผ์\": [\"ํ์ง ๊ธฐ๋ ์ค ์ถฉ๊ฒฉ ์ดํ ๋ฐ์ํจ\", \"๊ณผ๋ถํ ์ด์ฉ ํ ๋ฐ์ํจ\", \"์ ๊ฒ ์์ด ์ฐ์ ์ด์ฉํ ๋ค ๋ฐ์ํจ\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์์ด ๋ค๋ฅธ ์ฐจ๋์์๋ ๋ฐ๋ณต๋จ\", \"๊ตฌ์กฐ์ ์ผ๋ก ์ทจ์ฝํ ๋ถ์๋ก ๋ณด์\", \"์ฌ๋ฌ ๋ถ๋์์ ๊ฐ์ ํํ๋ก ๋ณด๊ณ ๋จ\"],\n",
" \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ์ด ๋๋ํจ\", \"์ฅ๊ธฐ๊ฐ ์ฌ์ฉ์ผ๋ก ๋
ธํ๋จ\", \"์๋ช
์ฃผ๊ธฐ ๋ง๊ธฐ์ ๋ฐ์ํจ\"]}\n",
"VENDORS = [\"์ธ์ฃผA\", \"์ธ์ฃผB\", \"์ธ์ฃผC\", \"์ธ์ฃผD\", \"๋ถ๋์ ๋น\"]\n",
"ACTIONS = [\"์ ๋น์ ์
๊ณ ๋ฅผ ์์ฒญํจ\", \"ํ์ฅ ์์ ์กฐ์น ํ ์ด์ฉ ์ค์\", \"๋ถํ ๊ตํ์ ์์ฒญํจ\", \"์ฌ๊ฒ์ฌ๋ฅผ ์์ฒญํจ\", \"์์ธ ํ์ธ์ด ํ์ํจ\"]\n",
"MAKER_OF = [p[7] for p in PARTS]\n",
"PRICE = np.array([p[6] for p in PARTS])\n",
"\n",
"\n",
"def usage_index(months, seed=0):\n",
" \"\"\"์๋ณ ํ๋ จ ๊ฐ๋(๋ฏธ๋ฆฌ ์ ์ ์๋ ๊ณํ ๊ฐ): ์ฐ 1ํ ์ ์ ์ ๊ฐ๋ ๊ณ์ + ์์ ์ก์\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 9] AAR ๊ธฐ๋ฐ ๊ฒฐํจ ์์ธ ์ค๋ถ๋ฅ ๋ถ์ ๋ฐ ํ
์คํธ ๋ง์ด๋ ๋ฃฐ์
์ฌํ๋ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_qdr(months=36, seed=1, scale=1.0, surge=True):\n",
" \"\"\"๋ฐํ: QDR DataFrame(๊ฑด๋น 1ํ), ์๋ณ usage, ๊ธ์ฆ ์ด๋ฒคํธ ๋ชฉ๋ก\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" use = usage_index(months, seed)\n",
" events = []\n",
" if surge:\n",
" for _ in range(max(2, months // 12 * 2)):\n",
" events.append((int(rng.integers(NP)), int(rng.integers(6, months))))\n",
" rows = []\n",
" wdef = [np.array([float(x.split(\":\")[1]) for x in p[8].split()]) for p in PARTS]\n",
" ddef = [[x.split(\":\")[0] for x in p[8].split()] for p in PARTS]\n",
" n = 0\n",
" for m in range(months):\n",
" for pi, p in enumerate(PARTS):\n",
" lam = p[2] * scale * (1 + 0.010 * m) * use[m] * (1 + p[3] * np.cos(2 * np.pi * (m % 12 - p[4]) / 12))\n",
" ev = (pi, m) in events\n",
" k = rng.poisson(lam * (2.6 if ev else 1.0))\n",
" for _ in range(k):\n",
" rows.append((n, m, pi, ev)); n += 1\n",
" N = len(rows)\n",
" A = np.array(rows)\n",
" month, part, ev = A[:, 1], A[:, 2], A[:, 3].astype(bool)\n",
" defect = np.array([rng.choice(ddef[i], p=wdef[i] / wdef[i].sum()) for i in part])\n",
" cr = np.array([PARTS[i][5][0] for i in part]); ma = np.array([PARTS[i][5][1] for i in part])\n",
" u = rng.random(N)\n",
" sev = np.where(u < cr, 2, np.where(u < cr + ma, 1, 0))\n",
" vendor = rng.choice(VENDORS, N, p=[0.25, 0.2, 0.2, 0.15, 0.2])\n",
" # ์์ธ: ๊ธฐ๋ณธ ๋น์จ์ ๋ถ์(์ ์กฐ์ฌ)ยท์
์ฒดยท์ด๋ฒคํธ ํจ๊ณผ๋ฅผ ๊ณฑํ๋ค\n",
" base = np.array([0.22, 0.20, 0.20, 0.16, 0.22])\n",
" P = np.tile(base, (N, 1))\n",
" P[vendor == \"์ธ์ฃผB\", 1] *= 2.6\n",
" P[ev, 0] *= 5.0\n",
" design = np.isin(part, [2, 3, 7]); P[design, 3] *= 2.2\n",
" P[np.isin(part, [4, 5, 11]), 0] *= 2.0 # ์ ์กฐ์ฌ MK3 ๋ฉํ ๋ถ์๋ ์ ์กฐ ๋ถ๋ ๋น์ค์ด ๋๋ค\n",
" P[defect == \"๋ง๋ชจ\", 4] *= 1.6; P[defect == \"๋์ \", 4] *= 1.3\n",
" P /= P.sum(axis=1, keepdims=True)\n",
" cause = np.array([CAUSES[rng.choice(5, p=P[i])] for i in range(N)])\n",
" since = np.where(cause == \"์ ๋น ํ์ง\", rng.exponential(35, N), rng.exponential(160, N)).clip(1, 720).round().astype(int)\n",
" wait = (rng.lognormal(1.3, 0.5, N) * np.where(vendor == \"์ธ์ฃผB\", 1.7, np.where(vendor == \"๋ถ๋์ ๋น\", 0.6, 1.0)) * (0.7 + 0.5 * use[month])).round(1)\n",
" reins = rng.random(N) < np.where(cause == \"์ ๋น ํ์ง\", 0.6, 0.08)\n",
" texts = []\n",
" for i in range(N):\n",
" pt = PARTS[part[i]]\n",
" cl = [f\"{rng.choice(pt[1])}์์ {rng.choice(DEFECTS[defect[i]])}\"]\n",
" if rng.random() < 0.7: cl.append(rng.choice(SEV_CUE[sev[i]]))\n",
" if rng.random() < 0.65: # ์์ธ ๋จ์: 8%๋ ์ค์ ์์ธ๊ณผ ๋ค๋ฅธ ๋จ์๊ฐ ์ ํ๋ค(๋ณด๊ณ ์์ ์ถ์ )\n",
" cc = cause[i] if rng.random() > 0.08 else rng.choice(CAUSES)\n",
" cl.append(rng.choice(CAUSE_CUE[cc]))\n",
" cl.append(rng.choice(ACTIONS))\n",
" if rng.random() < 0.5: cl.insert(0, f\"VU-{rng.integers(1, 31):02d} ์์\")\n",
" texts.append(\". \".join(cl) + \".\")\n",
" df = pd.DataFrame({\"qdr_id\": [f\"Q{seed}{i:06d}\" for i in range(N)], \"month\": month, \"unit\": [f\"VU-{x:02d}\" for x in rng.integers(1, 31, N)],\n",
" \"part\": part, \"defect\": defect, \"sev\": sev, \"cause\": cause, \"vendor\": vendor, \"maker\": [MAKER_OF[i] for i in part],\n",
" \"since_maint\": since, \"inspect_wait\": wait, \"reinspect\": reins, \"event\": ev, \"text\": texts})\n",
" df[\"cost\"] = PRICE[df[\"part\"]] * np.array([1.0, 2.0, 4.0])[df[\"sev\"]] # [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์๋ฆฌยท์กฐ์น ๋น์ฉ์ง์\n",
" return df, use, events\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# ๊ณตํต ํ
์คํธ ์กฐ๊ฐ: ์ ๊ทํ, ๊ฐ์ด ํ ํฐํ(์กฐ์ฌ ์ ๊ฑฐ), ๋ถ์ยท๊ฒฐํจยท์น๋ช
๋ยท์์ธ ํค์๋ ๋ผ๋ฒจ๋ง\n",
"# ---------------------------------------------------------------------------\n",
"TYPO = {\"๋์ณ\": \"๋์ \", \"๊ท ๋ ฌ\": \"๊ท ์ด\", \"์ ์ถ ๋ถ๋\": \"์ ์ด ๋ถ๋\", \"๋ถ์ฉ\": \"๋ถ์\", \"๋ง๋ฌ\": \"๋ง๋ชจ\", \"๊ณผ๋ ฌ\": \"๊ณผ์ด\", \"์ค์๋ฅ\": \"์ค์๋\", \"์ฌ๋\": \"์ ๋\"}\n",
"ABBR = {\"T/M\": \"๋ณ์๊ธฐ\", \"BAT\": \"๋ฐฐํฐ๋ฆฌ\"}\n",
"PART_KW = sorted([(a, i) for i, p in enumerate(PARTS) for a in p[1]], key=lambda x: -len(x[0]))\n",
"DEF_KW = {\"๊ท ์ด\": [\"๊ท ์ด\", \"ํฌ๋\", \"๊ฐ๋ผ\"], \"๋์ \": [\"๋์ \", \"์์ด\", \"๋์ค\"], \"๋ง๋ชจ\": [\"๋ง๋ชจ\", \"๋ณ์\"], \"๋ถ์\": [\"๋ถ์\", \"๋
น์ด\", \"์ฐํ\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋\", \"๋จ์ \", \"๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ\", \"์ฒด๊ฒฐ\", \"ํ๊ฑฐ\"], \"๊ณผ์ด\": [\"๊ณผ์ด\", \"์จ๋\", \"์ด์ด\"], \"์ค์๋\": [\"์ค์๋\", \"๋ถ์์ \", \"๋ฉ์ถค\"]}\n",
"SEV_KW = [(2, [\"์ฆ์ ์ด์ฉ ์ค์ง\", \"์ฌ์ฉ ๋ถ๊ฐ\", \"์ํ\"]), (1, [\"์ฑ๋ฅ ์ ํ\", \"์กฐ์ํ\"]), (0, [\"๊ฒฝ๋ฏธ\", \"์ง์ฅ์ด ์\"])]\n",
"CAUSE_KW = {\"์ ์กฐ ๋ถ๋\": [\"์ ํ\", \"์ถ๊ณ \", \"์ธ์\"], \"์ ๋น ํ์ง\": [\"์ ๋น ํ\", \"์ฌ์กฐ๋ฆฝ\", \"์ ๋น ์งํ\"], \"์ด์ฉ ๋ถ์ฃผ์\": [\"์ถฉ๊ฒฉ\", \"๊ณผ๋ถํ\", \"์ ๊ฒ ์์ด\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์\", \"๊ตฌ์กฐ์ \", \"์ฌ๋ฌ ๋ถ๋\"], \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ\", \"๋
ธํ\", \"์๋ช
\"]}\n",
"PARTICLES = (\"์์\", \"์ผ๋ก\", \"์ด\", \"๊ฐ\", \"์\", \"๋\", \"์\", \"๋ฅผ\", \"์\", \"์\", \"๋ก\", \"์\", \"๊ณผ\", \"๋\", \"ํจ\", \"์\", \"๋จ\")\n",
"\n",
"\n",
"def norm(t):\n",
" \"\"\"์ ๊ทํ: ์ ๊ฐ->๋ฐ๊ฐ, ํน์๋ฌธ์ยท์ค๋ณต ๊ณต๋ฐฑ ์ ๊ฑฐ, ์ฝ์ด ํ์ฅ, ์คํ ๊ต์ \"\"\"\n",
" # [TODO : ํ์ ์ค์ต 9] AAR ๊ธฐ๋ฐ ๊ฒฐํจ ์์ธ ์ค๋ถ๋ฅ ๋ถ์ ๋ฐ ํ
์คํธ ๋ง์ด๋ ๋ฃฐ์
์ฌํ๋ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tok(t):\n",
" out = []\n",
" for w in re.split(r\"[\\s.,]+\", norm(t)):\n",
" if not w or re.fullmatch(r\"[\\d\\-A-Za-z]+\", w):\n",
" continue\n",
" for p in PARTICLES:\n",
" if len(w) > len(p) + 1 and w.endswith(p):\n",
" w = w[:-len(p)]\n",
" break\n",
" out.append(w)\n",
" return out\n",
"\n",
"\n",
"def label_part(t):\n",
" for a, i in PART_KW:\n",
" if a in t:\n",
" return i\n",
" return -1\n",
"\n",
"\n",
"def label_defect(t):\n",
" for d, ks in DEF_KW.items():\n",
" if any(k in t for k in ks):\n",
" return d\n",
" return \"๋ฏธ๋ถ๋ฅ\"\n",
"\n",
"\n",
"def label_sev(t):\n",
" for s, ks in SEV_KW:\n",
" if any(k in t for k in ks):\n",
" return s\n",
" return -1\n",
"\n",
"\n",
"def label_cause(t):\n",
" for c, ks in CAUSE_KW.items():\n",
" if any(k in t for k in ks):\n",
" return c\n",
" return \"๋ฏธ์\"\n",
"\n",
"df, use, events = make_qdr(36, seed=1, scale=1.5)\n",
"df[\"t\"] = df[\"text\"].map(norm)\n",
"aar, val = df[(df[\"month\"] >= 12) & (df[\"month\"] < 24)].copy(), df[df[\"month\"] >= 24].copy()\n",
"print(f\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] AAR ๊ธฐ๊ฐ {len(aar)}๊ฑด, ๊ฒ์ฆ ๊ธฐ๊ฐ {len(val)}๊ฑด\")\n",
"\n",
"# 1) ๊ธฐ์กด ๋ฃฐ์
: ์์ ์ ์์ธ ๋จ์ ํค์๋(์ฒซ ์ผ์น)\n",
"for d in (aar, val):\n",
" d[\"rule0\"] = d[\"t\"].map(label_cause)\n",
"wrong = aar[aar[\"rule0\"] != aar[\"cause\"]]\n",
"no_cue = wrong[wrong[\"rule0\"] == \"๋ฏธ์\"]\n",
"conflict = wrong[wrong[\"rule0\"] != \"๋ฏธ์\"]\n",
"print(f\"๊ธฐ์กด ๋ฃฐ์
์ ํ๋: AAR {1 - len(wrong) / len(aar):.1%}, ๊ฒ์ฆ {(val['rule0'] == val['cause']).mean():.1%}\")\n",
"print(f\"AAR ์ค๋ถ๋ฅ {len(wrong)}๊ฑด ({len(wrong) / len(aar):.1%}): ์์ธ ๋จ์ ์์(๋ฏธ์) {len(no_cue)}๊ฑด ({len(no_cue) / len(wrong):.0%}), ๋จ์์ ์ค์ ์์ธ ๋ถ์ผ์น {len(conflict)}๊ฑด ({len(conflict) / len(wrong):.0%})\")\n",
"print(\" ๋ฏธ์ ๊ฑด์ ์ค์ ์์ธ: \" + \", \".join(f\"{k} {v}\" for k, v in no_cue[\"cause\"].value_counts().items()))\n",
"c0 = conflict.groupby([\"rule0\", \"cause\"]).size().sort_values(ascending=False).head(2)\n",
"print(\" ๋ถ์ผ์น ์์: \" + \", \".join(f\"๋ฃฐ '{a}' -> ์ค์ '{b}' {v}๊ฑด\" for (a, b), v in c0.items()))\n",
"ex = no_cue[no_cue[\"cause\"] == \"์ ๋น ํ์ง\"].iloc[0]\n",
"print(f\" ์ฌ๋ก: '{ex['text']}' (์
์ฒด {ex['vendor']}, ์ ๋น ํ {ex['since_maint']}์ผ) -> ๋ฃฐ ๋ฏธ์, ์ค์ ์ ๋น ํ์ง\")\n",
"\n",
"# 2) ์ฌํ๋: ๋ฏธ์ ๊ฑด์ ๋ณด์กฐ ๊ท์น ์ ์ฉ(AAR ๊ธฐ๊ฐ ๋ฏธ์ ๊ฑด๋ง์ผ๋ก ๊ฒฐ์ ํธ๋ฆฌ ํ์ต, ๊น์ด 3)\n",
"def meta(d):\n",
" X = pd.DataFrame({\"since_maint\": d[\"since_maint\"], \"ext_vendor\": (d[\"vendor\"] != \"๋ถ๋์ ๋น\").astype(int), \"vendor_B\": (d[\"vendor\"] == \"์ธ์ฃผB\").astype(int),\n",
" \"wait\": d[\"inspect_wait\"], \"reinspect\": d[\"reinspect\"].astype(int), \"sev\": d[\"sev\"], \"wear\": d[\"defect\"].isin([\"๋ง๋ชจ\", \"๋ถ์\"]).astype(int),\n",
" \"leak\": (d[\"defect\"] == \"๋์ \").astype(int)})\n",
" for p_ in (2, 3, 7):\n",
" X[f\"part{p_}\"] = (d[\"part\"] == p_).astype(int)\n",
" for p_ in (4, 5, 11):\n",
" X[f\"part{p_}\"] = (d[\"part\"] == p_).astype(int)\n",
" return X\n",
"\n",
"\n",
"tree = DecisionTreeClassifier(max_depth=3, min_samples_leaf=40, random_state=0).fit(meta(no_cue.assign(cause=no_cue[\"cause\"])), no_cue[\"cause\"])\n",
"print(\"\\n[์ฌํ๋ ๋ณด์กฐ ๊ท์น(๊ฒฐ์ ํธ๋ฆฌ, ๋ฏธ์ ๊ฑด ๋์)]\")\n",
"for line in export_text(tree, feature_names=list(meta(no_cue).columns), max_depth=3).splitlines()[:9]:\n",
" print(\" \" + line)\n",
"\n",
"\n",
"CONF = 0.45\n",
"\n",
"\n",
"def rule1(d):\n",
" out = d[\"rule0\"].copy()\n",
" unk = out == \"๋ฏธ์\"\n",
" if unk.any():\n",
" pr = tree.predict_proba(meta(d[unk]))\n",
" out[unk] = np.where(pr.max(axis=1) >= CONF, tree.classes_[pr.argmax(axis=1)], \"๋ฏธ์\") # ํ์ ์ด ๋ฎ์ผ๋ฉด ๋ฏธ์์ผ๋ก ๋จ๊ฒจ ์ฌ๋์ด ํ์ธ\n",
" return out\n",
"\n",
"\n",
"for d in (aar, val):\n",
" d[\"rule1\"] = rule1(d)\n",
"acc = lambda d, c: (d[c] == d[\"cause\"]).mean()\n",
"print(f\"\\n์ ํ๋ ๋ณํ: AAR(ํ๋) {acc(aar, 'rule0'):.1%} -> {acc(aar, 'rule1'):.1%}, ๊ฒ์ฆ(๋ฏธ์ฌ์ฉ) {acc(val, 'rule0'):.1%} -> {acc(val, 'rule1'):.1%}\")\n",
"for c in (\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\"):\n",
" m = val[\"cause\"] == c\n",
" print(f\" {c} ์ฌํ์จ(๊ฒ์ฆ): {(val.loc[m, 'rule0'] == c).mean():.0%} -> {(val.loc[m, 'rule1'] == c).mean():.0%}\")\n",
"\n",
"# 3) 3๋ ์ ๋ ํจ๊ณผ ์งํ (๊ฒ์ฆ 12๊ฐ์, ๊ฐ์ ๋จ๊ฐ)\n",
"REWORK, HOURS = 0.5, 0.8 # [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์ค๋ถ๋ฅ ๊ฑด๋น ์ฌ์์
๋น์ฉ = ์กฐ์น ๋น์ฉ x 0.5, ๋ฏธ์ ๊ฑด ์์์
์์ธ ํ์ธ 0.8์๊ฐ\n",
"cost0, cost1 = (val.loc[val[\"rule0\"] != val[\"cause\"], \"cost\"].sum() * REWORK), (val.loc[val[\"rule1\"] != val[\"cause\"], \"cost\"].sum() * REWORK)\n",
"h0, h1 = (val[\"rule0\"] == \"๋ฏธ์\").sum() * HOURS, (val[\"rule1\"] == \"๋ฏธ์\").sum() * HOURS\n",
"ext0, ext1 = ((val[\"rule0\"].isin([\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\"])) & (val[\"cause\"].isin([\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\"])) & (val[\"rule0\"] == val[\"cause\"])).sum(), \\\n",
" ((val[\"rule1\"].isin([\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\"])) & (val[\"cause\"].isin([\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\"])) & (val[\"rule1\"] == val[\"cause\"])).sum()\n",
"print(\"\\n[3๋ ์ ๋ ํจ๊ณผ ์งํ] (๊ฒ์ฆ 12๊ฐ์, ๊ฐ์ ๋จ๊ฐ)\")\n",
"print(f\" 1) ๋น์ฉ ์ ๊ฐ: ์ค๋ถ๋ฅ ์ฌ์์
๋น์ฉ์ง์ {cost0:.0f} -> {cost1:.0f} ({1 - cost1 / cost0:.1%} ์ ๊ฐ), ์ฐ ํ์ฐ ์ฝ {cost0 - cost1:.0f}\")\n",
"print(f\" 2) ์๊ฐ ๋จ์ถ: ์์ธ ์์์
ํ์ธ {h0:.0f}h -> {h1:.0f}h ({1 - h1 / h0:.1%} ๋จ์ถ, ํ์ {CONF:.0%} ๋ฏธ๋ง์ ๋ฏธ์์ผ๋ก ๋จ๊ฒจ ์ฌ๋์ด ํ์ธ)\")\n",
"print(f\" 3) ์ ์กฐยท์ ๋น ์ฑ
์ ๊ฑด ์ ํ ์๋ณ: {ext0}๊ฑด -> {ext1}๊ฑด (+{ext1 / ext0 - 1:.0%}), ํ์ ๊ตฌ์ยท์์ ์๊ตฌ ๊ทผ๊ฑฐ ํ๋ณด ์ฆ๊ฐ\")\n",
"print(\"[๋์
๋ก๋๋งต] ๋จ๊ธฐ(1~3์): ๋ณด์กฐ ๊ท์น ์ ์ฉ + ํ์ง๊ด ํ์ธ ๋ณํ / ์ค๊ธฐ(3~12์): ๋ถ๊ธฐ AAR๋ก ๊ท์น ์ฌํ์ต, ๋จ์ ์ฉ์ด ์ฌ์ ํ์ฅ / ์ฅ๊ธฐ(1๋
~): ๊ตฐ์ ์ ๋ณด์ฒด๊ณ ์ ๋น ์ด๋ ฅ๊ณผ ์์ธ ๋ผ๋ฒจ ํ๋ฅ\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
09 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ต์ก์ฉ๊ฐ์์๋ฃ] AAR ๊ธฐ๊ฐ 4568๊ฑด, ๊ฒ์ฆ ๊ธฐ๊ฐ 5103๊ฑด\n",
"๊ธฐ์กด ๋ฃฐ์
์ ํ๋: AAR 60.3%, ๊ฒ์ฆ 61.7%\n",
"AAR ์ค๋ถ๋ฅ 1815๊ฑด (39.7%): ์์ธ ๋จ์ ์์(๋ฏธ์) 1617๊ฑด (89%), ๋จ์์ ์ค์ ์์ธ ๋ถ์ผ์น 198๊ฑด (11%)\n",
" ๋ฏธ์ ๊ฑด์ ์ค์ ์์ธ: ์ ์กฐ ๋ถ๋ 407, ์์ฐ ๋
ธํ 354, ์ ๋น ํ์ง 331, ์ค๊ณ ์ทจ์ฝ 266, ์ด์ฉ ๋ถ์ฃผ์ 259\n",
" ๋ถ์ผ์น ์์: ๋ฃฐ '์ค๊ณ ์ทจ์ฝ' -> ์ค์ '์ ๋น ํ์ง' 15๊ฑด, ๋ฃฐ '์ ์กฐ ๋ถ๋' -> ์ค์ '์ ๋น ํ์ง' 15๊ฑด\n",
" ์ฌ๋ก: '์ฐ๋ฃ๊ณํต์์ ๋์ค ํ์ ์ด ์์. ์ฌ๊ฒ์ฌ๋ฅผ ์์ฒญํจ.' (์
์ฒด ์ธ์ฃผB, ์ ๋น ํ 6์ผ) -> ๋ฃฐ ๋ฏธ์, ์ค์ ์ ๋น ํ์ง\n",
"\n",
"[์ฌํ๋ ๋ณด์กฐ ๊ท์น(๊ฒฐ์ ํธ๋ฆฌ, ๋ฏธ์ ๊ฑด ๋์)]\n",
" |--- reinspect <= 0.50\n",
" | |--- since_maint <= 62.50\n",
" | | |--- vendor_B <= 0.50\n",
" | | | |--- class: ์ ์กฐ ๋ถ๋\n",
" | | |--- vendor_B > 0.50\n",
" | | | |--- class: ์ ๋น ํ์ง\n",
" | |--- since_maint > 62.50\n",
" | | |--- part5 <= 0.50\n",
" | | | |--- class: ์์ฐ ๋
ธํ\n",
"\n",
"์ ํ๋ ๋ณํ: AAR(ํ๋) 60.3% -> 65.4%, ๊ฒ์ฆ(๋ฏธ์ฌ์ฉ) 61.7% -> 66.2%\n",
" ์ ์กฐ ๋ถ๋ ์ฌํ์จ(๊ฒ์ฆ): 63% -> 67%\n",
" ์ ๋น ํ์ง ์ฌํ์จ(๊ฒ์ฆ): 62% -> 79%\n",
"\n",
"[3๋ ์ ๋ ํจ๊ณผ ์งํ] (๊ฒ์ฆ 12๊ฐ์, ๊ฐ์ ๋จ๊ฐ)\n",
" 1) ๋น์ฉ ์ ๊ฐ: ์ค๋ถ๋ฅ ์ฌ์์
๋น์ฉ์ง์ 8127 -> 7253 (10.8% ์ ๊ฐ), ์ฐ ํ์ฐ ์ฝ 874\n",
" 2) ์๊ฐ ๋จ์ถ: ์์ธ ์์์
ํ์ธ 1409h -> 1126h (20.1% ๋จ์ถ, ํ์ 45% ๋ฏธ๋ง์ ๋ฏธ์์ผ๋ก ๋จ๊ฒจ ์ฌ๋์ด ํ์ธ)\n",
" 3) ์ ์กฐยท์ ๋น ์ฑ
์ ๊ฑด ์ ํ ์๋ณ: 1454๊ฑด -> 1685๊ฑด (+16%), ํ์ ๊ตฌ์ยท์์ ์๊ตฌ ๊ทผ๊ฑฐ ํ๋ณด ์ฆ๊ฐ\n",
"[๋์
๋ก๋๋งต] ๋จ๊ธฐ(1~3์): ๋ณด์กฐ ๊ท์น ์ ์ฉ + ํ์ง๊ด ํ์ธ ๋ณํ / ์ค๊ธฐ(3~12์): ๋ถ๊ธฐ AAR๋ก ๊ท์น ์ฌํ์ต, ๋จ์ ์ฉ์ด ์ฌ์ ํ์ฅ / ์ฅ๊ธฐ(1๋
~): ๊ตฐ์ ์ ๋ณด์ฒด๊ณ ์ ๋น ์ด๋ ฅ๊ณผ ์์ธ ๋ผ๋ฒจ ํ๋ฅ\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
10. ์ผ์ ์ฅ๋น ๊ฒฐํจ ๋ณด๊ณ (QDR) ๋ฐ์ดํฐ ๋ถ์ ๋ฐ ํ์ง ์ทจ์ฝ์ ์๋ณ ๋๊ตฌ ํตํฉ ์์ฑ\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์ผ์ ์ฅ๋น ๊ฒฐํจ ๋ณด๊ณ (QDR) ๋ฐ์ดํฐ ๋ถ์ ๋ฐ ํ์ง ์ทจ์ฝ์ ์๋ณ ๋๊ตฌ๋ฅผ ์์ฑํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **QDR ๊ฒ์ฆยท์ ์ ๋ถํฐ ์น๋ช
๋ ๋ถ๋ฅ, ์ทจ์ฝ์ฑ ์ง์ยท๊ฒฝ๋ณด, ์์ธ ๋ถ๋ฅ, ์น์ธ ๋๊ธฐ ํ๋ณด ์ ์, ์ฌํ์ต๊น์ง ํ ํ์ผ๋ก ํตํฉํ๊ณ ์์ฒด ์ ๊ฒ๊ณผ ๋ธ๋ฆฌํ ์์ฝ์ ์ถ๋ ฅํ๋ ๋๊ตฌ๋ฅผ ์์ฑํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ง๋๋ฌยท์ด๋ฒ ๋ฌ ์ค์ ์ผ๋ก ์ฑํผ์ธ ๋ชจ๋ธ๊ณผ ๋์ ์ ๋ชจ๋ธ์ ํ์ตํด ๊ฒ์ฆ ์ ํ๋๋ฅผ ๋น๊ตํ๊ณ ๋น์ทํ๊ฑฐ๋ ๋์ ๋๋ง ๊ต์ฒด ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `docs/qlt05_operations_guide.md ๋ฐ pipeline/qlt05_retrain.py` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ง์ ๊ฐ๋ฅํ ์ด์ ๊ฐ๋ฅ์ฑ ๋ฐ ๊ตญ๋ฐฉ ๊ตฐ์ ์ ๋ณด์ฒด๊ณ์์ ์ฐ๋ ์ฉ์ด์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์๋ ์ฌํ์ต ํ์ดํ๋ผ์ธ ์ ์ ์คํ, ์ต์ข
๋ฌธ์ 100% ์๋น\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์ต์ข
์ธ๋๋ฌผ ๊ฒ์ ๋ฐ ํ๋ก์ ํธ ์๋ฃ ๋ฐํ์๋ฃ ์์ฑ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: End-to-End ์ ์ฒด ํ์ดํ๋ผ์ธ ์๋ํ ์คํ ๋ฐ ์ต์ข
๊ฒ์\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"QDR ์์ ๊ฒ์ฆยท์ ์ ยท๋ผ๋ฒจ๋ง, ์น๋ช
๋ ๋ถ๋ฅ ๋ชจ๋ธ์ ์๋ ์ฌํ์ต(์ฑํผ์ธยท๋์ ์ ๋น๊ต ํ ๊ต์ฒด), ๋ถ์๋ณ ์ทจ์ฝ์ฑ ์ง์์ ๊ธ์ฆ ๊ฒฝ๋ณด, ์ฌํ๋ ๊ท์น์ ์์ธ ๋ถ๋ฅ, ํ์ ๊ตฌ์ยท์ค๊ณ ๋ณ๊ฒฝยท์ธ์ฃผ์ ๋น ์์ ํ๋ณด์ ํ์ง๊ด ์น์ธ ๋๊ธฐ ๋ชฉ๋ก๊น์ง ์ผ์ QDR ํ์ง ์ทจ์ฝ์ ์๋ณ ๋๊ตฌ๋ฅผ ํ ํ์ผ๋ก ํตํฉ ์คํํ๊ณ , ์์ฒด ์ ๊ฒยท์ด์ ๋ฌธ์ ์๋น ์ ๊ฒยทํ์ง ๋ด๋น ์ฅ๊ต ๋ธ๋ฆฌํ ์์ฝ์ ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import re\n",
"import time\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"import scipy.sparse as sp\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"from sklearn.linear_model import LogisticRegression\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์ผ์ ์ฅ๋น ํ์ง๊ฒฐํจ๋ณด๊ณ (QDR) ์์ฑ๊ธฐ (์ค์ ๋ถ๋ยท์ฅ๋นยท์
์ฒดยท์ ๋น ์ค์ ์์)\n",
"# ๊ฐ์ ์ฅ๋น ๋ถ์ 12์ข
, ๊ฒฐํจ ์ ํ 8์ข
, ์น๋ช
๋(Critical/Major/Minor), ์์ธ 5์ข
, ๊ฐ์ ์ธ์ฃผ์ ๋น ์
์ฒด 4๊ณณ + ๋ถ๋์ ๋น.\n",
"# QDR 1๊ฑด๋ง๋ค ํ๊ตญ์ด ์์ ํ
์คํธ๋ฅผ ๋ง๋ค๊ณ , ์๋ณ ๊ฑด์๋ ๊ณ์ ยท์ถ์ธยทํ๋ จ ๊ฐ๋(usage)ยท์ผ๋ถ ์ ์กฐ ๋ถ๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ๋ฅธ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"PARTS = [ # (์ด๋ฆ, ๋ณ์นญ, ์ ๊ธฐ์ค ๊ฑด์, ๊ณ์ ์งํญ, ์ ์ ์, (Critical, Major ๋น์จ), ๊ฐ์ ๋จ๊ฐ์ง์, ์ ์กฐ์ฌ, ์ฃผ์ ๊ฒฐํจ ๊ฐ์ค์น)\n",
" (\"์์ง ์ฐ๋ฃ๊ณํต\", [\"์ฐ๋ฃ๊ณํต\", \"์ฐ๋ฃ ๋ผ์ธ\", \"์ฐ๋ฃ ํํ\"], 26, 0.15, 7, (0.10, 0.35), 6.0, \"MK1\", \"๋์ :4 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:2 ์ค์๋:2\"),\n",
" (\"๋ณ์๊ธฐ\", [\"๋ณ์๊ธฐ\", \"T/M\", \"๋ฏธ์
\"], 22, 0.10, 8, (0.12, 0.38), 9.0, \"MK1\", \"๋ง๋ชจ:4 ๋์ :2 ๊ณผ์ด:3 ์ค์๋:1\"),\n",
" (\"์ ๋ ์ฅ์น\", [\"์ ๋ ์ฅ์น\", \"๋ธ๋ ์ดํฌ\", \"์ ๋ ๋ผ์ธ\"], 20, 0.20, 12, (0.26, 0.40), 5.0, \"MK2\", \"๋ง๋ชจ:4 ๊ท ์ด:2 ๋์ :2 ์ฒด๊ฒฐ๋ถ๋:2\"),\n",
" (\"์กฐํฅ ์ฅ์น\", [\"์กฐํฅ ์ฅ์น\", \"ํธ๋ค ์ฐ๋๋ถ\", \"์กฐํฅ ๋ก๋\"], 14, 0.10, 6, (0.30, 0.40), 5.0, \"MK2\", \"์ฒด๊ฒฐ๋ถ๋:4 ๊ท ์ด:2 ๋ง๋ชจ:3 ์ค์๋:1\"),\n",
" (\"๋๊ฐ ๊ณํต\", [\"๋๊ฐ ๊ณํต\", \"๋ผ๋์์ดํฐ\", \"๋๊ฐ์ ๋ผ์ธ\"], 24, 0.30, 7, (0.05, 0.30), 3.0, \"MK3\", \"๋์ :4 ๊ณผ์ด:4 ๋ถ์:2\"),\n",
" (\"์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ\", [\"๋ฐฐํฐ๋ฆฌ\", \"์ ๊ธฐ ๊ณํต\", \"๋ฐ์ ๊ธฐ\"], 28, 0.35, 1, (0.04, 0.28), 2.5, \"MK3\", \"์ ์ด๋ถ๋:4 ๋ถ์:2 ๊ณผ์ด:2 ์ค์๋:2\"),\n",
" (\"ํต์ ๋ชจ๋\", [\"ํต์ ๋ชจ๋\", \"๋ฌด์ ๊ธฐ ์ฐ๋๋ถ\", \"์ํ
๋ ์ ์๋ถ\"], 16, 0.05, 4, (0.03, 0.25), 4.0, \"MK4\", \"์ ์ด๋ถ๋:5 ์ค์๋:4 ๋ถ์:1\"),\n",
" (\"์ ์ ๊ณํต\", [\"์ ์ ๊ณํต\", \"์ ์ ํธ์ค\", \"์ ์ ์ค๋ฆฐ๋\"], 18, 0.10, 9, (0.10, 0.35), 4.5, \"MK2\", \"๋์ :5 ๊ท ์ด:2 ๋ง๋ชจ:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"ํ๊ฐ ์ฅ์น\", [\"ํ๊ฐ ์ฅ์น\", \"์์คํ์
\", \"์์ถฉ๊ธฐ\"], 15, 0.08, 5, (0.06, 0.30), 3.5, \"MK4\", \"๋ง๋ชจ:4 ๊ท ์ด:3 ์ฒด๊ฒฐ๋ถ๋:2 ๋ถ์:1\"),\n",
" (\"์ฐจ์ฒด ์ฉ์ ๋ถ\", [\"์ฐจ์ฒด ์ฉ์ ๋ถ\", \"ํ๋ ์ ์ฉ์ ๋ถ\", \"์ฐจ์ฒด ์ด์๋ถ\"], 10, 0.05, 10, (0.28, 0.40), 7.0, \"MK1\", \"๊ท ์ด:6 ๋ถ์:3 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ดํยท์กฐ์ค ์ฅ๋น\", [\"๊ดํ ์ฅ๋น\", \"์กฐ์ค๊ฒฝ\", \"๊ด์ธก ์ฅ๋น\"], 8, 0.05, 3, (0.03, 0.22), 8.0, \"MK4\", \"์ค์๋:4 ์ ์ด๋ถ๋:3 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:1\"),\n",
" (\"๊ณต๊ธฐ ์ ํ ํํฐ\", [\"๊ณต๊ธฐ ํํฐ\", \"ํก๊ธฐ ํํฐ\", \"์ ํ ์ฅ์น\"], 12, 0.25, 4, (0.01, 0.12), 1.0, \"MK3\", \"๋ง๋ชจ:5 ๋ถ์:2 ์ฒด๊ฒฐ๋ถ๋:3\")]\n",
"NP = len(PARTS)\n",
"DEFECTS = { # ๊ฒฐํจ ์ ํ: ์ฆ์ ํํ(๋์์ด)\n",
" \"๊ท ์ด\": [\"๊ท ์ด์ด ๋ฐ๊ฒฌ๋จ\", \"ํฌ๋์ด ํ์ธ๋จ\", \"๊ฐ๋ผ์ง์ด ์์\"], \"๋์ \": [\"๋์ ๊ฐ ๋ฐ์ํจ\", \"์ค์ผ์ด ์์ด ๋์ด\", \"๋์ค ํ์ ์ด ์์\"],\n",
" \"๋ง๋ชจ\": [\"๋ง๋ชจ๊ฐ ์ฌํจ\", \"๊ณผ๋ํ ๋ง๋ชจ๊ฐ ํ์ธ๋จ\", \"๋ณ์ ํ์ ์ด ์์\"], \"๋ถ์\": [\"๋ถ์์ด ์งํ๋จ\", \"๋
น์ด ๋ฐ์ํจ\", \"์ฐํ ํ์ ์ด ์์\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋์ด ๋ฐ์ํจ\", \"์ปค๋ฅํฐ ๋จ์ ์ด ์์ฌ๋จ\", \"์ ํธ๊ฐ ๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ ํ๋ฆผ์ด ํ์ธ๋จ\", \"์ฒด๊ฒฐ ์ํ๊ฐ ๋ถ๋ํจ\", \"๊ณ ์ ๋ถ๊ฐ ํ๊ฑฐ์\"],\n",
" \"๊ณผ์ด\": [\"๊ณผ์ด ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํจ\", \"์จ๋๊ฐ ๊ณผ๋ํ๊ฒ ์์นํจ\", \"์ด์ด ์ฌํ๊ฒ ๋จ\"], \"์ค์๋\": [\"์ค์๋์ด ๋ฐ๋ณต๋จ\", \"์๋์ด ๋ถ์์ ํจ\", \"๊ฐํ์ ์ผ๋ก ๋ฉ์ถค\"]}\n",
"DNAMES = list(DEFECTS)\n",
"SEV = [\"Minor\", \"Major\", \"Critical\"]\n",
"SEV_CUE = {0: [\"๊ฒฝ๋ฏธํ ์์ค์\", \"์ด์ฉ์๋ ์ง์ฅ์ด ์์\", \"์ก์ ์ ๊ฒ์ ์ฌ์ํ ์ด์์\", \"๊ธฐ๋ฅ์๋ ์ํฅ์ด ์์\"],\n",
" 1: [\"์ฑ๋ฅ ์ ํ๊ฐ ์์\", \"์กฐ์ํ ์ ๋น๊ฐ ํ์ํจ\", \"์ถ๋ ฅ์ด ๋จ์ด์ง๋ ์ํ์\", \"๊ธฐ๋ฅ์ด ๋ถ๋ถ์ ์ผ๋ก ์ ํ๋จ\"],\n",
" 2: [\"์ฆ์ ์ด์ฉ ์ค์งํจ\", \"์์ ์ ์ํ์ด ์์ด ์ฌ์ฉ ๋ถ๊ฐ\", \"์๋ ์ ์ง๋ก ์๋ฌด ์ํ์ด ์ด๋ ค์\", \"์ธ๋ช
์์ ์ํ์ด ์ฐ๋ ค๋จ\"]}\n",
"CAUSES = [\"์ ์กฐ ๋ถ๋\", \"์ ๋น ํ์ง\", \"์ด์ฉ ๋ถ์ฃผ์\", \"์ค๊ณ ์ทจ์ฝ\", \"์์ฐ ๋
ธํ\"]\n",
"CAUSE_CUE = {\n",
" \"์ ์กฐ ๋ถ๋\": [\"์ ํ ์ธ์ ์งํ ๋ฐ์ํจ\", \"์ถ๊ณ ์ํ์์ ์ด๋ฏธ ์ด์์ด ์์์\", \"์ธ์ ํ ์ผ๋ง ์ง๋์ง ์์ ๋ฐ์ํจ\"],\n",
" \"์ ๋น ํ์ง\": [\"์ธ์ฃผ ์ ๋น ํ ์ผ๋ง ๋์ง ์์ ์ฌ๋ฐํจ\", \"์ ๋น ํ ์ฌ์กฐ๋ฆฝ ์ํ๊ฐ ๋ถ๋ํจ\", \"์ ๋น ์งํ ๊ฐ์ ๋ถ์์์ ๋ค์ ๋ฐ์ํจ\"],\n",
" \"์ด์ฉ ๋ถ์ฃผ์\": [\"ํ์ง ๊ธฐ๋ ์ค ์ถฉ๊ฒฉ ์ดํ ๋ฐ์ํจ\", \"๊ณผ๋ถํ ์ด์ฉ ํ ๋ฐ์ํจ\", \"์ ๊ฒ ์์ด ์ฐ์ ์ด์ฉํ ๋ค ๋ฐ์ํจ\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์์ด ๋ค๋ฅธ ์ฐจ๋์์๋ ๋ฐ๋ณต๋จ\", \"๊ตฌ์กฐ์ ์ผ๋ก ์ทจ์ฝํ ๋ถ์๋ก ๋ณด์\", \"์ฌ๋ฌ ๋ถ๋์์ ๊ฐ์ ํํ๋ก ๋ณด๊ณ ๋จ\"],\n",
" \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ์ด ๋๋ํจ\", \"์ฅ๊ธฐ๊ฐ ์ฌ์ฉ์ผ๋ก ๋
ธํ๋จ\", \"์๋ช
์ฃผ๊ธฐ ๋ง๊ธฐ์ ๋ฐ์ํจ\"]}\n",
"VENDORS = [\"์ธ์ฃผA\", \"์ธ์ฃผB\", \"์ธ์ฃผC\", \"์ธ์ฃผD\", \"๋ถ๋์ ๋น\"]\n",
"ACTIONS = [\"์ ๋น์ ์
๊ณ ๋ฅผ ์์ฒญํจ\", \"ํ์ฅ ์์ ์กฐ์น ํ ์ด์ฉ ์ค์\", \"๋ถํ ๊ตํ์ ์์ฒญํจ\", \"์ฌ๊ฒ์ฌ๋ฅผ ์์ฒญํจ\", \"์์ธ ํ์ธ์ด ํ์ํจ\"]\n",
"MAKER_OF = [p[7] for p in PARTS]\n",
"PRICE = np.array([p[6] for p in PARTS])\n",
"\n",
"\n",
"def usage_index(months, seed=0):\n",
" \"\"\"์๋ณ ํ๋ จ ๊ฐ๋(๋ฏธ๋ฆฌ ์ ์ ์๋ ๊ณํ ๊ฐ): ์ฐ 1ํ ์ ์ ์ ๊ฐ๋ ๊ณ์ + ์์ ์ก์\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 10] ์ผ์ ์ฅ๋น ๊ฒฐํจ ๋ณด๊ณ (QDR) ๋ฐ์ดํฐ ๋ถ์ ๋ฐ ํ์ง ์ทจ์ฝ์ ์๋ณ ๋๊ตฌ ํตํฉ ์์ฑ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_qdr(months=36, seed=1, scale=1.0, surge=True):\n",
" \"\"\"๋ฐํ: QDR DataFrame(๊ฑด๋น 1ํ), ์๋ณ usage, ๊ธ์ฆ ์ด๋ฒคํธ ๋ชฉ๋ก\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" use = usage_index(months, seed)\n",
" events = []\n",
" if surge:\n",
" for _ in range(max(2, months // 12 * 2)):\n",
" events.append((int(rng.integers(NP)), int(rng.integers(6, months))))\n",
" rows = []\n",
" wdef = [np.array([float(x.split(\":\")[1]) for x in p[8].split()]) for p in PARTS]\n",
" ddef = [[x.split(\":\")[0] for x in p[8].split()] for p in PARTS]\n",
" n = 0\n",
" for m in range(months):\n",
" for pi, p in enumerate(PARTS):\n",
" lam = p[2] * scale * (1 + 0.010 * m) * use[m] * (1 + p[3] * np.cos(2 * np.pi * (m % 12 - p[4]) / 12))\n",
" ev = (pi, m) in events\n",
" k = rng.poisson(lam * (2.6 if ev else 1.0))\n",
" for _ in range(k):\n",
" rows.append((n, m, pi, ev)); n += 1\n",
" N = len(rows)\n",
" A = np.array(rows)\n",
" month, part, ev = A[:, 1], A[:, 2], A[:, 3].astype(bool)\n",
" defect = np.array([rng.choice(ddef[i], p=wdef[i] / wdef[i].sum()) for i in part])\n",
" cr = np.array([PARTS[i][5][0] for i in part]); ma = np.array([PARTS[i][5][1] for i in part])\n",
" u = rng.random(N)\n",
" sev = np.where(u < cr, 2, np.where(u < cr + ma, 1, 0))\n",
" vendor = rng.choice(VENDORS, N, p=[0.25, 0.2, 0.2, 0.15, 0.2])\n",
" # ์์ธ: ๊ธฐ๋ณธ ๋น์จ์ ๋ถ์(์ ์กฐ์ฌ)ยท์
์ฒดยท์ด๋ฒคํธ ํจ๊ณผ๋ฅผ ๊ณฑํ๋ค\n",
" base = np.array([0.22, 0.20, 0.20, 0.16, 0.22])\n",
" P = np.tile(base, (N, 1))\n",
" P[vendor == \"์ธ์ฃผB\", 1] *= 2.6\n",
" P[ev, 0] *= 5.0\n",
" design = np.isin(part, [2, 3, 7]); P[design, 3] *= 2.2\n",
" P[np.isin(part, [4, 5, 11]), 0] *= 2.0 # ์ ์กฐ์ฌ MK3 ๋ฉํ ๋ถ์๋ ์ ์กฐ ๋ถ๋ ๋น์ค์ด ๋๋ค\n",
" P[defect == \"๋ง๋ชจ\", 4] *= 1.6; P[defect == \"๋์ \", 4] *= 1.3\n",
" P /= P.sum(axis=1, keepdims=True)\n",
" cause = np.array([CAUSES[rng.choice(5, p=P[i])] for i in range(N)])\n",
" since = np.where(cause == \"์ ๋น ํ์ง\", rng.exponential(35, N), rng.exponential(160, N)).clip(1, 720).round().astype(int)\n",
" wait = (rng.lognormal(1.3, 0.5, N) * np.where(vendor == \"์ธ์ฃผB\", 1.7, np.where(vendor == \"๋ถ๋์ ๋น\", 0.6, 1.0)) * (0.7 + 0.5 * use[month])).round(1)\n",
" reins = rng.random(N) < np.where(cause == \"์ ๋น ํ์ง\", 0.6, 0.08)\n",
" texts = []\n",
" for i in range(N):\n",
" pt = PARTS[part[i]]\n",
" cl = [f\"{rng.choice(pt[1])}์์ {rng.choice(DEFECTS[defect[i]])}\"]\n",
" if rng.random() < 0.7: cl.append(rng.choice(SEV_CUE[sev[i]]))\n",
" if rng.random() < 0.65: # ์์ธ ๋จ์: 8%๋ ์ค์ ์์ธ๊ณผ ๋ค๋ฅธ ๋จ์๊ฐ ์ ํ๋ค(๋ณด๊ณ ์์ ์ถ์ )\n",
" cc = cause[i] if rng.random() > 0.08 else rng.choice(CAUSES)\n",
" cl.append(rng.choice(CAUSE_CUE[cc]))\n",
" cl.append(rng.choice(ACTIONS))\n",
" if rng.random() < 0.5: cl.insert(0, f\"VU-{rng.integers(1, 31):02d} ์์\")\n",
" texts.append(\". \".join(cl) + \".\")\n",
" df = pd.DataFrame({\"qdr_id\": [f\"Q{seed}{i:06d}\" for i in range(N)], \"month\": month, \"unit\": [f\"VU-{x:02d}\" for x in rng.integers(1, 31, N)],\n",
" \"part\": part, \"defect\": defect, \"sev\": sev, \"cause\": cause, \"vendor\": vendor, \"maker\": [MAKER_OF[i] for i in part],\n",
" \"since_maint\": since, \"inspect_wait\": wait, \"reinspect\": reins, \"event\": ev, \"text\": texts})\n",
" df[\"cost\"] = PRICE[df[\"part\"]] * np.array([1.0, 2.0, 4.0])[df[\"sev\"]] # [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์๋ฆฌยท์กฐ์น ๋น์ฉ์ง์\n",
" return df, use, events\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# ๊ณตํต ํ
์คํธ ์กฐ๊ฐ: ์ ๊ทํ, ๊ฐ์ด ํ ํฐํ(์กฐ์ฌ ์ ๊ฑฐ), ๋ถ์ยท๊ฒฐํจยท์น๋ช
๋ยท์์ธ ํค์๋ ๋ผ๋ฒจ๋ง\n",
"# ---------------------------------------------------------------------------\n",
"TYPO = {\"๋์ณ\": \"๋์ \", \"๊ท ๋ ฌ\": \"๊ท ์ด\", \"์ ์ถ ๋ถ๋\": \"์ ์ด ๋ถ๋\", \"๋ถ์ฉ\": \"๋ถ์\", \"๋ง๋ฌ\": \"๋ง๋ชจ\", \"๊ณผ๋ ฌ\": \"๊ณผ์ด\", \"์ค์๋ฅ\": \"์ค์๋\", \"์ฌ๋\": \"์ ๋\"}\n",
"ABBR = {\"T/M\": \"๋ณ์๊ธฐ\", \"BAT\": \"๋ฐฐํฐ๋ฆฌ\"}\n",
"PART_KW = sorted([(a, i) for i, p in enumerate(PARTS) for a in p[1]], key=lambda x: -len(x[0]))\n",
"DEF_KW = {\"๊ท ์ด\": [\"๊ท ์ด\", \"ํฌ๋\", \"๊ฐ๋ผ\"], \"๋์ \": [\"๋์ \", \"์์ด\", \"๋์ค\"], \"๋ง๋ชจ\": [\"๋ง๋ชจ\", \"๋ณ์\"], \"๋ถ์\": [\"๋ถ์\", \"๋
น์ด\", \"์ฐํ\"],\n",
" \"์ ์ด๋ถ๋\": [\"์ ์ด ๋ถ๋\", \"๋จ์ \", \"๋๊น\"], \"์ฒด๊ฒฐ๋ถ๋\": [\"๋ณผํธ\", \"์ฒด๊ฒฐ\", \"ํ๊ฑฐ\"], \"๊ณผ์ด\": [\"๊ณผ์ด\", \"์จ๋\", \"์ด์ด\"], \"์ค์๋\": [\"์ค์๋\", \"๋ถ์์ \", \"๋ฉ์ถค\"]}\n",
"SEV_KW = [(2, [\"์ฆ์ ์ด์ฉ ์ค์ง\", \"์ฌ์ฉ ๋ถ๊ฐ\", \"์ํ\"]), (1, [\"์ฑ๋ฅ ์ ํ\", \"์กฐ์ํ\"]), (0, [\"๊ฒฝ๋ฏธ\", \"์ง์ฅ์ด ์\"])]\n",
"CAUSE_KW = {\"์ ์กฐ ๋ถ๋\": [\"์ ํ\", \"์ถ๊ณ \", \"์ธ์\"], \"์ ๋น ํ์ง\": [\"์ ๋น ํ\", \"์ฌ์กฐ๋ฆฝ\", \"์ ๋น ์งํ\"], \"์ด์ฉ ๋ถ์ฃผ์\": [\"์ถฉ๊ฒฉ\", \"๊ณผ๋ถํ\", \"์ ๊ฒ ์์ด\"],\n",
" \"์ค๊ณ ์ทจ์ฝ\": [\"๋์ผ ์ฆ์\", \"๊ตฌ์กฐ์ \", \"์ฌ๋ฌ ๋ถ๋\"], \"์์ฐ ๋
ธํ\": [\"์ฌ์ฉ ์ฐํ\", \"๋
ธํ\", \"์๋ช
\"]}\n",
"PARTICLES = (\"์์\", \"์ผ๋ก\", \"์ด\", \"๊ฐ\", \"์\", \"๋\", \"์\", \"๋ฅผ\", \"์\", \"์\", \"๋ก\", \"์\", \"๊ณผ\", \"๋\", \"ํจ\", \"์\", \"๋จ\")\n",
"\n",
"\n",
"def norm(t):\n",
" \"\"\"์ ๊ทํ: ์ ๊ฐ->๋ฐ๊ฐ, ํน์๋ฌธ์ยท์ค๋ณต ๊ณต๋ฐฑ ์ ๊ฑฐ, ์ฝ์ด ํ์ฅ, ์คํ ๊ต์ \"\"\"\n",
" # [TODO : ํ์ ์ค์ต 10] ์ผ์ ์ฅ๋น ๊ฒฐํจ ๋ณด๊ณ (QDR) ๋ฐ์ดํฐ ๋ถ์ ๋ฐ ํ์ง ์ทจ์ฝ์ ์๋ณ ๋๊ตฌ ํตํฉ ์์ฑ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tok(t):\n",
" out = []\n",
" for w in re.split(r\"[\\s.,]+\", norm(t)):\n",
" if not w or re.fullmatch(r\"[\\d\\-A-Za-z]+\", w):\n",
" continue\n",
" for p in PARTICLES:\n",
" if len(w) > len(p) + 1 and w.endswith(p):\n",
" w = w[:-len(p)]\n",
" break\n",
" out.append(w)\n",
" return out\n",
"\n",
"\n",
"def label_part(t):\n",
" for a, i in PART_KW:\n",
" if a in t:\n",
" return i\n",
" return -1\n",
"\n",
"\n",
"def label_defect(t):\n",
" for d, ks in DEF_KW.items():\n",
" if any(k in t for k in ks):\n",
" return d\n",
" return \"๋ฏธ๋ถ๋ฅ\"\n",
"\n",
"\n",
"def label_sev(t):\n",
" for s, ks in SEV_KW:\n",
" if any(k in t for k in ks):\n",
" return s\n",
" return -1\n",
"\n",
"\n",
"def label_cause(t):\n",
" for c, ks in CAUSE_KW.items():\n",
" if any(k in t for k in ks):\n",
" return c\n",
" return \"๋ฏธ์\"\n",
"\n",
"CONFIG = {\"retrain_tolerance\": 0.005, \"alarm_ratio\": 1.9, \"alarm_z\": 4.0, \"since_short\": 62, \"min_cases\": 60, \"mfg_share\": 0.22, \"design_share\": 0.20, \"vendor_share\": 0.28}\n",
"OPS_GUIDE = { # docs/qlt05_operations_guide.md ์์ฝ\n",
" \"1. ๋ชฉ์ ๊ณผ ๋ฒ์\": \"์ผ์ QDR ์์ ยท์ค์ ์ผ๋ก ๋ค๋ฐ ๊ฒฐํจ ๋ถ์์ ํ์ง ์ทจ์ฝ์ ์ ์๋ณํ๋ ๋ณด์กฐ ๋๊ตฌ. ํ์ ๊ตฌ์ยท์ค๊ณ ๋ณ๊ฒฝ ์์ ์ ๊ธฐ์ ๊ฒฐ์ ์ ํ์ง๊ด.\",\n",
" \"2. ์
๋ ฅ ๋ฐ์ดํฐ\": \"QDR(๋ถ๋ยท์ฅ๋นยท๋ถ์ยท์์ ยท์น๋ช
๋), ์ ๋น ์ํ์ฒ์ ๊ฒฝ๊ณผ์ผ, ๊ฒ์ฌ ๋๊ธฐยท์ฌ๊ฒ์ฌ. ๊ฐ์ ์๋ณ์ฝ๋ ์ฌ์ฉ, ์ค์ ๋ถ๋ ์๋ฃ๋ ๋ถ๋ ๋ด์์๋ง ์ฒ๋ฆฌ.\",\n",
" \"3. ์ผ์ผ ์ด์ฉ ์ ์ฐจ\": \"์ ์ -> ๊ฒ์ฆยท๊ฒฉ๋ฆฌ -> ์ ์ ยท๋ผ๋ฒจ๋ง -> ์น๋ช
๋ ๋ถ๋ฅ -> ์๋ณ ์ง๊ณยท๊ฒฝ๋ณด -> ํ๋ณด ์ ์(๋๊ธฐ) -> ํ์ง๊ด ๊ฒฐ์ -> ๊ฒฐ๊ณผ ๊ธฐ๋ก.\",\n",
" \"4. ์น์ธ(HITL) ๊ธฐ์ค\": \"์ ์กฐ์ฌ ํ์ ๊ตฌ์ยท์ค๊ณ ๋ณ๊ฒฝยท์ธ์ฃผ์ ๋น ์์ ์ ํ์ง๊ด ์น์ธ ํ ํ์ , ๋ฌด์๋ต์ ์กฐ์น ๋ณด๋ฅ ํ ๋ถ๊ธฐ ํ์ ์ฌ์์ , ์ฌ์ ๊ธฐ๋ก.\",\n",
" \"5. ์ฌํ์ต ์ ์ฐจ\": \"๋ถ๊ธฐ 1ํ ์ ๊ท QDR๋ก ๋์ ์ ํ์ต, ๊ฒ์ฆ ์ ํ๋๊ฐ ๋๋น ์ง์ง ์์ ๋๋ง ๊ต์ฒด.\",\n",
" \"6. ์ฅ์ ๋์\": \"๋ชจ๋ธ ์ค๋ฅ ์ ํค์๋ ๊ท์น์ผ๋ก ๋์ฒด, ์์ยท๋น์ ์ ์์ ์ ์๋ ํ์ธ ๋ชฉ๋ก์ผ๋ก ๋ถ๋ฆฌ.\",\n",
" \"7. ์ฐ๋ ์ธํฐํ์ด์ค\": \"๊ตญ๋ฐฉ ๊ตฐ์ ์ ๋ณด์ฒด๊ณ์ QDRยท์ ๋น ์ด๋ ฅ ํ์ค ํ๋(์ฅ๋น์ฝ๋ยท๋ถ๋ฅ๊ตฐยท์ผ์)๋ฅผ CSV/API๋ก ๊ตํํ๋ ์์ ์ฐจ๊ธฐ ๊ณผ์ ๋ก ๋๋ค.\",\n",
"}\n",
"names = [p[0] for p in PARTS]\n",
"oh = lambda d: sp.csr_matrix(np.hstack([np.eye(NP)[d[\"part\"]], pd.get_dummies(d[\"defect\"]).reindex(columns=DNAMES, fill_value=0).values.astype(float)]))\n",
"\n",
"\n",
"def validate(d):\n",
" ok = d[\"text\"].str.len().between(5, 400) & ~d[\"text\"].str.contains(\"๏ฟฝ\", regex=False)\n",
" return d[ok].copy(), d[~ok].copy()\n",
"\n",
"\n",
"def fit_sev(d):\n",
" vec = TfidfVectorizer(tokenizer=tok, lowercase=False, token_pattern=None, min_df=5)\n",
" X = sp.hstack([vec.fit_transform(d[\"text\"]), oh(d)]).tocsr()\n",
" return vec, LogisticRegression(C=5, max_iter=500).fit(X, d[\"sev\"])\n",
"\n",
"\n",
"def predict_sev(model, d):\n",
" vec, clf = model\n",
" return clf.predict(sp.hstack([vec.transform(d[\"text\"]), oh(d)]).tocsr())\n",
"\n",
"\n",
"def retrain(champion, old, new):\n",
" \"\"\"๋์ ์(์ง๋๋ฌ+์ด๋ฒ ๋ฌ ์ 2/3) vs ์ฑํผ์ธ(์ง๋๋ฌ) -> ์ด๋ฒ ๋ฌ ๋ค 1/3 ๊ฒ์ฆ ์ ํ๋ ๋น๊ต, ๋น์ทํ๊ฑฐ๋ ๋์ ๋๋ง ๊ต์ฒด\"\"\"\n",
" cut = new[\"month\"].quantile(2 / 3)\n",
" hold, new_tr = new[new[\"month\"] > cut], new[new[\"month\"] <= cut]\n",
" challenger = fit_sev(pd.concat([old, new_tr], ignore_index=True))\n",
" a_old, a_new = (predict_sev(champion, hold) == hold[\"sev\"].values).mean(), (predict_sev(challenger, hold) == hold[\"sev\"].values).mean()\n",
" swap = a_new >= a_old - CONFIG[\"retrain_tolerance\"]\n",
" return (challenger if swap else champion), {\"์ฑํผ์ธ\": round(float(a_old), 3), \"๋์ ์\": round(float(a_new), 3), \"๊ต์ฒด\": bool(swap)}\n",
"\n",
"\n",
"def sev_safe(model, d):\n",
" try:\n",
" return predict_sev(model, d), \"๋ชจ๋ธ\"\n",
" except Exception:\n",
" return d[\"text\"].map(label_sev).replace(-1, 0).values, \"ํค์๋ ๊ท์น ๋์ฒด\"\n",
"\n",
"\n",
"def cause_rule(d):\n",
" c = d[\"text\"].map(norm).map(label_cause)\n",
" short = (c == \"๋ฏธ์\") & (d[\"vendor\"] == \"์ธ์ฃผB\") & (d[\"since_maint\"] <= CONFIG[\"since_short\"])\n",
" return c.where(~short, \"์ ๋น ํ์ง\")\n",
"\n",
"\n",
"# ---------------- 1. ํตํฉ ์คํ ----------------\n",
"t_start = time.perf_counter()\n",
"old, _, _ = make_qdr(36, seed=11, scale=1.5) # ์ง๋๋ฌ ์ค์ \n",
"new, _, _ = make_qdr(36, seed=1, scale=1.5) # ์ด๋ฒ ๋ฌ ์ค์ \n",
"ops, use, events = make_qdr(24, seed=21, scale=1.5) # ๋ค์ ๋ฌ ์ด์ฉ(์ ๊ท QDR)\n",
"ops.iloc[[10, 300], ops.columns.get_loc(\"text\")] = [\"\", \"๏ฟฝ๏ฟฝ ์ค๋ฅ\"] # ์์ ์์ 2๊ฑด ์ฃผ์
\n",
"champion = fit_sev(old)\n",
"model, rt = retrain(champion, old, new)\n",
"print(f\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ์ฌํ์ต: {rt} -> {'๋์ ์ ๊ต์ฒด' if rt['๊ต์ฒด'] else '์ฑํผ์ธ ์ ์ง'}\")\n",
"good, bad = validate(ops)\n",
"good = good.copy()\n",
"good[\"sev_pred\"], src = sev_safe(model, good)\n",
"good[\"cause_pred\"] = cause_rule(good)\n",
"sec = time.perf_counter() - t_start\n",
"acc = (good[\"sev_pred\"] == good[\"sev\"]).mean()\n",
"print(f\"์ด์ฉ QDR {len(ops)}๊ฑด: ๋ถ์ {len(good)}๊ฑด, ์๋ ํ์ธ {len(bad)}๊ฑด, ์น๋ช
๋ ์ถ์ฒ '{src}', ์ ํ๋ {acc:.1%}, ์ ์ฒด ์คํ {sec:.1f}์ด\")\n",
"\n",
"# ์๋ณ ์ง๊ณยท๊ฒฝ๋ณดยท์ทจ์ฝ์ฑ ์ง์\n",
"M = good.pivot_table(index=\"part\", columns=\"month\", values=\"qdr_id\", aggfunc=\"size\", fill_value=0).reindex(index=range(NP), columns=range(24), fill_value=0)\n",
"rate = (M / use[:24]).T.rolling(12, min_periods=12).mean().shift(1).T\n",
"exp = rate * use[:24]\n",
"Z = (M - exp) / np.sqrt(exp.clip(lower=1))\n",
"A = (M / exp.clip(lower=1) > CONFIG[\"alarm_ratio\"]) & (Z > CONFIG[\"alarm_z\"])\n",
"W_SEV = np.array([1, 3, 9])\n",
"V = good.assign(v=W_SEV[good[\"sev_pred\"]]).groupby(\"part\")[\"v\"].sum()\n",
"recent = good[good[\"month\"] >= 21].assign(v=lambda d: W_SEV[d[\"sev_pred\"]]).groupby(\"part\")[\"v\"].sum().sort_values(ascending=False)\n",
"\n",
"# ํ๋ณด ์ ์(์น์ธ ๋๊ธฐ): ์ต๊ทผ 12๊ฐ์ ๊ทผ๊ฑฐ\n",
"ev = good[good[\"month\"] >= 12]\n",
"cands = []\n",
"for p, g in ev.groupby(\"part\"):\n",
" mf, ds = (g[\"cause_pred\"] == \"์ ์กฐ ๋ถ๋\").mean(), (g[\"cause_pred\"] == \"์ค๊ณ ์ทจ์ฝ\").mean()\n",
" if len(g) >= CONFIG[\"min_cases\"] and mf >= CONFIG[\"mfg_share\"]: cands.append((names[p], \"ํ์ ๊ตฌ์\", len(g), mf))\n",
" if len(g) >= CONFIG[\"min_cases\"] and ds >= CONFIG[\"design_share\"]: cands.append((names[p], \"์ค๊ณ ๋ณ๊ฒฝ\", len(g), ds))\n",
"for v, g in ev.groupby(\"vendor\"):\n",
" sh = (g[\"cause_pred\"] == \"์ ๋น ํ์ง\").mean()\n",
" if v != \"๋ถ๋์ ๋น\" and len(g) >= 100 and sh >= CONFIG[\"vendor_share\"]: cands.append((v, \"์ธ์ฃผ์ ๋น ์์ \", len(g), sh))\n",
"C = pd.DataFrame(cands, columns=[\"๋์\", \"์ ํ\", \"๊ทผ๊ฑฐ ๊ฑด์\", \"์์ธ ๋น์ค\"])\n",
"C[\"status\"] = \"PENDING\" # ํ์ง๊ด ๊ฒฐ์ ์ ๊น์ง ํ์ ํ์ง ์๋๋ค\n",
"\n",
"# ---------------- 2. ์์ฒด ์ ๊ฒ ----------------\n",
"_, bad_src = sev_safe(None, good.head(3))\n",
"checks = {\n",
" \"QDR ์ ์ค 0๊ฑด\": len(good) + len(bad) == len(ops),\n",
" \"์์ ์์ 2๊ฑด ์๋ ํ์ธ ๋ถ๋ฆฌ\": len(bad) == 2,\n",
" \"์ฌํ์ต ํ์ดํ๋ผ์ธ ์คํ\": \"๊ต์ฒด\" in rt,\n",
" \"์น๋ช
๋ ์ ํ๋ 80% ์ด์\": bool(acc >= 0.80),\n",
" \"๋ชจ๋ธ ์ฅ์ ์ ํค์๋ ๊ท์น ๋์ฒด\": bad_src == \"ํค์๋ ๊ท์น ๋์ฒด\",\n",
" \"๊ฒฝ๋ณด ์
๋ชจ๋ 12๊ฐ์ ์ดํ\": not bool(A.iloc[:, :12].values.any()),\n",
" \"ํ๋ณด ์ ์ 1๊ฑด ์ด์ ์์ฑ\": len(C) >= 1,\n",
" \"์๋ ์น์ธ 0๊ฑด(ํ๋ณด๋ ๋๊ธฐ)\": bool((C[\"status\"] == \"PENDING\").all()),\n",
"}\n",
"print(f\"\\n์์ฒด ์ ๊ฒ {sum(checks.values())}/{len(checks)} ํต๊ณผ, ์๋ ์ฌํ์ต ํ์ดํ๋ผ์ธ {'์ ์ ์คํ' if checks['์ฌํ์ต ํ์ดํ๋ผ์ธ ์คํ'] else '์คํจ'}\")\n",
"for k, v in checks.items():\n",
" if not v:\n",
" print(\" ์คํจ:\", k)\n",
"doc = sum(bool(v.strip()) for v in OPS_GUIDE.values()) / len(OPS_GUIDE)\n",
"print(f\"์ด์ ๋ฌธ์ {len(OPS_GUIDE)}๊ฐ ์ฅ ์์ฑ๋ฅ {doc:.0%} (๋ชฉํ 100%: {'๋ฌ์ฑ' if doc == 1 else '๋ฏธ๋ฌ'}) - \" + \", \".join(k.split(\". \")[1] for k in OPS_GUIDE))\n",
"\n",
"# ---------------- 3. ํ๋ฉด๊ณผ ๋ธ๋ฆฌํ ----------------\n",
"fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n",
"ax[0].imshow((M / exp.clip(lower=1)).clip(upper=3).values, aspect=\"auto\", cmap=\"Reds\"); ax[0].set_title(\"QDR concentration (x expected)\"); ax[0].set_xlabel(\"month\"); ax[0].set_ylabel(\"part\")\n",
"ax[1].bar(range(NP), V.reindex(range(NP)).fillna(0).values); ax[1].set_title(\"Vulnerability index by part\"); ax[1].set_xlabel(\"part id\")\n",
"plt.tight_layout(); plt.show()\n",
"\n",
"fa = [(names[p], int(m)) for p, m in zip(*np.where(A.values))]\n",
"print(\"\\n[ํ์ง ๋ด๋น ์ฅ๊ต ๋ธ๋ฆฌํ ์์ฝ] [๊ต์ก์ฉ๊ฐ์์๋ฃ]\")\n",
"print(f\"1. ์ด์ฉ 24๊ฐ์ QDR {len(ops)}๊ฑด ์ค ์์ {len(bad)}๊ฑด ์๋ ํ์ธ, ์น๋ช
๋ Critical {int((good['sev_pred'] == 2).sum())}๊ฑด({(good['sev_pred'] == 2).mean():.0%})\")\n",
"print(\"2. ์ต๊ทผ 3๊ฐ์ ์ทจ์ฝ์ฑ ์ง์ ์์ ๋ถ์: \" + \", \".join(f\"{names[p]} {v:.0f}\" for p, v in recent.head(3).items()))\n",
"print(f\"3. ์๋ณ ๊ธ์ฆ ๊ฒฝ๋ณด {len(fa)}๊ฑด\" + (\": \" + \", \".join(f\"{n}({m}์)\" for n, m in fa[:3]) if fa else \"\"))\n",
"print(f\"4. ํ๋ณด ์ ์ {len(C)}๊ฑด์ ํ์ง๊ด ์น์ธ ๋๊ธฐ(์๋ ์น์ธ 0๊ฑด): \" + \", \".join(f\"{r['๋์']} {r['์ ํ']}\" for _, r in C.head(4).iterrows()))\n",
"vb = good[good[\"vendor\"] == \"์ธ์ฃผB\"]\n",
"print(f\"5. ์ธ์ฃผB ๊ฒ์ฌ ๋๊ธฐ ํ๊ท {vb['inspect_wait'].mean():.1f}์ผ(ํ ์ํ์ฒ {good.loc[good['vendor'] != '์ธ์ฃผB', 'inspect_wait'].mean():.1f}์ผ), ์ฌ๊ฒ์ฌ์จ {vb['reinspect'].mean():.0%}\")\n",
"print(f\"6. ์น๋ช
๋ ๋ชจ๋ธ: {'์ ๊ท ์ค์ ์ผ๋ก ๊ต์ฒด' if rt['๊ต์ฒด'] else '๊ธฐ์กด ์ ์ง'}(๊ฒ์ฆ ์ ํ๋ {max(rt['์ฑํผ์ธ'], rt['๋์ ์']):.1%})\")\n",
"print(\"7. ๊ฑด์: ์ธ์ฃผ์ ๋น ์
์ฒด ์์ ์๊ตฌ์ ์ ์กฐ์ฌ ํ์ ๊ตฌ์ ๊ทผ๊ฑฐ ์ ๋ฆฌ, ๊ตญ๋ฐฉ ๊ตฐ์ ์ ๋ณด์ฒด๊ณ์ QDR ํ์ค ํ๋ ์ฐ๋ ์ธํฐํ์ด์ค ์ ์\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
10 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ์ฌํ์ต: {'์ฑํผ์ธ': 0.867, '๋์ ์': 0.865, '๊ต์ฒด': True} -> ๋์ ์ ๊ต์ฒด\n",
"์ด์ฉ QDR 8923๊ฑด: ๋ถ์ 8921๊ฑด, ์๋ ํ์ธ 2๊ฑด, ์น๋ช
๋ ์ถ์ฒ '๋ชจ๋ธ', ์ ํ๋ 87.8%, ์ ์ฒด ์คํ 7.3์ด\n",
"\n",
"์์ฒด ์ ๊ฒ 8/8 ํต๊ณผ, ์๋ ์ฌํ์ต ํ์ดํ๋ผ์ธ ์ ์ ์คํ\n",
"์ด์ ๋ฌธ์ 7๊ฐ ์ฅ ์์ฑ๋ฅ 100% (๋ชฉํ 100%: ๋ฌ์ฑ) - ๋ชฉ์ ๊ณผ ๋ฒ์, ์
๋ ฅ ๋ฐ์ดํฐ, ์ผ์ผ ์ด์ฉ ์ ์ฐจ, ์น์ธ(HITL) ๊ธฐ์ค, ์ฌํ์ต ์ ์ฐจ, ์ฅ์ ๋์, ์ฐ๋ ์ธํฐํ์ด์ค\n",
"\n",
"[ํ์ง ๋ด๋น ์ฅ๊ต ๋ธ๋ฆฌํ ์์ฝ] [๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"1. ์ด์ฉ 24๊ฐ์ QDR 8923๊ฑด ์ค ์์ 2๊ฑด ์๋ ํ์ธ, ์น๋ช
๋ Critical 717๊ฑด(8%)\n",
"2. ์ต๊ทผ 3๊ฐ์ ์ทจ์ฝ์ฑ ์ง์ ์์ ๋ถ์: ์ ๋ ์ฅ์น 378, ์กฐํฅ ์ฅ์น 268, ์์ง ์ฐ๋ฃ๊ณํต 244\n",
"3. ์๋ณ ๊ธ์ฆ ๊ฒฝ๋ณด 3๊ฑด: ์กฐํฅ ์ฅ์น(20์), ๋๊ฐ ๊ณํต(16์), ์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ(18์)\n",
"4. ํ๋ณด ์ ์ 3๊ฑด์ ํ์ง๊ด ์น์ธ ๋๊ธฐ(์๋ ์น์ธ 0๊ฑด): ๋๊ฐ ๊ณํต ํ์ ๊ตฌ์, ์ ๊ธฐยท๋ฐฐํฐ๋ฆฌ ํ์ ๊ตฌ์, ์ธ์ฃผB ์ธ์ฃผ์ ๋น ์์ \n",
"5. ์ธ์ฃผB ๊ฒ์ฌ ๋๊ธฐ ํ๊ท 8.7์ผ(ํ ์ํ์ฒ 4.5์ผ), ์ฌ๊ฒ์ฌ์จ 26%\n",
"6. ์น๋ช
๋ ๋ชจ๋ธ: ์ ๊ท ์ค์ ์ผ๋ก ๊ต์ฒด(๊ฒ์ฆ ์ ํ๋ 86.7%)\n",
"7. ๊ฑด์: ์ธ์ฃผ์ ๋น ์
์ฒด ์์ ์๊ตฌ์ ์ ์กฐ์ฌ ํ์ ๊ตฌ์ ๊ทผ๊ฑฐ ์ ๋ฆฌ, ๊ตญ๋ฐฉ ๊ตฐ์ ์ ๋ณด์ฒด๊ณ์ QDR ํ์ค ํ๋ ์ฐ๋ ์ธํฐํ์ด์ค ์ ์\n",
"```\n",
" \n"
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}