{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# ๐ฏ [DAT06] ์๊ฐ ์ ์ฐยท๋ณด๊ณ ยท์ทจํฉ One-click ์๋ํ\n",
"### Google Colab ์ค์ต ํ๊ฒฝ (๋ฐ์ดํฐยท์๋ํยท์
๋ฌดํ๋ซํผ / Level 3)\n",
"\n",
"[](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/DAT06/DAT06_solution.ipynb)\n",
"\n",
"---\n",
"\n",
"## ๐ 1. ํ๋ก์ ํธ ๊ฐ์ ๋ฐ ๋ฐฐ๊ฒฝ\n",
"\n",
"| ํญ๋ชฉ | ๋ด์ฉ |\n",
"| :--- | :--- |\n",
"| **๊ณผ์ ๋ถ๋ฅ** | ๋ฐ์ดํฐยท์๋ํยท์
๋ฌดํ๋ซํผ (Level 3) |\n",
"| **ํ์ต ์์ ์๊ฐ** | 10๊ฐ ๋ฏธ์
(5์ผ / ์ด 27.0์๊ฐ) |\n",
"| **ํ๊ฒฝ ์ฌ์** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n",
"\n",
"### ๐ ํ๋ก์ ํธ ๋ชฉํ\n",
"๋ฏธ๊ตญ ๊ตญ๋ฐฉ์ฌ๋ฌดํ๊ณ๊ตญ(DFAS)์ ๋ฐ๋ณต์ ์ธ ์ฌ๋ฌดยท์ ์ฐ ์
๋ฌด์ ์
๋ฌด ์๋ํ(RPA)๋ฅผ ๋์
ํด ์ฌ๋์ด ํ๋ ์ทจํฉ๊ณผ ์ ๊ฒ์ ์ค์ด๋ ์ฌ์
์ ์ถ์งํด ์๋ค. ์ด ๊ณต๊ฐ ์ฌ๋ก์ ํ์ต ์ด์ ์ ๋ฐ๋ณต ์
๋ฌด ๋ถํด, ์
๋ ฅ๊ณผ ํธ๋ฆฌ๊ฑฐ, ์๋ ์ฒ๋ฆฌ ์์, ์ค๋ฅ ๋ณต๊ตฌ, ์คํ ๋ก๊ทธ, ์๊ฐ ์ ๊ฐ์ด๋ค. ๋ณธ ํ๋ก์ ํธ๋ ์ด๋ฅผ ๋ฐํ์ผ๋ก ๊ต์ก์ฉ ๊ฐ์ ๋ถ๋ 24๊ฐ์ ์๊ฐ ๊ฒฐ์ฐ ์๋ฃ(๋ฌผ์ ๋ถ์ถ, ์๋ฆฌ๋ถ์ ์๋ชจ, ํ์ฝ ์ ์ฐ, ์ฅ๋น ๊ฐ๋๋ฅ )๋ฅผ ์ฝ๋๋ก ๋ง๋ค๊ณ , ํ์ผ ๋์ ๋ฉ๋ชจ๋ฆฌ ์์ ๊ฐ์ ๋ฌธ์์ ํ๋ก ๋ชจ์ํ๋ค. ํ์ต์๋ ์
๋ฌด ๋ถํด, ๋คํ์ ํ์ฑ, ๊ฒ์ฆ ๋ฃฐ ์์ง, ์ด์ ์์ธ ์ถ์ฒ, ์ผ๊ด์ฑ ๊ฒ์ฆ, ์น์ธยท๋ฐ๋ ค ๋์๋ณด๋, 3๋จ๊ณ ์ฌ๋ ๊ฒฐ์ฌ์ , ๋๋ ๋ฐฐ์น ์ํ, AAR ๋ณด์ , ์ต์ข
ํตํฉ์ ์ฐจ๋ก๋ก ์ํํ๋ค. ์ต์ข
๋ชฉํ๋ ์๊ฐ ์ ์ฐยท๋ณด๊ณ ยท์ทจํฉ์ ํ ๋ฒ์ ์คํํ๋ ๋ฐ๊ฐ์ ๋ฐ๋์ ์ฌ๋์ด ๊ฒฐ์ฌํ๋๋ก ํ๋ ์ํด๋ฆญ ์๋ํ ๋๊ตฌ๋ฅผ ์์ฑํ๋ ๊ฒ์ด๋ค.\n",
"\n",
"> **โ ๏ธ [๊ต์ก์ฉ ๊ฐ์ ๋ฐ์ดํฐ์
์์น]**\n",
"> - ๋ณธ ๋
ธํธ๋ถ์ ๋ชจ๋ ๋ฐ์ดํฐ(์ ์, ๋ก๊ทธ, ์๋๋ฆฌ์ค ๋ฑ)๋ ๋ณด์ ๊ท์ ์ ์ค์ํ์ฌ **์ฝ๋ ๋ด ๋์ ๊ธฐ๋ฐ ๊ฐ์ ๋ฐ์ดํฐ๋ก ์์ฑ**๋ฉ๋๋ค.\n",
"> - ์ค์ ๋ถ๋ ์์น, ์์ ์ธ๊ฐ, ์ค์ ์ ์ฅ ์ ๋ณด, ๊ตฐ์ฌ ๊ธฐ๋ฐ ๋ฐ์ดํฐ๋ ์ผ์ฒด ํฌํจ๋์ง ์์ต๋๋ค.\n",
"\n",
"---\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ============================================================================\n",
"# [Google Colab ํ๊ฒฝ ์ค์ ] ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น ๋ฐ ์ํฌํธ\n",
"# ============================================================================\n",
"!pip install -q numpy pandas matplotlib scipy scikit-learn\n",
"\n",
"import os\n",
"import sys\n",
"import time\n",
"import math\n",
"import json\n",
"import re\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import warnings\n",
"warnings.filterwarnings('ignore')\n",
"\n",
"# ๊ทธ๋ํ ์๋ฌธ ํฐํธ ๊ธฐ๋ณธ ์ค์ (ํ๊ฒฝ ๋
๋ฆฝ์ฑ ๋ณด์ฅ)\n",
"plt.rcParams['font.sans-serif'] = 'DejaVu Sans'\n",
"plt.rcParams['axes.unicode_minus'] = False\n",
"\n",
"print(f\"์คํ ํ๊ฒฝ ์ค๋น ์๋ฃ (Python {sys.version.split()[0]})\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
01. ๊ตฐ์ ์๊ฐ ๊ฒฐ์ฐ ํญ๋ชฉ(๋ฌผ์ ๋ถ์ถ, ์๋ฆฌ๋ถ์ ์๋ชจ, ํ์ฝ ์ ์ฐ, ์ฅ๋น ๊ฐ๋๋ฅ ) ๋ฐ์ดํฐ ๊ตฌ์กฐ ๋ถ์\n",
"**์์์๊ฐ**: ์ฝ 1.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ตฐ์ ์๊ฐ ๊ฒฐ์ฐ ํญ๋ชฉ(๋ฌผ์ ๋ถ์ถ, ์๋ฆฌ๋ถ์ ์๋ชจ, ํ์ฝ ์ ์ฐ) ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๋ถ์ํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์๊ฐ ๊ฒฐ์ฐ 4๊ฐ ํญ๋ชฉ(๋ฌผ์ ๋ถ์ถ, ์๋ฆฌ๋ถ์ ์๋ชจ, ํ์ฝ ์ ์ฐ, ์ฅ๋น ๊ฐ๋๋ฅ )์ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๋ถ์ํด ํ๋์ ์ฐ์์ ์ ์ํ๊ณ , ํํ ๊ฒฐ์ฐ ์
๋ฌด๋ฅผ AS-IS/TO-BE๋ก ๋น๊ตํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฐ์ ๋ถ๋ 24๊ฐ์ ์๊ฐ ์์ฅ(๊ธฐ์ดยท์๋ นยท๋ถ์ถยท๋ฐ๋ฉยท๊ธฐ๋ง)๊ณผ ์ฅ๋น ๊ฐ๋ ํํฉ์ ๋ง๋ค์ด ํญ๋ชฉ๋ณ ํ๋์ ์ฌ๊ณ ์ฐ์(๊ธฐ๋ง = ๊ธฐ์ด + ์๋ น + ๋ฐ๋ฉ - ๋ถ์ถ) ์ ๋ฆฌํ๊ณ ๊ท๊ฒฉํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `docs/dat06_as_is_to_be_spec.md` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ํํ ์
๋ฌด(AS-IS)์ ๊ฐ์ ์
๋ฌด(TO-BE) ๋น๊ต์ ๋ช
ํ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์
๋ฌด ๋จ๊ณ ๋ถํด 10๋จ๊ณ ์ด์, ๋ณ๋ชฉ ๊ตฌ๊ฐ 3๊ฐ์ ์ด์ ๋์ถ\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์
๋ฌด ์ ์์ ๋ฐ ์
์ถ๋ ฅ ๋ฐ์ดํฐ ๋ช
์ธ ์์ฑ ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ์ค๋ฌด ํ๋ก์ธ์ค์์ ์ผ์น์ฑ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# [TODO : ํ์ ์ค์ต 1] ๊ตฐ์ ์๊ฐ ๊ฒฐ์ฐ ํญ๋ชฉ(๋ฌผ์ ๋ถ์ถ, ์๋ฆฌ๋ถ์ ์๋ชจ, ํ์ฝ ์ ์ฐ, ์ฅ๋น ๊ฐ๋๋ฅ ) ๋ฐ์ดํฐ ๊ตฌ์กฐ ๋ถ์\n",
"# ๋ฏธ์
์ง์นจ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค์ ํ์ธํ๊ณ ์ฝ๋๋ฅผ ์คํ ๋ฐ ํ๋ํ์ธ์.\n",
"\"\"\"๊ตฐ์ ์๊ฐ ๊ฒฐ์ฐ 4๊ฐ ํญ๋ชฉ(๋ฌผ์ ๋ถ์ถ, ์๋ฆฌ๋ถ์ ์๋ชจ, ํ์ฝ ์ ์ฐ, ์ฅ๋น ๊ฐ๋๋ฅ )์ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๋ถ์ํด ์คํค๋ง๋ฅผ ์ ์ํ๊ณ , ์๊ฐ ๊ฒฐ์ฐ ์
๋ฌด๋ฅผ AS-IS/TO-BE ๋จ๊ณ๋ก ๋ถํดํด ๋ณ๋ชฉ ๊ตฌ๊ฐ์ ๋์ถํ๋ค.\"\"\"\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์๊ฐ ๊ฒฐ์ฐ ์์ฅ ์์ฑ๊ธฐ (์ค์ ๋ถ๋๋ช
ยทํ๋ชฉยท์๋ยท์ฌ๋ฌด ์ ๋ณด ์์)\n",
"# ๊ฐ์ ๋๋ 24๊ฐ x (๋ฌผ์ ๋ถ์ถ / ์๋ฆฌ๋ถ์ ์๋ชจ / ํ์ฝ ์ ์ฐ) ํ๋ชฉ ์์ฅ๊ณผ ์ฅ๋น ๊ฐ๋ ํํฉ์ ๋ง๋ ๋ค.\n",
"# ์ฌ๊ณ ์ฐ์: ๊ธฐ๋ง = ๊ธฐ์ด + ์๋ น + ๋ฐ๋ฉ - ๋ถ์ถ, ๋ค์ ๋ฌ ๊ธฐ์ด = ์ด๋ฒ ๋ฌ ๊ธฐ๋ง.\n",
"# ---------------------------------------------------------------------------\n",
"UNITS = [f\"๊ฐ์{i:02d}๋๋\" for i in range(1, 25)]\n",
"ITEMS = {\"๋ฌผ์๋ถ์ถ\": [\"๊ธ์๋ฅ\", \"ํผ๋ณต๋ฅ\", \"์ ๋ฅ\", \"์ ๋น์๋ชจํ\"],\n",
" \"์๋ฆฌ๋ถ์\": [\"ํํฐ๋ฅ\", \"๋ฒ ์ด๋ง๋ฅ\", \"์ ์ฅํ\", \"ํ์ด์ด๋ฅ\"],\n",
" \"ํ์ฝ์ ์ฐ\": [\"ํ๋ จํA\", \"ํ๋ จํB\", \"๋ชจ์ํ\"]}\n",
"UNIT_CODE = {\"๊ธ์๋ฅ\": \"BX\", \"ํผ๋ณต๋ฅ\": \"EA\", \"์ ๋ฅ\": \"L\", \"์ ๋น์๋ชจํ\": \"EA\", \"ํํฐ๋ฅ\": \"EA\", \"๋ฒ ์ด๋ง๋ฅ\": \"EA\",\n",
" \"์ ์ฅํ\": \"EA\", \"ํ์ด์ด๋ฅ\": \"EA\", \"ํ๋ จํA\": \"RD\", \"ํ๋ จํB\": \"RD\", \"๋ชจ์ํ\": \"RD\"}\n",
"LEDGER_COLS = [\"month\", \"unit\", \"cat\", \"item\", \"open\", \"recv\", \"issue\", \"ret\", \"close\"]\n",
"\n",
"\n",
"def make_ledger(n_months=2, seed=60):\n",
" \"\"\"n_months๊ฐ์ ์์ฅ(list of dict). ๋์งธ ๋ฌ ๊ธฐ์ด๋ ์ฒซ์งธ ๋ฌ ๊ธฐ๋ง๊ณผ ๊ฐ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows, prev = [], {}\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" for cat, items in ITEMS.items():\n",
" for it in items:\n",
" base = int(rng.integers(80, 400))\n",
" op = prev.get((u, it), base * 3)\n",
" recv = int(rng.integers(0, base))\n",
" issue = int(rng.integers(base // 4, base // 2 + 1))\n",
" ret = int(issue * rng.uniform(0, 0.15))\n",
" cl = op + recv + ret - issue\n",
" rows.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"cat\": cat, \"item\": it,\n",
" \"open\": op, \"recv\": recv, \"issue\": issue, \"ret\": ret, \"close\": cl})\n",
" prev[(u, it)] = cl\n",
" return rows\n",
"\n",
"\n",
"def make_equip(n_months=2, seed=61):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" hold = int(rng.integers(20, 60))\n",
" out.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"hold\": hold, \"operable\": int(hold * rng.uniform(0.78, 0.97))})\n",
" return out\n",
"\n",
"# 1) ๋ฐ์ดํฐ ๊ตฌ์กฐ ๋ถ์: ์์ฅ 4๊ฐ ํญ๋ชฉ์ ํ๋ยทํ์ฉ๊ฐยท์ฐ์\n",
"led = pd.DataFrame(make_ledger(2), columns=LEDGER_COLS)\n",
"eq = pd.DataFrame(make_equip(2))\n",
"SCHEMA = {\n",
" \"๋ฌผ์๋ถ์ถ\": {\"fields\": \"month, unit, item, open, recv, issue, ret, close\", \"rule\": \"close = open + recv + ret - issue\"},\n",
" \"์๋ฆฌ๋ถ์\": {\"fields\": \"month, unit, item, open, recv, issue(์๋ชจ), ret, close\", \"rule\": \"close = open + recv + ret - issue\"},\n",
" \"ํ์ฝ์ ์ฐ\": {\"fields\": \"month, unit, item, open, recv, issue(์ฌ์ฉ), ret(์ํ ๋ฐ๋ฉ), close\", \"rule\": \"close = open + recv + ret - issue\"},\n",
" \"์ฅ๋น๊ฐ๋๋ฅ \": {\"fields\": \"month, unit, hold(๋ณด์ ), operable(๊ฐ๋)\", \"rule\": \"๊ฐ๋๋ฅ = operable / hold, 0 <= ๊ฐ๋๋ฅ <= 1\"},\n",
"}\n",
"print(\"[๊ฒฐ์ฐ ํญ๋ชฉ๋ณ ๋ฐ์ดํฐ ๊ตฌ์กฐ] [๊ต์ก์ฉ๊ฐ์์๋ฃ]\")\n",
"for k, v in SCHEMA.items():\n",
" print(f\" - {k}: ํ๋[{v['fields']}] ๊ท์น[{v['rule']}]\")\n",
"print(f\"\\n์์ฅ {len(led)}ํ(๋ถ๋ {led['unit'].nunique()}๊ฐ x ํ๋ชฉ {led['item'].nunique()}์ข
x 2๊ฐ์), ์ฅ๋น ํํฉ {len(eq)}ํ\")\n",
"chk = ((led[\"open\"] + led[\"recv\"] + led[\"ret\"] - led[\"issue\"]) == led[\"close\"]).mean()\n",
"cont = led.pivot_table(index=[\"unit\", \"item\"], columns=\"month\", values=[\"open\", \"close\"])\n",
"cont_ok = (cont[(\"close\", \"2026-07\")] == cont[(\"open\", \"2026-08\")]).mean()\n",
"print(f\"์ฌ๊ณ ์ฐ์ ์ผ์น์จ {chk:.0%}, ์ ๊ฐ ์ฐ์์ฑ(์ ์ ๊ธฐ๋ง=๋น์ ๊ธฐ์ด) {cont_ok:.0%}\")\n",
"print(\"๋ถ๋ฅ๋ณ ์ ๋ถ์ถยท์๋ชจ๋ ํฉ๊ณ:\")\n",
"print(led[led[\"month\"] == \"2026-08\"].groupby(\"cat\")[\"issue\"].sum().to_string())\n",
"print(f\"์ฅ๋น ํ๊ท ๊ฐ๋๋ฅ {(eq['operable'] / eq['hold']).mean():.1%} (๊ฐ๋๋ฅ ๋ฎ์ ๋ถ๋ 3๊ฐ: \"\n",
" f\"{', '.join(eq.assign(r=eq['operable'] / eq['hold']).nsmallest(3, 'r')['unit'])})\")\n",
"\n",
"# 2) ์๊ฐ ๊ฒฐ์ฐ ์
๋ฌด AS-IS ๋ถํด(๋จ๊ณ, ์์ ๋ถ - ๊ฐ์ ์ธก์ ๊ฐ)์ TO-BE\n",
"steps = [(\"์ํ ๋ถ๋๋ณ ๊ฒฐ์ฐ ์๋ฃ ์์ (๋ฉ์ผยท๋ฉ์ ์ )\", 40, \"์ ์ถ ์์ ํ์คํยท์๋ ์์ง ํจ์\", 3),\n",
" (\"์ ์ถ ์์ ํ์ธ(์์ ๋ค๋ฅธ ํ์ผ ๊ตฌ๋ถ)\", 35, \"ํ์ ์๋ ํ๋ณยท๋ณ์นญ ์ฌ์ \", 2),\n",
" (\"์์
๊ฐ ๋ณต์ฌยท๋ถ์ฌ๋ฃ๊ธฐ ์ทจํฉ\", 90, \"์๋ ํ์ฑยท๋ณํฉ\", 2),\n",
" (\"ํ๋ชฉ ์ฝ๋ยท๋จ์ ์๊ธฐ ํต์ผ\", 60, \"์ฝ๋ยท๋จ์ ํ์ค ๋ณํํ\", 3),\n",
" (\"๊ธฐ์ดยท๊ธฐ๋ง ์ฌ๊ณ ์ฐ์ ๋์กฐ\", 55, \"๊ฒ์ฆ ๋ฃฐ 15์ข
์๋ ์ ์ฉ\", 2),\n",
" (\"์ ์ ๋๋น ์ฆ๊ฐ ๊ณ์ฐ\", 30, \"์ ์ ๋น๊ตํ ์๋ ์์ฑ\", 1),\n",
" (\"์ด์ ์์น ํ์ธ(์ ํ ๋ฌธ์)\", 75, \"์ด์์น ํ๋ณดยท์์ธ ์์ฝ ์ถ์ฒ\", 15),\n",
" (\"์ฅ๋น ๊ฐ๋๋ฅ ์ง๊ณ\", 30, \"๊ฐ๋๋ฅ ์๋ ๊ณ์ฐ\", 1),\n",
" (\"๊ฒฐ์ฐ ๋ณด๊ณ ์ ์์ ์์ฑ\", 80, \"ํ
ํ๋ฆฟ์ ์๋ ์ฑ์\", 3),\n",
" (\"๋ณด๊ธ๊ดยท๊ตฐ์๊ณผ์ฅ ๊ฒฐ์ฌ ์์ฒญยทํ์\", 45, \"3๋จ๊ณ ์ ์ ๊ฒฐ์ฌ ํ๋ฆ\", 15),\n",
" (\"๋ฐ๋ ค ์ฌํญ ์์ ํ ์ฌ์ ์ถ\", 50, \"๋ฐ๋ ค ์ฌ์ ๋ณ ์์ ์์น ์๋ด\", 10),\n",
" (\"์ต์ข
๋ณธ ๋ฐฐํฌยท๋ณด๊ด\", 20, \"๋ฐ๊ฐ๋ณธ ์๋ ๋ณด๊ดยท๋ฐฐํฌ\", 1)]\n",
"proc = pd.DataFrame(steps, columns=[\"์
๋ฌด ๋จ๊ณ\", \"AS-IS(๋ถ)\", \"TO-BE ๊ฐ์ \", \"TO-BE(๋ถ)\"])\n",
"proc.index = range(1, len(proc) + 1)\n",
"thr = proc[\"AS-IS(๋ถ)\"].mean() + 0.5 * proc[\"AS-IS(๋ถ)\"].std()\n",
"proc[\"๋ณ๋ชฉ\"] = np.where(proc[\"AS-IS(๋ถ)\"] >= thr, \"๋ณ๋ชฉ\", \"\")\n",
"print(f\"\\n[AS-IS / TO-BE ์
๋ฌด ๋จ๊ณ {len(proc)}๋จ๊ณ] (๋ณ๋ชฉ ๊ธฐ์ค: ํ๊ท +0.5ํ์คํธ์ฐจ = {thr:.0f}๋ถ ์ด์)\")\n",
"print(proc[[\"์
๋ฌด ๋จ๊ณ\", \"AS-IS(๋ถ)\", \"TO-BE(๋ถ)\", \"๋ณ๋ชฉ\"]].to_string())\n",
"nb = int((proc[\"๋ณ๋ชฉ\"] == \"๋ณ๋ชฉ\").sum())\n",
"a, t = proc[\"AS-IS(๋ถ)\"].sum(), proc[\"TO-BE(๋ถ)\"].sum()\n",
"print(f\"\\n์
๋ฌด ๋จ๊ณ {len(proc)}๋จ๊ณ(๋ชฉํ 10๋จ๊ณ ์ด์: {'๋ฌ์ฑ' if len(proc) >= 10 else '๋ฏธ๋ฌ'}), ๋ณ๋ชฉ {nb}๊ฐ์(๋ชฉํ 3๊ฐ์ ์ด์: {'๋ฌ์ฑ' if nb >= 3 else '๋ฏธ๋ฌ'})\")\n",
"print(f\"1ํ ๊ฒฐ์ฐ AS-IS {a}๋ถ -> TO-BE {t}๋ถ ({1 - t / a:.0%} ๋จ์ถ ์์, ๊ฐ์ ๊ฐ)\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
01 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ฒฐ์ฐ ํญ๋ชฉ๋ณ ๋ฐ์ดํฐ ๊ตฌ์กฐ] [๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
" - ๋ฌผ์๋ถ์ถ: ํ๋[month, unit, item, open, recv, issue, ret, close] ๊ท์น[close = open + recv + ret - issue]\n",
" - ์๋ฆฌ๋ถ์: ํ๋[month, unit, item, open, recv, issue(์๋ชจ), ret, close] ๊ท์น[close = open + recv + ret - issue]\n",
" - ํ์ฝ์ ์ฐ: ํ๋[month, unit, item, open, recv, issue(์ฌ์ฉ), ret(์ํ ๋ฐ๋ฉ), close] ๊ท์น[close = open + recv + ret - issue]\n",
" - ์ฅ๋น๊ฐ๋๋ฅ : ํ๋[month, unit, hold(๋ณด์ ), operable(๊ฐ๋)] ๊ท์น[๊ฐ๋๋ฅ = operable / hold, 0 <= ๊ฐ๋๋ฅ <= 1]\n",
"\n",
"์์ฅ 528ํ(๋ถ๋ 24๊ฐ x ํ๋ชฉ 11์ข
x 2๊ฐ์), ์ฅ๋น ํํฉ 48ํ\n",
"์ฌ๊ณ ์ฐ์ ์ผ์น์จ 100%, ์ ๊ฐ ์ฐ์์ฑ(์ ์ ๊ธฐ๋ง=๋น์ ๊ธฐ์ด) 100%\n",
"๋ถ๋ฅ๋ณ ์ ๋ถ์ถยท์๋ชจ๋ ํฉ๊ณ:\n",
"cat\n",
"๋ฌผ์๋ถ์ถ 8270\n",
"์๋ฆฌ๋ถ์ 8184\n",
"ํ์ฝ์ ์ฐ 6743\n",
"์ฅ๋น ํ๊ท ๊ฐ๋๋ฅ 87.0% (๊ฐ๋๋ฅ ๋ฎ์ ๋ถ๋ 3๊ฐ: ๊ฐ์23๋๋, ๊ฐ์19๋๋, ๊ฐ์22๋๋)\n",
"\n",
"[AS-IS / TO-BE ์
๋ฌด ๋จ๊ณ 12๋จ๊ณ] (๋ณ๋ชฉ ๊ธฐ์ค: ํ๊ท +0.5ํ์คํธ์ฐจ = 62๋ถ ์ด์)\n",
" ์
๋ฌด ๋จ๊ณ AS-IS(๋ถ) TO-BE(๋ถ) ๋ณ๋ชฉ\n",
"1 ์ํ ๋ถ๋๋ณ ๊ฒฐ์ฐ ์๋ฃ ์์ (๋ฉ์ผยท๋ฉ์ ์ ) 40 3 \n",
"2 ์ ์ถ ์์ ํ์ธ(์์ ๋ค๋ฅธ ํ์ผ ๊ตฌ๋ถ) 35 2 \n",
"3 ์์
๊ฐ ๋ณต์ฌยท๋ถ์ฌ๋ฃ๊ธฐ ์ทจํฉ 90 2 ๋ณ๋ชฉ\n",
"4 ํ๋ชฉ ์ฝ๋ยท๋จ์ ์๊ธฐ ํต์ผ 60 3 \n",
"5 ๊ธฐ์ดยท๊ธฐ๋ง ์ฌ๊ณ ์ฐ์ ๋์กฐ 55 2 \n",
"6 ์ ์ ๋๋น ์ฆ๊ฐ ๊ณ์ฐ 30 1 \n",
"7 ์ด์ ์์น ํ์ธ(์ ํ ๋ฌธ์) 75 15 ๋ณ๋ชฉ\n",
"8 ์ฅ๋น ๊ฐ๋๋ฅ ์ง๊ณ 30 1 \n",
"9 ๊ฒฐ์ฐ ๋ณด๊ณ ์ ์์ ์์ฑ 80 3 ๋ณ๋ชฉ\n",
"10 ๋ณด๊ธ๊ดยท๊ตฐ์๊ณผ์ฅ ๊ฒฐ์ฌ ์์ฒญยทํ์ 45 15 \n",
"11 ๋ฐ๋ ค ์ฌํญ ์์ ํ ์ฌ์ ์ถ 50 10 \n",
"12 ์ต์ข
๋ณธ ๋ฐฐํฌยท๋ณด๊ด 20 1 \n",
"\n",
"์
๋ฌด ๋จ๊ณ 12๋จ๊ณ(๋ชฉํ 10๋จ๊ณ ์ด์: ๋ฌ์ฑ), ๋ณ๋ชฉ 3๊ฐ์(๋ชฉํ 3๊ฐ์ ์ด์: ๋ฌ์ฑ)\n",
"1ํ ๊ฒฐ์ฐ AS-IS 610๋ถ -> TO-BE 58๋ถ (90% ๋จ์ถ ์์, ๊ฐ์ ๊ฐ)\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
02. ๋ค์ํ ์์์ ์์
/CSV ๋ฐ์ดํฐ ์๋ ํ์ฑ, ์ ํฉ์ฑ ๊ฒ์ฆ ๋ฐ ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ ๊ตฌ์ถ\n",
"**์์์๊ฐ**: ์ฝ 2.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์์
/CSV ๋ฐ์ดํฐ ์๋ ํ์ฑ, ์ ํฉ์ฑ ๊ฒ์ฆ ๋ฐ ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **CSV, ์์
๋ด๋ณด๋ด๊ธฐ(ํญ ๊ตฌ๋ถ), JSON, ๊ณต๋ฌธ ํ
์คํธ ํ์์ ๊ฒฐ์ฐ ์๋ฃ๋ฅผ ์๋ ํ์ฑํ๊ณ ์ ํฉ์ฑ์ ๊ฒ์ฆํ๋ ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์๋ก ๋ค๋ฅธ ์์์ ๊ฐ์ ๊ฒฐ์ฐ ์๋ฃ 4์ข
์ ๋ฉ๋ชจ๋ฆฌ ์์ ๋ฌธ์์ด๋ก ๋ง๋ค๊ณ ํ์๋ณ ํ์๋ก ํ์ค ๋ ์ฝ๋๋ก ๋ฐ๊พผ๋ค ๊ณผ์ ์ ์งํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/connectors/dat06_data_loader.py ๋ฐ ์คํค๋ง ์ ์์` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ตญ๋ฐฉ ์์/์์ ๋ณ๊ฒฝ์ ๋ํ ์ ์ฐํ ํ์ฅ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์ง์ ํ์ผ ํฌ๋งท(CSV, Excel, JSON ๋ฑ) ํ์ฑ ์ฑ๊ณต๋ฅ 100%\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ๋ฐ์ดํฐ ๋ก๋ ๋ฐ ์คํค๋ง ์ ํจ์ฑ ๊ฒ์ฌ ๋ชจ๋ ์์ฑ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๋ค์ํ ํํ์ ์ํ ๊ตฐ์ ๋ฌธ์ ๋์ ํ์ฑ ํ
์คํธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"CSV, ์์
๋ด๋ณด๋ด๊ธฐ(ํญ ๊ตฌ๋ถ), JSON, ๊ณต๋ฌธ ํ
์คํธ 4๊ฐ์ง ํ์์ ๊ฐ์ ๊ฒฐ์ฐ ์๋ฃ๋ฅผ ๋ฉ๋ชจ๋ฆฌ ์์์ ์๋ ํ์ฑํ๊ณ ๋จธ๋ฆฌ๊ธ ๋ณ์นญ ๋์ยท์ ํฉ์ฑ ๊ฒ์ฆยท์ ์ฒ๋ฆฌ๋ฅผ ๊ฑฐ์ณ ํ์ค ์์ฅ์ผ๋ก ํฉ์น๋ ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํ๋ค.\"\"\"\n",
"import csv\n",
"import io\n",
"import json\n",
"import re\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์๊ฐ ๊ฒฐ์ฐ ์์ฅ ์์ฑ๊ธฐ (์ค์ ๋ถ๋๋ช
ยทํ๋ชฉยท์๋ยท์ฌ๋ฌด ์ ๋ณด ์์)\n",
"# ๊ฐ์ ๋๋ 24๊ฐ x (๋ฌผ์ ๋ถ์ถ / ์๋ฆฌ๋ถ์ ์๋ชจ / ํ์ฝ ์ ์ฐ) ํ๋ชฉ ์์ฅ๊ณผ ์ฅ๋น ๊ฐ๋ ํํฉ์ ๋ง๋ ๋ค.\n",
"# ์ฌ๊ณ ์ฐ์: ๊ธฐ๋ง = ๊ธฐ์ด + ์๋ น + ๋ฐ๋ฉ - ๋ถ์ถ, ๋ค์ ๋ฌ ๊ธฐ์ด = ์ด๋ฒ ๋ฌ ๊ธฐ๋ง.\n",
"# ---------------------------------------------------------------------------\n",
"UNITS = [f\"๊ฐ์{i:02d}๋๋\" for i in range(1, 25)]\n",
"ITEMS = {\"๋ฌผ์๋ถ์ถ\": [\"๊ธ์๋ฅ\", \"ํผ๋ณต๋ฅ\", \"์ ๋ฅ\", \"์ ๋น์๋ชจํ\"],\n",
" \"์๋ฆฌ๋ถ์\": [\"ํํฐ๋ฅ\", \"๋ฒ ์ด๋ง๋ฅ\", \"์ ์ฅํ\", \"ํ์ด์ด๋ฅ\"],\n",
" \"ํ์ฝ์ ์ฐ\": [\"ํ๋ จํA\", \"ํ๋ จํB\", \"๋ชจ์ํ\"]}\n",
"UNIT_CODE = {\"๊ธ์๋ฅ\": \"BX\", \"ํผ๋ณต๋ฅ\": \"EA\", \"์ ๋ฅ\": \"L\", \"์ ๋น์๋ชจํ\": \"EA\", \"ํํฐ๋ฅ\": \"EA\", \"๋ฒ ์ด๋ง๋ฅ\": \"EA\",\n",
" \"์ ์ฅํ\": \"EA\", \"ํ์ด์ด๋ฅ\": \"EA\", \"ํ๋ จํA\": \"RD\", \"ํ๋ จํB\": \"RD\", \"๋ชจ์ํ\": \"RD\"}\n",
"LEDGER_COLS = [\"month\", \"unit\", \"cat\", \"item\", \"open\", \"recv\", \"issue\", \"ret\", \"close\"]\n",
"\n",
"\n",
"def make_ledger(n_months=2, seed=60):\n",
" \"\"\"n_months๊ฐ์ ์์ฅ(list of dict). ๋์งธ ๋ฌ ๊ธฐ์ด๋ ์ฒซ์งธ ๋ฌ ๊ธฐ๋ง๊ณผ ๊ฐ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows, prev = [], {}\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" for cat, items in ITEMS.items():\n",
" for it in items:\n",
" base = int(rng.integers(80, 400))\n",
" op = prev.get((u, it), base * 3)\n",
" recv = int(rng.integers(0, base))\n",
" issue = int(rng.integers(base // 4, base // 2 + 1))\n",
" ret = int(issue * rng.uniform(0, 0.15))\n",
" cl = op + recv + ret - issue\n",
" rows.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"cat\": cat, \"item\": it,\n",
" \"open\": op, \"recv\": recv, \"issue\": issue, \"ret\": ret, \"close\": cl})\n",
" prev[(u, it)] = cl\n",
" return rows\n",
"\n",
"\n",
"def make_equip(n_months=2, seed=61):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" hold = int(rng.integers(20, 60))\n",
" out.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"hold\": hold, \"operable\": int(hold * rng.uniform(0.78, 0.97))})\n",
" return out\n",
"\n",
"TRUTH = [r for r in make_ledger(2) if r[\"month\"] == \"2026-08\"]\n",
"FIELDS = [\"unit\", \"cat\", \"item\", \"open\", \"recv\", \"issue\", \"ret\", \"close\"]\n",
"ALIAS = {\"unit\": [\"unit\", \"๋ถ๋\", \"๋ถ๋๋ช
\", \"์ ์ถ๋ถ๋\"], \"cat\": [\"cat\", \"๊ตฌ๋ถ\", \"๋ถ๋ฅ\"], \"item\": [\"item\", \"ํ๋ชฉ\", \"ํ๋ชฉ๋ช
\"],\n",
" \"open\": [\"open\", \"๊ธฐ์ด\", \"๊ธฐ์ด์ฌ๊ณ \"], \"recv\": [\"recv\", \"์๋ น\", \"์
๊ณ \"], \"issue\": [\"issue\", \"๋ถ์ถ\", \"์๋ชจ\", \"์ฌ์ฉ\"],\n",
" \"ret\": [\"ret\", \"๋ฐ๋ฉ\"], \"close\": [\"close\", \"๊ธฐ๋ง\", \"๊ธฐ๋ง์ฌ๊ณ \"]}\n",
"H2F = {h: f for f, hs in ALIAS.items() for h in hs}\n",
"by_unit = lambda a, b: [r for r in TRUTH if UNITS[a] <= r[\"unit\"] <= UNITS[b]]\n",
"\n",
"# --- ๊ฐ์ ์๋ณธ 4์ข
(๋ฌธ์์ด) ์์ฑ: ํ์๋ง๋ค ์์์ด ๋ค๋ฅด๋ค ---\n",
"def mk_csv(rows):\n",
" # [TODO : ํ์ ์ค์ต 2] ๋ค์ํ ์์์ ์์
/CSV ๋ฐ์ดํฐ ์๋ ํ์ฑ, ์ ํฉ์ฑ ๊ฒ์ฆ ๋ฐ ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ ๊ตฌ์ถ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def mk_tab(rows): # ์์
์์ 'ํญ ๊ตฌ๋ถ ํ
์คํธ'๋ก ๋ด๋ณด๋ธ ํํ, ๋จธ๋ฆฌ๊ธ์ด ์์ ๊ฐ์ ์ผ๋ก ๋ฐ๋\n",
" # [TODO : ํ์ ์ค์ต 2] ๋ค์ํ ์์์ ์์
/CSV ๋ฐ์ดํฐ ์๋ ํ์ฑ, ์ ํฉ์ฑ ๊ฒ์ฆ ๋ฐ ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ ๊ตฌ์ถ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def mk_json(rows):\n",
" return json.dumps({\"form\": \"๊ฒฐ์ฐ์\", \"rows\": [{f: r[f] for f in FIELDS} for r in rows]}, ensure_ascii=False)\n",
"def mk_memo(rows): # ๊ณต๋ฌธ ๋ณธ๋ฌธ ํํ ๋น์ ํ ํ
์คํธ\n",
" return \"\\n\".join(f\"{i + 1}. {r['unit']} {r['cat']} {r['item']}: ๊ธฐ์ด {r['open']}, ์๋ น {r['recv']}, ๋ถ์ถ {r['issue']}, ๋ฐ๋ฉ {r['ret']}, ๊ธฐ๋ง {r['close']}\"\n",
" for i, r in enumerate(rows))\n",
"RAW = {\"csv\": mk_csv(by_unit(0, 2)), \"excel_tab\": mk_tab(by_unit(3, 5)), \"json\": mk_json(by_unit(6, 8)), \"memo\": mk_memo(by_unit(9, 11))}\n",
"RAW[\"csv\"] += \"\\n๊ฐ์99๋๋,๋ฌผ์๋ถ์ถ,๊ธ์๋ฅ,10,0,3,0,7\" # ์กด์ฌํ์ง ์๋ ๋ถ๋ ์ฝ๋(๋ณด๋ฅ ๋์)\n",
"RAW[\"memo\"] += \"\\n99. ๊ฐ์10๋๋ ํ์ฝ์ ์ฐ ํ๋ จํA: ๊ธฐ์ด 50, ์๋ น 10, ๋ถ์ถ abc, ๋ฐ๋ฉ 0, ๊ธฐ๋ง 60\" # ์ซ์ ์๋(๋ณด๋ฅ ๋์)\n",
"\n",
"# --- ํ์: ๋ชจ๋ ํ์ค ๋ ์ฝ๋(dict) ๋ฆฌ์คํธ๋ก ๋ณํ ---\n",
"def norm(row):\n",
" out = {}\n",
" for k, v in row.items():\n",
" f = H2F.get(str(k).strip())\n",
" if f:\n",
" out[f] = v.strip() if isinstance(v, str) else v\n",
" return out\n",
"def parse_csv(s, delim=\",\"):\n",
" return [norm(r) for r in csv.DictReader(io.StringIO(s), delimiter=delim)]\n",
"def parse_json(s):\n",
" return [norm(r) for r in json.loads(s)[\"rows\"]]\n",
"def parse_memo(s):\n",
" pat = re.compile(r\"\\d+\\.\\s*(\\S+)\\s+(\\S+)\\s+(\\S+):\\s*๊ธฐ์ด\\s*(\\S+),\\s*์๋ น\\s*(\\S+),\\s*๋ถ์ถ\\s*(\\S+),\\s*๋ฐ๋ฉ\\s*(\\S+),\\s*๊ธฐ๋ง\\s*(\\S+)\")\n",
" return [dict(zip(FIELDS, m.groups())) for m in map(pat.match, s.splitlines()) if m]\n",
"PARSERS = {\"csv\": parse_csv, \"excel_tab\": lambda s: parse_csv(s, \"\\t\"), \"json\": parse_json, \"memo\": parse_memo}\n",
"\n",
"# --- ์ ์ฒ๋ฆฌยท์ ํฉ์ฑ ๊ฒ์ฆ: ํ ๋ณํ, ์ฝ๋ ํ์ธ, ์ฐ์ ๋์กฐ. ์ค๋ฅ ๋ ์ฝ๋๋ ๋ฐ์ ๋ณด๋ฅ ---\n",
"known_items = {it for v in ITEMS.values() for it in v}\n",
"def clean(r, fmt):\n",
" try:\n",
" for f in (\"open\", \"recv\", \"issue\", \"ret\", \"close\"):\n",
" r[f] = int(r[f])\n",
" except (ValueError, KeyError):\n",
" return None, f\"[{fmt}] ์ซ์ ๋ณํ ์คํจ: {r.get('unit')} {r.get('item')}\"\n",
" if r[\"unit\"] not in UNITS:\n",
" return None, f\"[{fmt}] ์กด์ฌํ์ง ์๋ ๋ถ๋ ์ฝ๋ {r['unit']}\"\n",
" if r[\"item\"] not in known_items:\n",
" return None, f\"[{fmt}] ์ ์ ์๋ ํ๋ชฉ {r['item']}\"\n",
" if r[\"open\"] + r[\"recv\"] + r[\"ret\"] - r[\"issue\"] != r[\"close\"]:\n",
" return None, f\"[{fmt}] ์ฌ๊ณ ์ฐ์ ๋ถ์ผ์น {r['unit']} {r['item']}\"\n",
" return r, None\n",
"\n",
"good, hold, stat = [], [], {}\n",
"for fmt, text in RAW.items():\n",
" try:\n",
" recs = PARSERS[fmt](text)\n",
" stat[fmt] = {\"parse_ok\": True, \"n\": len(recs)}\n",
" except Exception as ex:\n",
" recs, stat[fmt] = [], {\"parse_ok\": False, \"n\": 0, \"err\": repr(ex)[:50]}\n",
" for r in recs:\n",
" c, why = clean(r, fmt)\n",
" (good if c else hold).append(c or why)\n",
"df = pd.DataFrame(good, columns=FIELDS).sort_values([\"unit\", \"item\"]).reset_index(drop=True)\n",
"\n",
"print(\"[ํ์๋ณ ํ์ฑ ๊ฒฐ๊ณผ] [๊ต์ก์ฉ๊ฐ์์๋ฃ]\")\n",
"for fmt, s in stat.items():\n",
" print(f\" - {fmt:<9} ํ์ฑ {'์ฑ๊ณต' if s['parse_ok'] else '์คํจ'}, ์ฝ์ ๋ ์ฝ๋ {s['n']}๊ฑด\")\n",
"rate = sum(s[\"parse_ok\"] for s in stat.values()) / len(stat)\n",
"print(f\"ํ์ฑ ์ฑ๊ณต๋ฅ {rate:.0%}({len(stat)}๊ฐ ํ์), ์ ์ ๋ฐ์ {len(df)}๊ฑด, ๋ฐ์ ๋ณด๋ฅ {len(hold)}๊ฑด\")\n",
"for w in hold:\n",
" print(\" ๋ณด๋ฅ:\", w)\n",
"truth = pd.DataFrame(by_unit(0, 11), columns=FIELDS).sort_values([\"unit\", \"item\"]).reset_index(drop=True)\n",
"same = df.equals(truth.astype(df.dtypes.to_dict()))\n",
"print(f\"์๋ณธ ๋๋น ๊ฐ ์ผ์น: {'์ ๋ถ ์ผ์น' if same else '๋ถ์ผ์น'} ({len(df)}ํ = 12๊ฐ ๋ถ๋ x 11ํ๋ชฉ = {12 * 11}ํ)\")\n",
"print(\"ํ์ค ์์ฅ ๋ฏธ๋ฆฌ๋ณด๊ธฐ(์ 4ํ):\")\n",
"print(df.head(4).to_string(index=False))\n",
"print(f\"\\n๋ชฉํ(4๊ฐ ํ์ ํ์ฑ ์ฑ๊ณต๋ฅ 100%, ์์ ๋ณ๊ฒฝ ๋์, ์ค๋ฅ ๋ ์ฝ๋ ๋ณด๋ฅ): {'๋ฌ์ฑ' if rate == 1 and same and len(hold) == 2 else '๋ฏธ๋ฌ'}\")\n",
"print(\"์ ์์ ๋์์ ALIAS ์ฌ์ ์ ๋จธ๋ฆฌ๊ธ ๋ณ์นญ๋ง ์ถ๊ฐํ๋ฉด ๋๋ค(์ฝ๋ ์์ ๋ถํ์).\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
02 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[ํ์๋ณ ํ์ฑ ๊ฒฐ๊ณผ] [๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
" - csv ํ์ฑ ์ฑ๊ณต, ์ฝ์ ๋ ์ฝ๋ 34๊ฑด\n",
" - excel_tab ํ์ฑ ์ฑ๊ณต, ์ฝ์ ๋ ์ฝ๋ 33๊ฑด\n",
" - json ํ์ฑ ์ฑ๊ณต, ์ฝ์ ๋ ์ฝ๋ 33๊ฑด\n",
" - memo ํ์ฑ ์ฑ๊ณต, ์ฝ์ ๋ ์ฝ๋ 34๊ฑด\n",
"ํ์ฑ ์ฑ๊ณต๋ฅ 100%(4๊ฐ ํ์), ์ ์ ๋ฐ์ 132๊ฑด, ๋ฐ์ ๋ณด๋ฅ 2๊ฑด\n",
" ๋ณด๋ฅ: [csv] ์กด์ฌํ์ง ์๋ ๋ถ๋ ์ฝ๋ ๊ฐ์99๋๋\n",
" ๋ณด๋ฅ: [memo] ์ซ์ ๋ณํ ์คํจ: ๊ฐ์10๋๋ ํ๋ จํA\n",
"์๋ณธ ๋๋น ๊ฐ ์ผ์น: ์ ๋ถ ์ผ์น (132ํ = 12๊ฐ ๋ถ๋ x 11ํ๋ชฉ = 132ํ)\n",
"ํ์ค ์์ฅ ๋ฏธ๋ฆฌ๋ณด๊ธฐ(์ 4ํ):\n",
" unit cat item open recv issue ret close\n",
"๊ฐ์01๋๋ ๋ฌผ์๋ถ์ถ ๊ธ์๋ฅ 402 51 39 3 417\n",
"๊ฐ์01๋๋ ํ์ฝ์ ์ฐ ๋ชจ์ํ 943 147 117 11 984\n",
"๊ฐ์01๋๋ ์๋ฆฌ๋ถ์ ๋ฒ ์ด๋ง๋ฅ 261 34 142 1 154\n",
"๊ฐ์01๋๋ ๋ฌผ์๋ถ์ถ ์ ๋ฅ 1279 257 108 5 1433\n",
"\n",
"๋ชฉํ(4๊ฐ ํ์ ํ์ฑ ์ฑ๊ณต๋ฅ 100%, ์์ ๋ณ๊ฒฝ ๋์, ์ค๋ฅ ๋ ์ฝ๋ ๋ณด๋ฅ): ๋ฌ์ฑ\n",
"์ ์์ ๋์์ ALIAS ์ฌ์ ์ ๋จธ๋ฆฌ๊ธ ๋ณ์นญ๋ง ์ถ๊ฐํ๋ฉด ๋๋ค(์ฝ๋ ์์ ๋ถํ์).\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
03. ํ์ค ์ ์ฐ ๊ท์น ๊ธฐ๋ฐ 1์ฐจ ์๊ฐ ๊ฒฐ์ฐ ์ง๊ณ ๋ฐ ํต๊ณ ์ฐ์ถ Baseline ์์ง ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"ํ์ค ์ ์ฐ ๊ท์น ๊ธฐ๋ฐ 1์ฐจ ์๊ฐ ๊ฒฐ์ฐ ์ง๊ณ Baseline ์์ง์ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **ํ์ค ์ ์ฐ ๊ท์น์ ์ค์ ๋ฌธ์์ด ๋ฃฐ 15์ข
์ผ๋ก ๋ถ๋ฆฌํ๊ณ , ์ด๋ฅผ ์ ์ฉํ 1์ฐจ ์๊ฐ ๊ฒฐ์ฐ ์ง๊ณ Baseline ์์ง์ ๊ตฌํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๋ฃฐ๋ง๋ค ๊ฐ์ ์กฐํญ ๋ฒํธ, ์กฐ๊ฑด์, ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ค์ ์ผ๋ก ๋์ด ์ฝ๋ ์์ ์์ด ๋ฐ๊ฟ ์ ์๊ฒ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/engine/dat06_rule_engine.py` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ตฐ ๊ท์ ๋ฐ ์ง์นจ ์กฐํญ๊ณผ์ ์ ํํ ์ผ์น์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์
๋ฌด ๊ฒ์ฆ ๋ฃฐ 15์ข
์ด์ ์ฝ๋ฉ, ๋ฃฐ ์ฒ๋ฆฌ ์ง์ฐ์๊ฐ 50ms ์ดํ\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ๋ฃฐ ์์ง ๋จ์ ํ
์คํธ 100% ํต๊ณผ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๊ฒฝ๊ณ ์กฐ๊ฑด(Boundary condition)์ ๋ํ ๋ฃฐ ํ์ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์ค์ ๋ฌธ์์ด๋ก ๋ถ๋ฆฌํ ๊ฒ์ฆ ๋ฃฐ 15์ข
(๊ฐ์ ์ ์ฐ ๊ท์ ์กฐํญ ๋์)์ผ๋ก ์๊ฐ ์์ฅ์ ๊ฒ์ฌํ๊ณ , ํ์ค ์ ์ฐ ๊ท์น์ ๋ฐ๋ฅธ 1์ฐจ ๊ฒฐ์ฐ ์ง๊ณ Baseline์ ๊ณ์ฐํ๋ฉฐ ์ฒ๋ฆฌ ์ง์ฐ์๊ฐ๊ณผ ๊ฒฝ๊ณ ์กฐ๊ฑด ๋จ์ ํ
์คํธ๋ฅผ ํ์ธํ๋ค.\"\"\"\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์๊ฐ ๊ฒฐ์ฐ ์์ฅ ์์ฑ๊ธฐ (์ค์ ๋ถ๋๋ช
ยทํ๋ชฉยท์๋ยท์ฌ๋ฌด ์ ๋ณด ์์)\n",
"# ๊ฐ์ ๋๋ 24๊ฐ x (๋ฌผ์ ๋ถ์ถ / ์๋ฆฌ๋ถ์ ์๋ชจ / ํ์ฝ ์ ์ฐ) ํ๋ชฉ ์์ฅ๊ณผ ์ฅ๋น ๊ฐ๋ ํํฉ์ ๋ง๋ ๋ค.\n",
"# ์ฌ๊ณ ์ฐ์: ๊ธฐ๋ง = ๊ธฐ์ด + ์๋ น + ๋ฐ๋ฉ - ๋ถ์ถ, ๋ค์ ๋ฌ ๊ธฐ์ด = ์ด๋ฒ ๋ฌ ๊ธฐ๋ง.\n",
"# ---------------------------------------------------------------------------\n",
"UNITS = [f\"๊ฐ์{i:02d}๋๋\" for i in range(1, 25)]\n",
"ITEMS = {\"๋ฌผ์๋ถ์ถ\": [\"๊ธ์๋ฅ\", \"ํผ๋ณต๋ฅ\", \"์ ๋ฅ\", \"์ ๋น์๋ชจํ\"],\n",
" \"์๋ฆฌ๋ถ์\": [\"ํํฐ๋ฅ\", \"๋ฒ ์ด๋ง๋ฅ\", \"์ ์ฅํ\", \"ํ์ด์ด๋ฅ\"],\n",
" \"ํ์ฝ์ ์ฐ\": [\"ํ๋ จํA\", \"ํ๋ จํB\", \"๋ชจ์ํ\"]}\n",
"UNIT_CODE = {\"๊ธ์๋ฅ\": \"BX\", \"ํผ๋ณต๋ฅ\": \"EA\", \"์ ๋ฅ\": \"L\", \"์ ๋น์๋ชจํ\": \"EA\", \"ํํฐ๋ฅ\": \"EA\", \"๋ฒ ์ด๋ง๋ฅ\": \"EA\",\n",
" \"์ ์ฅํ\": \"EA\", \"ํ์ด์ด๋ฅ\": \"EA\", \"ํ๋ จํA\": \"RD\", \"ํ๋ จํB\": \"RD\", \"๋ชจ์ํ\": \"RD\"}\n",
"LEDGER_COLS = [\"month\", \"unit\", \"cat\", \"item\", \"open\", \"recv\", \"issue\", \"ret\", \"close\"]\n",
"\n",
"\n",
"def make_ledger(n_months=2, seed=60):\n",
" \"\"\"n_months๊ฐ์ ์์ฅ(list of dict). ๋์งธ ๋ฌ ๊ธฐ์ด๋ ์ฒซ์งธ ๋ฌ ๊ธฐ๋ง๊ณผ ๊ฐ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows, prev = [], {}\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" for cat, items in ITEMS.items():\n",
" for it in items:\n",
" base = int(rng.integers(80, 400))\n",
" op = prev.get((u, it), base * 3)\n",
" recv = int(rng.integers(0, base))\n",
" issue = int(rng.integers(base // 4, base // 2 + 1))\n",
" ret = int(issue * rng.uniform(0, 0.15))\n",
" cl = op + recv + ret - issue\n",
" rows.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"cat\": cat, \"item\": it,\n",
" \"open\": op, \"recv\": recv, \"issue\": issue, \"ret\": ret, \"close\": cl})\n",
" prev[(u, it)] = cl\n",
" return rows\n",
"\n",
"\n",
"def make_equip(n_months=2, seed=61):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" hold = int(rng.integers(20, 60))\n",
" out.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"hold\": hold, \"operable\": int(hold * rng.uniform(0.78, 0.97))})\n",
" return out\n",
"\n",
"# 1) ๊ฒ์ฆ ๋ฃฐ ์ค์ : (๋ฃฐID, ๊ฐ์ ์กฐํญ, ์กฐ๊ฑด์(์ฐธ์ด์ด์ผ ์ ์), ์ค๋ฅ ๋ฉ์์ง) - ์ฝ๋ ์์ ์์ด ๋ฌธ์์ด๋ง ๋ฐ๊ฟ ๋ฃฐ ๋ณ๊ฒฝ\n",
"RULES = [\n",
" (\"V01\", \"๊ฐ์๊ท์ ์ 1์กฐ\", \"unit in UNITS\", \"์กด์ฌํ์ง ์๋ ๋ถ๋\"),\n",
" (\"V02\", \"๊ฐ์๊ท์ ์ 2์กฐ\", \"item in UNIT_CODE\", \"์ ์ ์๋ ํ๋ชฉ\"),\n",
" (\"V03\", \"๊ฐ์๊ท์ ์ 3์กฐ\", \"cat in ITEMS and item in ITEMS[cat]\", \"๊ตฌ๋ถ๊ณผ ํ๋ชฉ ๋ถ์ผ์น\"),\n",
" (\"V04\", \"๊ฐ์๊ท์ ์ 4์กฐ\", \"open >= 0\", \"๊ธฐ์ด ์ฌ๊ณ ์์\"),\n",
" (\"V05\", \"๊ฐ์๊ท์ ์ 4์กฐ\", \"recv >= 0\", \"์๋ น๋ ์์\"),\n",
" (\"V06\", \"๊ฐ์๊ท์ ์ 4์กฐ\", \"issue >= 0\", \"๋ถ์ถ๋ ์์\"),\n",
" (\"V07\", \"๊ฐ์๊ท์ ์ 4์กฐ\", \"ret >= 0\", \"๋ฐ๋ฉ๋ ์์\"),\n",
" (\"V08\", \"๊ฐ์๊ท์ ์ 5์กฐ\", \"close == open + recv + ret - issue\", \"๊ธฐ๋ง ์ฐ์ ๋ถ์ผ์น\"),\n",
" (\"V09\", \"๊ฐ์๊ท์ ์ 6์กฐ\", \"issue <= open + recv\", \"๊ฐ์ฉ๋ ์ด๊ณผ ๋ถ์ถ\"),\n",
" (\"V10\", \"๊ฐ์๊ท์ ์ 6์กฐ\", \"ret <= issue\", \"๋ถ์ถ๋ณด๋ค ํฐ ๋ฐ๋ฉ\"),\n",
" (\"V11\", \"๊ฐ์๊ท์ ์ 7์กฐ\", \"close >= 0\", \"๊ธฐ๋ง ์ฌ๊ณ ์์\"),\n",
" (\"V12\", \"๊ฐ์๊ท์ ์ 8์กฐ\", \"len(month) == 7 and month[4] == '-'\", \"๊ฒฐ์ฐ์ ํ์ ์ค๋ฅ\"),\n",
" (\"V13\", \"๊ฐ์๊ท์ ์ 9์กฐ\", \"cat != 'ํ์ฝ์ ์ฐ' or issue <= 5000\", \"ํ์ฝ ์ ์ฌ์ฉ๋ ํ๋ ์ด๊ณผ\"),\n",
" (\"V14\", \"๊ฐ์๊ท์ ์ 10์กฐ\", \"isinstance(open, int) and isinstance(close, int)\", \"์๋ ์๋ฃํ ์ค๋ฅ\"),\n",
" (\"V15\", \"๊ฐ์๊ท์ ์ 11์กฐ\", \"issue <= max(open + recv, 1) * 0.9 or issue == 0\", \"๋ถ์ถ ๋น์จ ๊ณผ๋ค(90% ์ด๊ณผ)\"),\n",
"]\n",
"COMPILED = [(rid, art, compile(expr, rid, \"eval\"), msg) for rid, art, expr, msg in RULES]\n",
"CTX = {\"UNITS\": UNITS, \"UNIT_CODE\": UNIT_CODE, \"ITEMS\": ITEMS}\n",
"\n",
"\n",
"def check_row(row):\n",
" # [TODO : ํ์ ์ค์ต 3] ํ์ค ์ ์ฐ ๊ท์น ๊ธฐ๋ฐ 1์ฐจ ์๊ฐ ๊ฒฐ์ฐ ์ง๊ณ ๋ฐ ํต๊ณ ์ฐ์ถ Baseline ์์ง ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"rows = make_ledger(2)\n",
"rng = np.random.default_rng(3)\n",
"for i in rng.choice(len(rows), 6, replace=False): # ์ค๋ฅ 6๊ฑด ์ฃผ์
(๋ฃฐ์ด ์ก๋์ง ํ์ธ)\n",
" k = int(rng.integers(0, 3))\n",
" rows[i] = {**rows[i], **[{\"close\": rows[i][\"close\"] + 7}, {\"issue\": rows[i][\"open\"] + rows[i][\"recv\"] + 50}, {\"ret\": -3}][k]}\n",
"lat, found = [], []\n",
"for r in rows:\n",
" t0 = time.perf_counter()\n",
" res = check_row(r)\n",
" lat.append((time.perf_counter() - t0) * 1000)\n",
" found += [(r[\"month\"], r[\"unit\"], r[\"item\"], *x) for x in res]\n",
"lat = np.array(lat)\n",
"print(f\"[๊ฒ์ฆ ๋ฃฐ {len(RULES)}์ข
์ ์ฉ] ๋์ {len(rows)}ํ, ์๋ฐ {len(found)}๊ฑด [๊ต์ก์ฉ๊ฐ์์๋ฃ]\")\n",
"cnt = pd.Series([f[3] for f in found]).value_counts()\n",
"print(\"๋ฃฐ๋ณ ์๋ฐ ๊ฑด์:\", {k: int(v) for k, v in cnt.items()})\n",
"print(\"์๋ฐ ์์(3๊ฑด):\")\n",
"for f in found[:3]:\n",
" print(\" \", f)\n",
"print(f\"๋ฃฐ ์ฒ๋ฆฌ ์ง์ฐ: ํ๊ท {lat.mean():.3f}ms, ์ต๋ {lat.max():.3f}ms (๋ชฉํ 50ms ์ดํ: {'๋ฌ์ฑ' if lat.max() <= 50 else '๋ฏธ๋ฌ'}, ํ๊ฒฝ์ ๋ฐ๋ผ ๋ค๋ฆ)\")\n",
"\n",
"# 3) 1์ฐจ ๊ฒฐ์ฐ ์ง๊ณ Baseline: ๋ถ๋ x ๊ตฌ๋ถ ํฉ๊ณ, ๊ตฌ๋ถ๋ณ ํต๊ณ\n",
"clean = pd.DataFrame([r for r in make_ledger(2) if r[\"month\"] == \"2026-08\"])\n",
"pv = clean.pivot_table(index=\"unit\", columns=\"cat\", values=\"issue\", aggfunc=\"sum\")\n",
"print(\"\\n[1์ฐจ ๊ฒฐ์ฐ ์ง๊ณ - 8์ ๋ถ์ถยท์๋ชจ๋ ๋ถ๋๋ณ(์์ 4๊ฐ)]\")\n",
"print(pv.head(4).to_string())\n",
"st = pv.agg([\"sum\", \"mean\", \"std\"]).round(1)\n",
"print(\"[๊ตฌ๋ถ๋ณ ํฉ๊ณยทํ๊ท ยทํ์คํธ์ฐจ]\")\n",
"print(st.to_string())\n",
"print(f\"์ ์ฒด ๊ธฐ๋ง ์ฌ๊ณ ํฉ๊ณ {int(clean['close'].sum())}, ๋ณด๊ณ ๋์ ๋ถ๋ {clean['unit'].nunique()}๊ฐ\")\n",
"\n",
"# 4) ๊ฒฝ๊ณ ์กฐ๊ฑด ๋จ์ ํ
์คํธ\n",
"base = {\"month\": \"2026-08\", \"unit\": UNITS[0], \"cat\": \"๋ฌผ์๋ถ์ถ\", \"item\": \"์ ๋ฅ\", \"open\": 100, \"recv\": 0, \"issue\": 0, \"ret\": 0, \"close\": 100}\n",
"tests = [(\"์ ์ ํ\", base, []),\n",
" (\"๋ถ์ถ = ๊ฐ์ฉ๋ ์ ํํ ์ผ์น(V15๋ง ์๋ฐ)\", {**base, \"issue\": 100, \"close\": 0}, [\"V15\"]),\n",
" (\"๋ถ์ถ = ๊ฐ์ฉ๋+1 (V09, V11, V15)\", {**base, \"issue\": 101, \"close\": -1}, [\"V09\", \"V11\", \"V15\"]),\n",
" (\"๊ธฐ๋ง 0 ํ์ฉ\", {**base, \"issue\": 90, \"close\": 10, \"ret\": 0}, []),\n",
" (\"๋ฐ๋ฉ = ๋ถ์ถ ๊ฒฝ๊ณ ํ์ฉ\", {**base, \"issue\": 50, \"ret\": 50, \"close\": 100}, []),\n",
" (\"ํ์ฝ ํ๋ 5000 ๊ฒฝ๊ณ\", {**base, \"cat\": \"ํ์ฝ์ ์ฐ\", \"item\": \"ํ๋ จํA\", \"open\": 9000, \"issue\": 5000, \"close\": 4000}, []),\n",
" (\"ํ์ฝ ํ๋ 5001 ์ด๊ณผ\", {**base, \"cat\": \"ํ์ฝ์ ์ฐ\", \"item\": \"ํ๋ จํA\", \"open\": 9000, \"issue\": 5001, \"close\": 3999}, [\"V13\"]),\n",
" (\"๊ฒฐ์ฐ์ ํ์ ์ค๋ฅ\", {**base, \"month\": \"202608\"}, [\"V12\"]),\n",
" (\"์๋ฃํ ์ค๋ฅ(๋ฌธ์์ด ์๋)\", {**base, \"open\": \"100\"}, [\"V14\"])] # ์ด ๊ฒฝ์ฐ V14 ํฌํจ ์ฌ๋ถ๋ง ํ์ธ\n",
"ok = 0\n",
"print(\"\\n[๊ฒฝ๊ณ ์กฐ๊ฑด ๋จ์ ํ
์คํธ]\")\n",
"for name, r, exp in tests:\n",
" got = sorted(x[0] for x in check_row(r))\n",
" p = set(exp) <= set(got) if name.startswith(\"์๋ฃํ\") else got == exp\n",
" ok += p\n",
" print(f\" - {name}: ๊ธฐ๋ {exp} / ๊ฒฐ๊ณผ {got} -> {'PASS' if p else 'FAIL'}\")\n",
"print(f\"๋จ์ ํ
์คํธ {ok}/{len(tests)} ํต๊ณผ({ok / len(tests):.0%})\")\n",
"print(f\"๋ชฉํ(๋ฃฐ 15์ข
์ด์, ์ง์ฐ 50ms ์ดํ, ๋จ์ ํ
์คํธ 100%): {'๋ฌ์ฑ' if len(RULES) >= 15 and lat.max() <= 50 and ok == len(tests) else '๋ฏธ๋ฌ'}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
03 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ฒ์ฆ ๋ฃฐ 15์ข
์ ์ฉ] ๋์ 528ํ, ์๋ฐ 13๊ฑด [๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"๋ฃฐ๋ณ ์๋ฐ ๊ฑด์: {'V08': 6, 'V09': 3, 'V15': 3, 'V07': 1}\n",
"์๋ฐ ์์(3๊ฑด):\n",
" ('2026-07', '๊ฐ์05๋๋', '๊ธ์๋ฅ', 'V08', '๊ฐ์๊ท์ ์ 5์กฐ', '๊ธฐ๋ง ์ฐ์ ๋ถ์ผ์น')\n",
" ('2026-07', '๊ฐ์05๋๋', '๊ธ์๋ฅ', 'V09', '๊ฐ์๊ท์ ์ 6์กฐ', '๊ฐ์ฉ๋ ์ด๊ณผ ๋ถ์ถ')\n",
" ('2026-07', '๊ฐ์05๋๋', '๊ธ์๋ฅ', 'V15', '๊ฐ์๊ท์ ์ 11์กฐ', '๋ถ์ถ ๋น์จ ๊ณผ๋ค(90% ์ด๊ณผ)')\n",
"๋ฃฐ ์ฒ๋ฆฌ ์ง์ฐ: ํ๊ท 0.008ms, ์ต๋ 0.072ms (๋ชฉํ 50ms ์ดํ: ๋ฌ์ฑ, ํ๊ฒฝ์ ๋ฐ๋ผ ๋ค๋ฆ)\n",
"\n",
"[1์ฐจ ๊ฒฐ์ฐ ์ง๊ณ - 8์ ๋ถ์ถยท์๋ชจ๋ ๋ถ๋๋ณ(์์ 4๊ฐ)]\n",
"cat ๋ฌผ์๋ถ์ถ ์๋ฆฌ๋ถ์ ํ์ฝ์ ์ฐ\n",
"unit \n",
"๊ฐ์01๋๋ 378 385 384\n",
"๊ฐ์02๋๋ 255 427 234\n",
"๊ฐ์03๋๋ 239 371 168\n",
"๊ฐ์04๋๋ 305 258 319\n",
"[๊ตฌ๋ถ๋ณ ํฉ๊ณยทํ๊ท ยทํ์คํธ์ฐจ]\n",
"cat ๋ฌผ์๋ถ์ถ ์๋ฆฌ๋ถ์ ํ์ฝ์ ์ฐ\n",
"sum 8270.0 8184.0 6743.0\n",
"mean 344.6 341.0 281.0\n",
"std 79.3 72.2 75.9\n",
"์ ์ฒด ๊ธฐ๋ง ์ฌ๊ณ ํฉ๊ณ 199291, ๋ณด๊ณ ๋์ ๋ถ๋ 24๊ฐ\n",
"\n",
"[๊ฒฝ๊ณ ์กฐ๊ฑด ๋จ์ ํ
์คํธ]\n",
" - ์ ์ ํ: ๊ธฐ๋ [] / ๊ฒฐ๊ณผ [] -> PASS\n",
" - ๋ถ์ถ = ๊ฐ์ฉ๋ ์ ํํ ์ผ์น(V15๋ง ์๋ฐ): ๊ธฐ๋ ['V15'] / ๊ฒฐ๊ณผ ['V15'] -> PASS\n",
" - ๋ถ์ถ = ๊ฐ์ฉ๋+1 (V09, V11, V15): ๊ธฐ๋ ['V09', 'V11', 'V15'] / ๊ฒฐ๊ณผ ['V09', 'V11', 'V15'] -> PASS\n",
" - ๊ธฐ๋ง 0 ํ์ฉ: ๊ธฐ๋ [] / ๊ฒฐ๊ณผ [] -> PASS\n",
" - ๋ฐ๋ฉ = ๋ถ์ถ ๊ฒฝ๊ณ ํ์ฉ: ๊ธฐ๋ [] / ๊ฒฐ๊ณผ [] -> PASS\n",
" - ํ์ฝ ํ๋ 5000 ๊ฒฝ๊ณ: ๊ธฐ๋ [] / ๊ฒฐ๊ณผ [] -> PASS\n",
" - ํ์ฝ ํ๋ 5001 ์ด๊ณผ: ๊ธฐ๋ ['V13'] / ๊ฒฐ๊ณผ ['V13'] -> PASS\n",
" - ๊ฒฐ์ฐ์ ํ์ ์ค๋ฅ: ๊ธฐ๋ ['V12'] / ๊ฒฐ๊ณผ ['V12'] -> PASS\n",
" - ์๋ฃํ ์ค๋ฅ(๋ฌธ์์ด ์๋): ๊ธฐ๋ ['V14'] / ๊ฒฐ๊ณผ ['V04', 'V08', 'V09', 'V14', 'V15'] -> PASS\n",
"๋จ์ ํ
์คํธ 9/9 ํต๊ณผ(100%)\n",
"๋ชฉํ(๋ฃฐ 15์ข
์ด์, ์ง์ฐ 50ms ์ดํ, ๋จ์ ํ
์คํธ 100%): ๋ฌ์ฑ\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
04. ๋ฐ์ดํฐ ์ด์์น(์ ์ ๋๋น ๊ธ์ฆ ํญ๋ชฉ) ์๋ ๊ฐ์ง ๋ฐ ์ด์ ์์ธ ์์ฝ ์ถ์ฒ ๋ชจ๋ ๊ณ ๋ํ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์ ์ ๋๋น ๊ธ์ฆ ํญ๋ชฉ ์ด์์น ๊ฐ์ง ๋ฐ ์ด์ ์์ธ ์์ฝ ์ถ์ฒ ๋ชจ๋์ ๊ณ ๋ํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ ์ ๋๋น ๊ธ์ฆํ ํ๋ชฉ์ ์๋ ๊ฐ์งํ๊ณ ์ด์ ์์ธ์ ์์ฝํด ์ถ์ฒํ๋ ๋ชจ๋์ ๊ณ ๋ํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **ํ๋ชฉ๋ณ ์ฆ๊ฐ์จ์ ๋ก๊ทธ ๊ฐ์ ์ค์๊ฐ๊ณผ MAD ๊ธฐ๋ฐ ์ ์๋ฅผ ์ ์ฉํด ๊ธ์ฆ ํ๋ชฉ์ ์ฐพ๋๋ค ๊ณผ์ ์ ์งํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/ai/dat06_intelligence_module.py` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ถ์ฒ ์ฌ์ ์ ํฉ๋ฆฌ์ฑ ๋ฐ ์ค๋ฌด ์์ฉ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์ถ์ฒ/๋ถ๋ฅ ์ ํ๋ 85% ์ด์ ๋ฌ์ฑ\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: AI ์ถ๋ก ๋ชจ๋ ๊ตฌํ ๋ฐ ๋ฒค์น๋งํฌ ํ
์คํธ ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๊ณผ๊ฑฐ ์น์ธ/์ฒ๋ฆฌ ์๋ฃ ๋ฐ์ดํฐ์์ ์ถ์ฒ ์ผ์น๋ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์ ์ ๋๋น ๊ธ์ฆํ ํ๋ชฉ์ ๋ก๋ฒ์คํธ ํต๊ณ๋ก ์๋ ๊ฐ์งํ๊ณ , ํ๋ จยท์ ๋น ์ผ์ ๋ฑ ๋งฅ๋ฝ ์ ๋ณด์ ๋์กฐํด ์ด์ ์์ธ์ ์์ฝยท์ถ์ฒํ๋ ๋ชจ๋์ ๊ตฌํํด ๊ฐ์ง ์ ํ๋์ ์์ธ ๋ถ๋ฅ ์ ํ๋๋ฅผ ์ธก์ ํ๋ค.\"\"\"\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์๊ฐ ๊ฒฐ์ฐ ์์ฅ ์์ฑ๊ธฐ (์ค์ ๋ถ๋๋ช
ยทํ๋ชฉยท์๋ยท์ฌ๋ฌด ์ ๋ณด ์์)\n",
"# ๊ฐ์ ๋๋ 24๊ฐ x (๋ฌผ์ ๋ถ์ถ / ์๋ฆฌ๋ถ์ ์๋ชจ / ํ์ฝ ์ ์ฐ) ํ๋ชฉ ์์ฅ๊ณผ ์ฅ๋น ๊ฐ๋ ํํฉ์ ๋ง๋ ๋ค.\n",
"# ์ฌ๊ณ ์ฐ์: ๊ธฐ๋ง = ๊ธฐ์ด + ์๋ น + ๋ฐ๋ฉ - ๋ถ์ถ, ๋ค์ ๋ฌ ๊ธฐ์ด = ์ด๋ฒ ๋ฌ ๊ธฐ๋ง.\n",
"# ---------------------------------------------------------------------------\n",
"UNITS = [f\"๊ฐ์{i:02d}๋๋\" for i in range(1, 25)]\n",
"ITEMS = {\"๋ฌผ์๋ถ์ถ\": [\"๊ธ์๋ฅ\", \"ํผ๋ณต๋ฅ\", \"์ ๋ฅ\", \"์ ๋น์๋ชจํ\"],\n",
" \"์๋ฆฌ๋ถ์\": [\"ํํฐ๋ฅ\", \"๋ฒ ์ด๋ง๋ฅ\", \"์ ์ฅํ\", \"ํ์ด์ด๋ฅ\"],\n",
" \"ํ์ฝ์ ์ฐ\": [\"ํ๋ จํA\", \"ํ๋ จํB\", \"๋ชจ์ํ\"]}\n",
"UNIT_CODE = {\"๊ธ์๋ฅ\": \"BX\", \"ํผ๋ณต๋ฅ\": \"EA\", \"์ ๋ฅ\": \"L\", \"์ ๋น์๋ชจํ\": \"EA\", \"ํํฐ๋ฅ\": \"EA\", \"๋ฒ ์ด๋ง๋ฅ\": \"EA\",\n",
" \"์ ์ฅํ\": \"EA\", \"ํ์ด์ด๋ฅ\": \"EA\", \"ํ๋ จํA\": \"RD\", \"ํ๋ จํB\": \"RD\", \"๋ชจ์ํ\": \"RD\"}\n",
"LEDGER_COLS = [\"month\", \"unit\", \"cat\", \"item\", \"open\", \"recv\", \"issue\", \"ret\", \"close\"]\n",
"\n",
"\n",
"def make_ledger(n_months=2, seed=60):\n",
" \"\"\"n_months๊ฐ์ ์์ฅ(list of dict). ๋์งธ ๋ฌ ๊ธฐ์ด๋ ์ฒซ์งธ ๋ฌ ๊ธฐ๋ง๊ณผ ๊ฐ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows, prev = [], {}\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" for cat, items in ITEMS.items():\n",
" for it in items:\n",
" base = int(rng.integers(80, 400))\n",
" op = prev.get((u, it), base * 3)\n",
" recv = int(rng.integers(0, base))\n",
" issue = int(rng.integers(base // 4, base // 2 + 1))\n",
" ret = int(issue * rng.uniform(0, 0.15))\n",
" cl = op + recv + ret - issue\n",
" rows.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"cat\": cat, \"item\": it,\n",
" \"open\": op, \"recv\": recv, \"issue\": issue, \"ret\": ret, \"close\": cl})\n",
" prev[(u, it)] = cl\n",
" return rows\n",
"\n",
"\n",
"def make_equip(n_months=2, seed=61):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" hold = int(rng.integers(20, 60))\n",
" out.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"hold\": hold, \"operable\": int(hold * rng.uniform(0.78, 0.97))})\n",
" return out\n",
"\n",
"# 1) ๊ฐ์ 2๊ฐ์ ๋ฐ์ดํฐ + ์ ๋ต(์จ์ ์์ธ) ์์ฑ\n",
"led = pd.DataFrame(make_ledger(2))\n",
"p7 = led[led[\"month\"] == \"2026-07\"].set_index([\"unit\", \"item\"])\n",
"p8 = led[led[\"month\"] == \"2026-08\"].set_index([\"unit\", \"item\"])\n",
"d = pd.DataFrame({\"cat\": p8[\"cat\"], \"prev\": p7[\"issue\"].clip(lower=1), \"cur\": p8[\"issue\"]})\n",
"rng = np.random.default_rng(4)\n",
"n = len(d)\n",
"d[\"train_days\"] = rng.integers(0, 5, n) # ํด๋น ๋ถ๋ ์ ํ๋ จ ์ผ์(๋งฅ๋ฝ)\n",
"d[\"repair_orders\"] = rng.integers(0, 4, n) # ์ ๋น ์ง์์ ๊ฑด์(๋งฅ๋ฝ)\n",
"d[\"special\"] = 0 # ์ผํ์ฑ ํน๋ณ ์ง๊ธ ์น์ธ ์ฌ๋ถ\n",
"d[\"truth\"] = \"์ ์\"\n",
"d[\"cur\"] = (d[\"prev\"] * rng.uniform(0.7, 1.4, n)).round().astype(int) # ์ ์ ๋ณ๋(-30%~+40%)\n",
"CAUSES = [\"ํ๋ จ ์ฆ๊ฐ\", \"์ ๋น ์ง์ค\", \"์
๋ ฅ ์๋ฆฟ์ ์ค๋ฅ\", \"์ผํ์ฑ ํน๋ณ ์ง๊ธ\", \"์์ธ ๋ถ๋ช
\"]\n",
"idx = rng.choice(n, 36, replace=False)\n",
"for k, i in enumerate(idx):\n",
" c = CAUSES[k % 5]\n",
" d.iloc[i, d.columns.get_loc(\"truth\")] = c\n",
" f = {\"ํ๋ จ ์ฆ๊ฐ\": rng.uniform(1.8, 3.0), \"์ ๋น ์ง์ค\": rng.uniform(1.8, 3.2), \"์
๋ ฅ ์๋ฆฟ์ ์ค๋ฅ\": 10.0,\n",
" \"์ผํ์ฑ ํน๋ณ ์ง๊ธ\": rng.uniform(2.0, 4.0), \"์์ธ ๋ถ๋ช
\": rng.uniform(1.9, 3.0)}[c]\n",
" d.iloc[i, d.columns.get_loc(\"cur\")] = int(d.iloc[i][\"prev\"] * f)\n",
" if c == \"ํ๋ จ ์ฆ๊ฐ\":\n",
" d.iloc[i, d.columns.get_loc(\"train_days\")] = rng.integers(8, 14)\n",
" if c == \"์ ๋น ์ง์ค\":\n",
" d.iloc[i, d.columns.get_loc(\"repair_orders\")] = rng.integers(7, 12)\n",
" if c == \"์ผํ์ฑ ํน๋ณ ์ง๊ธ\":\n",
" d.iloc[i, d.columns.get_loc(\"special\")] = 1\n",
"for i in rng.choice(n, 8, replace=False): # ์ ์์ด์ง๋ง ํ๋์ด ๋ง์๋ ๊ณณ(์คํ ์ ๋)\n",
" d.iloc[i, d.columns.get_loc(\"train_days\")] = rng.integers(8, 14)\n",
"\n",
"# 2) ๊ฐ์ง: ํ๋ชฉ๋ณ ๋ก๊ทธ ์ฆ๊ฐ์จ์ ์ค์๊ฐยทMAD ๊ธฐ๋ฐ z ์ ์\n",
"d[\"ratio\"] = d[\"cur\"] / d[\"prev\"]\n",
"lr = np.log(d[\"ratio\"])\n",
"med = lr.groupby(level=\"item\").transform(\"median\")\n",
"mad = (lr - med).abs().groupby(level=\"item\").transform(\"median\").clip(lower=0.05) * 1.4826\n",
"d[\"z\"] = (lr - med) / mad\n",
"d[\"flag\"] = (d[\"z\"] > 2.0) & (d[\"ratio\"] >= 1.6)\n",
"\n",
"# 3) ์์ธ ์์ฝ ์ถ์ฒ(๊ท์น ๊ธฐ๋ฐ, ์ฌ๋์ด ํ์ธํ ํ๋ณด๋ฅผ ์ ์)\n",
"def recommend(r):\n",
" # [TODO : ํ์ ์ค์ต 4] ๋ฐ์ดํฐ ์ด์์น(์ ์ ๋๋น ๊ธ์ฆ ํญ๋ชฉ) ์๋ ๊ฐ์ง ๋ฐ ์ด์ ์์ธ ์์ฝ ์ถ์ฒ ๋ชจ๋ ๊ณ ๋ํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"ACTION = {\"ํ๋ จ ์ฆ๊ฐ\": \"ํ๋ จ ๊ณํ ๋์กฐ ํ ์ ์ ์ฒ๋ฆฌ\", \"์ ๋น ์ง์ค\": \"์ ๋น ์ง์์์ ์๋ชจ ์๋ ๋์กฐ\", \"์
๋ ฅ ์๋ฆฟ์ ์ค๋ฅ\": \"์ ์ถ ๋ถ๋์ ์์น ์ฌํ์ธ ์์ฒญ\",\n",
" \"์ผํ์ฑ ํน๋ณ ์ง๊ธ\": \"์น์ธ ๋ฌธ์ ๋ฒํธ ํ์ธ\", \"์์ธ ๋ถ๋ช
\": \"๋ด๋น์๊ฐ ๋ถ๋์ ์ง์ ๋ฌธ์\"}\n",
"d[\"cause\"] = np.where(d[\"flag\"], d.apply(recommend, axis=1), \"์ ์\")\n",
"\n",
"# 4) ํ๊ฐ\n",
"truth_anom = d[\"truth\"] != \"์ ์\"\n",
"tp = int((d[\"flag\"] & truth_anom).sum()); fp = int((d[\"flag\"] & ~truth_anom).sum()); fn = int((~d[\"flag\"] & truth_anom).sum())\n",
"prec, rec = tp / max(tp + fp, 1), tp / max(tp + fn, 1)\n",
"acc = float((d[\"flag\"] == truth_anom).mean())\n",
"both = d[d[\"flag\"] & truth_anom]\n",
"cause_acc = float((both[\"cause\"] == both[\"truth\"]).mean())\n",
"total_acc = float(((d[\"cause\"] == d[\"truth\"])).mean())\n",
"print(f\"[์ด์ ๊ฐ์ง] ๋์ {n}๊ฑด(๋ถ๋ 24 x ํ๋ชฉ 11), ์ค์ ๊ธ์ฆ {int(truth_anom.sum())}๊ฑด [๊ต์ก์ฉ๊ฐ์์๋ฃ]\")\n",
"print(f\"๊ฐ์ง {int(d['flag'].sum())}๊ฑด: ์ ํ๋ {acc:.1%}, ์ ๋ฐ๋ {prec:.1%}, ์ฌํ์จ {rec:.1%} (TP {tp}, FP {fp}, FN {fn})\")\n",
"print(f\"์์ธ ๋ถ๋ฅ ์ ํ๋(๊ฐ์ง๋ ์ค์ ๊ธ์ฆ ์ค) {cause_acc:.1%}, ์ ์ฒด ๋ถ๋ฅ(์ ์ ํฌํจ) ์ ํ๋ {total_acc:.1%}\")\n",
"print(\"\\n[์์ธ๋ณ ๊ฐ์งยท์ถ์ฒ ํํฉ]\")\n",
"print(pd.crosstab(both[\"truth\"], both[\"cause\"]).to_string())\n",
"print(\"\\n[์ด์ ์์ธ ์์ฝ ์ถ์ฒ ์์ - ์ฆ๊ฐ์จ ์์ 5๊ฑด]\")\n",
"for (u, it), r in d[d[\"flag\"]].sort_values(\"ratio\", ascending=False).head(5).iterrows():\n",
" print(f\" - {u} {it}: ์ ์ {int(r['prev'])} -> ๋น์ {int(r['cur'])}({r['ratio']:.1f}๋ฐฐ), ์ถ์ ์์ธ '{r['cause']}' -> ์กฐ์น: {ACTION[r['cause']]}\")\n",
"print(\"์ถ์ฒ์ ํ๋ณด ์ ์์ด๋ฉฐ ์ต์ข
์์ธ ํ๋จ๊ณผ ์์น ์ ์ ์ ๋ด๋น์๊ฐ ํ์ธํ๋ค.\")\n",
"print(f\"\\n๋ชฉํ(์ถ์ฒยท๋ถ๋ฅ ์ ํ๋ 85% ์ด์): ๊ฐ์ง {'๋ฌ์ฑ' if acc >= 0.85 else '๋ฏธ๋ฌ'}, ์์ธ ๋ถ๋ฅ {'๋ฌ์ฑ' if cause_acc >= 0.85 else '๋ฏธ๋ฌ'}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
04 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[์ด์ ๊ฐ์ง] ๋์ 264๊ฑด(๋ถ๋ 24 x ํ๋ชฉ 11), ์ค์ ๊ธ์ฆ 36๊ฑด [๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"๊ฐ์ง 35๊ฑด: ์ ํ๋ 99.6%, ์ ๋ฐ๋ 100.0%, ์ฌํ์จ 97.2% (TP 35, FP 0, FN 1)\n",
"์์ธ ๋ถ๋ฅ ์ ํ๋(๊ฐ์ง๋ ์ค์ ๊ธ์ฆ ์ค) 100.0%, ์ ์ฒด ๋ถ๋ฅ(์ ์ ํฌํจ) ์ ํ๋ 99.6%\n",
"\n",
"[์์ธ๋ณ ๊ฐ์งยท์ถ์ฒ ํํฉ]\n",
"cause ์์ธ ๋ถ๋ช
์ผํ์ฑ ํน๋ณ ์ง๊ธ ์
๋ ฅ ์๋ฆฟ์ ์ค๋ฅ ์ ๋น ์ง์ค ํ๋ จ ์ฆ๊ฐ\n",
"truth \n",
"์์ธ ๋ถ๋ช
6 0 0 0 0\n",
"์ผํ์ฑ ํน๋ณ ์ง๊ธ 0 7 0 0 0\n",
"์
๋ ฅ ์๋ฆฟ์ ์ค๋ฅ 0 0 7 0 0\n",
"์ ๋น ์ง์ค 0 0 0 7 0\n",
"ํ๋ จ ์ฆ๊ฐ 0 0 0 0 8\n",
"\n",
"[์ด์ ์์ธ ์์ฝ ์ถ์ฒ ์์ - ์ฆ๊ฐ์จ ์์ 5๊ฑด]\n",
" - ๊ฐ์01๋๋ ํ๋ จํB: ์ ์ 93 -> ๋น์ 930(10.0๋ฐฐ), ์ถ์ ์์ธ '์
๋ ฅ ์๋ฆฟ์ ์ค๋ฅ' -> ์กฐ์น: ์ ์ถ ๋ถ๋์ ์์น ์ฌํ์ธ ์์ฒญ\n",
" - ๊ฐ์15๋๋ ํผ๋ณต๋ฅ: ์ ์ 119 -> ๋น์ 1190(10.0๋ฐฐ), ์ถ์ ์์ธ '์
๋ ฅ ์๋ฆฟ์ ์ค๋ฅ' -> ์กฐ์น: ์ ์ถ ๋ถ๋์ ์์น ์ฌํ์ธ ์์ฒญ\n",
" - ๊ฐ์15๋๋ ์ ๋น์๋ชจํ: ์ ์ 124 -> ๋น์ 1240(10.0๋ฐฐ), ์ถ์ ์์ธ '์
๋ ฅ ์๋ฆฟ์ ์ค๋ฅ' -> ์กฐ์น: ์ ์ถ ๋ถ๋์ ์์น ์ฌํ์ธ ์์ฒญ\n",
" - ๊ฐ์19๋๋ ๋ชจ์ํ: ์ ์ 174 -> ๋น์ 1740(10.0๋ฐฐ), ์ถ์ ์์ธ '์
๋ ฅ ์๋ฆฟ์ ์ค๋ฅ' -> ์กฐ์น: ์ ์ถ ๋ถ๋์ ์์น ์ฌํ์ธ ์์ฒญ\n",
" - ๊ฐ์21๋๋ ์ ๋น์๋ชจํ: ์ ์ 146 -> ๋น์ 1460(10.0๋ฐฐ), ์ถ์ ์์ธ '์
๋ ฅ ์๋ฆฟ์ ์ค๋ฅ' -> ์กฐ์น: ์ ์ถ ๋ถ๋์ ์์น ์ฌํ์ธ ์์ฒญ\n",
"์ถ์ฒ์ ํ๋ณด ์ ์์ด๋ฉฐ ์ต์ข
์์ธ ํ๋จ๊ณผ ์์น ์ ์ ์ ๋ด๋น์๊ฐ ํ์ธํ๋ค.\n",
"\n",
"๋ชฉํ(์ถ์ฒยท๋ถ๋ฅ ์ ํ๋ 85% ์ด์): ๊ฐ์ง ๋ฌ์ฑ, ์์ธ ๋ถ๋ฅ ๋ฌ์ฑ\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
05. ์ ์ฐ ๊ฒฐ๊ณผ์ ํ๊ณ์ ์ผ๊ด์ฑ ๊ฒ์ฆ ๋ฐ ์ ๋ขฐ๋ ๊ฒ์ฆ ์์ง ๊ฐ๋ฐ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์ ์ฐ ๊ฒฐ๊ณผ์ ํ๊ณ์ ์ผ๊ด์ฑ ๊ฒ์ฆ ๋ฐ ์ ๋ขฐ๋ ๊ฒ์ฆ ์์ง์ ๊ฐ๋ฐํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ ์ฐ ๊ฒฐ๊ณผ์ ํ๊ณ์ ์ผ๊ด์ฑ์ ๊ฒ์ฆํ๊ณ ๋ถ๋๋ณ ์ ๋ขฐ๋ ์ ์๋ฅผ ์ฐ์ถํ๋ ์์ง์ ์
๋ ฅ-๋ถ์-๊ฒํ -๊ฒฐ๊ณผ 4๊ฐ ํ๋ฉด์ผ๋ก ๊ตฌํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ฌ๊ณ ์ฐ์, ์ ์ ๊ธฐ๋ง๊ณผ ๋น์ ๊ธฐ์ด์ ์ฐ์์ฑ, ๋ถ๋ ํฉ๊ณ์ ๋ถ๋ฅ ํฉ๊ณ์ ๊ต์ฐจ ๋์กฐ, ์ฅ๋น ๊ฐ๋๋ฅ ๋ฒ์๋ฅผ ๊ฒ์ฌํ๊ณ ์ค๋ฅ 8๊ฑด์ ์ฃผ์
ํด ๊ฒ์ถ ์ฌ๋ถ ํ์ธํ๊ณ ์ ํจ์ฑ์ ์ ๊ฒํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/ui/dat06_app.py` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ตฐ ์ค๋ฌด์ ์นํ์ ๋์์ธ ๋ฐ ์กฐ์ ํธ์์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ํต์ฌ ํ๋ฉด 4์ข
(์
๋ ฅ-๋ถ์-๊ฒํ -๊ฒฐ๊ณผ) ๊ตฌํ ์๋ฃ\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: UI ํ๋กํ ํ์
๋ก์ปฌ ์๋ฒ ๊ตฌ๋ ๋ฐ ์ ์ฒด ํ์ด์ง ๋ค๋น๊ฒ์ด์
ํ์ธ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ์ฌ์ฉ์ฑ ํ๊ฐ(UX Walkthrough) ๋ฐ ์ธํฐ๋์
ํ
์คํธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# [TODO : ํ์ ์ค์ต 5] ์ ์ฐ ๊ฒฐ๊ณผ์ ํ๊ณ์ ์ผ๊ด์ฑ ๊ฒ์ฆ ๋ฐ ์ ๋ขฐ๋ ๊ฒ์ฆ ์์ง ๊ฐ๋ฐ\n",
"# ๋ฏธ์
์ง์นจ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค์ ํ์ธํ๊ณ ์ฝ๋๋ฅผ ์คํ ๋ฐ ํ๋ํ์ธ์.\n",
"\"\"\"๊ฒฐ์ฐ ๊ฒฐ๊ณผ์ ํ๊ณ์ ์ผ๊ด์ฑ(์ฌ๊ณ ์ฐ์ยท์ ์ฐ์์ฑยทํฉ๊ณ ๊ต์ฐจ ๋์กฐยท๊ฐ๋๋ฅ ๋ฒ์)์ ๊ฒ์ฆํด ๋ถ๋๋ณ ์ ๋ขฐ๋ ์ ์๋ฅผ ์ฐ์ถํ๊ณ , ์
๋ ฅ-๋ถ์-๊ฒํ -๊ฒฐ๊ณผ 4๊ฐ ํ๋ฉด์ ํ
์คํธ ํ๋ฉด์ผ๋ก ๊ตฌํํ๋ค.\"\"\"\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์๊ฐ ๊ฒฐ์ฐ ์์ฅ ์์ฑ๊ธฐ (์ค์ ๋ถ๋๋ช
ยทํ๋ชฉยท์๋ยท์ฌ๋ฌด ์ ๋ณด ์์)\n",
"# ๊ฐ์ ๋๋ 24๊ฐ x (๋ฌผ์ ๋ถ์ถ / ์๋ฆฌ๋ถ์ ์๋ชจ / ํ์ฝ ์ ์ฐ) ํ๋ชฉ ์์ฅ๊ณผ ์ฅ๋น ๊ฐ๋ ํํฉ์ ๋ง๋ ๋ค.\n",
"# ์ฌ๊ณ ์ฐ์: ๊ธฐ๋ง = ๊ธฐ์ด + ์๋ น + ๋ฐ๋ฉ - ๋ถ์ถ, ๋ค์ ๋ฌ ๊ธฐ์ด = ์ด๋ฒ ๋ฌ ๊ธฐ๋ง.\n",
"# ---------------------------------------------------------------------------\n",
"UNITS = [f\"๊ฐ์{i:02d}๋๋\" for i in range(1, 25)]\n",
"ITEMS = {\"๋ฌผ์๋ถ์ถ\": [\"๊ธ์๋ฅ\", \"ํผ๋ณต๋ฅ\", \"์ ๋ฅ\", \"์ ๋น์๋ชจํ\"],\n",
" \"์๋ฆฌ๋ถ์\": [\"ํํฐ๋ฅ\", \"๋ฒ ์ด๋ง๋ฅ\", \"์ ์ฅํ\", \"ํ์ด์ด๋ฅ\"],\n",
" \"ํ์ฝ์ ์ฐ\": [\"ํ๋ จํA\", \"ํ๋ จํB\", \"๋ชจ์ํ\"]}\n",
"UNIT_CODE = {\"๊ธ์๋ฅ\": \"BX\", \"ํผ๋ณต๋ฅ\": \"EA\", \"์ ๋ฅ\": \"L\", \"์ ๋น์๋ชจํ\": \"EA\", \"ํํฐ๋ฅ\": \"EA\", \"๋ฒ ์ด๋ง๋ฅ\": \"EA\",\n",
" \"์ ์ฅํ\": \"EA\", \"ํ์ด์ด๋ฅ\": \"EA\", \"ํ๋ จํA\": \"RD\", \"ํ๋ จํB\": \"RD\", \"๋ชจ์ํ\": \"RD\"}\n",
"LEDGER_COLS = [\"month\", \"unit\", \"cat\", \"item\", \"open\", \"recv\", \"issue\", \"ret\", \"close\"]\n",
"\n",
"\n",
"def make_ledger(n_months=2, seed=60):\n",
" \"\"\"n_months๊ฐ์ ์์ฅ(list of dict). ๋์งธ ๋ฌ ๊ธฐ์ด๋ ์ฒซ์งธ ๋ฌ ๊ธฐ๋ง๊ณผ ๊ฐ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows, prev = [], {}\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" for cat, items in ITEMS.items():\n",
" for it in items:\n",
" base = int(rng.integers(80, 400))\n",
" op = prev.get((u, it), base * 3)\n",
" recv = int(rng.integers(0, base))\n",
" issue = int(rng.integers(base // 4, base // 2 + 1))\n",
" ret = int(issue * rng.uniform(0, 0.15))\n",
" cl = op + recv + ret - issue\n",
" rows.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"cat\": cat, \"item\": it,\n",
" \"open\": op, \"recv\": recv, \"issue\": issue, \"ret\": ret, \"close\": cl})\n",
" prev[(u, it)] = cl\n",
" return rows\n",
"\n",
"\n",
"def make_equip(n_months=2, seed=61):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" hold = int(rng.integers(20, 60))\n",
" out.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"hold\": hold, \"operable\": int(hold * rng.uniform(0.78, 0.97))})\n",
" return out\n",
"\n",
"# ---------- ํ๋ฉด 1: ์
๋ ฅ ----------\n",
"led = pd.DataFrame(make_ledger(2))\n",
"eq = pd.DataFrame(make_equip(2))\n",
"cur = led[led[\"month\"] == \"2026-08\"].copy()\n",
"prev = led[led[\"month\"] == \"2026-07\"].set_index([\"unit\", \"item\"])\n",
"rng = np.random.default_rng(5)\n",
"faults = [] # ๊ฐ์ ์ค๋ฅ ์ฃผ์
8๊ฑด\n",
"for i in rng.choice(cur.index, 8, replace=False):\n",
" kind = [\"์ฐ์\", \"์ฐ์์ฑ\", \"์ฐ์\", \"์ฐ์์ฑ\"][len(faults) % 4]\n",
" if kind == \"์ฐ์\":\n",
" cur.loc[i, \"close\"] += int(rng.integers(5, 40))\n",
" else:\n",
" cur.loc[i, \"open\"] += int(rng.integers(5, 40)); cur.loc[i, \"close\"] = cur.loc[i, \"open\"] + cur.loc[i, \"recv\"] + cur.loc[i, \"ret\"] - cur.loc[i, \"issue\"]\n",
" faults.append((cur.loc[i, \"unit\"], cur.loc[i, \"item\"], kind))\n",
"eq2 = eq[eq[\"month\"] == \"2026-08\"].copy()\n",
"eq2.iloc[3, eq2.columns.get_loc(\"operable\")] = eq2.iloc[3][\"hold\"] + 4 # ๊ฐ๋ ๋์๊ฐ ๋ณด์ ๋์ ์ด๊ณผ\n",
"SCREEN1 = f\"์์ฅ {len(cur)}ํ(์ 2026-08), ์ฅ๋น ํํฉ {len(eq2)}ํ, ์ ์ ์์ฅ {len(prev)}ํ ์ ์ฌ / ์์ ๊ฒ์ฌ ํต๊ณผ\"\n",
"print(\"[ํ๋ฉด 1. ์
๋ ฅ]\", SCREEN1)\n",
"\n",
"# ---------- ํ๋ฉด 2: ๋ถ์(์ผ๊ด์ฑ ๊ฒ์ฌ 4์ข
) ----------\n",
"issues = []\n",
"for r in cur.itertuples():\n",
" if r.open + r.recv + r.ret - r.issue != r.close:\n",
" issues.append((r.unit, r.item, \"C1 ์ฌ๊ณ ์ฐ์ ๋ถ์ผ์น\", abs(r.open + r.recv + r.ret - r.issue - r.close)))\n",
" if prev.loc[(r.unit, r.item), \"close\"] != r.open:\n",
" issues.append((r.unit, r.item, \"C2 ์ ์ ๊ธฐ๋ง != ๋น์ ๊ธฐ์ด\", abs(prev.loc[(r.unit, r.item), \"close\"] - r.open)))\n",
"unit_sum = cur.groupby(\"unit\")[[\"open\", \"recv\", \"ret\", \"issue\", \"close\"]].sum()\n",
"cat_sum = cur.groupby([\"unit\", \"cat\"])[[\"open\", \"recv\", \"ret\", \"issue\", \"close\"]].sum().groupby(\"unit\").sum()\n",
"for u in unit_sum.index: # C3 ๊ต์ฐจ ๋์กฐ: ๋ถ๋ ํฉ๊ณ = ๋ถ๋ฅ ํฉ๊ณ์ ํฉ\n",
" if not (unit_sum.loc[u] == cat_sum.loc[u]).all():\n",
" issues.append((u, \"-\", \"C3 ํฉ๊ณ ๊ต์ฐจ ๋์กฐ ๋ถ์ผ์น\", 0))\n",
"for r in eq2.itertuples():\n",
" if not (0 <= r.operable <= r.hold):\n",
" issues.append((r.unit, \"์ฅ๋น\", \"C4 ๊ฐ๋ ๋์๊ฐ ๋ณด์ ๋์ ์ด๊ณผ\", r.operable - r.hold))\n",
"iss = pd.DataFrame(issues, columns=[\"๋ถ๋\", \"ํ๋ชฉ\", \"๊ฒ์ฌ\", \"์ฐจ์ด\"])\n",
"print(f\"[ํ๋ฉด 2. ๋ถ์] ๊ฒ์ฌ 4์ข
์ํ, ๋ถ์ผ์น {len(iss)}๊ฑด\")\n",
"print(iss[\"๊ฒ์ฌ\"].value_counts().to_string())\n",
"\n",
"# ---------- ์ ๋ขฐ๋ ์ ์ ----------\n",
"score = pd.Series(100.0, index=UNITS)\n",
"W = {\"C1\": 12, \"C2\": 10, \"C3\": 15, \"C4\": 8}\n",
"for r in iss.itertuples():\n",
" score[r.๋ถ๋] -= W[r.๊ฒ์ฌ[:2]]\n",
"score = score.clip(lower=0)\n",
"grade = pd.cut(score, [-1, 69, 89, 100], labels=[\"C(์ฌ์ ์ถ)\", \"B(๊ฒํ )\", \"A(์ํธ)\"])\n",
"\n",
"# ---------- ํ๋ฉด 3: ๊ฒํ ----------\n",
"print(f\"\\n[ํ๋ฉด 3. ๊ฒํ ] ์ ๋ขฐ๋ 100์ ๋ฏธ๋ง ๋ถ๋์ ๊ฒํ ๋๊ธฐ ์ฌํญ [๊ต์ก์ฉ๊ฐ์์๋ฃ]\")\n",
"low = iss.groupby(\"๋ถ๋\").agg(๊ฑด์=(\"๊ฒ์ฌ\", \"count\"), ์ ํ=(\"๊ฒ์ฌ\", lambda s: \", \".join(sorted(set(x[:2] for x in s))))).join(score.rename(\"์ ์\"))\n",
"print(low.sort_values(\"์ ์\").to_string())\n",
"print(\"๊ฒํ ์๋ ๋ถ์ผ์น ๊ฑด๋ณ๋ก '์์ ์์ฒญ / ์ ์ ํ์ธ' ์ค ์ ํํ๋ฉฐ, ์์ ์ ์ ์ถ ๋ถ๋๊ฐ ํ๋ค(์์คํ
์ด ๊ฐ์ ์์๋ก ๋ฐ๊พธ์ง ์๋๋ค).\")\n",
"\n",
"# ---------- ํ๋ฉด 4: ๊ฒฐ๊ณผ ----------\n",
"detected = {(u, i) for u, i, k in faults}\n",
"caught = {(r.๋ถ๋, r.ํ๋ชฉ) for r in iss.itertuples() if r.๊ฒ์ฌ[:2] in (\"C1\", \"C2\")}\n",
"print(f\"\\n[ํ๋ฉด 4. ๊ฒฐ๊ณผ] ๋ถ๋ {len(score)}๊ฐ ์ค A {int((grade == 'A(์ํธ)').sum())} / B {int((grade == 'B(๊ฒํ )').sum())} / C {int((grade == 'C(์ฌ์ ์ถ)').sum())}\")\n",
"print(f\"ํ๊ท ์ ๋ขฐ๋ {score.mean():.1f}์ , ์ฃผ์
์ค๋ฅ {len(faults)}๊ฑด ์ค ๊ฒ์ถ {len(detected & caught)}๊ฑด({len(detected & caught) / len(faults):.0%}), ๊ฐ๋๋ฅ ๋ฒ์ ์ค๋ฅ {int((iss['๊ฒ์ฌ'].str[:2] == 'C4').sum())}๊ฑด\")\n",
"ok = len(detected & caught) == len(faults)\n",
"print(f\"\\n๋ชฉํ(ํต์ฌ ํ๋ฉด 4์ข
๊ตฌํ, ์ผ๊ด์ฑ ๊ฒ์ฆ ์์ง ๋์): {'๋ฌ์ฑ' if ok else '๋ฏธ๋ฌ'} (ํ๋ฉด 4์ข
: ์
๋ ฅ/๋ถ์/๊ฒํ /๊ฒฐ๊ณผ)\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
05 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[ํ๋ฉด 1. ์
๋ ฅ] ์์ฅ 264ํ(์ 2026-08), ์ฅ๋น ํํฉ 24ํ, ์ ์ ์์ฅ 264ํ ์ ์ฌ / ์์ ๊ฒ์ฌ ํต๊ณผ\n",
"[ํ๋ฉด 2. ๋ถ์] ๊ฒ์ฌ 4์ข
์ํ, ๋ถ์ผ์น 9๊ฑด\n",
"๊ฒ์ฌ\n",
"C2 ์ ์ ๊ธฐ๋ง != ๋น์ ๊ธฐ์ด 4\n",
"C1 ์ฌ๊ณ ์ฐ์ ๋ถ์ผ์น 4\n",
"C4 ๊ฐ๋ ๋์๊ฐ ๋ณด์ ๋์ ์ด๊ณผ 1\n",
"\n",
"[ํ๋ฉด 3. ๊ฒํ ] ์ ๋ขฐ๋ 100์ ๋ฏธ๋ง ๋ถ๋์ ๊ฒํ ๋๊ธฐ ์ฌํญ [๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
" ๊ฑด์ ์ ํ ์ ์\n",
"๋ถ๋ \n",
"๊ฐ์16๋๋ 2 C1, C2 78.0\n",
"๊ฐ์12๋๋ 1 C1 88.0\n",
"๊ฐ์13๋๋ 1 C1 88.0\n",
"๊ฐ์20๋๋ 1 C1 88.0\n",
"๊ฐ์01๋๋ 1 C2 90.0\n",
"๊ฐ์07๋๋ 1 C2 90.0\n",
"๊ฐ์19๋๋ 1 C2 90.0\n",
"๊ฐ์04๋๋ 1 C4 92.0\n",
"๊ฒํ ์๋ ๋ถ์ผ์น ๊ฑด๋ณ๋ก '์์ ์์ฒญ / ์ ์ ํ์ธ' ์ค ์ ํํ๋ฉฐ, ์์ ์ ์ ์ถ ๋ถ๋๊ฐ ํ๋ค(์์คํ
์ด ๊ฐ์ ์์๋ก ๋ฐ๊พธ์ง ์๋๋ค).\n",
"\n",
"[ํ๋ฉด 4. ๊ฒฐ๊ณผ] ๋ถ๋ 24๊ฐ ์ค A 20 / B 4 / C 0\n",
"ํ๊ท ์ ๋ขฐ๋ 96.0์ , ์ฃผ์
์ค๋ฅ 8๊ฑด ์ค ๊ฒ์ถ 8๊ฑด(100%), ๊ฐ๋๋ฅ ๋ฒ์ ์ค๋ฅ 1๊ฑด\n",
"\n",
"๋ชฉํ(ํต์ฌ ํ๋ฉด 4์ข
๊ตฌํ, ์ผ๊ด์ฑ ๊ฒ์ฆ ์์ง ๋์): ๋ฌ์ฑ (ํ๋ฉด 4์ข
: ์
๋ ฅ/๋ถ์/๊ฒํ /๊ฒฐ๊ณผ)\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
06. ๊ตฐ์ ๊ฒฐ์ฐ ๋ณด๊ณ ์ ์์ฑ ๋ฐ ์น์ธ/๋ฐ๋ ค ๊ด๋ฆฌ ๋ํํ ์น ๋์๋ณด๋ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ตฐ์ ๊ฒฐ์ฐ ๋ณด๊ณ ์ ์์ฑ ๋ฐ ์น์ธ/๋ฐ๋ ค ๋ํํ ์น ๋์๋ณด๋๋ฅผ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๊ฒฐ์ฐ ๋ณด๊ณ ์ ์์ฑ๊ณผ ์น์ธยท๋ฐ๋ ค ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๋์๋ณด๋๋ฅผ ๊ตฌํํ๊ณ , ์์ธ ์ํฉ์์๋ ์ค๋จ๋์ง ์๋ ์
๋ ฅ ๊ฒ์ฆ๊ธฐ์ ์๋ฌ ํธ๋ค๋ฌ๋ฅผ ๊ฐ์ถ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๋ถ๋๋ณ ๋ณด๊ณ ์๋ฅผ ์์ฑํด ์์ฑ-์ ์ถ-์น์ธ-๋ฐ๋ ค ์ํ ์ ์ด๋ฅผ ๊ด๋ฆฌํ๊ณ ํํฉํ๊ณผ ๊ทธ๋ํ๋ก ๋ณด์ฌ ์ค๋ค ๊ณผ์ ์ ์งํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/utils/dat06_validator.py ๋ฐ ์๋ฌ ํธ๋ค๋ฌ` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ฌ์ฉ์์๊ฒ ์๊ธฐ ์ฌ์ด ์๋ฌ ๋ฉ์์ง ๋ฐ ์กฐ์น ๊ฐ์ด๋ ์ ๊ณต\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์์ธ ์ํฉ ๋ฐ์ ์ ํฌ๋์(Crash) 0๊ฑด, ์๋ฌ ๋ก๊ทธ ๊ธฐ๋ก๋ฅ 100%\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์์ธ ์ฃผ์
ํ
์คํธ 10์ข
ํต๊ณผ ๋ฐ ๊ฐ์ฌ ๋ก๊ทธ ํ์ผ ์์ฑ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๋น์ ์ ๋ฐ์ดํฐ(๊ฒฐ์ธก, ํน์๋ฌธ์, ๋์ฉ๋) ์คํธ๋ ์ค ํ
์คํธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๋ถ๋๋ณ ๊ฒฐ์ฐ ๋ณด๊ณ ์๋ฅผ ์์ฑํ๊ณ ์ ์ถยท์น์ธยท๋ฐ๋ ค ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๋์๋ณด๋(์ํ ํํฉํยท๊ทธ๋ํ)๋ฅผ ๊ตฌํํ๋ฉฐ, ์
๋ ฅ ๊ฒ์ฆ๊ธฐ์ ์๋ฌ ํธ๋ค๋ฌ๋ก ์์ธ 10์ข
์ ํฌ๋์ ์์ด ๋ก๊ทธ์ ๊ธฐ๋กํ๊ณ ์ฌ์ฉ์ ์กฐ์น ์๋ด๋ฅผ ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import json\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์๊ฐ ๊ฒฐ์ฐ ์์ฅ ์์ฑ๊ธฐ (์ค์ ๋ถ๋๋ช
ยทํ๋ชฉยท์๋ยท์ฌ๋ฌด ์ ๋ณด ์์)\n",
"# ๊ฐ์ ๋๋ 24๊ฐ x (๋ฌผ์ ๋ถ์ถ / ์๋ฆฌ๋ถ์ ์๋ชจ / ํ์ฝ ์ ์ฐ) ํ๋ชฉ ์์ฅ๊ณผ ์ฅ๋น ๊ฐ๋ ํํฉ์ ๋ง๋ ๋ค.\n",
"# ์ฌ๊ณ ์ฐ์: ๊ธฐ๋ง = ๊ธฐ์ด + ์๋ น + ๋ฐ๋ฉ - ๋ถ์ถ, ๋ค์ ๋ฌ ๊ธฐ์ด = ์ด๋ฒ ๋ฌ ๊ธฐ๋ง.\n",
"# ---------------------------------------------------------------------------\n",
"UNITS = [f\"๊ฐ์{i:02d}๋๋\" for i in range(1, 25)]\n",
"ITEMS = {\"๋ฌผ์๋ถ์ถ\": [\"๊ธ์๋ฅ\", \"ํผ๋ณต๋ฅ\", \"์ ๋ฅ\", \"์ ๋น์๋ชจํ\"],\n",
" \"์๋ฆฌ๋ถ์\": [\"ํํฐ๋ฅ\", \"๋ฒ ์ด๋ง๋ฅ\", \"์ ์ฅํ\", \"ํ์ด์ด๋ฅ\"],\n",
" \"ํ์ฝ์ ์ฐ\": [\"ํ๋ จํA\", \"ํ๋ จํB\", \"๋ชจ์ํ\"]}\n",
"UNIT_CODE = {\"๊ธ์๋ฅ\": \"BX\", \"ํผ๋ณต๋ฅ\": \"EA\", \"์ ๋ฅ\": \"L\", \"์ ๋น์๋ชจํ\": \"EA\", \"ํํฐ๋ฅ\": \"EA\", \"๋ฒ ์ด๋ง๋ฅ\": \"EA\",\n",
" \"์ ์ฅํ\": \"EA\", \"ํ์ด์ด๋ฅ\": \"EA\", \"ํ๋ จํA\": \"RD\", \"ํ๋ จํB\": \"RD\", \"๋ชจ์ํ\": \"RD\"}\n",
"LEDGER_COLS = [\"month\", \"unit\", \"cat\", \"item\", \"open\", \"recv\", \"issue\", \"ret\", \"close\"]\n",
"\n",
"\n",
"def make_ledger(n_months=2, seed=60):\n",
" \"\"\"n_months๊ฐ์ ์์ฅ(list of dict). ๋์งธ ๋ฌ ๊ธฐ์ด๋ ์ฒซ์งธ ๋ฌ ๊ธฐ๋ง๊ณผ ๊ฐ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows, prev = [], {}\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" for cat, items in ITEMS.items():\n",
" for it in items:\n",
" base = int(rng.integers(80, 400))\n",
" op = prev.get((u, it), base * 3)\n",
" recv = int(rng.integers(0, base))\n",
" issue = int(rng.integers(base // 4, base // 2 + 1))\n",
" ret = int(issue * rng.uniform(0, 0.15))\n",
" cl = op + recv + ret - issue\n",
" rows.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"cat\": cat, \"item\": it,\n",
" \"open\": op, \"recv\": recv, \"issue\": issue, \"ret\": ret, \"close\": cl})\n",
" prev[(u, it)] = cl\n",
" return rows\n",
"\n",
"\n",
"def make_equip(n_months=2, seed=61):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" hold = int(rng.integers(20, 60))\n",
" out.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"hold\": hold, \"operable\": int(hold * rng.uniform(0.78, 0.97))})\n",
" return out\n",
"\n",
"LEDGER = pd.DataFrame(make_ledger(2))\n",
"LOG, REPORTS = [], {}\n",
"\n",
"\n",
"class AppError(Exception):\n",
" def __init__(self, code, msg, guide):\n",
" super().__init__(msg); self.code, self.msg, self.guide = code, msg, guide\n",
"\n",
"\n",
"def validate(unit, month, rows):\n",
" # [TODO : ํ์ ์ค์ต 6] ๊ตฐ์ ๊ฒฐ์ฐ ๋ณด๊ณ ์ ์์ฑ ๋ฐ ์น์ธ/๋ฐ๋ ค ๊ด๋ฆฌ ๋ํํ ์น ๋์๋ณด๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def generate(unit, month, data=None):\n",
" # [TODO : ํ์ ์ค์ต 6] ๊ตฐ์ ๊ฒฐ์ฐ ๋ณด๊ณ ์ ์์ฑ ๋ฐ ์น์ธ/๋ฐ๋ ค ๊ด๋ฆฌ ๋ํํ ์น ๋์๋ณด๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"FLOW = {\"์ ์ถ\": (\"์์ฑ\", \"์ ์ถ\"), \"์น์ธ\": (\"์ ์ถ\", \"์น์ธ\"), \"๋ฐ๋ ค\": (\"์ ์ถ\", \"๋ฐ๋ ค\"), \"์ฌ์์ฑ\": (\"๋ฐ๋ ค\", \"์์ฑ\")}\n",
"\n",
"\n",
"def act(rid, action, who, reason=\"\"):\n",
" if rid not in REPORTS:\n",
" raise AppError(\"E08\", f\"๋ณด๊ณ ์ {rid} ์์\", \"๋ณด๊ณ ์ ๋ฒํธ๋ฅผ ๋ชฉ๋ก์์ ํ์ธ\")\n",
" if action not in FLOW:\n",
" raise AppError(\"E09\", f\"์ ์ ์๋ ์ฒ๋ฆฌ '{action}'\", \"์ ์ถ/์น์ธ/๋ฐ๋ ค/์ฌ์์ฑ ์ค ์ ํ\")\n",
" need, new = FLOW[action]\n",
" if REPORTS[rid][\"status\"] != need:\n",
" raise AppError(\"E10\", f\"ํ์ฌ ์ํ '{REPORTS[rid]['status']}'์์ '{action}' ๋ถ๊ฐ\", f\"'{need}' ์ํ์์๋ง ๊ฐ๋ฅ\")\n",
" if action == \"๋ฐ๋ ค\" and not reason.strip():\n",
" raise AppError(\"E11\", \"๋ฐ๋ ค ์ฌ์ ์์\", \"๋ฐ๋ ค ์ฌ์ ๋ฅผ ํ ์ค ์ด์ ์
๋ ฅ\")\n",
" REPORTS[rid][\"status\"] = new\n",
" REPORTS[rid][\"hist\"].append((new, who + (f\": {reason}\" if reason else \"\")))\n",
"\n",
"\n",
"def safe(fn, *a, **k):\n",
" \"\"\"์๋ฌ ํธ๋ค๋ฌ: ๋ชจ๋ ์์ธ๋ฅผ ์ก์ ๋ก๊ทธ์ ๋จ๊ธฐ๊ณ ์ฌ์ฉ์์๊ฒ ์๊ธฐ ์ฌ์ด ๋ฉ์์ง์ ์กฐ์น๋ฅผ ๋๋ ค์ค๋ค(ํฌ๋์ ์์).\"\"\"\n",
" try:\n",
" r = fn(*a, **k)\n",
" LOG.append({\"level\": \"INFO\", \"code\": \"OK\", \"msg\": fn.__name__}); return r\n",
" except AppError as e:\n",
" LOG.append({\"level\": \"ERROR\", \"code\": e.code, \"msg\": e.msg, \"guide\": e.guide}); return None\n",
" except Exception as e: # ์์ ๋ฐ ์ค๋ฅ๋ ๊ธฐ๋ก\n",
" LOG.append({\"level\": \"ERROR\", \"code\": \"E99\", \"msg\": repr(e)[:50], \"guide\": \"์ด์ ๋ด๋น์์๊ฒ ๋ก๊ทธ ์ ๋ฌ\"}); return None\n",
"\n",
"\n",
"# 1) ์ ์ ํ๋ฆ: 12๊ฐ ๋ถ๋ ๋ณด๊ณ ์ ์์ฑ -> ์ ์ถ -> ์น์ธ 8 / ๋ฐ๋ ค 2 / ๋๊ธฐ 2\n",
"rids = [safe(generate, u, \"2026-08\") for u in UNITS[:12]]\n",
"for i, rid in enumerate(rids):\n",
" safe(act, rid, \"์ ์ถ\", f\"๋ด๋น์{i % 3 + 1}\")\n",
" if i < 8:\n",
" safe(act, rid, \"์น์ธ\", \"๋ณด๊ธ๊ด\")\n",
" elif i < 10:\n",
" safe(act, rid, \"๋ฐ๋ ค\", \"๋ณด๊ธ๊ด\", \"๊ธฐ๋ง ์ฌ๊ณ ์ฐ์ ์ฌํ์ธ ์๋ง\")\n",
"board = pd.DataFrame([{\"๋ณด๊ณ ์\": k, **{x: v[x] for x in (\"unit\", \"status\", \"issue\", \"close\")}} for k, v in REPORTS.items()])\n",
"print(\"[๊ฒฐ์ฐ ๋ณด๊ณ ์ ํํฉํ] [๊ต์ก์ฉ๊ฐ์์๋ฃ]\")\n",
"print(board.rename(columns={\"unit\": \"๋ถ๋\", \"status\": \"์ํ\", \"issue\": \"๋ถ์ถํฉ\", \"close\": \"๊ธฐ๋งํฉ\"}).to_string(index=False))\n",
"print(\"์ํ๋ณ:\", board[\"status\"].value_counts().to_dict())\n",
"safe(act, rids[8], \"์ฌ์์ฑ\", \"๋ด๋น์\")\n",
"print(\"๋ฐ๋ ค ๊ฑด ์ฌ์์ฑ ํ ์ํ:\", REPORTS[rids[8]][\"status\"], \"/ ์ด๋ ฅ:\", REPORTS[rids[8]][\"hist\"][-3:])\n",
"\n",
"# 2) ์์ธ ์ฃผ์
10์ข
\n",
"LOG.clear()\n",
"bad = LEDGER[(LEDGER[\"unit\"] == UNITS[20]) & (LEDGER[\"month\"] == \"2026-08\")].copy()\n",
"neg = bad.copy(); neg.iloc[0, neg.columns.get_loc(\"issue\")] = -5\n",
"txt = bad.copy(); txt[\"issue\"] = txt[\"issue\"].astype(str) + \"๊ฐ\"\n",
"nocol = bad.drop(columns=[\"ret\"])\n",
"cases = [(\"๋ถ๋๋ช
์ค๋ฅ\", generate, (\"๊ฐ์99๋๋\", \"2026-08\")), (\"์ ์ค๋ฅ\", generate, (UNITS[20], \"2027-01\")),\n",
" (\"๋น ์๋ฃ\", generate, (UNITS[20], \"2026-08\", bad.iloc[0:0])), (\"์ด ๋๋ฝ\", generate, (UNITS[20], \"2026-08\", nocol)),\n",
" (\"์ซ์ ์๋\", generate, (UNITS[20], \"2026-08\", txt)), (\"์์\", generate, (UNITS[20], \"2026-08\", neg)),\n",
" (\"์ค๋ณต ์์ฑ\", generate, (UNITS[0], \"2026-08\")), (\"์๋ ๋ณด๊ณ ์\", act, (\"RPT-00-00\", \"์น์ธ\", \"๋ณด๊ธ๊ด\")),\n",
" (\"์ํ ์๋ฐ(๋ฏธ์ ์ถ ์น์ธ)\", act, (rids[8], \"์น์ธ\", \"๋ณด๊ธ๊ด\")), (\"๋ฐ๋ ค ์ฌ์ ๋๋ฝ\", act, (rids[11], \"๋ฐ๋ ค\", \"๋ณด๊ธ๊ด\", \" \"))]\n",
"crash = 0\n",
"LOG.clear()\n",
"for name, fn, args in cases:\n",
" try:\n",
" safe(fn, *args)\n",
" except BaseException:\n",
" crash += 1\n",
"errs = [l for l in LOG if l[\"level\"] == \"ERROR\"]\n",
"print(\"\\n[์์ธ ์ฃผ์
๊ฒฐ๊ณผ - ์ฌ์ฉ์ ์๋ด ๋ฉ์์ง]\")\n",
"for (name, _, _), l in zip(cases, errs):\n",
" print(f\" {l['code']} {name:<14} {l['msg']:<28} -> ์กฐ์น: {l['guide']}\")\n",
"rate = len(errs) / len(cases)\n",
"print(f\"์์ธ {len(cases)}์ข
์ค ๋ก๊ทธ ๊ธฐ๋ก {len(errs)}๊ฑด(๊ธฐ๋ก๋ฅ {rate:.0%}), ํฌ๋์ {crash}๊ฑด\")\n",
"print(\"๋ก๊ทธ ์์:\", json.dumps(errs[0], ensure_ascii=False))\n",
"print(f\"๋ชฉํ(ํฌ๋์ 0๊ฑด, ์๋ฌ ๋ก๊ทธ ๊ธฐ๋ก๋ฅ 100%): {'๋ฌ์ฑ' if crash == 0 and rate == 1 else '๋ฏธ๋ฌ'}\")\n",
"\n",
"fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n",
"cnt = board[\"status\"].map({\"์น์ธ\": \"approved\", \"๋ฐ๋ ค\": \"rejected\", \"์ ์ถ\": \"pending\"}).fillna(\"draft\").value_counts()\n",
"ax[0].bar(cnt.index, cnt.values, color=\"tab:blue\"); ax[0].set_title(\"Report status\")\n",
"ax[1].barh(board[\"unit\"].str[2:4], board[\"issue\"], color=\"tab:gray\"); ax[1].set_title(\"Issued quantity by unit (synthetic)\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
06 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ฒฐ์ฐ ๋ณด๊ณ ์ ํํฉํ] [๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
" ๋ณด๊ณ ์ ๋ถ๋ ์ํ ๋ถ์ถํฉ ๊ธฐ๋งํฉ\n",
"RPT-01-08 ๊ฐ์01๋๋ ์น์ธ 1147 8915\n",
"RPT-02-08 ๊ฐ์02๋๋ ์น์ธ 916 9139\n",
"RPT-03-08 ๊ฐ์03๋๋ ์น์ธ 778 7428\n",
"RPT-04-08 ๊ฐ์04๋๋ ์น์ธ 882 8357\n",
"RPT-05-08 ๊ฐ์05๋๋ ์น์ธ 920 7718\n",
"RPT-06-08 ๊ฐ์06๋๋ ์น์ธ 887 10211\n",
"RPT-07-08 ๊ฐ์07๋๋ ์น์ธ 967 8567\n",
"RPT-08-08 ๊ฐ์08๋๋ ์น์ธ 877 7738\n",
"RPT-09-08 ๊ฐ์09๋๋ ๋ฐ๋ ค 1056 8987\n",
"RPT-10-08 ๊ฐ์10๋๋ ๋ฐ๋ ค 1015 9029\n",
"RPT-11-08 ๊ฐ์11๋๋ ์ ์ถ 1130 6845\n",
"RPT-12-08 ๊ฐ์12๋๋ ์ ์ถ 1266 9330\n",
"์ํ๋ณ: {'์น์ธ': 8, '๋ฐ๋ ค': 2, '์ ์ถ': 2}\n",
"๋ฐ๋ ค ๊ฑด ์ฌ์์ฑ ํ ์ํ: ์์ฑ / ์ด๋ ฅ: [('์ ์ถ', '๋ด๋น์3'), ('๋ฐ๋ ค', '๋ณด๊ธ๊ด: ๊ธฐ๋ง ์ฌ๊ณ ์ฐ์ ์ฌํ์ธ ์๋ง'), ('์์ฑ', '๋ด๋น์')]\n",
"\n",
"[์์ธ ์ฃผ์
๊ฒฐ๊ณผ - ์ฌ์ฉ์ ์๋ด ๋ฉ์์ง]\n",
" E01 ๋ถ๋๋ช
์ค๋ฅ ์กด์ฌํ์ง ์๋ ๋ถ๋ '๊ฐ์99๋๋' -> ์กฐ์น: ๋ถ๋ ๋ชฉ๋ก(๊ฐ์01~24๋๋)์์ ์ ํ\n",
" E02 ์ ์ค๋ฅ ๊ฒฐ์ฐ์ '2027-01' ์๋ฃ ์์ -> ์กฐ์น: 2026-07 ๋๋ 2026-08 ์ค ์ ํ\n",
" E03 ๋น ์๋ฃ ํด๋น ๋ถ๋ ์๋ฃ๊ฐ ๋น์ด ์์ -> ์กฐ์น: ์ ์ถ ๋ถ๋์ ์์ฅ ์ฌ์ ์ถ ์์ฒญ\n",
" E04 ์ด ๋๋ฝ ํ์ ์ด 'ret' ๋๋ฝ -> ์กฐ์น: ํ์ค ์์์ ๋ค์ ๋ด๋ ค๋ฐ์ ์์ฑ\n",
" E05 ์ซ์ ์๋ 'issue' ์ด์ ์ซ์๊ฐ ์๋ ๊ฐ -> ์กฐ์น: ์ซ์๋ง ์
๋ ฅ(๋จ์ ๋ฌธ์ ์ ๊ฑฐ)\n",
" E06 ์์ 'issue' ์ด์ ์์ -> ์กฐ์น: ์๋ 0 ์ด์์ผ๋ก ์์ \n",
" E07 ์ค๋ณต ์์ฑ ์ด๋ฏธ ์์ฑ๋ ๋ณด๊ณ ์ RPT-01-08 -> ์กฐ์น: ๊ธฐ์กด ๋ณด๊ณ ์๋ฅผ ์ด์ด ์์ ยท์ฌ์ ์ถ\n",
" E08 ์๋ ๋ณด๊ณ ์ ๋ณด๊ณ ์ RPT-00-00 ์์ -> ์กฐ์น: ๋ณด๊ณ ์ ๋ฒํธ๋ฅผ ๋ชฉ๋ก์์ ํ์ธ\n",
" E10 ์ํ ์๋ฐ(๋ฏธ์ ์ถ ์น์ธ) ํ์ฌ ์ํ '์์ฑ'์์ '์น์ธ' ๋ถ๊ฐ -> ์กฐ์น: '์ ์ถ' ์ํ์์๋ง ๊ฐ๋ฅ\n",
" E11 ๋ฐ๋ ค ์ฌ์ ๋๋ฝ ๋ฐ๋ ค ์ฌ์ ์์ -> ์กฐ์น: ๋ฐ๋ ค ์ฌ์ ๋ฅผ ํ ์ค ์ด์ ์
๋ ฅ\n",
"์์ธ 10์ข
์ค ๋ก๊ทธ ๊ธฐ๋ก 10๊ฑด(๊ธฐ๋ก๋ฅ 100%), ํฌ๋์ 0๊ฑด\n",
"๋ก๊ทธ ์์: {\"level\": \"ERROR\", \"code\": \"E01\", \"msg\": \"์กด์ฌํ์ง ์๋ ๋ถ๋ '๊ฐ์99๋๋'\", \"guide\": \"๋ถ๋ ๋ชฉ๋ก(๊ฐ์01~24๋๋)์์ ์ ํ\"}\n",
"๋ชฉํ(ํฌ๋์ 0๊ฑด, ์๋ฌ ๋ก๊ทธ ๊ธฐ๋ก๋ฅ 100%): ๋ฌ์ฑ\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
07. ๊ฒฐ์ฐ ๋ณด๊ณ ์ ์ต์ข
๋ฐ๊ฐ ์ ๊ตฐ์๊ณผ์ฅ/๋ณด๊ธ๊ด 3๋จ๊ณ HITL ์ ์ ๊ฒฐ์ฌ์ ์ฐ๊ณ ์ค๊ณ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ฒฐ์ฐ ๋ณด๊ณ ์ ์ต์ข
๋ฐ๊ฐ ์ ๊ตฐ์๊ณผ์ฅ/๋ณด๊ธ๊ด 3๋จ๊ณ HITL ๊ฒฐ์ฌ์ ์ ์ค๊ณํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๊ฒฐ์ฐ ๋ณด๊ณ ์ ๋ฐ๊ฐ ์ ๋ด๋น์ ๊ธฐ์, ๋ณด๊ธ๊ด ๊ฒํ , ๊ตฐ์๊ณผ์ฅ ์น์ธ์ 3๋จ๊ณ ์ฌ๋ ๊ฒฐ์ฌ์ ์ ์ค๊ณํ๊ณ ์ด๋ ฅ์ ๋ฌด๊ฒฐ์ฑ๊ณผ ๊ถํ ํต์ ๋ฅผ ๊ฒ์ฆํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฒฐ์ฌ ๋จ๊ณ๋ณ ์ญํ ๊ณผ ์์๋ฅผ ์ค์ ์ผ๋ก ๊ณ ์ ํ๊ณ , ์์ ์๋ฐ, ๋ณธ์ธ ๊ฒฐ์ฌ, ๊ทผ๊ฑฐ ๋ฏธ๊ธฐ์ฌ, ๋ฏธ๋ฑ๋ก ์ฌ์ฉ์๋ฅผ ์ฐจ๋จ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/workflow/dat06_approval_system.py` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ตฐ ๋ณด์ ๊ท์ ๋ฐ ํ์ ์ ์ฐจ ๋ถํฉ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์ด๋ ฅ ๊ธฐ๋ก ๋ฌด๊ฒฐ์ฑ 100%, ์น์ธ ๊ถํ ์ฐํ ์๋ ์ฐจ๋จ์จ 100%\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ๊ฒฐ์ฌ์ ์น์ธ/๋ฐ๋ ค ์๋ฎฌ๋ ์ด์
๋ฐ ์ด๋ ฅ ์กฐํ ํ๋ฉด ๊ตฌํ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๊ถํ๋ณ ์ ๊ทผ ์ ์ด(RBAC) ํ
์คํธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๊ฒฐ์ฐ ๋ณด๊ณ ์ ๋ฐ๊ฐ ์ ๋ด๋น์ ๊ธฐ์, ๋ณด๊ธ๊ด ๊ฒํ , ๊ตฐ์๊ณผ์ฅ ์ต์ข
์น์ธ์ 3๋จ๊ณ ์ฌ๋ ๊ฒฐ์ฌ์ ์ ์ค๊ณํ๊ณ , ํด์ ์ฒด์ธ ๊ฐ์ฌ ์ด๋ ฅ์ ๋ฌด๊ฒฐ์ฑ๊ณผ ๊ถํ ์ฐํ ์๋ 10์ข
์ ์ฐจ๋จ์ ๊ฒ์ฆํ๋ค. ์๋ ๋ฐ๊ฐ์ ํ์ฉํ์ง ์๋๋ค.\"\"\"\n",
"import hashlib\n",
"import json\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์ง์ฑ
ยท์ธ๋ฌผ(๊ฐ๋ช
). ์ค์ ์ ์ ๊ฒฐ์ฌ ์ฒด๊ณ์ ๋ฌด๊ดํ ํ์ต์ฉ ๋ชจ์\n",
"USERS = {\"kim\": \"๋ด๋น์\", \"lee\": \"๋ด๋น์\", \"park\": \"๋ณด๊ธ๊ด\", \"choi\": \"๊ตฐ์๊ณผ์ฅ\", \"han\": \"์ด๋์\"}\n",
"LINE = [(\"๊ธฐ์\", \"๋ด๋น์\"), (\"๊ฒํ \", \"๋ณด๊ธ๊ด\"), (\"์น์ธ\", \"๊ตฐ์๊ณผ์ฅ\")] # ์์ ๊ณ ์ 3๋จ๊ณ\n",
"AUDIT = []\n",
"\n",
"\n",
"def _h(prev, rec):\n",
" # [TODO : ํ์ ์ค์ต 7] ๊ฒฐ์ฐ ๋ณด๊ณ ์ ์ต์ข
๋ฐ๊ฐ ์ ๊ตฐ์๊ณผ์ฅ/๋ณด๊ธ๊ด 3๋จ๊ณ HITL ์ ์ ๊ฒฐ์ฌ์ ์ฐ๊ณ ์ค๊ณ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def log(doc, user, action, result, note=\"\"):\n",
" # [TODO : ํ์ ์ค์ต 7] ๊ฒฐ์ฐ ๋ณด๊ณ ์ ์ต์ข
๋ฐ๊ฐ ์ ๊ตฐ์๊ณผ์ฅ/๋ณด๊ธ๊ด 3๋จ๊ณ HITL ์ ์ ๊ฒฐ์ฌ์ ์ฐ๊ณ ์ค๊ณ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def verify_chain():\n",
" prev, ok = \"GENESIS\", 0\n",
" for r in AUDIT:\n",
" body = {k: v for k, v in r.items() if k != \"hash\"}\n",
" ok += (r[\"hash\"] == _h(prev, body)); prev = r[\"hash\"]\n",
" return ok, len(AUDIT)\n",
"\n",
"\n",
"DOCS = {}\n",
"\n",
"\n",
"def step(doc, user, action, comment=\"\"):\n",
" \"\"\"๊ฒฐ์ฌ ์ฒ๋ฆฌ. ๊ถํยท์์ยท๋ณธ์ธ ๊ฒฐ์ฌ ๊ธ์งยท๊ทผ๊ฑฐ ๊ธฐ์ฌ๋ฅผ ๋ชจ๋ ํต๊ณผํด์ผ ๊ธฐ๋ก๋๊ณ , ์คํจ๋ ์ฐจ๋จ ๊ธฐ๋ก์ ๋จ๊ธด๋ค.\"\"\"\n",
" d = DOCS.setdefault(doc, {\"stage\": 0, \"signers\": [], \"published\": False})\n",
" role = USERS.get(user)\n",
" if role is None:\n",
" return log(doc, user, action, \"์ฐจ๋จ\", \"๋ฏธ๋ฑ๋ก ์ฌ์ฉ์\"), False\n",
" if d[\"stage\"] >= len(LINE):\n",
" return log(doc, user, action, \"์ฐจ๋จ\", \"์ด๋ฏธ ๊ฒฐ์ฌ ์๋ฃ\"), False\n",
" need_act, need_role = LINE[d[\"stage\"]]\n",
" if action != need_act:\n",
" return log(doc, user, action, \"์ฐจ๋จ\", f\"์์ ์๋ฐ: ํ์ฌ ๋จ๊ณ '{need_act}'\"), False\n",
" if role != need_role:\n",
" return log(doc, user, action, \"์ฐจ๋จ\", f\"๊ถํ ์์: ์ญํ '{role}'์(๋) '{action}' ๋ถ๊ฐ({need_role} ํ์)\"), False\n",
" if user in d[\"signers\"]:\n",
" return log(doc, user, action, \"์ฐจ๋จ\", \"๋ณธ์ธ์ด ์ ๋จ๊ณ์ ์๋ช
ํ ๋ฌธ์๋ ๋ค์ ๋จ๊ณ๋ฅผ ๊ฒฐ์ฌํ ์ ์์\"), False\n",
" if action != \"๊ธฐ์\" and len(comment.strip()) < 5:\n",
" return log(doc, user, action, \"์ฐจ๋จ\", \"๊ฒํ ๊ทผ๊ฑฐ(5์ ์ด์) ๋ฏธ๊ธฐ์ฌ\"), False\n",
" d[\"stage\"] += 1; d[\"signers\"].append(user)\n",
" log(doc, user, action, \"์น์ธ\", comment or \"๊ธฐ์ ์์ \")\n",
" return None, True\n",
"\n",
"\n",
"def publish(doc, user):\n",
" d = DOCS.get(doc)\n",
" if not d or d[\"stage\"] < len(LINE):\n",
" log(doc, user, \"๋ฐ๊ฐ\", \"์ฐจ๋จ\", \"3๋จ๊ณ ๊ฒฐ์ฌ ๋ฏธ์๋ฃ(์๋ ๋ฐ๊ฐ ๊ธ์ง)\"); return False\n",
" if USERS.get(user) != \"๋ด๋น์\":\n",
" log(doc, user, \"๋ฐ๊ฐ\", \"์ฐจ๋จ\", \"๋ฐ๊ฐ์ ๋ด๋น์๊ฐ ๊ฒฐ์ฌ ์๋ฃ ํ ์๋ ์คํ\"); return False\n",
" d[\"published\"] = True; log(doc, user, \"๋ฐ๊ฐ\", \"์น์ธ\", \"๊ฒฐ์ฌ 3๋จ๊ณ ์๋ฃ ํ ์๋ ๋ฐ๊ฐ\"); return True\n",
"\n",
"\n",
"# 1) ์ ์ ๊ฒฐ์ฌ ํ๋ฆ 2๊ฑด\n",
"for doc, drafter in ((\"RPT-2026-08-A\", \"kim\"), (\"RPT-2026-08-B\", \"lee\")):\n",
" step(doc, drafter, \"๊ธฐ์\")\n",
" step(doc, \"park\", \"๊ฒํ \", \"์ฌ๊ณ ์ฐ์ ๋ฐ ์ ์ ์ฐ์์ฑ ํ์ธ ์๋ฃ\")\n",
" step(doc, \"choi\", \"์น์ธ\", \"๋ณด๊ธ๊ด ๊ฒํ ์๊ฒฌ ํ์ธ, ๋ฐ๊ฐ ์น์ธ\")\n",
" publish(doc, drafter)\n",
"\n",
"# 2) ๊ถํ ์ฐํ ์๋ 10์ข
- ๊ฒฐ์ฌ๊ฐ ์งํ๋๋ ์์์ ๋ง์ถฐ ์๋ํ๊ณ , ๋ชจ๋ ์ฐจ๋จ๋์ด์ผ ํ๋ค\n",
"blocked, total = 0, 0\n",
"print(\"[๊ถํ ์ฐํ ์๋ ๊ฒฐ๊ณผ] [๊ต์ก์ฉ๊ฐ์์๋ฃ]\")\n",
"\n",
"\n",
"def attempt(name, fn):\n",
" global blocked, total\n",
" n = len(AUDIT); fn(); r = AUDIT[-1]\n",
" ok = len(AUDIT) == n + 1 and r[\"result\"] == \"์ฐจ๋จ\"\n",
" blocked += ok; total += 1\n",
" print(f\" - {name}: {r['result']} ({r['note']})\")\n",
"\n",
"\n",
"attempt(\"๋ฏธ๋ฑ๋ก ์ฌ์ฉ์ ๊ธฐ์\", lambda: step(\"RPT-ATT\", \"hacker\", \"๊ธฐ์\"))\n",
"attempt(\"์ด๋์๊ฐ ๊ธฐ์\", lambda: step(\"RPT-ATT\", \"han\", \"๊ธฐ์\"))\n",
"attempt(\"๊ธฐ์ ์ ์ ๊ตฐ์๊ณผ์ฅ์ด ์น์ธ(๋จ๊ณ ๊ฑด๋๋ฐ๊ธฐ)\", lambda: step(\"RPT-ATT\", \"choi\", \"์น์ธ\", \"๋ฐ๋ก ์น์ธํฉ๋๋ค ํ์ธ\"))\n",
"step(\"RPT-ATT\", \"kim\", \"๊ธฐ์\")\n",
"attempt(\"๊ธฐ์์ ๋ณธ์ธ์ด ๊ฒํ ๊น์ง ์ฒ๋ฆฌ\", lambda: step(\"RPT-ATT\", \"kim\", \"๊ฒํ \", \"๋ณธ์ธ์ด ๊ฒํ ํฉ๋๋ค ํ์ธ\"))\n",
"attempt(\"๋ค๋ฅธ ๋ด๋น์๊ฐ ๊ฒํ ๋จ๊ณ ์ฒ๋ฆฌ\", lambda: step(\"RPT-ATT\", \"lee\", \"๊ฒํ \", \"๋ค๋ฅธ ๋ด๋น์ ๊ฒํ ์๋ ํ์ธ\"))\n",
"attempt(\"๊ฒํ ๊ทผ๊ฑฐ ์์ด ๋ณด๊ธ๊ด ๊ฒํ \", lambda: step(\"RPT-ATT\", \"park\", \"๊ฒํ \", \"\"))\n",
"attempt(\"๋ณด๊ธ๊ด ๊ฒํ ์ ์ ๊ตฐ์๊ณผ์ฅ์ด ์น์ธ\", lambda: step(\"RPT-ATT\", \"choi\", \"์น์ธ\", \"๋ณด๊ธ๊ด ๋จ๊ณ๋ฅผ ์๋ตํ ์น์ธ ํ์ธ\"))\n",
"attempt(\"๊ฒฐ์ฌ ๋ฏธ์๋ฃ ์ํ์์ ๋ฐ๊ฐ\", lambda: publish(\"RPT-ATT\", \"kim\"))\n",
"step(\"RPT-ATT\", \"park\", \"๊ฒํ \", \"์ฐ์ ๊ฒ์ฆ ๊ฒฐ๊ณผ ์ด์ ์์ ํ์ธ\")\n",
"step(\"RPT-ATT\", \"choi\", \"์น์ธ\", \"์ต์ข
์น์ธํฉ๋๋ค ํ์ธ\")\n",
"attempt(\"์๊ฒฐ๋ ๋ฌธ์ ์ฌ์น์ธ ์๋\", lambda: step(\"RPT-ATT\", \"choi\", \"์น์ธ\", \"ํ ๋ฒ ๋ ์น์ธ ์๋ ํ์ธ\"))\n",
"attempt(\"๋ณด๊ธ๊ด์ด ๋ฐ๊ฐ ์๋\", lambda: publish(\"RPT-ATT\", \"park\"))\n",
"print(f\"์ฐํ ์๋ {total}๊ฑด ์ค ์ฐจ๋จ {blocked}๊ฑด({blocked / total:.0%})\")\n",
"\n",
"# 3) ์ด๋ ฅ ๋ฌด๊ฒฐ์ฑ: ์ ์ ์ฒด์ธ ๊ฒ์ฆ ํ ์ยท๋ณ์กฐ ์ํ\n",
"ok, tot = verify_chain()\n",
"print(f\"\\n์ด๋ ฅ {tot}๊ฑด ํด์ ์ฒด์ธ ๊ฒ์ฆ: {ok}/{tot} ์ผ์น({ok / tot:.0%})\")\n",
"k = next(i for i, r in enumerate(AUDIT) if r[\"result\"] == \"์ฐจ๋จ\")\n",
"saved = AUDIT[k][\"result\"]\n",
"AUDIT[k][\"result\"] = \"์น์ธ\" # ์ฐจ๋จ ๊ธฐ๋ก์ ์น์ธ์ผ๋ก ์์กฐ\n",
"ok2, _ = verify_chain()\n",
"AUDIT[k][\"result\"] = saved\n",
"print(f\"์์กฐ ์ํ: {k + 1}๋ฒ ๊ธฐ๋ก ๋ณ์กฐ ์ ๊ฒ์ฆ {ok2}/{tot} -> ๋ณ์กฐ ํ์ง {'์ฑ๊ณต' if ok2 < tot else '์คํจ'}, ์๋ณต ํ {verify_chain()[0]}/{tot}\")\n",
"st = pd.DataFrame(AUDIT).groupby(\"doc\")[\"result\"].value_counts().unstack(fill_value=0)\n",
"print(\"\\n[๋ฌธ์๋ณ ๊ฒฐ์ฌ ์ด๋ ฅ ์์ฝ]\")\n",
"print(st.to_string())\n",
"pubs = [k for k, v in DOCS.items() if v[\"published\"]]\n",
"print(\"๋ฐ๊ฐ ์๋ฃ ๋ฌธ์:\", pubs, \"/ ์ฐํ ์ํ ๋ฌธ์ RPT-ATT๋ ๋ฐ๊ฐ๋์ง ์์ / ์๋ ๋ฐ๊ฐ 0๊ฑด(๋ฐ๊ฐ์ ๋ด๋น์ ์๋ ์คํ)\")\n",
"print(f\"๋ชฉํ(์ด๋ ฅ ๋ฌด๊ฒฐ์ฑ 100%, ์ฐํ ์ฐจ๋จ์จ 100%): {'๋ฌ์ฑ' if ok == tot and blocked == total and ok2 < tot else '๋ฏธ๋ฌ'}\")\n",
"print(\"์ค์ ๊ฒฐ์ฌ์ ๋ณด์ ๊ท์ ์ ๋ถ๋ ์ง์นจ๊ณผ ์งํ๊ณํต์ ๋ฐ๋ฅธ๋ค. ๋ณธ ์ฝ๋๋ ํ์ต์ฉ ๋ชจ์์ด๋ค.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
07 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ถํ ์ฐํ ์๋ ๊ฒฐ๊ณผ] [๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
" - ๋ฏธ๋ฑ๋ก ์ฌ์ฉ์ ๊ธฐ์: ์ฐจ๋จ (๋ฏธ๋ฑ๋ก ์ฌ์ฉ์)\n",
" - ์ด๋์๊ฐ ๊ธฐ์: ์ฐจ๋จ (๊ถํ ์์: ์ญํ '์ด๋์'์(๋) '๊ธฐ์' ๋ถ๊ฐ(๋ด๋น์ ํ์))\n",
" - ๊ธฐ์ ์ ์ ๊ตฐ์๊ณผ์ฅ์ด ์น์ธ(๋จ๊ณ ๊ฑด๋๋ฐ๊ธฐ): ์ฐจ๋จ (์์ ์๋ฐ: ํ์ฌ ๋จ๊ณ '๊ธฐ์')\n",
" - ๊ธฐ์์ ๋ณธ์ธ์ด ๊ฒํ ๊น์ง ์ฒ๋ฆฌ: ์ฐจ๋จ (๊ถํ ์์: ์ญํ '๋ด๋น์'์(๋) '๊ฒํ ' ๋ถ๊ฐ(๋ณด๊ธ๊ด ํ์))\n",
" - ๋ค๋ฅธ ๋ด๋น์๊ฐ ๊ฒํ ๋จ๊ณ ์ฒ๋ฆฌ: ์ฐจ๋จ (๊ถํ ์์: ์ญํ '๋ด๋น์'์(๋) '๊ฒํ ' ๋ถ๊ฐ(๋ณด๊ธ๊ด ํ์))\n",
" - ๊ฒํ ๊ทผ๊ฑฐ ์์ด ๋ณด๊ธ๊ด ๊ฒํ : ์ฐจ๋จ (๊ฒํ ๊ทผ๊ฑฐ(5์ ์ด์) ๋ฏธ๊ธฐ์ฌ)\n",
" - ๋ณด๊ธ๊ด ๊ฒํ ์ ์ ๊ตฐ์๊ณผ์ฅ์ด ์น์ธ: ์ฐจ๋จ (์์ ์๋ฐ: ํ์ฌ ๋จ๊ณ '๊ฒํ ')\n",
" - ๊ฒฐ์ฌ ๋ฏธ์๋ฃ ์ํ์์ ๋ฐ๊ฐ: ์ฐจ๋จ (3๋จ๊ณ ๊ฒฐ์ฌ ๋ฏธ์๋ฃ(์๋ ๋ฐ๊ฐ ๊ธ์ง))\n",
" - ์๊ฒฐ๋ ๋ฌธ์ ์ฌ์น์ธ ์๋: ์ฐจ๋จ (์ด๋ฏธ ๊ฒฐ์ฌ ์๋ฃ)\n",
" - ๋ณด๊ธ๊ด์ด ๋ฐ๊ฐ ์๋: ์ฐจ๋จ (๋ฐ๊ฐ์ ๋ด๋น์๊ฐ ๊ฒฐ์ฌ ์๋ฃ ํ ์๋ ์คํ)\n",
"์ฐํ ์๋ 10๊ฑด ์ค ์ฐจ๋จ 10๊ฑด(100%)\n",
"\n",
"์ด๋ ฅ 21๊ฑด ํด์ ์ฒด์ธ ๊ฒ์ฆ: 21/21 ์ผ์น(100%)\n",
"์์กฐ ์ํ: 9๋ฒ ๊ธฐ๋ก ๋ณ์กฐ ์ ๊ฒ์ฆ 20/21 -> ๋ณ์กฐ ํ์ง ์ฑ๊ณต, ์๋ณต ํ 21/21\n",
"\n",
"[๋ฌธ์๋ณ ๊ฒฐ์ฌ ์ด๋ ฅ ์์ฝ]\n",
"result ์น์ธ ์ฐจ๋จ\n",
"doc \n",
"RPT-2026-08-A 4 0\n",
"RPT-2026-08-B 4 0\n",
"RPT-ATT 3 10\n",
"๋ฐ๊ฐ ์๋ฃ ๋ฌธ์: ['RPT-2026-08-A', 'RPT-2026-08-B'] / ์ฐํ ์ํ ๋ฌธ์ RPT-ATT๋ ๋ฐ๊ฐ๋์ง ์์ / ์๋ ๋ฐ๊ฐ 0๊ฑด(๋ฐ๊ฐ์ ๋ด๋น์ ์๋ ์คํ)\n",
"๋ชฉํ(์ด๋ ฅ ๋ฌด๊ฒฐ์ฑ 100%, ์ฐํ ์ฐจ๋จ์จ 100%): ๋ฌ์ฑ\n",
"์ค์ ๊ฒฐ์ฌ์ ๋ณด์ ๊ท์ ์ ๋ถ๋ ์ง์นจ๊ณผ ์งํ๊ณํต์ ๋ฐ๋ฅธ๋ค. ๋ณธ ์ฝ๋๋ ํ์ต์ฉ ๋ชจ์์ด๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
08. ์์ญ ๊ฐ ์ํ ๋ถ๋ ์๊ฐ ๊ฒฐ์ฐ ๋ณด๊ณ ์ ์ํด๋ฆญ ์ผ๊ด ์์ฑ ๋ฐฐ์น(Batch) ์คํธ๋ ์ค ํ
์คํธ\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์์ญ ๊ฐ ์ํ ๋ถ๋ ์๊ฐ ๊ฒฐ์ฐ ๋ณด๊ณ ์ ์ผ๊ด ์์ฑ ๋ฐฐ์น ์คํธ๋ ์ค ํ
์คํธ๋ฅผ ์ํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์์ญ ๊ฐ ์ํ ๋ถ๋์ ์๊ฐ ๊ฒฐ์ฐ ๋ณด๊ณ ์๋ฅผ ์ํด๋ฆญ์ผ๋ก ์ผ๊ด ์์ฑํ๋ ๋ฐฐ์น๋ฅผ ๋ง๋ค๊ณ ์ฅ์ ยท์์ ์๋ฃ๋ฅผ ์ฃผ์
ํด ์คํธ๋ ์ค ์ํ์ ์ํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฐ์ ๋ถ๋ 40๊ฐ x 12๊ฐ์, 480๊ฑด ๋ณด๊ณ ์๋ฅผ ํ ๋ฒ์ ์์ฑ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/pipeline/dat06_batch_runner.py` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์์์
๋๋น ์
๋ฌด ์์ ์๊ฐ 80% ์ด์ ์ ๊ฐ ํจ๊ณผ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์ ์ฒด ๋ฐฐ์น ํ์ดํ๋ผ์ธ ์คํ ์๊ฐ 5๋ถ ์ด๋ด, ์๋ํ ์ฑ๊ณต๋ฅ 99% ์ด์\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์ค์ผ์ค๋ฌ ์ฐ๋ ํ
์คํธ ๋ฐ ์๋ ์คํ ํ์ธ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ์ค์ ์๊ฐ ์
๋ฌด ๋ฐ์ดํฐ 1๋
์น ์ผ๊ด ๋ฐฐ์น ์๋ฎฌ๋ ์ด์
\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"40๊ฐ ๊ฐ์ ์ํ ๋ถ๋ x 12๊ฐ์(480๊ฑด) ์๊ฐ ๊ฒฐ์ฐ ๋ณด๊ณ ์๋ฅผ ํ ๋ฒ์ ์์ฑํ๋ ๋ฐฐ์น๋ฅผ ์คํํ๋ฉฐ, ์ผ์ ์ฅ์ ์ ์์ ์๋ฃ๋ฅผ ์ฃผ์
ํด ์ฌ์๋ยท๊ฒฉ๋ฆฌ ์ฒ๋ฆฌ๋ฅผ ํ์ธํ๊ณ ์ ์ฒด ์คํ ์๊ฐ, ์๋ํ ์ฑ๊ณต๋ฅ , ์์์
๋๋น ์ ๊ฐ๋ฅ ์ ์ธก์ ํ๋ค.\"\"\"\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์๊ฐ ๊ฒฐ์ฐ ์์ฅ ์์ฑ๊ธฐ (์ค์ ๋ถ๋๋ช
ยทํ๋ชฉยท์๋ยท์ฌ๋ฌด ์ ๋ณด ์์)\n",
"# ๊ฐ์ ๋๋ 24๊ฐ x (๋ฌผ์ ๋ถ์ถ / ์๋ฆฌ๋ถ์ ์๋ชจ / ํ์ฝ ์ ์ฐ) ํ๋ชฉ ์์ฅ๊ณผ ์ฅ๋น ๊ฐ๋ ํํฉ์ ๋ง๋ ๋ค.\n",
"# ์ฌ๊ณ ์ฐ์: ๊ธฐ๋ง = ๊ธฐ์ด + ์๋ น + ๋ฐ๋ฉ - ๋ถ์ถ, ๋ค์ ๋ฌ ๊ธฐ์ด = ์ด๋ฒ ๋ฌ ๊ธฐ๋ง.\n",
"# ---------------------------------------------------------------------------\n",
"UNITS = [f\"๊ฐ์{i:02d}๋๋\" for i in range(1, 25)]\n",
"ITEMS = {\"๋ฌผ์๋ถ์ถ\": [\"๊ธ์๋ฅ\", \"ํผ๋ณต๋ฅ\", \"์ ๋ฅ\", \"์ ๋น์๋ชจํ\"],\n",
" \"์๋ฆฌ๋ถ์\": [\"ํํฐ๋ฅ\", \"๋ฒ ์ด๋ง๋ฅ\", \"์ ์ฅํ\", \"ํ์ด์ด๋ฅ\"],\n",
" \"ํ์ฝ์ ์ฐ\": [\"ํ๋ จํA\", \"ํ๋ จํB\", \"๋ชจ์ํ\"]}\n",
"UNIT_CODE = {\"๊ธ์๋ฅ\": \"BX\", \"ํผ๋ณต๋ฅ\": \"EA\", \"์ ๋ฅ\": \"L\", \"์ ๋น์๋ชจํ\": \"EA\", \"ํํฐ๋ฅ\": \"EA\", \"๋ฒ ์ด๋ง๋ฅ\": \"EA\",\n",
" \"์ ์ฅํ\": \"EA\", \"ํ์ด์ด๋ฅ\": \"EA\", \"ํ๋ จํA\": \"RD\", \"ํ๋ จํB\": \"RD\", \"๋ชจ์ํ\": \"RD\"}\n",
"LEDGER_COLS = [\"month\", \"unit\", \"cat\", \"item\", \"open\", \"recv\", \"issue\", \"ret\", \"close\"]\n",
"\n",
"\n",
"def make_ledger(n_months=2, seed=60):\n",
" \"\"\"n_months๊ฐ์ ์์ฅ(list of dict). ๋์งธ ๋ฌ ๊ธฐ์ด๋ ์ฒซ์งธ ๋ฌ ๊ธฐ๋ง๊ณผ ๊ฐ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows, prev = [], {}\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" for cat, items in ITEMS.items():\n",
" for it in items:\n",
" base = int(rng.integers(80, 400))\n",
" op = prev.get((u, it), base * 3)\n",
" recv = int(rng.integers(0, base))\n",
" issue = int(rng.integers(base // 4, base // 2 + 1))\n",
" ret = int(issue * rng.uniform(0, 0.15))\n",
" cl = op + recv + ret - issue\n",
" rows.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"cat\": cat, \"item\": it,\n",
" \"open\": op, \"recv\": recv, \"issue\": issue, \"ret\": ret, \"close\": cl})\n",
" prev[(u, it)] = cl\n",
" return rows\n",
"\n",
"\n",
"def make_equip(n_months=2, seed=61):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" hold = int(rng.integers(20, 60))\n",
" out.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"hold\": hold, \"operable\": int(hold * rng.uniform(0.78, 0.97))})\n",
" return out\n",
"\n",
"UNITS = [f\"๊ฐ์{i:02d}๋๋\" for i in range(1, 41)] # ๋ฐฐ์น์ฉ์ผ๋ก 40๊ฐ ๋ถ๋๋ก ํ์ฅ\n",
"t_all = time.perf_counter()\n",
"rows = make_ledger(12, seed=8)\n",
"led = pd.DataFrame(rows)\n",
"lab = {f\"2026-{i + 7:02d}\": f\"{2026 + (i + 6) // 12}-{(i + 6) % 12 + 1:02d}\" for i in range(12)} # 2026-07 ~ 2027-06\n",
"led[\"month\"] = led[\"month\"].map(lab)\n",
"months = sorted(led[\"month\"].unique())\n",
"print(f\"[๋ฐฐ์น ์
๋ ฅ] ๋ถ๋ {len(UNITS)}๊ฐ x {len(months)}๊ฐ์ = {len(UNITS) * len(months)}๊ฑด, ์์ฅ {len(led)}ํ [๊ต์ก์ฉ๊ฐ์์๋ฃ]\")\n",
"\n",
"# ์ฅ์ ์ฃผ์
: ์ผ์ ์ฅ์ (์ฌ์๋๋ก ๋ณต๊ตฌ) 9๊ฑด, ์์ ์๋ฃ(๊ฒฉ๋ฆฌ) 3๊ฑด\n",
"rng = np.random.default_rng(80)\n",
"keys = [(u, m) for u in UNITS for m in months]\n",
"pick = rng.choice(len(keys), 12, replace=False)\n",
"transient = {keys[i] for i in pick[:9]}\n",
"corrupt = {keys[i] for i in pick[9:]}\n",
"for (u, m) in corrupt: # ๊ธฐ๋ง ์์น๋ฅผ ํผ์(์ฐ์ ๋ถ์ผ์น)\n",
" i = led[(led[\"unit\"] == u) & (led[\"month\"] == m)].index[0]\n",
" led.loc[i, \"close\"] = led.loc[i, \"close\"] + 999\n",
"attempts_log = {}\n",
"\n",
"\n",
"def build_report(u, m, g):\n",
" # [TODO : ํ์ ์ค์ต 8] ์์ญ ๊ฐ ์ํ ๋ถ๋ ์๊ฐ ๊ฒฐ์ฐ ๋ณด๊ณ ์ ์ํด๋ฆญ ์ผ๊ด ์์ฑ ๋ฐฐ์น(Batch) ์คํธ๋ ์ค ํ
์คํธ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"done, quarantine, retries = {}, [], 0\n",
"t0 = time.perf_counter()\n",
"for (u, m), g in led.groupby([\"unit\", \"month\"]):\n",
" for attempt in range(1, 4): # ์ต๋ 3ํ ์ฌ์๋\n",
" try:\n",
" done[(u, m)] = build_report(u, m, g)\n",
" break\n",
" except TimeoutError:\n",
" retries += 1\n",
" except ValueError as ex: # ์๋ฃ ์ค๋ฅ๋ ์ฌ์๋ํด๋ ์์ฉ์์ผ๋ฏ๋ก ์ฆ์ ๊ฒฉ๋ฆฌํ๊ณ ๋ค์ ๊ฑด์ผ๋ก ์งํ\n",
" quarantine.append((u, m, str(ex)))\n",
" break\n",
"run_s = time.perf_counter() - t0\n",
"total = len(keys)\n",
"succ = len(done) / total\n",
"print(f\"\\n[๋ฐฐ์น ์คํ] ์์ฑ {len(done)}๊ฑด, ๊ฒฉ๋ฆฌ {len(quarantine)}๊ฑด, ์ฌ์๋ {retries}ํ\")\n",
"for q in quarantine:\n",
" print(\" ๊ฒฉ๋ฆฌ(๋ด๋น์ ํ์ธ ํ์):\", q)\n",
"print(f\"๋ณด๊ณ ์ ์์ฑ ์์ ์คํ ์๊ฐ {run_s:.2f}์ด, ์ ์ฒด(์๋ฃ ์์ฑ ํฌํจ) {time.perf_counter() - t_all:.2f}์ด (๋ชฉํ 5๋ถ ์ด๋ด: {'๋ฌ์ฑ' if time.perf_counter() - t_all < 300 else '๋ฏธ๋ฌ'})\")\n",
"print(f\"์๋ํ ์ฑ๊ณต๋ฅ {succ:.1%} (์ผ์ ์ฅ์ {len(transient)}๊ฑด์ ์ฌ์๋๋ก ์๋ ๋ณต๊ตฌ, ์์ ์๋ฃ๋ ๊ฒฉ๋ฆฌ ํ ๋ณด๊ณ )\")\n",
"\n",
"# ์์์
๋๋น ์ ๊ฐ(๊ฐ์ ๊ฐ): ๋ถ๋ ๋ณด๊ณ ์ 1๊ฑด ์์์
25๋ถ(๋ฏธ์
1 AS-IS 610๋ถ/24๊ฐ ๋ถ๋ ๊ธฐ์ค), ์๋ํ ํ ์ฌ๋ ๊ฒํ 2๋ถ/๊ฑด, ๊ฒฉ๋ฆฌ ๊ฑด ์๋ ์ฒ๋ฆฌ 15๋ถ\n",
"manual = total * 25\n",
"auto = total * 2 + len(quarantine) * 15 + run_s / 60\n",
"print(f\"\\n[์
๋ฌด ์์ ๋น๊ต(๊ฐ์ ๊ฐ)] ์์์
{manual}๋ถ({manual / 60:.0f}์๊ฐ) vs ์๋ํ {auto:.0f}๋ถ({auto / 60:.1f}์๊ฐ) -> ์ ๊ฐ {1 - auto / manual:.1%}\")\n",
"\n",
"# ์คํธ๋ ์ค: ๋ถํ 3๋ฐฐ(1,440๊ฑด) ๋ฐ๋ณต ์คํ์ ์ฒ๋ฆฌ๋\n",
"big = list(led.groupby([\"unit\", \"month\"]))\n",
"t0 = time.perf_counter(); n = 0\n",
"for _ in range(3):\n",
" for (u, m), g in big:\n",
" if (u, m) in corrupt:\n",
" continue\n",
" attempts_log[(u, m)] = 9 # ์ผ์ ์ฅ์ ์ฃผ์
ํด์ (์ฒ๋ฆฌ๋ ์ธก์ ๋ชฉ์ )\n",
" build_report(u, m, g); n += 1\n",
"dt = time.perf_counter() - t0\n",
"print(f\"๋ถํ ์ํ {n}๊ฑด ์ฒ๋ฆฌ {dt:.2f}์ด (์ด๋น {n / dt:.0f}๊ฑด), ์ค๋ฅ 0๊ฑด\")\n",
"print(f\"\\n๋ชฉํ(์ ์ฒด 5๋ถ ์ด๋ด, ์๋ํ ์ฑ๊ณต๋ฅ 99% ์ด์, ์์์
๋๋น 80% ์ด์ ์ ๊ฐ): \"\n",
" f\"{'๋ฌ์ฑ' if run_s < 300 and succ >= 0.99 and 1 - auto / manual >= 0.8 else '๋ฏธ๋ฌ'}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
08 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๋ฐฐ์น ์
๋ ฅ] ๋ถ๋ 40๊ฐ x 12๊ฐ์ = 480๊ฑด, ์์ฅ 5280ํ [๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"\n",
"[๋ฐฐ์น ์คํ] ์์ฑ 477๊ฑด, ๊ฒฉ๋ฆฌ 3๊ฑด, ์ฌ์๋ 9ํ\n",
" ๊ฒฉ๋ฆฌ(๋ด๋น์ ํ์ธ ํ์): ('๊ฐ์10๋๋', '2027-05', '์ฌ๊ณ ์ฐ์ ๋ถ์ผ์น 1๊ฑด')\n",
" ๊ฒฉ๋ฆฌ(๋ด๋น์ ํ์ธ ํ์): ('๊ฐ์24๋๋', '2027-02', '์ฌ๊ณ ์ฐ์ ๋ถ์ผ์น 1๊ฑด')\n",
" ๊ฒฉ๋ฆฌ(๋ด๋น์ ํ์ธ ํ์): ('๊ฐ์36๋๋', '2026-12', '์ฌ๊ณ ์ฐ์ ๋ถ์ผ์น 1๊ฑด')\n",
"๋ณด๊ณ ์ ์์ฑ ์์ ์คํ ์๊ฐ 1.06์ด, ์ ์ฒด(์๋ฃ ์์ฑ ํฌํจ) 1.13์ด (๋ชฉํ 5๋ถ ์ด๋ด: ๋ฌ์ฑ)\n",
"์๋ํ ์ฑ๊ณต๋ฅ 99.4% (์ผ์ ์ฅ์ 9๊ฑด์ ์ฌ์๋๋ก ์๋ ๋ณต๊ตฌ, ์์ ์๋ฃ๋ ๊ฒฉ๋ฆฌ ํ ๋ณด๊ณ )\n",
"\n",
"[์
๋ฌด ์์ ๋น๊ต(๊ฐ์ ๊ฐ)] ์์์
12000๋ถ(200์๊ฐ) vs ์๋ํ 1005๋ถ(16.8์๊ฐ) -> ์ ๊ฐ 91.6%\n",
"๋ถํ ์ํ 1431๊ฑด ์ฒ๋ฆฌ 3.63์ด (์ด๋น 394๊ฑด), ์ค๋ฅ 0๊ฑด\n",
"\n",
"๋ชฉํ(์ ์ฒด 5๋ถ ์ด๋ด, ์๋ํ ์ฑ๊ณต๋ฅ 99% ์ด์, ์์์
๋๋น 80% ์ด์ ์ ๊ฐ): ๋ฌ์ฑ\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
09. AAR ๊ธฐ๋ฐ ๊ฒฐ์ฐ ์์น ์ค์ง๊ณ ์ฌ๋ก ๋ถ์ ๋ฐ ์ง๊ณ ๋ฃฐ์
/ํ
ํ๋ฆฟ ๋ณด์ \n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"AAR ๊ธฐ๋ฐ ๊ฒฐ์ฐ ์์น ์ค์ง๊ณ ์ฌ๋ก๋ฅผ ๋ถ์ํ๊ณ ๋ฃฐ์
์ ๋ณด์ ํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **AAR ์ฌ๋ก์ ๊ฒฐ์ฐ ์์น ์ค์ง๊ณ 5์ ํ์ ์ฌํยท๋ถ์ํ๊ณ ์ง๊ณ ๋ฃฐ์
๊ณผ ํ
ํ๋ฆฟ์ ๋ณด์ ํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ค๋ณต ์ ์ถ, ๋จ์ ํผ์ฉ(kL), ์ ์ ๋ฌธ์ ๋๋ฝ, ๋ฐ์คยท๋ฐ ํผ์ฉ, ํ๋ชฉ ๋ณ์นญ ๋๋ฝ์ 5์ ํ์ ๊ฐ์ ์ ์ถ ์๋ฃ์ ์ฌ์ด ๋ฃฐ์
v1์ ์ค์ง๊ณ๋ฅผ ์ฌํ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `docs/dat06_uat_feedback_report.md ๋ฐ ํจ์น ๋ด์ญ` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ค๋ฌด ์
๋ฌด ์์ฐ์ฑ ๊ธฐ์ฌ๋ ๋ฐ ํ์ฅ ์ ์์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: UAT ์ฒดํฌ๋ฆฌ์คํธ ๋ง์กฑ๋ 90์ ์ด์, ๊ฐ์ ์๊ตฌ 5๊ฑด ๋ฐ์ ์๋ฃ\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: UAT ๊ฒฐ๊ณผ ๋ณด๊ณ ์ ์์ฑ ๋ฐ ์ต์ข
๋ฒ๊ทธ ํฝ์ค ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ์ค์ ์
๋ฌด ์๋๋ฆฌ์ค ๊ธฐ๋ฐ ์ต์ข
๊ฒ์ ํ
์คํธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์ฌํ๊ฒํ (AAR) ์ฌ๋ก์์ ๊ฒฐ์ฐ ์์น ์ค์ง๊ณ 5์ ํ(์ค๋ณต ํ, ๋จ์ ํผ์ฉ, ์ ์ ๋๋ฝ, ๋ฐ์คยท๋ฐ ํผ์ฉ, ํ๋ชฉ ๋ณ์นญ ๋๋ฝ)์ ์ฌํํด ์์ธ์ ๋ถ์ํ๊ณ ์ง๊ณ ๋ฃฐ์
v1์ v2๋ก ๋ณด์ ํ ๋ค, ์ฌ์ง๊ณ ๊ฒฐ๊ณผ์ UAT ์ฒดํฌ๋ฆฌ์คํธ ์ ์, ๊ฐ์ ์๊ตฌ ๋ฐ์ ํํฉ, ํจ์น ๋ด์ญ์ ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์๊ฐ ๊ฒฐ์ฐ ์์ฅ ์์ฑ๊ธฐ (์ค์ ๋ถ๋๋ช
ยทํ๋ชฉยท์๋ยท์ฌ๋ฌด ์ ๋ณด ์์)\n",
"# ๊ฐ์ ๋๋ 24๊ฐ x (๋ฌผ์ ๋ถ์ถ / ์๋ฆฌ๋ถ์ ์๋ชจ / ํ์ฝ ์ ์ฐ) ํ๋ชฉ ์์ฅ๊ณผ ์ฅ๋น ๊ฐ๋ ํํฉ์ ๋ง๋ ๋ค.\n",
"# ์ฌ๊ณ ์ฐ์: ๊ธฐ๋ง = ๊ธฐ์ด + ์๋ น + ๋ฐ๋ฉ - ๋ถ์ถ, ๋ค์ ๋ฌ ๊ธฐ์ด = ์ด๋ฒ ๋ฌ ๊ธฐ๋ง.\n",
"# ---------------------------------------------------------------------------\n",
"UNITS = [f\"๊ฐ์{i:02d}๋๋\" for i in range(1, 25)]\n",
"ITEMS = {\"๋ฌผ์๋ถ์ถ\": [\"๊ธ์๋ฅ\", \"ํผ๋ณต๋ฅ\", \"์ ๋ฅ\", \"์ ๋น์๋ชจํ\"],\n",
" \"์๋ฆฌ๋ถ์\": [\"ํํฐ๋ฅ\", \"๋ฒ ์ด๋ง๋ฅ\", \"์ ์ฅํ\", \"ํ์ด์ด๋ฅ\"],\n",
" \"ํ์ฝ์ ์ฐ\": [\"ํ๋ จํA\", \"ํ๋ จํB\", \"๋ชจ์ํ\"]}\n",
"UNIT_CODE = {\"๊ธ์๋ฅ\": \"BX\", \"ํผ๋ณต๋ฅ\": \"EA\", \"์ ๋ฅ\": \"L\", \"์ ๋น์๋ชจํ\": \"EA\", \"ํํฐ๋ฅ\": \"EA\", \"๋ฒ ์ด๋ง๋ฅ\": \"EA\",\n",
" \"์ ์ฅํ\": \"EA\", \"ํ์ด์ด๋ฅ\": \"EA\", \"ํ๋ จํA\": \"RD\", \"ํ๋ จํB\": \"RD\", \"๋ชจ์ํ\": \"RD\"}\n",
"LEDGER_COLS = [\"month\", \"unit\", \"cat\", \"item\", \"open\", \"recv\", \"issue\", \"ret\", \"close\"]\n",
"\n",
"\n",
"def make_ledger(n_months=2, seed=60):\n",
" \"\"\"n_months๊ฐ์ ์์ฅ(list of dict). ๋์งธ ๋ฌ ๊ธฐ์ด๋ ์ฒซ์งธ ๋ฌ ๊ธฐ๋ง๊ณผ ๊ฐ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows, prev = [], {}\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" for cat, items in ITEMS.items():\n",
" for it in items:\n",
" base = int(rng.integers(80, 400))\n",
" op = prev.get((u, it), base * 3)\n",
" recv = int(rng.integers(0, base))\n",
" issue = int(rng.integers(base // 4, base // 2 + 1))\n",
" ret = int(issue * rng.uniform(0, 0.15))\n",
" cl = op + recv + ret - issue\n",
" rows.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"cat\": cat, \"item\": it,\n",
" \"open\": op, \"recv\": recv, \"issue\": issue, \"ret\": ret, \"close\": cl})\n",
" prev[(u, it)] = cl\n",
" return rows\n",
"\n",
"\n",
"def make_equip(n_months=2, seed=61):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" hold = int(rng.integers(20, 60))\n",
" out.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"hold\": hold, \"operable\": int(hold * rng.uniform(0.78, 0.97))})\n",
" return out\n",
"\n",
"# 1) ๊ฐ์ ์ ์ถ ๋ ์ฝ๋ ์์ฑ: ์ ๋ต(์๋ถ์ถ = ๋ถ์ถ - ๋ฐ๋ฉ)๊ณผ ๋ถ๋๊ฐ ์ค์ ๋ก ๋ธ ์ ์ถ ํํ(์ค์ง๊ณ ์ ๋ฐ ์์ธ ํฌํจ)\n",
"led = pd.DataFrame(make_ledger(1))\n",
"rng = np.random.default_rng(9)\n",
"truth = {(r.unit, r.item): r.issue - r.ret for r in led.itertuples()}\n",
"subs, doc = [], 0\n",
"def add(u, it, kind, qty, uom, name=None):\n",
" # [TODO : ํ์ ์ค์ต 9] AAR ๊ธฐ๋ฐ ๊ฒฐ์ฐ ์์น ์ค์ง๊ณ ์ฌ๋ก ๋ถ์ ๋ฐ ์ง๊ณ ๋ฃฐ์
/ํ
ํ๋ฆฟ ๋ณด์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"for r in led.itertuples():\n",
" add(r.unit, r.item, \"issue\", r.issue, UNIT_CODE[r.item]); add(r.unit, r.item, \"return\", r.ret, UNIT_CODE[r.item])\n",
"keys = list(truth)\n",
"pick = lambda n, f=lambda k: True: [keys[i] for i in rng.choice([j for j, k in enumerate(keys) if f(k)], n, replace=False)]\n",
"CASE = {}\n",
"for k in pick(6): # ์ฌ๋ก1: ๊ฐ์ ๋ฌธ์๋ฒํธ๊ฐ ์ค๋ณต ์ ์ถ\n",
" CASE.setdefault(\"์ค๋ณต ํ\", []).append(k); subs.append(dict(next(s for s in subs if (s[\"unit\"], s[\"item\"]) == k and s[\"kind\"] == \"issue\")))\n",
"for k in pick(4, lambda k: k[1] == \"์ ๋ฅ\"): # ์ฌ๋ก2: ์ ๋ฅ๋ฅผ kL ๋จ์๋ก ์ ์ถ(1kL = 1000L)\n",
" CASE.setdefault(\"๋จ์ ํผ์ฉ(kL)\", []).append(k)\n",
" for s in subs:\n",
" if (s[\"unit\"], s[\"item\"]) == k:\n",
" s[\"qty\"], s[\"uom\"] = s[\"qty\"] / 1000, \"kL\"\n",
"for k in pick(5): # ์ฌ๋ก3: ๋ถ์ถ ํ ์ ์ ๋ฌธ์(์์ ์กฐ์ )๋ฅผ ๋ณ๋๋ก ์ ์ถ\n",
" CASE.setdefault(\"์ ์ ๋๋ฝ\", []).append(k); d = int(rng.integers(5, 20))\n",
" next(s for s in subs if (s[\"unit\"], s[\"item\"]) == k and s[\"kind\"] == \"issue\")[\"qty\"] += d\n",
" add(k[0], k[1], \"adjust\", -d, UNIT_CODE[k[1]])\n",
"for k in pick(4, lambda k: k[1].startswith(\"ํ๋ จํ\")): # ์ฌ๋ก4: ํ์ฝ์ ๋ฐ์ค(BX, 1BX=20๋ฐ)๋ก ์ ์ถ\n",
" CASE.setdefault(\"๋ฐ์คยท๋ฐ ํผ์ฉ\", []).append(k)\n",
" for s in subs:\n",
" if (s[\"unit\"], s[\"item\"]) == k:\n",
" s[\"qty\"], s[\"uom\"] = s[\"qty\"] / 20, \"BX\"\n",
"for k in pick(5, lambda k: k[1] == \"ํผ๋ณต๋ฅ\"): # ์ฌ๋ก5: ํ๋ชฉ๋ช
์ ์ค์ฌ์ ์ ์ถ('ํผ๋ณต')\n",
" CASE.setdefault(\"ํ๋ชฉ ๋ณ์นญ ๋๋ฝ\", []).append(k)\n",
" for s in subs:\n",
" if (s[\"unit\"], s[\"item\"]) == k:\n",
" s[\"item\"] = \"ํผ๋ณต\"\n",
"S = pd.DataFrame(subs)\n",
"\n",
"# 2) ์ง๊ณ ๋ฃฐ์
v1(AAR ์ด์ ) / v2(๋ณด์ ํ)\n",
"def aggregate(S, ruleset):\n",
" # [TODO : ํ์ ์ค์ต 9] AAR ๊ธฐ๋ฐ ๊ฒฐ์ฐ ์์น ์ค์ง๊ณ ์ฌ๋ก ๋ถ์ ๋ฐ ์ง๊ณ ๋ฃฐ์
/ํ
ํ๋ฆฟ ๋ณด์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def compare(agg):\n",
" tt = pd.Series(truth)\n",
" tt.index.names = [\"unit\", \"item\"]\n",
" diff = (agg.reindex(tt.index).fillna(0) - tt)\n",
" return diff[diff.abs() > 0.5]\n",
"\n",
"v1, v2 = compare(aggregate(S, \"v1\")), compare(aggregate(S, \"v2\"))\n",
"print(f\"[AAR ์ค์ง๊ณ ๋ถ์] ์ ์ถ ๋ ์ฝ๋ {len(S)}๊ฑด, ์ ๋ต ๋์กฐ ๋์ {len(truth)}๊ฐ (๋ถ๋ x ํ๋ชฉ) [๊ต์ก์ฉ๊ฐ์์๋ฃ]\")\n",
"print(f\"๋ฃฐ์
v1 ์ค์ง๊ณ {len(v1)}๊ฑด(์ค์ฐจ ํฉ๊ณ {v1.abs().sum():.0f}), ๋ฃฐ์
v2 ์ค์ง๊ณ {len(v2)}๊ฑด\")\n",
"print(\"\\n[์ฌ๋ก๋ณ ์์ธ ๋ถ์ - v1]\")\n",
"rows = []\n",
"for name, ks in CASE.items():\n",
" hit = [k for k in ks if k in v1.index]\n",
" fix = [k for k in ks if k not in v2.index]\n",
" err = float(sum(abs(v1[k]) for k in hit))\n",
" rows.append({\"์ฌ๋ก\": name, \"๋ฐ์ ์
\": len(ks), \"v1 ์ค๋ฅ ์
\": len(hit), \"v1 ์ค์ฐจ\": round(err), \"v2 ์ ์\": f\"{len(fix)}/{len(ks)}\"})\n",
"print(pd.DataFrame(rows).to_string(index=False))\n",
"CAUSE = {\"์ค๋ณต ํ\": \"๋ฌธ์๋ฒํธ ์ค๋ณต ์ ๊ฑฐ ๊ท์น ์์\", \"๋จ์ ํผ์ฉ(kL)\": \"๋จ์ ํ์ฐํ ์์\", \"์ ์ ๋๋ฝ\": \"์ ์ (์์ ์กฐ์ ) ๋ฌธ์๋ฅผ ์ง๊ณ์์ ์ ์ธ\",\n",
" \"๋ฐ์คยท๋ฐ ํผ์ฉ\": \"BX->RD ํ์ฐํ ์์\", \"ํ๋ชฉ ๋ณ์นญ ๋๋ฝ\": \"๋ณ์นญ ์ฌ์ ์๊ณ ๋ฏธ๋ฑ๋ก ํ๋ชฉ์ ์กฐ์ฉํ ๋ฒ๋ฆผ\"}\n",
"print(\"\\n[์์ธ -> ๋ฃฐ์
๋ณด์ ]\")\n",
"for k, v in CAUSE.items():\n",
" print(f\" - {k}: {v} -> v2 ํจ์น: \" + {\"์ค๋ณต ํ\": \"doc ๊ธฐ์ค drop_duplicates\", \"๋จ์ ํผ์ฉ(kL)\": \"kL x1000\", \"์ ์ ๋๋ฝ\": \"adjust ํฌํจ ์ง๊ณ\",\n",
" \"๋ฐ์คยท๋ฐ ํผ์ฉ\": \"BX x20\", \"ํ๋ชฉ ๋ณ์นญ ๋๋ฝ\": \"๋ณ์นญ ์ฌ์ + ๋ฏธ๋ฑ๋ก ํ๋ชฉ์ ๋ก๊ทธ๋ก ๋ณด๋ฅ\"}[k])\n",
"\n",
"# 3) ํ๊ท ์ํ ๋ฐ UAT ์ฒดํฌ๋ฆฌ์คํธ(์๋ ์ ๊ฒ 20ํญ๋ชฉ)\n",
"agg2 = aggregate(S, \"v2\")\n",
"checks = [(\"์ ๋ต ๋์กฐ ์ค์ง๊ณ 0๊ฑด\", len(v2) == 0), (\"์ค๋ณต ๋ฌธ์ ์ ๊ฑฐ\", agg2.equals(aggregate(S.drop_duplicates(\"doc\"), \"v2\"))),\n",
" (\"v2 ์ฌ์คํ ๊ฒฐ๊ณผ ๋์ผ(๋ฉฑ๋ฑ)\", aggregate(S, \"v2\").equals(agg2)),\n",
" (\"kL ๋ถ๋ 4๊ณณ ์ ์\", all(k not in v2.index for k in CASE[\"๋จ์ ํผ์ฉ(kL)\"])), (\"BX ๋ถ๋ 4๊ณณ ์ ์\", all(k not in v2.index for k in CASE[\"๋ฐ์คยท๋ฐ ํผ์ฉ\"])),\n",
" (\"์ ์ ๋ฌธ์ 5๊ฑด ๋ฐ์\", all(k not in v2.index for k in CASE[\"์ ์ ๋๋ฝ\"])), (\"๋ณ์นญ 5๊ฑด ๋ฐ์\", all(k not in v2.index for k in CASE[\"ํ๋ชฉ ๋ณ์นญ ๋๋ฝ\"])),\n",
" (\"์ ์ ๋ถ๋ v1 ๊ฒฐ๊ณผ์ ๋ถ๋ณ\", len(set(truth) - set(v1.index) - set(v2.index)) == len(truth) - len(v1)),\n",
" (\"์ ์ฒด ๋ถ์ถ ํฉ๊ณ ์ ๋ต๊ณผ ์ผ์น\", abs(agg2.sum() - sum(truth.values())) < 1), (\"๋ถ๋ 24๊ฐ ๋ชจ๋ ์ง๊ณ\", agg2.index.get_level_values(0).nunique() == 24),\n",
" (\"ํ๋ชฉ 11์ข
๋ชจ๋ ์ง๊ณ\", agg2.index.get_level_values(1).nunique() == 11), (\"์์ ์๋ถ์ถ 0๊ฑด\", (agg2 >= 0).all()),\n",
" (\"๋ฏธ๋ฑ๋ก ํ๋ชฉ ์
๋ ฅ ์ ์ค๋ฅ ์๋ ๋ณด๋ฅ\", aggregate(pd.concat([S, pd.DataFrame([{\"doc\": \"X1\", \"unit\": UNITS[0], \"item\": \"๋ฏธ๋ฑ๋ก\", \"kind\": \"issue\", \"qty\": 5, \"uom\": \"EA\"}])]), \"v2\").shape == agg2.shape),\n",
" (\"๋น ์
๋ ฅ์์๋ ์ค๋ฅ ์์\", aggregate(S.iloc[0:0], \"v2\").empty), (\"์ฒ๋ฆฌ ๊ฒฐ๊ณผ ๋ถ๋xํ๋ชฉ ๋จ์ผ ํค\", agg2.index.is_unique),\n",
" (\"v1 ๋๋น ์ค์ฐจ ๊ฐ์ 100%\", len(v2) == 0 and len(v1) > 0), (\"๋จ์ ํ์ฐ ํ ์์ ์ค์ฐจ ์์\", bool(((agg2 - agg2.round()).abs() < 1e-6).all())),\n",
" (\"์ฌ๋ก 5์ ํ ๋ชจ๋ ์ฌํยท๋ถ์\", len(CASE) == 5), (\"์์ธ-ํจ์น ๋์ํ ์์ฑ\", len(CAUSE) == 5), (\"๊ฐ์ ์๊ตฌ 5๊ฑด ๋ฐ์\", len(CASE) == 5 and len(v2) == 0)]\n",
"passed = sum(ok for _, ok in checks)\n",
"score = 100 * passed / len(checks)\n",
"print(\"\\n[UAT ์ฒดํฌ๋ฆฌ์คํธ]\", f\"{passed}/{len(checks)}ํญ๋ชฉ ํต๊ณผ, ์ ์ {score:.0f}์ \")\n",
"for n, ok in checks:\n",
" if not ok:\n",
" print(\" ๋ฏธํต๊ณผ:\", n)\n",
"print(\"\\n[๊ฐ์ ์๊ตฌ ๋ฐ์ ํํฉ]\")\n",
"for i, (k, ks) in enumerate(CASE.items(), 1):\n",
" print(f\" ์๊ตฌ {i}. {k} ๋์: {'๋ฐ์ ์๋ฃ' if all(x not in v2.index for x in ks) else '๋ฏธ๋ฐ์'}\")\n",
"print(f\"\\n๋ชฉํ(UAT 90์ ์ด์, ๊ฐ์ ์๊ตฌ 5๊ฑด ๋ฐ์): {'๋ฌ์ฑ' if score >= 90 and len(v2) == 0 else '๋ฏธ๋ฌ'}\")\n",
"print(\"ํจ์น ๋ด์ญ: ๋ฃฐ์
v1 -> v2 (์ค๋ณต ์ ๊ฑฐ, ๋จ์ ํ์ฐํ, ์ ์ ๋ฐ์, ๋ณ์นญ ์ฌ์ , ๋ฏธ๋ฑ๋ก ํ๋ชฉ ๋ณด๋ฅ). ์ฌ์ง๊ณ ์ ๋ด๋น์๊ฐ ๋ณด๋ฅ ๊ฑด์ ํ์ธํ๋ค.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
09 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[AAR ์ค์ง๊ณ ๋ถ์] ์ ์ถ ๋ ์ฝ๋ 539๊ฑด, ์ ๋ต ๋์กฐ ๋์ 264๊ฐ (๋ถ๋ x ํ๋ชฉ) [๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"๋ฃฐ์
v1 ์ค์ง๊ณ 24๊ฑด(์ค์ฐจ ํฉ๊ณ 1787), ๋ฃฐ์
v2 ์ค์ง๊ณ 0๊ฑด\n",
"\n",
"[์ฌ๋ก๋ณ ์์ธ ๋ถ์ - v1]\n",
" ์ฌ๋ก ๋ฐ์ ์
v1 ์ค๋ฅ ์
v1 ์ค์ฐจ v2 ์ ์\n",
" ์ค๋ณต ํ 6 6 546 6/6\n",
"๋จ์ ํผ์ฉ(kL) 4 4 241 4/4\n",
" ์ ์ ๋๋ฝ 5 5 71 5/5\n",
" ๋ฐ์คยท๋ฐ ํผ์ฉ 4 4 367 4/4\n",
" ํ๋ชฉ ๋ณ์นญ ๋๋ฝ 5 5 563 5/5\n",
"\n",
"[์์ธ -> ๋ฃฐ์
๋ณด์ ]\n",
" - ์ค๋ณต ํ: ๋ฌธ์๋ฒํธ ์ค๋ณต ์ ๊ฑฐ ๊ท์น ์์ -> v2 ํจ์น: doc ๊ธฐ์ค drop_duplicates\n",
" - ๋จ์ ํผ์ฉ(kL): ๋จ์ ํ์ฐํ ์์ -> v2 ํจ์น: kL x1000\n",
" - ์ ์ ๋๋ฝ: ์ ์ (์์ ์กฐ์ ) ๋ฌธ์๋ฅผ ์ง๊ณ์์ ์ ์ธ -> v2 ํจ์น: adjust ํฌํจ ์ง๊ณ\n",
" - ๋ฐ์คยท๋ฐ ํผ์ฉ: BX->RD ํ์ฐํ ์์ -> v2 ํจ์น: BX x20\n",
" - ํ๋ชฉ ๋ณ์นญ ๋๋ฝ: ๋ณ์นญ ์ฌ์ ์๊ณ ๋ฏธ๋ฑ๋ก ํ๋ชฉ์ ์กฐ์ฉํ ๋ฒ๋ฆผ -> v2 ํจ์น: ๋ณ์นญ ์ฌ์ + ๋ฏธ๋ฑ๋ก ํ๋ชฉ์ ๋ก๊ทธ๋ก ๋ณด๋ฅ\n",
"\n",
"[UAT ์ฒดํฌ๋ฆฌ์คํธ] 20/20ํญ๋ชฉ ํต๊ณผ, ์ ์ 100์ \n",
"\n",
"[๊ฐ์ ์๊ตฌ ๋ฐ์ ํํฉ]\n",
" ์๊ตฌ 1. ์ค๋ณต ํ ๋์: ๋ฐ์ ์๋ฃ\n",
" ์๊ตฌ 2. ๋จ์ ํผ์ฉ(kL) ๋์: ๋ฐ์ ์๋ฃ\n",
" ์๊ตฌ 3. ์ ์ ๋๋ฝ ๋์: ๋ฐ์ ์๋ฃ\n",
" ์๊ตฌ 4. ๋ฐ์คยท๋ฐ ํผ์ฉ ๋์: ๋ฐ์ ์๋ฃ\n",
" ์๊ตฌ 5. ํ๋ชฉ ๋ณ์นญ ๋๋ฝ ๋์: ๋ฐ์ ์๋ฃ\n",
"\n",
"๋ชฉํ(UAT 90์ ์ด์, ๊ฐ์ ์๊ตฌ 5๊ฑด ๋ฐ์): ๋ฌ์ฑ\n",
"ํจ์น ๋ด์ญ: ๋ฃฐ์
v1 -> v2 (์ค๋ณต ์ ๊ฑฐ, ๋จ์ ํ์ฐํ, ์ ์ ๋ฐ์, ๋ณ์นญ ์ฌ์ , ๋ฏธ๋ฑ๋ก ํ๋ชฉ ๋ณด๋ฅ). ์ฌ์ง๊ณ ์ ๋ด๋น์๊ฐ ๋ณด๋ฅ ๊ฑด์ ํ์ธํ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
10. ๊ตฐ์ ์๊ฐ ๊ฒฐ์ฐ ๋ฐ ๋ณด๊ธ ํํฉ ๋ณด๊ณ ์ ์ํด๋ฆญ ์๋ ์์ฑ ๋๊ตฌ ์ต์ข
์์ฑ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ตฐ์ ์๊ฐ ๊ฒฐ์ฐ ๋ฐ ๋ณด๊ธ ํํฉ ๋ณด๊ณ ์ ์ํด๋ฆญ ์๋ ์์ฑ ๋๊ตฌ๋ฅผ ์์ฑํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์์ง๋ถํฐ ๊ฒ์ฆ, ์ง๊ณ, ์ด์ ์ ๊ฒ, ๋ณด๊ณ ์ ์์ฑ, ๊ฒฐ์ฌ ๋๊ธฐ ๋ถ๋ฆฌ๊น์ง ํ ๋ฒ์ ์คํ๋๋ ์๊ฐ ๊ฒฐ์ฐ ์ํด๋ฆญ ๋๊ตฌ๋ฅผ ์์ฑํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **ํ๊ฒฝ ์ ๊ฒ ํ CSVยทJSON ํผํฉ ์๋ฃ๋ฅผ ์์งํ๊ณ ๊ฒ์ฆ ๋ฃฐ๊ณผ ์ง๊ณ, ์ด์ ์ ๊ฒ, ์ ๋ขฐ๋ ์ ์๋ฅผ ๊ฑฐ์ณ 24๊ฐ ๋ถ๋ ๋ณด๊ณ ์ ์์ฑํ์ฌ ์์ธ์ ๋ถ์ํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `dist/dat06_package/, README.md, docs/dat06_final_manual.md` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์์ฉ ์ํํธ์จ์ด ์์ค์ ์์ฑ๋ ๋ฐ ์กฐ์ง ํ์ฐ ๊ฐ์น\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์ค์น๋ถํฐ ์คํ๊น์ง ์ํด๋ฆญ ์ค์น ์คํฌ๋ฆฝํธ ์๋น, ๋งค๋ด์ผ ์์ฑ๋ฅ 100%\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์ต์ข
๋ฐฐํฌ๋ณธ ์์ฑ ๋ฐ ๊ฒ์ ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: Clean ํ๊ฒฝ์์์ ์ค์น ๋ฐ ์ด๊ธฐํ ํ
์คํธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"ํ๊ฒฝ ์ ๊ฒ๋ถํฐ 4ํ์ ์๋ฃ ์์ง, ๊ฒ์ฆ ๋ฃฐ ์ ์ฉ, ์๊ฐ ๊ฒฐ์ฐ ์ง๊ณ, ์ด์์น ์ ๊ฒ, ์ ๋ขฐ๋ ์ ์, 24๊ฐ ๋ถ๋ ๋ณด๊ณ ์ ์์ฑ, 3๋จ๊ณ ๊ฒฐ์ฌ ๋๊ธฐ ๋ถ๋ฆฌ๊น์ง ํ ๋ฒ์ ์คํํ๋ ์ํด๋ฆญ ๊ฒฐ์ฐ ๋๊ตฌ๋ฅผ ์์ฑํ๊ณ ์ค์น ์คํฌ๋ฆฝํธ ๊ฐ์ยท๋งค๋ด์ผ ๋ชฉ์ฐจยท์์ฒด ์ ๊ฒยท๋ธ๋ฆฌํ ์์ฝ์ ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import csv\n",
"import importlib.util\n",
"import io\n",
"import json\n",
"import sys\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ฐ์ ์๊ฐ ๊ฒฐ์ฐ ์์ฅ ์์ฑ๊ธฐ (์ค์ ๋ถ๋๋ช
ยทํ๋ชฉยท์๋ยท์ฌ๋ฌด ์ ๋ณด ์์)\n",
"# ๊ฐ์ ๋๋ 24๊ฐ x (๋ฌผ์ ๋ถ์ถ / ์๋ฆฌ๋ถ์ ์๋ชจ / ํ์ฝ ์ ์ฐ) ํ๋ชฉ ์์ฅ๊ณผ ์ฅ๋น ๊ฐ๋ ํํฉ์ ๋ง๋ ๋ค.\n",
"# ์ฌ๊ณ ์ฐ์: ๊ธฐ๋ง = ๊ธฐ์ด + ์๋ น + ๋ฐ๋ฉ - ๋ถ์ถ, ๋ค์ ๋ฌ ๊ธฐ์ด = ์ด๋ฒ ๋ฌ ๊ธฐ๋ง.\n",
"# ---------------------------------------------------------------------------\n",
"UNITS = [f\"๊ฐ์{i:02d}๋๋\" for i in range(1, 25)]\n",
"ITEMS = {\"๋ฌผ์๋ถ์ถ\": [\"๊ธ์๋ฅ\", \"ํผ๋ณต๋ฅ\", \"์ ๋ฅ\", \"์ ๋น์๋ชจํ\"],\n",
" \"์๋ฆฌ๋ถ์\": [\"ํํฐ๋ฅ\", \"๋ฒ ์ด๋ง๋ฅ\", \"์ ์ฅํ\", \"ํ์ด์ด๋ฅ\"],\n",
" \"ํ์ฝ์ ์ฐ\": [\"ํ๋ จํA\", \"ํ๋ จํB\", \"๋ชจ์ํ\"]}\n",
"UNIT_CODE = {\"๊ธ์๋ฅ\": \"BX\", \"ํผ๋ณต๋ฅ\": \"EA\", \"์ ๋ฅ\": \"L\", \"์ ๋น์๋ชจํ\": \"EA\", \"ํํฐ๋ฅ\": \"EA\", \"๋ฒ ์ด๋ง๋ฅ\": \"EA\",\n",
" \"์ ์ฅํ\": \"EA\", \"ํ์ด์ด๋ฅ\": \"EA\", \"ํ๋ จํA\": \"RD\", \"ํ๋ จํB\": \"RD\", \"๋ชจ์ํ\": \"RD\"}\n",
"LEDGER_COLS = [\"month\", \"unit\", \"cat\", \"item\", \"open\", \"recv\", \"issue\", \"ret\", \"close\"]\n",
"\n",
"\n",
"def make_ledger(n_months=2, seed=60):\n",
" \"\"\"n_months๊ฐ์ ์์ฅ(list of dict). ๋์งธ ๋ฌ ๊ธฐ์ด๋ ์ฒซ์งธ ๋ฌ ๊ธฐ๋ง๊ณผ ๊ฐ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows, prev = [], {}\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" for cat, items in ITEMS.items():\n",
" for it in items:\n",
" base = int(rng.integers(80, 400))\n",
" op = prev.get((u, it), base * 3)\n",
" recv = int(rng.integers(0, base))\n",
" issue = int(rng.integers(base // 4, base // 2 + 1))\n",
" ret = int(issue * rng.uniform(0, 0.15))\n",
" cl = op + recv + ret - issue\n",
" rows.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"cat\": cat, \"item\": it,\n",
" \"open\": op, \"recv\": recv, \"issue\": issue, \"ret\": ret, \"close\": cl})\n",
" prev[(u, it)] = cl\n",
" return rows\n",
"\n",
"\n",
"def make_equip(n_months=2, seed=61):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for m in range(n_months):\n",
" for u in UNITS:\n",
" hold = int(rng.integers(20, 60))\n",
" out.append({\"month\": f\"2026-{m + 7:02d}\", \"unit\": u, \"hold\": hold, \"operable\": int(hold * rng.uniform(0.78, 0.97))})\n",
" return out\n",
"\n",
"LOG = []\n",
"def stage(name):\n",
" # [TODO : ํ์ ์ค์ต 10] ๊ตฐ์ ์๊ฐ ๊ฒฐ์ฐ ๋ฐ ๋ณด๊ธ ํํฉ ๋ณด๊ณ ์ ์ํด๋ฆญ ์๋ ์์ฑ ๋๊ตฌ ์ต์ข
์์ฑ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"INSTALL = [\"1. ๊ฐ์ ํ๊ฒฝ ์์ฑ ๋ฐ ํ์ ๋ชจ๋ ์ค์น(numpy, pandas, matplotlib)\", \"2. ํ๊ฒฝ ์ ๊ฒ: ๋ชจ๋ยท๋ฒ์ ํ์ธ\", \"3. ์ํ ์๋ฃ๋ก ์ํ ์คํ\", \"4. ์คํ: run_monthly_close()\", \"5. ๊ฒฐ๊ณผ ํ์ธ: ๋ณด๊ณ ์ 24๊ฑด, ๊ฒฐ์ฌ ๋๊ธฐ ๋ชฉ๋ก\"]\n",
"mods = [\"numpy\", \"pandas\", \"matplotlib\", \"csv\", \"json\"]\n",
"env_ok = sum(importlib.util.find_spec(m) is not None for m in mods)\n",
"print(f\"[0] ํ๊ฒฝ ์ ๊ฒ: Python {sys.version_info.major}.{sys.version_info.minor}, ํ์ ๋ชจ๋ {env_ok}/{len(mods)} ํ์ธ [๊ต์ก์ฉ๊ฐ์์๋ฃ]\")\n",
"\n",
"# ---------------- 1) ์์ง(CSV + JSON ๋ ํ์ ํผํฉ) ----------------\n",
"@stage(\"์์งยทํ์ฑ\")\n",
"def collect():\n",
" # [TODO : ํ์ ์ค์ต 10] ๊ตฐ์ ์๊ฐ ๊ฒฐ์ฐ ๋ฐ ๋ณด๊ธ ํํฉ ๋ณด๊ณ ์ ์ํด๋ฆญ ์๋ ์์ฑ ๋๊ตฌ ์ต์ข
์์ฑ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"RULES = [(\"V1 ์ฐ์\", lambda d: d[\"open\"] + d[\"recv\"] + d[\"ret\"] - d[\"issue\"] == d[\"close\"]), (\"V2 ์์\", lambda d: (d[[\"open\", \"recv\", \"issue\", \"ret\", \"close\"]] >= 0).all(axis=1)),\n",
" (\"V3 ๊ฐ์ฉ๋\", lambda d: d[\"issue\"] <= d[\"open\"] + d[\"recv\"]), (\"V4 ๋ฐ๋ฉ\", lambda d: d[\"ret\"] <= d[\"issue\"]), (\"V5 ๋ถ๋\", lambda d: d[\"unit\"].isin(UNITS)),\n",
" (\"V6 ํ๋ชฉ\", lambda d: d[\"item\"].isin(UNIT_CODE))]\n",
"@stage(\"๊ฒ์ฆ\")\n",
"def check(df):\n",
" bad = pd.Series(False, index=df.index)\n",
" for _, f in RULES:\n",
" bad |= ~f(df)\n",
" return int(bad.sum())\n",
"\n",
"@stage(\"์ง๊ณ\")\n",
"def aggregate(df):\n",
" return df.groupby([\"unit\", \"cat\"])[[\"issue\", \"ret\", \"close\"]].sum().unstack(\"cat\")\n",
"\n",
"@stage(\"์ด์ ์ ๊ฒ\")\n",
"def anomalies(df):\n",
" prev = pd.DataFrame([r for r in make_ledger(2) if r[\"month\"] == \"2026-07\"]).set_index([\"unit\", \"item\"])[\"issue\"]\n",
" ratio = df.set_index([\"unit\", \"item\"])[\"issue\"] / prev.clip(lower=1)\n",
" return ratio[ratio >= 4.0]\n",
"\n",
"@stage(\"์ ๋ขฐ๋\")\n",
"def reliability(df, nbad):\n",
" return round(100 - 100 * nbad / len(df), 1)\n",
"\n",
"@stage(\"๋ณด๊ณ ์ ์์ฑ\")\n",
"def build(df, agg):\n",
" out = {}\n",
" for u in UNITS:\n",
" g = df[df[\"unit\"] == u]\n",
" out[u] = {\"unit\": u, \"issue\": int(g[\"issue\"].sum()), \"close\": int(g[\"close\"].sum()), \"status\": \"๊ฒฐ์ฌ ๋๊ธฐ(๊ธฐ์)\", \"published\": False}\n",
" return out\n",
"\n",
"df = collect()\n",
"nbad = check(df)\n",
"agg = aggregate(df)\n",
"anom = anomalies(df)\n",
"rel = reliability(df, nbad)\n",
"reports = build(df, agg)\n",
"\n",
"# ---------------- 3) ๊ฒฐ๊ณผ ----------------\n",
"print(f\"[1] ์์ง {len(df)}ํ(CSV+JSON ํผํฉ), ๊ฒ์ฆ ์๋ฐ {nbad}๊ฑด, ์ด์ ๊ธ์ฆ ํ๋ณด {len(anom)}๊ฑด, ์๋ฃ ์ ๋ขฐ๋ {rel}์ \")\n",
"tbl = pd.DataFrame(reports).T[[\"issue\", \"close\", \"status\"]].head(5)\n",
"print(\"[2] ๋ถ๋๋ณ ๊ฒฐ์ฐ ๋ณด๊ณ ์(์์ 5๊ฐ ๋ถ๋) - ๋ชจ๋ ๋ณด๊ณ ์๋ ๊ฒฐ์ฌ ๋๊ธฐ ์ํ\")\n",
"print(tbl.to_string())\n",
"tot = agg[\"issue\"].sum()\n",
"print(\"[3] ๋ถ๋ฅ๋ณ ๋ถ์ถยท์๋ชจ ํฉ๊ณ:\", tot.astype(int).to_dict())\n",
"print(\"[4] ๋จ๊ณ๋ณ ์คํ ๋ก๊ทธ:\")\n",
"for n, st, t in LOG:\n",
" print(f\" - {n:<8} {st:<5} {t * 1000:.1f}ms\")\n",
"run_ms = sum(t for _, _, t in LOG) * 1000\n",
"\n",
"# ---------------- 4) ์์ฒด ์ ๊ฒ ----------------\n",
"MANUAL = [\"1. ๊ฐ์ยท์ค์น\", \"2. ์
๋ ฅ ์๋ฃ ์์\", \"3. ์คํ ๋ฐฉ๋ฒ(์ํด๋ฆญ)\", \"4. ๊ฒ์ฆ ๋ฃฐ ๋ชฉ๋ก\", \"5. ์ด์ ํญ๋ชฉ ์ฒ๋ฆฌ\", \"6. ๊ฒฐ์ฌ์ ยท๊ถํ\", \"7. ์ค๋ฅ ๋ฉ์์ง ์กฐ์น\", \"8. ๋ฐฐ์น ์ด์\", \"9. ๊ฐ์ ์ด๋ ฅยท๋ฌธ์\"]\n",
"done_sec = len(MANUAL)\n",
"checks = [(\"ํ๊ฒฝ ์ ๊ฒ 5/5\", env_ok == len(mods)), (\"๋ชจ๋ ๋จ๊ณ OK\", all(s == \"OK\" for _, s, _ in LOG)), (\"24๊ฐ ๋ถ๋ ๋ณด๊ณ ์ ์์ฑ\", len(reports) == 24),\n",
" (\"์๋ ๋ฐ๊ฐ 0๊ฑด\", not any(r[\"published\"] for r in reports.values())), (\"์์ฅ ์ฐ์ ์๋ฐ 0๊ฑด\", nbad == 0),\n",
" (\"์คํ ์๊ฐ 1๋ถ ์ด๋ด\", run_ms < 60000), (\"๋งค๋ด์ผ 9์ฅ ์์ฑ(์์ฑ๋ฅ 100%)\", done_sec == len(MANUAL)),\n",
" (\"์ค์น ์ ์ฐจ 5๋จ๊ณ ์๋น\", len(INSTALL) == 5)]\n",
"print(\"\\n[5] ์์ฒด ์ ๊ฒ\")\n",
"for n, ok in checks:\n",
" print(f\" {'PASS' if ok else 'FAIL'} {n}\")\n",
"npass = sum(ok for _, ok in checks)\n",
"print(\"์ค์น ์คํฌ๋ฆฝํธ ๊ฐ์:\", \" / \".join(INSTALL))\n",
"print(\"๋งค๋ด์ผ ๋ชฉ์ฐจ:\", \" | \".join(MANUAL))\n",
"asis, tobe = 610, 58\n",
"print(f\"\\n[๋ธ๋ฆฌํ ์์ฝ] 24๊ฐ ๋ถ๋ ๊ฒฐ์ฐ ๋ณด๊ณ ์๋ฅผ {run_ms:.0f}ms์ ์๋ ์์ฑ(์์์
๊ฐ์ {asis}๋ถ -> ๊ฒํ ํฌํจ {tobe}๋ถ, {1 - tobe / asis:.0%} ์ ๊ฐ ๊ฐ์ ๊ฐ).\")\n",
"print(f\"์๋ฃ ์ ๋ขฐ๋ {rel}์ , ์ด์ ๊ธ์ฆ ํ๋ณด {len(anom)}๊ฑด์ ๋ด๋น์ ํ์ธ ๋์, ๋ณด๊ณ ์ 24๊ฑด ์ ๋ถ ๋ณด๊ธ๊ด -> ๊ตฐ์๊ณผ์ฅ ๊ฒฐ์ฌ ๋๊ธฐ(์๋ ๋ฐ๊ฐ ์์).\")\n",
"print(f\"์์ฒด ์ ๊ฒ {npass}/{len(checks)} PASS. ๋ชฉํ(์ํด๋ฆญ ์ค์นยท์คํ ์๋น, ๋งค๋ด์ผ 100%): {'๋ฌ์ฑ' if npass == len(checks) else '๋ฏธ๋ฌ'}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
10 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[0] ํ๊ฒฝ ์ ๊ฒ: Python 3.11, ํ์ ๋ชจ๋ 5/5 ํ์ธ [๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"[1] ์์ง 264ํ(CSV+JSON ํผํฉ), ๊ฒ์ฆ ์๋ฐ 0๊ฑด, ์ด์ ๊ธ์ฆ ํ๋ณด 9๊ฑด, ์๋ฃ ์ ๋ขฐ๋ 100.0์ \n",
"[2] ๋ถ๋๋ณ ๊ฒฐ์ฐ ๋ณด๊ณ ์(์์ 5๊ฐ ๋ถ๋) - ๋ชจ๋ ๋ณด๊ณ ์๋ ๊ฒฐ์ฌ ๋๊ธฐ ์ํ\n",
" issue close status\n",
"๊ฐ์01๋๋ 1147 8915 ๊ฒฐ์ฌ ๋๊ธฐ(๊ธฐ์)\n",
"๊ฐ์02๋๋ 916 9139 ๊ฒฐ์ฌ ๋๊ธฐ(๊ธฐ์)\n",
"๊ฐ์03๋๋ 778 7428 ๊ฒฐ์ฌ ๋๊ธฐ(๊ธฐ์)\n",
"๊ฐ์04๋๋ 882 8357 ๊ฒฐ์ฌ ๋๊ธฐ(๊ธฐ์)\n",
"๊ฐ์05๋๋ 920 7718 ๊ฒฐ์ฌ ๋๊ธฐ(๊ธฐ์)\n",
"[3] ๋ถ๋ฅ๋ณ ๋ถ์ถยท์๋ชจ ํฉ๊ณ: {'๋ฌผ์๋ถ์ถ': 8270, '์๋ฆฌ๋ถ์': 8184, 'ํ์ฝ์ ์ฐ': 6743}\n",
"[4] ๋จ๊ณ๋ณ ์คํ ๋ก๊ทธ:\n",
" - ์์งยทํ์ฑ OK 19.0ms\n",
" - ๊ฒ์ฆ OK 8.2ms\n",
" - ์ง๊ณ OK 13.2ms\n",
" - ์ด์ ์ ๊ฒ OK 23.8ms\n",
" - ์ ๋ขฐ๋ OK 0.0ms\n",
" - ๋ณด๊ณ ์ ์์ฑ OK 33.2ms\n",
"\n",
"[5] ์์ฒด ์ ๊ฒ\n",
" PASS ํ๊ฒฝ ์ ๊ฒ 5/5\n",
" PASS ๋ชจ๋ ๋จ๊ณ OK\n",
" PASS 24๊ฐ ๋ถ๋ ๋ณด๊ณ ์ ์์ฑ\n",
" PASS ์๋ ๋ฐ๊ฐ 0๊ฑด\n",
" PASS ์์ฅ ์ฐ์ ์๋ฐ 0๊ฑด\n",
" PASS ์คํ ์๊ฐ 1๋ถ ์ด๋ด\n",
" PASS ๋งค๋ด์ผ 9์ฅ ์์ฑ(์์ฑ๋ฅ 100%)\n",
" PASS ์ค์น ์ ์ฐจ 5๋จ๊ณ ์๋น\n",
"์ค์น ์คํฌ๋ฆฝํธ ๊ฐ์: 1. ๊ฐ์ ํ๊ฒฝ ์์ฑ ๋ฐ ํ์ ๋ชจ๋ ์ค์น(numpy, pandas, matplotlib) / 2. ํ๊ฒฝ ์ ๊ฒ: ๋ชจ๋ยท๋ฒ์ ํ์ธ / 3. ์ํ ์๋ฃ๋ก ์ํ ์คํ / 4. ์คํ: run_monthly_close() / 5. ๊ฒฐ๊ณผ ํ์ธ: ๋ณด๊ณ ์ 24๊ฑด, ๊ฒฐ์ฌ ๋๊ธฐ ๋ชฉ๋ก\n",
"๋งค๋ด์ผ ๋ชฉ์ฐจ: 1. ๊ฐ์ยท์ค์น | 2. ์
๋ ฅ ์๋ฃ ์์ | 3. ์คํ ๋ฐฉ๋ฒ(์ํด๋ฆญ) | 4. ๊ฒ์ฆ ๋ฃฐ ๋ชฉ๋ก | 5. ์ด์ ํญ๋ชฉ ์ฒ๋ฆฌ | 6. ๊ฒฐ์ฌ์ ยท๊ถํ | 7. ์ค๋ฅ ๋ฉ์์ง ์กฐ์น | 8. ๋ฐฐ์น ์ด์ | 9. ๊ฐ์ ์ด๋ ฅยท๋ฌธ์\n",
"\n",
"[๋ธ๋ฆฌํ ์์ฝ] 24๊ฐ ๋ถ๋ ๊ฒฐ์ฐ ๋ณด๊ณ ์๋ฅผ 97ms์ ์๋ ์์ฑ(์์์
๊ฐ์ 610๋ถ -> ๊ฒํ ํฌํจ 58๋ถ, 90% ์ ๊ฐ ๊ฐ์ ๊ฐ).\n",
"์๋ฃ ์ ๋ขฐ๋ 100.0์ , ์ด์ ๊ธ์ฆ ํ๋ณด 9๊ฑด์ ๋ด๋น์ ํ์ธ ๋์, ๋ณด๊ณ ์ 24๊ฑด ์ ๋ถ ๋ณด๊ธ๊ด -> ๊ตฐ์๊ณผ์ฅ ๊ฒฐ์ฌ ๋๊ธฐ(์๋ ๋ฐ๊ฐ ์์).\n",
"์์ฒด ์ ๊ฒ 8/8 PASS. ๋ชฉํ(์ํด๋ฆญ ์ค์นยท์คํ ์๋น, ๋งค๋ด์ผ 100%): ๋ฌ์ฑ\n",
"```\n",
" \n"
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}