{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ๐ŸŽฏ [PRC01] AI ๊ธฐ๋ฐ˜ ์กฐ๋‹ฌ๊ณ„ํš์•ˆ ๊ฒ€์ฆ Tool\n", "### Google Colab ์‹ค์Šต ํ™˜๊ฒฝ (์กฐ๋‹ฌยท๊ณ„์•ฝยท๊ตญ์‚ฐํ™” / Level 3)\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/PRC01/PRC01_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", "์กฐ๋‹ฌ๊ณ„ํš์•ˆ๊ณผ ๊ณ„์•ฝ์„œ๋Š” ์ง€์ฒด์ƒ๊ธˆ๋ฅ , ์„ ๊ธˆ, ๋ณด์ฆ, ํ•˜๋„๊ธ‰, ํ•„์ˆ˜ ์กฐํ•ญ ๊ฐ™์€ ํ•ญ๋ชฉ์ด ๊ทœ์ •์— ๋งž๋Š”์ง€ ์‚ฌ๋žŒ์ด ํ•œ ๊ฑด์”ฉ ์ฝ๊ณ  ๋Œ€์กฐํ•ด์•ผ ํ•ด์„œ ๊ฒ€ํ† ๊ฐ€ ๋Šฆ์–ด์ง€๊ณ  ๋ถˆ๊ณต์ • ์กฐํ•ญ์ด๋‚˜ ๋‚ฉ๊ธฐ ์ง€์ฒด ์œ„ํ—˜ ๋ฌธ๊ตฌ๋ฅผ ๋†“์น˜๊ธฐ ์‰ฝ๋‹ค. ๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ์กฐ๋‹ฌ๊ณ„ํš์•ˆ๊ณผ ๊ณ„์•ฝ์„œ์˜ ์˜ค๋ฅ˜์™€ ์œ„ํ—˜์„ ๊ฒ€์ฆํ•˜๋Š” ๋„๊ตฌ๋ฅผ ๋งŒ๋“œ๋Š” ๊ณผ์ œ๋กœ, ์กฐ๋‹ฌ ์—…๋ฌด์˜ ๋ณ‘๋ชฉ๊ณผ ์œ„ํ—˜ ์‹ ํ˜ธ ๊ธฐ์ค€, ์กฐํ•ญ ์ˆ˜์ • ๋Œ€์•ˆ, ๊ณ„์•ฝ ์˜๋ขฐ ๊ทผ๊ฑฐ์™€ ๋ณ€๊ฒฝ ์ด๋ ฅ, ์—…๋ฌด ํšจ๊ณผ ์ธก์ •๊นŒ์ง€ ํ•œ ํ๋ฆ„์œผ๋กœ ๊ตฌํ˜„ํ•œ๋‹ค. ๊ต์œก์šฉ ๊ฐ€์ƒ ๊ณ„์•ฝ ์ž๋ฃŒ๋กœ ๊ณ„์•ฝ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ ์ •์˜, ์„œ์‹๋ณ„ ์ž๋ฃŒ ์ •์ œ์™€ ์กฐํ•ญ ์ •๊ทœํ™”, ๋ฃฐ ๊ธฐ๋ฐ˜ ๊ทœ์ • ์ค€์ˆ˜ ํŒ๋ณ„, ์ž์—ฐ์–ด์ฒ˜๋ฆฌ ๊ธฐ๋ฐ˜ ๋…์†Œยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ ์‹๋ณ„, ์œ„ํ—˜ ์ ์ˆ˜์™€ ์‹ ๋ขฐ๋„ ์—”์ง„, ์กฐํ•ญ ํ•˜์ด๋ผ์ดํŠธ ๋Œ€์‹œ๋ณด๋“œ์™€ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ, ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด ๊ฒ€ํ†  HITL ์›Œํฌํ”Œ๋กœ์šฐ, 1,000๊ฑด ์ผ๊ด„ ์Šคํฌ๋ฆฌ๋‹ ๋ถ€ํ•˜ ์‹œํ—˜, 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", "๊ตญ๋ฐฉ ์กฐ๋‹ฌ ๊ณ„์•ฝ์„œ, ์ž…์ฐฐ ๊ณต๊ณ ๋ฌธ ๋ฐ ๋ฐฉ์œ„์‚ฌ์—…๋ฒ• ๊ทœ์ • ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ๋ถ„์„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์กฐ๋‹ฌ๊ณ„ํš์•ˆ๊ณผ ๊ณ„์•ฝ์„œ(๊ณ„์•ฝ ๋ฐฉ์‹ยท๊ธˆ์•กยท๊ธฐ๊ฐ„ยท๋Œ€๊ธˆยท๋ณด์ฆ ์กฐ๊ฑด, ํ•„์ˆ˜ ์กฐํ•ญ ์œ ๋ฌด)์˜ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์™€ ์ž…์ถœ๋ ฅ ๋ช…์„ธ๋ฅผ ์ •์˜ํ•˜๊ณ , ํ˜„ํ–‰(AS-IS) ์กฐ๋‹ฌ๊ณ„ํš์•ˆ ๊ฒ€์ฆ ์—…๋ฌด๋ฅผ ๋‹จ๊ณ„๋กœ ๋‚˜๋ˆ„์–ด ๋ณ‘๋ชฉ์„ ์ฐพ๋Š”๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ณ„์•ฝ ์‹๋ณ„, ๊ณ„์•ฝ ๋ฐฉ์‹ยท๊ธˆ์•กยท๊ธฐ๊ฐ„, ์ง€์ฒด์ƒ๊ธˆ๋ฅ ยท์„ ๊ธˆยท์ดํ–‰๋ณด์ฆยทํ•˜์ž๋ณด์ฆ, ํ•˜๋„๊ธ‰ ๋น„์œจ, ํ•„์ˆ˜ ์กฐํ•ญ ์œ ๋ฌด๋ฅผ ๋ฌถ์–ด ํ•„๋“œ 16๊ฐœ์˜ ์Šคํ‚ค๋งˆ์™€ ๊ฒ€์ฆ ๊ทœ์น™ ์ž‘์„ฑํ•˜์—ฌ ์›์ธ์„ ๋ถ„์„ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/prc01_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": [ "\"\"\"๊ฐ€์ƒ ์กฐ๋‹ฌ๊ณ„ํš์•ˆยท๊ณ„์•ฝ์„œ ์ž๋ฃŒ(๊ณ„์•ฝ ์†์„ฑ 16๊ฐœ + ์กฐํ•ญ ๋ฌธ์žฅ)์˜ ์Šคํ‚ค๋งˆ์™€ ๊ฒ€์ฆ ๊ทœ์น™์„ ์ •์˜ํ•˜๊ณ  ์˜ค๋ฅ˜ ์ฃผ์ž… ์‹œํ—˜์œผ๋กœ ๊ฒ€์ถœ๋ ฅ์„ ํ™•์ธํ•œ ๋’ค, ํ˜„ํ–‰(AS-IS) ์กฐ๋‹ฌ๊ณ„ํš์•ˆ ๊ฒ€์ฆ ์—…๋ฌด๋ฅผ 12๋‹จ๊ณ„๋กœ ๋ถ„ํ•ดํ•ด ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„๊ณผ ๊ฐœ์„ (TO-BE) ํšจ๊ณผ๋ฅผ ๋น„๊ตํ•œ๋‹ค.\"\"\"\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์กฐ๋‹ฌ๊ณ„ํš์•ˆยท๊ณ„์•ฝ์„œ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ณ„์•ฝยท์—…์ฒดยท๋ฒ•๋ น ์กฐ๋ฌธ ์—†์Œ)\n", "# ๊ณ„์•ฝ ์†์„ฑ 16๊ฐœ + ์กฐํ•ญ ๋ฌธ์žฅ 6๊ฐœ๋ฅผ ๋งŒ๋“ค๊ณ , ์†์„ฑ ์œ„๋ฐ˜ยท๋…์†Œ ์กฐํ•ญยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ์„ž์ธ '๊ทœ์ • ์œ„๋ฐ˜ ์—ฌ๋ถ€'๋ฅผ ํ•จ๊ป˜ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "TYPES = [\"๋ฌผํ’ˆ\", \"์šฉ์—ญ\", \"๊ณต์‚ฌ\"]\n", "METHODS = [\"์ผ๋ฐ˜๊ฒฝ์Ÿ\", \"์ œํ•œ๊ฒฝ์Ÿ\", \"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]\n", "CLASSES = [\"์ •์ƒ\", \"๋…์†Œ\", \"๋‚ฉ๊ธฐ์œ„ํ—˜\"]\n", "OBJ = [\"๋‚ฉํ’ˆ ๋ฌผํ’ˆ\", \"์šฉ์—ญ ๊ฒฐ๊ณผ๋ฌผ\", \"์‹œ๊ณต ๋ชฉ์ ๋ฌผ\", \"์ •๋น„ ์„ฑ๊ณผ๋ฌผ\"]\n", "\n", "N_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋‚ฉ๊ธฐ๋ฅผ ์ง€์ฒดํ•œ ๊ฒฝ์šฐ ์ง€์ฒด์ผ์ˆ˜ 1์ผ๋งˆ๋‹ค ๊ณ„์•ฝ๊ธˆ์•ก์˜ {p}/1000์— ํ•ด๋‹นํ•˜๋Š” ์ง€์ฒด์ƒ๊ธˆ์„ ๋ถ€๊ณผํ•œ๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๊ฐ€ ํ™•์ธ๋œ ๊ฒฝ์šฐ์— ํ•œํ•˜์—ฌ ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์Šน์ธ์„ ๋ฐ›์•„ ๋ฉด์ œํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ ์—ฐ์žฅ์€ ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๋ฅผ ํ™•์ธํ•˜๊ณ  ์„œ๋ฉด์œผ๋กœ ์Šน์ธํ•œ ๊ฒฝ์šฐ์—๋งŒ ์ธ์ •ํ•œ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋‚ฉํ’ˆ๋œ {o}์„ {d}์ผ ์ด๋‚ด์— ๊ฒ€์‚ฌํ•˜๊ณ  ํ•ฉ๊ฒฉํ•œ ํ›„์— ๋Œ€๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ํ•˜๋„๊ธ‰์„ ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ์‚ฌ์ „์— ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์„œ๋ฉด ์Šน์ธ์„ ๋ฐ›์•„์•ผ ํ•œ๋‹ค.\",\n", " \"์–ด๋А ํ•œ์ชฝ์ด ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•˜๋ ค๋ฉด ์‚ฌ์œ ๋ฅผ ๋ช…์‹œํ•ด {d}์ผ ์ „์— ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"{o}์— ํ•˜์ž๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ๋ณด์ฆ ๊ธฐ๊ฐ„ ์•ˆ์— ๋ฌด์ƒ์œผ๋กœ ๋ณด์ˆ˜ํ•˜๊ฑฐ๋‚˜ ๊ต์ฒดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ๋น„๊ณต๊ฐœ ์‚ฌํ•ญ์€ ์ œ3์ž์—๊ฒŒ ๋ˆ„์„คํ•˜์ง€ ์•Š์œผ๋ฉฐ ์œ„๋ฐ˜ ์‹œ ์ฑ…์ž„์„ ์ง„๋‹ค.\",\n", " \"{o}์— ๋Œ€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์˜ ๊ท€์†๊ณผ ์‚ฌ์šฉ ๋ฒ”์œ„๋Š” ๋ณ„๋„ ํ•ฉ์˜์„œ์— ๋”ฐ๋ผ ์ •ํ•œ๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ํ•ฉ๊ฒฉ ํ›„ ์ฒญ๊ตฌ์„œ๋ฅผ ์ ‘์ˆ˜ํ•œ ๋‚ ๋ถ€ํ„ฐ {d}์ผ ์ด๋‚ด์— ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ๊ธˆ์•ก์˜ ์กฐ์ •์€ ๋ฌผ๊ฐ€๋ณ€๋™ ๊ธฐ์ค€์— ๋”ฐ๋ผ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ์„œ๋ฉด์œผ๋กœ ํ•ฉ์˜ํ•œ ๊ฒฝ์šฐ์—๋งŒ ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ดํ–‰๋ณด์ฆ์„œ๋ฅผ ๊ณ„์•ฝ ์ฒด๊ฒฐ ์‹œ ์ œ์ถœํ•˜๋ฉฐ ๋ณด์ฆ๊ธˆ์€ ๊ณ„์•ฝ ์ดํ–‰ ์™„๋ฃŒ ํ›„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ณ„์•ฝ์„œ ๋ณ„ํ‘œ์˜ ์ผ์ž๋ฅผ ๋”ฐ๋ฅด๋ฉฐ ๋ณ€๊ฒฝ ์‹œ ์„œ๋ฉด ํ•ฉ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผํ•˜๋ฉฐ ์ด์˜๋Š” ์ •ํ•ด์ง„ ์ ˆ์ฐจ๋กœ ์ œ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•˜์ง€ ๋ชปํ•œ {o}์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๊ต์ฒดํ•˜๋ฉฐ ๊ทธ ๋น„์šฉ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ๊ท€์ฑ… ์‚ฌ์œ ๋กœ ์ดํ–‰์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•˜๊ณ  ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"์ง€์—ฐ์ด ์˜ˆ์ƒ๋˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ๋ฐœ์ฃผ๊ธฐ๊ด€์— ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•œ๋‹ค.\",\n", " \"๋ณด์ฆ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง€๊ณ  ๋ณด์ˆ˜ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์€ ๋จผ์ € ํ˜‘์˜๋กœ ํ•ด๊ฒฐํ•˜๊ณ  ํ˜‘์˜๊ฐ€ ๋˜์ง€ ์•Š์œผ๋ฉด ๊ด€ํ•  ๋ฒ•์›์˜ ํŒ๋‹จ์— ๋”ฐ๋ฅธ๋‹ค.\",\n", "]\n", "T_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ง€์ฒด์ƒ๊ธˆ์€ ์ „์•ก ๋ฉด์ œํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋Œ€ํ•ด ์ด์˜๋ฅผ ์ œ๊ธฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ฆ‰์‹œ ๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ ๋ณด๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ดํ›„ ๊ฒ€์‚ฌ๋ฅผ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์–ธ์ œ๋“ ์ง€ ์ผ๋ฐฉ์ ์œผ๋กœ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ์— ๋”ฐ๋ฅธ ์†ํ•ด๋Š” ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"{o}์˜ ์ถ”๊ฐ€ ๋น„์šฉ์€ ์‚ฌ์œ ๋ฅผ ๋ถˆ๋ฌธํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ์ „์•ก ๋ถ€๋‹ดํ•˜๋ฉฐ ๋ณ„๋„ ํ˜‘์˜ ์—†์ด ์ฒญ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"{o}์— ๊ด€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์€ ๋ชจ๋‘ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ๊ท€์†๋˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"ํ•˜๋„๊ธ‰์€ ์‹ ๊ณ  ์—†์ด ์ œํ•œ ์—†์ด ํ—ˆ์šฉํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ํ•˜๋„๊ธ‰ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ์ „์— ์ „์•ก ์„ ์ง€๊ธ‰ํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ฐ˜ํ™˜ ์ฒญ๊ตฌ๊ถŒ์„ ํฌ๊ธฐํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ์‚ฌํ•ญ์˜ ๋ˆ„์„ค๋กœ ์ธํ•œ ์ฑ…์ž„์„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"ํ•˜์ž ๋ณด์ˆ˜ ์ฑ…์ž„์€ ์ธ๋„ ํ›„ ์ฆ‰์‹œ ์†Œ๋ฉธํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ณด์ˆ˜๋ฅผ ์š”๊ตฌํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฒญ๊ตฌํ•˜๋Š” ๊ธˆ์•ก์„ ๊ฑฐ์ ˆํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ๊ฑฐ์ ˆํ•˜๋ฉด ์œ„์•ฝ๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์ด ์ƒ๊ธฐ๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ •ํ•˜๋Š” ์ค‘์žฌ ๊ธฐ๊ด€์—์„œ๋งŒ ํ•ด๊ฒฐํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์‚ฌ๋Š” ์ƒ๋žตํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์‹œํ—˜ ๊ฒฐ๊ณผ์„œ๋กœ ๋Œ€์‹ ํ•œ๋‹ค.\",\n", " \"๊ฒฌ์  ๊ธˆ์•ก์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ๊ทธ๋Œ€๋กœ ํ™•์ •ํ•˜๊ณ  ์ฆ๋น™ ์ž๋ฃŒ๋Š” ๋ฐ›์ง€ ์•Š๋Š”๋‹ค.\",\n", "]\n", "D_TPL = [\n", " \"๋‚ฉ๊ธฐ๋Š” ์ž์žฌ ์ˆ˜๊ธ‰ ์‚ฌ์ •์— ๋”ฐ๋ผ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ฐ€๋Šฅํ•œ ํ•œ ์ง€ํ‚ค๋„๋ก ๋…ธ๋ ฅํ•˜๋ฉฐ ์ง€์—ฐ๋˜์–ด๋„ ์‚ฌ์œ ๋ฅผ ๋ณด๊ณ ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณต๊ธ‰ ์ง€์—ฐ์ด๋‚˜ ์„ ํ–‰ ์—…์ฒด ์‚ฌ์ •๋„ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ์œผ๋กœ ๋ณด์•„ ๋‚ฉ๊ธฐ๋ฅผ ์ž๋™ ์—ฐ์žฅํ•œ๋‹ค.\",\n", " \"์ฐฉ์ˆ˜์ผ์€ ์„ ํ–‰ ์—…์ฒด์˜ ๋‚ฉํ’ˆ์ด ๋๋‚œ ๋’ค ๋ณ„๋„๋กœ ์ •ํ•˜๋ฉฐ ์™„๋ฃŒ ๊ธฐํ•œ์€ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ์ผ ์ดํ›„ 1ํšŒ์— ํ•œํ•ด ๊ธฐํ•œ ์—†์ด ์ž๋™ ์—ฐ์žฅ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํ•œ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๋Š” ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ƒ์‚ฐ ์ผ์ •์ด ํ™•์ •๋˜๋Š” ๋Œ€๋กœ ํ†ต๋ณดํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์‹œ์ ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž ์‚ฌ์ •์ด ํ—ˆ๋ฝํ•˜๋Š” ๋•Œ๋กœ ํ•˜๊ณ  ๋‚ ์งœ๋Š” ๋”ฐ๋กœ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ž์žฌ๊ฐ€ ๋Šฆ๊ฒŒ ๋“ค์–ด์˜ค๋ฉด ์™„๋ฃŒ ์ผ์ •์€ ๊ทธ๋งŒํผ ์ž๋™์œผ๋กœ ๋Šฆ์ถฐ์ง„๋‹ค.\",\n", " \"๊ธฐํ•œ ๋‚ด์— ์ธ๋„ํ•˜์ง€ ๋ชปํ•ด๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ •์ด ๋Šฆ์–ด์ ธ๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ƒˆ ๊ณ„ํš์„œ๋ฅผ ๋‚ผ ํ•„์š”๊ฐ€ ์—†๋‹ค.\",\n", "]\n", "# ํ•™์Šต์— ์“ฐ์ง€ ์•Š๋Š” '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'(๋ชจ๋ธ์ด ๋ฌธ์žฅ ํ‹€์„ ์™ธ์› ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์šฉ๋„)\n", "HOLD = {\n", " \"์ •์ƒ\": [\"๊ฒ€์‚ฌ์— ๋ถˆํ•ฉ๊ฒฉํ•œ ๊ฒฝ์šฐ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ • ๊ธฐํ•œ ์•ˆ์— ์žฌ๋‚ฉํ’ˆํ•˜๋ฉฐ ์žฌ๊ฒ€์‚ฌ ๋น„์šฉ์€ ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ฒ•๋ น์„ ์œ„๋ฐ˜ํ•˜์—ฌ ๊ณ„์•ฝ์„ ์ดํ–‰ํ•˜์ง€ ๋ชปํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด ํ†ต์ง€ ํ›„ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๊ฐ€ ์ง€์—ฐ๋  ์šฐ๋ ค๊ฐ€ ์žˆ์œผ๋ฉด ์ƒ๋Œ€์ž๋Š” ์ฆ‰์‹œ ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"ํ•˜์ž๋‹ด๋ณด ์ฑ…์ž„ ๊ธฐ๊ฐ„์€ ์ธ์ˆ˜์ผ๋ถ€ํ„ฐ ๊ธฐ์‚ฐํ•˜๋ฉฐ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง„๋‹ค.\"],\n", " \"๋…์†Œ\": [\"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๋Œ€๊ธˆ์„ ์ด์œ  ์—†์ด ๊ฑฐ์ ˆํ•˜์ง€ ๋ชปํ•˜๋ฉฐ ๊ฑฐ์ ˆ ์‹œ ์œ„์•ฝ๊ธˆ์„ ๋‚ธ๋‹ค.\",\n", " \"๊ณ„์•ฝ ๋‚ด์šฉ์— ๊ด€ํ•œ ๋ถ„์Ÿ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€์—์„œ๋งŒ ๋‹ค๋ฃจ๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์ˆ˜ ๊ถŒํ•œ์€ ํฌ๊ธฐํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ž์ฒด ์‹œํ—˜ ์„ฑ์ ์„œ๋กœ ๊ฐˆ์Œํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ œ์ถœํ•œ ๊ฒฌ์ ์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ํ™•์ • ๋‹จ๊ฐ€๋กœ ํ•˜๋ฉฐ ์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"๋‚ฉํ’ˆ ์‹œ๊ธฐ๋Š” ์ƒ๋Œ€์ž์˜ ํ˜•ํŽธ์ด ํ—ˆ๋ฝํ•˜๋Š” ์‹œ์ ์œผ๋กœ ํ•˜๊ณ  ๋ณ„๋„ ๋‚ ์งœ๋Š” ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์›์ž์žฌ ํ™•๋ณด๊ฐ€ ๋Šฆ์–ด์ง€๋ฉด ์™„๋ฃŒ ์ผ์ž๋Š” ์ž๋™์œผ๋กœ ๋’ค๋กœ ๋ฏธ๋ค„์ง€๋Š” ๊ฒƒ์œผ๋กœ ๋ณธ๋‹ค.\",\n", " \"์ธ๋„ ๊ธฐํ•œ์€ ์ฐธ๊ณ ์šฉ์ด๋ฉฐ ์ง€ํ‚ค์ง€ ๋ชปํ•ด๋„ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ • ์ง€์—ฐ์ด ์žˆ์–ด๋„ ์ƒ๋Œ€์ž๋Š” ๋ณ€๊ฒฝ ๊ณ„ํš์„ ์ œ์ถœํ•  ์˜๋ฌด๊ฐ€ ์—†๋‹ค.\"],\n", "}\n", "SUFFIX = [\"\", \"\", \"\", \" ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ์‹œํ–‰ ์„ธ์น™์— ๋”ฐ๋ฅธ๋‹ค.\", \" ์ด ์กฐํ•ญ์€ ๊ณ„์•ฝ ์ „์ฒด์— ์ ์šฉํ•œ๋‹ค.\", \" ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ํ˜‘์˜ํ•œ๋‹ค.\", \" ๊ด€๋ จ ์„œ์‹์€ ๋ณ„์ง€๋ฅผ ๋”ฐ๋ฅธ๋‹ค.\"]\n", "SWAP = [(\"๋ฐœ์ฃผ๊ธฐ๊ด€\", \"๊ณ„์•ฝ๋‹ด๋‹น๊ด€\"), (\"๊ณ„์•ฝ์ƒ๋Œ€์ž\", \"์—…์ฒด\")]\n", "\n", "\n", "def make_clause(rng, cls, hold=False, idx=None):\n", " \"\"\"์กฐํ•ญ ํ•œ ๋ฌธ์žฅ ์ƒ์„ฑ. ํ‘œํ˜„ ์ผ๋ถ€๋Š” ๋™์˜์–ด๋กœ ๋ฐ”๊พธ๊ณ  ์ ‘๋ฏธ ๋ฌธ๊ตฌ๋ฅผ ๋ฌด์ž‘์œ„๋กœ ๋ถ™์ธ๋‹ค(idx๋ฅผ ์ฃผ๋ฉด ๊ทธ ๋ฒˆํ˜ธ์˜ ๋ฌธ์žฅ ํ‹€์„ ์‚ฌ์šฉ).\"\"\"\n", " if idx is not None:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[idx].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " elif hold:\n", " s = HOLD[cls][rng.integers(len(HOLD[cls]))]\n", " else:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[rng.integers(len(tpl))].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " if rng.random() < 0.3:\n", " a, b = SWAP[rng.integers(2)]\n", " s = s.replace(a, b)\n", " return s + SUFFIX[rng.integers(len(SUFFIX))]\n", "\n", "\n", "def make_contracts(n=200, seed=7):\n", " \"\"\"๊ฐ€์ƒ ๊ณ„์•ฝ n๊ฑด(์†์„ฑ 16๊ฐœ + ์กฐํ•ญ 6๊ฐœ + ๊ทœ์ • ์œ„๋ฐ˜ ์ •๋‹ต)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " sl = rng.beta(1.2, 3.2, n) # ๊ณ„์•ฝ์„œ ์ž‘์„ฑ์ด ํ—ˆ์ˆ ํ•œ ์ •๋„(์ž ์žฌ ๋ณ€์ˆ˜)\n", " ctype = rng.choice(TYPES, n, p=[.5, .3, .2])\n", " amount = np.clip(np.exp(rng.normal(np.log(220), 1.1, n)), 5, 5000).round(0)\n", " method = np.where(rng.random(n) < .06 + .3 * sl, \"์ˆ˜์˜๊ณ„์•ฝ\", rng.choice(METHODS[:3], n, p=[.5, .3, .2]))\n", " period = np.array([rng.integers(60, 300) if t == \"๋ฌผํ’ˆ\" else rng.integers(90, 400) if t == \"์šฉ์—ญ\" else rng.integers(180, 720) for t in ctype])\n", " delivery = (period * rng.uniform(.5, 1.0, n)).astype(int)\n", " bad_logic = rng.random(n) < .015 * (1 + 3 * sl)\n", " delivery = np.where(bad_logic, period + rng.integers(10, 60, n), delivery)\n", " pen = np.where(rng.random(n) < .22 * sl, rng.choice([.3, .5, .8], n), rng.choice([1.0, 1.5, 2.0, 2.5], n))\n", " adv = np.where(rng.random(n) < .22 * sl, rng.choice([50, 60, 70], n), rng.choice([0, 10, 20, 30, 40], n))\n", " bond = np.where(rng.random(n) < .25 * sl + .5 * (adv > 40), rng.choice([0, 5], n), rng.choice([10, 10, 15, 20], n))\n", " war = np.array([rng.choice([3, 6]) if (rng.random() < .25 * s and t == \"๋ฌผํ’ˆ\") else rng.choice([12, 24, 36]) if t == \"๋ฌผํ’ˆ\" else rng.choice([0, 6, 12]) if t == \"์šฉ์—ญ\" else rng.choice([24, 36, 60]) for t, s in zip(ctype, sl)])\n", " insp = np.where(rng.random(n) < .3 * sl, rng.choice([21, 30], n), rng.choice([5, 7, 10, 14], n))\n", " sub = np.where(rng.random(n) < .15 * sl, rng.choice([60, 70, 80], n), rng.choice([0, 10, 20, 30, 40, 50], n))\n", " padj = rng.random(n) < .75 - .4 * sl\n", " term = rng.random(n) > .015 * (1 + 6 * sl)\n", " sec = rng.random(n) > .015 * (1 + 6 * sl)\n", " ip = rng.random(n) > .03 * (1 + 4 * sl)\n", " clauses, cls_true = [], []\n", " for i in range(n):\n", " pt, pd_ = .01 + .08 * sl[i], .02 + .08 * sl[i]\n", " cl = [rng.choice(CLASSES, p=[1 - pt - pd_, pt, pd_]) for _ in range(6)]\n", " cls_true.append(cl); clauses.append([make_clause(rng, c) for c in cl])\n", " df = pd.DataFrame({\"contract_id\": [f\"C-{i + 1:04d}\" for i in range(n)], \"ctype\": ctype, \"method\": method, \"amount_m\": amount,\n", " \"period_days\": period, \"delivery_days\": delivery, \"penalty_permil\": pen, \"advance_pct\": adv, \"perf_bond_pct\": bond,\n", " \"warranty_months\": war, \"inspection_days\": insp, \"subcontract_pct\": sub, \"price_adjust\": padj,\n", " \"termination\": term, \"security\": sec, \"ip_clause\": ip})\n", " hard = ((pen < 1.0) | ((adv > 40) & (bond < 10)) | ((amount >= 300) & (method == \"์ˆ˜์˜๊ณ„์•ฝ\")) | ((ctype == \"๋ฌผํ’ˆ\") & (war < 12))\n", " | ~term | ~sec | (sub > 50) | (delivery > period))\n", " toxic = np.array([c.count(\"๋…์†Œ\") for c in cls_true])\n", " delay = np.array([c.count(\"๋‚ฉ๊ธฐ์œ„ํ—˜\") for c in cls_true])\n", " split = (method == \"์ˆ˜์˜๊ณ„์•ฝ\") & (amount >= 250) & (amount < 300) & (rng.random(n) < .6) # ๊ณ„์•ฝ ์ชผ๊ฐœ๊ธฐ ์˜์‹ฌ(์ž„๊ณ„๊ฐ’ ๋ฐ”๋กœ ์•„๋ž˜)\n", " df[\"clauses\"], df[\"clause_cls\"] = clauses, cls_true\n", " df[\"n_toxic\"], df[\"n_delay\"] = toxic, delay\n", " viol = hard | (toxic > 0) | split | (delay >= 2)\n", " waived = hard & (toxic == 0) & (rng.random(n) < .12) # ๊ณ„์•ฝ๊ด€์ด ์˜ˆ์™ธ ์‚ฌ์œ ๋ฅผ ์ธ์ •ํ•ด ์œ„๋ฐ˜์œผ๋กœ ๋ณด์ง€ ์•Š์€ ๊ฑด(์ž๋ฃŒ์˜ ํ˜„์‹ค์  ์žก์Œ)\n", " hidden = ~viol & (rng.random(n) < .03 + .08 * sl) # ์†์„ฑยท๋ฌธ๊ตฌ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜(๋ณ„์ฒจ ํ•ฉ์˜ ๋“ฑ)\n", " df[\"violation\"] = (viol & ~waived) | hidden\n", " df[\"vtype\"] = np.where(hidden, \"๊ธฐํƒ€\", np.where(hard, \"์†์„ฑ์œ„๋ฐ˜\", np.where(toxic > 0, \"๋…์†Œ์กฐํ•ญ\", np.where(split | (delay >= 2), \"๊ธฐํƒ€\", \"-\"))))\n", " return df\n", "\n", "# 1) ์Šคํ‚ค๋งˆ ์ •์˜: ๊ณ„์•ฝ ์‹๋ณ„, ๊ณ„์•ฝ ๋ฐฉ์‹ยท๊ธˆ์•กยท๊ธฐ๊ฐ„, ๋Œ€๊ธˆยท๋ณด์ฆ ์กฐ๊ฑด, ํ•„์ˆ˜ ์กฐํ•ญ ์œ ๋ฌด\n", "SCHEMA = {\n", " \"contract_id\": {\"type\": str, \"pattern\": r\"^C-\\d{4}$\", \"desc\": \"๊ณ„์•ฝ(๊ณ„ํš์•ˆ) ๋ฒˆํ˜ธ\"},\n", " \"ctype\": {\"type\": str, \"enum\": TYPES, \"desc\": \"๊ณ„์•ฝ ์œ ํ˜•\"},\n", " \"method\": {\"type\": str, \"enum\": METHODS, \"desc\": \"๊ณ„์•ฝ ๋ฐฉ์‹\"},\n", " \"amount_m\": {\"type\": (int, float), \"min\": 1, \"max\": 100000, \"desc\": \"๊ณ„์•ฝ ๊ธˆ์•ก(๋ฐฑ๋งŒ์›)\"},\n", " \"period_days\": {\"type\": int, \"min\": 1, \"max\": 1500, \"desc\": \"๊ณ„์•ฝ ๊ธฐ๊ฐ„(์ผ)\"},\n", " \"delivery_days\": {\"type\": int, \"min\": 1, \"max\": 1500, \"desc\": \"๋‚ฉํ’ˆ ๊ธฐํ•œ(์ฐฉ์ˆ˜ ํ›„ ์ผ์ˆ˜)\"},\n", " \"penalty_permil\": {\"type\": (int, float), \"min\": 0, \"max\": 10, \"desc\": \"์ง€์ฒด์ƒ๊ธˆ๋ฅ (1/1000)\"},\n", " \"advance_pct\": {\"type\": (int, float), \"min\": 0, \"max\": 100, \"desc\": \"์„ ๊ธˆ ๋น„์œจ(%)\"},\n", " \"perf_bond_pct\": {\"type\": (int, float), \"min\": 0, \"max\": 100, \"desc\": \"์ดํ–‰๋ณด์ฆ ๋น„์œจ(%)\"},\n", " \"warranty_months\": {\"type\": int, \"min\": 0, \"max\": 120, \"desc\": \"ํ•˜์ž๋ณด์ฆ ๊ธฐ๊ฐ„(๊ฐœ์›”)\"},\n", " \"inspection_days\": {\"type\": int, \"min\": 1, \"max\": 90, \"desc\": \"๊ฒ€์‚ฌ ๊ธฐ๊ฐ„(์ผ)\"},\n", " \"subcontract_pct\": {\"type\": (int, float), \"min\": 0, \"max\": 100, \"desc\": \"ํ•˜๋„๊ธ‰ ๋น„์œจ(%)\"},\n", " \"price_adjust\": {\"type\": bool, \"desc\": \"๋ฌผ๊ฐ€๋ณ€๋™ ์กฐํ•ญ ์œ ๋ฌด\"},\n", " \"termination\": {\"type\": bool, \"desc\": \"ํ•ด์ง€ ์กฐํ•ญ ์œ ๋ฌด\"},\n", " \"security\": {\"type\": bool, \"desc\": \"๋ณด์•ˆ ์กฐํ•ญ ์œ ๋ฌด\"},\n", " \"ip_clause\": {\"type\": bool, \"desc\": \"์ง€์‹์žฌ์‚ฐ ์กฐํ•ญ ์œ ๋ฌด\"},\n", "}\n", "\n", "\n", "def check(name, v, r):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ๊ตญ๋ฐฉ ์กฐ๋‹ฌ ๊ณ„์•ฝ์„œ, ์ž…์ฐฐ ๊ณต๊ณ ๋ฌธ ๋ฐ ๋ฐฉ์œ„์‚ฌ์—…๋ฒ• ๊ทœ์ • ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ๋ถ„์„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def validate(rec):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ๊ตญ๋ฐฉ ์กฐ๋‹ฌ ๊ณ„์•ฝ์„œ, ์ž…์ฐฐ ๊ณต๊ณ ๋ฌธ ๋ฐ ๋ฐฉ์œ„์‚ฌ์—…๋ฒ• ๊ทœ์ • ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ๋ถ„์„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def to_record(row):\n", " return {k: (row[k].item() if hasattr(row[k], \"item\") else row[k]) for k in SCHEMA}\n", "\n", "\n", "con = make_contracts(100, seed=7)\n", "records = [to_record(r) for _, r in con.iterrows()]\n", "# ๋‚ฉํ’ˆ ๊ธฐํ•œ์ด ๊ณ„์•ฝ ๊ธฐ๊ฐ„๋ณด๋‹ค ๊ธด ๊ฑด์€ '๊ณ„์•ฝ์„œ ๋‚ด์šฉ์˜ ๊ฒฐํ•จ'์ด์ง€ ์ž๋ฃŒ ํ˜•์‹ ์˜ค๋ฅ˜๊ฐ€ ์•„๋‹ˆ๋ฏ€๋กœ ํ˜•์‹ ๊ฒ€์ฆ์—์„œ๋Š” ๋”ฐ๋กœ ์„ผ๋‹ค\n", "fmt_ok = sum(not [e for e in validate(r) if \"๋‚ฉํ’ˆ ๊ธฐํ•œ\" not in e] for r in records)\n", "logic = sum(any(\"๋‚ฉํ’ˆ ๊ธฐํ•œ\" in e for e in validate(r)) for r in records)\n", "print(f\"[์Šคํ‚ค๋งˆ ๊ฒ€์ฆ] ๊ณ„์•ฝ {len(records)}๊ฑด ์ค‘ ํ˜•์‹ ํ†ต๊ณผ {fmt_ok}๊ฑด, ํ˜•์‹ ์˜ค๋ฅ˜ {len(records) - fmt_ok}๊ฑด / ํ•„๋“œ {len(SCHEMA)}๊ฐœ / ์กฐํ•ญ ๋ฌธ์žฅ {sum(len(c) for c in con['clauses'])}๊ฐœ\")\n", "print(f\"๋…ผ๋ฆฌ ๊ทœ์น™(๋‚ฉํ’ˆ ๊ธฐํ•œ > ๊ณ„์•ฝ ๊ธฐ๊ฐ„) ์œ„๋ฐ˜ {logic}๊ฑด์€ '๊ณ„์•ฝ ๊ฒฐํ•จ'์œผ๋กœ ๋ถ„๋ฅ˜ํ•ด ๋ฃฐ ์—”์ง„(๋ฏธ์…˜ 3)์—์„œ ๋‹ค๋ฃฌ๋‹ค\")\n", "print(f\"์ˆ˜์˜๊ณ„์•ฝ {int((con['method'] == '์ˆ˜์˜๊ณ„์•ฝ').sum())}๊ฑด, ํ‰๊ท  ๊ธˆ์•ก {con['amount_m'].mean():.0f}๋ฐฑ๋งŒ์›, ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.0 ๋ฏธ๋งŒ {int((con['penalty_permil'] < 1.0).sum())}๊ฑด, ๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ {int((~con['security']).sum())}๊ฑด\")\n", "\n", "# 2) ์˜ค๋ฅ˜ ์ฃผ์ž… ์‹œํ—˜: ํ˜•์‹ยทํ—ˆ์šฉ๊ฐ’ยท๋ฒ”์œ„ยท์ž๋ฃŒํ˜•ยท๋…ผ๋ฆฌ ๊ทœ์น™ ์œ„๋ฐ˜ 7์ข…\n", "base = records[1]\n", "bad = dict(base, contract_id=\"C12\", method=\"ํ˜‘์˜๊ณ„์•ฝ\", amount_m=-5, period_days=\"90\")\n", "bad2 = dict(base, delivery_days=base[\"period_days\"] + 30)\n", "bad3 = dict(base, security=None)\n", "bad4 = {k: v for k, v in base.items() if k != \"penalty_permil\"}\n", "print(\"\\n[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\")\n", "for e in validate(bad) + validate(bad2) + validate(bad3) + validate(bad4):\n", " print(\" -\", e)\n", "\n", "# 3) AS-IS ์—…๋ฌด ๋‹จ๊ณ„ ๋ถ„ํ•ด: [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์กฐ๋‹ฌ๊ณ„ํš์•ˆ ๊ฒ€์ฆ 30๊ฑด์˜ ๋‹จ๊ณ„๋ณ„ ์†Œ์š”์ผ(์ž‘์—… + ๋Œ€๊ธฐ)\n", "rng = np.random.default_rng(3)\n", "STEPS = [(\"๊ณ„ํš์•ˆยท๊ณ„์•ฝ์„œ ์ดˆ์•ˆ ์ ‘์ˆ˜\", 1, 2), (\"์„œ์‹ยท์ฒจ๋ถ€ ์„œ๋ฅ˜ ํ™•์ธ\", 2, 3), (\"์‚ฌ์—…ยท์˜ˆ์‚ฐ ๋ถ€ํ•ฉ ๊ฒ€ํ† \", 3, 2),\n", " (\"๊ณ„์•ฝ ๋ฐฉ์‹ ์ ์ •์„ฑ ๊ฒ€ํ† \", 2, 1), (\"์กฐํ•ญ๋ณ„ ์œ„ํ—˜ ๋ฌธ๊ตฌ ์ˆ˜์ž‘์—… ๊ฒ€ํ† \", 6, 2), (\"๊ทœ์ • ์ค€์ˆ˜ ์ ๊ฒ€ํ‘œ ์ž‘์„ฑ\", 4, 1),\n", " (\"์œ„ํ—˜ ์กฐํ•ญ ํ˜‘์˜ ํšŒ์˜\", 1, 8), (\"๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด ๊ฒ€ํ†  ์˜๋ขฐ\", 1, 9), (\"์ˆ˜์ • ์š”์ฒญยท์—…์ฒด ํ˜‘์˜\", 3, 7),\n", " (\"์ˆ˜์ •๋ณธ ์žฌ๊ฒ€ํ† \", 3, 2), (\"๊ฒฐ์žฌ ์ƒ์‹ \", 1, 5), (\"๊ณ„์•ฝ ์˜๋ขฐ ๋“ฑ๋ก\", 1, 2)]\n", "TOBE = {1: (\"์„œ์‹ ์ž๋™ ์ ๊ฒ€(๋ฏธ์…˜ 2)\", 0.2), 4: (\"์œ„ํ—˜ ๋ฌธ๊ตฌ ์ž๋™ ์‹๋ณ„(๋ฏธ์…˜ 4)\", 0.15), 5: (\"๋ฃฐ ์—”์ง„ ์ ๊ฒ€ํ‘œ ์ž๋™ ์ƒ์„ฑ(๋ฏธ์…˜ 3)\", 0.1),\n", " 6: (\"์œ„ํ—˜ ์ ์ˆ˜ ๊ธฐ๋ฐ˜ ํšŒ์˜ ์•ˆ๊ฑด ์„ ๋ณ„(๋ฏธ์…˜ 5)\", 0.3), 9: (\"์ˆ˜์ • ๋Œ€์กฐ ์ž๋™ ๋น„๊ต(๋ฏธ์…˜ 6)\", 0.4)}\n", "rows = []\n", "for i, (name, work, wait) in enumerate(STEPS):\n", " w = rng.gamma(4, work / 4, 30); q = rng.gamma(2, wait / 2, 30)\n", " f = TOBE.get(i, (\"์œ ์ง€(์‚ฌ๋žŒ ํŒ๋‹จ)\", 1.0))\n", " rows.append({\"๋‹จ๊ณ„\": f\"{i + 1:02d}. {name}\", \"์ž‘์—…(์ผ)\": w.mean(), \"๋Œ€๊ธฐ(์ผ)\": q.mean(), \"AS-IS(์ผ)\": w.mean() + q.mean(),\n", " \"TO-BE ๋ฐฉ์‹\": f[0], \"TO-BE(์ผ)\": w.mean() * f[1] + q.mean() * (f[1] if f[1] < 1 else 1.0)})\n", "proc = pd.DataFrame(rows)\n", "share = proc[\"AS-IS(์ผ)\"] / proc[\"AS-IS(์ผ)\"].sum()\n", "proc[\"๋ณ‘๋ชฉ\"] = np.where((share >= share.sort_values().iloc[-3]) | (proc[\"๋Œ€๊ธฐ(์ผ)\"] / proc[\"AS-IS(์ผ)\"] > 0.75), \"๋ณ‘๋ชฉ\", \"\")\n", "print(\"\\n[AS-IS / TO-BE ์—…๋ฌด ๋‹จ๊ณ„ ๋น„๊ต]\")\n", "print(proc.round(1).to_string(index=False))\n", "n_bn = int((proc[\"๋ณ‘๋ชฉ\"] == \"๋ณ‘๋ชฉ\").sum())\n", "asis, tobe = proc[\"AS-IS(์ผ)\"].sum(), proc[\"TO-BE(์ผ)\"].sum()\n", "print(f\"\\n์—…๋ฌด ๋‹จ๊ณ„ {len(proc)}๊ฐœ(๋ชฉํ‘œ 10๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(proc) >= 10 else '๋ฏธ๋‹ฌ'}), ๋ณ‘๋ชฉ {n_bn}๊ฐœ์†Œ(๋ชฉํ‘œ 3๊ฐœ์†Œ ์ด์ƒ: {'๋‹ฌ์„ฑ' if n_bn >= 3 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\"๊ณ„ํš์•ˆ 1๊ฑด ๊ฒ€์ฆ ์†Œ์š”: AS-IS {asis:.1f}์ผ -> TO-BE {tobe:.1f}์ผ ({1 - tobe / asis:.0%} ๋‹จ์ถ• ์˜ˆ์ƒ). ๋ฒ•๋ฌดยท๊ณ„์•ฝ๊ด€ ๊ฒ€ํ† ์™€ ๊ฒฐ์žฌ๋Š” ์‚ฌ๋žŒ์ด ์ˆ˜ํ–‰ํ•œ๋‹ค.\")\n", "\n", "# 4) ์ž…์ถœ๋ ฅ ๋ฐ์ดํ„ฐ ๋ช…์„ธ ์š”์•ฝ\n", "print(\"\\n[์ž…์ถœ๋ ฅ ๋ช…์„ธ] ์ž…๋ ฅ: ๊ณ„์•ฝ ์†์„ฑ 16๊ฐœ ํ•„๋“œ + ์กฐํ•ญ ๋ฌธ์žฅ ๋ชฉ๋ก / ์ถœ๋ ฅ: ์œ„๋ฐ˜ ๋“ฑ๊ธ‰, ์ ์ค‘ ๋ฃฐยท๊ทผ๊ฑฐ ์กฐํ•ญ, ์œ„ํ—˜ ๋ฌธ๊ตฌ ํ•˜์ด๋ผ์ดํŠธ, ๊ฒ€ํ†  ์ด๋ ฅ\")\n", "print(\"[๊ถŒํ•œ๋ณ„ ์š”๊ตฌ] ๊ณ„์•ฝ ๋‹ด๋‹น: ์ž…๋ ฅยท์ˆ˜์ • / ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด: ๊ฒ€ํ† ยท์˜๊ฒฌ / ์Šน์ธ๊ถŒ์ž: ๊ฒฐ์žฌ(๋ฏธ์…˜ 7์—์„œ ๊ตฌํ˜„)\")\n", "\n", "plt.figure(figsize=(9, 5))\n", "y = np.arange(len(proc))\n", "plt.barh(y - 0.2, proc[\"AS-IS(์ผ)\"], 0.4, label=\"AS-IS\", color=\"tab:gray\")\n", "plt.barh(y + 0.2, proc[\"TO-BE(์ผ)\"], 0.4, label=\"TO-BE\", color=\"tab:blue\")\n", "plt.yticks(y, [f\"step {i + 1}{' *' if b else ''}\" for i, b in enumerate(proc[\"๋ณ‘๋ชฉ\"])])\n", "plt.gca().invert_yaxis(); plt.xlabel(\"days per case\"); plt.legend()\n", "plt.title(\"Procurement plan review: AS-IS vs TO-BE (* = bottleneck, synthetic)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 01 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[์Šคํ‚ค๋งˆ ๊ฒ€์ฆ] ๊ณ„์•ฝ 100๊ฑด ์ค‘ ํ˜•์‹ ํ†ต๊ณผ 100๊ฑด, ํ˜•์‹ ์˜ค๋ฅ˜ 0๊ฑด / ํ•„๋“œ 16๊ฐœ / ์กฐํ•ญ ๋ฌธ์žฅ 600๊ฐœ\n", "๋…ผ๋ฆฌ ๊ทœ์น™(๋‚ฉํ’ˆ ๊ธฐํ•œ > ๊ณ„์•ฝ ๊ธฐ๊ฐ„) ์œ„๋ฐ˜ 2๊ฑด์€ '๊ณ„์•ฝ ๊ฒฐํ•จ'์œผ๋กœ ๋ถ„๋ฅ˜ํ•ด ๋ฃฐ ์—”์ง„(๋ฏธ์…˜ 3)์—์„œ ๋‹ค๋ฃฌ๋‹ค\n", "์ˆ˜์˜๊ณ„์•ฝ 14๊ฑด, ํ‰๊ท  ๊ธˆ์•ก 273๋ฐฑ๋งŒ์›, ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.0 ๋ฏธ๋งŒ 2๊ฑด, ๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ 4๊ฑด\n", "\n", "[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\n", " - contract_id: ํ˜•์‹ ๋ถˆ์ผ์น˜ 'C12'\n", " - method: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๊ฐ’ 'ํ˜‘์˜๊ณ„์•ฝ'\n", " - amount_m: ์ตœ์†Œ๊ฐ’ 1 ๋ฏธ๋งŒ\n", " - period_days: ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜(str)\n", " - delivery_days: ๋‚ฉํ’ˆ ๊ธฐํ•œ์ด ๊ณ„์•ฝ ๊ธฐ๊ฐ„๋ณด๋‹ค ๊น€\n", " - security: ํ•„์ˆ˜ ๊ฐ’ ๋ˆ„๋ฝ\n", " - penalty_permil: ํ•„๋“œ ๋ˆ„๋ฝ\n", "\n", "[AS-IS / TO-BE ์—…๋ฌด ๋‹จ๊ณ„ ๋น„๊ต]\n", " ๋‹จ๊ณ„ ์ž‘์—…(์ผ) ๋Œ€๊ธฐ(์ผ) AS-IS(์ผ) TO-BE ๋ฐฉ์‹ TO-BE(์ผ) ๋ณ‘๋ชฉ\n", " 01. ๊ณ„ํš์•ˆยท๊ณ„์•ฝ์„œ ์ดˆ์•ˆ ์ ‘์ˆ˜ 1.0 2.0 3.0 ์œ ์ง€(์‚ฌ๋žŒ ํŒ๋‹จ) 3.0 \n", " 02. ์„œ์‹ยท์ฒจ๋ถ€ ์„œ๋ฅ˜ ํ™•์ธ 2.6 2.8 5.4 ์„œ์‹ ์ž๋™ ์ ๊ฒ€(๋ฏธ์…˜ 2) 1.1 \n", " 03. ์‚ฌ์—…ยท์˜ˆ์‚ฐ ๋ถ€ํ•ฉ ๊ฒ€ํ†  3.1 2.2 5.3 ์œ ์ง€(์‚ฌ๋žŒ ํŒ๋‹จ) 5.3 \n", " 04. ๊ณ„์•ฝ ๋ฐฉ์‹ ์ ์ •์„ฑ ๊ฒ€ํ†  2.3 1.0 3.3 ์œ ์ง€(์‚ฌ๋žŒ ํŒ๋‹จ) 3.3 \n", "05. ์กฐํ•ญ๋ณ„ ์œ„ํ—˜ ๋ฌธ๊ตฌ ์ˆ˜์ž‘์—… ๊ฒ€ํ†  7.2 2.2 9.4 ์œ„ํ—˜ ๋ฌธ๊ตฌ ์ž๋™ ์‹๋ณ„(๋ฏธ์…˜ 4) 1.4 ๋ณ‘๋ชฉ\n", " 06. ๊ทœ์ • ์ค€์ˆ˜ ์ ๊ฒ€ํ‘œ ์ž‘์„ฑ 4.0 1.0 5.1 ๋ฃฐ ์—”์ง„ ์ ๊ฒ€ํ‘œ ์ž๋™ ์ƒ์„ฑ(๋ฏธ์…˜ 3) 0.5 \n", " 07. ์œ„ํ—˜ ์กฐํ•ญ ํ˜‘์˜ ํšŒ์˜ 0.9 7.5 8.4 ์œ„ํ—˜ ์ ์ˆ˜ ๊ธฐ๋ฐ˜ ํšŒ์˜ ์•ˆ๊ฑด ์„ ๋ณ„(๋ฏธ์…˜ 5) 2.5 ๋ณ‘๋ชฉ\n", " 08. ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด ๊ฒ€ํ†  ์˜๋ขฐ 0.9 8.9 9.8 ์œ ์ง€(์‚ฌ๋žŒ ํŒ๋‹จ) 9.8 ๋ณ‘๋ชฉ\n", " 09. ์ˆ˜์ • ์š”์ฒญยท์—…์ฒด ํ˜‘์˜ 3.3 6.7 10.0 ์œ ์ง€(์‚ฌ๋žŒ ํŒ๋‹จ) 10.0 ๋ณ‘๋ชฉ\n", " 10. ์ˆ˜์ •๋ณธ ์žฌ๊ฒ€ํ†  3.1 2.4 5.5 ์ˆ˜์ • ๋Œ€์กฐ ์ž๋™ ๋น„๊ต(๋ฏธ์…˜ 6) 2.2 \n", " 11. ๊ฒฐ์žฌ ์ƒ์‹  0.9 5.7 6.6 ์œ ์ง€(์‚ฌ๋žŒ ํŒ๋‹จ) 6.6 ๋ณ‘๋ชฉ\n", " 12. ๊ณ„์•ฝ ์˜๋ขฐ ๋“ฑ๋ก 1.0 2.1 3.1 ์œ ์ง€(์‚ฌ๋žŒ ํŒ๋‹จ) 3.1 \n", "\n", "์—…๋ฌด ๋‹จ๊ณ„ 12๊ฐœ(๋ชฉํ‘œ 10๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ), ๋ณ‘๋ชฉ 5๊ฐœ์†Œ(๋ชฉํ‘œ 3๊ฐœ์†Œ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "๊ณ„ํš์•ˆ 1๊ฑด ๊ฒ€์ฆ ์†Œ์š”: AS-IS 75.1์ผ -> TO-BE 49.0์ผ (35% ๋‹จ์ถ• ์˜ˆ์ƒ). ๋ฒ•๋ฌดยท๊ณ„์•ฝ๊ด€ ๊ฒ€ํ† ์™€ ๊ฒฐ์žฌ๋Š” ์‚ฌ๋žŒ์ด ์ˆ˜ํ–‰ํ•œ๋‹ค.\n", "\n", "[์ž…์ถœ๋ ฅ ๋ช…์„ธ] ์ž…๋ ฅ: ๊ณ„์•ฝ ์†์„ฑ 16๊ฐœ ํ•„๋“œ + ์กฐํ•ญ ๋ฌธ์žฅ ๋ชฉ๋ก / ์ถœ๋ ฅ: ์œ„๋ฐ˜ ๋“ฑ๊ธ‰, ์ ์ค‘ ๋ฃฐยท๊ทผ๊ฑฐ ์กฐํ•ญ, ์œ„ํ—˜ ๋ฌธ๊ตฌ ํ•˜์ด๋ผ์ดํŠธ, ๊ฒ€ํ†  ์ด๋ ฅ\n", "[๊ถŒํ•œ๋ณ„ ์š”๊ตฌ] ๊ณ„์•ฝ ๋‹ด๋‹น: ์ž…๋ ฅยท์ˆ˜์ • / ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด: ๊ฒ€ํ† ยท์˜๊ฒฌ / ์Šน์ธ๊ถŒ์ž: ๊ฒฐ์žฌ(๋ฏธ์…˜ 7์—์„œ ๊ตฌํ˜„)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 02. ๊ณ„์•ฝ์„œ ํ…์ŠคํŠธ ํŒŒ์‹ฑ, ๋ถˆ๊ณต์ • ์กฐํ•ญ ํ‚ค์›Œ๋“œ ์ถ”์ถœ ๋ฐ ์กฐํ•ญ๋ณ„ ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๊ณ„์•ฝ์„œ ํ…์ŠคํŠธ ํŒŒ์‹ฑ, ๋ถˆ๊ณต์ • ์กฐํ•ญ ํ‚ค์›Œ๋“œ ์ถ”์ถœ ๋ฐ ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์„œ์‹์ด ๋‹ค๋ฅธ ๊ณ„์•ฝ ์ž๋ฃŒ(CSV, ์—‘์…€ ๋‚ด๋ณด๋‚ด๊ธฐ, JSON, ๊ณ„์•ฝ์„œยท๊ณต๋ฌธ ํ…์ŠคํŠธ)๋ฅผ ํ•˜๋‚˜์˜ ํ‘œ์ค€ ์Šคํ‚ค๋งˆ๋กœ ๋ฐ”๊พธ๊ณ  ๋ถˆ๊ณต์ • ์กฐํ•ญ ํ‚ค์›Œ๋“œ๋ฅผ ์ถ”์ถœํ•˜๋Š” ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์—ด ์ด๋ฆ„ ๋ณ„์นญ ์‚ฌ์ „์œผ๋กœ CSV, ํƒญ ๊ตฌ๋ถ„(์—‘์…€ ๋‚ด๋ณด๋‚ด๊ธฐ), JSON ์„ธ ํ˜•์‹์„ ํ‘œ์ค€ ์Šคํ‚ค๋งˆ๋กœ ๋ฐ”๊พธ๊ณ , ๊ธˆ์•ก(์–ต์›ยท๋ฐฑ๋งŒ์›ยท๋งŒ์›ยท์›), ๊ธฐ๊ฐ„(์ผยท์ฃผยท๊ฐœ์›”ยท๋…„), ์ง€์ฒด์ƒ๊ธˆ๋ฅ (%, 1/1000, ์ฒœ๋ถ„์˜), ์„ ๊ธˆ ํ‘œ๊ธฐ๋ฅผ ํ†ต์ผ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 5๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/connectors/prc01_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, ์—‘์…€ ๋‚ด๋ณด๋‚ด๊ธฐ TSV, JSON, ๊ณ„์•ฝ์„œยท๊ณต๋ฌธ ํ…์ŠคํŠธ)๋ฅผ ์—ด ์ด๋ฆ„ ๋ณ„์นญ ์‚ฌ์ „๊ณผ ์ •๊ทœ์‹์œผ๋กœ ํ‘œ์ค€ ์Šคํ‚ค๋งˆ๋กœ ๋ฐ”๊พธ๊ณ , ๊ธˆ์•กยท๊ธฐ๊ฐ„ยท์ง€์ฒด์ƒ๊ธˆ๋ฅ ยท์„ ๊ธˆ ํ‘œ๊ธฐ์™€ ์กฐํ•ญ ๋ฌธ์žฅ์„ ์ •๊ทœํ™”ํ•˜๋ฉฐ ๋ถˆ๊ณต์ • ์กฐํ•ญ ํ‚ค์›Œ๋“œ๋ฅผ ์ถ”์ถœํ•˜๋Š” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.\"\"\"\n", "import csv\n", "import io\n", "import json\n", "import re\n", "import pandas as pd\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์„œ๋กœ ๋‹ค๋ฅธ ํ˜•์‹์˜ ์›์‹œ ์ž๋ฃŒ (์‹ค์ œ ๊ณ„์•ฝ ์•„๋‹˜). ๊ธˆ์•กยท๊ธฐ๊ฐ„ยท๋น„์œจ ํ‘œ๊ธฐ๊ฐ€ ์ผ๋ถ€๋Ÿฌ ์ œ๊ฐ๊ฐ์ด๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "RAW_CSV = \"\"\"๊ณ„์•ฝ๋ฒˆํ˜ธ,๊ณ„์•ฝ๋ฐฉ์‹,๊ณ„์•ฝ๊ธˆ์•ก,๊ณ„์•ฝ๊ธฐ๊ฐ„,์ง€์ฒด์ƒ๊ธˆ๋ฅ ,์„ ๊ธˆ\n", "C-0101,์ผ๋ฐ˜๊ฒฝ์Ÿ,1.5์–ต์›,90์ผ,0.15%,30%\n", "C-0102,์ˆ˜์˜๊ณ„์•ฝ,\"3,200๋งŒ์›\",3๊ฐœ์›”,1.5/1000,20ํผ์„ผํŠธ\n", "C-0103,์ œํ•œ๊ฒฝ์Ÿ,250๋ฐฑ๋งŒ์›,1๋…„,1.0โ€ฐ,0 %\n", "C-0104,์ง€๋ช…๊ฒฝ์Ÿ,\"1,200,000,000์›\",180์ผ,์ฒœ๋ถ„์˜ 2.0,10%\n", "C-0105,์ˆ˜์˜๊ณ„์•ฝ,4800๋งŒ์›,45์ผ,0.05%,60%\n", "C-0106,์ผ๋ฐ˜๊ฒฝ์Ÿ,95๋ฐฑ๋งŒ์›,12์ฃผ,1.5/1000,0%\n", "\"\"\"\n", "RAW_TSV = \"๊ณ„์•ฝ ๋ฒˆํ˜ธ\\t๊ณ„์•ฝ ๋ฐฉ์‹\\t์ด์•ก\\t์ดํ–‰๊ธฐ๊ฐ„\\t์ง€์ฒด์œจ\\t์„ ๊ธ‰๊ธˆ\\n\" \\\n", " \"C-0107\\t์ผ๋ฐ˜๊ฒฝ์Ÿ\\t0.8์–ต์›\\t60์ผ\\t0.1%\\t10%\\n\" \\\n", " \"C-0108\\t์ˆ˜์˜๊ณ„์•ฝ\\t290๋ฐฑ๋งŒ์›\\t2๊ฐœ์›”\\t1.0/1000\\t50ํผ์„ผํŠธ\\n\" \\\n", " \"C-0101\\t์ผ๋ฐ˜๊ฒฝ์Ÿ\\t150๋ฐฑ๋งŒ์›\\t3๊ฐœ์›”\\t1.5/1000\\t30%\\n\" \\\n", " \"C-0109\\t์ œํ•œ๊ฒฝ์Ÿ\\t5,000๋งŒ์›\\t1๋…„\\t์ฒœ๋ถ„์˜ 1.5\\t0%\\n\" \\\n", " \"C-0110\\t์ง€๋ช…๊ฒฝ์Ÿ\\t2.2์–ต์›\\t240์ผ\\t0.2%\\t20%\\n\"\n", "RAW_JSON = json.dumps({\"status\": \"OK\", \"data\": [\n", " {\"id\": \"C-0111\", \"method\": \"์ผ๋ฐ˜๊ฒฝ์Ÿ\", \"terms\": {\"amount\": \"75๋ฐฑ๋งŒ์›\", \"period\": {\"value\": 4, \"unit\": \"month\"}, \"penalty\": \"1.5/1000\", \"advance\": \"10%\"}},\n", " {\"id\": \"C-0112\", \"method\": \"์ˆ˜์˜๊ณ„์•ฝ\", \"terms\": {\"amount\": \"3.4์–ต์›\", \"period\": {\"value\": 20, \"unit\": \"week\"}, \"penalty\": \"0.08%\", \"advance\": \"40ํผ์„ผํŠธ\"}},\n", " {\"id\": \"C-0113\", \"method\": \"์ œํ•œ๊ฒฝ์Ÿ\", \"terms\": {\"amount\": \"880๋งŒ์›\", \"period\": {\"value\": 30, \"unit\": \"day\"}, \"penalty\": \"2.0โ€ฐ\", \"advance\": \"0%\"}}]},\n", " ensure_ascii=False)\n", "DOCS = [\n", " \"๊ณ„์•ฝ์„œ(๊ฐ€์ƒ)\\n์ œ1์กฐ(๊ณ„์•ฝ๊ธˆ์•ก) ๊ณ„์•ฝ๊ธˆ์•ก์€ ๊ธˆ 150,000,000์›์œผ๋กœ ํ•œ๋‹ค.\\n์ œ2์กฐ(๊ณ„์•ฝ๊ธฐ๊ฐ„) ๊ณ„์•ฝ๊ธฐ๊ฐ„์€ ๊ณ„์•ฝ์ผ๋ถ€ํ„ฐ 3๊ฐœ์›”๋กœ ํ•œ๋‹ค.\\n\"\n", " \"์ œ3์กฐ(์ง€์ฒด์ƒ๊ธˆ) ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋‚ฉ๊ธฐ๋ฅผ ์ง€์ฒดํ•œ ๊ฒฝ์šฐ ์ง€์ฒด์ผ์ˆ˜ 1์ผ๋งˆ๋‹ค ๊ณ„์•ฝ๊ธˆ์•ก์˜ 0.15%๋ฅผ ์ง€์ฒด์ƒ๊ธˆ์œผ๋กœ ๋ถ€๊ณผํ•œ๋‹ค.\\n\"\n", " \"์ œ4์กฐ(์„ ๊ธˆ) ์„ ๊ธˆ์€ ๊ณ„์•ฝ๊ธˆ์•ก์˜ 30ํผ์„ผํŠธ ์ด๋‚ด์—์„œ ์ง€๊ธ‰ํ•œ๋‹ค.\\n์ œ5์กฐ(๊ฒ€์‚ฌ) ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋‚ฉํ’ˆ ํ›„ 7์ผ ์ด๋‚ด์— ๊ฒ€์‚ฌํ•œ๋‹ค.\\n\"\n", " \"์ œ6์กฐ(ํ•ด์ง€) ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์–ธ์ œ๋“ ์ง€ ์ผ๋ฐฉ์ ์œผ๋กœ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ์— ๋”ฐ๋ฅธ ์†ํ•ด๋Š” ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"์ˆ˜์‹ : ๊ณ„์•ฝ๋‹ด๋‹น๊ด€\\n์ œ๋ชฉ: ์กฐ๋‹ฌ๊ณ„ํš์•ˆ ๊ฒ€ํ†  ์š”์ฒญ\\n1. ๊ณ„์•ฝ ๊ฐœ์š”\\n ๊ฐ€. ๊ณ„์•ฝ๊ธˆ์•ก : 2.5์–ต์›\\n ๋‚˜. ๊ณ„์•ฝ๊ธฐ๊ฐ„ : 1๋…„\\n ๋‹ค. ์ง€์ฒด์ƒ๊ธˆ๋ฅ  : ์ฒœ๋ถ„์˜ 0.8\\n ๋ผ. ์„ ๊ธˆ : ๊ณ„์•ฝ๊ธˆ์•ก์˜ 60 %\\n\"\n", " \"2. ์ฃผ์š” ์กฐํ•ญ\\n ๊ฐ€. ๋‚ฉ๊ธฐ๋Š” ์ž์žฌ ์ˆ˜๊ธ‰ ์‚ฌ์ •์— ๋”ฐ๋ผ ๊ณ„์•ฝ ์ƒ๋Œ€์ž๊ฐ€ ์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.\\n ๋‚˜. ํ•˜๋„๊ธ‰์€ ์‹ ๊ณ  ์—†์ด ์ œํ•œ ์—†์ด ํ—ˆ์šฉํ•œ๋‹ค.\",\n", " \"[๊ณ„์•ฝ ์กฐ๊ฑดํ‘œ]\\n๊ณ„์•ฝ๊ธˆ์•ก: 320๋ฐฑ๋งŒ์›\\n๊ณ„์•ฝ๊ธฐ๊ฐ„: 120์ผ\\n์ง€์ฒด์ƒ๊ธˆ๋ฅ : 1.5/1000\\n์„ ๊ธˆ: 20%\\n[์กฐํ•ญ ์š”์•ฝ]\\n\"\n", " \"(1) ์ง€์ฒด์ƒ๊ธˆ์€ ๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผํ•œ๋‹ค.\\n(2) ๋‚ฉํ’ˆ ์ฆ‰์‹œ ๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ ๋ณธ๋‹ค.\",\n", " \"CONTRACT SUMMARY (virtual)\\nContract Amount: KRW 95 million\\nPeriod: 6 months\\nLate Penalty: 1.0 permil\\nAdvance Payment: 10 percent\\n\"\n", " \"Clause: ์ง€์ฒด์ƒ๊ธˆ์€ ์ „์•ก ๋ฉด์ œํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์˜๋ฅผ ์ œ๊ธฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ 1๊ฑด ์š”์•ฝ - ๊ธˆ์•ก 4,800๋งŒ์› / ๊ธฐ๊ฐ„ 45์ผ / ์ง€์ฒด์ƒ๊ธˆ 1.0โ€ฐ / ์„ ๊ธˆ 0% / ๋น„๊ณ : ๋‚ฉ๊ธฐ์ผ ์ดํ›„ 1ํšŒ์— ํ•œํ•ด ๊ธฐํ•œ ์—†์ด ์ž๋™ ์—ฐ์žฅ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํ•œ๋‹ค.\",\n", "]\n", "\n", "# ---- ์ •๊ทœํ™” ๊ทœ์น™(์„œ์‹์ด ๋ฐ”๋€Œ๋ฉด ์ด ์‚ฌ์ „๋งŒ ๊ณ ์น˜๋ฉด ๋œ๋‹ค) ----\n", "COL_ALIAS = {\"contract_id\": [\"๊ณ„์•ฝ๋ฒˆํ˜ธ\", \"๊ณ„์•ฝ ๋ฒˆํ˜ธ\", \"id\"], \"method\": [\"๊ณ„์•ฝ๋ฐฉ์‹\", \"๊ณ„์•ฝ ๋ฐฉ์‹\", \"method\"], \"amount\": [\"๊ณ„์•ฝ๊ธˆ์•ก\", \"์ด์•ก\", \"amount\"],\n", " \"period\": [\"๊ณ„์•ฝ๊ธฐ๊ฐ„\", \"์ดํ–‰๊ธฐ๊ฐ„\", \"period\"], \"penalty\": [\"์ง€์ฒด์ƒ๊ธˆ๋ฅ \", \"์ง€์ฒด์œจ\", \"penalty\"], \"advance\": [\"์„ ๊ธˆ\", \"์„ ๊ธ‰๊ธˆ\", \"advance\"]}\n", "UNIT_DAYS = {\"์ผ\": 1, \"day\": 1, \"์ฃผ\": 7, \"week\": 7, \"๊ฐœ์›”\": 30, \"month\": 30, \"months\": 30, \"๋…„\": 365, \"year\": 365}\n", "KW = {\"๋…์†Œ\": [\"๋ฉด์ œ\", \"์ผ๋ฐฉ์ \", \"์‹ ๊ณ  ์—†์ด\", \"์ œํ•œ ์—†์ด\", \"์ „์•ก ๋ถ€๋‹ด\", \"ํฌ๊ธฐ\", \"๋ˆ„์„ค\", \"์ง€์ง€ ์•Š๋Š”๋‹ค\"], \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์ž๋™ ์—ฐ์žฅ\", \"๊ธฐํ•œ ์—†์ด\", \"์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค\", \"์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"ํ†ต๋ณดํ•œ๋‹ค\"]}\n", "# ์„œ์‹๋ณ„ ์ถ”์ถœ ๊ทœ์น™(๋น„์ •ํ˜• ์„œ์‹ ๋ณด์™„ ์‹œ ์—ฌ๊ธฐ์— ์ •๊ทœ์‹์„ ํ•œ ์ค„ ์ถ”๊ฐ€ํ•œ๋‹ค)\n", "DOC_PAT = {\"amount\": [r\"(?:๊ณ„์•ฝ๊ธˆ์•ก|๊ธˆ์•ก|Contract Amount)\\s*(?:์€|๋Š”)?\\s*[:๏ผš]?\\s*(?:๊ธˆ\\s*|KRW\\s*)?([\\d,\\.]+\\s*(?:์–ต์›|๋ฐฑ๋งŒ์›|๋งŒ์›|million|์›))\"],\n", " \"period\": [r\"(?:๊ณ„์•ฝ๊ธฐ๊ฐ„|๊ธฐ๊ฐ„|Period)\\s*(?:์€|๋Š”)?\\s*[:๏ผš]?\\s*(?:๊ณ„์•ฝ์ผ๋ถ€ํ„ฐ\\s*)?(\\d+\\s*(?:์ผ|๊ฐœ์›”|๋…„|months?|days?))\"],\n", " \"advance\": [r\"(?:์„ ๊ธˆ|Advance Payment)[^\\n/]*?([\\d.]+\\s*(?:%|ํผ์„ผํŠธ|percent))\"]}\n", "PEN_PAT = [r\"์ฒœ๋ถ„์˜\\s*([\\d.]+)\", r\"([\\d.]+)\\s*%\", r\"([\\d.]+)\\s*(?:/1000|โ€ฐ|permil)\"]\n", "CLAUSE_PAT = [r\"^์ œ\\d+์กฐ\\([^)]*\\)\\s*(.+)$\", r\"^\\s*[๊ฐ€-๋ผ]\\.\\s*(.+๋‹ค\\.)$\", r\"^\\(\\d+\\)\\s*(.+)$\", r\"^Clause:\\s*(.+)$\", r\"๋น„๊ณ :\\s*(.+)$\"]\n", "\n", "\n", "def norm_amount(s):\n", " \"\"\"๊ธˆ์•ก ํ‘œ๊ธฐ๋ฅผ ๋ฐฑ๋งŒ์› ๋‹จ์œ„ ์ˆซ์ž๋กœ ํ†ต์ผ: 1.5์–ต์›, 250๋ฐฑ๋งŒ์›, 3,200๋งŒ์›, 1,200,000,000์›, KRW 95 million.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ๊ณ„์•ฝ์„œ ํ…์ŠคํŠธ ํŒŒ์‹ฑ, ๋ถˆ๊ณต์ • ์กฐํ•ญ ํ‚ค์›Œ๋“œ ์ถ”์ถœ ๋ฐ ์กฐํ•ญ๋ณ„ ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def norm_period(v):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ๊ณ„์•ฝ์„œ ํ…์ŠคํŠธ ํŒŒ์‹ฑ, ๋ถˆ๊ณต์ • ์กฐํ•ญ ํ‚ค์›Œ๋“œ ์ถ”์ถœ ๋ฐ ์กฐํ•ญ๋ณ„ ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def norm_rate(s, per_mille=False):\n", " \"\"\"๋น„์œจ ํ‘œ๊ธฐ๋ฅผ ํ†ต์ผ: ์ง€์ฒด์ƒ๊ธˆ๋ฅ ์€ 1/1000(์ฒœ๋ถ„์œจ) ๋‹จ์œ„, ์„ ๊ธˆ์€ % ๋‹จ์œ„.\"\"\"\n", " s = str(s).strip()\n", " for pat, k in [(r\"^์ฒœ๋ถ„์˜\\s*([\\d.]+)$\", 1), (r\"^([\\d.]+)\\s*(?:/1000|โ€ฐ|permil)$\", 1), (r\"^([\\d.]+)\\s*(?:%|ํผ์„ผํŠธ|percent)$\", 10 if per_mille else 1)]:\n", " m = re.match(pat, s)\n", " if m:\n", " return round(float(m.group(1)) * k, 3)\n", " raise ValueError(f\"๋น„์œจ ํ•ด์„ ๋ถˆ๊ฐ€: {s}\")\n", "\n", "\n", "def pick(row, key):\n", " for k in COL_ALIAS[key]:\n", " if k in row:\n", " return row[k]\n", " raise KeyError(f\"ํ•„์ˆ˜ ์—ด ์—†์Œ: {key}\")\n", "\n", "\n", "def to_std(row, src):\n", " return {\"contract_id\": str(pick(row, \"contract_id\")).strip(), \"method\": str(pick(row, \"method\")).strip(), \"amount_m\": round(norm_amount(pick(row, \"amount\")), 2),\n", " \"period_days\": norm_period(pick(row, \"period\")), \"penalty_permil\": norm_rate(pick(row, \"penalty\"), True), \"advance_pct\": norm_rate(pick(row, \"advance\")), \"source\": src}\n", "\n", "\n", "def load(text, fmt):\n", " \"\"\"ํ˜•์‹๋ณ„ ๋กœ๋”: csv / tsv(์—‘์…€ ๋‚ด๋ณด๋‚ด๊ธฐ) / json. ํ–‰๋งˆ๋‹ค ์„ฑ๊ณตยท์‹คํŒจ๋ฅผ ๋”ฐ๋กœ ์„ผ๋‹ค.\"\"\"\n", " if fmt == \"json\":\n", " rows = [{\"id\": r[\"id\"], \"method\": r[\"method\"], \"amount\": r[\"terms\"][\"amount\"], \"period\": r[\"terms\"][\"period\"], \"penalty\": r[\"terms\"][\"penalty\"],\n", " \"advance\": r[\"terms\"][\"advance\"]} for r in json.loads(text)[\"data\"]]\n", " else:\n", " rows = list(csv.DictReader(io.StringIO(text), delimiter=\"\\t\" if fmt == \"tsv\" else \",\"))\n", " ok, fail = [], []\n", " for r in rows:\n", " try:\n", " ok.append(to_std(r, fmt))\n", " except (KeyError, ValueError) as e:\n", " fail.append((r, str(e)))\n", " return ok, fail\n", "\n", "\n", "def norm_clause(s):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ ์ •๊ทœํ™”: ๋„์–ด์“ฐ๊ธฐยทํ˜ธ์นญ ํ†ต์ผ, ๋ฒˆํ˜ธ ์ œ๊ฑฐ.\"\"\"\n", " s = re.sub(r\"^\\s*(?:์ œ\\d+์กฐ\\([^)]*\\)|\\(\\d+\\)|[๊ฐ€-๋ผ]\\.)\\s*\", \"\", s)\n", " s = s.replace(\"๊ณ„์•ฝ ์ƒ๋Œ€์ž\", \"๊ณ„์•ฝ์ƒ๋Œ€์ž\").replace(\"๋ฐœ์ฃผ ๊ธฐ๊ด€\", \"๋ฐœ์ฃผ๊ธฐ๊ด€\").replace(\"๊ณ„์•ฝ๋‹ด๋‹น๊ด€\", \"๋ฐœ์ฃผ๊ธฐ๊ด€\")\n", " return re.sub(r\"\\s+\", \" \", s).strip()\n", "\n", "\n", "def parse_doc(text):\n", " \"\"\"๊ณ„์•ฝ์„œยท๊ณต๋ฌธ ํ…์ŠคํŠธ 1๊ฑด์—์„œ ๊ธˆ์•กยท๊ธฐ๊ฐ„ยท์ง€์ฒด์ƒ๊ธˆ๋ฅ ยท์„ ๊ธˆ๊ณผ ์กฐํ•ญ ๋ฌธ์žฅ์„ ์ถ”์ถœํ•œ๋‹ค.\"\"\"\n", " out = {}\n", " for key, pats in DOC_PAT.items():\n", " for p in pats:\n", " m = re.search(p, text)\n", " if m:\n", " out[key] = m.group(1); break\n", " else:\n", " raise ValueError(f\"ํ•„์ˆ˜ ํ•ญ๋ชฉ({key}) ์ถ”์ถœ ์‹คํŒจ\")\n", " pen = None\n", " for line in re.split(r\"\\n| / \", text):\n", " if re.search(r\"์ง€์ฒด์ƒ๊ธˆ|Late Penalty\", line):\n", " for p in PEN_PAT:\n", " m = re.search(p, line)\n", " if m:\n", " unit = \"%\" if p == PEN_PAT[1] else \"\"\n", " pen = (m.group(1) + unit) if unit else ((\"์ฒœ๋ถ„์˜ \" + m.group(1)) if p == PEN_PAT[0] else m.group(0)); break\n", " if pen:\n", " break\n", " if not pen:\n", " raise ValueError(\"ํ•„์ˆ˜ ํ•ญ๋ชฉ(์ง€์ฒด์ƒ๊ธˆ๋ฅ ) ์ถ”์ถœ ์‹คํŒจ\")\n", " clauses = []\n", " for line in re.split(r\"\\n| / \", text):\n", " for p in CLAUSE_PAT:\n", " m = re.search(p, line.strip())\n", " if m:\n", " c = norm_clause(m.group(1) if p != CLAUSE_PAT[2] else line)\n", " if c.endswith((\"๋‹ค.\", \"ํ•œ๋‹ค\")) or \"๋‹ค.\" in c:\n", " clauses.append(c)\n", " break\n", " return {\"amount_m\": round(norm_amount(out[\"amount\"].replace(\"KRW\", \"\").strip()), 2), \"period_days\": norm_period(out[\"period\"]),\n", " \"penalty_permil\": norm_rate(pen, True), \"advance_pct\": norm_rate(out[\"advance\"].replace(\"percent\", \"ํผ์„ผํŠธ\")), \"clauses\": clauses}\n", "\n", "\n", "def extract_keywords(clause):\n", " return [f\"{c}:{k}\" for c, ks in KW.items() for k in ks if k in clause]\n", "\n", "\n", "# 1) ํ˜•์‹๋ณ„ ํŒŒ์‹ฑ\n", "results, total, success = [], 0, 0\n", "for fmt, text in [(\"csv\", RAW_CSV), (\"tsv\", RAW_TSV), (\"json\", RAW_JSON)]:\n", " ok, fail = load(text, fmt)\n", " results += ok; total += len(ok) + len(fail); success += len(ok)\n", " print(f\"[{fmt.upper():4s}] {len(ok) + len(fail)}ํ–‰ ์ค‘ {len(ok)}ํ–‰ ์„ฑ๊ณต\" + (f\", ์‹คํŒจ {[e for _, e in fail]}\" if fail else \"\"))\n", "docs, d_fail = [], 0\n", "for i, t in enumerate(DOCS, 1):\n", " try:\n", " docs.append(parse_doc(t))\n", " except ValueError as e:\n", " d_fail += 1; print(f\"๋ฌธ์„œ {i} ํŒŒ์‹ฑ ์‹คํŒจ:\", e)\n", "total += len(DOCS); success += len(docs)\n", "print(f\"[๊ณ„์•ฝ์„œยท๊ณต๋ฌธ ํ…์ŠคํŠธ] {len(DOCS)}๊ฑด ์ค‘ {len(docs)}๊ฑด ์„ฑ๊ณต\")\n", "rate = success / total\n", "print(f\"\\n์ „์ฒด ํŒŒ์‹ฑ ์„ฑ๊ณต๋ฅ  {rate:.0%} ({success}/{total}) - ๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if rate == 1 else '๋ฏธ๋‹ฌ'}\")\n", "\n", "# 2) ์ •์ œ ๊ฒฐ๊ณผ(ํ‘œ๊ธฐ ํ†ต์ผ, ์ค‘๋ณต ๊ณ„์•ฝ ์ •๋ฆฌ)\n", "std = pd.DataFrame(results)\n", "before = len(std)\n", "dup = std[std.duplicated(\"contract_id\", keep=False)].sort_values(\"contract_id\")\n", "same = dup.drop(columns=\"source\").drop_duplicates().shape[0] == dup[\"contract_id\"].nunique() if len(dup) else True\n", "std = std.drop_duplicates(\"contract_id\").sort_values(\"contract_id\")\n", "print(f\"\\n[์ •์ œ๋œ ๊ณ„์•ฝ ์ž๋ฃŒ] {before}ํ–‰ -> ์ค‘๋ณต ๊ณ„์•ฝ ์ •๋ฆฌ ํ›„ {len(std)}ํ–‰ (์ค‘๋ณต {before - len(std)}๊ฑด: ํ‘œ๊ธฐ๋Š” ๋‹ฌ๋ž์ง€๋งŒ ์ •๊ทœํ™” ํ›„ ๊ฐ’์ด {'๋™์ผ' if same else '๋ถˆ์ผ์น˜'})\")\n", "print(std.to_string(index=False))\n", "\n", "# 3) ๊ณ„์•ฝ์„œ ํ…์ŠคํŠธ ํŒŒ์‹ฑ ๊ฒฐ๊ณผ์™€ ๋ถˆ๊ณต์ • ์กฐํ•ญ ํ‚ค์›Œ๋“œ\n", "print(\"\\n[๊ณ„์•ฝ์„œยท๊ณต๋ฌธ ํŒŒ์‹ฑ ๊ฒฐ๊ณผ]\")\n", "for i, d in enumerate(docs, 1):\n", " print(f\" ๋ฌธ์„œ {i}: ๊ธˆ์•ก {d['amount_m']:g}๋ฐฑ๋งŒ์›, ๊ธฐ๊ฐ„ {d['period_days']}์ผ, ์ง€์ฒด์ƒ๊ธˆ๋ฅ  {d['penalty_permil']:g}/1000, ์„ ๊ธˆ {d['advance_pct']:g}%, ์กฐํ•ญ {len(d['clauses'])}๊ฐœ\")\n", "print(\"\\n[์กฐํ•ญ๋ณ„ ์ •๊ทœํ™”์™€ ๋ถˆ๊ณต์ •ยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ]\")\n", "n_kw = 0\n", "for i, d in enumerate(docs, 1):\n", " for c in d[\"clauses\"]:\n", " kws = extract_keywords(c)\n", " if kws:\n", " n_kw += 1; print(f\" ๋ฌธ์„œ {i}: {c[:38]}... -> {', '.join(kws)}\")\n", "print(f\"ํ‚ค์›Œ๋“œ๊ฐ€ ๊ฑธ๋ฆฐ ์กฐํ•ญ {n_kw}๊ฐœ (ํ‚ค์›Œ๋“œ๋Š” ํ›„๋ณด ํ‘œ์‹œ์ด๋ฉฐ ์‹ค์ œ ๋ถˆ๊ณต์ • ์—ฌ๋ถ€๋Š” ๊ณ„์•ฝ๊ด€์ด ํŒ๋‹จํ•œ๋‹ค. ์˜ˆ: ๋ฌธ์„œ 3์˜ '๋ฉด์ œ'๋Š” '๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผ'๋ผ๋Š” ์ •์ƒ ๋ฌธ์žฅ์˜ ์˜คํƒ ํ›„๋ณด -> ๋ฏธ์…˜ 4์—์„œ ๋ฌธ๋งฅ ๋ชจ๋ธ๋กœ ๋ณด์™„)\")\n", "\n", "# 4) ์„œ์‹ ๋ณ€๊ฒฝ ๋Œ€์‘ ์‹œํ—˜ โ‘ : ์—ด ์ด๋ฆ„์ด ๋ฐ”๋€ ์ƒˆ ์–‘์‹ -> ๋ณ„์นญ ์‚ฌ์ „ ์ถ”๊ฐ€\n", "new_form = \"๊ณ„์•ฝ ์‹๋ณ„๋ฒˆํ˜ธ,๋ฐฉ์‹,๋Œ€๊ธˆ ์ด์•ก,์ˆ˜ํ–‰๊ธฐ๊ฐ„,์ง€์ฒด์ƒ๊ธˆ ๋น„์œจ,์„ ๊ธ‰\\nC-0121,์ผ๋ฐ˜๊ฒฝ์Ÿ,120๋ฐฑ๋งŒ์›,90์ผ,1.5/1000,10%\\n\"\n", "ok, fail = load(new_form, \"csv\")\n", "print(f\"\\n์ƒˆ ์–‘์‹(์—ด ์ด๋ฆ„ ๋ณ€๊ฒฝ) 1์ฐจ ์‹œ๋„: ์„ฑ๊ณต {len(ok)} / ์‹คํŒจ {len(fail)} -> {fail[0][1] if fail else '-'}\")\n", "for k, alias in {\"contract_id\": \"๊ณ„์•ฝ ์‹๋ณ„๋ฒˆํ˜ธ\", \"method\": \"๋ฐฉ์‹\", \"amount\": \"๋Œ€๊ธˆ ์ด์•ก\", \"period\": \"์ˆ˜ํ–‰๊ธฐ๊ฐ„\", \"penalty\": \"์ง€์ฒด์ƒ๊ธˆ ๋น„์œจ\", \"advance\": \"์„ ๊ธ‰\"}.items():\n", " COL_ALIAS[k].append(alias)\n", "ok, fail = load(new_form, \"csv\")\n", "print(f\"๋ณ„์นญ ์‚ฌ์ „ ๊ฐฑ์‹  ํ›„: ์„ฑ๊ณต {len(ok)} / ์‹คํŒจ {len(fail)} (์ฝ”๋“œ ์ˆ˜์ • ์—†์ด ์„ค์ •๋งŒ ๋ณ€๊ฒฝ)\")\n", "\n", "# 5) ์„œ์‹ ๋ณ€๊ฒฝ ๋Œ€์‘ ์‹œํ—˜ โ‘ก: ๋น„์ •ํ˜• ๊ณต๋ฌธ์˜ ํ‘œํ˜„์ด ๋‹ฌ๋ผ์ง„ ๊ฒฝ์šฐ -> ์ •๊ทœ์‹ ํ•œ ์ค„ ์ถ”๊ฐ€\n", "new_doc = \"์ˆ˜์‹ : ๊ณ„์•ฝ๋‹ด๋‹น๊ด€\\n1. ๋Œ€๊ธˆ ์ด์•ก : 1.2์–ต์›\\n2. ๊ณ„์•ฝ๊ธฐ๊ฐ„ : 6๊ฐœ์›”\\n3. ์ง€์ฒด์ƒ๊ธˆ๋ฅ  : 1.0/1000\\n4. ์„ ๊ธˆ : ๊ณ„์•ฝ๊ธˆ์•ก์˜ 10%\\n5. ์กฐํ•ญ: ๋‚ฉ๊ธฐ์ผ ์ดํ›„ 1ํšŒ์— ํ•œํ•ด ๊ธฐํ•œ ์—†์ด ์ž๋™ ์—ฐ์žฅ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํ•œ๋‹ค.\"\n", "try:\n", " parse_doc(new_doc); r1 = \"์„ฑ๊ณต\"\n", "except ValueError as e:\n", " r1 = f\"์‹คํŒจ({e})\"\n", "DOC_PAT[\"amount\"].append(r\"(?:๋Œ€๊ธˆ ์ด์•ก)\\s*[:๏ผš]?\\s*([\\d,\\.]+\\s*(?:์–ต์›|๋ฐฑ๋งŒ์›|๋งŒ์›|์›))\")\n", "d = parse_doc(new_doc)\n", "print(f\"์ƒˆ ๊ณต๋ฌธ ํ‘œํ˜„('๋Œ€๊ธˆ ์ด์•ก') 1์ฐจ ์‹œ๋„: {r1} -> ๊ทœ์น™ 1์ค„ ์ถ”๊ฐ€ ํ›„ ๊ธˆ์•ก {d['amount_m']:g}๋ฐฑ๋งŒ์› ์ถ”์ถœ ์„ฑ๊ณต\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 02 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[CSV ] 6ํ–‰ ์ค‘ 6ํ–‰ ์„ฑ๊ณต\n", "[TSV ] 5ํ–‰ ์ค‘ 5ํ–‰ ์„ฑ๊ณต\n", "[JSON] 3ํ–‰ ์ค‘ 3ํ–‰ ์„ฑ๊ณต\n", "[๊ณ„์•ฝ์„œยท๊ณต๋ฌธ ํ…์ŠคํŠธ] 5๊ฑด ์ค‘ 5๊ฑด ์„ฑ๊ณต\n", "\n", "์ „์ฒด ํŒŒ์‹ฑ ์„ฑ๊ณต๋ฅ  100% (19/19) - ๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ\n", "\n", "[์ •์ œ๋œ ๊ณ„์•ฝ ์ž๋ฃŒ] 14ํ–‰ -> ์ค‘๋ณต ๊ณ„์•ฝ ์ •๋ฆฌ ํ›„ 13ํ–‰ (์ค‘๋ณต 1๊ฑด: ํ‘œ๊ธฐ๋Š” ๋‹ฌ๋ž์ง€๋งŒ ์ •๊ทœํ™” ํ›„ ๊ฐ’์ด ๋™์ผ)\n", "contract_id method amount_m period_days penalty_permil advance_pct source\n", " C-0101 ์ผ๋ฐ˜๊ฒฝ์Ÿ 150.0 90 1.5 30.0 csv\n", " C-0102 ์ˆ˜์˜๊ณ„์•ฝ 32.0 90 1.5 20.0 csv\n", " C-0103 ์ œํ•œ๊ฒฝ์Ÿ 250.0 365 1.0 0.0 csv\n", " C-0104 ์ง€๋ช…๊ฒฝ์Ÿ 1200.0 180 2.0 10.0 csv\n", " C-0105 ์ˆ˜์˜๊ณ„์•ฝ 48.0 45 0.5 60.0 csv\n", " C-0106 ์ผ๋ฐ˜๊ฒฝ์Ÿ 95.0 84 1.5 0.0 csv\n", " C-0107 ์ผ๋ฐ˜๊ฒฝ์Ÿ 80.0 60 1.0 10.0 tsv\n", " C-0108 ์ˆ˜์˜๊ณ„์•ฝ 290.0 60 1.0 50.0 tsv\n", " C-0109 ์ œํ•œ๊ฒฝ์Ÿ 50.0 365 1.5 0.0 tsv\n", " C-0110 ์ง€๋ช…๊ฒฝ์Ÿ 220.0 240 2.0 20.0 tsv\n", " C-0111 ์ผ๋ฐ˜๊ฒฝ์Ÿ 75.0 120 1.5 10.0 json\n", " C-0112 ์ˆ˜์˜๊ณ„์•ฝ 340.0 140 0.8 40.0 json\n", " C-0113 ์ œํ•œ๊ฒฝ์Ÿ 8.8 30 2.0 0.0 json\n", "\n", "[๊ณ„์•ฝ์„œยท๊ณต๋ฌธ ํŒŒ์‹ฑ ๊ฒฐ๊ณผ]\n", " ๋ฌธ์„œ 1: ๊ธˆ์•ก 150๋ฐฑ๋งŒ์›, ๊ธฐ๊ฐ„ 90์ผ, ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.5/1000, ์„ ๊ธˆ 30%, ์กฐํ•ญ 6๊ฐœ\n", " ๋ฌธ์„œ 2: ๊ธˆ์•ก 250๋ฐฑ๋งŒ์›, ๊ธฐ๊ฐ„ 365์ผ, ์ง€์ฒด์ƒ๊ธˆ๋ฅ  0.8/1000, ์„ ๊ธˆ 60%, ์กฐํ•ญ 2๊ฐœ\n", " ๋ฌธ์„œ 3: ๊ธˆ์•ก 320๋ฐฑ๋งŒ์›, ๊ธฐ๊ฐ„ 120์ผ, ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.5/1000, ์„ ๊ธˆ 20%, ์กฐํ•ญ 2๊ฐœ\n", " ๋ฌธ์„œ 4: ๊ธˆ์•ก 95๋ฐฑ๋งŒ์›, ๊ธฐ๊ฐ„ 180์ผ, ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1/1000, ์„ ๊ธˆ 10%, ์กฐํ•ญ 1๊ฐœ\n", " ๋ฌธ์„œ 5: ๊ธˆ์•ก 48๋ฐฑ๋งŒ์›, ๊ธฐ๊ฐ„ 45์ผ, ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1/1000, ์„ ๊ธˆ 0%, ์กฐํ•ญ 1๊ฐœ\n", "\n", "[์กฐํ•ญ๋ณ„ ์ •๊ทœํ™”์™€ ๋ถˆ๊ณต์ •ยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ]\n", " ๋ฌธ์„œ 1: ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์–ธ์ œ๋“ ์ง€ ์ผ๋ฐฉ์ ์œผ๋กœ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ์— ๋”ฐ๋ฅธ ... -> ๋…์†Œ:์ผ๋ฐฉ์ \n", " ๋ฌธ์„œ 2: ๋‚ฉ๊ธฐ๋Š” ์ž์žฌ ์ˆ˜๊ธ‰ ์‚ฌ์ •์— ๋”ฐ๋ผ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.... -> ๋‚ฉ๊ธฐ์œ„ํ—˜:์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค\n", " ๋ฌธ์„œ 2: ํ•˜๋„๊ธ‰์€ ์‹ ๊ณ  ์—†์ด ์ œํ•œ ์—†์ด ํ—ˆ์šฉํ•œ๋‹ค.... -> ๋…์†Œ:์‹ ๊ณ  ์—†์ด, ๋…์†Œ:์ œํ•œ ์—†์ด\n", " ๋ฌธ์„œ 3: ์ง€์ฒด์ƒ๊ธˆ์€ ๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผํ•œ๋‹ค.... -> ๋…์†Œ:๋ฉด์ œ\n", " ๋ฌธ์„œ 4: ์ง€์ฒด์ƒ๊ธˆ์€ ์ „์•ก ๋ฉด์ œํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์˜๋ฅผ ์ œ๊ธฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.... -> ๋…์†Œ:๋ฉด์ œ\n", " ๋ฌธ์„œ 5: ๋‚ฉ๊ธฐ์ผ ์ดํ›„ 1ํšŒ์— ํ•œํ•ด ๊ธฐํ•œ ์—†์ด ์ž๋™ ์—ฐ์žฅ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํ•œ๋‹ค.... -> ๋‚ฉ๊ธฐ์œ„ํ—˜:์ž๋™ ์—ฐ์žฅ, ๋‚ฉ๊ธฐ์œ„ํ—˜:๊ธฐํ•œ ์—†์ด\n", "ํ‚ค์›Œ๋“œ๊ฐ€ ๊ฑธ๋ฆฐ ์กฐํ•ญ 6๊ฐœ (ํ‚ค์›Œ๋“œ๋Š” ํ›„๋ณด ํ‘œ์‹œ์ด๋ฉฐ ์‹ค์ œ ๋ถˆ๊ณต์ • ์—ฌ๋ถ€๋Š” ๊ณ„์•ฝ๊ด€์ด ํŒ๋‹จํ•œ๋‹ค. ์˜ˆ: ๋ฌธ์„œ 3์˜ '๋ฉด์ œ'๋Š” '๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผ'๋ผ๋Š” ์ •์ƒ ๋ฌธ์žฅ์˜ ์˜คํƒ ํ›„๋ณด -> ๋ฏธ์…˜ 4์—์„œ ๋ฌธ๋งฅ ๋ชจ๋ธ๋กœ ๋ณด์™„)\n", "\n", "์ƒˆ ์–‘์‹(์—ด ์ด๋ฆ„ ๋ณ€๊ฒฝ) 1์ฐจ ์‹œ๋„: ์„ฑ๊ณต 0 / ์‹คํŒจ 1 -> 'ํ•„์ˆ˜ ์—ด ์—†์Œ: contract_id'\n", "... (์ดํ•˜ ์ƒ๋žต)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 03. ๋ฐฉ์œ„์‚ฌ์—… ๊ณ„์•ฝ ํ•„์ˆ˜ ๊ทœ์ • ์ค€์ˆ˜ ์—ฌ๋ถ€ ์ž๋™ ํŒ๋ณ„ 1์ฐจ Rule ์—”์ง„ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋ฐฉ์œ„์‚ฌ์—… ๊ณ„์•ฝ ํ•„์ˆ˜ ๊ทœ์ • ์ค€์ˆ˜ ์—ฌ๋ถ€ ํŒ๋ณ„ Rule ์—”์ง„์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ฐฉ์œ„์‚ฌ์—… ๊ณ„์•ฝ ํ•„์ˆ˜ ๊ทœ์ •(๊ฐ€์ƒ ์ง€์นจ)์˜ ์ค€์ˆ˜ ์—ฌ๋ถ€๋ฅผ ํŒ๋ณ„ํ•˜๋Š” ๋ฃฐ 15์ข… ์ด์ƒ์„ YAML ์„ค์ •์œผ๋กœ ์ฝ”๋”ฉํ•˜๊ณ  ๊ณ„์•ฝ๋ณ„ 1์ฐจ ํŒ๋ณ„ ๋“ฑ๊ธ‰์„ ์‚ฐ์ถœํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ง€์ฒด์ƒ๊ธˆ๋ฅ  ํ•˜ํ•œ, ์„ ๊ธˆ ํ•œ๋„์™€ ๋ณด์ฆ, ์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„, ์ดํ–‰๋ณด์ฆ, ํ•˜์ž๋ณด์ฆ, ํ•ด์ง€ยท๋ณด์•ˆยท์ง€์‹์žฌ์‚ฐ ์กฐํ•ญ ์œ ๋ฌด, ํ•˜๋„๊ธ‰ ๋น„์œจ, ๊ฒ€์‚ฌ ๊ธฐ๊ฐ„, ๋‚ฉํ’ˆ ๊ธฐํ•œ ๋…ผ๋ฆฌ, ๋ฌผ๊ฐ€๋ณ€๋™ ์กฐํ•ญ, ์žฅ๊ธฐ ๊ณ„์•ฝ, ๋ถˆ๊ณต์ • ํ‚ค์›Œ๋“œ ๋“ฑ 16์ข… ๋ฃฐ์„ ๊ฐ€์ค‘์น˜ ํ•ฉ์œผ๋กœ ๊ณ ์œ„ํ—˜ยท์ฃผ์˜ยท๊ด€์‹ฌ ๋“ฑ๊ธ‰์— ์—ฐ๊ฒฐ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/engine/prc01_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": [ "\"\"\"๊ณ„์•ฝ ํ•„์ˆ˜ ๊ทœ์ • ์ค€์ˆ˜ ์—ฌ๋ถ€๋ฅผ ํŒ๋ณ„ํ•˜๋Š” 16์ข… ๋ฃฐ(YAML ์„ค์ •)์„ ์ฝ”๋”ฉํ•ด ๊ณ„์•ฝ๋ณ„ 1์ฐจ ํŒ๋ณ„ ๋“ฑ๊ธ‰์„ ์‚ฐ์ถœํ•˜๊ณ , ๊ฒฝ๊ณ„๊ฐ’ ๋‹จ์œ„ ์‹œํ—˜ 32๊ฑด, ๋ฃฐ ์ฒ˜๋ฆฌ ์ง€์—ฐ์‹œ๊ฐ„, ๋ฃฐ๋ณ„ ๊ทผ๊ฑฐ ์กฐํ•ญ ์—ฐ๊ฒฐ, ๋“ฑ๊ธ‰๋ณ„ ์‹ค์ œ ์œ„๋ฐ˜์œจยท์žฌํ˜„์œจ, ๊ทœ์ • ๋ณ€๊ฒฝ ์‹œ ์„ค์ •๋งŒ ๋ฐ”๊พธ๋Š” ํ™•์žฅ์„ฑ์„ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์กฐ๋‹ฌ๊ณ„ํš์•ˆยท๊ณ„์•ฝ์„œ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ณ„์•ฝยท์—…์ฒดยท๋ฒ•๋ น ์กฐ๋ฌธ ์—†์Œ)\n", "# ๊ณ„์•ฝ ์†์„ฑ 16๊ฐœ + ์กฐํ•ญ ๋ฌธ์žฅ 6๊ฐœ๋ฅผ ๋งŒ๋“ค๊ณ , ์†์„ฑ ์œ„๋ฐ˜ยท๋…์†Œ ์กฐํ•ญยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ์„ž์ธ '๊ทœ์ • ์œ„๋ฐ˜ ์—ฌ๋ถ€'๋ฅผ ํ•จ๊ป˜ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "TYPES = [\"๋ฌผํ’ˆ\", \"์šฉ์—ญ\", \"๊ณต์‚ฌ\"]\n", "METHODS = [\"์ผ๋ฐ˜๊ฒฝ์Ÿ\", \"์ œํ•œ๊ฒฝ์Ÿ\", \"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]\n", "CLASSES = [\"์ •์ƒ\", \"๋…์†Œ\", \"๋‚ฉ๊ธฐ์œ„ํ—˜\"]\n", "OBJ = [\"๋‚ฉํ’ˆ ๋ฌผํ’ˆ\", \"์šฉ์—ญ ๊ฒฐ๊ณผ๋ฌผ\", \"์‹œ๊ณต ๋ชฉ์ ๋ฌผ\", \"์ •๋น„ ์„ฑ๊ณผ๋ฌผ\"]\n", "\n", "N_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋‚ฉ๊ธฐ๋ฅผ ์ง€์ฒดํ•œ ๊ฒฝ์šฐ ์ง€์ฒด์ผ์ˆ˜ 1์ผ๋งˆ๋‹ค ๊ณ„์•ฝ๊ธˆ์•ก์˜ {p}/1000์— ํ•ด๋‹นํ•˜๋Š” ์ง€์ฒด์ƒ๊ธˆ์„ ๋ถ€๊ณผํ•œ๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๊ฐ€ ํ™•์ธ๋œ ๊ฒฝ์šฐ์— ํ•œํ•˜์—ฌ ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์Šน์ธ์„ ๋ฐ›์•„ ๋ฉด์ œํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ ์—ฐ์žฅ์€ ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๋ฅผ ํ™•์ธํ•˜๊ณ  ์„œ๋ฉด์œผ๋กœ ์Šน์ธํ•œ ๊ฒฝ์šฐ์—๋งŒ ์ธ์ •ํ•œ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋‚ฉํ’ˆ๋œ {o}์„ {d}์ผ ์ด๋‚ด์— ๊ฒ€์‚ฌํ•˜๊ณ  ํ•ฉ๊ฒฉํ•œ ํ›„์— ๋Œ€๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ํ•˜๋„๊ธ‰์„ ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ์‚ฌ์ „์— ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์„œ๋ฉด ์Šน์ธ์„ ๋ฐ›์•„์•ผ ํ•œ๋‹ค.\",\n", " \"์–ด๋А ํ•œ์ชฝ์ด ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•˜๋ ค๋ฉด ์‚ฌ์œ ๋ฅผ ๋ช…์‹œํ•ด {d}์ผ ์ „์— ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"{o}์— ํ•˜์ž๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ๋ณด์ฆ ๊ธฐ๊ฐ„ ์•ˆ์— ๋ฌด์ƒ์œผ๋กœ ๋ณด์ˆ˜ํ•˜๊ฑฐ๋‚˜ ๊ต์ฒดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ๋น„๊ณต๊ฐœ ์‚ฌํ•ญ์€ ์ œ3์ž์—๊ฒŒ ๋ˆ„์„คํ•˜์ง€ ์•Š์œผ๋ฉฐ ์œ„๋ฐ˜ ์‹œ ์ฑ…์ž„์„ ์ง„๋‹ค.\",\n", " \"{o}์— ๋Œ€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์˜ ๊ท€์†๊ณผ ์‚ฌ์šฉ ๋ฒ”์œ„๋Š” ๋ณ„๋„ ํ•ฉ์˜์„œ์— ๋”ฐ๋ผ ์ •ํ•œ๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ํ•ฉ๊ฒฉ ํ›„ ์ฒญ๊ตฌ์„œ๋ฅผ ์ ‘์ˆ˜ํ•œ ๋‚ ๋ถ€ํ„ฐ {d}์ผ ์ด๋‚ด์— ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ๊ธˆ์•ก์˜ ์กฐ์ •์€ ๋ฌผ๊ฐ€๋ณ€๋™ ๊ธฐ์ค€์— ๋”ฐ๋ผ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ์„œ๋ฉด์œผ๋กœ ํ•ฉ์˜ํ•œ ๊ฒฝ์šฐ์—๋งŒ ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ดํ–‰๋ณด์ฆ์„œ๋ฅผ ๊ณ„์•ฝ ์ฒด๊ฒฐ ์‹œ ์ œ์ถœํ•˜๋ฉฐ ๋ณด์ฆ๊ธˆ์€ ๊ณ„์•ฝ ์ดํ–‰ ์™„๋ฃŒ ํ›„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ณ„์•ฝ์„œ ๋ณ„ํ‘œ์˜ ์ผ์ž๋ฅผ ๋”ฐ๋ฅด๋ฉฐ ๋ณ€๊ฒฝ ์‹œ ์„œ๋ฉด ํ•ฉ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผํ•˜๋ฉฐ ์ด์˜๋Š” ์ •ํ•ด์ง„ ์ ˆ์ฐจ๋กœ ์ œ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•˜์ง€ ๋ชปํ•œ {o}์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๊ต์ฒดํ•˜๋ฉฐ ๊ทธ ๋น„์šฉ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ๊ท€์ฑ… ์‚ฌ์œ ๋กœ ์ดํ–‰์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•˜๊ณ  ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"์ง€์—ฐ์ด ์˜ˆ์ƒ๋˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ๋ฐœ์ฃผ๊ธฐ๊ด€์— ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•œ๋‹ค.\",\n", " \"๋ณด์ฆ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง€๊ณ  ๋ณด์ˆ˜ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์€ ๋จผ์ € ํ˜‘์˜๋กœ ํ•ด๊ฒฐํ•˜๊ณ  ํ˜‘์˜๊ฐ€ ๋˜์ง€ ์•Š์œผ๋ฉด ๊ด€ํ•  ๋ฒ•์›์˜ ํŒ๋‹จ์— ๋”ฐ๋ฅธ๋‹ค.\",\n", "]\n", "T_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ง€์ฒด์ƒ๊ธˆ์€ ์ „์•ก ๋ฉด์ œํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋Œ€ํ•ด ์ด์˜๋ฅผ ์ œ๊ธฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ฆ‰์‹œ ๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ ๋ณด๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ดํ›„ ๊ฒ€์‚ฌ๋ฅผ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์–ธ์ œ๋“ ์ง€ ์ผ๋ฐฉ์ ์œผ๋กœ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ์— ๋”ฐ๋ฅธ ์†ํ•ด๋Š” ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"{o}์˜ ์ถ”๊ฐ€ ๋น„์šฉ์€ ์‚ฌ์œ ๋ฅผ ๋ถˆ๋ฌธํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ์ „์•ก ๋ถ€๋‹ดํ•˜๋ฉฐ ๋ณ„๋„ ํ˜‘์˜ ์—†์ด ์ฒญ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"{o}์— ๊ด€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์€ ๋ชจ๋‘ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ๊ท€์†๋˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"ํ•˜๋„๊ธ‰์€ ์‹ ๊ณ  ์—†์ด ์ œํ•œ ์—†์ด ํ—ˆ์šฉํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ํ•˜๋„๊ธ‰ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ์ „์— ์ „์•ก ์„ ์ง€๊ธ‰ํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ฐ˜ํ™˜ ์ฒญ๊ตฌ๊ถŒ์„ ํฌ๊ธฐํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ์‚ฌํ•ญ์˜ ๋ˆ„์„ค๋กœ ์ธํ•œ ์ฑ…์ž„์„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"ํ•˜์ž ๋ณด์ˆ˜ ์ฑ…์ž„์€ ์ธ๋„ ํ›„ ์ฆ‰์‹œ ์†Œ๋ฉธํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ณด์ˆ˜๋ฅผ ์š”๊ตฌํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฒญ๊ตฌํ•˜๋Š” ๊ธˆ์•ก์„ ๊ฑฐ์ ˆํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ๊ฑฐ์ ˆํ•˜๋ฉด ์œ„์•ฝ๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์ด ์ƒ๊ธฐ๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ •ํ•˜๋Š” ์ค‘์žฌ ๊ธฐ๊ด€์—์„œ๋งŒ ํ•ด๊ฒฐํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์‚ฌ๋Š” ์ƒ๋žตํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์‹œํ—˜ ๊ฒฐ๊ณผ์„œ๋กœ ๋Œ€์‹ ํ•œ๋‹ค.\",\n", " \"๊ฒฌ์  ๊ธˆ์•ก์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ๊ทธ๋Œ€๋กœ ํ™•์ •ํ•˜๊ณ  ์ฆ๋น™ ์ž๋ฃŒ๋Š” ๋ฐ›์ง€ ์•Š๋Š”๋‹ค.\",\n", "]\n", "D_TPL = [\n", " \"๋‚ฉ๊ธฐ๋Š” ์ž์žฌ ์ˆ˜๊ธ‰ ์‚ฌ์ •์— ๋”ฐ๋ผ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ฐ€๋Šฅํ•œ ํ•œ ์ง€ํ‚ค๋„๋ก ๋…ธ๋ ฅํ•˜๋ฉฐ ์ง€์—ฐ๋˜์–ด๋„ ์‚ฌ์œ ๋ฅผ ๋ณด๊ณ ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณต๊ธ‰ ์ง€์—ฐ์ด๋‚˜ ์„ ํ–‰ ์—…์ฒด ์‚ฌ์ •๋„ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ์œผ๋กœ ๋ณด์•„ ๋‚ฉ๊ธฐ๋ฅผ ์ž๋™ ์—ฐ์žฅํ•œ๋‹ค.\",\n", " \"์ฐฉ์ˆ˜์ผ์€ ์„ ํ–‰ ์—…์ฒด์˜ ๋‚ฉํ’ˆ์ด ๋๋‚œ ๋’ค ๋ณ„๋„๋กœ ์ •ํ•˜๋ฉฐ ์™„๋ฃŒ ๊ธฐํ•œ์€ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ์ผ ์ดํ›„ 1ํšŒ์— ํ•œํ•ด ๊ธฐํ•œ ์—†์ด ์ž๋™ ์—ฐ์žฅ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํ•œ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๋Š” ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ƒ์‚ฐ ์ผ์ •์ด ํ™•์ •๋˜๋Š” ๋Œ€๋กœ ํ†ต๋ณดํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์‹œ์ ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž ์‚ฌ์ •์ด ํ—ˆ๋ฝํ•˜๋Š” ๋•Œ๋กœ ํ•˜๊ณ  ๋‚ ์งœ๋Š” ๋”ฐ๋กœ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ž์žฌ๊ฐ€ ๋Šฆ๊ฒŒ ๋“ค์–ด์˜ค๋ฉด ์™„๋ฃŒ ์ผ์ •์€ ๊ทธ๋งŒํผ ์ž๋™์œผ๋กœ ๋Šฆ์ถฐ์ง„๋‹ค.\",\n", " \"๊ธฐํ•œ ๋‚ด์— ์ธ๋„ํ•˜์ง€ ๋ชปํ•ด๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ •์ด ๋Šฆ์–ด์ ธ๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ƒˆ ๊ณ„ํš์„œ๋ฅผ ๋‚ผ ํ•„์š”๊ฐ€ ์—†๋‹ค.\",\n", "]\n", "# ํ•™์Šต์— ์“ฐ์ง€ ์•Š๋Š” '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'(๋ชจ๋ธ์ด ๋ฌธ์žฅ ํ‹€์„ ์™ธ์› ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์šฉ๋„)\n", "HOLD = {\n", " \"์ •์ƒ\": [\"๊ฒ€์‚ฌ์— ๋ถˆํ•ฉ๊ฒฉํ•œ ๊ฒฝ์šฐ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ • ๊ธฐํ•œ ์•ˆ์— ์žฌ๋‚ฉํ’ˆํ•˜๋ฉฐ ์žฌ๊ฒ€์‚ฌ ๋น„์šฉ์€ ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ฒ•๋ น์„ ์œ„๋ฐ˜ํ•˜์—ฌ ๊ณ„์•ฝ์„ ์ดํ–‰ํ•˜์ง€ ๋ชปํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด ํ†ต์ง€ ํ›„ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๊ฐ€ ์ง€์—ฐ๋  ์šฐ๋ ค๊ฐ€ ์žˆ์œผ๋ฉด ์ƒ๋Œ€์ž๋Š” ์ฆ‰์‹œ ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"ํ•˜์ž๋‹ด๋ณด ์ฑ…์ž„ ๊ธฐ๊ฐ„์€ ์ธ์ˆ˜์ผ๋ถ€ํ„ฐ ๊ธฐ์‚ฐํ•˜๋ฉฐ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง„๋‹ค.\"],\n", " \"๋…์†Œ\": [\"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๋Œ€๊ธˆ์„ ์ด์œ  ์—†์ด ๊ฑฐ์ ˆํ•˜์ง€ ๋ชปํ•˜๋ฉฐ ๊ฑฐ์ ˆ ์‹œ ์œ„์•ฝ๊ธˆ์„ ๋‚ธ๋‹ค.\",\n", " \"๊ณ„์•ฝ ๋‚ด์šฉ์— ๊ด€ํ•œ ๋ถ„์Ÿ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€์—์„œ๋งŒ ๋‹ค๋ฃจ๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์ˆ˜ ๊ถŒํ•œ์€ ํฌ๊ธฐํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ž์ฒด ์‹œํ—˜ ์„ฑ์ ์„œ๋กœ ๊ฐˆ์Œํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ œ์ถœํ•œ ๊ฒฌ์ ์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ํ™•์ • ๋‹จ๊ฐ€๋กœ ํ•˜๋ฉฐ ์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"๋‚ฉํ’ˆ ์‹œ๊ธฐ๋Š” ์ƒ๋Œ€์ž์˜ ํ˜•ํŽธ์ด ํ—ˆ๋ฝํ•˜๋Š” ์‹œ์ ์œผ๋กœ ํ•˜๊ณ  ๋ณ„๋„ ๋‚ ์งœ๋Š” ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์›์ž์žฌ ํ™•๋ณด๊ฐ€ ๋Šฆ์–ด์ง€๋ฉด ์™„๋ฃŒ ์ผ์ž๋Š” ์ž๋™์œผ๋กœ ๋’ค๋กœ ๋ฏธ๋ค„์ง€๋Š” ๊ฒƒ์œผ๋กœ ๋ณธ๋‹ค.\",\n", " \"์ธ๋„ ๊ธฐํ•œ์€ ์ฐธ๊ณ ์šฉ์ด๋ฉฐ ์ง€ํ‚ค์ง€ ๋ชปํ•ด๋„ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ • ์ง€์—ฐ์ด ์žˆ์–ด๋„ ์ƒ๋Œ€์ž๋Š” ๋ณ€๊ฒฝ ๊ณ„ํš์„ ์ œ์ถœํ•  ์˜๋ฌด๊ฐ€ ์—†๋‹ค.\"],\n", "}\n", "SUFFIX = [\"\", \"\", \"\", \" ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ์‹œํ–‰ ์„ธ์น™์— ๋”ฐ๋ฅธ๋‹ค.\", \" ์ด ์กฐํ•ญ์€ ๊ณ„์•ฝ ์ „์ฒด์— ์ ์šฉํ•œ๋‹ค.\", \" ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ํ˜‘์˜ํ•œ๋‹ค.\", \" ๊ด€๋ จ ์„œ์‹์€ ๋ณ„์ง€๋ฅผ ๋”ฐ๋ฅธ๋‹ค.\"]\n", "SWAP = [(\"๋ฐœ์ฃผ๊ธฐ๊ด€\", \"๊ณ„์•ฝ๋‹ด๋‹น๊ด€\"), (\"๊ณ„์•ฝ์ƒ๋Œ€์ž\", \"์—…์ฒด\")]\n", "\n", "\n", "def make_clause(rng, cls, hold=False, idx=None):\n", " \"\"\"์กฐํ•ญ ํ•œ ๋ฌธ์žฅ ์ƒ์„ฑ. ํ‘œํ˜„ ์ผ๋ถ€๋Š” ๋™์˜์–ด๋กœ ๋ฐ”๊พธ๊ณ  ์ ‘๋ฏธ ๋ฌธ๊ตฌ๋ฅผ ๋ฌด์ž‘์œ„๋กœ ๋ถ™์ธ๋‹ค(idx๋ฅผ ์ฃผ๋ฉด ๊ทธ ๋ฒˆํ˜ธ์˜ ๋ฌธ์žฅ ํ‹€์„ ์‚ฌ์šฉ).\"\"\"\n", " if idx is not None:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[idx].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " elif hold:\n", " s = HOLD[cls][rng.integers(len(HOLD[cls]))]\n", " else:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[rng.integers(len(tpl))].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " if rng.random() < 0.3:\n", " a, b = SWAP[rng.integers(2)]\n", " s = s.replace(a, b)\n", " return s + SUFFIX[rng.integers(len(SUFFIX))]\n", "\n", "\n", "def make_contracts(n=200, seed=7):\n", " \"\"\"๊ฐ€์ƒ ๊ณ„์•ฝ n๊ฑด(์†์„ฑ 16๊ฐœ + ์กฐํ•ญ 6๊ฐœ + ๊ทœ์ • ์œ„๋ฐ˜ ์ •๋‹ต)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " sl = rng.beta(1.2, 3.2, n) # ๊ณ„์•ฝ์„œ ์ž‘์„ฑ์ด ํ—ˆ์ˆ ํ•œ ์ •๋„(์ž ์žฌ ๋ณ€์ˆ˜)\n", " ctype = rng.choice(TYPES, n, p=[.5, .3, .2])\n", " amount = np.clip(np.exp(rng.normal(np.log(220), 1.1, n)), 5, 5000).round(0)\n", " method = np.where(rng.random(n) < .06 + .3 * sl, \"์ˆ˜์˜๊ณ„์•ฝ\", rng.choice(METHODS[:3], n, p=[.5, .3, .2]))\n", " period = np.array([rng.integers(60, 300) if t == \"๋ฌผํ’ˆ\" else rng.integers(90, 400) if t == \"์šฉ์—ญ\" else rng.integers(180, 720) for t in ctype])\n", " delivery = (period * rng.uniform(.5, 1.0, n)).astype(int)\n", " bad_logic = rng.random(n) < .015 * (1 + 3 * sl)\n", " delivery = np.where(bad_logic, period + rng.integers(10, 60, n), delivery)\n", " pen = np.where(rng.random(n) < .22 * sl, rng.choice([.3, .5, .8], n), rng.choice([1.0, 1.5, 2.0, 2.5], n))\n", " adv = np.where(rng.random(n) < .22 * sl, rng.choice([50, 60, 70], n), rng.choice([0, 10, 20, 30, 40], n))\n", " bond = np.where(rng.random(n) < .25 * sl + .5 * (adv > 40), rng.choice([0, 5], n), rng.choice([10, 10, 15, 20], n))\n", " war = np.array([rng.choice([3, 6]) if (rng.random() < .25 * s and t == \"๋ฌผํ’ˆ\") else rng.choice([12, 24, 36]) if t == \"๋ฌผํ’ˆ\" else rng.choice([0, 6, 12]) if t == \"์šฉ์—ญ\" else rng.choice([24, 36, 60]) for t, s in zip(ctype, sl)])\n", " insp = np.where(rng.random(n) < .3 * sl, rng.choice([21, 30], n), rng.choice([5, 7, 10, 14], n))\n", " sub = np.where(rng.random(n) < .15 * sl, rng.choice([60, 70, 80], n), rng.choice([0, 10, 20, 30, 40, 50], n))\n", " padj = rng.random(n) < .75 - .4 * sl\n", " term = rng.random(n) > .015 * (1 + 6 * sl)\n", " sec = rng.random(n) > .015 * (1 + 6 * sl)\n", " ip = rng.random(n) > .03 * (1 + 4 * sl)\n", " clauses, cls_true = [], []\n", " for i in range(n):\n", " pt, pd_ = .01 + .08 * sl[i], .02 + .08 * sl[i]\n", " cl = [rng.choice(CLASSES, p=[1 - pt - pd_, pt, pd_]) for _ in range(6)]\n", " cls_true.append(cl); clauses.append([make_clause(rng, c) for c in cl])\n", " df = pd.DataFrame({\"contract_id\": [f\"C-{i + 1:04d}\" for i in range(n)], \"ctype\": ctype, \"method\": method, \"amount_m\": amount,\n", " \"period_days\": period, \"delivery_days\": delivery, \"penalty_permil\": pen, \"advance_pct\": adv, \"perf_bond_pct\": bond,\n", " \"warranty_months\": war, \"inspection_days\": insp, \"subcontract_pct\": sub, \"price_adjust\": padj,\n", " \"termination\": term, \"security\": sec, \"ip_clause\": ip})\n", " hard = ((pen < 1.0) | ((adv > 40) & (bond < 10)) | ((amount >= 300) & (method == \"์ˆ˜์˜๊ณ„์•ฝ\")) | ((ctype == \"๋ฌผํ’ˆ\") & (war < 12))\n", " | ~term | ~sec | (sub > 50) | (delivery > period))\n", " toxic = np.array([c.count(\"๋…์†Œ\") for c in cls_true])\n", " delay = np.array([c.count(\"๋‚ฉ๊ธฐ์œ„ํ—˜\") for c in cls_true])\n", " split = (method == \"์ˆ˜์˜๊ณ„์•ฝ\") & (amount >= 250) & (amount < 300) & (rng.random(n) < .6) # ๊ณ„์•ฝ ์ชผ๊ฐœ๊ธฐ ์˜์‹ฌ(์ž„๊ณ„๊ฐ’ ๋ฐ”๋กœ ์•„๋ž˜)\n", " df[\"clauses\"], df[\"clause_cls\"] = clauses, cls_true\n", " df[\"n_toxic\"], df[\"n_delay\"] = toxic, delay\n", " viol = hard | (toxic > 0) | split | (delay >= 2)\n", " waived = hard & (toxic == 0) & (rng.random(n) < .12) # ๊ณ„์•ฝ๊ด€์ด ์˜ˆ์™ธ ์‚ฌ์œ ๋ฅผ ์ธ์ •ํ•ด ์œ„๋ฐ˜์œผ๋กœ ๋ณด์ง€ ์•Š์€ ๊ฑด(์ž๋ฃŒ์˜ ํ˜„์‹ค์  ์žก์Œ)\n", " hidden = ~viol & (rng.random(n) < .03 + .08 * sl) # ์†์„ฑยท๋ฌธ๊ตฌ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜(๋ณ„์ฒจ ํ•ฉ์˜ ๋“ฑ)\n", " df[\"violation\"] = (viol & ~waived) | hidden\n", " df[\"vtype\"] = np.where(hidden, \"๊ธฐํƒ€\", np.where(hard, \"์†์„ฑ์œ„๋ฐ˜\", np.where(toxic > 0, \"๋…์†Œ์กฐํ•ญ\", np.where(split | (delay >= 2), \"๊ธฐํƒ€\", \"-\"))))\n", " return df\n", "\n", "# ---- ๊ณ„์•ฝ ๊ทœ์ • ์ค€์ˆ˜ ํŒ๋ณ„ ๋ฃฐ ์„ค์ •(YAML ํ˜•์‹ ๋ฌธ์ž์—ด: ๊ทœ์ •์ด ๋ฐ”๋€Œ๋ฉด ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ด ์„ค์ •๋งŒ ๊ณ ์นœ๋‹ค) ----\n", "RULES_YAML = \"\"\"\n", "- id: R01\n", " name: ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.0/1000 ๋ฏธ๋งŒ\n", " field: penalty_permil\n", " op: \"<\"\n", " value: 1.0\n", " weight: 3\n", "- id: R02\n", " name: ์„ ๊ธˆ ๋น„์œจ 40% ์ดˆ๊ณผ\n", " field: advance_pct\n", " op: \">\"\n", " value: 40\n", " weight: 2\n", "- id: R03\n", " name: ์„ ๊ธˆ 40% ์ดˆ๊ณผ์ธ๋ฐ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: adv_no_bond\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R04\n", " name: 300๋ฐฑ๋งŒ์› ์ด์ƒ ์ˆ˜์˜๊ณ„์•ฝ\n", " field: sole_amount\n", " op: \">=\"\n", " value: 300\n", " weight: 3\n", "- id: R05\n", " name: 500๋ฐฑ๋งŒ์› ์ด์ƒ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: bond_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R06\n", " name: ๋ฌผํ’ˆ ํ•˜์ž๋ณด์ฆ 12๊ฐœ์›” ๋ฏธ๋งŒ\n", " field: warranty_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R07\n", " name: ํ•ด์ง€ ์กฐํ•ญ ์—†์Œ\n", " field: termination\n", " op: \"==\"\n", " value: false\n", " weight: 2\n", "- id: R08\n", " name: ๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ\n", " field: security\n", " op: \"==\"\n", " value: false\n", " weight: 3\n", "- id: R09\n", " name: ์ง€์‹์žฌ์‚ฐ ์กฐํ•ญ ์—†์Œ(๋ฌผํ’ˆยท์šฉ์—ญ)\n", " field: ip_missing\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R10\n", " name: ํ•˜๋„๊ธ‰ ๋น„์œจ 50% ์ดˆ๊ณผ\n", " field: subcontract_pct\n", " op: \">\"\n", " value: 50\n", " weight: 3\n", "- id: R11\n", " name: ๊ฒ€์‚ฌ ๊ธฐ๊ฐ„ 14์ผ ์ดˆ๊ณผ\n", " field: inspection_days\n", " op: \">\"\n", " value: 14\n", " weight: 1\n", "- id: R12\n", " name: ๋‚ฉํ’ˆ ๊ธฐํ•œ์ด ๊ณ„์•ฝ ๊ธฐ๊ฐ„ ์ดˆ๊ณผ\n", " field: logic_err\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R13\n", " name: 180์ผ ์ด์ƒ ๊ณ„์•ฝ์— ๋ฌผ๊ฐ€๋ณ€๋™ ์กฐํ•ญ ์—†์Œ\n", " field: no_padj_long\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R14\n", " name: 1000๋ฐฑ๋งŒ์› ์ด์ƒ ๋น„๊ฒฝ์Ÿ ๊ณ„์•ฝ\n", " field: noncomp_big\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R15\n", " name: ๊ณ„์•ฝ ๊ธฐ๊ฐ„ 365์ผ ์ดˆ๊ณผ\n", " field: period_days\n", " op: \">\"\n", " value: 365\n", " weight: 1\n", "- id: R16\n", " name: ๋ถˆ๊ณต์ •ยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ ํฌํ•จ\n", " field: kw_hits\n", " op: \">=\"\n", " value: 1\n", " weight: 3\n", "\"\"\"\n", "GRADE_CUT = {\"๊ณ ์œ„ํ—˜\": 6, \"์ฃผ์˜\": 3} # ๊ฐ€์ค‘์น˜ ํ•ฉ ๊ธฐ์ค€ 1์ฐจ ํŒ๋ณ„ ๋“ฑ๊ธ‰(ํ•„์š” ์‹œ ์„ค์ •๋งŒ ๋ฐ”๊พผ๋‹ค)\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถˆ๊ณต์ • ์กฐํ•ญยท๋‚ฉ๊ธฐ ์ง€์ฒด ์œ„ํ—˜ ๋ฌธ๊ตฌ ์‚ฌ์ „(์‹ค์ œ ๊ทœ์ • ์กฐ๋ฌธ์ด ์•„๋‹˜)\n", "KEYWORDS = {\"๋…์†Œ\": [\"๋ฉด์ œํ•˜๋ฉฐ\", \"์ผ๋ฐฉ์ ์œผ๋กœ\", \"ํฌ๊ธฐ\", \"์ง€์ง€ ์•Š๋Š”๋‹ค\", \"์ฆ‰์‹œ ์†Œ๋ฉธ\", \"์ œํ•œ ์—†์ด\", \"์‹ ๊ณ  ์—†์ด\", \"์ „์•ก ๋ถ€๋‹ด\", \"๋ณ„๋„ ํ˜‘์˜ ์—†์ด\", \"๊ฒ€์‚ฌ ์ „์—\", \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ\", \"์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค\", \"์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์ž๋™ ์—ฐ์žฅ\", \"๊ธฐํ•œ ์—†์ด\", \"์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"๋…ธ๋ ฅํ•˜๋ฉฐ\", \"์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค\", \"ํ†ต๋ณดํ•œ๋‹ค\", \"์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค\", \"์˜๋ฌด๊ฐ€ ์—†๋‹ค\"]}\n", "\n", "\n", "def parse_rules(text):\n", " \"\"\"๊ฐ„์ด YAML ํŒŒ์„œ(๋ชฉ๋ก + key: value). ์ˆซ์žยทtrue/falseยท๋”ฐ์˜ดํ‘œ ๋ฌธ์ž์—ด์„ ํ•ด์„ํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ๋ฐฉ์œ„์‚ฌ์—… ๊ณ„์•ฝ ํ•„์ˆ˜ ๊ทœ์ • ์ค€์ˆ˜ ์—ฌ๋ถ€ ์ž๋™ ํŒ๋ณ„ 1์ฐจ Rule ์—”์ง„ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "OPS = {\"==\": lambda a, b: a == b, \"!=\": lambda a, b: a != b, \">\": lambda a, b: a > b, \">=\": lambda a, b: a >= b,\n", " \"<\": lambda a, b: a < b, \"<=\": lambda a, b: a <= b}\n", "\n", "\n", "def kw_count(clauses):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ ๋ชฉ๋ก์—์„œ ๋…์†Œยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ๊ฐ€ ๋“ค์–ด ์žˆ๋Š” ์กฐํ•ญ ์ˆ˜.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ๋ฐฉ์œ„์‚ฌ์—… ๊ณ„์•ฝ ํ•„์ˆ˜ ๊ทœ์ • ์ค€์ˆ˜ ์—ฌ๋ถ€ ์ž๋™ ํŒ๋ณ„ 1์ฐจ Rule ์—”์ง„ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def derive(df):\n", " \"\"\"๋ฃฐ์— ์“ฐ๋Š” ํŒŒ์ƒ ํ•„๋“œ(์„ ๊ธˆ+๋ณด์ฆ ๋ถ€์กฑ, ์ˆ˜์˜๊ณ„์•ฝ ๊ณ ์•ก, ๋…ผ๋ฆฌ ์˜ค๋ฅ˜, ํ‚ค์›Œ๋“œ ์ ์ค‘ ๋“ฑ)๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " d = df.copy()\n", " d[\"adv_no_bond\"] = (d[\"advance_pct\"] > 40) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"sole_amount\"] = np.where(d[\"method\"] == \"์ˆ˜์˜๊ณ„์•ฝ\", d[\"amount_m\"], 0.0) # ์ˆ˜์˜๊ณ„์•ฝ์ผ ๋•Œ๋งŒ ๊ธˆ์•ก(ํ•œ๋„๋Š” YAML์—์„œ ์กฐ์ •)\n", " d[\"bond_gap\"] = (d[\"amount_m\"] >= 500) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"warranty_gap\"] = (d[\"ctype\"] == \"๋ฌผํ’ˆ\") & (d[\"warranty_months\"] < 12)\n", " d[\"ip_missing\"] = (~d[\"ip_clause\"]) & (d[\"ctype\"] != \"๊ณต์‚ฌ\")\n", " d[\"logic_err\"] = d[\"delivery_days\"] > d[\"period_days\"]\n", " d[\"no_padj_long\"] = (d[\"period_days\"] >= 180) & (~d[\"price_adjust\"])\n", " d[\"noncomp_big\"] = d[\"method\"].isin([\"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]) & (d[\"amount_m\"] >= 1000)\n", " d[\"kw_hits\"] = [kw_count(c) for c in d[\"clauses\"]] if \"clauses\" in d else 0\n", " return d\n", "\n", "\n", "def apply_rules(rec, rules):\n", " \"\"\"๋ ˆ์ฝ”๋“œ 1๊ฑด(dict)์— ๋ฃฐ์„ ์ ์šฉํ•ด (์ ์ˆ˜, ๋“ฑ๊ธ‰, ์ ์ค‘ ๋ฃฐ id ๋ชฉ๋ก)์„ ๋Œ๋ ค์ค€๋‹ค. ๊ฐ’์ด ์—†์œผ๋ฉด ํ•ด๋‹น ๋ฃฐ์€ ๊ฑด๋„ˆ๋›ด๋‹ค.\"\"\"\n", " hits = []\n", " for r in rules:\n", " v = rec.get(r[\"field\"])\n", " if v is None or (isinstance(v, float) and np.isnan(v)):\n", " continue\n", " if OPS[r[\"op\"]](v, r[\"value\"]):\n", " hits.append(r)\n", " score = sum(r[\"weight\"] for r in hits)\n", " grade = \"๊ณ ์œ„ํ—˜\" if score >= GRADE_CUT[\"๊ณ ์œ„ํ—˜\"] else (\"์ฃผ์˜\" if score >= GRADE_CUT[\"์ฃผ์˜\"] else \"๊ด€์‹ฌ\")\n", " return score, grade, [r[\"id\"] for r in hits]\n", "\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ฃฐ๊ณผ ๊ฐ€์ƒ ์ง€์นจ ์กฐํ•ญ์˜ ๋Œ€์‘ํ‘œ(์‹ค์ œ ๋ฒ•๋ นยท๊ทœ์ • ์กฐ๋ฌธ์ด ์•„๋‹ˆ๋ผ ์—ฐ๊ฒฐ ๋ฐฉ์‹์„ ๋ณด์—ฌ ์ฃผ๋Š” ์˜ˆ์‹œ)\n", "CLAUSE = {\"R01\": \"๊ฐ€์ƒ์ง€์นจ 5์กฐ1ํ•ญ(์ง€์ฒด์ƒ๊ธˆ)\", \"R02\": \"๊ฐ€์ƒ์ง€์นจ 6์กฐ1ํ•ญ(์„ ๊ธˆ ํ•œ๋„)\", \"R03\": \"๊ฐ€์ƒ์ง€์นจ 6์กฐ2ํ•ญ(์„ ๊ธˆ๊ณผ ๋ณด์ฆ)\", \"R04\": \"๊ฐ€์ƒ์ง€์นจ 3์กฐ1ํ•ญ(์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„)\",\n", " \"R05\": \"๊ฐ€์ƒ์ง€์นจ 6์กฐ3ํ•ญ(์ดํ–‰๋ณด์ฆ)\", \"R06\": \"๊ฐ€์ƒ์ง€์นจ 7์กฐ1ํ•ญ(ํ•˜์ž๋ณด์ฆ)\", \"R07\": \"๊ฐ€์ƒ์ง€์นจ 8์กฐ1ํ•ญ(๊ณ„์•ฝ ํ•ด์ง€)\", \"R08\": \"๊ฐ€์ƒ์ง€์นจ 9์กฐ1ํ•ญ(๋ณด์•ˆ ์กฐํ•ญ)\",\n", " \"R09\": \"๊ฐ€์ƒ์ง€์นจ 9์กฐ2ํ•ญ(์ง€์‹์žฌ์‚ฐ)\", \"R10\": \"๊ฐ€์ƒ์ง€์นจ 4์กฐ1ํ•ญ(ํ•˜๋„๊ธ‰ ์ œํ•œ)\", \"R11\": \"๊ฐ€์ƒ์ง€์นจ 5์กฐ2ํ•ญ(๊ฒ€์‚ฌ ๊ธฐ๊ฐ„)\", \"R12\": \"๊ฐ€์ƒ์ง€์นจ 5์กฐ3ํ•ญ(๋‚ฉํ’ˆ ๊ธฐํ•œ)\",\n", " \"R13\": \"๊ฐ€์ƒ์ง€์นจ 6์กฐ4ํ•ญ(๋ฌผ๊ฐ€๋ณ€๋™)\", \"R14\": \"๊ฐ€์ƒ์ง€์นจ 3์กฐ2ํ•ญ(๊ฒฝ์Ÿ ๊ณ„์•ฝ ์›์น™)\", \"R15\": \"๊ฐ€์ƒ์ง€์นจ 3์กฐ3ํ•ญ(์žฅ๊ธฐ ๊ณ„์•ฝ)\", \"R16\": \"๊ฐ€์ƒ์ง€์นจ 8์กฐ2ํ•ญ(๋ถˆ๊ณต์ • ์กฐํ•ญ)\"}\n", "\n", "rules = parse_rules(RULES_YAML)\n", "con = make_contracts(200, seed=7)\n", "df = derive(con)\n", "recs = df.to_dict(\"records\")\n", "\n", "# 1) ์ „์ฒด ์Šคํฌ๋ฆฌ๋‹๊ณผ ์ฒ˜๋ฆฌ ์ง€์—ฐ์‹œ๊ฐ„(๋ ˆ์ฝ”๋“œ 1๊ฑด๋‹น)\n", "apply_rules(recs[0], rules) # ์ฒซ ํ˜ธ์ถœ ์ง€์—ฐ(์˜ˆ์—ด)์„ ์ธก์ •์—์„œ ์ œ์™ธ\n", "lat, res = [], []\n", "for r in recs:\n", " t0 = time.perf_counter()\n", " out = apply_rules(r, rules)\n", " lat.append((time.perf_counter() - t0) * 1000)\n", " res.append(out)\n", "df[\"score\"], df[\"grade\"], df[\"hits\"] = [x[0] for x in res], [x[1] for x in res], [x[2] for x in res]\n", "print(f\"[๋ฃฐ ์„ค์ •] ๊ฒ€์ฆ ๋ฃฐ {len(rules)}์ข…(๋ชฉํ‘œ 15์ข… ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(rules) >= 15 else '๋ฏธ๋‹ฌ'}), ๋ชจ๋“  ๋ฃฐ์— ๊ฐ€์ƒ ์ง€์นจ ์กฐํ•ญ ์—ฐ๊ฒฐ {all(r['id'] in CLAUSE for r in rules)}\")\n", "print(f\"๋ฃฐ ์ฒ˜๋ฆฌ ์ง€์—ฐ์‹œ๊ฐ„(๋ ˆ์ฝ”๋“œ 1๊ฑด): ํ‰๊ท  {np.mean(lat):.3f}ms, 99๋ฐฑ๋ถ„์œ„ {np.percentile(lat, 99):.3f}ms, ์ตœ๋Œ€ {np.max(lat):.3f}ms (๋ชฉํ‘œ 50ms ์ดํ•˜, 99๋ฐฑ๋ถ„์œ„ ๊ธฐ์ค€: {'๋‹ฌ์„ฑ' if np.percentile(lat, 99) <= 50 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# 2) ๊ฒฝ๊ณ„ ์กฐ๊ฑด ๋‹จ์œ„ ์‹œํ—˜: ๊ธฐ์ค€๊ฐ’ ๋ฐ”๋กœ ์•„๋ž˜ยท์œ„์—์„œ ๋ฃฐ์ด ์ •ํ™•ํžˆ ์ผœ์ง€๊ณ  ๊บผ์ง€๋Š”๊ฐ€\n", "CLEAN = [make_clause(np.random.default_rng(i), \"์ •์ƒ\") for i in range(6)]\n", "BASE = dict(contract_id=\"C-0000\", ctype=\"๋ฌผํ’ˆ\", method=\"์ผ๋ฐ˜๊ฒฝ์Ÿ\", amount_m=100.0, period_days=120, delivery_days=100, penalty_permil=1.5, advance_pct=20, perf_bond_pct=15,\n", " warranty_months=24, inspection_days=7, subcontract_pct=20, price_adjust=True, termination=True, security=True, ip_clause=True, clauses=CLEAN)\n", "TESTS = [(\"R01\", dict(penalty_permil=0.9), True), (\"R01\", dict(penalty_permil=1.0), False), (\"R02\", dict(advance_pct=41), True), (\"R02\", dict(advance_pct=40), False),\n", " (\"R03\", dict(advance_pct=50, perf_bond_pct=9), True), (\"R03\", dict(advance_pct=50, perf_bond_pct=10), False),\n", " (\"R04\", dict(method=\"์ˆ˜์˜๊ณ„์•ฝ\", amount_m=300), True), (\"R04\", dict(method=\"์ˆ˜์˜๊ณ„์•ฝ\", amount_m=299), False),\n", " (\"R05\", dict(amount_m=500, perf_bond_pct=9), True), (\"R05\", dict(amount_m=499, perf_bond_pct=9), False),\n", " (\"R06\", dict(warranty_months=11), True), (\"R06\", dict(warranty_months=12), False), (\"R07\", dict(termination=False), True), (\"R07\", dict(termination=True), False),\n", " (\"R08\", dict(security=False), True), (\"R08\", dict(security=True), False), (\"R09\", dict(ip_clause=False, ctype=\"์šฉ์—ญ\"), True), (\"R09\", dict(ip_clause=False, ctype=\"๊ณต์‚ฌ\"), False),\n", " (\"R10\", dict(subcontract_pct=51), True), (\"R10\", dict(subcontract_pct=50), False), (\"R11\", dict(inspection_days=15), True), (\"R11\", dict(inspection_days=14), False),\n", " (\"R12\", dict(delivery_days=121), True), (\"R12\", dict(delivery_days=120), False), (\"R13\", dict(period_days=180, delivery_days=170, price_adjust=False), True),\n", " (\"R13\", dict(period_days=179, delivery_days=170, price_adjust=False), False), (\"R14\", dict(method=\"์ง€๋ช…๊ฒฝ์Ÿ\", amount_m=1000), True), (\"R14\", dict(method=\"์ง€๋ช…๊ฒฝ์Ÿ\", amount_m=999), False),\n", " (\"R15\", dict(period_days=366, delivery_days=300), True), (\"R15\", dict(period_days=365, delivery_days=300), False),\n", " (\"R16\", dict(clauses=CLEAN[:5] + [\"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์–ธ์ œ๋“ ์ง€ ์ผ๋ฐฉ์ ์œผ๋กœ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\"]), True), (\"R16\", dict(clauses=CLEAN), False)]\n", "passed = 0\n", "for rid, chg, expect in TESTS:\n", " row = derive(pd.DataFrame([{**BASE, **chg}])).iloc[0].to_dict()\n", " got = rid in apply_rules(row, rules)[2]\n", " passed += got == expect\n", " if got != expect:\n", " print(f\" [FAIL] {rid} {chg} ๊ธฐ๋Œ€ {expect} ๊ฒฐ๊ณผ {got}\")\n", "base_hits = apply_rules(derive(pd.DataFrame([BASE])).iloc[0].to_dict(), rules)\n", "print(f\"\\n[๊ฒฝ๊ณ„ ์กฐ๊ฑด ๋‹จ์œ„ ์‹œํ—˜] {passed}/{len(TESTS)}๊ฑด ํ†ต๊ณผ ({passed / len(TESTS):.0%}), ์ •์ƒ ๊ธฐ์ค€ ๊ณ„์•ฝ์˜ ์ ์ค‘ ๋ฃฐ {base_hits[2] or '์—†์Œ'} -> ๋“ฑ๊ธ‰ {base_hits[1]} (๋ชฉํ‘œ 100% ํ†ต๊ณผ: {'๋‹ฌ์„ฑ' if passed == len(TESTS) else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# 3) ๋“ฑ๊ธ‰ ๋ถ„ํฌ์™€ ์‹ค์ œ ์œ„๋ฐ˜ ์—ฌ๋ถ€(๋ฒ•๋ฌดยท๊ณ„์•ฝ๊ด€ ๊ฒ€ํ† ์—์„œ ํ™•์ธ๋œ ๊ทœ์ • ์œ„๋ฐ˜ ์ •๋‹ต) ๋Œ€๋น„ ํƒ์ง€๋ ฅ\n", "print(\"\\n[1์ฐจ ํŒ๋ณ„ ๋“ฑ๊ธ‰ ๋ถ„ํฌ]\")\n", "print(df.groupby(\"grade\").agg(๊ณ„์•ฝ์ˆ˜=(\"contract_id\", \"size\"), ์‹ค์ œ์œ„๋ฐ˜์œจ=(\"violation\", \"mean\")).reindex([\"๊ณ ์œ„ํ—˜\", \"์ฃผ์˜\", \"๊ด€์‹ฌ\"]).round(2).to_string())\n", "y = df[\"violation\"].to_numpy()\n", "flag = df[\"grade\"].isin([\"๊ณ ์œ„ํ—˜\", \"์ฃผ์˜\"]).to_numpy()\n", "tp = int((flag & y).sum())\n", "print(f\"๊ณ ์œ„ํ—˜ยท์ฃผ์˜ ์„ ๋ณ„: ์‹ค์ œ ์œ„๋ฐ˜ ๊ณ„์•ฝ {y.sum()}๊ฑด ์ค‘ {tp}๊ฑด ํฌ์ฐฉ(์žฌํ˜„์œจ {tp / y.sum():.0%}), ์„ ๋ณ„ {flag.sum()}๊ฑด ์ค‘ ์ •๋ฐ€๋„ {tp / max(flag.sum(), 1):.0%}\")\n", "\n", "# 4) ๋ฃฐ๋ณ„ ์ ์ค‘ ๊ฑด์ˆ˜์™€ ๊ทผ๊ฑฐ ์กฐํ•ญ\n", "cnt = pd.Series([h for hs in df[\"hits\"] for h in hs]).value_counts()\n", "tbl = pd.DataFrame([{\"๋ฃฐ\": r[\"id\"], \"์ด๋ฆ„\": r[\"name\"], \"๊ฐ€์ค‘์น˜\": r[\"weight\"], \"์ ์ค‘\": int(cnt.get(r[\"id\"], 0)), \"๊ทผ๊ฑฐ ์กฐํ•ญ\": CLAUSE[r[\"id\"]]} for r in rules])\n", "print(\"\\n[๋ฃฐ๋ณ„ ์ ์ค‘ ํ˜„ํ™ฉ]\")\n", "print(tbl.to_string(index=False))\n", "\n", "# 5) ๊ณ ์œ„ํ—˜ ์ƒ์œ„ 3๊ฑด์˜ ํŒ์ • ๊ทผ๊ฑฐ(์„ค๋ช… ๊ฐ€๋Šฅ์„ฑ)\n", "print(\"\\n[๊ณ ์œ„ํ—˜ ์ƒ์œ„ 3๊ฑด๊ณผ ๊ทผ๊ฑฐ]\")\n", "for _, r in df.sort_values(\"score\", ascending=False).head(3).iterrows():\n", " print(f\" {r['contract_id']} {r['ctype']} {r['method']} {r['amount_m']:.0f}๋ฐฑ๋งŒ์› ์ ์ˆ˜ {r['score']} {r['grade']}\")\n", " for h in r[\"hits\"][:3]:\n", " print(f\" - {h} {next(x['name'] for x in rules if x['id'] == h)} / {CLAUSE[h]}\")\n", "\n", "# 6) ๊ทœ์ • ๋ณ€๊ฒฝ ๋Œ€์‘: ์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„๋ฅผ 300์—์„œ 250๋ฐฑ๋งŒ์›์œผ๋กœ ๋‚ฎ์ถ”๋Š” ์„ค์ • ๋ณ€๊ฒฝ(์ฝ”๋“œ ์ˆ˜์ • ์—†์Œ)\n", "rules2 = parse_rules(RULES_YAML.replace('op: \">=\"\\n value: 300', 'op: \">=\"\\n value: 250'))\n", "o2 = [apply_rules(r, rules2) for r in recs]\n", "g2 = np.array([x[1] in (\"๊ณ ์œ„ํ—˜\", \"์ฃผ์˜\") for x in o2])\n", "n_new = int((g2 & ~flag).sum())\n", "print(f\"\\n[์„ค์ • ๋ณ€๊ฒฝ] ์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„ 300 -> 250๋ฐฑ๋งŒ์›(YAML ๊ฐ’๋งŒ ๋ณ€๊ฒฝ): ์ƒˆ๋กœ ์„ ๋ณ„๋œ ๊ณ„์•ฝ {n_new}๊ฑด(๊ณ„์•ฝ ์ชผ๊ฐœ๊ธฐ ์˜์‹ฌ ๊ตฌ๊ฐ„ 250~299๋ฐฑ๋งŒ์›), ์žฌํ˜„์œจ {tp / y.sum():.0%} -> {(g2 & y).sum() / y.sum():.0%}\")\n", "rules3 = parse_rules(RULES_YAML.replace(\"weight: 3\\n- id: R02\", \"weight: 3\\n- id: R02\").replace(\"op: \\\"<\\\"\\n value: 1.0\", \"op: \\\"<\\\"\\n value: 1.2\"))\n", "o3 = [apply_rules(r, rules3)[0] for r in recs]\n", "print(f\"[์„ค์ • ๋ณ€๊ฒฝ] ๋ฃฐ R01 ์ง€์ฒด์ƒ๊ธˆ๋ฅ  ๊ธฐ์ค€ 1.0 -> 1.2: ์ ์ˆ˜๊ฐ€ ๋ฐ”๋€ ๊ณ„์•ฝ {sum(a != b for a, b in zip(df['score'], o3))}๊ฑด (YAML ๊ฐ’๋งŒ ๋ณ€๊ฒฝ)\")\n", "print(\"์ฒ˜๋ฆฌ ํ›„ ์‚ฌ๋žŒ ๊ฒ€ํ† : ๋ฃฐ ํŒ๋ณ„์€ 1์ฐจ ์„ ๋ณ„์ด๋ฉฐ, ์ตœ์ข… ๊ทœ์ • ์œ„๋ฐ˜ ํŒ๋‹จ๊ณผ ์ˆ˜์ • ์š”๊ตฌ๋Š” ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด ๋‹ด๋‹น์ž๊ฐ€ ๊ฒฐ์ •ํ•œ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n", "ax[0].bar(tbl[\"๋ฃฐ\"], tbl[\"์ ์ค‘\"], color=\"tab:blue\"); ax[0].set_title(\"Rule hits by rule (synthetic)\"); ax[0].set_ylabel(\"contracts\")\n", "g = df.groupby(\"grade\")[\"violation\"].mean().reindex([\"๊ด€์‹ฌ\", \"์ฃผ์˜\", \"๊ณ ์œ„ํ—˜\"])\n", "ax[1].bar([\"low\", \"watch\", \"high\"], g.values, color=[\"tab:green\", \"gold\", \"tab:red\"]); ax[1].set_title(\"Actual violation rate by screening grade\"); ax[1].set_ylim(0, 1.05)\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 03 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๋ฃฐ ์„ค์ •] ๊ฒ€์ฆ ๋ฃฐ 16์ข…(๋ชฉํ‘œ 15์ข… ์ด์ƒ: ๋‹ฌ์„ฑ), ๋ชจ๋“  ๋ฃฐ์— ๊ฐ€์ƒ ์ง€์นจ ์กฐํ•ญ ์—ฐ๊ฒฐ True\n", "๋ฃฐ ์ฒ˜๋ฆฌ ์ง€์—ฐ์‹œ๊ฐ„(๋ ˆ์ฝ”๋“œ 1๊ฑด): ํ‰๊ท  0.008ms, 99๋ฐฑ๋ถ„์œ„ 0.036ms, ์ตœ๋Œ€ 0.100ms (๋ชฉํ‘œ 50ms ์ดํ•˜, 99๋ฐฑ๋ถ„์œ„ ๊ธฐ์ค€: ๋‹ฌ์„ฑ)\n", "\n", "[๊ฒฝ๊ณ„ ์กฐ๊ฑด ๋‹จ์œ„ ์‹œํ—˜] 32/32๊ฑด ํ†ต๊ณผ (100%), ์ •์ƒ ๊ธฐ์ค€ ๊ณ„์•ฝ์˜ ์ ์ค‘ ๋ฃฐ ์—†์Œ -> ๋“ฑ๊ธ‰ ๊ด€์‹ฌ (๋ชฉํ‘œ 100% ํ†ต๊ณผ: ๋‹ฌ์„ฑ)\n", "\n", "[1์ฐจ ํŒ๋ณ„ ๋“ฑ๊ธ‰ ๋ถ„ํฌ]\n", " ๊ณ„์•ฝ์ˆ˜ ์‹ค์ œ์œ„๋ฐ˜์œจ\n", "grade \n", "๊ณ ์œ„ํ—˜ 22 1.00\n", "์ฃผ์˜ 68 0.72\n", "๊ด€์‹ฌ 110 0.07\n", "๊ณ ์œ„ํ—˜ยท์ฃผ์˜ ์„ ๋ณ„: ์‹ค์ œ ์œ„๋ฐ˜ ๊ณ„์•ฝ 79๊ฑด ์ค‘ 71๊ฑด ํฌ์ฐฉ(์žฌํ˜„์œจ 90%), ์„ ๋ณ„ 90๊ฑด ์ค‘ ์ •๋ฐ€๋„ 79%\n", "\n", "[๋ฃฐ๋ณ„ ์ ์ค‘ ํ˜„ํ™ฉ]\n", " ๋ฃฐ ์ด๋ฆ„ ๊ฐ€์ค‘์น˜ ์ ์ค‘ ๊ทผ๊ฑฐ ์กฐํ•ญ\n", "R01 ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.0/1000 ๋ฏธ๋งŒ 3 10 ๊ฐ€์ƒ์ง€์นจ 5์กฐ1ํ•ญ(์ง€์ฒด์ƒ๊ธˆ)\n", "R02 ์„ ๊ธˆ ๋น„์œจ 40% ์ดˆ๊ณผ 2 11 ๊ฐ€์ƒ์ง€์นจ 6์กฐ1ํ•ญ(์„ ๊ธˆ ํ•œ๋„)\n", "R03 ์„ ๊ธˆ 40% ์ดˆ๊ณผ์ธ๋ฐ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ 3 5 ๊ฐ€์ƒ์ง€์นจ 6์กฐ2ํ•ญ(์„ ๊ธˆ๊ณผ ๋ณด์ฆ)\n", "R04 300๋ฐฑ๋งŒ์› ์ด์ƒ ์ˆ˜์˜๊ณ„์•ฝ 3 10 ๊ฐ€์ƒ์ง€์นจ 3์กฐ1ํ•ญ(์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„)\n", "R05 500๋ฐฑ๋งŒ์› ์ด์ƒ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ 2 4 ๊ฐ€์ƒ์ง€์นจ 6์กฐ3ํ•ญ(์ดํ–‰๋ณด์ฆ)\n", "R06 ๋ฌผํ’ˆ ํ•˜์ž๋ณด์ฆ 12๊ฐœ์›” ๋ฏธ๋งŒ 2 5 ๊ฐ€์ƒ์ง€์นจ 7์กฐ1ํ•ญ(ํ•˜์ž๋ณด์ฆ)\n", "R07 ํ•ด์ง€ ์กฐํ•ญ ์—†์Œ 2 8 ๊ฐ€์ƒ์ง€์นจ 8์กฐ1ํ•ญ(๊ณ„์•ฝ ํ•ด์ง€)\n", "R08 ๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ 3 5 ๊ฐ€์ƒ์ง€์นจ 9์กฐ1ํ•ญ(๋ณด์•ˆ ์กฐํ•ญ)\n", "R09 ์ง€์‹์žฌ์‚ฐ ์กฐํ•ญ ์—†์Œ(๋ฌผํ’ˆยท์šฉ์—ญ) 1 9 ๊ฐ€์ƒ์ง€์นจ 9์กฐ2ํ•ญ(์ง€์‹์žฌ์‚ฐ)\n", "R10 ํ•˜๋„๊ธ‰ ๋น„์œจ 50% ์ดˆ๊ณผ 3 8 ๊ฐ€์ƒ์ง€์นจ 4์กฐ1ํ•ญ(ํ•˜๋„๊ธ‰ ์ œํ•œ)\n", "R11 ๊ฒ€์‚ฌ ๊ธฐ๊ฐ„ 14์ผ ์ดˆ๊ณผ 1 21 ๊ฐ€์ƒ์ง€์นจ 5์กฐ2ํ•ญ(๊ฒ€์‚ฌ ๊ธฐ๊ฐ„)\n", "R12 ๋‚ฉํ’ˆ ๊ธฐํ•œ์ด ๊ณ„์•ฝ ๊ธฐ๊ฐ„ ์ดˆ๊ณผ 3 7 ๊ฐ€์ƒ์ง€์นจ 5์กฐ3ํ•ญ(๋‚ฉํ’ˆ ๊ธฐํ•œ)\n", "R13 180์ผ ์ด์ƒ ๊ณ„์•ฝ์— ๋ฌผ๊ฐ€๋ณ€๋™ ์กฐํ•ญ ์—†์Œ 1 38 ๊ฐ€์ƒ์ง€์นจ 6์กฐ4ํ•ญ(๋ฌผ๊ฐ€๋ณ€๋™)\n", "R14 1000๋ฐฑ๋งŒ์› ์ด์ƒ ๋น„๊ฒฝ์Ÿ ๊ณ„์•ฝ 2 3 ๊ฐ€์ƒ์ง€์นจ 3์กฐ2ํ•ญ(๊ฒฝ์Ÿ ๊ณ„์•ฝ ์›์น™)\n", "R15 ๊ณ„์•ฝ ๊ธฐ๊ฐ„ 365์ผ ์ดˆ๊ณผ 1 24 ๊ฐ€์ƒ์ง€์นจ 3์กฐ3ํ•ญ(์žฅ๊ธฐ ๊ณ„์•ฝ)\n", "R16 ๋ถˆ๊ณต์ •ยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ ํฌํ•จ 3 61 ๊ฐ€์ƒ์ง€์นจ 8์กฐ2ํ•ญ(๋ถˆ๊ณต์ • ์กฐํ•ญ)\n", "\n", "[๊ณ ์œ„ํ—˜ ์ƒ์œ„ 3๊ฑด๊ณผ ๊ทผ๊ฑฐ]\n", " C-0108 ๋ฌผํ’ˆ ์ˆ˜์˜๊ณ„์•ฝ 961๋ฐฑ๋งŒ์› ์ ์ˆ˜ 12 ๊ณ ์œ„ํ—˜\n", " - R01 ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.0/1000 ๋ฏธ๋งŒ / ๊ฐ€์ƒ์ง€์นจ 5์กฐ1ํ•ญ(์ง€์ฒด์ƒ๊ธˆ)\n", " - R04 300๋ฐฑ๋งŒ์› ์ด์ƒ ์ˆ˜์˜๊ณ„์•ฝ / ๊ฐ€์ƒ์ง€์นจ 3์กฐ1ํ•ญ(์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„)\n", " - R12 ๋‚ฉํ’ˆ ๊ธฐํ•œ์ด ๊ณ„์•ฝ ๊ธฐ๊ฐ„ ์ดˆ๊ณผ / ๊ฐ€์ƒ์ง€์นจ 5์กฐ3ํ•ญ(๋‚ฉํ’ˆ ๊ธฐํ•œ)\n", " C-0062 ์šฉ์—ญ ์ˆ˜์˜๊ณ„์•ฝ 1109๋ฐฑ๋งŒ์› ์ ์ˆ˜ 11 ๊ณ ์œ„ํ—˜\n", " - R01 ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.0/1000 ๋ฏธ๋งŒ / ๊ฐ€์ƒ์ง€์นจ 5์กฐ1ํ•ญ(์ง€์ฒด์ƒ๊ธˆ)\n", " - R04 300๋ฐฑ๋งŒ์› ์ด์ƒ ์ˆ˜์˜๊ณ„์•ฝ / ๊ฐ€์ƒ์ง€์นจ 3์กฐ1ํ•ญ(์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„)\n", "... (์ดํ•˜ ์ƒ๋žต)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 04. LLM/์ž์—ฐ์–ด์ฒ˜๋ฆฌ ๊ธฐ๋ฐ˜ ๋…์†Œ ์กฐํ•ญ, ๋‚ฉ๊ธฐ ์ง€์ฒด ์œ„ํ—˜ ๋ฌธ๊ตฌ ์ž๋™ ์‹๋ณ„ ๋ชจ๋ธ ๊ณ ๋„ํ™”\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ž์—ฐ์–ด์ฒ˜๋ฆฌ ๊ธฐ๋ฐ˜ ๋…์†Œ ์กฐํ•ญ ๋ฐ ๋‚ฉ๊ธฐ ์ง€์ฒด ์œ„ํ—˜ ๋ฌธ๊ตฌ ์‹๋ณ„ ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์ž์—ฐ์–ด์ฒ˜๋ฆฌ(๊ธ€์ž n-gram TF-IDF์™€ ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€)๋กœ ๋…์†Œ ์กฐํ•ญ๊ณผ ๋‚ฉ๊ธฐ ์ง€์ฒด ์œ„ํ—˜ ๋ฌธ๊ตฌ๋ฅผ ์‹๋ณ„ํ•˜๋Š” ๋ชจ๋ธ์„ ๋งŒ๋“ค๊ณ  ์ˆ˜์ • ๊ถŒ๊ณ  ๋ฌธ์•ˆ์„ ์ œ์‹œํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์กฐํ•ญ ๋ฌธ์žฅ์„ ์ •์ƒยท๋…์†Œยท๋‚ฉ๊ธฐ์œ„ํ—˜ ์„ธ ์ข…๋ฅ˜๋กœ ๋ถ„๋ฅ˜ํ•˜๋Š” ๋ชจ๋ธ์„ ํ•™์Šตํ•˜๊ณ , ํ•™์Šต์— ์“ด ๋ฌธ์žฅ ํ‹€๊ณผ ์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„ ๋‘ ์‹œํ—˜ ์„ธํŠธ๋กœ ์ •ํ™•๋„๋ฅผ ๊ณ„์‚ฐํ•ด ๋‹จ์ˆœ ํ‚ค์›Œ๋“œ ๊ธฐ์ค€์„ ๊ณผ ๋น„๊ต ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/ai/prc01_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-gram TF-IDF ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€ ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด ์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„๊นŒ์ง€ ์ •ํ™•๋„ 85% ์ด์ƒ์„ ํ™•์ธํ•˜๊ณ , ๋‹จ์ˆœ ํ‚ค์›Œ๋“œ ๊ธฐ์ค€์„ ๊ณผ ๋น„๊ตํ•˜๋ฉฐ, ์œ„ํ—˜ ์กฐํ•ญ๋งˆ๋‹ค ๊ทผ๊ฑฐ ํ‘œํ˜„๊ณผ ํ‘œ์ค€ ๋ฌธ์•ˆ Top-3 ์ˆ˜์ • ๊ถŒ๊ณ (์œ ์‚ฌ๋„ ์ ์ˆ˜)๋ฅผ ์ œ์‹œํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.metrics import confusion_matrix\n", "from sklearn.metrics.pairwise import cosine_similarity\n", "from sklearn.pipeline import make_pipeline\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์กฐ๋‹ฌ๊ณ„ํš์•ˆยท๊ณ„์•ฝ์„œ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ณ„์•ฝยท์—…์ฒดยท๋ฒ•๋ น ์กฐ๋ฌธ ์—†์Œ)\n", "# ๊ณ„์•ฝ ์†์„ฑ 16๊ฐœ + ์กฐํ•ญ ๋ฌธ์žฅ 6๊ฐœ๋ฅผ ๋งŒ๋“ค๊ณ , ์†์„ฑ ์œ„๋ฐ˜ยท๋…์†Œ ์กฐํ•ญยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ์„ž์ธ '๊ทœ์ • ์œ„๋ฐ˜ ์—ฌ๋ถ€'๋ฅผ ํ•จ๊ป˜ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "TYPES = [\"๋ฌผํ’ˆ\", \"์šฉ์—ญ\", \"๊ณต์‚ฌ\"]\n", "METHODS = [\"์ผ๋ฐ˜๊ฒฝ์Ÿ\", \"์ œํ•œ๊ฒฝ์Ÿ\", \"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]\n", "CLASSES = [\"์ •์ƒ\", \"๋…์†Œ\", \"๋‚ฉ๊ธฐ์œ„ํ—˜\"]\n", "OBJ = [\"๋‚ฉํ’ˆ ๋ฌผํ’ˆ\", \"์šฉ์—ญ ๊ฒฐ๊ณผ๋ฌผ\", \"์‹œ๊ณต ๋ชฉ์ ๋ฌผ\", \"์ •๋น„ ์„ฑ๊ณผ๋ฌผ\"]\n", "\n", "N_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋‚ฉ๊ธฐ๋ฅผ ์ง€์ฒดํ•œ ๊ฒฝ์šฐ ์ง€์ฒด์ผ์ˆ˜ 1์ผ๋งˆ๋‹ค ๊ณ„์•ฝ๊ธˆ์•ก์˜ {p}/1000์— ํ•ด๋‹นํ•˜๋Š” ์ง€์ฒด์ƒ๊ธˆ์„ ๋ถ€๊ณผํ•œ๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๊ฐ€ ํ™•์ธ๋œ ๊ฒฝ์šฐ์— ํ•œํ•˜์—ฌ ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์Šน์ธ์„ ๋ฐ›์•„ ๋ฉด์ œํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ ์—ฐ์žฅ์€ ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๋ฅผ ํ™•์ธํ•˜๊ณ  ์„œ๋ฉด์œผ๋กœ ์Šน์ธํ•œ ๊ฒฝ์šฐ์—๋งŒ ์ธ์ •ํ•œ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋‚ฉํ’ˆ๋œ {o}์„ {d}์ผ ์ด๋‚ด์— ๊ฒ€์‚ฌํ•˜๊ณ  ํ•ฉ๊ฒฉํ•œ ํ›„์— ๋Œ€๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ํ•˜๋„๊ธ‰์„ ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ์‚ฌ์ „์— ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์„œ๋ฉด ์Šน์ธ์„ ๋ฐ›์•„์•ผ ํ•œ๋‹ค.\",\n", " \"์–ด๋А ํ•œ์ชฝ์ด ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•˜๋ ค๋ฉด ์‚ฌ์œ ๋ฅผ ๋ช…์‹œํ•ด {d}์ผ ์ „์— ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"{o}์— ํ•˜์ž๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ๋ณด์ฆ ๊ธฐ๊ฐ„ ์•ˆ์— ๋ฌด์ƒ์œผ๋กœ ๋ณด์ˆ˜ํ•˜๊ฑฐ๋‚˜ ๊ต์ฒดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ๋น„๊ณต๊ฐœ ์‚ฌํ•ญ์€ ์ œ3์ž์—๊ฒŒ ๋ˆ„์„คํ•˜์ง€ ์•Š์œผ๋ฉฐ ์œ„๋ฐ˜ ์‹œ ์ฑ…์ž„์„ ์ง„๋‹ค.\",\n", " \"{o}์— ๋Œ€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์˜ ๊ท€์†๊ณผ ์‚ฌ์šฉ ๋ฒ”์œ„๋Š” ๋ณ„๋„ ํ•ฉ์˜์„œ์— ๋”ฐ๋ผ ์ •ํ•œ๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ํ•ฉ๊ฒฉ ํ›„ ์ฒญ๊ตฌ์„œ๋ฅผ ์ ‘์ˆ˜ํ•œ ๋‚ ๋ถ€ํ„ฐ {d}์ผ ์ด๋‚ด์— ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ๊ธˆ์•ก์˜ ์กฐ์ •์€ ๋ฌผ๊ฐ€๋ณ€๋™ ๊ธฐ์ค€์— ๋”ฐ๋ผ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ์„œ๋ฉด์œผ๋กœ ํ•ฉ์˜ํ•œ ๊ฒฝ์šฐ์—๋งŒ ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ดํ–‰๋ณด์ฆ์„œ๋ฅผ ๊ณ„์•ฝ ์ฒด๊ฒฐ ์‹œ ์ œ์ถœํ•˜๋ฉฐ ๋ณด์ฆ๊ธˆ์€ ๊ณ„์•ฝ ์ดํ–‰ ์™„๋ฃŒ ํ›„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ณ„์•ฝ์„œ ๋ณ„ํ‘œ์˜ ์ผ์ž๋ฅผ ๋”ฐ๋ฅด๋ฉฐ ๋ณ€๊ฒฝ ์‹œ ์„œ๋ฉด ํ•ฉ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผํ•˜๋ฉฐ ์ด์˜๋Š” ์ •ํ•ด์ง„ ์ ˆ์ฐจ๋กœ ์ œ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•˜์ง€ ๋ชปํ•œ {o}์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๊ต์ฒดํ•˜๋ฉฐ ๊ทธ ๋น„์šฉ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ๊ท€์ฑ… ์‚ฌ์œ ๋กœ ์ดํ–‰์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•˜๊ณ  ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"์ง€์—ฐ์ด ์˜ˆ์ƒ๋˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ๋ฐœ์ฃผ๊ธฐ๊ด€์— ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•œ๋‹ค.\",\n", " \"๋ณด์ฆ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง€๊ณ  ๋ณด์ˆ˜ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์€ ๋จผ์ € ํ˜‘์˜๋กœ ํ•ด๊ฒฐํ•˜๊ณ  ํ˜‘์˜๊ฐ€ ๋˜์ง€ ์•Š์œผ๋ฉด ๊ด€ํ•  ๋ฒ•์›์˜ ํŒ๋‹จ์— ๋”ฐ๋ฅธ๋‹ค.\",\n", "]\n", "T_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ง€์ฒด์ƒ๊ธˆ์€ ์ „์•ก ๋ฉด์ œํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋Œ€ํ•ด ์ด์˜๋ฅผ ์ œ๊ธฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ฆ‰์‹œ ๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ ๋ณด๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ดํ›„ ๊ฒ€์‚ฌ๋ฅผ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์–ธ์ œ๋“ ์ง€ ์ผ๋ฐฉ์ ์œผ๋กœ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ์— ๋”ฐ๋ฅธ ์†ํ•ด๋Š” ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"{o}์˜ ์ถ”๊ฐ€ ๋น„์šฉ์€ ์‚ฌ์œ ๋ฅผ ๋ถˆ๋ฌธํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ์ „์•ก ๋ถ€๋‹ดํ•˜๋ฉฐ ๋ณ„๋„ ํ˜‘์˜ ์—†์ด ์ฒญ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"{o}์— ๊ด€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์€ ๋ชจ๋‘ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ๊ท€์†๋˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"ํ•˜๋„๊ธ‰์€ ์‹ ๊ณ  ์—†์ด ์ œํ•œ ์—†์ด ํ—ˆ์šฉํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ํ•˜๋„๊ธ‰ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ์ „์— ์ „์•ก ์„ ์ง€๊ธ‰ํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ฐ˜ํ™˜ ์ฒญ๊ตฌ๊ถŒ์„ ํฌ๊ธฐํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ์‚ฌํ•ญ์˜ ๋ˆ„์„ค๋กœ ์ธํ•œ ์ฑ…์ž„์„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"ํ•˜์ž ๋ณด์ˆ˜ ์ฑ…์ž„์€ ์ธ๋„ ํ›„ ์ฆ‰์‹œ ์†Œ๋ฉธํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ณด์ˆ˜๋ฅผ ์š”๊ตฌํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฒญ๊ตฌํ•˜๋Š” ๊ธˆ์•ก์„ ๊ฑฐ์ ˆํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ๊ฑฐ์ ˆํ•˜๋ฉด ์œ„์•ฝ๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์ด ์ƒ๊ธฐ๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ •ํ•˜๋Š” ์ค‘์žฌ ๊ธฐ๊ด€์—์„œ๋งŒ ํ•ด๊ฒฐํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์‚ฌ๋Š” ์ƒ๋žตํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์‹œํ—˜ ๊ฒฐ๊ณผ์„œ๋กœ ๋Œ€์‹ ํ•œ๋‹ค.\",\n", " \"๊ฒฌ์  ๊ธˆ์•ก์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ๊ทธ๋Œ€๋กœ ํ™•์ •ํ•˜๊ณ  ์ฆ๋น™ ์ž๋ฃŒ๋Š” ๋ฐ›์ง€ ์•Š๋Š”๋‹ค.\",\n", "]\n", "D_TPL = [\n", " \"๋‚ฉ๊ธฐ๋Š” ์ž์žฌ ์ˆ˜๊ธ‰ ์‚ฌ์ •์— ๋”ฐ๋ผ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ฐ€๋Šฅํ•œ ํ•œ ์ง€ํ‚ค๋„๋ก ๋…ธ๋ ฅํ•˜๋ฉฐ ์ง€์—ฐ๋˜์–ด๋„ ์‚ฌ์œ ๋ฅผ ๋ณด๊ณ ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณต๊ธ‰ ์ง€์—ฐ์ด๋‚˜ ์„ ํ–‰ ์—…์ฒด ์‚ฌ์ •๋„ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ์œผ๋กœ ๋ณด์•„ ๋‚ฉ๊ธฐ๋ฅผ ์ž๋™ ์—ฐ์žฅํ•œ๋‹ค.\",\n", " \"์ฐฉ์ˆ˜์ผ์€ ์„ ํ–‰ ์—…์ฒด์˜ ๋‚ฉํ’ˆ์ด ๋๋‚œ ๋’ค ๋ณ„๋„๋กœ ์ •ํ•˜๋ฉฐ ์™„๋ฃŒ ๊ธฐํ•œ์€ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ์ผ ์ดํ›„ 1ํšŒ์— ํ•œํ•ด ๊ธฐํ•œ ์—†์ด ์ž๋™ ์—ฐ์žฅ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํ•œ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๋Š” ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ƒ์‚ฐ ์ผ์ •์ด ํ™•์ •๋˜๋Š” ๋Œ€๋กœ ํ†ต๋ณดํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์‹œ์ ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž ์‚ฌ์ •์ด ํ—ˆ๋ฝํ•˜๋Š” ๋•Œ๋กœ ํ•˜๊ณ  ๋‚ ์งœ๋Š” ๋”ฐ๋กœ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ž์žฌ๊ฐ€ ๋Šฆ๊ฒŒ ๋“ค์–ด์˜ค๋ฉด ์™„๋ฃŒ ์ผ์ •์€ ๊ทธ๋งŒํผ ์ž๋™์œผ๋กœ ๋Šฆ์ถฐ์ง„๋‹ค.\",\n", " \"๊ธฐํ•œ ๋‚ด์— ์ธ๋„ํ•˜์ง€ ๋ชปํ•ด๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ •์ด ๋Šฆ์–ด์ ธ๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ƒˆ ๊ณ„ํš์„œ๋ฅผ ๋‚ผ ํ•„์š”๊ฐ€ ์—†๋‹ค.\",\n", "]\n", "# ํ•™์Šต์— ์“ฐ์ง€ ์•Š๋Š” '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'(๋ชจ๋ธ์ด ๋ฌธ์žฅ ํ‹€์„ ์™ธ์› ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์šฉ๋„)\n", "HOLD = {\n", " \"์ •์ƒ\": [\"๊ฒ€์‚ฌ์— ๋ถˆํ•ฉ๊ฒฉํ•œ ๊ฒฝ์šฐ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ • ๊ธฐํ•œ ์•ˆ์— ์žฌ๋‚ฉํ’ˆํ•˜๋ฉฐ ์žฌ๊ฒ€์‚ฌ ๋น„์šฉ์€ ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ฒ•๋ น์„ ์œ„๋ฐ˜ํ•˜์—ฌ ๊ณ„์•ฝ์„ ์ดํ–‰ํ•˜์ง€ ๋ชปํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด ํ†ต์ง€ ํ›„ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๊ฐ€ ์ง€์—ฐ๋  ์šฐ๋ ค๊ฐ€ ์žˆ์œผ๋ฉด ์ƒ๋Œ€์ž๋Š” ์ฆ‰์‹œ ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"ํ•˜์ž๋‹ด๋ณด ์ฑ…์ž„ ๊ธฐ๊ฐ„์€ ์ธ์ˆ˜์ผ๋ถ€ํ„ฐ ๊ธฐ์‚ฐํ•˜๋ฉฐ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง„๋‹ค.\"],\n", " \"๋…์†Œ\": [\"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๋Œ€๊ธˆ์„ ์ด์œ  ์—†์ด ๊ฑฐ์ ˆํ•˜์ง€ ๋ชปํ•˜๋ฉฐ ๊ฑฐ์ ˆ ์‹œ ์œ„์•ฝ๊ธˆ์„ ๋‚ธ๋‹ค.\",\n", " \"๊ณ„์•ฝ ๋‚ด์šฉ์— ๊ด€ํ•œ ๋ถ„์Ÿ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€์—์„œ๋งŒ ๋‹ค๋ฃจ๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์ˆ˜ ๊ถŒํ•œ์€ ํฌ๊ธฐํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ž์ฒด ์‹œํ—˜ ์„ฑ์ ์„œ๋กœ ๊ฐˆ์Œํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ œ์ถœํ•œ ๊ฒฌ์ ์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ํ™•์ • ๋‹จ๊ฐ€๋กœ ํ•˜๋ฉฐ ์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"๋‚ฉํ’ˆ ์‹œ๊ธฐ๋Š” ์ƒ๋Œ€์ž์˜ ํ˜•ํŽธ์ด ํ—ˆ๋ฝํ•˜๋Š” ์‹œ์ ์œผ๋กœ ํ•˜๊ณ  ๋ณ„๋„ ๋‚ ์งœ๋Š” ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์›์ž์žฌ ํ™•๋ณด๊ฐ€ ๋Šฆ์–ด์ง€๋ฉด ์™„๋ฃŒ ์ผ์ž๋Š” ์ž๋™์œผ๋กœ ๋’ค๋กœ ๋ฏธ๋ค„์ง€๋Š” ๊ฒƒ์œผ๋กœ ๋ณธ๋‹ค.\",\n", " \"์ธ๋„ ๊ธฐํ•œ์€ ์ฐธ๊ณ ์šฉ์ด๋ฉฐ ์ง€ํ‚ค์ง€ ๋ชปํ•ด๋„ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ • ์ง€์—ฐ์ด ์žˆ์–ด๋„ ์ƒ๋Œ€์ž๋Š” ๋ณ€๊ฒฝ ๊ณ„ํš์„ ์ œ์ถœํ•  ์˜๋ฌด๊ฐ€ ์—†๋‹ค.\"],\n", "}\n", "SUFFIX = [\"\", \"\", \"\", \" ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ์‹œํ–‰ ์„ธ์น™์— ๋”ฐ๋ฅธ๋‹ค.\", \" ์ด ์กฐํ•ญ์€ ๊ณ„์•ฝ ์ „์ฒด์— ์ ์šฉํ•œ๋‹ค.\", \" ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ํ˜‘์˜ํ•œ๋‹ค.\", \" ๊ด€๋ จ ์„œ์‹์€ ๋ณ„์ง€๋ฅผ ๋”ฐ๋ฅธ๋‹ค.\"]\n", "SWAP = [(\"๋ฐœ์ฃผ๊ธฐ๊ด€\", \"๊ณ„์•ฝ๋‹ด๋‹น๊ด€\"), (\"๊ณ„์•ฝ์ƒ๋Œ€์ž\", \"์—…์ฒด\")]\n", "\n", "\n", "def make_clause(rng, cls, hold=False, idx=None):\n", " \"\"\"์กฐํ•ญ ํ•œ ๋ฌธ์žฅ ์ƒ์„ฑ. ํ‘œํ˜„ ์ผ๋ถ€๋Š” ๋™์˜์–ด๋กœ ๋ฐ”๊พธ๊ณ  ์ ‘๋ฏธ ๋ฌธ๊ตฌ๋ฅผ ๋ฌด์ž‘์œ„๋กœ ๋ถ™์ธ๋‹ค(idx๋ฅผ ์ฃผ๋ฉด ๊ทธ ๋ฒˆํ˜ธ์˜ ๋ฌธ์žฅ ํ‹€์„ ์‚ฌ์šฉ).\"\"\"\n", " if idx is not None:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[idx].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " elif hold:\n", " s = HOLD[cls][rng.integers(len(HOLD[cls]))]\n", " else:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[rng.integers(len(tpl))].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " if rng.random() < 0.3:\n", " a, b = SWAP[rng.integers(2)]\n", " s = s.replace(a, b)\n", " return s + SUFFIX[rng.integers(len(SUFFIX))]\n", "\n", "\n", "def make_contracts(n=200, seed=7):\n", " \"\"\"๊ฐ€์ƒ ๊ณ„์•ฝ n๊ฑด(์†์„ฑ 16๊ฐœ + ์กฐํ•ญ 6๊ฐœ + ๊ทœ์ • ์œ„๋ฐ˜ ์ •๋‹ต)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " sl = rng.beta(1.2, 3.2, n) # ๊ณ„์•ฝ์„œ ์ž‘์„ฑ์ด ํ—ˆ์ˆ ํ•œ ์ •๋„(์ž ์žฌ ๋ณ€์ˆ˜)\n", " ctype = rng.choice(TYPES, n, p=[.5, .3, .2])\n", " amount = np.clip(np.exp(rng.normal(np.log(220), 1.1, n)), 5, 5000).round(0)\n", " method = np.where(rng.random(n) < .06 + .3 * sl, \"์ˆ˜์˜๊ณ„์•ฝ\", rng.choice(METHODS[:3], n, p=[.5, .3, .2]))\n", " period = np.array([rng.integers(60, 300) if t == \"๋ฌผํ’ˆ\" else rng.integers(90, 400) if t == \"์šฉ์—ญ\" else rng.integers(180, 720) for t in ctype])\n", " delivery = (period * rng.uniform(.5, 1.0, n)).astype(int)\n", " bad_logic = rng.random(n) < .015 * (1 + 3 * sl)\n", " delivery = np.where(bad_logic, period + rng.integers(10, 60, n), delivery)\n", " pen = np.where(rng.random(n) < .22 * sl, rng.choice([.3, .5, .8], n), rng.choice([1.0, 1.5, 2.0, 2.5], n))\n", " adv = np.where(rng.random(n) < .22 * sl, rng.choice([50, 60, 70], n), rng.choice([0, 10, 20, 30, 40], n))\n", " bond = np.where(rng.random(n) < .25 * sl + .5 * (adv > 40), rng.choice([0, 5], n), rng.choice([10, 10, 15, 20], n))\n", " war = np.array([rng.choice([3, 6]) if (rng.random() < .25 * s and t == \"๋ฌผํ’ˆ\") else rng.choice([12, 24, 36]) if t == \"๋ฌผํ’ˆ\" else rng.choice([0, 6, 12]) if t == \"์šฉ์—ญ\" else rng.choice([24, 36, 60]) for t, s in zip(ctype, sl)])\n", " insp = np.where(rng.random(n) < .3 * sl, rng.choice([21, 30], n), rng.choice([5, 7, 10, 14], n))\n", " sub = np.where(rng.random(n) < .15 * sl, rng.choice([60, 70, 80], n), rng.choice([0, 10, 20, 30, 40, 50], n))\n", " padj = rng.random(n) < .75 - .4 * sl\n", " term = rng.random(n) > .015 * (1 + 6 * sl)\n", " sec = rng.random(n) > .015 * (1 + 6 * sl)\n", " ip = rng.random(n) > .03 * (1 + 4 * sl)\n", " clauses, cls_true = [], []\n", " for i in range(n):\n", " pt, pd_ = .01 + .08 * sl[i], .02 + .08 * sl[i]\n", " cl = [rng.choice(CLASSES, p=[1 - pt - pd_, pt, pd_]) for _ in range(6)]\n", " cls_true.append(cl); clauses.append([make_clause(rng, c) for c in cl])\n", " df = pd.DataFrame({\"contract_id\": [f\"C-{i + 1:04d}\" for i in range(n)], \"ctype\": ctype, \"method\": method, \"amount_m\": amount,\n", " \"period_days\": period, \"delivery_days\": delivery, \"penalty_permil\": pen, \"advance_pct\": adv, \"perf_bond_pct\": bond,\n", " \"warranty_months\": war, \"inspection_days\": insp, \"subcontract_pct\": sub, \"price_adjust\": padj,\n", " \"termination\": term, \"security\": sec, \"ip_clause\": ip})\n", " hard = ((pen < 1.0) | ((adv > 40) & (bond < 10)) | ((amount >= 300) & (method == \"์ˆ˜์˜๊ณ„์•ฝ\")) | ((ctype == \"๋ฌผํ’ˆ\") & (war < 12))\n", " | ~term | ~sec | (sub > 50) | (delivery > period))\n", " toxic = np.array([c.count(\"๋…์†Œ\") for c in cls_true])\n", " delay = np.array([c.count(\"๋‚ฉ๊ธฐ์œ„ํ—˜\") for c in cls_true])\n", " split = (method == \"์ˆ˜์˜๊ณ„์•ฝ\") & (amount >= 250) & (amount < 300) & (rng.random(n) < .6) # ๊ณ„์•ฝ ์ชผ๊ฐœ๊ธฐ ์˜์‹ฌ(์ž„๊ณ„๊ฐ’ ๋ฐ”๋กœ ์•„๋ž˜)\n", " df[\"clauses\"], df[\"clause_cls\"] = clauses, cls_true\n", " df[\"n_toxic\"], df[\"n_delay\"] = toxic, delay\n", " viol = hard | (toxic > 0) | split | (delay >= 2)\n", " waived = hard & (toxic == 0) & (rng.random(n) < .12) # ๊ณ„์•ฝ๊ด€์ด ์˜ˆ์™ธ ์‚ฌ์œ ๋ฅผ ์ธ์ •ํ•ด ์œ„๋ฐ˜์œผ๋กœ ๋ณด์ง€ ์•Š์€ ๊ฑด(์ž๋ฃŒ์˜ ํ˜„์‹ค์  ์žก์Œ)\n", " hidden = ~viol & (rng.random(n) < .03 + .08 * sl) # ์†์„ฑยท๋ฌธ๊ตฌ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜(๋ณ„์ฒจ ํ•ฉ์˜ ๋“ฑ)\n", " df[\"violation\"] = (viol & ~waived) | hidden\n", " df[\"vtype\"] = np.where(hidden, \"๊ธฐํƒ€\", np.where(hard, \"์†์„ฑ์œ„๋ฐ˜\", np.where(toxic > 0, \"๋…์†Œ์กฐํ•ญ\", np.where(split | (delay >= 2), \"๊ธฐํƒ€\", \"-\"))))\n", " return df\n", "\n", "SIMPLE_KW = {\"๋…์†Œ\": [\"๋ฉด์ œ\", \"์ผ๋ฐฉ\", \"ํฌ๊ธฐ\", \"์ „์•ก\", \"๋ˆ„์„ค\", \"ํ•˜๋„๊ธ‰\", \"์„ ์ง€๊ธ‰\"], \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์—ฐ์žฅ\", \"์ž๋™\", \"๋…ธ๋ ฅ\", \"ํ†ต๋ณด\", \"๊ธฐํ•œ\"]}\n", "\n", "# ---- ์กฐํ•ญ ๋ฌธ์žฅ ๋ถ„๋ฅ˜(์ •์ƒ / ๋…์†Œ / ๋‚ฉ๊ธฐ์œ„ํ—˜): ๊ธ€์ž n-gram TF-IDF + ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€ ----\n", "def clause_set(seed, n, hold=False, mix=(.55, .25, .20)):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ n๊ฐœ์™€ ์ •๋‹ต ๋ถ„๋ฅ˜๋ฅผ ๋งŒ๋“ ๋‹ค. hold=True๋ฉด ํ•™์Šต์— ์“ฐ์ง€ ์•Š์€ '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'์œผ๋กœ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] LLM/์ž์—ฐ์–ด์ฒ˜๋ฆฌ ๊ธฐ๋ฐ˜ ๋…์†Œ ์กฐํ•ญ, ๋‚ฉ๊ธฐ ์ง€์ฒด ์œ„ํ—˜ ๋ฌธ๊ตฌ ์ž๋™ ์‹๋ณ„ ๋ชจ๋ธ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def fit_clause_model(seed=100, n=1200):\n", " \"\"\"ํ•™์Šต์šฉ ์กฐํ•ญ ๋ฌธ์žฅ์œผ๋กœ ๋ถ„๋ฅ˜ ๋ชจ๋ธ์„ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] LLM/์ž์—ฐ์–ด์ฒ˜๋ฆฌ ๊ธฐ๋ฐ˜ ๋…์†Œ ์กฐํ•ญ, ๋‚ฉ๊ธฐ ์ง€์ฒด ์œ„ํ—˜ ๋ฌธ๊ตฌ ์ž๋™ ์‹๋ณ„ ๋ชจ๋ธ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def keyword_pred(s):\n", " \"\"\"๋น„๊ต์šฉ ๊ธฐ์ค€์„ : ๋‹จ์ˆœ ํ‚ค์›Œ๋“œ(๋ถ€์ • ํ‘œํ˜„ยท๋ฌธ๋งฅ์„ ๋ณด์ง€ ์•Š์Œ) ์ ์ค‘์œผ๋กœ๋งŒ ๋ถ„๋ฅ˜.\"\"\"\n", " if any(k in s for k in SIMPLE_KW[\"๋…์†Œ\"]):\n", " return \"๋…์†Œ\"\n", " return \"๋‚ฉ๊ธฐ์œ„ํ—˜\" if any(k in s for k in SIMPLE_KW[\"๋‚ฉ๊ธฐ์œ„ํ—˜\"]) else \"์ •์ƒ\"\n", "\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ‘œ์ค€ ๋ฌธ์•ˆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(์ˆ˜์ • ๊ถŒ๊ณ ์šฉ, ์‹ค์ œ ํ‘œ์ค€๊ณ„์•ฝ์„œ ์กฐ๋ฌธ์ด ์•„๋‹˜)\n", "STD = {\"์ง€์ฒด์ƒ๊ธˆ\": \"๋‚ฉ๊ธฐ๋ฅผ ์ง€์ฒดํ•˜๋ฉด ์ง€์ฒด์ผ์ˆ˜๋งˆ๋‹ค ๊ณ„์•ฝ๊ธˆ์•ก์˜ 1/1000 ์ด์ƒ์— ํ•ด๋‹นํ•˜๋Š” ์ง€์ฒด์ƒ๊ธˆ์„ ๋ถ€๊ณผํ•˜๋ฉฐ ๋ฉด์ œ๋Š” ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๊ฐ€ ํ™•์ธ๋˜๊ณ  ์„œ๋ฉด ์Šน์ธ์„ ๋ฐ›์€ ๊ฒฝ์šฐ์— ํ•œ๋‹ค.\",\n", " \"๊ฒ€์‚ฌ\": \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋‚ฉํ’ˆ๋œ ๋ฌผํ’ˆ์„ ์ •ํ•ด์ง„ ๊ธฐ๊ฐ„ ์•ˆ์— ๊ฒ€์‚ฌํ•˜๊ณ  ํ•ฉ๊ฒฉํ•œ ํ›„์— ๋Œ€๊ธˆ์„ ์ง€๊ธ‰ํ•˜๋ฉฐ ๊ฒ€์‚ฌ ๊ถŒํ•œ์€ ์ƒ๋žตํ•˜๊ฑฐ๋‚˜ ๋Œ€์ฒดํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"ํ•ด์ง€\": \"๊ณ„์•ฝ ํ•ด์ง€๋Š” ์‚ฌ์œ ๋ฅผ ๋ช…์‹œํ•˜๊ณ  ์ •ํ•ด์ง„ ๊ธฐ๊ฐ„ ์ „์— ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•ด์•ผ ํ•˜๋ฉฐ ์ฑ…์ž„ ์žˆ๋Š” ์ชฝ์ด ์†ํ•ด๋ฅผ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"์ถ”๊ฐ€๋น„์šฉ\": \"์ถ”๊ฐ€ ๋น„์šฉ์€ ์‚ฌ์ „์— ํ˜‘์˜ํ•ด ์„œ๋ฉด์œผ๋กœ ํ•ฉ์˜ํ•œ ๋ฒ”์œ„์—์„œ๋งŒ ์ธ์ •ํ•˜๋ฉฐ ์ฆ๋น™์„ ๊ฐ–์ถ”์–ด ์ฒญ๊ตฌํ•œ๋‹ค.\",\n", " \"์ง€์‹์žฌ์‚ฐ\": \"์ง€์‹์žฌ์‚ฐ๊ถŒ์˜ ๊ท€์†๊ณผ ์‚ฌ์šฉ ๋ฒ”์œ„๋Š” ๋ณ„๋„ ํ•ฉ์˜์„œ๋กœ ์ •ํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ชฉ์ ๋ฌผ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"ํ•˜๋„๊ธ‰\": \"ํ•˜๋„๊ธ‰์€ ์‚ฌ์ „์— ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์„œ๋ฉด ์Šน์ธ์„ ๋ฐ›์•„์•ผ ํ•˜๋ฉฐ ํ•˜๋„๊ธ‰ ๋‚ด์šฉ์€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.\",\n", " \"๋Œ€๊ธˆ\": \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ํ•ฉ๊ฒฉ ํ›„ ์ฒญ๊ตฌ๋ฅผ ๋ฐ›์•„ ์ •ํ•ด์ง„ ๊ธฐ๊ฐ„ ์•ˆ์— ์ง€๊ธ‰ํ•˜๊ณ  ์„ ์ง€๊ธ‰์€ ๋ณด์ฆ์„ ๊ฐ–์ถ˜ ๋ฒ”์œ„์—์„œ๋งŒ ํ•œ๋‹ค.\",\n", " \"๋ณด์•ˆ\": \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ๋น„๊ณต๊ฐœ ์‚ฌํ•ญ์€ ๋ˆ„์„คํ•˜์ง€ ์•Š์œผ๋ฉฐ ์œ„๋ฐ˜ ์‹œ ์ฑ…์ž„์„ ์ง„๋‹ค.\",\n", " \"ํ•˜์ž\": \"๋ณด์ฆ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง€๊ณ  ๋ฌด์ƒ์œผ๋กœ ๋ณด์ˆ˜ํ•˜๊ฑฐ๋‚˜ ๊ต์ฒดํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ\": \"๋ถ„์Ÿ์€ ๋จผ์ € ํ˜‘์˜๋กœ ํ•ด๊ฒฐํ•˜๊ณ  ํ˜‘์˜๊ฐ€ ๋˜์ง€ ์•Š์œผ๋ฉด ๊ด€ํ•  ๋ฒ•์›์˜ ํŒ๋‹จ์— ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ\": \"๋‚ฉ๊ธฐ๋Š” ๊ณ„์•ฝ์„œ์— ๋‚ ์งœ๋กœ ๋ช…์‹œํ•˜๊ณ  ๋ณ€๊ฒฝ์€ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ  ํ™•์ธ ํ›„ ์„œ๋ฉด ์Šน์ธ์œผ๋กœ๋งŒ ํ•œ๋‹ค.\"}\n", "TOPIC_T = [\"์ง€์ฒด์ƒ๊ธˆ\", \"๊ฒ€์‚ฌ\", \"ํ•ด์ง€\", \"์ถ”๊ฐ€๋น„์šฉ\", \"์ง€์‹์žฌ์‚ฐ\", \"ํ•˜๋„๊ธ‰\", \"๋Œ€๊ธˆ\", \"๋ณด์•ˆ\", \"ํ•˜์ž\", \"๋Œ€๊ธˆ\", \"๋ถ„์Ÿ\", \"๊ฒ€์‚ฌ\", \"์ถ”๊ฐ€๋น„์šฉ\"] # T_TPL ์ˆœ์„œ์™€ ๋Œ€์‘\n", "\n", "model = fit_clause_model()\n", "Xa, ya = clause_set(200, 600) # ํ•™์Šต๊ณผ ๊ฐ™์€ ๋ฌธ์žฅ ํ‹€(์ƒˆ๋กœ ๋ฝ‘์€ ๋ฌธ์žฅ)\n", "Xb, yb = clause_set(201, 300, hold=True) # ํ•™์Šต์— ์—†๋Š” ์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„\n", "print(f\"[๋ฐ์ดํ„ฐ] ํ•™์Šต ์กฐํ•ญ 1,200๊ฐœ / ์‹œํ—˜ A(๊ฐ™์€ ๋ฌธ์žฅ ํ‹€) {len(Xa)}๊ฐœ / ์‹œํ—˜ B(์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„) {len(Xb)}๊ฐœ / ๋ถ„๋ฅ˜ 3์ข…: {', '.join(CLASSES)}\")\n", "\n", "pa, pb = model.predict(Xa), model.predict(Xb)\n", "acc_a, acc_b = np.mean(pa == np.array(ya)), np.mean(pb == np.array(yb))\n", "X_all, y_all, p_all = Xa + Xb, np.array(ya + yb), np.concatenate([pa, pb])\n", "acc = np.mean(p_all == y_all)\n", "kb = np.array([keyword_pred(s) for s in X_all])\n", "print(f\"\\n[๋ถ„๋ฅ˜ ์ •ํ™•๋„] ์‹œํ—˜ A {acc_a:.1%}, ์‹œํ—˜ B(์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„) {acc_b:.1%}, ์ „์ฒด {acc:.1%} (๋ชฉํ‘œ 85% ์ด์ƒ: {'๋‹ฌ์„ฑ' if acc >= 0.85 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\"๋‹จ์ˆœ ํ‚ค์›Œ๋“œ ๊ธฐ์ค€์„  ์ „์ฒด {np.mean(kb == y_all):.1%} (๋ถ€์ •ยท๋ฌธ๋งฅ์„ ๋ณด์ง€ ๋ชปํ•ด '๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผ' ๊ฐ™์€ ์ •์ƒ ๋ฌธ์žฅ์„ ์˜คํƒ)\")\n", "cm = pd.DataFrame(confusion_matrix(y_all, p_all, labels=CLASSES), index=[f\"์‹ค์ œ {c}\" for c in CLASSES], columns=[f\"์˜ˆ์ธก {c}\" for c in CLASSES])\n", "print(cm.to_string())\n", "risk_idx = y_all != \"์ •์ƒ\"\n", "rec_ = np.mean(p_all[risk_idx] != \"์ •์ƒ\")\n", "print(f\"์œ„ํ—˜ ์กฐํ•ญ(๋…์†Œยท๋‚ฉ๊ธฐ์œ„ํ—˜) ํฌ์ฐฉ๋ฅ  {rec_:.1%}, ์ •์ƒ ๋ฌธ์žฅ์„ ์œ„ํ—˜์œผ๋กœ ์˜คํƒํ•œ ๋น„์œจ {np.mean(p_all[~risk_idx] != '์ •์ƒ'):.1%}\")\n", "\n", "# ์˜ค๋ถ„๋ฅ˜ ์‚ฌ๋ก€\n", "print(\"\\n[์˜ค๋ถ„๋ฅ˜ ์‚ฌ๋ก€(์‹œํ—˜ B)]\")\n", "shown = 0\n", "for s, t, p in zip(Xb, yb, pb):\n", " if t != p and shown < 3:\n", " shown += 1; print(f\" ์‹ค์ œ {t} / ์˜ˆ์ธก {p}: {s[:48]}...\")\n", "if shown == 0:\n", " print(\" ์—†์Œ\")\n", "\n", "# ๊ทผ๊ฑฐ ํ‘œํ˜„: ํ•ด๋‹น ๋ถ„๋ฅ˜์— ๊ฐ€์žฅ ํฌ๊ฒŒ ๊ธฐ์—ฌํ•œ ๊ธ€์ž ์กฐ๊ฐ\n", "vec, lr = model[0], model[-1]\n", "names = np.array(vec.get_feature_names_out())\n", "\n", "\n", "def evidence(text, cls, k=3):\n", " x = vec.transform([text])\n", " contrib = x.multiply(lr.coef_[list(lr.classes_).index(cls)]).toarray()[0]\n", " return [names[j].strip() for j in np.argsort(-contrib)[:k] if contrib[j] > 0]\n", "\n", "\n", "# ํ‘œ์ค€ ๋ฌธ์•ˆ Top-3 ์ถ”์ฒœ(๊ณผ๊ฑฐ ์ฒ˜๋ฆฌ ์™„๋ฃŒ ์‚ฌ๋ก€์™€์˜ ์ผ์น˜๋„): ๋…์†Œ ์กฐํ•ญ 120๊ฐœ์™€ ๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ 60๊ฐœ\n", "rng = np.random.default_rng(5)\n", "sv = TfidfVectorizer(analyzer=\"char_wb\", ngram_range=(2, 3)).fit(list(STD.values()) + X_all)\n", "SM = sv.transform(list(STD.values()))\n", "topics = list(STD)\n", "tests = [(make_clause(rng, \"๋…์†Œ\", idx=i), TOPIC_T[i]) for i in rng.integers(0, len(T_TPL), 120)] + [(make_clause(rng, \"๋‚ฉ๊ธฐ์œ„ํ—˜\"), \"๋‚ฉ๊ธฐ\") for _ in range(60)]\n", "jn = topics.index(\"๋‚ฉ๊ธฐ\")\n", "\n", "\n", "def similarity(s):\n", " \"\"\"๋ฌธ์•ˆ ์œ ์‚ฌ๋„. ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ๊ฐ€ ๋‚ฉ๊ธฐ์œ„ํ—˜์ด๋ฉด ๋‚ฉ๊ธฐ ๋ฌธ์•ˆ์„ ์šฐ์„ ํ•˜๊ณ , ๋…์†Œ ์กฐํ•ญ์ด๋ฉด ๋‚ฉ๊ธฐ ๋ฌธ์•ˆ์€ ์ œ์™ธํ•œ๋‹ค.\"\"\"\n", " raw = cosine_similarity(sv.transform([s]), SM)[0]\n", " sc = raw.copy()\n", " sc[jn] += 1.0 if model.predict([s])[0] == \"๋‚ฉ๊ธฐ์œ„ํ—˜\" else -1.0\n", " return sc, raw\n", "\n", "\n", "hit1 = hit3 = 0\n", "for s, t in tests:\n", " sc, _ = similarity(s)\n", " order = np.argsort(-sc)[:3]\n", " hit1 += topics[order[0]] == t; hit3 += t in [topics[j] for j in order]\n", "print(f\"\\n[ํ‘œ์ค€ ๋ฌธ์•ˆ ์ถ”์ฒœ ์ผ์น˜๋„] ์œ„ํ—˜ ์กฐํ•ญ {len(tests)}๊ฑด: Top-1 {hit1 / len(tests):.1%}, Top-3 {hit3 / len(tests):.1%} (๊ณผ๊ฑฐ ์ฒ˜๋ฆฌ๋œ ์ˆ˜์ • ๋ฌธ์•ˆ ์ฃผ์ œ์™€ ์ผ์น˜ ๊ธฐ์ค€)\")\n", "print(\"\\n[์œ„ํ—˜ ์กฐํ•ญ๋ณ„ ๊ทผ๊ฑฐ์™€ ์ˆ˜์ • ๊ถŒ๊ณ  Top-3 ์˜ˆ์‹œ]\")\n", "for s, t in [tests[0], tests[7], tests[-1]]:\n", " pred = model.predict([s])[0]; prob = model.predict_proba([s])[0].max()\n", " sc, sim = similarity(s)\n", " order = np.argsort(-sc)[:3]\n", " print(f\" ์กฐํ•ญ: {s[:46]}...\")\n", " print(f\" ๋ถ„๋ฅ˜ {pred}(ํ™•์‹ ๋„ {prob:.2f}), ๊ทผ๊ฑฐ ํ‘œํ˜„ {evidence(s, pred) or '-'}\")\n", " for r, j in enumerate(order, 1):\n", " print(f\" ๊ถŒ๊ณ  {r}์ˆœ์œ„ [{topics[j]}] ์œ ์‚ฌ๋„ {sim[j]:.2f}: {list(STD.values())[j][:30]}...\")\n", "print(\"โ€ป ๋ชจ๋ธ์€ ํ›„๋ณด๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ์ˆ˜์ • ๋ฌธ์•ˆ์€ ์ฐธ๊ณ ์šฉ์ด๋‹ค. ๋ถˆ๊ณต์ • ์กฐํ•ญ ์—ฌ๋ถ€์™€ ๋ฌธ์•ˆ ํ™•์ •์€ ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด ๋‹ด๋‹น์ž๊ฐ€ ํŒ๋‹จํ•œ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n", "ax[0].bar([\"keyword\", \"model A\", \"model B\", \"model all\"], [np.mean(kb == y_all), acc_a, acc_b, acc], color=[\"gray\", \"tab:blue\", \"tab:orange\", \"tab:green\"])\n", "ax[0].axhline(.85, color=\"r\", ls=\"--\"); ax[0].set_ylim(0, 1.05); ax[0].set_title(\"Clause classification accuracy\")\n", "im = ax[1].imshow(cm.to_numpy(), cmap=\"Blues\"); ax[1].set_xticks(range(3), [\"ok\", \"toxic\", \"delay\"]); ax[1].set_yticks(range(3), [\"ok\", \"toxic\", \"delay\"]); ax[1].set_title(\"Confusion matrix (all test)\")\n", "for i in range(3):\n", " for j in range(3):\n", " ax[1].text(j, i, int(cm.to_numpy()[i, j]), ha=\"center\", va=\"center\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 04 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๋ฐ์ดํ„ฐ] ํ•™์Šต ์กฐํ•ญ 1,200๊ฐœ / ์‹œํ—˜ A(๊ฐ™์€ ๋ฌธ์žฅ ํ‹€) 600๊ฐœ / ์‹œํ—˜ B(์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„) 300๊ฐœ / ๋ถ„๋ฅ˜ 3์ข…: ์ •์ƒ, ๋…์†Œ, ๋‚ฉ๊ธฐ์œ„ํ—˜\n", "\n", "[๋ถ„๋ฅ˜ ์ •ํ™•๋„] ์‹œํ—˜ A 100.0%, ์‹œํ—˜ B(์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„) 95.3%, ์ „์ฒด 98.4% (๋ชฉํ‘œ 85% ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "๋‹จ์ˆœ ํ‚ค์›Œ๋“œ ๊ธฐ์ค€์„  ์ „์ฒด 64.4% (๋ถ€์ •ยท๋ฌธ๋งฅ์„ ๋ณด์ง€ ๋ชปํ•ด '๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผ' ๊ฐ™์€ ์ •์ƒ ๋ฌธ์žฅ์„ ์˜คํƒ)\n", " ์˜ˆ์ธก ์ •์ƒ ์˜ˆ์ธก ๋…์†Œ ์˜ˆ์ธก ๋‚ฉ๊ธฐ์œ„ํ—˜\n", "์‹ค์ œ ์ •์ƒ 504 0 0\n", "์‹ค์ œ ๋…์†Œ 0 222 0\n", "์‹ค์ œ ๋‚ฉ๊ธฐ์œ„ํ—˜ 14 0 160\n", "์œ„ํ—˜ ์กฐํ•ญ(๋…์†Œยท๋‚ฉ๊ธฐ์œ„ํ—˜) ํฌ์ฐฉ๋ฅ  96.5%, ์ •์ƒ ๋ฌธ์žฅ์„ ์œ„ํ—˜์œผ๋กœ ์˜คํƒํ•œ ๋น„์œจ 0.0%\n", "\n", "[์˜ค๋ถ„๋ฅ˜ ์‚ฌ๋ก€(์‹œํ—˜ B)]\n", " ์‹ค์ œ ๋‚ฉ๊ธฐ์œ„ํ—˜ / ์˜ˆ์ธก ์ •์ƒ: ์ผ์ • ์ง€์—ฐ์ด ์žˆ์–ด๋„ ์ƒ๋Œ€์ž๋Š” ๋ณ€๊ฒฝ ๊ณ„ํš์„ ์ œ์ถœํ•  ์˜๋ฌด๊ฐ€ ์—†๋‹ค. ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€...\n", " ์‹ค์ œ ๋‚ฉ๊ธฐ์œ„ํ—˜ / ์˜ˆ์ธก ์ •์ƒ: ์ผ์ • ์ง€์—ฐ์ด ์žˆ์–ด๋„ ์ƒ๋Œ€์ž๋Š” ๋ณ€๊ฒฝ ๊ณ„ํš์„ ์ œ์ถœํ•  ์˜๋ฌด๊ฐ€ ์—†๋‹ค. ์ด ์กฐํ•ญ์€ ๊ณ„์•ฝ ์ „์ฒด์— ...\n", " ์‹ค์ œ ๋‚ฉ๊ธฐ์œ„ํ—˜ / ์˜ˆ์ธก ์ •์ƒ: ์ผ์ • ์ง€์—ฐ์ด ์žˆ์–ด๋„ ์ƒ๋Œ€์ž๋Š” ๋ณ€๊ฒฝ ๊ณ„ํš์„ ์ œ์ถœํ•  ์˜๋ฌด๊ฐ€ ์—†๋‹ค....\n", "\n", "[ํ‘œ์ค€ ๋ฌธ์•ˆ ์ถ”์ฒœ ์ผ์น˜๋„] ์œ„ํ—˜ ์กฐํ•ญ 180๊ฑด: Top-1 94.4%, Top-3 97.8% (๊ณผ๊ฑฐ ์ฒ˜๋ฆฌ๋œ ์ˆ˜์ • ๋ฌธ์•ˆ ์ฃผ์ œ์™€ ์ผ์น˜ ๊ธฐ์ค€)\n", "\n", "[์œ„ํ—˜ ์กฐํ•ญ๋ณ„ ๊ทผ๊ฑฐ์™€ ์ˆ˜์ • ๊ถŒ๊ณ  Top-3 ์˜ˆ์‹œ]\n", " ์กฐํ•ญ: ํ•˜์ž ๋ณด์ˆ˜ ์ฑ…์ž„์€ ์ธ๋„ ํ›„ ์ฆ‰์‹œ ์†Œ๋ฉธํ•˜๋ฉฐ ๊ณ„์•ฝ๋‹ด๋‹น๊ด€์€ ๋ณด์ˆ˜๋ฅผ ์š”๊ตฌํ•  ์ˆ˜ ์—†๋‹ค....\n", " ๋ถ„๋ฅ˜ ๋…์†Œ(ํ™•์‹ ๋„ 0.99), ๊ทผ๊ฑฐ ํ‘œํ˜„ ['๊ด€์€', '๊ด€์€', '์ฆ‰์‹œ']\n", " ๊ถŒ๊ณ  1์ˆœ์œ„ [ํ•˜์ž] ์œ ์‚ฌ๋„ 0.12: ๋ณด์ฆ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง€๊ณ  ๋ฌด์ƒ...\n", " ๊ถŒ๊ณ  2์ˆœ์œ„ [๊ฒ€์‚ฌ] ์œ ์‚ฌ๋„ 0.09: ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋‚ฉํ’ˆ๋œ ๋ฌผํ’ˆ์„ ์ •ํ•ด์ง„ ๊ธฐ๊ฐ„ ์•ˆ์— ๊ฒ€์‚ฌํ•˜๊ณ  ํ•ฉ...\n", " ๊ถŒ๊ณ  3์ˆœ์œ„ [ํ•˜๋„๊ธ‰] ์œ ์‚ฌ๋„ 0.06: ํ•˜๋„๊ธ‰์€ ์‚ฌ์ „์— ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์„œ๋ฉด ์Šน์ธ์„ ๋ฐ›์•„์•ผ ํ•˜๋ฉฐ ํ•˜...\n", " ์กฐํ•ญ: ์šฉ์—ญ ๊ฒฐ๊ณผ๋ฌผ์˜ ์ถ”๊ฐ€ ๋น„์šฉ์€ ์‚ฌ์œ ๋ฅผ ๋ถˆ๋ฌธํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ์ „์•ก ๋ถ€๋‹ดํ•˜๋ฉฐ ๋ณ„๋„ ํ˜‘์˜ ์—†์ด...\n", " ๋ถ„๋ฅ˜ ๋…์†Œ(ํ™•์‹ ๋„ 0.99), ๊ทผ๊ฑฐ ํ‘œํ˜„ ['์ „์•ก', '์•ก', '์ „์•ก']\n", " ๊ถŒ๊ณ  1์ˆœ์œ„ [์ถ”๊ฐ€๋น„์šฉ] ์œ ์‚ฌ๋„ 0.16: ์ถ”๊ฐ€ ๋น„์šฉ์€ ์‚ฌ์ „์— ํ˜‘์˜ํ•ด ์„œ๋ฉด์œผ๋กœ ํ•ฉ์˜ํ•œ ๋ฒ”์œ„์—์„œ๋งŒ ...\n", " ๊ถŒ๊ณ  2์ˆœ์œ„ [์ง€์‹์žฌ์‚ฐ] ์œ ์‚ฌ๋„ 0.12: ์ง€์‹์žฌ์‚ฐ๊ถŒ์˜ ๊ท€์†๊ณผ ์‚ฌ์šฉ ๋ฒ”์œ„๋Š” ๋ณ„๋„ ํ•ฉ์˜์„œ๋กœ ์ •ํ•˜๋ฉฐ ...\n", " ๊ถŒ๊ณ  3์ˆœ์œ„ [ํ•ด์ง€] ์œ ์‚ฌ๋„ 0.12: ๊ณ„์•ฝ ํ•ด์ง€๋Š” ์‚ฌ์œ ๋ฅผ ๋ช…์‹œํ•˜๊ณ  ์ •ํ•ด์ง„ ๊ธฐ๊ฐ„ ์ „์— ์„œ๋ฉด์œผ๋กœ...\n", " ์กฐํ•ญ: ๋‚ฉํ’ˆ ์ผ์ •์€ ๊ฐ€๋Šฅํ•œ ํ•œ ์ง€ํ‚ค๋„๋ก ๋…ธ๋ ฅํ•˜๋ฉฐ ์ง€์—ฐ๋˜์–ด๋„ ์‚ฌ์œ ๋ฅผ ๋ณด๊ณ ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์„ธ๋ถ€ ...\n", " ๋ถ„๋ฅ˜ ๋‚ฉ๊ธฐ์œ„ํ—˜(ํ™•์‹ ๋„ 0.99), ๊ทผ๊ฑฐ ํ‘œํ˜„ ['์ผ์ •', '์ผ์ •', '๋„']\n", " ๊ถŒ๊ณ  1์ˆœ์œ„ [๋‚ฉ๊ธฐ] ์œ ์‚ฌ๋„ 0.03: ๋‚ฉ๊ธฐ๋Š” ๊ณ„์•ฝ์„œ์— ๋‚ ์งœ๋กœ ๋ช…์‹œํ•˜๊ณ  ๋ณ€๊ฒฝ์€ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ  ...\n", " ๊ถŒ๊ณ  2์ˆœ์œ„ [๋ณด์•ˆ] ์œ ์‚ฌ๋„ 0.08: ๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ๋น„๊ณต๊ฐœ ์‚ฌํ•ญ์€ ๋ˆ„์„คํ•˜์ง€ ์•Š์œผ๋ฉฐ ...\n", " ๊ถŒ๊ณ  3์ˆœ์œ„ [ํ•ด์ง€] ์œ ์‚ฌ๋„ 0.06: ๊ณ„์•ฝ ํ•ด์ง€๋Š” ์‚ฌ์œ ๋ฅผ ๋ช…์‹œํ•˜๊ณ  ์ •ํ•ด์ง„ ๊ธฐ๊ฐ„ ์ „์— ์„œ๋ฉด์œผ๋กœ...\n", "โ€ป ๋ชจ๋ธ์€ ํ›„๋ณด๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ์ˆ˜์ • ๋ฌธ์•ˆ์€ ์ฐธ๊ณ ์šฉ์ด๋‹ค. ๋ถˆ๊ณต์ • ์กฐํ•ญ ์—ฌ๋ถ€์™€ ๋ฌธ์•ˆ ํ™•์ •์€ ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด ๋‹ด๋‹น์ž๊ฐ€ ํŒ๋‹จํ•œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 05. ๊ณ„์•ฝ ์œ„ํ—˜ ์ ์ˆ˜ ์‚ฐ์ถœ ๋ฐ ๊ทœ์ • ์œ„๋ฐ˜ ๊ฐ€๋Šฅ์„ฑ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ์—”์ง„ ๊ฐœ๋ฐœ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๊ณ„์•ฝ ์œ„ํ—˜ ์ ์ˆ˜ ์‚ฐ์ถœ ๋ฐ ๊ทœ์ • ์œ„๋ฐ˜ ๊ฐ€๋Šฅ์„ฑ ์‹ ๋ขฐ๋„ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ฃฐ ์ ์ค‘ ๋‚ด์—ญ๊ณผ ์กฐํ•ญ ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ๋ฅผ ๊ฒฐํ•ฉํ•ด ๊ณ„์•ฝ ์œ„ํ—˜ ์ ์ˆ˜์™€ ๊ทœ์ • ์œ„๋ฐ˜ ๊ฐ€๋Šฅ์„ฑ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜๋ฅผ ์‚ฐ์ถœํ•˜๊ณ  ์ž…๋ ฅ-๋ถ„์„-๊ฒ€ํ† -๊ฒฐ๊ณผ 4๊ฐœ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋ฃฐ 16์ข… ์ ์ค‘ ์—ฌ๋ถ€, ๋ชจ๋ธ์ด ์ฐพ์€ ๋…์†Œยท๋‚ฉ๊ธฐ์œ„ํ—˜ ์กฐํ•ญ ์ˆ˜, ๊ธˆ์•ก์„ ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€๋กœ ๊ฒฐํ•ฉํ•ด 0~100์ ์˜ ์œ„ํ—˜ ์ ์ˆ˜๋ฅผ ๋งŒ๋“ค๊ณ , ์ ์ˆ˜์˜ ๋šœ๋ ทํ•จ๊ณผ ๋ฃฐยท๋ชจ๋ธ ์ผ์น˜ ์—ฌ๋ถ€๋กœ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ์ •์˜ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/ui/prc01_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": [ "\"\"\"๋ฃฐ ์ ์ค‘ ๋‚ด์—ญ๊ณผ ์กฐํ•ญ ๋ถ„๋ฅ˜ ๋ชจ๋ธ์˜ ์œ„ํ—˜ ์กฐํ•ญ ์ˆ˜๋ฅผ ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€๋กœ ๊ฒฐํ•ฉํ•ด ๊ณ„์•ฝ๋ณ„ ์œ„๋ฐ˜ ์œ„ํ—˜ ์ ์ˆ˜(0~100)์™€ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜๋ฅผ ์‚ฐ์ถœํ•˜๋Š” ์—”์ง„์„ ๋งŒ๋“ค๊ณ , ๋ณ„๋„ ๊ฐ€์ƒ ๊ณ„์•ฝ๊ตฐ์—์„œ ์„ฑ๋Šฅยท์‹ ๋ขฐ๋„ ๋ณด์ •์„ ๊ฒ€์ฆํ•˜๋ฉฐ ์ž…๋ ฅ-๋ถ„์„-๊ฒ€ํ† -๊ฒฐ๊ณผ 4๊ฐœ ํ™”๋ฉด(ํ…์ŠคํŠธ ํŒจ๋„๊ณผ ๊ทธ๋ž˜ํ”„)๊ณผ ์ƒํƒœ ๋ฑƒ์ง€ยทํ•„ํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.metrics import roc_auc_score, brier_score_loss\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์กฐ๋‹ฌ๊ณ„ํš์•ˆยท๊ณ„์•ฝ์„œ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ณ„์•ฝยท์—…์ฒดยท๋ฒ•๋ น ์กฐ๋ฌธ ์—†์Œ)\n", "# ๊ณ„์•ฝ ์†์„ฑ 16๊ฐœ + ์กฐํ•ญ ๋ฌธ์žฅ 6๊ฐœ๋ฅผ ๋งŒ๋“ค๊ณ , ์†์„ฑ ์œ„๋ฐ˜ยท๋…์†Œ ์กฐํ•ญยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ์„ž์ธ '๊ทœ์ • ์œ„๋ฐ˜ ์—ฌ๋ถ€'๋ฅผ ํ•จ๊ป˜ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "TYPES = [\"๋ฌผํ’ˆ\", \"์šฉ์—ญ\", \"๊ณต์‚ฌ\"]\n", "METHODS = [\"์ผ๋ฐ˜๊ฒฝ์Ÿ\", \"์ œํ•œ๊ฒฝ์Ÿ\", \"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]\n", "CLASSES = [\"์ •์ƒ\", \"๋…์†Œ\", \"๋‚ฉ๊ธฐ์œ„ํ—˜\"]\n", "OBJ = [\"๋‚ฉํ’ˆ ๋ฌผํ’ˆ\", \"์šฉ์—ญ ๊ฒฐ๊ณผ๋ฌผ\", \"์‹œ๊ณต ๋ชฉ์ ๋ฌผ\", \"์ •๋น„ ์„ฑ๊ณผ๋ฌผ\"]\n", "\n", "N_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋‚ฉ๊ธฐ๋ฅผ ์ง€์ฒดํ•œ ๊ฒฝ์šฐ ์ง€์ฒด์ผ์ˆ˜ 1์ผ๋งˆ๋‹ค ๊ณ„์•ฝ๊ธˆ์•ก์˜ {p}/1000์— ํ•ด๋‹นํ•˜๋Š” ์ง€์ฒด์ƒ๊ธˆ์„ ๋ถ€๊ณผํ•œ๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๊ฐ€ ํ™•์ธ๋œ ๊ฒฝ์šฐ์— ํ•œํ•˜์—ฌ ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์Šน์ธ์„ ๋ฐ›์•„ ๋ฉด์ œํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ ์—ฐ์žฅ์€ ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๋ฅผ ํ™•์ธํ•˜๊ณ  ์„œ๋ฉด์œผ๋กœ ์Šน์ธํ•œ ๊ฒฝ์šฐ์—๋งŒ ์ธ์ •ํ•œ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋‚ฉํ’ˆ๋œ {o}์„ {d}์ผ ์ด๋‚ด์— ๊ฒ€์‚ฌํ•˜๊ณ  ํ•ฉ๊ฒฉํ•œ ํ›„์— ๋Œ€๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ํ•˜๋„๊ธ‰์„ ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ์‚ฌ์ „์— ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์„œ๋ฉด ์Šน์ธ์„ ๋ฐ›์•„์•ผ ํ•œ๋‹ค.\",\n", " \"์–ด๋А ํ•œ์ชฝ์ด ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•˜๋ ค๋ฉด ์‚ฌ์œ ๋ฅผ ๋ช…์‹œํ•ด {d}์ผ ์ „์— ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"{o}์— ํ•˜์ž๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ๋ณด์ฆ ๊ธฐ๊ฐ„ ์•ˆ์— ๋ฌด์ƒ์œผ๋กœ ๋ณด์ˆ˜ํ•˜๊ฑฐ๋‚˜ ๊ต์ฒดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ๋น„๊ณต๊ฐœ ์‚ฌํ•ญ์€ ์ œ3์ž์—๊ฒŒ ๋ˆ„์„คํ•˜์ง€ ์•Š์œผ๋ฉฐ ์œ„๋ฐ˜ ์‹œ ์ฑ…์ž„์„ ์ง„๋‹ค.\",\n", " \"{o}์— ๋Œ€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์˜ ๊ท€์†๊ณผ ์‚ฌ์šฉ ๋ฒ”์œ„๋Š” ๋ณ„๋„ ํ•ฉ์˜์„œ์— ๋”ฐ๋ผ ์ •ํ•œ๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ํ•ฉ๊ฒฉ ํ›„ ์ฒญ๊ตฌ์„œ๋ฅผ ์ ‘์ˆ˜ํ•œ ๋‚ ๋ถ€ํ„ฐ {d}์ผ ์ด๋‚ด์— ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ๊ธˆ์•ก์˜ ์กฐ์ •์€ ๋ฌผ๊ฐ€๋ณ€๋™ ๊ธฐ์ค€์— ๋”ฐ๋ผ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ์„œ๋ฉด์œผ๋กœ ํ•ฉ์˜ํ•œ ๊ฒฝ์šฐ์—๋งŒ ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ดํ–‰๋ณด์ฆ์„œ๋ฅผ ๊ณ„์•ฝ ์ฒด๊ฒฐ ์‹œ ์ œ์ถœํ•˜๋ฉฐ ๋ณด์ฆ๊ธˆ์€ ๊ณ„์•ฝ ์ดํ–‰ ์™„๋ฃŒ ํ›„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ณ„์•ฝ์„œ ๋ณ„ํ‘œ์˜ ์ผ์ž๋ฅผ ๋”ฐ๋ฅด๋ฉฐ ๋ณ€๊ฒฝ ์‹œ ์„œ๋ฉด ํ•ฉ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผํ•˜๋ฉฐ ์ด์˜๋Š” ์ •ํ•ด์ง„ ์ ˆ์ฐจ๋กœ ์ œ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•˜์ง€ ๋ชปํ•œ {o}์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๊ต์ฒดํ•˜๋ฉฐ ๊ทธ ๋น„์šฉ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ๊ท€์ฑ… ์‚ฌ์œ ๋กœ ์ดํ–‰์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•˜๊ณ  ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"์ง€์—ฐ์ด ์˜ˆ์ƒ๋˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ๋ฐœ์ฃผ๊ธฐ๊ด€์— ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•œ๋‹ค.\",\n", " \"๋ณด์ฆ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง€๊ณ  ๋ณด์ˆ˜ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์€ ๋จผ์ € ํ˜‘์˜๋กœ ํ•ด๊ฒฐํ•˜๊ณ  ํ˜‘์˜๊ฐ€ ๋˜์ง€ ์•Š์œผ๋ฉด ๊ด€ํ•  ๋ฒ•์›์˜ ํŒ๋‹จ์— ๋”ฐ๋ฅธ๋‹ค.\",\n", "]\n", "T_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ง€์ฒด์ƒ๊ธˆ์€ ์ „์•ก ๋ฉด์ œํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋Œ€ํ•ด ์ด์˜๋ฅผ ์ œ๊ธฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ฆ‰์‹œ ๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ ๋ณด๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ดํ›„ ๊ฒ€์‚ฌ๋ฅผ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์–ธ์ œ๋“ ์ง€ ์ผ๋ฐฉ์ ์œผ๋กœ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ์— ๋”ฐ๋ฅธ ์†ํ•ด๋Š” ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"{o}์˜ ์ถ”๊ฐ€ ๋น„์šฉ์€ ์‚ฌ์œ ๋ฅผ ๋ถˆ๋ฌธํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ์ „์•ก ๋ถ€๋‹ดํ•˜๋ฉฐ ๋ณ„๋„ ํ˜‘์˜ ์—†์ด ์ฒญ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"{o}์— ๊ด€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์€ ๋ชจ๋‘ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ๊ท€์†๋˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"ํ•˜๋„๊ธ‰์€ ์‹ ๊ณ  ์—†์ด ์ œํ•œ ์—†์ด ํ—ˆ์šฉํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ํ•˜๋„๊ธ‰ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ์ „์— ์ „์•ก ์„ ์ง€๊ธ‰ํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ฐ˜ํ™˜ ์ฒญ๊ตฌ๊ถŒ์„ ํฌ๊ธฐํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ์‚ฌํ•ญ์˜ ๋ˆ„์„ค๋กœ ์ธํ•œ ์ฑ…์ž„์„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"ํ•˜์ž ๋ณด์ˆ˜ ์ฑ…์ž„์€ ์ธ๋„ ํ›„ ์ฆ‰์‹œ ์†Œ๋ฉธํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ณด์ˆ˜๋ฅผ ์š”๊ตฌํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฒญ๊ตฌํ•˜๋Š” ๊ธˆ์•ก์„ ๊ฑฐ์ ˆํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ๊ฑฐ์ ˆํ•˜๋ฉด ์œ„์•ฝ๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์ด ์ƒ๊ธฐ๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ •ํ•˜๋Š” ์ค‘์žฌ ๊ธฐ๊ด€์—์„œ๋งŒ ํ•ด๊ฒฐํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์‚ฌ๋Š” ์ƒ๋žตํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์‹œํ—˜ ๊ฒฐ๊ณผ์„œ๋กœ ๋Œ€์‹ ํ•œ๋‹ค.\",\n", " \"๊ฒฌ์  ๊ธˆ์•ก์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ๊ทธ๋Œ€๋กœ ํ™•์ •ํ•˜๊ณ  ์ฆ๋น™ ์ž๋ฃŒ๋Š” ๋ฐ›์ง€ ์•Š๋Š”๋‹ค.\",\n", "]\n", "D_TPL = [\n", " \"๋‚ฉ๊ธฐ๋Š” ์ž์žฌ ์ˆ˜๊ธ‰ ์‚ฌ์ •์— ๋”ฐ๋ผ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ฐ€๋Šฅํ•œ ํ•œ ์ง€ํ‚ค๋„๋ก ๋…ธ๋ ฅํ•˜๋ฉฐ ์ง€์—ฐ๋˜์–ด๋„ ์‚ฌ์œ ๋ฅผ ๋ณด๊ณ ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณต๊ธ‰ ์ง€์—ฐ์ด๋‚˜ ์„ ํ–‰ ์—…์ฒด ์‚ฌ์ •๋„ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ์œผ๋กœ ๋ณด์•„ ๋‚ฉ๊ธฐ๋ฅผ ์ž๋™ ์—ฐ์žฅํ•œ๋‹ค.\",\n", " \"์ฐฉ์ˆ˜์ผ์€ ์„ ํ–‰ ์—…์ฒด์˜ ๋‚ฉํ’ˆ์ด ๋๋‚œ ๋’ค ๋ณ„๋„๋กœ ์ •ํ•˜๋ฉฐ ์™„๋ฃŒ ๊ธฐํ•œ์€ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ์ผ ์ดํ›„ 1ํšŒ์— ํ•œํ•ด ๊ธฐํ•œ ์—†์ด ์ž๋™ ์—ฐ์žฅ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํ•œ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๋Š” ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ƒ์‚ฐ ์ผ์ •์ด ํ™•์ •๋˜๋Š” ๋Œ€๋กœ ํ†ต๋ณดํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์‹œ์ ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž ์‚ฌ์ •์ด ํ—ˆ๋ฝํ•˜๋Š” ๋•Œ๋กœ ํ•˜๊ณ  ๋‚ ์งœ๋Š” ๋”ฐ๋กœ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ž์žฌ๊ฐ€ ๋Šฆ๊ฒŒ ๋“ค์–ด์˜ค๋ฉด ์™„๋ฃŒ ์ผ์ •์€ ๊ทธ๋งŒํผ ์ž๋™์œผ๋กœ ๋Šฆ์ถฐ์ง„๋‹ค.\",\n", " \"๊ธฐํ•œ ๋‚ด์— ์ธ๋„ํ•˜์ง€ ๋ชปํ•ด๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ •์ด ๋Šฆ์–ด์ ธ๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ƒˆ ๊ณ„ํš์„œ๋ฅผ ๋‚ผ ํ•„์š”๊ฐ€ ์—†๋‹ค.\",\n", "]\n", "# ํ•™์Šต์— ์“ฐ์ง€ ์•Š๋Š” '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'(๋ชจ๋ธ์ด ๋ฌธ์žฅ ํ‹€์„ ์™ธ์› ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์šฉ๋„)\n", "HOLD = {\n", " \"์ •์ƒ\": [\"๊ฒ€์‚ฌ์— ๋ถˆํ•ฉ๊ฒฉํ•œ ๊ฒฝ์šฐ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ • ๊ธฐํ•œ ์•ˆ์— ์žฌ๋‚ฉํ’ˆํ•˜๋ฉฐ ์žฌ๊ฒ€์‚ฌ ๋น„์šฉ์€ ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ฒ•๋ น์„ ์œ„๋ฐ˜ํ•˜์—ฌ ๊ณ„์•ฝ์„ ์ดํ–‰ํ•˜์ง€ ๋ชปํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด ํ†ต์ง€ ํ›„ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๊ฐ€ ์ง€์—ฐ๋  ์šฐ๋ ค๊ฐ€ ์žˆ์œผ๋ฉด ์ƒ๋Œ€์ž๋Š” ์ฆ‰์‹œ ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"ํ•˜์ž๋‹ด๋ณด ์ฑ…์ž„ ๊ธฐ๊ฐ„์€ ์ธ์ˆ˜์ผ๋ถ€ํ„ฐ ๊ธฐ์‚ฐํ•˜๋ฉฐ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง„๋‹ค.\"],\n", " \"๋…์†Œ\": [\"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๋Œ€๊ธˆ์„ ์ด์œ  ์—†์ด ๊ฑฐ์ ˆํ•˜์ง€ ๋ชปํ•˜๋ฉฐ ๊ฑฐ์ ˆ ์‹œ ์œ„์•ฝ๊ธˆ์„ ๋‚ธ๋‹ค.\",\n", " \"๊ณ„์•ฝ ๋‚ด์šฉ์— ๊ด€ํ•œ ๋ถ„์Ÿ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€์—์„œ๋งŒ ๋‹ค๋ฃจ๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์ˆ˜ ๊ถŒํ•œ์€ ํฌ๊ธฐํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ž์ฒด ์‹œํ—˜ ์„ฑ์ ์„œ๋กœ ๊ฐˆ์Œํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ œ์ถœํ•œ ๊ฒฌ์ ์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ํ™•์ • ๋‹จ๊ฐ€๋กœ ํ•˜๋ฉฐ ์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"๋‚ฉํ’ˆ ์‹œ๊ธฐ๋Š” ์ƒ๋Œ€์ž์˜ ํ˜•ํŽธ์ด ํ—ˆ๋ฝํ•˜๋Š” ์‹œ์ ์œผ๋กœ ํ•˜๊ณ  ๋ณ„๋„ ๋‚ ์งœ๋Š” ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์›์ž์žฌ ํ™•๋ณด๊ฐ€ ๋Šฆ์–ด์ง€๋ฉด ์™„๋ฃŒ ์ผ์ž๋Š” ์ž๋™์œผ๋กœ ๋’ค๋กœ ๋ฏธ๋ค„์ง€๋Š” ๊ฒƒ์œผ๋กœ ๋ณธ๋‹ค.\",\n", " \"์ธ๋„ ๊ธฐํ•œ์€ ์ฐธ๊ณ ์šฉ์ด๋ฉฐ ์ง€ํ‚ค์ง€ ๋ชปํ•ด๋„ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ • ์ง€์—ฐ์ด ์žˆ์–ด๋„ ์ƒ๋Œ€์ž๋Š” ๋ณ€๊ฒฝ ๊ณ„ํš์„ ์ œ์ถœํ•  ์˜๋ฌด๊ฐ€ ์—†๋‹ค.\"],\n", "}\n", "SUFFIX = [\"\", \"\", \"\", \" ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ์‹œํ–‰ ์„ธ์น™์— ๋”ฐ๋ฅธ๋‹ค.\", \" ์ด ์กฐํ•ญ์€ ๊ณ„์•ฝ ์ „์ฒด์— ์ ์šฉํ•œ๋‹ค.\", \" ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ํ˜‘์˜ํ•œ๋‹ค.\", \" ๊ด€๋ จ ์„œ์‹์€ ๋ณ„์ง€๋ฅผ ๋”ฐ๋ฅธ๋‹ค.\"]\n", "SWAP = [(\"๋ฐœ์ฃผ๊ธฐ๊ด€\", \"๊ณ„์•ฝ๋‹ด๋‹น๊ด€\"), (\"๊ณ„์•ฝ์ƒ๋Œ€์ž\", \"์—…์ฒด\")]\n", "\n", "\n", "def make_clause(rng, cls, hold=False, idx=None):\n", " \"\"\"์กฐํ•ญ ํ•œ ๋ฌธ์žฅ ์ƒ์„ฑ. ํ‘œํ˜„ ์ผ๋ถ€๋Š” ๋™์˜์–ด๋กœ ๋ฐ”๊พธ๊ณ  ์ ‘๋ฏธ ๋ฌธ๊ตฌ๋ฅผ ๋ฌด์ž‘์œ„๋กœ ๋ถ™์ธ๋‹ค(idx๋ฅผ ์ฃผ๋ฉด ๊ทธ ๋ฒˆํ˜ธ์˜ ๋ฌธ์žฅ ํ‹€์„ ์‚ฌ์šฉ).\"\"\"\n", " if idx is not None:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[idx].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " elif hold:\n", " s = HOLD[cls][rng.integers(len(HOLD[cls]))]\n", " else:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[rng.integers(len(tpl))].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " if rng.random() < 0.3:\n", " a, b = SWAP[rng.integers(2)]\n", " s = s.replace(a, b)\n", " return s + SUFFIX[rng.integers(len(SUFFIX))]\n", "\n", "\n", "def make_contracts(n=200, seed=7):\n", " \"\"\"๊ฐ€์ƒ ๊ณ„์•ฝ n๊ฑด(์†์„ฑ 16๊ฐœ + ์กฐํ•ญ 6๊ฐœ + ๊ทœ์ • ์œ„๋ฐ˜ ์ •๋‹ต)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " sl = rng.beta(1.2, 3.2, n) # ๊ณ„์•ฝ์„œ ์ž‘์„ฑ์ด ํ—ˆ์ˆ ํ•œ ์ •๋„(์ž ์žฌ ๋ณ€์ˆ˜)\n", " ctype = rng.choice(TYPES, n, p=[.5, .3, .2])\n", " amount = np.clip(np.exp(rng.normal(np.log(220), 1.1, n)), 5, 5000).round(0)\n", " method = np.where(rng.random(n) < .06 + .3 * sl, \"์ˆ˜์˜๊ณ„์•ฝ\", rng.choice(METHODS[:3], n, p=[.5, .3, .2]))\n", " period = np.array([rng.integers(60, 300) if t == \"๋ฌผํ’ˆ\" else rng.integers(90, 400) if t == \"์šฉ์—ญ\" else rng.integers(180, 720) for t in ctype])\n", " delivery = (period * rng.uniform(.5, 1.0, n)).astype(int)\n", " bad_logic = rng.random(n) < .015 * (1 + 3 * sl)\n", " delivery = np.where(bad_logic, period + rng.integers(10, 60, n), delivery)\n", " pen = np.where(rng.random(n) < .22 * sl, rng.choice([.3, .5, .8], n), rng.choice([1.0, 1.5, 2.0, 2.5], n))\n", " adv = np.where(rng.random(n) < .22 * sl, rng.choice([50, 60, 70], n), rng.choice([0, 10, 20, 30, 40], n))\n", " bond = np.where(rng.random(n) < .25 * sl + .5 * (adv > 40), rng.choice([0, 5], n), rng.choice([10, 10, 15, 20], n))\n", " war = np.array([rng.choice([3, 6]) if (rng.random() < .25 * s and t == \"๋ฌผํ’ˆ\") else rng.choice([12, 24, 36]) if t == \"๋ฌผํ’ˆ\" else rng.choice([0, 6, 12]) if t == \"์šฉ์—ญ\" else rng.choice([24, 36, 60]) for t, s in zip(ctype, sl)])\n", " insp = np.where(rng.random(n) < .3 * sl, rng.choice([21, 30], n), rng.choice([5, 7, 10, 14], n))\n", " sub = np.where(rng.random(n) < .15 * sl, rng.choice([60, 70, 80], n), rng.choice([0, 10, 20, 30, 40, 50], n))\n", " padj = rng.random(n) < .75 - .4 * sl\n", " term = rng.random(n) > .015 * (1 + 6 * sl)\n", " sec = rng.random(n) > .015 * (1 + 6 * sl)\n", " ip = rng.random(n) > .03 * (1 + 4 * sl)\n", " clauses, cls_true = [], []\n", " for i in range(n):\n", " pt, pd_ = .01 + .08 * sl[i], .02 + .08 * sl[i]\n", " cl = [rng.choice(CLASSES, p=[1 - pt - pd_, pt, pd_]) for _ in range(6)]\n", " cls_true.append(cl); clauses.append([make_clause(rng, c) for c in cl])\n", " df = pd.DataFrame({\"contract_id\": [f\"C-{i + 1:04d}\" for i in range(n)], \"ctype\": ctype, \"method\": method, \"amount_m\": amount,\n", " \"period_days\": period, \"delivery_days\": delivery, \"penalty_permil\": pen, \"advance_pct\": adv, \"perf_bond_pct\": bond,\n", " \"warranty_months\": war, \"inspection_days\": insp, \"subcontract_pct\": sub, \"price_adjust\": padj,\n", " \"termination\": term, \"security\": sec, \"ip_clause\": ip})\n", " hard = ((pen < 1.0) | ((adv > 40) & (bond < 10)) | ((amount >= 300) & (method == \"์ˆ˜์˜๊ณ„์•ฝ\")) | ((ctype == \"๋ฌผํ’ˆ\") & (war < 12))\n", " | ~term | ~sec | (sub > 50) | (delivery > period))\n", " toxic = np.array([c.count(\"๋…์†Œ\") for c in cls_true])\n", " delay = np.array([c.count(\"๋‚ฉ๊ธฐ์œ„ํ—˜\") for c in cls_true])\n", " split = (method == \"์ˆ˜์˜๊ณ„์•ฝ\") & (amount >= 250) & (amount < 300) & (rng.random(n) < .6) # ๊ณ„์•ฝ ์ชผ๊ฐœ๊ธฐ ์˜์‹ฌ(์ž„๊ณ„๊ฐ’ ๋ฐ”๋กœ ์•„๋ž˜)\n", " df[\"clauses\"], df[\"clause_cls\"] = clauses, cls_true\n", " df[\"n_toxic\"], df[\"n_delay\"] = toxic, delay\n", " viol = hard | (toxic > 0) | split | (delay >= 2)\n", " waived = hard & (toxic == 0) & (rng.random(n) < .12) # ๊ณ„์•ฝ๊ด€์ด ์˜ˆ์™ธ ์‚ฌ์œ ๋ฅผ ์ธ์ •ํ•ด ์œ„๋ฐ˜์œผ๋กœ ๋ณด์ง€ ์•Š์€ ๊ฑด(์ž๋ฃŒ์˜ ํ˜„์‹ค์  ์žก์Œ)\n", " hidden = ~viol & (rng.random(n) < .03 + .08 * sl) # ์†์„ฑยท๋ฌธ๊ตฌ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜(๋ณ„์ฒจ ํ•ฉ์˜ ๋“ฑ)\n", " df[\"violation\"] = (viol & ~waived) | hidden\n", " df[\"vtype\"] = np.where(hidden, \"๊ธฐํƒ€\", np.where(hard, \"์†์„ฑ์œ„๋ฐ˜\", np.where(toxic > 0, \"๋…์†Œ์กฐํ•ญ\", np.where(split | (delay >= 2), \"๊ธฐํƒ€\", \"-\"))))\n", " return df\n", "\n", "# ---- ๊ณ„์•ฝ ๊ทœ์ • ์ค€์ˆ˜ ํŒ๋ณ„ ๋ฃฐ ์„ค์ •(YAML ํ˜•์‹ ๋ฌธ์ž์—ด: ๊ทœ์ •์ด ๋ฐ”๋€Œ๋ฉด ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ด ์„ค์ •๋งŒ ๊ณ ์นœ๋‹ค) ----\n", "RULES_YAML = \"\"\"\n", "- id: R01\n", " name: ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.0/1000 ๋ฏธ๋งŒ\n", " field: penalty_permil\n", " op: \"<\"\n", " value: 1.0\n", " weight: 3\n", "- id: R02\n", " name: ์„ ๊ธˆ ๋น„์œจ 40% ์ดˆ๊ณผ\n", " field: advance_pct\n", " op: \">\"\n", " value: 40\n", " weight: 2\n", "- id: R03\n", " name: ์„ ๊ธˆ 40% ์ดˆ๊ณผ์ธ๋ฐ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: adv_no_bond\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R04\n", " name: 300๋ฐฑ๋งŒ์› ์ด์ƒ ์ˆ˜์˜๊ณ„์•ฝ\n", " field: sole_amount\n", " op: \">=\"\n", " value: 300\n", " weight: 3\n", "- id: R05\n", " name: 500๋ฐฑ๋งŒ์› ์ด์ƒ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: bond_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R06\n", " name: ๋ฌผํ’ˆ ํ•˜์ž๋ณด์ฆ 12๊ฐœ์›” ๋ฏธ๋งŒ\n", " field: warranty_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R07\n", " name: ํ•ด์ง€ ์กฐํ•ญ ์—†์Œ\n", " field: termination\n", " op: \"==\"\n", " value: false\n", " weight: 2\n", "- id: R08\n", " name: ๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ\n", " field: security\n", " op: \"==\"\n", " value: false\n", " weight: 3\n", "- id: R09\n", " name: ์ง€์‹์žฌ์‚ฐ ์กฐํ•ญ ์—†์Œ(๋ฌผํ’ˆยท์šฉ์—ญ)\n", " field: ip_missing\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R10\n", " name: ํ•˜๋„๊ธ‰ ๋น„์œจ 50% ์ดˆ๊ณผ\n", " field: subcontract_pct\n", " op: \">\"\n", " value: 50\n", " weight: 3\n", "- id: R11\n", " name: ๊ฒ€์‚ฌ ๊ธฐ๊ฐ„ 14์ผ ์ดˆ๊ณผ\n", " field: inspection_days\n", " op: \">\"\n", " value: 14\n", " weight: 1\n", "- id: R12\n", " name: ๋‚ฉํ’ˆ ๊ธฐํ•œ์ด ๊ณ„์•ฝ ๊ธฐ๊ฐ„ ์ดˆ๊ณผ\n", " field: logic_err\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R13\n", " name: 180์ผ ์ด์ƒ ๊ณ„์•ฝ์— ๋ฌผ๊ฐ€๋ณ€๋™ ์กฐํ•ญ ์—†์Œ\n", " field: no_padj_long\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R14\n", " name: 1000๋ฐฑ๋งŒ์› ์ด์ƒ ๋น„๊ฒฝ์Ÿ ๊ณ„์•ฝ\n", " field: noncomp_big\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R15\n", " name: ๊ณ„์•ฝ ๊ธฐ๊ฐ„ 365์ผ ์ดˆ๊ณผ\n", " field: period_days\n", " op: \">\"\n", " value: 365\n", " weight: 1\n", "- id: R16\n", " name: ๋ถˆ๊ณต์ •ยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ ํฌํ•จ\n", " field: kw_hits\n", " op: \">=\"\n", " value: 1\n", " weight: 3\n", "\"\"\"\n", "GRADE_CUT = {\"๊ณ ์œ„ํ—˜\": 6, \"์ฃผ์˜\": 3} # ๊ฐ€์ค‘์น˜ ํ•ฉ ๊ธฐ์ค€ 1์ฐจ ํŒ๋ณ„ ๋“ฑ๊ธ‰(ํ•„์š” ์‹œ ์„ค์ •๋งŒ ๋ฐ”๊พผ๋‹ค)\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถˆ๊ณต์ • ์กฐํ•ญยท๋‚ฉ๊ธฐ ์ง€์ฒด ์œ„ํ—˜ ๋ฌธ๊ตฌ ์‚ฌ์ „(์‹ค์ œ ๊ทœ์ • ์กฐ๋ฌธ์ด ์•„๋‹˜)\n", "KEYWORDS = {\"๋…์†Œ\": [\"๋ฉด์ œํ•˜๋ฉฐ\", \"์ผ๋ฐฉ์ ์œผ๋กœ\", \"ํฌ๊ธฐ\", \"์ง€์ง€ ์•Š๋Š”๋‹ค\", \"์ฆ‰์‹œ ์†Œ๋ฉธ\", \"์ œํ•œ ์—†์ด\", \"์‹ ๊ณ  ์—†์ด\", \"์ „์•ก ๋ถ€๋‹ด\", \"๋ณ„๋„ ํ˜‘์˜ ์—†์ด\", \"๊ฒ€์‚ฌ ์ „์—\", \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ\", \"์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค\", \"์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์ž๋™ ์—ฐ์žฅ\", \"๊ธฐํ•œ ์—†์ด\", \"์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"๋…ธ๋ ฅํ•˜๋ฉฐ\", \"์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค\", \"ํ†ต๋ณดํ•œ๋‹ค\", \"์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค\", \"์˜๋ฌด๊ฐ€ ์—†๋‹ค\"]}\n", "\n", "\n", "def parse_rules(text):\n", " \"\"\"๊ฐ„์ด YAML ํŒŒ์„œ(๋ชฉ๋ก + key: value). ์ˆซ์žยทtrue/falseยท๋”ฐ์˜ดํ‘œ ๋ฌธ์ž์—ด์„ ํ•ด์„ํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ๊ณ„์•ฝ ์œ„ํ—˜ ์ ์ˆ˜ ์‚ฐ์ถœ ๋ฐ ๊ทœ์ • ์œ„๋ฐ˜ ๊ฐ€๋Šฅ์„ฑ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ์—”์ง„ ๊ฐœ๋ฐœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "OPS = {\"==\": lambda a, b: a == b, \"!=\": lambda a, b: a != b, \">\": lambda a, b: a > b, \">=\": lambda a, b: a >= b,\n", " \"<\": lambda a, b: a < b, \"<=\": lambda a, b: a <= b}\n", "\n", "\n", "def kw_count(clauses):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ ๋ชฉ๋ก์—์„œ ๋…์†Œยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ๊ฐ€ ๋“ค์–ด ์žˆ๋Š” ์กฐํ•ญ ์ˆ˜.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ๊ณ„์•ฝ ์œ„ํ—˜ ์ ์ˆ˜ ์‚ฐ์ถœ ๋ฐ ๊ทœ์ • ์œ„๋ฐ˜ ๊ฐ€๋Šฅ์„ฑ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ์—”์ง„ ๊ฐœ๋ฐœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def derive(df):\n", " \"\"\"๋ฃฐ์— ์“ฐ๋Š” ํŒŒ์ƒ ํ•„๋“œ(์„ ๊ธˆ+๋ณด์ฆ ๋ถ€์กฑ, ์ˆ˜์˜๊ณ„์•ฝ ๊ณ ์•ก, ๋…ผ๋ฆฌ ์˜ค๋ฅ˜, ํ‚ค์›Œ๋“œ ์ ์ค‘ ๋“ฑ)๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " d = df.copy()\n", " d[\"adv_no_bond\"] = (d[\"advance_pct\"] > 40) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"sole_amount\"] = np.where(d[\"method\"] == \"์ˆ˜์˜๊ณ„์•ฝ\", d[\"amount_m\"], 0.0) # ์ˆ˜์˜๊ณ„์•ฝ์ผ ๋•Œ๋งŒ ๊ธˆ์•ก(ํ•œ๋„๋Š” YAML์—์„œ ์กฐ์ •)\n", " d[\"bond_gap\"] = (d[\"amount_m\"] >= 500) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"warranty_gap\"] = (d[\"ctype\"] == \"๋ฌผํ’ˆ\") & (d[\"warranty_months\"] < 12)\n", " d[\"ip_missing\"] = (~d[\"ip_clause\"]) & (d[\"ctype\"] != \"๊ณต์‚ฌ\")\n", " d[\"logic_err\"] = d[\"delivery_days\"] > d[\"period_days\"]\n", " d[\"no_padj_long\"] = (d[\"period_days\"] >= 180) & (~d[\"price_adjust\"])\n", " d[\"noncomp_big\"] = d[\"method\"].isin([\"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]) & (d[\"amount_m\"] >= 1000)\n", " d[\"kw_hits\"] = [kw_count(c) for c in d[\"clauses\"]] if \"clauses\" in d else 0\n", " return d\n", "\n", "\n", "def apply_rules(rec, rules):\n", " \"\"\"๋ ˆ์ฝ”๋“œ 1๊ฑด(dict)์— ๋ฃฐ์„ ์ ์šฉํ•ด (์ ์ˆ˜, ๋“ฑ๊ธ‰, ์ ์ค‘ ๋ฃฐ id ๋ชฉ๋ก)์„ ๋Œ๋ ค์ค€๋‹ค. ๊ฐ’์ด ์—†์œผ๋ฉด ํ•ด๋‹น ๋ฃฐ์€ ๊ฑด๋„ˆ๋›ด๋‹ค.\"\"\"\n", " hits = []\n", " for r in rules:\n", " v = rec.get(r[\"field\"])\n", " if v is None or (isinstance(v, float) and np.isnan(v)):\n", " continue\n", " if OPS[r[\"op\"]](v, r[\"value\"]):\n", " hits.append(r)\n", " score = sum(r[\"weight\"] for r in hits)\n", " grade = \"๊ณ ์œ„ํ—˜\" if score >= GRADE_CUT[\"๊ณ ์œ„ํ—˜\"] else (\"์ฃผ์˜\" if score >= GRADE_CUT[\"์ฃผ์˜\"] else \"๊ด€์‹ฌ\")\n", " return score, grade, [r[\"id\"] for r in hits]\n", "\n", "SIMPLE_KW = {\"๋…์†Œ\": [\"๋ฉด์ œ\", \"์ผ๋ฐฉ\", \"ํฌ๊ธฐ\", \"์ „์•ก\", \"๋ˆ„์„ค\", \"ํ•˜๋„๊ธ‰\", \"์„ ์ง€๊ธ‰\"], \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์—ฐ์žฅ\", \"์ž๋™\", \"๋…ธ๋ ฅ\", \"ํ†ต๋ณด\", \"๊ธฐํ•œ\"]}\n", "\n", "# ---- ์กฐํ•ญ ๋ฌธ์žฅ ๋ถ„๋ฅ˜(์ •์ƒ / ๋…์†Œ / ๋‚ฉ๊ธฐ์œ„ํ—˜): ๊ธ€์ž n-gram TF-IDF + ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€ ----\n", "def clause_set(seed, n, hold=False, mix=(.55, .25, .20)):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ n๊ฐœ์™€ ์ •๋‹ต ๋ถ„๋ฅ˜๋ฅผ ๋งŒ๋“ ๋‹ค. hold=True๋ฉด ํ•™์Šต์— ์“ฐ์ง€ ์•Š์€ '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'์œผ๋กœ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " y = rng.choice(CLASSES, n, p=list(mix))\n", " return [make_clause(rng, c, hold) for c in y], list(y)\n", "\n", "\n", "def fit_clause_model(seed=100, n=1200):\n", " \"\"\"ํ•™์Šต์šฉ ์กฐํ•ญ ๋ฌธ์žฅ์œผ๋กœ ๋ถ„๋ฅ˜ ๋ชจ๋ธ์„ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " X, y = clause_set(seed, n)\n", " return make_pipeline(TfidfVectorizer(analyzer=\"char_wb\", ngram_range=(2, 4), sublinear_tf=True), LogisticRegression(C=20, max_iter=2000)).fit(X, y)\n", "\n", "\n", "def keyword_pred(s):\n", " \"\"\"๋น„๊ต์šฉ ๊ธฐ์ค€์„ : ๋‹จ์ˆœ ํ‚ค์›Œ๋“œ(๋ถ€์ • ํ‘œํ˜„ยท๋ฌธ๋งฅ์„ ๋ณด์ง€ ์•Š์Œ) ์ ์ค‘์œผ๋กœ๋งŒ ๋ถ„๋ฅ˜.\"\"\"\n", " if any(k in s for k in SIMPLE_KW[\"๋…์†Œ\"]):\n", " return \"๋…์†Œ\"\n", " return \"๋‚ฉ๊ธฐ์œ„ํ—˜\" if any(k in s for k in SIMPLE_KW[\"๋‚ฉ๊ธฐ์œ„ํ—˜\"]) else \"์ •์ƒ\"\n", "\n", "RULES = parse_rules(RULES_YAML)\n", "RID = [r[\"id\"] for r in RULES]\n", "WARN = {\"๊ณ ์œ„ํ—˜\": 60, \"์ฃผ์˜\": 30} # ์œ„ํ—˜ ์ ์ˆ˜ ๋“ฑ๊ธ‰ ๊ธฐ์ค€(์ ์ˆ˜ = ์œ„๋ฐ˜ ํ™•๋ฅ  x 100)\n", "\n", "\n", "def featurize(con, cm):\n", " \"\"\"๋ฃฐ 16์ข… ์ ์ค‘ ์—ฌ๋ถ€ + ์กฐํ•ญ ๋ชจ๋ธ์ด ์ฐพ์€ ๋…์†Œยท๋‚ฉ๊ธฐ์œ„ํ—˜ ์กฐํ•ญ ์ˆ˜ + ๊ธˆ์•ก(๋กœ๊ทธ).\"\"\"\n", " d = derive(con)\n", " H = np.array([[r in apply_rules(x, RULES)[2] for r in RID] for x in d.to_dict(\"records\")], float)\n", " flat = [c for cl in con[\"clauses\"] for c in cl]\n", " pred = np.array(cm.predict(flat)).reshape(len(con), 6)\n", " nt, nd = (pred == \"๋…์†Œ\").sum(1), (pred == \"๋‚ฉ๊ธฐ์œ„ํ—˜\").sum(1)\n", " return np.column_stack([H, nt, nd, np.log1p(con[\"amount_m\"].to_numpy())]), H, nt, nd\n", "\n", "\n", "FEAT_NAMES = [f\"{r['id']} {r['name']}\" for r in RULES] + [\"๋…์†Œ ์กฐํ•ญ ์ˆ˜(๋ชจ๋ธ)\", \"๋‚ฉ๊ธฐ์œ„ํ—˜ ๋ฌธ๊ตฌ ์ˆ˜(๋ชจ๋ธ)\", \"๊ธˆ์•ก(๋กœ๊ทธ)\"]\n", "cm = fit_clause_model()\n", "train, test = make_contracts(800, seed=11), make_contracts(300, seed=7)\n", "Xtr, _, _, _ = featurize(train, cm)\n", "Xte, Hte, nt, nd = featurize(test, cm)\n", "lr = make_pipeline(StandardScaler(), LogisticRegression(C=0.5, max_iter=1000)).fit(Xtr, train[\"violation\"].astype(int))\n", "y = test[\"violation\"].astype(int).to_numpy()\n", "p = lr.predict_proba(Xte)[:, 1]\n", "score = p * 100\n", "rule_flag = np.array([apply_rules(x, RULES)[1] != \"๊ด€์‹ฌ\" for x in derive(test).to_dict(\"records\")])\n", "agree = (rule_flag == (p >= 0.5)).astype(float)\n", "conf = np.clip(0.6 * np.abs(2 * p - 1) + 0.4 * agree, 0, 1) * 100 # ์‹ ๋ขฐ๋„ ์ง€์ˆ˜: ํ™•๋ฅ ์˜ ๋šœ๋ ทํ•จ + ๋ฃฐยท๋ชจ๋ธ ์ผ์น˜\n", "level = np.where(score >= WARN[\"๊ณ ์œ„ํ—˜\"], \"๊ณ ์œ„ํ—˜\", np.where(score >= WARN[\"์ฃผ์˜\"], \"์ฃผ์˜\", \"๊ด€์‹ฌ\"))\n", "T = test.assign(score=score, conf=conf, level=level, y=y)\n", "\n", "# ===== ํ™”๋ฉด 1. ์ž…๋ ฅ: ์ž…๋ ฅ ์š”์•ฝ๊ณผ ๊ฒ€์ฆ =====\n", "miss = int(test[[\"amount_m\", \"penalty_permil\", \"advance_pct\"]].isna().sum().sum())\n", "print(\"[ํ™”๋ฉด 1. ์ž…๋ ฅ] [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\")\n", "print(f\" ๊ฒ€์ฆ์šฉ ๊ณ„์•ฝ {len(test)}๊ฑด(์กฐํ•ญ {len(test) * 6}๊ฐœ) / ํ•™์Šต ๊ณ„์•ฝ {len(train)}๊ฑด / ์ž…๋ ฅ ์ง€ํ‘œ {len(FEAT_NAMES)}๊ฐœ / ํ•„์ˆ˜๊ฐ’ ๊ฒฐ์ธก {miss}๊ฑด\")\n", "print(f\" ์ˆ˜์˜๊ณ„์•ฝ {int((test['method'] == '์ˆ˜์˜๊ณ„์•ฝ').sum())}๊ฑด, ํ‰๊ท  ๊ธˆ์•ก {test['amount_m'].mean():.0f}๋ฐฑ๋งŒ์›, ๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ {int((~test['security']).sum())}๊ฑด\")\n", "\n", "# ===== ํ™”๋ฉด 2. ๋ถ„์„: ์ ์ˆ˜ ๋ถ„ํฌ์™€ ์„ฑ๋Šฅ =====\n", "auc = roc_auc_score(y, score)\n", "print(\"\\n[ํ™”๋ฉด 2. ๋ถ„์„] ์œ„๋ฐ˜ ์œ„ํ—˜ ์ ์ˆ˜์™€ ๋ชจ๋ธ ์„ฑ๋Šฅ\")\n", "print(f\" ROC-AUC {auc:.3f}, Brier {brier_score_loss(y, p):.3f} (์‹ค์ œ ์œ„๋ฐ˜ ๋น„์œจ {y.mean():.0%}), ๋ฃฐ ์ ์ˆ˜ ๋‹จ๋… AUC {roc_auc_score(y, Hte @ np.array([r['weight'] for r in RULES])):.3f}\")\n", "print(T.groupby(\"level\").agg(๊ณ„์•ฝ์ˆ˜=(\"contract_id\", \"size\"), ํ‰๊ท ์ ์ˆ˜=(\"score\", \"mean\"), ์‹ค์ œ์œ„๋ฐ˜์œจ=(\"y\", \"mean\")).reindex([\"๊ณ ์œ„ํ—˜\", \"์ฃผ์˜\", \"๊ด€์‹ฌ\"]).round(2).to_string())\n", "coef = pd.Series(lr[-1].coef_[0], index=FEAT_NAMES).sort_values(ascending=False)\n", "print(\" ์œ„ํ—˜ ์ƒ์Šน ์š”์ธ(ํšŒ๊ท€ ๊ณ„์ˆ˜ ์ƒ์œ„ 4):\", \", \".join(f\"{k.split(' ')[0]} {v:+.2f}\" for k, v in coef.head(4).items()))\n", "print(\" [์‹ ๋ขฐ๋„ ์ง€์ˆ˜์™€ ์‹ค์ œ ์ •ํ™•๋„] (ํŒ์ • = ์ ์ˆ˜ 50 ์ด์ƒ์„ ์œ„๋ฐ˜ ๊ฐ€๋Šฅ์œผ๋กœ ๋ด„)\")\n", "bins = pd.cut(T[\"conf\"], [0, 50, 75, 90, 100.1], labels=[\"50 ๋ฏธ๋งŒ\", \"50~75\", \"75~90\", \"90 ์ด์ƒ\"])\n", "ok = ((p >= 0.5).astype(int) == y)\n", "cal = pd.DataFrame({\"bin\": bins, \"ok\": ok}).groupby(\"bin\", observed=True).agg(๊ฑด์ˆ˜=(\"ok\", \"size\"), ์‹ค์ œ์ •ํ™•๋„=(\"ok\", \"mean\")).round(2)\n", "print(cal.to_string())\n", "\n", "# ===== ํ™”๋ฉด 3. ๊ฒ€ํ† : ๊ณ ์œ„ํ—˜ ๊ณ„์•ฝ์„ ๋‹ด๋‹น์ž๊ฐ€ ํ™•์ธ =====\n", "rev = T[T[\"level\"] == \"๊ณ ์œ„ํ—˜\"].sort_values(\"score\", ascending=False)\n", "contrib = (lr[0].transform(Xte) * lr[-1].coef_[0])\n", "BADGE = {\"๊ณ ์œ„ํ—˜\": \"[๊ฒ€ํ†  ํ•„์ˆ˜]\", \"์ฃผ์˜\": \"[ํ™•์ธ ๊ถŒ๊ณ ]\", \"๊ด€์‹ฌ\": \"[ํ†ต๊ณผ ํ›„๋ณด]\"}\n", "print(f\"\\n[ํ™”๋ฉด 3. ๊ฒ€ํ† ] ๊ณ ์œ„ํ—˜ {len(rev)}๊ฑด ์ค‘ ์ƒ์œ„ 5๊ฑด (๋‹ด๋‹น์ž๊ฐ€ ๊ทผ๊ฑฐ๋ฅผ ํ™•์ธํ•˜๊ณ  ์กฐ์น˜ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •)\")\n", "for i, r in rev.head(5).iterrows():\n", " top = [FEAT_NAMES[j].split(\" \")[0] + \":\" + FEAT_NAMES[j].split(\" \", 1)[1][:10] for j in np.argsort(-contrib[i])[:2] if contrib[i][j] > 0]\n", " print(f\" {BADGE[r['level']]} {r['contract_id']} {r['ctype']} {r['amount_m']:.0f}๋ฐฑ๋งŒ์› | ์œ„ํ—˜ {r['score']:.0f}์  ์‹ ๋ขฐ๋„ {r['conf']:.0f} | ์š”์ธ {', '.join(top) or '-'} | ์ƒํƒœ ๊ฒ€ํ† ๋Œ€๊ธฐ\")\n", "\n", "# ===== ํ™”๋ฉด 4. ๊ฒฐ๊ณผ: ์š”์•ฝ, ๋ฑƒ์ง€ ํ•„ํ„ฐ, ๋‚ด๋ณด๋‚ด๊ธฐ =====\n", "print(\"\\n[ํ™”๋ฉด 4. ๊ฒฐ๊ณผ]\")\n", "cnt = T[\"level\"].value_counts()\n", "print(f\" {BADGE['๊ณ ์œ„ํ—˜']} {cnt.get('๊ณ ์œ„ํ—˜', 0)}๊ฑด, {BADGE['์ฃผ์˜']} {cnt.get('์ฃผ์˜', 0)}๊ฑด, {BADGE['๊ด€์‹ฌ']} {cnt.get('๊ด€์‹ฌ', 0)}๊ฑด\")\n", "f = T[(T[\"level\"] == \"์ฃผ์˜\") & (T[\"conf\"] < 75)]\n", "print(f\" ํ•„ํ„ฐ ์˜ˆ์‹œ: ๋“ฑ๊ธ‰ '์ฃผ์˜' ์ด๋ฉด์„œ ์‹ ๋ขฐ๋„ 75 ๋ฏธ๋งŒ -> {len(f)}๊ฑด(์‚ฌ๋žŒ์ด ๋จผ์ € ํ™•์ธํ•  ๊ฒฝ๊ณ„ ์‚ฌ๋ก€)\")\n", "print(f\" ๊ณ ์œ„ํ—˜ ๋“ฑ๊ธ‰์˜ ์‹ค์ œ ์œ„๋ฐ˜ {int(rev['y'].sum())}๊ฑด({rev['y'].mean():.0%}) vs ๊ด€์‹ฌ ๋“ฑ๊ธ‰ {T[T['level'] == '๊ด€์‹ฌ']['y'].mean():.0%}\")\n", "print(\" ๋‚ด๋ณด๋‚ด๊ธฐ ํ•ญ๋ชฉ: ๊ณ„์•ฝ๋ฒˆํ˜ธ, ์œ„ํ—˜ ์ ์ˆ˜, ์‹ ๋ขฐ๋„, ๋“ฑ๊ธ‰ ๋ฑƒ์ง€, ์š”์ธ, ๊ฒ€ํ†  ์ƒํƒœ(๊ฒ€ํ† ๋Œ€๊ธฐ)\")\n", "print(\"ํ™”๋ฉด 4์ข…(์ž…๋ ฅ-๋ถ„์„-๊ฒ€ํ† -๊ฒฐ๊ณผ) ๊ตฌํ˜„: 4/4 (๋ชฉํ‘œ 4์ข…: ๋‹ฌ์„ฑ)\")\n", "print(\"โ€ป ์ ์ˆ˜์™€ ์‹ ๋ขฐ๋„๋Š” ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ •ํ•˜๋Š” ์ฐธ๊ณ ๊ฐ’์ด๋ฉฐ, ๊ทœ์ • ์œ„๋ฐ˜ ์—ฌ๋ถ€์˜ ํŒ๋‹จ๊ณผ ์กฐ์น˜๋Š” ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด ๋‹ด๋‹น์ž๊ฐ€ ํ•œ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(2, 2, figsize=(11, 7))\n", "ax[0, 0].bar([\"sole-source\", \"no security\", \"no termination\"], [(test[\"method\"] == \"์ˆ˜์˜๊ณ„์•ฝ\").sum(), (~test[\"security\"]).sum(), (~test[\"termination\"]).sum()]); ax[0, 0].set_title(\"Screen 1: input summary (count)\")\n", "ax[0, 1].hist([T[T[\"y\"] == 0][\"score\"], T[T[\"y\"] == 1][\"score\"]], bins=15, label=[\"no violation\", \"violation\"], stacked=True); ax[0, 1].legend(); ax[0, 1].set_title(\"Screen 2: risk score distribution\")\n", "ax[1, 0].scatter(T[\"score\"], T[\"conf\"], c=T[\"y\"], cmap=\"coolwarm\", s=14); ax[1, 0].set_xlabel(\"risk score\"); ax[1, 0].set_ylabel(\"confidence index\"); ax[1, 0].set_title(\"Screen 3: review map (color = actual)\")\n", "ax[1, 1].bar([\"high\", \"watch\", \"low\"], [cnt.get(\"๊ณ ์œ„ํ—˜\", 0), cnt.get(\"์ฃผ์˜\", 0), cnt.get(\"๊ด€์‹ฌ\", 0)], color=[\"tab:red\", \"gold\", \"tab:green\"]); ax[1, 1].set_title(\"Screen 4: result by grade\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 05 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[ํ™”๋ฉด 1. ์ž…๋ ฅ] [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\n", " ๊ฒ€์ฆ์šฉ ๊ณ„์•ฝ 300๊ฑด(์กฐํ•ญ 1800๊ฐœ) / ํ•™์Šต ๊ณ„์•ฝ 800๊ฑด / ์ž…๋ ฅ ์ง€ํ‘œ 19๊ฐœ / ํ•„์ˆ˜๊ฐ’ ๊ฒฐ์ธก 0๊ฑด\n", " ์ˆ˜์˜๊ณ„์•ฝ 38๊ฑด, ํ‰๊ท  ๊ธˆ์•ก 381๋ฐฑ๋งŒ์›, ๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ 10๊ฑด\n", "\n", "[ํ™”๋ฉด 2. ๋ถ„์„] ์œ„๋ฐ˜ ์œ„ํ—˜ ์ ์ˆ˜์™€ ๋ชจ๋ธ ์„ฑ๋Šฅ\n", " ROC-AUC 0.954, Brier 0.062 (์‹ค์ œ ์œ„๋ฐ˜ ๋น„์œจ 41%), ๋ฃฐ ์ ์ˆ˜ ๋‹จ๋… AUC 0.889\n", " ๊ณ„์•ฝ์ˆ˜ ํ‰๊ท ์ ์ˆ˜ ์‹ค์ œ์œ„๋ฐ˜์œจ\n", "level \n", "๊ณ ์œ„ํ—˜ 112 90.48 0.94\n", "์ฃผ์˜ 8 48.49 1.00\n", "๊ด€์‹ฌ 180 10.12 0.05\n", " ์œ„ํ—˜ ์ƒ์Šน ์š”์ธ(ํšŒ๊ท€ ๊ณ„์ˆ˜ ์ƒ์œ„ 4): ๋…์†Œ +2.62, R07 +1.04, R01 +0.98, R04 +0.76\n", " [์‹ ๋ขฐ๋„ ์ง€์ˆ˜์™€ ์‹ค์ œ ์ •ํ™•๋„] (ํŒ์ • = ์ ์ˆ˜ 50 ์ด์ƒ์„ ์œ„๋ฐ˜ ๊ฐ€๋Šฅ์œผ๋กœ ๋ด„)\n", " ๊ฑด์ˆ˜ ์‹ค์ œ์ •ํ™•๋„\n", "bin \n", "50 ๋ฏธ๋งŒ 38 0.84\n", "50~75 31 0.87\n", "75~90 67 0.97\n", "90 ์ด์ƒ 164 0.95\n", "\n", "[ํ™”๋ฉด 3. ๊ฒ€ํ† ] ๊ณ ์œ„ํ—˜ 112๊ฑด ์ค‘ ์ƒ์œ„ 5๊ฑด (๋‹ด๋‹น์ž๊ฐ€ ๊ทผ๊ฑฐ๋ฅผ ํ™•์ธํ•˜๊ณ  ์กฐ์น˜ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •)\n", " [๊ฒ€ํ†  ํ•„์ˆ˜] C-0222 ๋ฌผํ’ˆ 162๋ฐฑ๋งŒ์› | ์œ„ํ—˜ 100์  ์‹ ๋ขฐ๋„ 100 | ์š”์ธ ๋…์†Œ:์กฐํ•ญ ์ˆ˜(๋ชจ๋ธ), R06:๋ฌผํ’ˆ ํ•˜์ž๋ณด์ฆ 12 | ์ƒํƒœ ๊ฒ€ํ† ๋Œ€๊ธฐ\n", " [๊ฒ€ํ†  ํ•„์ˆ˜] C-0028 ๋ฌผํ’ˆ 590๋ฐฑ๋งŒ์› | ์œ„ํ—˜ 100์  ์‹ ๋ขฐ๋„ 100 | ์š”์ธ ๋…์†Œ:์กฐํ•ญ ์ˆ˜(๋ชจ๋ธ), R08:๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ | ์ƒํƒœ ๊ฒ€ํ† ๋Œ€๊ธฐ\n", " [๊ฒ€ํ†  ํ•„์ˆ˜] C-0266 ๋ฌผํ’ˆ 68๋ฐฑ๋งŒ์› | ์œ„ํ—˜ 100์  ์‹ ๋ขฐ๋„ 100 | ์š”์ธ ๋…์†Œ:์กฐํ•ญ ์ˆ˜(๋ชจ๋ธ), R08:๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ | ์ƒํƒœ ๊ฒ€ํ† ๋Œ€๊ธฐ\n", " [๊ฒ€ํ†  ํ•„์ˆ˜] C-0037 ๋ฌผํ’ˆ 62๋ฐฑ๋งŒ์› | ์œ„ํ—˜ 100์  ์‹ ๋ขฐ๋„ 100 | ์š”์ธ ๋…์†Œ:์กฐํ•ญ ์ˆ˜(๋ชจ๋ธ), R10:ํ•˜๋„๊ธ‰ ๋น„์œจ 50% | ์ƒํƒœ ๊ฒ€ํ† ๋Œ€๊ธฐ\n", " [๊ฒ€ํ†  ํ•„์ˆ˜] C-0221 ์šฉ์—ญ 101๋ฐฑ๋งŒ์› | ์œ„ํ—˜ 100์  ์‹ ๋ขฐ๋„ 100 | ์š”์ธ ๋…์†Œ:์กฐํ•ญ ์ˆ˜(๋ชจ๋ธ), R10:ํ•˜๋„๊ธ‰ ๋น„์œจ 50% | ์ƒํƒœ ๊ฒ€ํ† ๋Œ€๊ธฐ\n", "\n", "[ํ™”๋ฉด 4. ๊ฒฐ๊ณผ]\n", " [๊ฒ€ํ†  ํ•„์ˆ˜] 112๊ฑด, [ํ™•์ธ ๊ถŒ๊ณ ] 8๊ฑด, [ํ†ต๊ณผ ํ›„๋ณด] 180๊ฑด\n", " ํ•„ํ„ฐ ์˜ˆ์‹œ: ๋“ฑ๊ธ‰ '์ฃผ์˜' ์ด๋ฉด์„œ ์‹ ๋ขฐ๋„ 75 ๋ฏธ๋งŒ -> 8๊ฑด(์‚ฌ๋žŒ์ด ๋จผ์ € ํ™•์ธํ•  ๊ฒฝ๊ณ„ ์‚ฌ๋ก€)\n", " ๊ณ ์œ„ํ—˜ ๋“ฑ๊ธ‰์˜ ์‹ค์ œ ์œ„๋ฐ˜ 105๊ฑด(94%) vs ๊ด€์‹ฌ ๋“ฑ๊ธ‰ 5%\n", " ๋‚ด๋ณด๋‚ด๊ธฐ ํ•ญ๋ชฉ: ๊ณ„์•ฝ๋ฒˆํ˜ธ, ์œ„ํ—˜ ์ ์ˆ˜, ์‹ ๋ขฐ๋„, ๋“ฑ๊ธ‰ ๋ฑƒ์ง€, ์š”์ธ, ๊ฒ€ํ†  ์ƒํƒœ(๊ฒ€ํ† ๋Œ€๊ธฐ)\n", "ํ™”๋ฉด 4์ข…(์ž…๋ ฅ-๋ถ„์„-๊ฒ€ํ† -๊ฒฐ๊ณผ) ๊ตฌํ˜„: 4/4 (๋ชฉํ‘œ 4์ข…: ๋‹ฌ์„ฑ)\n", "โ€ป ์ ์ˆ˜์™€ ์‹ ๋ขฐ๋„๋Š” ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ •ํ•˜๋Š” ์ฐธ๊ณ ๊ฐ’์ด๋ฉฐ, ๊ทœ์ • ์œ„๋ฐ˜ ์—ฌ๋ถ€์˜ ํŒ๋‹จ๊ณผ ์กฐ์น˜๋Š” ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด ๋‹ด๋‹น์ž๊ฐ€ ํ•œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 06. ๊ณ„์•ฝ์„œ ์กฐํ•ญ๋ณ„ ์œ„ํ—˜๋„ ํ•˜์ด๋ผ์ดํŠธ ๋ฐ ์กฐ๋‹ฌ ๊ฒ€ํ†  ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๊ณ„์•ฝ์„œ ์กฐํ•ญ๋ณ„ ์œ„ํ—˜๋„ ํ•˜์ด๋ผ์ดํŠธ ๋ฐ ์กฐ๋‹ฌ ๊ฒ€ํ†  ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๊ณ„์•ฝ์„œ ์กฐํ•ญ๋ณ„ ์œ„ํ—˜๋„ ํ•˜์ด๋ผ์ดํŠธ์™€ ์กฐ๋‹ฌ ๊ฒ€ํ†  ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์ž…๋ ฅ ๊ฒ€์ฆ๊ธฐยท์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ๋กœ ์˜ˆ์™ธ ์ƒํ™ฉ์—์„œ๋„ ํฌ๋ž˜์‹œ ์—†์ด ๋™์ž‘ํ•˜๊ฒŒ ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ณ„์•ฝ ๋ฐฉ์‹ x ์œ ํ˜• ์œ„ํ—˜ ์ง€๋„์™€ ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ๋งŽ์€ ๊ณ„์•ฝ์˜ ์กฐํ•ญ๋ณ„ ํ•˜์ด๋ผ์ดํŠธ(๋…์†Œยท๋‚ฉ๊ธฐ์œ„ํ—˜ ํ‘œ์‹œ, ์œ„ํ—˜ ํ‘œํ˜„ ๊ฐ•์กฐ)๋ฅผ ๋ณด์—ฌ ์ฃผ๋Š” ๋Œ€์‹œ๋ณด๋“œ ์ž‘์„ฑํ•˜์—ฌ ์›์ธ์„ ๋ถ„์„ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/utils/prc01_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": [ "\"\"\"๊ณ„์•ฝ์„œ ์กฐํ•ญ๋ณ„ ์œ„ํ—˜๋„ ํ•˜์ด๋ผ์ดํŠธ์™€ ๊ณ„์•ฝ ๋ฐฉ์‹ x ์œ ํ˜• ์œ„ํ—˜ ์ง€๋„๋ฅผ ๋ณด์—ฌ ์ฃผ๋Š” ์กฐ๋‹ฌ ๊ฒ€ํ†  ๋Œ€์‹œ๋ณด๋“œ์— ์ž…๋ ฅ ๊ฒ€์ฆ๊ธฐยท์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌยท์žฌ์‹œ๋„ยท๋กค๋ฐฑ์„ ๋ถ™์ด๊ณ , ๊นจ์ง„ ์ž…๋ ฅ 20์ข…(๊ฒฐ์ธกยทํŠน์ˆ˜๋ฌธ์žยท๋Œ€์šฉ๋Ÿ‰ยท์ผ์‹œ ์˜ค๋ฅ˜ ํฌํ•จ)์„ ์ฃผ์ž…ํ•ด ํฌ๋ž˜์‹œ 0๊ฑดยท์—๋Ÿฌ ๋กœ๊ทธ ๊ธฐ๋ก๋ฅ  100%ยท์‚ฌ์šฉ์ž์šฉ ์กฐ์น˜ ์•ˆ๋‚ด๋ฅผ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import csv\n", "import io\n", "import json\n", "import re\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์กฐ๋‹ฌ๊ณ„ํš์•ˆยท๊ณ„์•ฝ์„œ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ณ„์•ฝยท์—…์ฒดยท๋ฒ•๋ น ์กฐ๋ฌธ ์—†์Œ)\n", "# ๊ณ„์•ฝ ์†์„ฑ 16๊ฐœ + ์กฐํ•ญ ๋ฌธ์žฅ 6๊ฐœ๋ฅผ ๋งŒ๋“ค๊ณ , ์†์„ฑ ์œ„๋ฐ˜ยท๋…์†Œ ์กฐํ•ญยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ์„ž์ธ '๊ทœ์ • ์œ„๋ฐ˜ ์—ฌ๋ถ€'๋ฅผ ํ•จ๊ป˜ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "TYPES = [\"๋ฌผํ’ˆ\", \"์šฉ์—ญ\", \"๊ณต์‚ฌ\"]\n", "METHODS = [\"์ผ๋ฐ˜๊ฒฝ์Ÿ\", \"์ œํ•œ๊ฒฝ์Ÿ\", \"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]\n", "CLASSES = [\"์ •์ƒ\", \"๋…์†Œ\", \"๋‚ฉ๊ธฐ์œ„ํ—˜\"]\n", "OBJ = [\"๋‚ฉํ’ˆ ๋ฌผํ’ˆ\", \"์šฉ์—ญ ๊ฒฐ๊ณผ๋ฌผ\", \"์‹œ๊ณต ๋ชฉ์ ๋ฌผ\", \"์ •๋น„ ์„ฑ๊ณผ๋ฌผ\"]\n", "\n", "N_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋‚ฉ๊ธฐ๋ฅผ ์ง€์ฒดํ•œ ๊ฒฝ์šฐ ์ง€์ฒด์ผ์ˆ˜ 1์ผ๋งˆ๋‹ค ๊ณ„์•ฝ๊ธˆ์•ก์˜ {p}/1000์— ํ•ด๋‹นํ•˜๋Š” ์ง€์ฒด์ƒ๊ธˆ์„ ๋ถ€๊ณผํ•œ๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๊ฐ€ ํ™•์ธ๋œ ๊ฒฝ์šฐ์— ํ•œํ•˜์—ฌ ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์Šน์ธ์„ ๋ฐ›์•„ ๋ฉด์ œํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ ์—ฐ์žฅ์€ ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๋ฅผ ํ™•์ธํ•˜๊ณ  ์„œ๋ฉด์œผ๋กœ ์Šน์ธํ•œ ๊ฒฝ์šฐ์—๋งŒ ์ธ์ •ํ•œ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋‚ฉํ’ˆ๋œ {o}์„ {d}์ผ ์ด๋‚ด์— ๊ฒ€์‚ฌํ•˜๊ณ  ํ•ฉ๊ฒฉํ•œ ํ›„์— ๋Œ€๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ํ•˜๋„๊ธ‰์„ ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ์‚ฌ์ „์— ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์„œ๋ฉด ์Šน์ธ์„ ๋ฐ›์•„์•ผ ํ•œ๋‹ค.\",\n", " \"์–ด๋А ํ•œ์ชฝ์ด ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•˜๋ ค๋ฉด ์‚ฌ์œ ๋ฅผ ๋ช…์‹œํ•ด {d}์ผ ์ „์— ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"{o}์— ํ•˜์ž๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ๋ณด์ฆ ๊ธฐ๊ฐ„ ์•ˆ์— ๋ฌด์ƒ์œผ๋กœ ๋ณด์ˆ˜ํ•˜๊ฑฐ๋‚˜ ๊ต์ฒดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ๋น„๊ณต๊ฐœ ์‚ฌํ•ญ์€ ์ œ3์ž์—๊ฒŒ ๋ˆ„์„คํ•˜์ง€ ์•Š์œผ๋ฉฐ ์œ„๋ฐ˜ ์‹œ ์ฑ…์ž„์„ ์ง„๋‹ค.\",\n", " \"{o}์— ๋Œ€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์˜ ๊ท€์†๊ณผ ์‚ฌ์šฉ ๋ฒ”์œ„๋Š” ๋ณ„๋„ ํ•ฉ์˜์„œ์— ๋”ฐ๋ผ ์ •ํ•œ๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ํ•ฉ๊ฒฉ ํ›„ ์ฒญ๊ตฌ์„œ๋ฅผ ์ ‘์ˆ˜ํ•œ ๋‚ ๋ถ€ํ„ฐ {d}์ผ ์ด๋‚ด์— ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ๊ธˆ์•ก์˜ ์กฐ์ •์€ ๋ฌผ๊ฐ€๋ณ€๋™ ๊ธฐ์ค€์— ๋”ฐ๋ผ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ์„œ๋ฉด์œผ๋กœ ํ•ฉ์˜ํ•œ ๊ฒฝ์šฐ์—๋งŒ ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ดํ–‰๋ณด์ฆ์„œ๋ฅผ ๊ณ„์•ฝ ์ฒด๊ฒฐ ์‹œ ์ œ์ถœํ•˜๋ฉฐ ๋ณด์ฆ๊ธˆ์€ ๊ณ„์•ฝ ์ดํ–‰ ์™„๋ฃŒ ํ›„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ณ„์•ฝ์„œ ๋ณ„ํ‘œ์˜ ์ผ์ž๋ฅผ ๋”ฐ๋ฅด๋ฉฐ ๋ณ€๊ฒฝ ์‹œ ์„œ๋ฉด ํ•ฉ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผํ•˜๋ฉฐ ์ด์˜๋Š” ์ •ํ•ด์ง„ ์ ˆ์ฐจ๋กœ ์ œ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•˜์ง€ ๋ชปํ•œ {o}์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๊ต์ฒดํ•˜๋ฉฐ ๊ทธ ๋น„์šฉ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ๊ท€์ฑ… ์‚ฌ์œ ๋กœ ์ดํ–‰์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•˜๊ณ  ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"์ง€์—ฐ์ด ์˜ˆ์ƒ๋˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ๋ฐœ์ฃผ๊ธฐ๊ด€์— ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•œ๋‹ค.\",\n", " \"๋ณด์ฆ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง€๊ณ  ๋ณด์ˆ˜ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์€ ๋จผ์ € ํ˜‘์˜๋กœ ํ•ด๊ฒฐํ•˜๊ณ  ํ˜‘์˜๊ฐ€ ๋˜์ง€ ์•Š์œผ๋ฉด ๊ด€ํ•  ๋ฒ•์›์˜ ํŒ๋‹จ์— ๋”ฐ๋ฅธ๋‹ค.\",\n", "]\n", "T_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ง€์ฒด์ƒ๊ธˆ์€ ์ „์•ก ๋ฉด์ œํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋Œ€ํ•ด ์ด์˜๋ฅผ ์ œ๊ธฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ฆ‰์‹œ ๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ ๋ณด๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ดํ›„ ๊ฒ€์‚ฌ๋ฅผ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์–ธ์ œ๋“ ์ง€ ์ผ๋ฐฉ์ ์œผ๋กœ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ์— ๋”ฐ๋ฅธ ์†ํ•ด๋Š” ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"{o}์˜ ์ถ”๊ฐ€ ๋น„์šฉ์€ ์‚ฌ์œ ๋ฅผ ๋ถˆ๋ฌธํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ์ „์•ก ๋ถ€๋‹ดํ•˜๋ฉฐ ๋ณ„๋„ ํ˜‘์˜ ์—†์ด ์ฒญ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"{o}์— ๊ด€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์€ ๋ชจ๋‘ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ๊ท€์†๋˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"ํ•˜๋„๊ธ‰์€ ์‹ ๊ณ  ์—†์ด ์ œํ•œ ์—†์ด ํ—ˆ์šฉํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ํ•˜๋„๊ธ‰ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ์ „์— ์ „์•ก ์„ ์ง€๊ธ‰ํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ฐ˜ํ™˜ ์ฒญ๊ตฌ๊ถŒ์„ ํฌ๊ธฐํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ์‚ฌํ•ญ์˜ ๋ˆ„์„ค๋กœ ์ธํ•œ ์ฑ…์ž„์„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"ํ•˜์ž ๋ณด์ˆ˜ ์ฑ…์ž„์€ ์ธ๋„ ํ›„ ์ฆ‰์‹œ ์†Œ๋ฉธํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ณด์ˆ˜๋ฅผ ์š”๊ตฌํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฒญ๊ตฌํ•˜๋Š” ๊ธˆ์•ก์„ ๊ฑฐ์ ˆํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ๊ฑฐ์ ˆํ•˜๋ฉด ์œ„์•ฝ๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์ด ์ƒ๊ธฐ๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ •ํ•˜๋Š” ์ค‘์žฌ ๊ธฐ๊ด€์—์„œ๋งŒ ํ•ด๊ฒฐํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์‚ฌ๋Š” ์ƒ๋žตํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์‹œํ—˜ ๊ฒฐ๊ณผ์„œ๋กœ ๋Œ€์‹ ํ•œ๋‹ค.\",\n", " \"๊ฒฌ์  ๊ธˆ์•ก์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ๊ทธ๋Œ€๋กœ ํ™•์ •ํ•˜๊ณ  ์ฆ๋น™ ์ž๋ฃŒ๋Š” ๋ฐ›์ง€ ์•Š๋Š”๋‹ค.\",\n", "]\n", "D_TPL = [\n", " \"๋‚ฉ๊ธฐ๋Š” ์ž์žฌ ์ˆ˜๊ธ‰ ์‚ฌ์ •์— ๋”ฐ๋ผ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ฐ€๋Šฅํ•œ ํ•œ ์ง€ํ‚ค๋„๋ก ๋…ธ๋ ฅํ•˜๋ฉฐ ์ง€์—ฐ๋˜์–ด๋„ ์‚ฌ์œ ๋ฅผ ๋ณด๊ณ ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณต๊ธ‰ ์ง€์—ฐ์ด๋‚˜ ์„ ํ–‰ ์—…์ฒด ์‚ฌ์ •๋„ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ์œผ๋กœ ๋ณด์•„ ๋‚ฉ๊ธฐ๋ฅผ ์ž๋™ ์—ฐ์žฅํ•œ๋‹ค.\",\n", " \"์ฐฉ์ˆ˜์ผ์€ ์„ ํ–‰ ์—…์ฒด์˜ ๋‚ฉํ’ˆ์ด ๋๋‚œ ๋’ค ๋ณ„๋„๋กœ ์ •ํ•˜๋ฉฐ ์™„๋ฃŒ ๊ธฐํ•œ์€ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ์ผ ์ดํ›„ 1ํšŒ์— ํ•œํ•ด ๊ธฐํ•œ ์—†์ด ์ž๋™ ์—ฐ์žฅ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํ•œ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๋Š” ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ƒ์‚ฐ ์ผ์ •์ด ํ™•์ •๋˜๋Š” ๋Œ€๋กœ ํ†ต๋ณดํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์‹œ์ ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž ์‚ฌ์ •์ด ํ—ˆ๋ฝํ•˜๋Š” ๋•Œ๋กœ ํ•˜๊ณ  ๋‚ ์งœ๋Š” ๋”ฐ๋กœ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ž์žฌ๊ฐ€ ๋Šฆ๊ฒŒ ๋“ค์–ด์˜ค๋ฉด ์™„๋ฃŒ ์ผ์ •์€ ๊ทธ๋งŒํผ ์ž๋™์œผ๋กœ ๋Šฆ์ถฐ์ง„๋‹ค.\",\n", " \"๊ธฐํ•œ ๋‚ด์— ์ธ๋„ํ•˜์ง€ ๋ชปํ•ด๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ •์ด ๋Šฆ์–ด์ ธ๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ƒˆ ๊ณ„ํš์„œ๋ฅผ ๋‚ผ ํ•„์š”๊ฐ€ ์—†๋‹ค.\",\n", "]\n", "# ํ•™์Šต์— ์“ฐ์ง€ ์•Š๋Š” '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'(๋ชจ๋ธ์ด ๋ฌธ์žฅ ํ‹€์„ ์™ธ์› ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์šฉ๋„)\n", "HOLD = {\n", " \"์ •์ƒ\": [\"๊ฒ€์‚ฌ์— ๋ถˆํ•ฉ๊ฒฉํ•œ ๊ฒฝ์šฐ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ • ๊ธฐํ•œ ์•ˆ์— ์žฌ๋‚ฉํ’ˆํ•˜๋ฉฐ ์žฌ๊ฒ€์‚ฌ ๋น„์šฉ์€ ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ฒ•๋ น์„ ์œ„๋ฐ˜ํ•˜์—ฌ ๊ณ„์•ฝ์„ ์ดํ–‰ํ•˜์ง€ ๋ชปํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด ํ†ต์ง€ ํ›„ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๊ฐ€ ์ง€์—ฐ๋  ์šฐ๋ ค๊ฐ€ ์žˆ์œผ๋ฉด ์ƒ๋Œ€์ž๋Š” ์ฆ‰์‹œ ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"ํ•˜์ž๋‹ด๋ณด ์ฑ…์ž„ ๊ธฐ๊ฐ„์€ ์ธ์ˆ˜์ผ๋ถ€ํ„ฐ ๊ธฐ์‚ฐํ•˜๋ฉฐ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง„๋‹ค.\"],\n", " \"๋…์†Œ\": [\"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๋Œ€๊ธˆ์„ ์ด์œ  ์—†์ด ๊ฑฐ์ ˆํ•˜์ง€ ๋ชปํ•˜๋ฉฐ ๊ฑฐ์ ˆ ์‹œ ์œ„์•ฝ๊ธˆ์„ ๋‚ธ๋‹ค.\",\n", " \"๊ณ„์•ฝ ๋‚ด์šฉ์— ๊ด€ํ•œ ๋ถ„์Ÿ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€์—์„œ๋งŒ ๋‹ค๋ฃจ๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์ˆ˜ ๊ถŒํ•œ์€ ํฌ๊ธฐํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ž์ฒด ์‹œํ—˜ ์„ฑ์ ์„œ๋กœ ๊ฐˆ์Œํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ œ์ถœํ•œ ๊ฒฌ์ ์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ํ™•์ • ๋‹จ๊ฐ€๋กœ ํ•˜๋ฉฐ ์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"๋‚ฉํ’ˆ ์‹œ๊ธฐ๋Š” ์ƒ๋Œ€์ž์˜ ํ˜•ํŽธ์ด ํ—ˆ๋ฝํ•˜๋Š” ์‹œ์ ์œผ๋กœ ํ•˜๊ณ  ๋ณ„๋„ ๋‚ ์งœ๋Š” ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์›์ž์žฌ ํ™•๋ณด๊ฐ€ ๋Šฆ์–ด์ง€๋ฉด ์™„๋ฃŒ ์ผ์ž๋Š” ์ž๋™์œผ๋กœ ๋’ค๋กœ ๋ฏธ๋ค„์ง€๋Š” ๊ฒƒ์œผ๋กœ ๋ณธ๋‹ค.\",\n", " \"์ธ๋„ ๊ธฐํ•œ์€ ์ฐธ๊ณ ์šฉ์ด๋ฉฐ ์ง€ํ‚ค์ง€ ๋ชปํ•ด๋„ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ • ์ง€์—ฐ์ด ์žˆ์–ด๋„ ์ƒ๋Œ€์ž๋Š” ๋ณ€๊ฒฝ ๊ณ„ํš์„ ์ œ์ถœํ•  ์˜๋ฌด๊ฐ€ ์—†๋‹ค.\"],\n", "}\n", "SUFFIX = [\"\", \"\", \"\", \" ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ์‹œํ–‰ ์„ธ์น™์— ๋”ฐ๋ฅธ๋‹ค.\", \" ์ด ์กฐํ•ญ์€ ๊ณ„์•ฝ ์ „์ฒด์— ์ ์šฉํ•œ๋‹ค.\", \" ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ํ˜‘์˜ํ•œ๋‹ค.\", \" ๊ด€๋ จ ์„œ์‹์€ ๋ณ„์ง€๋ฅผ ๋”ฐ๋ฅธ๋‹ค.\"]\n", "SWAP = [(\"๋ฐœ์ฃผ๊ธฐ๊ด€\", \"๊ณ„์•ฝ๋‹ด๋‹น๊ด€\"), (\"๊ณ„์•ฝ์ƒ๋Œ€์ž\", \"์—…์ฒด\")]\n", "\n", "\n", "def make_clause(rng, cls, hold=False, idx=None):\n", " \"\"\"์กฐํ•ญ ํ•œ ๋ฌธ์žฅ ์ƒ์„ฑ. ํ‘œํ˜„ ์ผ๋ถ€๋Š” ๋™์˜์–ด๋กœ ๋ฐ”๊พธ๊ณ  ์ ‘๋ฏธ ๋ฌธ๊ตฌ๋ฅผ ๋ฌด์ž‘์œ„๋กœ ๋ถ™์ธ๋‹ค(idx๋ฅผ ์ฃผ๋ฉด ๊ทธ ๋ฒˆํ˜ธ์˜ ๋ฌธ์žฅ ํ‹€์„ ์‚ฌ์šฉ).\"\"\"\n", " if idx is not None:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[idx].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " elif hold:\n", " s = HOLD[cls][rng.integers(len(HOLD[cls]))]\n", " else:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[rng.integers(len(tpl))].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " if rng.random() < 0.3:\n", " a, b = SWAP[rng.integers(2)]\n", " s = s.replace(a, b)\n", " return s + SUFFIX[rng.integers(len(SUFFIX))]\n", "\n", "\n", "def make_contracts(n=200, seed=7):\n", " \"\"\"๊ฐ€์ƒ ๊ณ„์•ฝ n๊ฑด(์†์„ฑ 16๊ฐœ + ์กฐํ•ญ 6๊ฐœ + ๊ทœ์ • ์œ„๋ฐ˜ ์ •๋‹ต)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " sl = rng.beta(1.2, 3.2, n) # ๊ณ„์•ฝ์„œ ์ž‘์„ฑ์ด ํ—ˆ์ˆ ํ•œ ์ •๋„(์ž ์žฌ ๋ณ€์ˆ˜)\n", " ctype = rng.choice(TYPES, n, p=[.5, .3, .2])\n", " amount = np.clip(np.exp(rng.normal(np.log(220), 1.1, n)), 5, 5000).round(0)\n", " method = np.where(rng.random(n) < .06 + .3 * sl, \"์ˆ˜์˜๊ณ„์•ฝ\", rng.choice(METHODS[:3], n, p=[.5, .3, .2]))\n", " period = np.array([rng.integers(60, 300) if t == \"๋ฌผํ’ˆ\" else rng.integers(90, 400) if t == \"์šฉ์—ญ\" else rng.integers(180, 720) for t in ctype])\n", " delivery = (period * rng.uniform(.5, 1.0, n)).astype(int)\n", " bad_logic = rng.random(n) < .015 * (1 + 3 * sl)\n", " delivery = np.where(bad_logic, period + rng.integers(10, 60, n), delivery)\n", " pen = np.where(rng.random(n) < .22 * sl, rng.choice([.3, .5, .8], n), rng.choice([1.0, 1.5, 2.0, 2.5], n))\n", " adv = np.where(rng.random(n) < .22 * sl, rng.choice([50, 60, 70], n), rng.choice([0, 10, 20, 30, 40], n))\n", " bond = np.where(rng.random(n) < .25 * sl + .5 * (adv > 40), rng.choice([0, 5], n), rng.choice([10, 10, 15, 20], n))\n", " war = np.array([rng.choice([3, 6]) if (rng.random() < .25 * s and t == \"๋ฌผํ’ˆ\") else rng.choice([12, 24, 36]) if t == \"๋ฌผํ’ˆ\" else rng.choice([0, 6, 12]) if t == \"์šฉ์—ญ\" else rng.choice([24, 36, 60]) for t, s in zip(ctype, sl)])\n", " insp = np.where(rng.random(n) < .3 * sl, rng.choice([21, 30], n), rng.choice([5, 7, 10, 14], n))\n", " sub = np.where(rng.random(n) < .15 * sl, rng.choice([60, 70, 80], n), rng.choice([0, 10, 20, 30, 40, 50], n))\n", " padj = rng.random(n) < .75 - .4 * sl\n", " term = rng.random(n) > .015 * (1 + 6 * sl)\n", " sec = rng.random(n) > .015 * (1 + 6 * sl)\n", " ip = rng.random(n) > .03 * (1 + 4 * sl)\n", " clauses, cls_true = [], []\n", " for i in range(n):\n", " pt, pd_ = .01 + .08 * sl[i], .02 + .08 * sl[i]\n", " cl = [rng.choice(CLASSES, p=[1 - pt - pd_, pt, pd_]) for _ in range(6)]\n", " cls_true.append(cl); clauses.append([make_clause(rng, c) for c in cl])\n", " df = pd.DataFrame({\"contract_id\": [f\"C-{i + 1:04d}\" for i in range(n)], \"ctype\": ctype, \"method\": method, \"amount_m\": amount,\n", " \"period_days\": period, \"delivery_days\": delivery, \"penalty_permil\": pen, \"advance_pct\": adv, \"perf_bond_pct\": bond,\n", " \"warranty_months\": war, \"inspection_days\": insp, \"subcontract_pct\": sub, \"price_adjust\": padj,\n", " \"termination\": term, \"security\": sec, \"ip_clause\": ip})\n", " hard = ((pen < 1.0) | ((adv > 40) & (bond < 10)) | ((amount >= 300) & (method == \"์ˆ˜์˜๊ณ„์•ฝ\")) | ((ctype == \"๋ฌผํ’ˆ\") & (war < 12))\n", " | ~term | ~sec | (sub > 50) | (delivery > period))\n", " toxic = np.array([c.count(\"๋…์†Œ\") for c in cls_true])\n", " delay = np.array([c.count(\"๋‚ฉ๊ธฐ์œ„ํ—˜\") for c in cls_true])\n", " split = (method == \"์ˆ˜์˜๊ณ„์•ฝ\") & (amount >= 250) & (amount < 300) & (rng.random(n) < .6) # ๊ณ„์•ฝ ์ชผ๊ฐœ๊ธฐ ์˜์‹ฌ(์ž„๊ณ„๊ฐ’ ๋ฐ”๋กœ ์•„๋ž˜)\n", " df[\"clauses\"], df[\"clause_cls\"] = clauses, cls_true\n", " df[\"n_toxic\"], df[\"n_delay\"] = toxic, delay\n", " viol = hard | (toxic > 0) | split | (delay >= 2)\n", " waived = hard & (toxic == 0) & (rng.random(n) < .12) # ๊ณ„์•ฝ๊ด€์ด ์˜ˆ์™ธ ์‚ฌ์œ ๋ฅผ ์ธ์ •ํ•ด ์œ„๋ฐ˜์œผ๋กœ ๋ณด์ง€ ์•Š์€ ๊ฑด(์ž๋ฃŒ์˜ ํ˜„์‹ค์  ์žก์Œ)\n", " hidden = ~viol & (rng.random(n) < .03 + .08 * sl) # ์†์„ฑยท๋ฌธ๊ตฌ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜(๋ณ„์ฒจ ํ•ฉ์˜ ๋“ฑ)\n", " df[\"violation\"] = (viol & ~waived) | hidden\n", " df[\"vtype\"] = np.where(hidden, \"๊ธฐํƒ€\", np.where(hard, \"์†์„ฑ์œ„๋ฐ˜\", np.where(toxic > 0, \"๋…์†Œ์กฐํ•ญ\", np.where(split | (delay >= 2), \"๊ธฐํƒ€\", \"-\"))))\n", " return df\n", "\n", "# ---- ๊ณ„์•ฝ ๊ทœ์ • ์ค€์ˆ˜ ํŒ๋ณ„ ๋ฃฐ ์„ค์ •(YAML ํ˜•์‹ ๋ฌธ์ž์—ด: ๊ทœ์ •์ด ๋ฐ”๋€Œ๋ฉด ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ด ์„ค์ •๋งŒ ๊ณ ์นœ๋‹ค) ----\n", "RULES_YAML = \"\"\"\n", "- id: R01\n", " name: ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.0/1000 ๋ฏธ๋งŒ\n", " field: penalty_permil\n", " op: \"<\"\n", " value: 1.0\n", " weight: 3\n", "- id: R02\n", " name: ์„ ๊ธˆ ๋น„์œจ 40% ์ดˆ๊ณผ\n", " field: advance_pct\n", " op: \">\"\n", " value: 40\n", " weight: 2\n", "- id: R03\n", " name: ์„ ๊ธˆ 40% ์ดˆ๊ณผ์ธ๋ฐ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: adv_no_bond\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R04\n", " name: 300๋ฐฑ๋งŒ์› ์ด์ƒ ์ˆ˜์˜๊ณ„์•ฝ\n", " field: sole_amount\n", " op: \">=\"\n", " value: 300\n", " weight: 3\n", "- id: R05\n", " name: 500๋ฐฑ๋งŒ์› ์ด์ƒ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: bond_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R06\n", " name: ๋ฌผํ’ˆ ํ•˜์ž๋ณด์ฆ 12๊ฐœ์›” ๋ฏธ๋งŒ\n", " field: warranty_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R07\n", " name: ํ•ด์ง€ ์กฐํ•ญ ์—†์Œ\n", " field: termination\n", " op: \"==\"\n", " value: false\n", " weight: 2\n", "- id: R08\n", " name: ๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ\n", " field: security\n", " op: \"==\"\n", " value: false\n", " weight: 3\n", "- id: R09\n", " name: ์ง€์‹์žฌ์‚ฐ ์กฐํ•ญ ์—†์Œ(๋ฌผํ’ˆยท์šฉ์—ญ)\n", " field: ip_missing\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R10\n", " name: ํ•˜๋„๊ธ‰ ๋น„์œจ 50% ์ดˆ๊ณผ\n", " field: subcontract_pct\n", " op: \">\"\n", " value: 50\n", " weight: 3\n", "- id: R11\n", " name: ๊ฒ€์‚ฌ ๊ธฐ๊ฐ„ 14์ผ ์ดˆ๊ณผ\n", " field: inspection_days\n", " op: \">\"\n", " value: 14\n", " weight: 1\n", "- id: R12\n", " name: ๋‚ฉํ’ˆ ๊ธฐํ•œ์ด ๊ณ„์•ฝ ๊ธฐ๊ฐ„ ์ดˆ๊ณผ\n", " field: logic_err\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R13\n", " name: 180์ผ ์ด์ƒ ๊ณ„์•ฝ์— ๋ฌผ๊ฐ€๋ณ€๋™ ์กฐํ•ญ ์—†์Œ\n", " field: no_padj_long\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R14\n", " name: 1000๋ฐฑ๋งŒ์› ์ด์ƒ ๋น„๊ฒฝ์Ÿ ๊ณ„์•ฝ\n", " field: noncomp_big\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R15\n", " name: ๊ณ„์•ฝ ๊ธฐ๊ฐ„ 365์ผ ์ดˆ๊ณผ\n", " field: period_days\n", " op: \">\"\n", " value: 365\n", " weight: 1\n", "- id: R16\n", " name: ๋ถˆ๊ณต์ •ยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ ํฌํ•จ\n", " field: kw_hits\n", " op: \">=\"\n", " value: 1\n", " weight: 3\n", "\"\"\"\n", "GRADE_CUT = {\"๊ณ ์œ„ํ—˜\": 6, \"์ฃผ์˜\": 3} # ๊ฐ€์ค‘์น˜ ํ•ฉ ๊ธฐ์ค€ 1์ฐจ ํŒ๋ณ„ ๋“ฑ๊ธ‰(ํ•„์š” ์‹œ ์„ค์ •๋งŒ ๋ฐ”๊พผ๋‹ค)\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถˆ๊ณต์ • ์กฐํ•ญยท๋‚ฉ๊ธฐ ์ง€์ฒด ์œ„ํ—˜ ๋ฌธ๊ตฌ ์‚ฌ์ „(์‹ค์ œ ๊ทœ์ • ์กฐ๋ฌธ์ด ์•„๋‹˜)\n", "KEYWORDS = {\"๋…์†Œ\": [\"๋ฉด์ œํ•˜๋ฉฐ\", \"์ผ๋ฐฉ์ ์œผ๋กœ\", \"ํฌ๊ธฐ\", \"์ง€์ง€ ์•Š๋Š”๋‹ค\", \"์ฆ‰์‹œ ์†Œ๋ฉธ\", \"์ œํ•œ ์—†์ด\", \"์‹ ๊ณ  ์—†์ด\", \"์ „์•ก ๋ถ€๋‹ด\", \"๋ณ„๋„ ํ˜‘์˜ ์—†์ด\", \"๊ฒ€์‚ฌ ์ „์—\", \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ\", \"์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค\", \"์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์ž๋™ ์—ฐ์žฅ\", \"๊ธฐํ•œ ์—†์ด\", \"์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"๋…ธ๋ ฅํ•˜๋ฉฐ\", \"์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค\", \"ํ†ต๋ณดํ•œ๋‹ค\", \"์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค\", \"์˜๋ฌด๊ฐ€ ์—†๋‹ค\"]}\n", "\n", "\n", "def parse_rules(text):\n", " \"\"\"๊ฐ„์ด YAML ํŒŒ์„œ(๋ชฉ๋ก + key: value). ์ˆซ์žยทtrue/falseยท๋”ฐ์˜ดํ‘œ ๋ฌธ์ž์—ด์„ ํ•ด์„ํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ๊ณ„์•ฝ์„œ ์กฐํ•ญ๋ณ„ ์œ„ํ—˜๋„ ํ•˜์ด๋ผ์ดํŠธ ๋ฐ ์กฐ๋‹ฌ ๊ฒ€ํ†  ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "OPS = {\"==\": lambda a, b: a == b, \"!=\": lambda a, b: a != b, \">\": lambda a, b: a > b, \">=\": lambda a, b: a >= b,\n", " \"<\": lambda a, b: a < b, \"<=\": lambda a, b: a <= b}\n", "\n", "\n", "def kw_count(clauses):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ ๋ชฉ๋ก์—์„œ ๋…์†Œยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ๊ฐ€ ๋“ค์–ด ์žˆ๋Š” ์กฐํ•ญ ์ˆ˜.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ๊ณ„์•ฝ์„œ ์กฐํ•ญ๋ณ„ ์œ„ํ—˜๋„ ํ•˜์ด๋ผ์ดํŠธ ๋ฐ ์กฐ๋‹ฌ ๊ฒ€ํ†  ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def derive(df):\n", " \"\"\"๋ฃฐ์— ์“ฐ๋Š” ํŒŒ์ƒ ํ•„๋“œ(์„ ๊ธˆ+๋ณด์ฆ ๋ถ€์กฑ, ์ˆ˜์˜๊ณ„์•ฝ ๊ณ ์•ก, ๋…ผ๋ฆฌ ์˜ค๋ฅ˜, ํ‚ค์›Œ๋“œ ์ ์ค‘ ๋“ฑ)๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " d = df.copy()\n", " d[\"adv_no_bond\"] = (d[\"advance_pct\"] > 40) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"sole_amount\"] = np.where(d[\"method\"] == \"์ˆ˜์˜๊ณ„์•ฝ\", d[\"amount_m\"], 0.0) # ์ˆ˜์˜๊ณ„์•ฝ์ผ ๋•Œ๋งŒ ๊ธˆ์•ก(ํ•œ๋„๋Š” YAML์—์„œ ์กฐ์ •)\n", " d[\"bond_gap\"] = (d[\"amount_m\"] >= 500) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"warranty_gap\"] = (d[\"ctype\"] == \"๋ฌผํ’ˆ\") & (d[\"warranty_months\"] < 12)\n", " d[\"ip_missing\"] = (~d[\"ip_clause\"]) & (d[\"ctype\"] != \"๊ณต์‚ฌ\")\n", " d[\"logic_err\"] = d[\"delivery_days\"] > d[\"period_days\"]\n", " d[\"no_padj_long\"] = (d[\"period_days\"] >= 180) & (~d[\"price_adjust\"])\n", " d[\"noncomp_big\"] = d[\"method\"].isin([\"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]) & (d[\"amount_m\"] >= 1000)\n", " d[\"kw_hits\"] = [kw_count(c) for c in d[\"clauses\"]] if \"clauses\" in d else 0\n", " return d\n", "\n", "\n", "def apply_rules(rec, rules):\n", " \"\"\"๋ ˆ์ฝ”๋“œ 1๊ฑด(dict)์— ๋ฃฐ์„ ์ ์šฉํ•ด (์ ์ˆ˜, ๋“ฑ๊ธ‰, ์ ์ค‘ ๋ฃฐ id ๋ชฉ๋ก)์„ ๋Œ๋ ค์ค€๋‹ค. ๊ฐ’์ด ์—†์œผ๋ฉด ํ•ด๋‹น ๋ฃฐ์€ ๊ฑด๋„ˆ๋›ด๋‹ค.\"\"\"\n", " hits = []\n", " for r in rules:\n", " v = rec.get(r[\"field\"])\n", " if v is None or (isinstance(v, float) and np.isnan(v)):\n", " continue\n", " if OPS[r[\"op\"]](v, r[\"value\"]):\n", " hits.append(r)\n", " score = sum(r[\"weight\"] for r in hits)\n", " grade = \"๊ณ ์œ„ํ—˜\" if score >= GRADE_CUT[\"๊ณ ์œ„ํ—˜\"] else (\"์ฃผ์˜\" if score >= GRADE_CUT[\"์ฃผ์˜\"] else \"๊ด€์‹ฌ\")\n", " return score, grade, [r[\"id\"] for r in hits]\n", "\n", "SIMPLE_KW = {\"๋…์†Œ\": [\"๋ฉด์ œ\", \"์ผ๋ฐฉ\", \"ํฌ๊ธฐ\", \"์ „์•ก\", \"๋ˆ„์„ค\", \"ํ•˜๋„๊ธ‰\", \"์„ ์ง€๊ธ‰\"], \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์—ฐ์žฅ\", \"์ž๋™\", \"๋…ธ๋ ฅ\", \"ํ†ต๋ณด\", \"๊ธฐํ•œ\"]}\n", "\n", "# ---- ์กฐํ•ญ ๋ฌธ์žฅ ๋ถ„๋ฅ˜(์ •์ƒ / ๋…์†Œ / ๋‚ฉ๊ธฐ์œ„ํ—˜): ๊ธ€์ž n-gram TF-IDF + ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€ ----\n", "def clause_set(seed, n, hold=False, mix=(.55, .25, .20)):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ n๊ฐœ์™€ ์ •๋‹ต ๋ถ„๋ฅ˜๋ฅผ ๋งŒ๋“ ๋‹ค. hold=True๋ฉด ํ•™์Šต์— ์“ฐ์ง€ ์•Š์€ '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'์œผ๋กœ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " y = rng.choice(CLASSES, n, p=list(mix))\n", " return [make_clause(rng, c, hold) for c in y], list(y)\n", "\n", "\n", "def fit_clause_model(seed=100, n=1200):\n", " \"\"\"ํ•™์Šต์šฉ ์กฐํ•ญ ๋ฌธ์žฅ์œผ๋กœ ๋ถ„๋ฅ˜ ๋ชจ๋ธ์„ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " X, y = clause_set(seed, n)\n", " return make_pipeline(TfidfVectorizer(analyzer=\"char_wb\", ngram_range=(2, 4), sublinear_tf=True), LogisticRegression(C=20, max_iter=2000)).fit(X, y)\n", "\n", "\n", "def keyword_pred(s):\n", " \"\"\"๋น„๊ต์šฉ ๊ธฐ์ค€์„ : ๋‹จ์ˆœ ํ‚ค์›Œ๋“œ(๋ถ€์ • ํ‘œํ˜„ยท๋ฌธ๋งฅ์„ ๋ณด์ง€ ์•Š์Œ) ์ ์ค‘์œผ๋กœ๋งŒ ๋ถ„๋ฅ˜.\"\"\"\n", " if any(k in s for k in SIMPLE_KW[\"๋…์†Œ\"]):\n", " return \"๋…์†Œ\"\n", " return \"๋‚ฉ๊ธฐ์œ„ํ—˜\" if any(k in s for k in SIMPLE_KW[\"๋‚ฉ๊ธฐ์œ„ํ—˜\"]) else \"์ •์ƒ\"\n", "\n", "NUM = [\"amount_m\", \"period_days\", \"delivery_days\", \"penalty_permil\", \"advance_pct\", \"perf_bond_pct\", \"warranty_months\", \"inspection_days\", \"subcontract_pct\"]\n", "BOOL = [\"price_adjust\", \"termination\", \"security\", \"ip_clause\"]\n", "NEED = [\"contract_id\", \"ctype\", \"method\"] + NUM + BOOL + [\"clauses\"]\n", "RANGE = {\"amount_m\": (1, 100000), \"period_days\": (1, 1500), \"delivery_days\": (1, 1500), \"penalty_permil\": (0, 10), \"advance_pct\": (0, 100), \"perf_bond_pct\": (0, 100),\n", " \"warranty_months\": (0, 120), \"inspection_days\": (1, 90), \"subcontract_pct\": (0, 100)}\n", "LOG, COMMITTED = [], {}\n", "RULES = parse_rules(RULES_YAML)\n", "CM = fit_clause_model()\n", "MAX_CLAUSE = 600\n", "\n", "\n", "class UserError(Exception):\n", " \"\"\"์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ ์ค„ ๋ฉ”์‹œ์ง€์™€ ์กฐ์น˜ ๊ฐ€์ด๋“œ๋ฅผ ํ•จ๊ป˜ ๊ฐ€์ง„ ์˜ˆ์™ธ.\"\"\"\n", "\n", " def __init__(self, code, msg, action):\n", " super().__init__(msg); self.code, self.msg, self.action = code, msg, action\n", "\n", "\n", "class Transient(Exception):\n", " \"\"\"์ผ์‹œ ์˜ค๋ฅ˜(์žฌ์‹œ๋„ ๋Œ€์ƒ).\"\"\"\n", "\n", "\n", "def log(level, code, detail):\n", " LOG.append({\"seq\": len(LOG) + 1, \"level\": level, \"code\": code, \"detail\": str(detail)[:70]})\n", "\n", "\n", "def read_input(src):\n", " \"\"\"์ž…๋ ฅ(CSV ๋ฌธ์ž์—ด / JSON ๋ฌธ์ž์—ด / DataFrame)์„ DataFrame์œผ๋กœ ์ฝ๋Š”๋‹ค. ์‹คํŒจํ•˜๋ฉด UserError.\"\"\"\n", " if src is None:\n", " raise UserError(\"E01\", \"์ž…๋ ฅ์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.\", \"ํŒŒ์ผ์„ ๋‹ค์‹œ ์„ ํƒํ•ด ์ฃผ์„ธ์š”.\")\n", " if isinstance(src, pd.DataFrame):\n", " df = src.copy()\n", " elif isinstance(src, str):\n", " if not src.strip():\n", " raise UserError(\"E02\", \"๋นˆ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.\", \"๋‚ด์šฉ์ด ์žˆ๋Š” ํŒŒ์ผ์ธ์ง€ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.\")\n", " try:\n", " df = pd.DataFrame(json.loads(src)) if src.lstrip()[0] in \"[{\" else pd.DataFrame(list(csv.DictReader(io.StringIO(src))))\n", " except Exception as e:\n", " raise UserError(\"E03\", \"ํŒŒ์ผ ํ˜•์‹์„ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.\", \"CSV(์‰ผํ‘œ ๊ตฌ๋ถ„) ๋˜๋Š” JSON(๋ชฉ๋ก) ํ˜•์‹์œผ๋กœ ์ €์žฅํ–ˆ๋Š”์ง€ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.\") from e\n", " else:\n", " raise UserError(\"E04\", f\"์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ž…๋ ฅ ํ˜•์‹์ž…๋‹ˆ๋‹ค({type(src).__name__}).\", \"CSV ๋˜๋Š” JSON ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ด ์ฃผ์„ธ์š”.\")\n", " if df.empty:\n", " raise UserError(\"E02\", \"์ฝ์€ ํ–‰์ด 0๊ฑด์ž…๋‹ˆ๋‹ค.\", \"๋จธ๋ฆฌ๊ธ€๋งŒ ์žˆ๋Š” ํŒŒ์ผ์ด ์•„๋‹Œ์ง€ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.\")\n", " return df\n", "\n", "\n", "def clean_text(s):\n", " \"\"\"ํŠน์ˆ˜๋ฌธ์žยท์ œ์–ด๋ฌธ์ž ์ œ๊ฑฐ, ์ง€๋‚˜์น˜๊ฒŒ ๊ธด ์กฐํ•ญ์€ ์ž˜๋ผ ๋‚ด๊ธฐ.\"\"\"\n", " s = re.sub(r\"[\\x00-\\x08\\x0b-\\x1f\\x7f<>]\", \" \", str(s))\n", " return re.sub(r\"\\s+\", \" \", s).strip()[:MAX_CLAUSE]\n", "\n", "\n", "def validate_table(df):\n", " \"\"\"์—ด ์ ๊ฒ€ -> ํ–‰ ๋‹จ์œ„ ์ ๊ฒ€. ๋ฌธ์ œ ํ–‰์€ ๊ฒฉ๋ฆฌํ•˜๊ณ  ๋‚˜๋จธ์ง€๋กœ ๊ณ„์† ์ง„ํ–‰ํ•œ๋‹ค. ๋ฐ˜ํ™˜: (์ •์ƒ ํ–‰, ๊ฒฉ๋ฆฌ ๋‚ด์—ญ)\"\"\"\n", " miss = [c for c in NEED if c not in df.columns]\n", " if miss:\n", " raise UserError(\"E10\", f\"ํ•„์ˆ˜ ์—ด์ด ์—†์Šต๋‹ˆ๋‹ค: {', '.join(miss[:4])}{' ์™ธ' if len(miss) > 4 else ''}\", \"์—ด ์ด๋ฆ„์ด ์–‘์‹(๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ)๊ณผ ๊ฐ™์€์ง€ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.\")\n", " d, bad = df.copy().reset_index(drop=True), {}\n", " for c in NUM:\n", " conv = pd.to_numeric(d[c], errors=\"coerce\")\n", " for i in d.index[conv.isna()]:\n", " bad.setdefault(i, f\"{c}: ์ˆซ์ž๊ฐ€ ์•„๋‹˜/๋ˆ„๋ฝ\")\n", " d[c] = conv\n", " for c, (lo, hi) in RANGE.items():\n", " for i in d.index[(d[c] < lo) | (d[c] > hi)]:\n", " bad.setdefault(i, f\"{c}: ํ—ˆ์šฉ ๋ฒ”์œ„({lo}~{hi}) ๋ฐ–\")\n", " for c, ok in ((\"ctype\", TYPES), (\"method\", METHODS)):\n", " for i in d.index[~d[c].isin(ok)]:\n", " bad.setdefault(i, f\"{c}: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๊ฐ’\")\n", " for i in d.index[d[\"contract_id\"].duplicated(keep=\"first\")]:\n", " bad.setdefault(i, \"contract_id: ์ค‘๋ณต\")\n", " for i in d.index[~d[\"contract_id\"].astype(str).str.match(r\"^C-\\d{4}$\")]:\n", " bad.setdefault(i, \"contract_id: ํ˜•์‹ ์˜ค๋ฅ˜\")\n", " for c in BOOL:\n", " d[c] = d[c].map(lambda v: v if isinstance(v, (bool, np.bool_)) else str(v).strip().lower() in (\"true\", \"1\", \"์˜ˆ\", \"y\"))\n", " d[\"clauses\"] = d[\"clauses\"].map(lambda v: [clean_text(x) for x in (v if isinstance(v, list) else str(v).split(\" || \")) if str(x).strip()])\n", " for i in d.index[d[\"clauses\"].map(len) == 0]:\n", " bad.setdefault(i, \"clauses: ์กฐํ•ญ ์—†์Œ\")\n", " if len(bad) == len(d):\n", " raise UserError(\"E11\", \"์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ–‰์ด ์—†์Šต๋‹ˆ๋‹ค.\", \"๊ฒฉ๋ฆฌ ์‚ฌ์œ ๋ฅผ ํ™•์ธํ•ด ๊ฐ’์„ ๊ณ ์นœ ๋’ค ๋‹ค์‹œ ์˜ฌ๋ ค ์ฃผ์„ธ์š”.\")\n", " return d.drop(index=list(bad)).reset_index(drop=True), bad\n", "\n", "\n", "MARK = {\"๋…์†Œ\": \"[!!]\", \"๋‚ฉ๊ธฐ์œ„ํ—˜\": \"[! ]\", \"์ •์ƒ\": \"[ok]\"}\n", "\n", "\n", "def highlight(clauses):\n", " \"\"\"์กฐํ•ญ๋ณ„ ์œ„ํ—˜ ํ‘œ์‹œ: ๋ชจ๋ธ ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ์™€ ํ‚ค์›Œ๋“œ ์œ„์น˜๋ฅผ ใ€ ใ€‘๋กœ ๊ฐ•์กฐํ•œ๋‹ค.\"\"\"\n", " cls = CM.predict(clauses)\n", " out = []\n", " for c, k in zip(clauses, cls):\n", " for kw in KEYWORDS[\"๋…์†Œ\"] + KEYWORDS[\"๋‚ฉ๊ธฐ์œ„ํ—˜\"]:\n", " if k != \"์ •์ƒ\" and kw in c:\n", " c = c.replace(kw, f\"ใ€{kw}ใ€‘\")\n", " out.append((k, c))\n", " return out\n", "\n", "\n", "def build_dashboard(df, fail_times=0, state=None):\n", " \"\"\"๋Œ€์‹œ๋ณด๋“œ ์‚ฐ์ถœ๋ฌผ์„ ํ•œ ๋ฒˆ์— ๋งŒ๋“ ๋‹ค(์ „๋ถ€ ์„ฑ๊ณตํ•ด์•ผ ๋ฐ˜์˜ = ํŠธ๋žœ์žญ์…˜). ๋ฐ˜ํ™˜: ๊ฒฐ๊ณผ dict, ๊ฒฉ๋ฆฌ ๋‚ด์—ญ\"\"\"\n", " d, bad = validate_table(read_input(df))\n", " if state is not None and state[\"calls\"] < fail_times:\n", " state[\"calls\"] += 1\n", " raise Transient(\"์™ธ๋ถ€ ์ €์žฅ์†Œ ์‘๋‹ต ์ง€์—ฐ(๋ชจ์˜)\")\n", " dd = derive(d)\n", " dd[\"score\"] = [apply_rules(r, RULES)[0] for r in dd.to_dict(\"records\")]\n", " heat = dd.pivot_table(index=\"method\", columns=\"ctype\", values=\"score\", aggfunc=\"mean\").round(1)\n", " top = dd.sort_values([\"kw_hits\", \"score\"], ascending=False).head(3) # ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ๋งŽ์€ ๊ณ„์•ฝ๋ถ€ํ„ฐ\n", " hl = {r[\"contract_id\"]: highlight(r[\"clauses\"]) for _, r in top.iterrows()}\n", " return {\"heat\": heat, \"top\": top[[\"contract_id\", \"ctype\", \"method\", \"amount_m\", \"score\"]], \"hl\": hl, \"n\": len(dd)}, bad\n", "\n", "\n", "def safe_dashboard(case, src, fail_times=0, always_fail=False):\n", " \"\"\"๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ์žก์•„ ํฌ๋ž˜์‹œ ์—†์ด ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ยท์กฐ์น˜ ์•ˆ๋‚ด๋ฅผ ๋Œ๋ ค์ฃผ๊ณ  ์—๋Ÿฌ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธด๋‹ค. ์ผ์‹œ ์˜ค๋ฅ˜๋Š” 3ํšŒ๊นŒ์ง€ ์žฌ์‹œ๋„ํ•˜๊ณ , ๋๋‚ด ์‹คํŒจํ•˜๋ฉด ์ง์ „ ์ •์ƒ ๊ฒฐ๊ณผ๋กœ ๋กค๋ฐฑํ•œ๋‹ค.\"\"\"\n", " state = {\"calls\": 0}\n", " for attempt in range(1, 4):\n", " try:\n", " if always_fail:\n", " raise Transient(\"์ €์žฅ์†Œ ์—ฐ๊ฒฐ ๋ถˆ๊ฐ€(๋ชจ์˜)\")\n", " res, bad = build_dashboard(src, fail_times, state)\n", " for i, why in bad.items():\n", " log(\"WARN\", \"W01\", f\"{case}: ํ–‰ {i} ๊ฒฉ๋ฆฌ - {why}\")\n", " COMMITTED[\"last\"] = res # ์ „๋ถ€ ์„ฑ๊ณตํ–ˆ์„ ๋•Œ๋งŒ ๋ฐ˜์˜\n", " return {\"case\": case, \"ok\": True, \"msg\": f\"ํ‘œ์‹œ ์™„๋ฃŒ(๊ฒฉ๋ฆฌ {len(bad)}ํ–‰)\" if bad else \"ํ‘œ์‹œ ์™„๋ฃŒ\" + (f\", ์žฌ์‹œ๋„ {attempt - 1}ํšŒ ํ›„ ์„ฑ๊ณต\" if attempt > 1 else \"\"), \"action\": \"-\"}\n", " except UserError as e:\n", " log(\"ERROR\", e.code, f\"{case}: {e.msg}\")\n", " return {\"case\": case, \"ok\": False, \"msg\": e.msg, \"action\": e.action}\n", " except Transient as e:\n", " log(\"WARN\", \"W02\", f\"{case}: ์ผ์‹œ ์˜ค๋ฅ˜ ์‹œ๋„ {attempt}/3 - {e}\")\n", " time.sleep(0.01)\n", " except Exception as e: # ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ์˜ค๋ฅ˜๋„ ํฌ๋ž˜์‹œ ๋Œ€์‹  ์•ˆ๋‚ด๋กœ ๋ณ€ํ™˜\n", " log(\"ERROR\", \"E99\", f\"{case}: {type(e).__name__} {e}\")\n", " return {\"case\": case, \"ok\": False, \"msg\": \"์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.\", \"action\": \"์˜ค๋ฅ˜ ์ฝ”๋“œ E99์™€ ์ž…๋ ฅ ํŒŒ์ผ์„ ๋‹ด๋‹น์ž์—๊ฒŒ ์ „๋‹ฌํ•ด ์ฃผ์„ธ์š”.\"}\n", " log(\"ERROR\", \"E50\", f\"{case}: ์žฌ์‹œ๋„ 3ํšŒ ์‹คํŒจ -> ์ง์ „ ์ •์ƒ ๊ฒฐ๊ณผ๋กœ ๋กค๋ฐฑ\")\n", " return {\"case\": case, \"ok\": False, \"msg\": \"์ผ์‹œ์ ์ธ ๋ฌธ์ œ๋กœ ์ €์žฅํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ์ง์ „ ์ •์ƒ ํ™”๋ฉด์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.\", \"action\": \"์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด ์ฃผ์„ธ์š”.\"}\n", "\n", "\n", "con = make_contracts(100, seed=7)\n", "base = con.assign(clauses=con[\"clauses\"].map(lambda c: \" || \".join(c))) # CSV์ฒ˜๋Ÿผ ์กฐํ•ญ์„ ํ•œ ์นธ์— ์ด์–ด ๋ถ™์ธ ํ˜•ํƒœ\n", "csv_ok = base.drop(columns=[\"clause_cls\", \"n_toxic\", \"n_delay\", \"violation\", \"vtype\"]).to_csv(index=False)\n", "\n", "\n", "def mut(**kw):\n", " d = base.copy()\n", " for k, v in kw.items():\n", " d[k] = d[k].astype(object); d.loc[d.index[:3], k] = v\n", " return d\n", "\n", "\n", "big = pd.concat([base] * 200, ignore_index=True)\n", "big[\"contract_id\"] = [f\"C-{i % 10000:04d}\" for i in range(len(big))] # ๋Œ€์šฉ๋Ÿ‰(20,000ํ–‰, ๋ฒˆํ˜ธ ์ค‘๋ณต์€ ๊ฒฉ๋ฆฌ ๋Œ€์ƒ)\n", "cases = [\n", " (\"์ •์ƒ ์ž…๋ ฅ\", base, {}), (\"์ •์ƒ CSV ๋ฌธ์ž์—ด\", csv_ok, {}), (\"์ž…๋ ฅ ์—†์Œ(None)\", None, {}), (\"๋นˆ ๋ฌธ์ž์—ด\", \" \", {}), (\"๊นจ์ง„ JSON\", '[{\"contract_id\": \"C-0001\", ', {}),\n", " (\"์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ˜•์‹(์ •์ˆ˜)\", 12345, {}), (\"๋จธ๋ฆฌ๊ธ€๋งŒ ์žˆ๋Š” CSV\", \",\".join(base.columns) + \"\\n\", {}), (\"ํ•„์ˆ˜ ์—ด ๋ˆ„๋ฝ\", base.drop(columns=[\"penalty_permil\", \"clauses\"]), {}),\n", " (\"์ˆซ์ž ์—ด์— ๋ฌธ์ž\", mut(amount_m=\"๋ฏธ์ •\"), {}), (\"์Œ์ˆ˜ ๊ธˆ์•ก\", mut(amount_m=-5), {}), (\"๊ณ„์•ฝ ๋ฐฉ์‹ ๊ฒฐ์ธก\", mut(method=np.nan), {}), (\"ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ์œ ํ˜•\", mut(ctype=\"๊ธฐํƒ€\"), {}),\n", " (\"๊ณ„์•ฝ๋ฒˆํ˜ธ ์ค‘๋ณตยทํ˜•์‹ ์˜ค๋ฅ˜\", mut(contract_id=\"C-9\"), {}), (\"์กฐํ•ญ ์นธ์ด ๋น„์–ด ์žˆ์Œ\", mut(clauses=\"\"), {}),\n", " (\"ํŠน์ˆ˜๋ฌธ์žยท์ œ์–ด๋ฌธ์žยทํƒœ๊ทธ\", mut(clauses=\" \\x00\\x07 ์ง€์ฒด์ƒ๊ธˆ์€ ์ „์•ก ๋ฉด์ œํ•˜๋ฉฐ\"), {}),\n", " (\"์กฐํ•ญ ๋ฌธ์žฅ 1MB\", mut(clauses=\"๊ฐ€\" * 1_000_000), {}), (\"๊ทน๋‹จ๊ฐ’(1e12)\", mut(amount_m=1e12), {}),\n", " (\"๋Œ€์šฉ๋Ÿ‰ 20,000ํ–‰\", big, {}), (\"์ผ์‹œ ์˜ค๋ฅ˜ 2ํšŒ ํ›„ ์„ฑ๊ณต\", base, {\"fail_times\": 2}), (\"๋๋‚ด ์‹คํŒจ(๋กค๋ฐฑ)\", base, {\"always_fail\": True}),\n", "]\n", "res, crash, times = [], 0, {}\n", "for name, src, kw in cases:\n", " t0 = time.perf_counter()\n", " try:\n", " res.append(safe_dashboard(name, src, **kw))\n", " except BaseException: # safe_dashboard ๋ฐ–์œผ๋กœ ์˜ˆ์™ธ๊ฐ€ ์ƒˆ ๋‚˜๊ฐ„ ๊ฑด์ˆ˜\n", " crash += 1\n", " times[name] = time.perf_counter() - t0\n", "print(f\"[์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ ์‹œํ—˜] ์ž…๋ ฅ {len(cases)}์ข… ์‹คํ–‰\")\n", "for r in res:\n", " print(f\" {'OK ' if r['ok'] else '์•ˆ๋‚ด'} {r['case']:22s} -> {r['msg'][:46]}\" + (f\" | ์กฐ์น˜: {r['action'][:34]}\" if r[\"action\"] != \"-\" else \"\"))\n", "n_err = sum(not r[\"ok\"] for r in res)\n", "logged_err = sum(l[\"level\"] == \"ERROR\" for l in LOG)\n", "print(f\"\\n์˜ˆ์™ธ ์ฃผ์ž… {len(cases)}์ข… ์‹คํ–‰ ์™„๋ฃŒ(๋ชฉํ‘œ 10์ข… ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(cases) >= 10 else '๋ฏธ๋‹ฌ'}), ํฌ๋ž˜์‹œ {crash}๊ฑด(๋ชฉํ‘œ 0๊ฑด: {'๋‹ฌ์„ฑ' if crash == 0 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\"์˜ค๋ฅ˜ {n_err}๊ฑด ์ค‘ ERROR ๋กœ๊ทธ {logged_err}๊ฑด -> ๊ธฐ๋ก๋ฅ  {min(logged_err / max(n_err, 1), 1):.0%}(๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if logged_err == n_err else '๋ฏธ๋‹ฌ'}), ์•ˆ๋‚ด ๋ฌธ๊ตฌ์— ์กฐ์น˜ ๊ฐ€์ด๋“œ ํฌํ•จ {sum(r['action'] != '-' for r in res if not r['ok'])}/{n_err}๊ฑด\")\n", "print(f\"ํ–‰ ๊ฒฉ๋ฆฌ ๊ฒฝ๊ณ  {sum(l['code'] == 'W01' for l in LOG)}๊ฑด, ์žฌ์‹œ๋„ ๊ฒฝ๊ณ  {sum(l['code'] == 'W02' for l in LOG)}๊ฑด ๊ธฐ๋ก, ๋Œ€์šฉ๋Ÿ‰ 20,000ํ–‰ ์ฒ˜๋ฆฌ {times['๋Œ€์šฉ๋Ÿ‰ 20,000ํ–‰']:.1f}์ดˆ\")\n", "print(\"์ง์ „ ์ •์ƒ ๊ฒฐ๊ณผ ์œ ์ง€(๋กค๋ฐฑ): ๋งˆ์ง€๋ง‰ ๋ฐ˜์˜๋œ ํ™”๋ฉด์˜ ๊ณ„์•ฝ ์ˆ˜ =\", COMMITTED[\"last\"][\"n\"], \"๊ฑด\")\n", "print(\"\\n[์—๋Ÿฌ ๋กœ๊ทธ ์•ž 5๊ฑด]\")\n", "print(pd.DataFrame(LOG).head(5).to_string(index=False))\n", "\n", "# ์ •์ƒ ์ž…๋ ฅ์œผ๋กœ ๋Œ€์‹œ๋ณด๋“œ(์กฐํ•ญ ํ•˜์ด๋ผ์ดํŠธ + ์œ„ํ—˜ ์ง€๋„) ๋ณด์—ฌ ์ฃผ๊ธฐ\n", "out, bad = build_dashboard(base)\n", "print(\"\\n[์œ„ํ—˜ ์ง€๋„] ๊ณ„์•ฝ ๋ฐฉ์‹ x ์œ ํ˜• ํ‰๊ท  ๋ฃฐ ์ ์ˆ˜\")\n", "print(out[\"heat\"].fillna(0).to_string())\n", "print(\"\\n[์กฐํ•ญ๋ณ„ ์œ„ํ—˜ ํ•˜์ด๋ผ์ดํŠธ] ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ๋งŽ์€ ์ƒ์œ„ 3๊ฑด (ใ€ ใ€‘= ์œ„ํ—˜ ํ‘œํ˜„, [!!]=๋…์†Œ, [! ]=๋‚ฉ๊ธฐ์œ„ํ—˜, [ok]=์ •์ƒ)\")\n", "for _, r in out[\"top\"].iterrows():\n", " print(f\" {r['contract_id']} {r['ctype']} {r['method']} {r['amount_m']:.0f}๋ฐฑ๋งŒ์› ๋ฃฐ ์ ์ˆ˜ {r['score']}\")\n", " for k, c in out[\"hl\"][r[\"contract_id\"]]:\n", " print(f\" {MARK[k]} {c[:52]}\")\n", "print(\"โ€ป ํ•˜์ด๋ผ์ดํŠธ๋Š” ๊ฒ€ํ†  ์šฐ์„ ์ˆœ์œ„ ํ‘œ์‹œ์ด๋ฉฐ ๋ถˆ๊ณต์ • ์—ฌ๋ถ€์˜ ์ตœ์ข… ํŒ๋‹จ์€ ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด ๋‹ด๋‹น์ž๊ฐ€ ํ•œ๋‹ค.\")\n", "\n", "heat = out[\"heat\"].fillna(0)\n", "fig, ax = plt.subplots(1, 3, figsize=(13, 4))\n", "im = ax[0].imshow(heat.to_numpy(), cmap=\"Reds\"); ax[0].set_xticks(range(heat.shape[1]), [\"goods\", \"service\", \"works\"][:heat.shape[1]]); ax[0].set_yticks(range(heat.shape[0]), [f\"m{i + 1}\" for i in range(heat.shape[0])])\n", "ax[0].set_title(\"Risk map (method x type)\"); plt.colorbar(im, ax=ax[0])\n", "ax[1].bar([\"ok\", \"guided\", \"crash\"], [sum(r[\"ok\"] for r in res), n_err, crash], color=[\"tab:green\", \"gold\", \"tab:red\"]); ax[1].set_title(\"Error injection results (20 cases)\")\n", "cnt = pd.Series([k for _, cl in out[\"hl\"].items() for k, _ in cl]).value_counts()\n", "ax[2].bar([{\"๋…์†Œ\": \"toxic\", \"๋‚ฉ๊ธฐ์œ„ํ—˜\": \"delay\", \"์ •์ƒ\": \"ok\"}[k] for k in cnt.index], cnt.values, color=\"tab:blue\"); ax[2].set_title(\"Clause labels in top-3 contracts\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 06 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ ์‹œํ—˜] ์ž…๋ ฅ 20์ข… ์‹คํ–‰\n", " OK ์ •์ƒ ์ž…๋ ฅ -> ํ‘œ์‹œ ์™„๋ฃŒ\n", " OK ์ •์ƒ CSV ๋ฌธ์ž์—ด -> ํ‘œ์‹œ ์™„๋ฃŒ\n", " ์•ˆ๋‚ด ์ž…๋ ฅ ์—†์Œ(None) -> ์ž…๋ ฅ์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค. | ์กฐ์น˜: ํŒŒ์ผ์„ ๋‹ค์‹œ ์„ ํƒํ•ด ์ฃผ์„ธ์š”.\n", " ์•ˆ๋‚ด ๋นˆ ๋ฌธ์ž์—ด -> ๋นˆ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค. | ์กฐ์น˜: ๋‚ด์šฉ์ด ์žˆ๋Š” ํŒŒ์ผ์ธ์ง€ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.\n", " ์•ˆ๋‚ด ๊นจ์ง„ JSON -> ํŒŒ์ผ ํ˜•์‹์„ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. | ์กฐ์น˜: CSV(์‰ผํ‘œ ๊ตฌ๋ถ„) ๋˜๋Š” JSON(๋ชฉ๋ก) ํ˜•์‹์œผ๋กœ ์ €์žฅํ–ˆ๋Š”์ง€ \n", " ์•ˆ๋‚ด ์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ˜•์‹(์ •์ˆ˜) -> ์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ž…๋ ฅ ํ˜•์‹์ž…๋‹ˆ๋‹ค(int). | ์กฐ์น˜: CSV ๋˜๋Š” JSON ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ด ์ฃผ์„ธ์š”.\n", " ์•ˆ๋‚ด ๋จธ๋ฆฌ๊ธ€๋งŒ ์žˆ๋Š” CSV -> ์ฝ์€ ํ–‰์ด 0๊ฑด์ž…๋‹ˆ๋‹ค. | ์กฐ์น˜: ๋จธ๋ฆฌ๊ธ€๋งŒ ์žˆ๋Š” ํŒŒ์ผ์ด ์•„๋‹Œ์ง€ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.\n", " ์•ˆ๋‚ด ํ•„์ˆ˜ ์—ด ๋ˆ„๋ฝ -> ํ•„์ˆ˜ ์—ด์ด ์—†์Šต๋‹ˆ๋‹ค: penalty_permil, clauses | ์กฐ์น˜: ์—ด ์ด๋ฆ„์ด ์–‘์‹(๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ)๊ณผ ๊ฐ™์€์ง€ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.\n", " OK ์ˆซ์ž ์—ด์— ๋ฌธ์ž -> ํ‘œ์‹œ ์™„๋ฃŒ(๊ฒฉ๋ฆฌ 3ํ–‰)\n", " OK ์Œ์ˆ˜ ๊ธˆ์•ก -> ํ‘œ์‹œ ์™„๋ฃŒ(๊ฒฉ๋ฆฌ 3ํ–‰)\n", " OK ๊ณ„์•ฝ ๋ฐฉ์‹ ๊ฒฐ์ธก -> ํ‘œ์‹œ ์™„๋ฃŒ(๊ฒฉ๋ฆฌ 3ํ–‰)\n", " OK ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ์œ ํ˜• -> ํ‘œ์‹œ ์™„๋ฃŒ(๊ฒฉ๋ฆฌ 3ํ–‰)\n", " OK ๊ณ„์•ฝ๋ฒˆํ˜ธ ์ค‘๋ณตยทํ˜•์‹ ์˜ค๋ฅ˜ -> ํ‘œ์‹œ ์™„๋ฃŒ(๊ฒฉ๋ฆฌ 3ํ–‰)\n", " OK ์กฐํ•ญ ์นธ์ด ๋น„์–ด ์žˆ์Œ -> ํ‘œ์‹œ ์™„๋ฃŒ(๊ฒฉ๋ฆฌ 3ํ–‰)\n", " OK ํŠน์ˆ˜๋ฌธ์žยท์ œ์–ด๋ฌธ์žยทํƒœ๊ทธ -> ํ‘œ์‹œ ์™„๋ฃŒ\n", " OK ์กฐํ•ญ ๋ฌธ์žฅ 1MB -> ํ‘œ์‹œ ์™„๋ฃŒ\n", " OK ๊ทน๋‹จ๊ฐ’(1e12) -> ํ‘œ์‹œ ์™„๋ฃŒ(๊ฒฉ๋ฆฌ 3ํ–‰)\n", " OK ๋Œ€์šฉ๋Ÿ‰ 20,000ํ–‰ -> ํ‘œ์‹œ ์™„๋ฃŒ(๊ฒฉ๋ฆฌ 10000ํ–‰)\n", " OK ์ผ์‹œ ์˜ค๋ฅ˜ 2ํšŒ ํ›„ ์„ฑ๊ณต -> ํ‘œ์‹œ ์™„๋ฃŒ, ์žฌ์‹œ๋„ 2ํšŒ ํ›„ ์„ฑ๊ณต\n", " ์•ˆ๋‚ด ๋๋‚ด ์‹คํŒจ(๋กค๋ฐฑ) -> ์ผ์‹œ์ ์ธ ๋ฌธ์ œ๋กœ ์ €์žฅํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ์ง์ „ ์ •์ƒ ํ™”๋ฉด์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. | ์กฐ์น˜: ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด ์ฃผ์„ธ์š”.\n", "\n", "์˜ˆ์™ธ ์ฃผ์ž… 20์ข… ์‹คํ–‰ ์™„๋ฃŒ(๋ชฉํ‘œ 10์ข… ์ด์ƒ: ๋‹ฌ์„ฑ), ํฌ๋ž˜์‹œ 0๊ฑด(๋ชฉํ‘œ 0๊ฑด: ๋‹ฌ์„ฑ)\n", "์˜ค๋ฅ˜ 7๊ฑด ์ค‘ ERROR ๋กœ๊ทธ 7๊ฑด -> ๊ธฐ๋ก๋ฅ  100%(๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ), ์•ˆ๋‚ด ๋ฌธ๊ตฌ์— ์กฐ์น˜ ๊ฐ€์ด๋“œ ํฌํ•จ 7/7๊ฑด\n", "ํ–‰ ๊ฒฉ๋ฆฌ ๊ฒฝ๊ณ  10021๊ฑด, ์žฌ์‹œ๋„ ๊ฒฝ๊ณ  5๊ฑด ๊ธฐ๋ก, ๋Œ€์šฉ๋Ÿ‰ 20,000ํ–‰ ์ฒ˜๋ฆฌ 2.6์ดˆ\n", "์ง์ „ ์ •์ƒ ๊ฒฐ๊ณผ ์œ ์ง€(๋กค๋ฐฑ): ๋งˆ์ง€๋ง‰ ๋ฐ˜์˜๋œ ํ™”๋ฉด์˜ ๊ณ„์•ฝ ์ˆ˜ = 100 ๊ฑด\n", "\n", "[์—๋Ÿฌ ๋กœ๊ทธ ์•ž 5๊ฑด]\n", " seq level code detail\n", " 1 ERROR E01 ์ž…๋ ฅ ์—†์Œ(None): ์ž…๋ ฅ์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.\n", " 2 ERROR E02 ๋นˆ ๋ฌธ์ž์—ด: ๋นˆ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.\n", " 3 ERROR E03 ๊นจ์ง„ JSON: ํŒŒ์ผ ํ˜•์‹์„ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.\n", " 4 ERROR E04 ์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ˜•์‹(์ •์ˆ˜): ์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ž…๋ ฅ ํ˜•์‹์ž…๋‹ˆ๋‹ค(int).\n", " 5 ERROR E02 ๋จธ๋ฆฌ๊ธ€๋งŒ ์žˆ๋Š” CSV: ์ฝ์€ ํ–‰์ด 0๊ฑด์ž…๋‹ˆ๋‹ค.\n", "\n", "[์œ„ํ—˜ ์ง€๋„] ๊ณ„์•ฝ ๋ฐฉ์‹ x ์œ ํ˜• ํ‰๊ท  ๋ฃฐ ์ ์ˆ˜\n", "ctype ๊ณต์‚ฌ ๋ฌผํ’ˆ ์šฉ์—ญ\n", "method \n", "์ˆ˜์˜๊ณ„์•ฝ 1.0 2.5 2.3\n", "์ผ๋ฐ˜๊ฒฝ์Ÿ 2.7 1.8 1.1\n", "... (์ดํ•˜ ์ƒ๋žต)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 07. ๊ณ„์•ฝ ์Šน์ธ ์ „ ๋ฒ•๋ฌด/๊ณ„์•ฝ๊ด€ ์ˆ˜๋™ ๊ฒ€ํ†  ๋ฐ ์กฐํ•ญ ์ˆ˜์ • ๊ถŒ๊ณ  HITL ์ ˆ์ฐจ ์„ค๊ณ„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๊ณ„์•ฝ ์Šน์ธ ์ „ ๊ณ„์•ฝ๊ด€ ์ˆ˜๋™ ๊ฒ€ํ†  ๋ฐ ์กฐํ•ญ ์ˆ˜์ • ๊ถŒ๊ณ  HITL ์ ˆ์ฐจ๋ฅผ ์„ค๊ณ„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๊ณ„์•ฝ ์Šน์ธ ์ „ ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด ์ˆ˜๋™ ๊ฒ€ํ† ์™€ ์กฐํ•ญ ์ˆ˜์ • ๊ถŒ๊ณ ๋ฅผ ์œ„ํ•œ HITL ์ ˆ์ฐจ๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ์ด๋ ฅ ๋ฌด๊ฒฐ์„ฑ๊ณผ ๊ถŒํ•œ ํ†ต์ œ๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์‹œ์Šคํ…œ ์ œ์•ˆ, ๊ณ„์•ฝ๋‹ด๋‹น ์ƒ์‹ , ๊ณ„์•ฝ๊ด€ ๊ฒ€ํ† , ๋ฒ•๋ฌด ๊ฒ€ํ† , ์Šน์ธ๊ถŒ์ž ๊ฒฐ์žฌ์˜ ์ˆœ์„œ๋ฅผ ์ƒํƒœ ๊ธฐ๊ณ„๋กœ ๊ตฌํ˜„ํ•˜๊ณ  ์—ญํ• ๋ณ„ ๊ถŒํ•œ์„ ์ตœ์†Œ๋กœ ๋‘”๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/workflow/prc01_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": [ "\"\"\"๊ณ ์œ„ํ—˜ ๊ณ„์•ฝ์˜ ์กฐํ•ญ ์ˆ˜์ • ๊ถŒ๊ณ ๋ฅผ ๊ณ„์•ฝ๋‹ด๋‹น ์ƒ์‹  -> ๊ณ„์•ฝ๊ด€ ๊ฒ€ํ†  -> ๋ฒ•๋ฌด ๊ฒ€ํ†  -> ์Šน์ธ๊ถŒ์ž ๊ฒฐ์žฌ์˜ HITL ์›Œํฌํ”Œ๋กœ์šฐ๋กœ ์ฒ˜๋ฆฌํ•˜๋˜, ๋ฐ˜๋ ค ์‚ฌ์œ  ํ•„์ˆ˜ํ™”ยท์žฌ๊ธฐ์•ˆ ์—ฐ๊ณ„ยท์‹œ์Šคํ…œ ์ž๋™ ํ™•์ • ๊ธˆ์ง€ยท๊ถŒํ•œ ์šฐํšŒ ์ฐจ๋‹จ(RBAC)๊ณผ ํ•ด์‹œ ์ฒด์ธ ๊ฐ์‚ฌ ๋กœ๊ทธ์˜ ์ด๋ ฅ ๋ฌด๊ฒฐ์„ฑยท์œ„๋ณ€์กฐ ํƒ์ง€๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import hashlib\n", "import json\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์กฐ๋‹ฌ๊ณ„ํš์•ˆยท๊ณ„์•ฝ์„œ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ณ„์•ฝยท์—…์ฒดยท๋ฒ•๋ น ์กฐ๋ฌธ ์—†์Œ)\n", "# ๊ณ„์•ฝ ์†์„ฑ 16๊ฐœ + ์กฐํ•ญ ๋ฌธ์žฅ 6๊ฐœ๋ฅผ ๋งŒ๋“ค๊ณ , ์†์„ฑ ์œ„๋ฐ˜ยท๋…์†Œ ์กฐํ•ญยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ์„ž์ธ '๊ทœ์ • ์œ„๋ฐ˜ ์—ฌ๋ถ€'๋ฅผ ํ•จ๊ป˜ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "TYPES = [\"๋ฌผํ’ˆ\", \"์šฉ์—ญ\", \"๊ณต์‚ฌ\"]\n", "METHODS = [\"์ผ๋ฐ˜๊ฒฝ์Ÿ\", \"์ œํ•œ๊ฒฝ์Ÿ\", \"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]\n", "CLASSES = [\"์ •์ƒ\", \"๋…์†Œ\", \"๋‚ฉ๊ธฐ์œ„ํ—˜\"]\n", "OBJ = [\"๋‚ฉํ’ˆ ๋ฌผํ’ˆ\", \"์šฉ์—ญ ๊ฒฐ๊ณผ๋ฌผ\", \"์‹œ๊ณต ๋ชฉ์ ๋ฌผ\", \"์ •๋น„ ์„ฑ๊ณผ๋ฌผ\"]\n", "\n", "N_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋‚ฉ๊ธฐ๋ฅผ ์ง€์ฒดํ•œ ๊ฒฝ์šฐ ์ง€์ฒด์ผ์ˆ˜ 1์ผ๋งˆ๋‹ค ๊ณ„์•ฝ๊ธˆ์•ก์˜ {p}/1000์— ํ•ด๋‹นํ•˜๋Š” ์ง€์ฒด์ƒ๊ธˆ์„ ๋ถ€๊ณผํ•œ๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๊ฐ€ ํ™•์ธ๋œ ๊ฒฝ์šฐ์— ํ•œํ•˜์—ฌ ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์Šน์ธ์„ ๋ฐ›์•„ ๋ฉด์ œํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ ์—ฐ์žฅ์€ ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๋ฅผ ํ™•์ธํ•˜๊ณ  ์„œ๋ฉด์œผ๋กœ ์Šน์ธํ•œ ๊ฒฝ์šฐ์—๋งŒ ์ธ์ •ํ•œ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋‚ฉํ’ˆ๋œ {o}์„ {d}์ผ ์ด๋‚ด์— ๊ฒ€์‚ฌํ•˜๊ณ  ํ•ฉ๊ฒฉํ•œ ํ›„์— ๋Œ€๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ํ•˜๋„๊ธ‰์„ ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ์‚ฌ์ „์— ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์„œ๋ฉด ์Šน์ธ์„ ๋ฐ›์•„์•ผ ํ•œ๋‹ค.\",\n", " \"์–ด๋А ํ•œ์ชฝ์ด ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•˜๋ ค๋ฉด ์‚ฌ์œ ๋ฅผ ๋ช…์‹œํ•ด {d}์ผ ์ „์— ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"{o}์— ํ•˜์ž๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ๋ณด์ฆ ๊ธฐ๊ฐ„ ์•ˆ์— ๋ฌด์ƒ์œผ๋กœ ๋ณด์ˆ˜ํ•˜๊ฑฐ๋‚˜ ๊ต์ฒดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ๋น„๊ณต๊ฐœ ์‚ฌํ•ญ์€ ์ œ3์ž์—๊ฒŒ ๋ˆ„์„คํ•˜์ง€ ์•Š์œผ๋ฉฐ ์œ„๋ฐ˜ ์‹œ ์ฑ…์ž„์„ ์ง„๋‹ค.\",\n", " \"{o}์— ๋Œ€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์˜ ๊ท€์†๊ณผ ์‚ฌ์šฉ ๋ฒ”์œ„๋Š” ๋ณ„๋„ ํ•ฉ์˜์„œ์— ๋”ฐ๋ผ ์ •ํ•œ๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ํ•ฉ๊ฒฉ ํ›„ ์ฒญ๊ตฌ์„œ๋ฅผ ์ ‘์ˆ˜ํ•œ ๋‚ ๋ถ€ํ„ฐ {d}์ผ ์ด๋‚ด์— ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ๊ธˆ์•ก์˜ ์กฐ์ •์€ ๋ฌผ๊ฐ€๋ณ€๋™ ๊ธฐ์ค€์— ๋”ฐ๋ผ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ์„œ๋ฉด์œผ๋กœ ํ•ฉ์˜ํ•œ ๊ฒฝ์šฐ์—๋งŒ ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ดํ–‰๋ณด์ฆ์„œ๋ฅผ ๊ณ„์•ฝ ์ฒด๊ฒฐ ์‹œ ์ œ์ถœํ•˜๋ฉฐ ๋ณด์ฆ๊ธˆ์€ ๊ณ„์•ฝ ์ดํ–‰ ์™„๋ฃŒ ํ›„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ณ„์•ฝ์„œ ๋ณ„ํ‘œ์˜ ์ผ์ž๋ฅผ ๋”ฐ๋ฅด๋ฉฐ ๋ณ€๊ฒฝ ์‹œ ์„œ๋ฉด ํ•ฉ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผํ•˜๋ฉฐ ์ด์˜๋Š” ์ •ํ•ด์ง„ ์ ˆ์ฐจ๋กœ ์ œ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•˜์ง€ ๋ชปํ•œ {o}์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๊ต์ฒดํ•˜๋ฉฐ ๊ทธ ๋น„์šฉ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ๊ท€์ฑ… ์‚ฌ์œ ๋กœ ์ดํ–‰์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•˜๊ณ  ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"์ง€์—ฐ์ด ์˜ˆ์ƒ๋˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ๋ฐœ์ฃผ๊ธฐ๊ด€์— ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•œ๋‹ค.\",\n", " \"๋ณด์ฆ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง€๊ณ  ๋ณด์ˆ˜ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์€ ๋จผ์ € ํ˜‘์˜๋กœ ํ•ด๊ฒฐํ•˜๊ณ  ํ˜‘์˜๊ฐ€ ๋˜์ง€ ์•Š์œผ๋ฉด ๊ด€ํ•  ๋ฒ•์›์˜ ํŒ๋‹จ์— ๋”ฐ๋ฅธ๋‹ค.\",\n", "]\n", "T_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ง€์ฒด์ƒ๊ธˆ์€ ์ „์•ก ๋ฉด์ œํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋Œ€ํ•ด ์ด์˜๋ฅผ ์ œ๊ธฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ฆ‰์‹œ ๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ ๋ณด๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ดํ›„ ๊ฒ€์‚ฌ๋ฅผ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์–ธ์ œ๋“ ์ง€ ์ผ๋ฐฉ์ ์œผ๋กœ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ์— ๋”ฐ๋ฅธ ์†ํ•ด๋Š” ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"{o}์˜ ์ถ”๊ฐ€ ๋น„์šฉ์€ ์‚ฌ์œ ๋ฅผ ๋ถˆ๋ฌธํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ์ „์•ก ๋ถ€๋‹ดํ•˜๋ฉฐ ๋ณ„๋„ ํ˜‘์˜ ์—†์ด ์ฒญ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"{o}์— ๊ด€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์€ ๋ชจ๋‘ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ๊ท€์†๋˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"ํ•˜๋„๊ธ‰์€ ์‹ ๊ณ  ์—†์ด ์ œํ•œ ์—†์ด ํ—ˆ์šฉํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ํ•˜๋„๊ธ‰ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ์ „์— ์ „์•ก ์„ ์ง€๊ธ‰ํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ฐ˜ํ™˜ ์ฒญ๊ตฌ๊ถŒ์„ ํฌ๊ธฐํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ์‚ฌํ•ญ์˜ ๋ˆ„์„ค๋กœ ์ธํ•œ ์ฑ…์ž„์„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"ํ•˜์ž ๋ณด์ˆ˜ ์ฑ…์ž„์€ ์ธ๋„ ํ›„ ์ฆ‰์‹œ ์†Œ๋ฉธํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ณด์ˆ˜๋ฅผ ์š”๊ตฌํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฒญ๊ตฌํ•˜๋Š” ๊ธˆ์•ก์„ ๊ฑฐ์ ˆํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ๊ฑฐ์ ˆํ•˜๋ฉด ์œ„์•ฝ๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์ด ์ƒ๊ธฐ๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ •ํ•˜๋Š” ์ค‘์žฌ ๊ธฐ๊ด€์—์„œ๋งŒ ํ•ด๊ฒฐํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์‚ฌ๋Š” ์ƒ๋žตํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์‹œํ—˜ ๊ฒฐ๊ณผ์„œ๋กœ ๋Œ€์‹ ํ•œ๋‹ค.\",\n", " \"๊ฒฌ์  ๊ธˆ์•ก์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ๊ทธ๋Œ€๋กœ ํ™•์ •ํ•˜๊ณ  ์ฆ๋น™ ์ž๋ฃŒ๋Š” ๋ฐ›์ง€ ์•Š๋Š”๋‹ค.\",\n", "]\n", "D_TPL = [\n", " \"๋‚ฉ๊ธฐ๋Š” ์ž์žฌ ์ˆ˜๊ธ‰ ์‚ฌ์ •์— ๋”ฐ๋ผ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ฐ€๋Šฅํ•œ ํ•œ ์ง€ํ‚ค๋„๋ก ๋…ธ๋ ฅํ•˜๋ฉฐ ์ง€์—ฐ๋˜์–ด๋„ ์‚ฌ์œ ๋ฅผ ๋ณด๊ณ ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณต๊ธ‰ ์ง€์—ฐ์ด๋‚˜ ์„ ํ–‰ ์—…์ฒด ์‚ฌ์ •๋„ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ์œผ๋กœ ๋ณด์•„ ๋‚ฉ๊ธฐ๋ฅผ ์ž๋™ ์—ฐ์žฅํ•œ๋‹ค.\",\n", " \"์ฐฉ์ˆ˜์ผ์€ ์„ ํ–‰ ์—…์ฒด์˜ ๋‚ฉํ’ˆ์ด ๋๋‚œ ๋’ค ๋ณ„๋„๋กœ ์ •ํ•˜๋ฉฐ ์™„๋ฃŒ ๊ธฐํ•œ์€ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ์ผ ์ดํ›„ 1ํšŒ์— ํ•œํ•ด ๊ธฐํ•œ ์—†์ด ์ž๋™ ์—ฐ์žฅ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํ•œ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๋Š” ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ƒ์‚ฐ ์ผ์ •์ด ํ™•์ •๋˜๋Š” ๋Œ€๋กœ ํ†ต๋ณดํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์‹œ์ ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž ์‚ฌ์ •์ด ํ—ˆ๋ฝํ•˜๋Š” ๋•Œ๋กœ ํ•˜๊ณ  ๋‚ ์งœ๋Š” ๋”ฐ๋กœ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ž์žฌ๊ฐ€ ๋Šฆ๊ฒŒ ๋“ค์–ด์˜ค๋ฉด ์™„๋ฃŒ ์ผ์ •์€ ๊ทธ๋งŒํผ ์ž๋™์œผ๋กœ ๋Šฆ์ถฐ์ง„๋‹ค.\",\n", " \"๊ธฐํ•œ ๋‚ด์— ์ธ๋„ํ•˜์ง€ ๋ชปํ•ด๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ •์ด ๋Šฆ์–ด์ ธ๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ƒˆ ๊ณ„ํš์„œ๋ฅผ ๋‚ผ ํ•„์š”๊ฐ€ ์—†๋‹ค.\",\n", "]\n", "# ํ•™์Šต์— ์“ฐ์ง€ ์•Š๋Š” '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'(๋ชจ๋ธ์ด ๋ฌธ์žฅ ํ‹€์„ ์™ธ์› ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์šฉ๋„)\n", "HOLD = {\n", " \"์ •์ƒ\": [\"๊ฒ€์‚ฌ์— ๋ถˆํ•ฉ๊ฒฉํ•œ ๊ฒฝ์šฐ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ • ๊ธฐํ•œ ์•ˆ์— ์žฌ๋‚ฉํ’ˆํ•˜๋ฉฐ ์žฌ๊ฒ€์‚ฌ ๋น„์šฉ์€ ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ฒ•๋ น์„ ์œ„๋ฐ˜ํ•˜์—ฌ ๊ณ„์•ฝ์„ ์ดํ–‰ํ•˜์ง€ ๋ชปํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด ํ†ต์ง€ ํ›„ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๊ฐ€ ์ง€์—ฐ๋  ์šฐ๋ ค๊ฐ€ ์žˆ์œผ๋ฉด ์ƒ๋Œ€์ž๋Š” ์ฆ‰์‹œ ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"ํ•˜์ž๋‹ด๋ณด ์ฑ…์ž„ ๊ธฐ๊ฐ„์€ ์ธ์ˆ˜์ผ๋ถ€ํ„ฐ ๊ธฐ์‚ฐํ•˜๋ฉฐ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง„๋‹ค.\"],\n", " \"๋…์†Œ\": [\"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๋Œ€๊ธˆ์„ ์ด์œ  ์—†์ด ๊ฑฐ์ ˆํ•˜์ง€ ๋ชปํ•˜๋ฉฐ ๊ฑฐ์ ˆ ์‹œ ์œ„์•ฝ๊ธˆ์„ ๋‚ธ๋‹ค.\",\n", " \"๊ณ„์•ฝ ๋‚ด์šฉ์— ๊ด€ํ•œ ๋ถ„์Ÿ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€์—์„œ๋งŒ ๋‹ค๋ฃจ๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์ˆ˜ ๊ถŒํ•œ์€ ํฌ๊ธฐํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ž์ฒด ์‹œํ—˜ ์„ฑ์ ์„œ๋กœ ๊ฐˆ์Œํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ œ์ถœํ•œ ๊ฒฌ์ ์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ํ™•์ • ๋‹จ๊ฐ€๋กœ ํ•˜๋ฉฐ ์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"๋‚ฉํ’ˆ ์‹œ๊ธฐ๋Š” ์ƒ๋Œ€์ž์˜ ํ˜•ํŽธ์ด ํ—ˆ๋ฝํ•˜๋Š” ์‹œ์ ์œผ๋กœ ํ•˜๊ณ  ๋ณ„๋„ ๋‚ ์งœ๋Š” ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์›์ž์žฌ ํ™•๋ณด๊ฐ€ ๋Šฆ์–ด์ง€๋ฉด ์™„๋ฃŒ ์ผ์ž๋Š” ์ž๋™์œผ๋กœ ๋’ค๋กœ ๋ฏธ๋ค„์ง€๋Š” ๊ฒƒ์œผ๋กœ ๋ณธ๋‹ค.\",\n", " \"์ธ๋„ ๊ธฐํ•œ์€ ์ฐธ๊ณ ์šฉ์ด๋ฉฐ ์ง€ํ‚ค์ง€ ๋ชปํ•ด๋„ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ • ์ง€์—ฐ์ด ์žˆ์–ด๋„ ์ƒ๋Œ€์ž๋Š” ๋ณ€๊ฒฝ ๊ณ„ํš์„ ์ œ์ถœํ•  ์˜๋ฌด๊ฐ€ ์—†๋‹ค.\"],\n", "}\n", "SUFFIX = [\"\", \"\", \"\", \" ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ์‹œํ–‰ ์„ธ์น™์— ๋”ฐ๋ฅธ๋‹ค.\", \" ์ด ์กฐํ•ญ์€ ๊ณ„์•ฝ ์ „์ฒด์— ์ ์šฉํ•œ๋‹ค.\", \" ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ํ˜‘์˜ํ•œ๋‹ค.\", \" ๊ด€๋ จ ์„œ์‹์€ ๋ณ„์ง€๋ฅผ ๋”ฐ๋ฅธ๋‹ค.\"]\n", "SWAP = [(\"๋ฐœ์ฃผ๊ธฐ๊ด€\", \"๊ณ„์•ฝ๋‹ด๋‹น๊ด€\"), (\"๊ณ„์•ฝ์ƒ๋Œ€์ž\", \"์—…์ฒด\")]\n", "\n", "\n", "def make_clause(rng, cls, hold=False, idx=None):\n", " \"\"\"์กฐํ•ญ ํ•œ ๋ฌธ์žฅ ์ƒ์„ฑ. ํ‘œํ˜„ ์ผ๋ถ€๋Š” ๋™์˜์–ด๋กœ ๋ฐ”๊พธ๊ณ  ์ ‘๋ฏธ ๋ฌธ๊ตฌ๋ฅผ ๋ฌด์ž‘์œ„๋กœ ๋ถ™์ธ๋‹ค(idx๋ฅผ ์ฃผ๋ฉด ๊ทธ ๋ฒˆํ˜ธ์˜ ๋ฌธ์žฅ ํ‹€์„ ์‚ฌ์šฉ).\"\"\"\n", " if idx is not None:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[idx].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " elif hold:\n", " s = HOLD[cls][rng.integers(len(HOLD[cls]))]\n", " else:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[rng.integers(len(tpl))].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " if rng.random() < 0.3:\n", " a, b = SWAP[rng.integers(2)]\n", " s = s.replace(a, b)\n", " return s + SUFFIX[rng.integers(len(SUFFIX))]\n", "\n", "\n", "def make_contracts(n=200, seed=7):\n", " \"\"\"๊ฐ€์ƒ ๊ณ„์•ฝ n๊ฑด(์†์„ฑ 16๊ฐœ + ์กฐํ•ญ 6๊ฐœ + ๊ทœ์ • ์œ„๋ฐ˜ ์ •๋‹ต)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " sl = rng.beta(1.2, 3.2, n) # ๊ณ„์•ฝ์„œ ์ž‘์„ฑ์ด ํ—ˆ์ˆ ํ•œ ์ •๋„(์ž ์žฌ ๋ณ€์ˆ˜)\n", " ctype = rng.choice(TYPES, n, p=[.5, .3, .2])\n", " amount = np.clip(np.exp(rng.normal(np.log(220), 1.1, n)), 5, 5000).round(0)\n", " method = np.where(rng.random(n) < .06 + .3 * sl, \"์ˆ˜์˜๊ณ„์•ฝ\", rng.choice(METHODS[:3], n, p=[.5, .3, .2]))\n", " period = np.array([rng.integers(60, 300) if t == \"๋ฌผํ’ˆ\" else rng.integers(90, 400) if t == \"์šฉ์—ญ\" else rng.integers(180, 720) for t in ctype])\n", " delivery = (period * rng.uniform(.5, 1.0, n)).astype(int)\n", " bad_logic = rng.random(n) < .015 * (1 + 3 * sl)\n", " delivery = np.where(bad_logic, period + rng.integers(10, 60, n), delivery)\n", " pen = np.where(rng.random(n) < .22 * sl, rng.choice([.3, .5, .8], n), rng.choice([1.0, 1.5, 2.0, 2.5], n))\n", " adv = np.where(rng.random(n) < .22 * sl, rng.choice([50, 60, 70], n), rng.choice([0, 10, 20, 30, 40], n))\n", " bond = np.where(rng.random(n) < .25 * sl + .5 * (adv > 40), rng.choice([0, 5], n), rng.choice([10, 10, 15, 20], n))\n", " war = np.array([rng.choice([3, 6]) if (rng.random() < .25 * s and t == \"๋ฌผํ’ˆ\") else rng.choice([12, 24, 36]) if t == \"๋ฌผํ’ˆ\" else rng.choice([0, 6, 12]) if t == \"์šฉ์—ญ\" else rng.choice([24, 36, 60]) for t, s in zip(ctype, sl)])\n", " insp = np.where(rng.random(n) < .3 * sl, rng.choice([21, 30], n), rng.choice([5, 7, 10, 14], n))\n", " sub = np.where(rng.random(n) < .15 * sl, rng.choice([60, 70, 80], n), rng.choice([0, 10, 20, 30, 40, 50], n))\n", " padj = rng.random(n) < .75 - .4 * sl\n", " term = rng.random(n) > .015 * (1 + 6 * sl)\n", " sec = rng.random(n) > .015 * (1 + 6 * sl)\n", " ip = rng.random(n) > .03 * (1 + 4 * sl)\n", " clauses, cls_true = [], []\n", " for i in range(n):\n", " pt, pd_ = .01 + .08 * sl[i], .02 + .08 * sl[i]\n", " cl = [rng.choice(CLASSES, p=[1 - pt - pd_, pt, pd_]) for _ in range(6)]\n", " cls_true.append(cl); clauses.append([make_clause(rng, c) for c in cl])\n", " df = pd.DataFrame({\"contract_id\": [f\"C-{i + 1:04d}\" for i in range(n)], \"ctype\": ctype, \"method\": method, \"amount_m\": amount,\n", " \"period_days\": period, \"delivery_days\": delivery, \"penalty_permil\": pen, \"advance_pct\": adv, \"perf_bond_pct\": bond,\n", " \"warranty_months\": war, \"inspection_days\": insp, \"subcontract_pct\": sub, \"price_adjust\": padj,\n", " \"termination\": term, \"security\": sec, \"ip_clause\": ip})\n", " hard = ((pen < 1.0) | ((adv > 40) & (bond < 10)) | ((amount >= 300) & (method == \"์ˆ˜์˜๊ณ„์•ฝ\")) | ((ctype == \"๋ฌผํ’ˆ\") & (war < 12))\n", " | ~term | ~sec | (sub > 50) | (delivery > period))\n", " toxic = np.array([c.count(\"๋…์†Œ\") for c in cls_true])\n", " delay = np.array([c.count(\"๋‚ฉ๊ธฐ์œ„ํ—˜\") for c in cls_true])\n", " split = (method == \"์ˆ˜์˜๊ณ„์•ฝ\") & (amount >= 250) & (amount < 300) & (rng.random(n) < .6) # ๊ณ„์•ฝ ์ชผ๊ฐœ๊ธฐ ์˜์‹ฌ(์ž„๊ณ„๊ฐ’ ๋ฐ”๋กœ ์•„๋ž˜)\n", " df[\"clauses\"], df[\"clause_cls\"] = clauses, cls_true\n", " df[\"n_toxic\"], df[\"n_delay\"] = toxic, delay\n", " viol = hard | (toxic > 0) | split | (delay >= 2)\n", " waived = hard & (toxic == 0) & (rng.random(n) < .12) # ๊ณ„์•ฝ๊ด€์ด ์˜ˆ์™ธ ์‚ฌ์œ ๋ฅผ ์ธ์ •ํ•ด ์œ„๋ฐ˜์œผ๋กœ ๋ณด์ง€ ์•Š์€ ๊ฑด(์ž๋ฃŒ์˜ ํ˜„์‹ค์  ์žก์Œ)\n", " hidden = ~viol & (rng.random(n) < .03 + .08 * sl) # ์†์„ฑยท๋ฌธ๊ตฌ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜(๋ณ„์ฒจ ํ•ฉ์˜ ๋“ฑ)\n", " df[\"violation\"] = (viol & ~waived) | hidden\n", " df[\"vtype\"] = np.where(hidden, \"๊ธฐํƒ€\", np.where(hard, \"์†์„ฑ์œ„๋ฐ˜\", np.where(toxic > 0, \"๋…์†Œ์กฐํ•ญ\", np.where(split | (delay >= 2), \"๊ธฐํƒ€\", \"-\"))))\n", " return df\n", "\n", "# ---- ๊ณ„์•ฝ ๊ทœ์ • ์ค€์ˆ˜ ํŒ๋ณ„ ๋ฃฐ ์„ค์ •(YAML ํ˜•์‹ ๋ฌธ์ž์—ด: ๊ทœ์ •์ด ๋ฐ”๋€Œ๋ฉด ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ด ์„ค์ •๋งŒ ๊ณ ์นœ๋‹ค) ----\n", "RULES_YAML = \"\"\"\n", "- id: R01\n", " name: ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.0/1000 ๋ฏธ๋งŒ\n", " field: penalty_permil\n", " op: \"<\"\n", " value: 1.0\n", " weight: 3\n", "- id: R02\n", " name: ์„ ๊ธˆ ๋น„์œจ 40% ์ดˆ๊ณผ\n", " field: advance_pct\n", " op: \">\"\n", " value: 40\n", " weight: 2\n", "- id: R03\n", " name: ์„ ๊ธˆ 40% ์ดˆ๊ณผ์ธ๋ฐ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: adv_no_bond\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R04\n", " name: 300๋ฐฑ๋งŒ์› ์ด์ƒ ์ˆ˜์˜๊ณ„์•ฝ\n", " field: sole_amount\n", " op: \">=\"\n", " value: 300\n", " weight: 3\n", "- id: R05\n", " name: 500๋ฐฑ๋งŒ์› ์ด์ƒ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: bond_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R06\n", " name: ๋ฌผํ’ˆ ํ•˜์ž๋ณด์ฆ 12๊ฐœ์›” ๋ฏธ๋งŒ\n", " field: warranty_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R07\n", " name: ํ•ด์ง€ ์กฐํ•ญ ์—†์Œ\n", " field: termination\n", " op: \"==\"\n", " value: false\n", " weight: 2\n", "- id: R08\n", " name: ๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ\n", " field: security\n", " op: \"==\"\n", " value: false\n", " weight: 3\n", "- id: R09\n", " name: ์ง€์‹์žฌ์‚ฐ ์กฐํ•ญ ์—†์Œ(๋ฌผํ’ˆยท์šฉ์—ญ)\n", " field: ip_missing\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R10\n", " name: ํ•˜๋„๊ธ‰ ๋น„์œจ 50% ์ดˆ๊ณผ\n", " field: subcontract_pct\n", " op: \">\"\n", " value: 50\n", " weight: 3\n", "- id: R11\n", " name: ๊ฒ€์‚ฌ ๊ธฐ๊ฐ„ 14์ผ ์ดˆ๊ณผ\n", " field: inspection_days\n", " op: \">\"\n", " value: 14\n", " weight: 1\n", "- id: R12\n", " name: ๋‚ฉํ’ˆ ๊ธฐํ•œ์ด ๊ณ„์•ฝ ๊ธฐ๊ฐ„ ์ดˆ๊ณผ\n", " field: logic_err\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R13\n", " name: 180์ผ ์ด์ƒ ๊ณ„์•ฝ์— ๋ฌผ๊ฐ€๋ณ€๋™ ์กฐํ•ญ ์—†์Œ\n", " field: no_padj_long\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R14\n", " name: 1000๋ฐฑ๋งŒ์› ์ด์ƒ ๋น„๊ฒฝ์Ÿ ๊ณ„์•ฝ\n", " field: noncomp_big\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R15\n", " name: ๊ณ„์•ฝ ๊ธฐ๊ฐ„ 365์ผ ์ดˆ๊ณผ\n", " field: period_days\n", " op: \">\"\n", " value: 365\n", " weight: 1\n", "- id: R16\n", " name: ๋ถˆ๊ณต์ •ยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ ํฌํ•จ\n", " field: kw_hits\n", " op: \">=\"\n", " value: 1\n", " weight: 3\n", "\"\"\"\n", "GRADE_CUT = {\"๊ณ ์œ„ํ—˜\": 6, \"์ฃผ์˜\": 3} # ๊ฐ€์ค‘์น˜ ํ•ฉ ๊ธฐ์ค€ 1์ฐจ ํŒ๋ณ„ ๋“ฑ๊ธ‰(ํ•„์š” ์‹œ ์„ค์ •๋งŒ ๋ฐ”๊พผ๋‹ค)\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถˆ๊ณต์ • ์กฐํ•ญยท๋‚ฉ๊ธฐ ์ง€์ฒด ์œ„ํ—˜ ๋ฌธ๊ตฌ ์‚ฌ์ „(์‹ค์ œ ๊ทœ์ • ์กฐ๋ฌธ์ด ์•„๋‹˜)\n", "KEYWORDS = {\"๋…์†Œ\": [\"๋ฉด์ œํ•˜๋ฉฐ\", \"์ผ๋ฐฉ์ ์œผ๋กœ\", \"ํฌ๊ธฐ\", \"์ง€์ง€ ์•Š๋Š”๋‹ค\", \"์ฆ‰์‹œ ์†Œ๋ฉธ\", \"์ œํ•œ ์—†์ด\", \"์‹ ๊ณ  ์—†์ด\", \"์ „์•ก ๋ถ€๋‹ด\", \"๋ณ„๋„ ํ˜‘์˜ ์—†์ด\", \"๊ฒ€์‚ฌ ์ „์—\", \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ\", \"์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค\", \"์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์ž๋™ ์—ฐ์žฅ\", \"๊ธฐํ•œ ์—†์ด\", \"์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"๋…ธ๋ ฅํ•˜๋ฉฐ\", \"์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค\", \"ํ†ต๋ณดํ•œ๋‹ค\", \"์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค\", \"์˜๋ฌด๊ฐ€ ์—†๋‹ค\"]}\n", "\n", "\n", "def parse_rules(text):\n", " \"\"\"๊ฐ„์ด YAML ํŒŒ์„œ(๋ชฉ๋ก + key: value). ์ˆซ์žยทtrue/falseยท๋”ฐ์˜ดํ‘œ ๋ฌธ์ž์—ด์„ ํ•ด์„ํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 7] ๊ณ„์•ฝ ์Šน์ธ ์ „ ๋ฒ•๋ฌด/๊ณ„์•ฝ๊ด€ ์ˆ˜๋™ ๊ฒ€ํ†  ๋ฐ ์กฐํ•ญ ์ˆ˜์ • ๊ถŒ๊ณ  HITL ์ ˆ์ฐจ ์„ค๊ณ„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "OPS = {\"==\": lambda a, b: a == b, \"!=\": lambda a, b: a != b, \">\": lambda a, b: a > b, \">=\": lambda a, b: a >= b,\n", " \"<\": lambda a, b: a < b, \"<=\": lambda a, b: a <= b}\n", "\n", "\n", "def kw_count(clauses):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ ๋ชฉ๋ก์—์„œ ๋…์†Œยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ๊ฐ€ ๋“ค์–ด ์žˆ๋Š” ์กฐํ•ญ ์ˆ˜.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 7] ๊ณ„์•ฝ ์Šน์ธ ์ „ ๋ฒ•๋ฌด/๊ณ„์•ฝ๊ด€ ์ˆ˜๋™ ๊ฒ€ํ†  ๋ฐ ์กฐํ•ญ ์ˆ˜์ • ๊ถŒ๊ณ  HITL ์ ˆ์ฐจ ์„ค๊ณ„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def derive(df):\n", " \"\"\"๋ฃฐ์— ์“ฐ๋Š” ํŒŒ์ƒ ํ•„๋“œ(์„ ๊ธˆ+๋ณด์ฆ ๋ถ€์กฑ, ์ˆ˜์˜๊ณ„์•ฝ ๊ณ ์•ก, ๋…ผ๋ฆฌ ์˜ค๋ฅ˜, ํ‚ค์›Œ๋“œ ์ ์ค‘ ๋“ฑ)๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " d = df.copy()\n", " d[\"adv_no_bond\"] = (d[\"advance_pct\"] > 40) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"sole_amount\"] = np.where(d[\"method\"] == \"์ˆ˜์˜๊ณ„์•ฝ\", d[\"amount_m\"], 0.0) # ์ˆ˜์˜๊ณ„์•ฝ์ผ ๋•Œ๋งŒ ๊ธˆ์•ก(ํ•œ๋„๋Š” YAML์—์„œ ์กฐ์ •)\n", " d[\"bond_gap\"] = (d[\"amount_m\"] >= 500) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"warranty_gap\"] = (d[\"ctype\"] == \"๋ฌผํ’ˆ\") & (d[\"warranty_months\"] < 12)\n", " d[\"ip_missing\"] = (~d[\"ip_clause\"]) & (d[\"ctype\"] != \"๊ณต์‚ฌ\")\n", " d[\"logic_err\"] = d[\"delivery_days\"] > d[\"period_days\"]\n", " d[\"no_padj_long\"] = (d[\"period_days\"] >= 180) & (~d[\"price_adjust\"])\n", " d[\"noncomp_big\"] = d[\"method\"].isin([\"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]) & (d[\"amount_m\"] >= 1000)\n", " d[\"kw_hits\"] = [kw_count(c) for c in d[\"clauses\"]] if \"clauses\" in d else 0\n", " return d\n", "\n", "\n", "def apply_rules(rec, rules):\n", " \"\"\"๋ ˆ์ฝ”๋“œ 1๊ฑด(dict)์— ๋ฃฐ์„ ์ ์šฉํ•ด (์ ์ˆ˜, ๋“ฑ๊ธ‰, ์ ์ค‘ ๋ฃฐ id ๋ชฉ๋ก)์„ ๋Œ๋ ค์ค€๋‹ค. ๊ฐ’์ด ์—†์œผ๋ฉด ํ•ด๋‹น ๋ฃฐ์€ ๊ฑด๋„ˆ๋›ด๋‹ค.\"\"\"\n", " hits = []\n", " for r in rules:\n", " v = rec.get(r[\"field\"])\n", " if v is None or (isinstance(v, float) and np.isnan(v)):\n", " continue\n", " if OPS[r[\"op\"]](v, r[\"value\"]):\n", " hits.append(r)\n", " score = sum(r[\"weight\"] for r in hits)\n", " grade = \"๊ณ ์œ„ํ—˜\" if score >= GRADE_CUT[\"๊ณ ์œ„ํ—˜\"] else (\"์ฃผ์˜\" if score >= GRADE_CUT[\"์ฃผ์˜\"] else \"๊ด€์‹ฌ\")\n", " return score, grade, [r[\"id\"] for r in hits]\n", "\n", "SIMPLE_KW = {\"๋…์†Œ\": [\"๋ฉด์ œ\", \"์ผ๋ฐฉ\", \"ํฌ๊ธฐ\", \"์ „์•ก\", \"๋ˆ„์„ค\", \"ํ•˜๋„๊ธ‰\", \"์„ ์ง€๊ธ‰\"], \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์—ฐ์žฅ\", \"์ž๋™\", \"๋…ธ๋ ฅ\", \"ํ†ต๋ณด\", \"๊ธฐํ•œ\"]}\n", "\n", "# ---- ์กฐํ•ญ ๋ฌธ์žฅ ๋ถ„๋ฅ˜(์ •์ƒ / ๋…์†Œ / ๋‚ฉ๊ธฐ์œ„ํ—˜): ๊ธ€์ž n-gram TF-IDF + ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€ ----\n", "def clause_set(seed, n, hold=False, mix=(.55, .25, .20)):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ n๊ฐœ์™€ ์ •๋‹ต ๋ถ„๋ฅ˜๋ฅผ ๋งŒ๋“ ๋‹ค. hold=True๋ฉด ํ•™์Šต์— ์“ฐ์ง€ ์•Š์€ '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'์œผ๋กœ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " y = rng.choice(CLASSES, n, p=list(mix))\n", " return [make_clause(rng, c, hold) for c in y], list(y)\n", "\n", "\n", "def fit_clause_model(seed=100, n=1200):\n", " \"\"\"ํ•™์Šต์šฉ ์กฐํ•ญ ๋ฌธ์žฅ์œผ๋กœ ๋ถ„๋ฅ˜ ๋ชจ๋ธ์„ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " X, y = clause_set(seed, n)\n", " return make_pipeline(TfidfVectorizer(analyzer=\"char_wb\", ngram_range=(2, 4), sublinear_tf=True), LogisticRegression(C=20, max_iter=2000)).fit(X, y)\n", "\n", "\n", "def keyword_pred(s):\n", " \"\"\"๋น„๊ต์šฉ ๊ธฐ์ค€์„ : ๋‹จ์ˆœ ํ‚ค์›Œ๋“œ(๋ถ€์ • ํ‘œํ˜„ยท๋ฌธ๋งฅ์„ ๋ณด์ง€ ์•Š์Œ) ์ ์ค‘์œผ๋กœ๋งŒ ๋ถ„๋ฅ˜.\"\"\"\n", " if any(k in s for k in SIMPLE_KW[\"๋…์†Œ\"]):\n", " return \"๋…์†Œ\"\n", " return \"๋‚ฉ๊ธฐ์œ„ํ—˜\" if any(k in s for k in SIMPLE_KW[\"๋‚ฉ๊ธฐ์œ„ํ—˜\"]) else \"์ •์ƒ\"\n", "\n", "# ---- ์—ญํ• ๊ณผ ๊ถŒํ•œ(์ตœ์†Œ ๊ถŒํ•œ ์›์น™): ์‹œ์Šคํ…œ์€ '์ œ์•ˆ'๋งŒ ํ•  ์ˆ˜ ์žˆ๊ณ  ํ™•์ • ๊ถŒํ•œ์ด ์—†๋‹ค ----\n", "PERM = {\"์‹œ์Šคํ…œ\": {\"propose\"}, \"๊ณ„์•ฝ๋‹ด๋‹น\": {\"submit\", \"redraft\", \"view\"}, \"๊ณ„์•ฝ๊ด€\": {\"review_contract\", \"reject\", \"view\"}, \"๋ฒ•๋ฌด\": {\"review_legal\", \"reject\", \"view\"},\n", " \"์Šน์ธ๊ถŒ์ž\": {\"approve\", \"reject\", \"view\"}, \"๊ฐ์‚ฌ๊ด€\": {\"view\", \"audit\"},\n", " \"๋ถ€์„œ์žฅ\": {\"submit\", \"approve\", \"reject\", \"view\"}} # ์†Œ๊ทœ๋ชจ ๋ถ€์„œ๋Š” ์ƒ์‹ ๊ณผ ๊ฒฐ์žฌ๋ฅผ ๊ฒธํ•  ์ˆ˜ ์žˆ์œผ๋‚˜ ๊ฐ™์€ ๊ฑด์€ ๋ถ„๋ฆฌ\n", "NEXT = {\"์ œ์•ˆ\": (\"submit\", \"์ƒ์‹ \"), \"์ƒ์‹ \": (\"review_contract\", \"๊ณ„์•ฝ๊ด€๊ฒ€ํ† \"), \"๊ณ„์•ฝ๊ด€๊ฒ€ํ† \": (\"review_legal\", \"๋ฒ•๋ฌด๊ฒ€ํ† \"), \"๋ฒ•๋ฌด๊ฒ€ํ† \": (\"approve\", \"์Šน์ธ\")}\n", "USERS = {\"sys\": \"์‹œ์Šคํ…œ\", \"kim\": \"๊ณ„์•ฝ๋‹ด๋‹น\", \"lee\": \"๊ณ„์•ฝ๊ด€\", \"park\": \"๋ฒ•๋ฌด\", \"choi\": \"์Šน์ธ๊ถŒ์ž\", \"jung\": \"๊ฐ์‚ฌ๊ด€\", \"oh\": \"๋ถ€์„œ์žฅ\"}\n", "MAX_REDRAFT = 2\n", "\n", "\n", "class Chain:\n", " \"\"\"ํ•ด์‹œ ์ฒด์ธ ๊ฐ์‚ฌ ๋กœ๊ทธ: ๊ฐ ๊ธฐ๋ก์ด ์ง์ „ ๊ธฐ๋ก์˜ ํ•ด์‹œ๋ฅผ ํฌํ•จํ•ด ์ค‘๊ฐ„ ์ˆ˜์ •์ด ๋“œ๋Ÿฌ๋‚œ๋‹ค.\"\"\"\n", "\n", " def __init__(self):\n", " self.log = []\n", "\n", " def add(self, **body):\n", " prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n", " body = {\"seq\": len(self.log), **body}\n", " self.log.append({**body, \"prev\": prev, \"hash\": hashlib.sha256((prev + json.dumps(body, sort_keys=True, ensure_ascii=False)).encode()).hexdigest()})\n", "\n", " def verify(self):\n", " prev = \"GENESIS\"\n", " for e in self.log:\n", " b = {k: v for k, v in e.items() if k not in (\"prev\", \"hash\")}\n", " if e[\"prev\"] != prev or hashlib.sha256((prev + json.dumps(b, sort_keys=True, ensure_ascii=False)).encode()).hexdigest() != e[\"hash\"]:\n", " return e[\"seq\"]\n", " prev = e[\"hash\"]\n", " return None\n", "\n", "\n", "class Workflow:\n", " def __init__(self):\n", " self.audit, self.cases, self.blocked = Chain(), {}, []\n", "\n", " def _deny(self, who, case, action, why):\n", " self.audit.add(event=\"DENIED\", who=who, case=case, action=action, reason=why)\n", " self.blocked.append((who, case, action, why)); return False, why\n", "\n", " def act(self, who, case, action, reason=\"\", evidence=\"\"):\n", " role = USERS.get(who)\n", " if role is None:\n", " return self._deny(who, case, action, \"๋ฏธ๋“ฑ๋ก ์‚ฌ์šฉ์ž\")\n", " if action == \"delete_log\":\n", " return self._deny(who, case, action, \"๊ฐ์‚ฌ ๋กœ๊ทธ ์‚ญ์ œยท์ˆ˜์ • ๊ถŒํ•œ์€ ๋ˆ„๊ตฌ์—๊ฒŒ๋„ ์—†์Œ\")\n", " if action not in PERM[role]:\n", " return self._deny(who, case, action, f\"๊ถŒํ•œ ์—†์Œ({role} ์—ญํ• ์— {action} ๊ถŒํ•œ ์—†์Œ)\")\n", " if action == \"propose\":\n", " self.cases[case] = {\"state\": \"์ œ์•ˆ\", \"submitter\": None, \"version\": 1}\n", " self.audit.add(event=\"PROPOSE\", who=who, case=case, reason=reason); return True, \"์ œ์•ˆ ๋“ฑ๋ก\"\n", " c = self.cases.get(case)\n", " if c is None:\n", " return self._deny(who, case, action, \"์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฑด\")\n", " if c[\"state\"] in (\"์Šน์ธ\", \"์ข…๊ฒฐ(๋ฐ˜๋ ค)\"):\n", " return self._deny(who, case, action, f\"์ด๋ฏธ ์ข…๊ฒฐ๋œ ๊ฑด({c['state']})\")\n", " if not reason.strip() or not evidence.strip():\n", " return self._deny(who, case, action, \"์‚ฌ์œ ยท๊ทผ๊ฑฐ ์ž…๋ ฅ ํ•„์ˆ˜(๋ฐ˜๋ ค ์‚ฌ์œ  ํฌํ•จ)\")\n", " if action == \"reject\":\n", " if c[\"state\"] in (\"์ œ์•ˆ\", \"๋ฐ˜๋ ค\"):\n", " return self._deny(who, case, action, f\"๋ฐ˜๋ คํ•  ์ˆ˜ ์—†๋Š” ๋‹จ๊ณ„({c['state']})\")\n", " c[\"state\"] = \"์ข…๊ฒฐ(๋ฐ˜๋ ค)\" if c[\"version\"] > MAX_REDRAFT else \"๋ฐ˜๋ ค\"\n", " self.audit.add(event=\"REJECTED\", who=who, role=role, case=case, version=c[\"version\"], reason=reason, evidence=evidence); return True, c[\"state\"]\n", " if action == \"redraft\":\n", " if c[\"state\"] != \"๋ฐ˜๋ ค\":\n", " return self._deny(who, case, action, f\"์žฌ๊ธฐ์•ˆ์€ ๋ฐ˜๋ ค๋œ ๊ฑด๋งŒ ๊ฐ€๋Šฅ(ํ˜„์žฌ {c['state']})\")\n", " if who != c[\"submitter\"]:\n", " return self._deny(who, case, action, \"์žฌ๊ธฐ์•ˆ์€ ์ตœ์ดˆ ์ƒ์‹ ์ž๋งŒ ๊ฐ€๋Šฅ\")\n", " c[\"state\"], c[\"version\"] = \"์ƒ์‹ \", c[\"version\"] + 1\n", " self.audit.add(event=\"REDRAFT\", who=who, role=role, case=case, version=c[\"version\"], reason=reason, evidence=evidence); return True, \"์žฌ๊ธฐ์•ˆ ์ƒ์‹ \"\n", " if c[\"state\"] == \"๋ฐ˜๋ ค\":\n", " return self._deny(who, case, action, \"๋ฐ˜๋ ค๋œ ๊ฑด์€ ์žฌ๊ธฐ์•ˆ ํ›„์—๋งŒ ์ง„ํ–‰ ๊ฐ€๋Šฅ\")\n", " need, nxt = NEXT[c[\"state\"]]\n", " if action != need:\n", " return self._deny(who, case, action, f\"๋‹จ๊ณ„ ์œ„๋ฐ˜(ํ˜„์žฌ {c['state']}, ํ•„์š” ํ–‰์œ„ {need})\")\n", " if action == \"submit\":\n", " c[\"submitter\"] = who\n", " if action == \"approve\" and who == c[\"submitter\"]:\n", " return self._deny(who, case, action, \"์ƒ์‹ ์ž์™€ ์Šน์ธ์ž ๋ถ„๋ฆฌ ์›์น™ ์œ„๋ฐ˜\")\n", " c[\"state\"] = nxt\n", " self.audit.add(event=action.upper(), who=who, role=role, case=case, version=c[\"version\"], to=nxt, reason=reason, evidence=evidence); return True, nxt\n", "\n", "\n", "# ---- ์ƒ์‹  ๋Œ€์ƒ: ๋ฃฐ ์ ์ˆ˜๊ฐ€ ๋†’์€ ๊ณ„์•ฝ 12๊ฑด๊ณผ ๋ชจ๋ธ์ด ์ฐพ์€ ์œ„ํ—˜ ์กฐํ•ญ ----\n", "cm = fit_clause_model()\n", "rules = parse_rules(RULES_YAML)\n", "con = make_contracts(100, seed=7)\n", "d = derive(con)\n", "sc = [apply_rules(r, rules) for r in d.to_dict(\"records\")]\n", "d[\"score\"], d[\"hits\"] = [x[0] for x in sc], [x[2] for x in sc]\n", "picked = d.sort_values(\"score\", ascending=False).head(12)\n", "rng = np.random.default_rng(1)\n", "LEGAL = {\"R08\", \"R07\", \"R12\", \"R10\"} # ๋ฒ•๋ฌด ๊ฒ€ํ† ์—์„œ ๋ณด๋Š” ํ•ญ๋ชฉ(๋ณด์•ˆยทํ•ด์ง€ยท๋…ผ๋ฆฌยทํ•˜๋„๊ธ‰)\n", "\n", "wf = Workflow()\n", "rows = []\n", "for k, (_, p) in enumerate(picked.iterrows(), 1):\n", " case = f\"C{k:02d}\"\n", " flagged = [i + 1 for i, c in enumerate(cm.predict(list(p[\"clauses\"]))) if c != \"์ •์ƒ\"]\n", " wf.act(\"sys\", case, \"propose\", reason=f\"{p['contract_id']} ๋ฃฐ ์ ์ˆ˜ {p['score']}, ์œ„ํ—˜ ์กฐํ•ญ {len(flagged)}๊ฐœ -> ์ˆ˜์ • ๊ถŒ๊ณ  ์ œ์•ˆ\")\n", " wf.act(\"kim\", case, \"submit\", \"์œ„ํ—˜ ์กฐํ•ญ ์ˆ˜์ • ๊ถŒ๊ณ  ๊ฒ€ํ†  ์š”์ฒญ\", f\"๋ฃฐ ์ ์ค‘ {','.join(p['hits'][:4])}, ์œ„ํ—˜ ์กฐํ•ญ {flagged or '์—†์Œ'}\")\n", " c_issue, l_issue = bool(flagged), sorted(LEGAL & set(p[\"hits\"])) # ๊ณ„์•ฝ๊ด€์ด ๋ณผ ๋ฌธ์ œ, ๋ฒ•๋ฌด๊ฐ€ ๋ณผ ๋ฌธ์ œ\n", " note, big, justified = \"์Šน์ธ๊ถŒ์ž ๊ฒฐ์ •\", p[\"amount_m\"] >= 1000 and p[\"method\"] != \"์ผ๋ฐ˜๊ฒฝ์Ÿ\", False\n", " for _ in range(8):\n", " state = wf.cases[case][\"state\"]\n", " if state in (\"์Šน์ธ\", \"์ข…๊ฒฐ(๋ฐ˜๋ ค)\"):\n", " break\n", " if state == \"์ƒ์‹ \":\n", " if c_issue:\n", " wf.act(\"lee\", case, \"reject\", f\"์œ„ํ—˜ ์กฐํ•ญ {flagged} ์ˆ˜์ • ํ•„์š”\", \"์กฐํ•ญ ๋ชจ๋ธ ์œ„ํ—˜ ๋ถ„๋ฅ˜์™€ ๊ณ„์•ฝ๊ด€ ํŒ๋‹จ ์ผ์น˜\")\n", " else:\n", " wf.act(\"lee\", case, \"review_contract\", \"์กฐํ•ญ ๊ฒ€ํ†  ์™„๋ฃŒ\", f\"์œ„ํ—˜ ์กฐํ•ญ {'์ •๋ฆฌ๋จ' if flagged else '์—†์Œ'}\")\n", " elif state == \"๊ณ„์•ฝ๊ด€๊ฒ€ํ† \":\n", " if rng.random() < 0.15:\n", " note = \"์ž๋™ ํ™•์ • ์—†์Œ: ๋ฒ•๋ฌด ์‘๋‹ต ๋Œ€๊ธฐ\"; break # ๋ฌด์‘๋‹ต์€ ๋ณด๋ฅ˜๋กœ ๋‘”๋‹ค\n", " if l_issue:\n", " wf.act(\"park\", case, \"reject\", f\"ํ•„์ˆ˜ ์กฐํ•ญ ๋ณด์™„ ํ•„์š”({','.join(l_issue)})\", \"ํ•ด์ง€ยท๋ณด์•ˆยทํ•˜๋„๊ธ‰ยท๋‚ฉํ’ˆ ๊ธฐํ•œ ์กฐ๊ฑด ์ ๊ฒ€\")\n", " else:\n", " wf.act(\"park\", case, \"review_legal\", \"๋ฒ•๋ฌด ๊ฒ€ํ†  ์™„๋ฃŒ\", \"ํ•„์ˆ˜ ์กฐํ•ญ ์ ๊ฒ€ ์ด์ƒ ์—†์Œ\")\n", " elif state == \"๋ฒ•๋ฌด๊ฒ€ํ† \":\n", " if big and not justified:\n", " wf.act(\"choi\", case, \"reject\", \"๊ณ ์•ก ๋น„๊ฒฝ์Ÿ ๊ณ„์•ฝ์€ ์‚ฌ์œ  ์†Œ๋ช… ํ›„ ์žฌ์ƒ์‹ \", f\"๊ธˆ์•ก {p['amount_m']:.0f}๋ฐฑ๋งŒ์›, {p['method']}\")\n", " else:\n", " wf.act(\"choi\", case, \"approve\", \"์Šน์ธ: ์ˆ˜์ •๋ณธ์œผ๋กœ ๊ณ„์•ฝ ์˜๋ขฐ ์ง„ํ–‰\", f\"๋ฃฐ ์ ์ˆ˜ {p['score']}, ์œ„ํ—˜ ์กฐํ•ญยทํ•„์ˆ˜ ์กฐํ•ญ ์ •๋ฆฌ ์™„๋ฃŒ\")\n", " elif state == \"๋ฐ˜๋ ค\":\n", " wf.act(\"kim\", case, \"redraft\", \"๋ฐ˜๋ ค ์‚ฌ์œ  ๋ฐ˜์˜ํ•ด ์ˆ˜์ •\", f\"์ˆ˜์ • ๋Œ€์ƒ {flagged if c_issue else l_issue or ['์†Œ๋ช… ์ž๋ฃŒ']}, ์žฌ๊ธฐ์•ˆ {wf.cases[case]['version']}์ฐจ\")\n", " if rng.random() < 0.65: # ์žฌ๊ธฐ์•ˆ ํ›„ ๋ฌธ์ œ๊ฐ€ ์ •๋ฆฌ๋  ํ™•๋ฅ (๋ชจ์˜)\n", " c_issue, l_issue = False, []\n", " justified = True # ๊ณ ์•ก ๋น„๊ฒฝ์Ÿ ๊ฑด์€ ์žฌ๊ธฐ์•ˆ ๋•Œ ์‚ฌ์œ  ์†Œ๋ช…์„œ๋ฅผ ์ฒจ๋ถ€(๋ชจ์˜)\n", " final = wf.cases[case][\"state\"]\n", " if final not in (\"์Šน์ธ\", \"์ข…๊ฒฐ(๋ฐ˜๋ ค)\") and note.startswith(\"์ž๋™\"):\n", " final = \"๋ณด๋ฅ˜(๋ฌด์‘๋‹ต)\"\n", " elif final == \"์ข…๊ฒฐ(๋ฐ˜๋ ค)\":\n", " note = \"์žฌ๊ธฐ์•ˆ ํ•œ๋„(2ํšŒ) ์ดˆ๊ณผ\"\n", " rows.append((case, p[\"contract_id\"], final, wf.cases[case][\"version\"], note))\n", "print(\"[๊ฒฐ์žฌ ๊ฒฐ๊ณผ] 12๊ฑด (๋ชจ์˜ ๊ฒฐ์žฌ์ž ๊ธฐ์ค€, ์‹ค์ œ ๊ฒฐ์žฌ๋Š” ์‚ฌ๋žŒ์ด ํ•œ๋‹ค)\")\n", "print(pd.DataFrame(rows, columns=[\"๊ฑด\", \"๊ณ„์•ฝ\", \"์ตœ์ข… ์ƒํƒœ\", \"๋ฒ„์ „\", \"๋น„๊ณ \"]).to_string(index=False))\n", "st = pd.Series([r[2] for r in rows]).value_counts().to_dict()\n", "n_redraft = sum(e[\"event\"] == \"REDRAFT\" for e in wf.audit.log)\n", "print(\"์ƒํƒœ ์ง‘๊ณ„:\", st, f\"/ ๋ฐ˜๋ ค ํ›„ ์žฌ๊ธฐ์•ˆ {n_redraft}ํšŒ, ๋ฐ˜๋ ค ์ด๋ ฅ {sum(e['event'] == 'REJECTED' for e in wf.audit.log)}๊ฑด(๋ชจ๋‘ ์‚ฌ์œ ยท๊ทผ๊ฑฐ ๊ธฐ๋ก)\")\n", "\n", "# ---- ๊ถŒํ•œ ์šฐํšŒยท์ ˆ์ฐจ ์œ„๋ฐ˜ ์‹œ๋„ ----\n", "wf.act(\"sys\", \"T01\", \"propose\", reason=\"์‹œํ—˜ ๊ฑด\")\n", "wf.act(\"kim\", \"T01\", \"submit\", \"์‹œํ—˜ ์ƒ์‹ \", \"๊ทผ๊ฑฐ\")\n", "attempts = [\n", " (\"์‹œ์Šคํ…œ ์ž๋™ ์Šน์ธ\", lambda: wf.act(\"sys\", \"T01\", \"approve\", \"์ž๋™\", \"์ž๋™\")),\n", " (\"๊ณ„์•ฝ๋‹ด๋‹น์ด ๊ณ„์•ฝ๊ด€ ๊ฒ€ํ†  ๋Œ€ํ–‰\", lambda: wf.act(\"kim\", \"T01\", \"review_contract\", \"๋Œ€ํ–‰\", \"๊ทผ๊ฑฐ\")),\n", " (\"๊ฒ€ํ†  ๋‹จ๊ณ„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ์Šน์ธ\", lambda: wf.act(\"choi\", \"T01\", \"approve\", \"๋ฐ”๋กœ ์Šน์ธ\", \"๊ทผ๊ฑฐ\")),\n", " (\"๋ฏธ๋“ฑ๋ก ์‚ฌ์šฉ์ž ์ ‘๊ทผ\", lambda: wf.act(\"guest\", \"T01\", \"review_contract\", \"์—ด๋žŒ\", \"๊ทผ๊ฑฐ\")),\n", " (\"์‚ฌ์œ  ์—†๋Š” ๊ฒ€ํ† \", lambda: wf.act(\"lee\", \"T01\", \"review_contract\", \"\", \"\")),\n", " (\"์‚ฌ์œ  ์—†๋Š” ๋ฐ˜๋ ค\", lambda: wf.act(\"lee\", \"T01\", \"reject\", \"\", \"๊ทผ๊ฑฐ\")),\n", " (\"๊ฐ์‚ฌ ๋กœ๊ทธ ์‚ญ์ œ\", lambda: wf.act(\"kim\", \"T01\", \"delete_log\", \"์ •๋ฆฌ\", \"๊ทผ๊ฑฐ\")),\n", " (\"๊ฐ์‚ฌ๊ด€์ด ์Šน์ธ ์‹œ๋„\", lambda: wf.act(\"jung\", \"T01\", \"approve\", \"์Šน์ธ\", \"๊ทผ๊ฑฐ\")),\n", " (\"์ข…๊ฒฐ๋œ ๊ฑด ์žฌ์Šน์ธ\", lambda: wf.act(\"choi\", next(c for c, v in wf.cases.items() if v[\"state\"] == \"์Šน์ธ\"), \"approve\", \"์žฌ์Šน์ธ\", \"๊ทผ๊ฑฐ\")),\n", " (\"์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฑด ์Šน์ธ\", lambda: wf.act(\"choi\", \"X99\", \"approve\", \"์Šน์ธ\", \"๊ทผ๊ฑฐ\")),\n", "]\n", "wf.act(\"sys\", \"T02\", \"propose\", reason=\"์‹œํ—˜\"); wf.act(\"oh\", \"T02\", \"submit\", \"์ƒ์‹ \", \"๊ทผ๊ฑฐ\")\n", "wf.act(\"lee\", \"T02\", \"review_contract\", \"์กฐํ•ญ ํ™•์ธ\", \"์œ„ํ—˜ ์กฐํ•ญ ์—†์Œ\"); wf.act(\"park\", \"T02\", \"review_legal\", \"๋ฒ•๋ฌด ํ™•์ธ\", \"์ด์ƒ ์—†์Œ\")\n", "attempts.append((\"์ƒ์‹ ์ž๊ฐ€ ๋ณธ์ธ ๊ฑด ์Šน์ธ(๋ถ„๋ฆฌ ์›์น™)\", lambda: wf.act(\"oh\", \"T02\", \"approve\", \"๋ณธ์ธ ์Šน์ธ\", \"๊ทผ๊ฑฐ\")))\n", "wf.act(\"sys\", \"T03\", \"propose\", reason=\"์‹œํ—˜\"); wf.act(\"kim\", \"T03\", \"submit\", \"์ƒ์‹ \", \"๊ทผ๊ฑฐ\"); wf.act(\"lee\", \"T03\", \"reject\", \"์ˆ˜์ • ํ•„์š”\", \"๊ทผ๊ฑฐ\")\n", "attempts.append((\"๋ฐ˜๋ ค ๊ฑด์„ ์žฌ๊ธฐ์•ˆ ์—†์ด ๊ฒ€ํ†  ์ง„ํ–‰\", lambda: wf.act(\"lee\", \"T03\", \"review_contract\", \"๊ทธ๋Œ€๋กœ ์ง„ํ–‰\", \"๊ทผ๊ฑฐ\")))\n", "attempts.append((\"ํƒ€์ธ์ด ๋Œ€์‹  ์žฌ๊ธฐ์•ˆ\", lambda: wf.act(\"oh\", \"T03\", \"redraft\", \"๋Œ€์‹  ์ˆ˜์ •\", \"๊ทผ๊ฑฐ\")))\n", "print(\"\\n[๊ถŒํ•œ ์šฐํšŒยท์ ˆ์ฐจ ์œ„๋ฐ˜ ์‹œ๋„]\")\n", "blocked = 0\n", "for name, fn in attempts:\n", " ok, why = fn()\n", " blocked += (not ok)\n", " print(f\" {name:24s}-> {'์ฐจ๋‹จ' if not ok else 'ํ†ต๊ณผ(๊ฒฐํ•จ)'}: {why}\")\n", "print(f\"์šฐํšŒยท์œ„๋ฐ˜ ์‹œ๋„ ์ฐจ๋‹จ {blocked}/{len(attempts)} ({blocked / len(attempts):.0%}) (๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if blocked == len(attempts) else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# ---- RBAC ๋งคํŠธ๋ฆญ์Šค(์—ญํ• ๋ณ„ ์ ‘๊ทผ ์ œ์–ด ์‹œํ—˜) ----\n", "acts = [\"propose\", \"submit\", \"review_contract\", \"review_legal\", \"approve\", \"reject\", \"redraft\", \"audit\"]\n", "mx = pd.DataFrame([[(\"O\" if a in PERM[r] else \"-\") for a in acts] for r in PERM], index=list(PERM), columns=[\"์ œ์•ˆ\", \"์ƒ์‹ \", \"๊ณ„์•ฝ๊ด€๊ฒ€ํ† \", \"๋ฒ•๋ฌด๊ฒ€ํ† \", \"์Šน์ธ\", \"๋ฐ˜๋ ค\", \"์žฌ๊ธฐ์•ˆ\", \"๊ฐ์‚ฌ์กฐํšŒ\"])\n", "print(\"\\n[์—ญํ• ๋ณ„ ๊ถŒํ•œ ๋งคํŠธ๋ฆญ์Šค(O ํ—ˆ์šฉ / - ๋ถˆ๊ฐ€)]\")\n", "print(mx.to_string())\n", "print(\"์Šน์ธ(approve) ๊ถŒํ•œ์ด ์žˆ๋Š” ์—ญํ• :\", [r for r in PERM if \"approve\" in PERM[r]], \"-> ์‹œ์Šคํ…œยท๊ณ„์•ฝ๋‹ด๋‹นยท๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด์—๋Š” ์—†์Œ\")\n", "\n", "# ---- ์ด๋ ฅ ๋ฌด๊ฒฐ์„ฑ๊ณผ ์ด๋ ฅ ์กฐํšŒ ----\n", "bad = wf.audit.verify()\n", "print(f\"\\n๊ฐ์‚ฌ ๋กœ๊ทธ {len(wf.audit.log)}๊ฑด, ํ•ด์‹œ ์ฒด์ธ ๊ฒ€์ฆ: {'์ •์ƒ' if bad is None else f'seq {bad}์—์„œ ๋ถˆ์ผ์น˜'} -> ์ด๋ ฅ ๋ฌด๊ฒฐ์„ฑ {'100%' if bad is None else 'ํ›ผ์†'}(๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if bad is None else '๋ฏธ๋‹ฌ'})\")\n", "idx = 10\n", "saved = json.dumps(wf.audit.log[idx], ensure_ascii=False)\n", "wf.audit.log[idx][\"reason\"] = \"์‚ฌํ›„ ์ˆ˜์ •\"\n", "v = wf.audit.verify()\n", "print(f\"{idx}๋ฒˆ์งธ ๊ธฐ๋ก ์ž„์˜ ์ˆ˜์ • ํ›„ ๊ฒ€์ฆ: {'์œ„๋ณ€์กฐ ํƒ์ง€(seq ' + str(v) + ')' if v is not None else 'ํƒ์ง€ ์‹คํŒจ'}\")\n", "wf.audit.log[idx] = json.loads(saved)\n", "print(\"\\n[์ด๋ ฅ ์กฐํšŒ ํ™”๋ฉด] C02 ๊ฑด\")\n", "for e in wf.audit.log:\n", " if e.get(\"case\") == \"C02\":\n", " print(f\" #{e['seq']:03d} {e['event']:15s} {e.get('role', e['who']):6s} v{e.get('version', 1)} | {e.get('reason', '')[:34]}\")\n", "print(\"โ€ป ๊ณ„์•ฝ ์กฐํ•ญ์˜ ์ตœ์ข… ํŒ๋‹จ, ๋ฒ•๋ฌด ๊ฒ€ํ† , ๊ณ„์•ฝ ์Šน์ธ์€ ์‚ฌ๋žŒ(๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌดยท์Šน์ธ๊ถŒ์ž)์ด ํ•˜๋ฉฐ, ์‹œ์Šคํ…œ์€ ์œ„ํ—˜ ํ‘œ์‹œ์™€ ์ˆ˜์ • ๊ถŒ๊ณ ๋งŒ ์ œ๊ณตํ•œ๋‹ค. ๋ณด์•ˆ ๊ทœ์ •๊ณผ ๊ฒฐ์žฌ์„ ์€ ์†Œ์† ๊ธฐ๊ด€ ๊ทœ์ •์„ ๋”ฐ๋ฅธ๋‹ค.\")\n", "\n", "plt.figure(figsize=(6, 3.5)); EN = {\"์Šน์ธ\": \"approved\", \"๋ฐ˜๋ ค\": \"rejected\", \"์ข…๊ฒฐ(๋ฐ˜๋ ค)\": \"closed-rejected\", \"๋ณด๋ฅ˜(๋ฌด์‘๋‹ต)\": \"on hold\"}\n", "plt.bar([EN.get(k, k) for k in st], list(st.values()), color=\"tab:blue\"); plt.title(\"HITL workflow outcomes (12 cases)\"); plt.ylabel(\"cases\"); plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 07 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ฒฐ์žฌ ๊ฒฐ๊ณผ] 12๊ฑด (๋ชจ์˜ ๊ฒฐ์žฌ์ž ๊ธฐ์ค€, ์‹ค์ œ ๊ฒฐ์žฌ๋Š” ์‚ฌ๋žŒ์ด ํ•œ๋‹ค)\n", " ๊ฑด ๊ณ„์•ฝ ์ตœ์ข… ์ƒํƒœ ๋ฒ„์ „ ๋น„๊ณ \n", "C01 C-0072 ๋ณด๋ฅ˜(๋ฌด์‘๋‹ต) 2 ์ž๋™ ํ™•์ • ์—†์Œ: ๋ฒ•๋ฌด ์‘๋‹ต ๋Œ€๊ธฐ\n", "C02 C-0099 ์Šน์ธ 3 ์Šน์ธ๊ถŒ์ž ๊ฒฐ์ •\n", "C03 C-0024 ์Šน์ธ 3 ์Šน์ธ๊ถŒ์ž ๊ฒฐ์ •\n", "C04 C-0067 ์Šน์ธ 2 ์Šน์ธ๊ถŒ์ž ๊ฒฐ์ •\n", "C05 C-0085 ์Šน์ธ 2 ์Šน์ธ๊ถŒ์ž ๊ฒฐ์ •\n", "C06 C-0022 ์Šน์ธ 1 ์Šน์ธ๊ถŒ์ž ๊ฒฐ์ •\n", "C07 C-0077 ์Šน์ธ 2 ์Šน์ธ๊ถŒ์ž ๊ฒฐ์ •\n", "C08 C-0040 ์Šน์ธ 2 ์Šน์ธ๊ถŒ์ž ๊ฒฐ์ •\n", "C09 C-0070 ์Šน์ธ 2 ์Šน์ธ๊ถŒ์ž ๊ฒฐ์ •\n", "C10 C-0065 ์Šน์ธ 1 ์Šน์ธ๊ถŒ์ž ๊ฒฐ์ •\n", "C11 C-0028 ์Šน์ธ 2 ์Šน์ธ๊ถŒ์ž ๊ฒฐ์ •\n", "C12 C-0007 ์ข…๊ฒฐ(๋ฐ˜๋ ค) 3 ์žฌ๊ธฐ์•ˆ ํ•œ๋„(2ํšŒ) ์ดˆ๊ณผ\n", "์ƒํƒœ ์ง‘๊ณ„: {'์Šน์ธ': 10, '๋ณด๋ฅ˜(๋ฌด์‘๋‹ต)': 1, '์ข…๊ฒฐ(๋ฐ˜๋ ค)': 1} / ๋ฐ˜๋ ค ํ›„ ์žฌ๊ธฐ์•ˆ 13ํšŒ, ๋ฐ˜๋ ค ์ด๋ ฅ 14๊ฑด(๋ชจ๋‘ ์‚ฌ์œ ยท๊ทผ๊ฑฐ ๊ธฐ๋ก)\n", "\n", "[๊ถŒํ•œ ์šฐํšŒยท์ ˆ์ฐจ ์œ„๋ฐ˜ ์‹œ๋„]\n", " ์‹œ์Šคํ…œ ์ž๋™ ์Šน์ธ -> ์ฐจ๋‹จ: ๊ถŒํ•œ ์—†์Œ(์‹œ์Šคํ…œ ์—ญํ• ์— approve ๊ถŒํ•œ ์—†์Œ)\n", " ๊ณ„์•ฝ๋‹ด๋‹น์ด ๊ณ„์•ฝ๊ด€ ๊ฒ€ํ†  ๋Œ€ํ–‰ -> ์ฐจ๋‹จ: ๊ถŒํ•œ ์—†์Œ(๊ณ„์•ฝ๋‹ด๋‹น ์—ญํ• ์— review_contract ๊ถŒํ•œ ์—†์Œ)\n", " ๊ฒ€ํ†  ๋‹จ๊ณ„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ์Šน์ธ -> ์ฐจ๋‹จ: ๋‹จ๊ณ„ ์œ„๋ฐ˜(ํ˜„์žฌ ์ƒ์‹ , ํ•„์š” ํ–‰์œ„ review_contract)\n", " ๋ฏธ๋“ฑ๋ก ์‚ฌ์šฉ์ž ์ ‘๊ทผ -> ์ฐจ๋‹จ: ๋ฏธ๋“ฑ๋ก ์‚ฌ์šฉ์ž\n", " ์‚ฌ์œ  ์—†๋Š” ๊ฒ€ํ†  -> ์ฐจ๋‹จ: ์‚ฌ์œ ยท๊ทผ๊ฑฐ ์ž…๋ ฅ ํ•„์ˆ˜(๋ฐ˜๋ ค ์‚ฌ์œ  ํฌํ•จ)\n", " ์‚ฌ์œ  ์—†๋Š” ๋ฐ˜๋ ค -> ์ฐจ๋‹จ: ์‚ฌ์œ ยท๊ทผ๊ฑฐ ์ž…๋ ฅ ํ•„์ˆ˜(๋ฐ˜๋ ค ์‚ฌ์œ  ํฌํ•จ)\n", " ๊ฐ์‚ฌ ๋กœ๊ทธ ์‚ญ์ œ -> ์ฐจ๋‹จ: ๊ฐ์‚ฌ ๋กœ๊ทธ ์‚ญ์ œยท์ˆ˜์ • ๊ถŒํ•œ์€ ๋ˆ„๊ตฌ์—๊ฒŒ๋„ ์—†์Œ\n", " ๊ฐ์‚ฌ๊ด€์ด ์Šน์ธ ์‹œ๋„ -> ์ฐจ๋‹จ: ๊ถŒํ•œ ์—†์Œ(๊ฐ์‚ฌ๊ด€ ์—ญํ• ์— approve ๊ถŒํ•œ ์—†์Œ)\n", " ์ข…๊ฒฐ๋œ ๊ฑด ์žฌ์Šน์ธ -> ์ฐจ๋‹จ: ์ด๋ฏธ ์ข…๊ฒฐ๋œ ๊ฑด(์Šน์ธ)\n", " ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฑด ์Šน์ธ -> ์ฐจ๋‹จ: ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฑด\n", " ์ƒ์‹ ์ž๊ฐ€ ๋ณธ์ธ ๊ฑด ์Šน์ธ(๋ถ„๋ฆฌ ์›์น™) -> ์ฐจ๋‹จ: ์ƒ์‹ ์ž์™€ ์Šน์ธ์ž ๋ถ„๋ฆฌ ์›์น™ ์œ„๋ฐ˜\n", " ๋ฐ˜๋ ค ๊ฑด์„ ์žฌ๊ธฐ์•ˆ ์—†์ด ๊ฒ€ํ†  ์ง„ํ–‰ -> ์ฐจ๋‹จ: ๋ฐ˜๋ ค๋œ ๊ฑด์€ ์žฌ๊ธฐ์•ˆ ํ›„์—๋งŒ ์ง„ํ–‰ ๊ฐ€๋Šฅ\n", " ํƒ€์ธ์ด ๋Œ€์‹  ์žฌ๊ธฐ์•ˆ -> ์ฐจ๋‹จ: ๊ถŒํ•œ ์—†์Œ(๋ถ€์„œ์žฅ ์—ญํ• ์— redraft ๊ถŒํ•œ ์—†์Œ)\n", "์šฐํšŒยท์œ„๋ฐ˜ ์‹œ๋„ ์ฐจ๋‹จ 13/13 (100%) (๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ)\n", "\n", "[์—ญํ• ๋ณ„ ๊ถŒํ•œ ๋งคํŠธ๋ฆญ์Šค(O ํ—ˆ์šฉ / - ๋ถˆ๊ฐ€)]\n", " ์ œ์•ˆ ์ƒ์‹  ๊ณ„์•ฝ๊ด€๊ฒ€ํ†  ๋ฒ•๋ฌด๊ฒ€ํ†  ์Šน์ธ ๋ฐ˜๋ ค ์žฌ๊ธฐ์•ˆ ๊ฐ์‚ฌ์กฐํšŒ\n", "์‹œ์Šคํ…œ O - - - - - - -\n", "๊ณ„์•ฝ๋‹ด๋‹น - O - - - - O -\n", "๊ณ„์•ฝ๊ด€ - - O - - O - -\n", "๋ฒ•๋ฌด - - - O - O - -\n", "์Šน์ธ๊ถŒ์ž - - - - O O - -\n", "๊ฐ์‚ฌ๊ด€ - - - - - - - O\n", "... (์ดํ•˜ ์ƒ๋žต)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 08. 1,000๊ฑด ๋Œ€๊ทœ๋ชจ ์กฐ๋‹ฌ ๊ณ„์•ฝ์„œ ์ผ๊ด„ ์Šคํฌ๋ฆฌ๋‹ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋ฐ ์ฒ˜๋ฆฌ ์†๋„ ๊ฒ€์ฆ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋Œ€๊ทœ๋ชจ ์กฐ๋‹ฌ ๊ณ„์•ฝ์„œ ์ผ๊ด„ ์Šคํฌ๋ฆฌ๋‹ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์›” 1,000๊ฑด ๊ทœ๋ชจ์˜ ๊ณ„์•ฝ์„œ๋ฅผ ์ผ๊ด„ ์Šคํฌ๋ฆฌ๋‹ํ•˜๋Š” ๋ฐฐ์น˜ ํŒŒ์ดํ”„๋ผ์ธ์˜ ๋ถ€ํ•˜ ์‹œํ—˜๊ณผ ์ฒ˜๋ฆฌ ์†๋„ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์›” 1,000๊ฑด์”ฉ 12๊ฐœ์›”๋ถ„ ๊ฐ€์ƒ ๊ณ„์•ฝ ๋ฐฐ์น˜๋ฅผ ๊ฐ„์ด ์Šค์ผ€์ค„๋Ÿฌ๋กœ ์ž๋™ ์‹คํ–‰ํ•˜๋ฉด์„œ ์›”๋งˆ๋‹ค ์†์ƒ ๋ ˆ์ฝ”๋“œ 5๊ฑด๊ณผ 2% ์ผ์‹œ ์˜ค๋ฅ˜ ์ฃผ์ž…ํ•˜์—ฌ ์ฒ˜๋ฆฌ ์ง€์—ฐ ๋ฐ ๋ณต์›๋ ฅ์„ ์‹œํ—˜ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/pipeline/prc01_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": [ "\"\"\"1,000๊ฑด ๊ทœ๋ชจ์˜ ์›”๊ฐ„ ๊ณ„์•ฝ์„œ ์ผ๊ด„ ์Šคํฌ๋ฆฌ๋‹ ๋ฐฐ์น˜(์ž…๋ ฅ ๊ฒ€์ฆ, ๋ฃฐ 16์ข…, ์กฐํ•ญ ๋ถ„๋ฅ˜, ์œ„ํ—˜ ์ ์ˆ˜)๋ฅผ 12๊ฐœ์›”๋ถ„ ์Šค์ผ€์ค„๋Ÿฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์œผ๋กœ ์‹คํ–‰ํ•˜๊ณ , ์†์ƒ ๋ ˆ์ฝ”๋“œยท์ผ์‹œ ์˜ค๋ฅ˜๋ฅผ ์ฃผ์ž…ํ•œ ์ƒํƒœ์—์„œ ์‹คํ–‰ ์‹œ๊ฐ„(5๋ถ„ ์ด๋‚ด)ยท์ž๋™ํ™” ์„ฑ๊ณต๋ฅ (99% ์ด์ƒ)ยท์ˆ˜์ž‘์—… ๋Œ€๋น„ ์‹œ๊ฐ„ ์ ˆ๊ฐ(80% ์ด์ƒ)ยท์ง„ํ–‰๋ฅ  ํ‘œ์‹œยท์™„๋ฃŒ ์•Œ๋ฆผยท์‹œ๊ฐ„ ์ดˆ๊ณผ ์‹œ ๊ธฐ๋Šฅ ์ถ•์†Œ๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์กฐ๋‹ฌ๊ณ„ํš์•ˆยท๊ณ„์•ฝ์„œ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ณ„์•ฝยท์—…์ฒดยท๋ฒ•๋ น ์กฐ๋ฌธ ์—†์Œ)\n", "# ๊ณ„์•ฝ ์†์„ฑ 16๊ฐœ + ์กฐํ•ญ ๋ฌธ์žฅ 6๊ฐœ๋ฅผ ๋งŒ๋“ค๊ณ , ์†์„ฑ ์œ„๋ฐ˜ยท๋…์†Œ ์กฐํ•ญยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ์„ž์ธ '๊ทœ์ • ์œ„๋ฐ˜ ์—ฌ๋ถ€'๋ฅผ ํ•จ๊ป˜ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "TYPES = [\"๋ฌผํ’ˆ\", \"์šฉ์—ญ\", \"๊ณต์‚ฌ\"]\n", "METHODS = [\"์ผ๋ฐ˜๊ฒฝ์Ÿ\", \"์ œํ•œ๊ฒฝ์Ÿ\", \"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]\n", "CLASSES = [\"์ •์ƒ\", \"๋…์†Œ\", \"๋‚ฉ๊ธฐ์œ„ํ—˜\"]\n", "OBJ = [\"๋‚ฉํ’ˆ ๋ฌผํ’ˆ\", \"์šฉ์—ญ ๊ฒฐ๊ณผ๋ฌผ\", \"์‹œ๊ณต ๋ชฉ์ ๋ฌผ\", \"์ •๋น„ ์„ฑ๊ณผ๋ฌผ\"]\n", "\n", "N_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋‚ฉ๊ธฐ๋ฅผ ์ง€์ฒดํ•œ ๊ฒฝ์šฐ ์ง€์ฒด์ผ์ˆ˜ 1์ผ๋งˆ๋‹ค ๊ณ„์•ฝ๊ธˆ์•ก์˜ {p}/1000์— ํ•ด๋‹นํ•˜๋Š” ์ง€์ฒด์ƒ๊ธˆ์„ ๋ถ€๊ณผํ•œ๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๊ฐ€ ํ™•์ธ๋œ ๊ฒฝ์šฐ์— ํ•œํ•˜์—ฌ ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์Šน์ธ์„ ๋ฐ›์•„ ๋ฉด์ œํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ ์—ฐ์žฅ์€ ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๋ฅผ ํ™•์ธํ•˜๊ณ  ์„œ๋ฉด์œผ๋กœ ์Šน์ธํ•œ ๊ฒฝ์šฐ์—๋งŒ ์ธ์ •ํ•œ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋‚ฉํ’ˆ๋œ {o}์„ {d}์ผ ์ด๋‚ด์— ๊ฒ€์‚ฌํ•˜๊ณ  ํ•ฉ๊ฒฉํ•œ ํ›„์— ๋Œ€๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ํ•˜๋„๊ธ‰์„ ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ์‚ฌ์ „์— ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์„œ๋ฉด ์Šน์ธ์„ ๋ฐ›์•„์•ผ ํ•œ๋‹ค.\",\n", " \"์–ด๋А ํ•œ์ชฝ์ด ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•˜๋ ค๋ฉด ์‚ฌ์œ ๋ฅผ ๋ช…์‹œํ•ด {d}์ผ ์ „์— ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"{o}์— ํ•˜์ž๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ๋ณด์ฆ ๊ธฐ๊ฐ„ ์•ˆ์— ๋ฌด์ƒ์œผ๋กœ ๋ณด์ˆ˜ํ•˜๊ฑฐ๋‚˜ ๊ต์ฒดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ๋น„๊ณต๊ฐœ ์‚ฌํ•ญ์€ ์ œ3์ž์—๊ฒŒ ๋ˆ„์„คํ•˜์ง€ ์•Š์œผ๋ฉฐ ์œ„๋ฐ˜ ์‹œ ์ฑ…์ž„์„ ์ง„๋‹ค.\",\n", " \"{o}์— ๋Œ€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์˜ ๊ท€์†๊ณผ ์‚ฌ์šฉ ๋ฒ”์œ„๋Š” ๋ณ„๋„ ํ•ฉ์˜์„œ์— ๋”ฐ๋ผ ์ •ํ•œ๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ํ•ฉ๊ฒฉ ํ›„ ์ฒญ๊ตฌ์„œ๋ฅผ ์ ‘์ˆ˜ํ•œ ๋‚ ๋ถ€ํ„ฐ {d}์ผ ์ด๋‚ด์— ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ๊ธˆ์•ก์˜ ์กฐ์ •์€ ๋ฌผ๊ฐ€๋ณ€๋™ ๊ธฐ์ค€์— ๋”ฐ๋ผ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ์„œ๋ฉด์œผ๋กœ ํ•ฉ์˜ํ•œ ๊ฒฝ์šฐ์—๋งŒ ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ดํ–‰๋ณด์ฆ์„œ๋ฅผ ๊ณ„์•ฝ ์ฒด๊ฒฐ ์‹œ ์ œ์ถœํ•˜๋ฉฐ ๋ณด์ฆ๊ธˆ์€ ๊ณ„์•ฝ ์ดํ–‰ ์™„๋ฃŒ ํ›„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ณ„์•ฝ์„œ ๋ณ„ํ‘œ์˜ ์ผ์ž๋ฅผ ๋”ฐ๋ฅด๋ฉฐ ๋ณ€๊ฒฝ ์‹œ ์„œ๋ฉด ํ•ฉ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผํ•˜๋ฉฐ ์ด์˜๋Š” ์ •ํ•ด์ง„ ์ ˆ์ฐจ๋กœ ์ œ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•˜์ง€ ๋ชปํ•œ {o}์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๊ต์ฒดํ•˜๋ฉฐ ๊ทธ ๋น„์šฉ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ๊ท€์ฑ… ์‚ฌ์œ ๋กœ ์ดํ–‰์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•˜๊ณ  ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"์ง€์—ฐ์ด ์˜ˆ์ƒ๋˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ๋ฐœ์ฃผ๊ธฐ๊ด€์— ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•œ๋‹ค.\",\n", " \"๋ณด์ฆ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง€๊ณ  ๋ณด์ˆ˜ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์€ ๋จผ์ € ํ˜‘์˜๋กœ ํ•ด๊ฒฐํ•˜๊ณ  ํ˜‘์˜๊ฐ€ ๋˜์ง€ ์•Š์œผ๋ฉด ๊ด€ํ•  ๋ฒ•์›์˜ ํŒ๋‹จ์— ๋”ฐ๋ฅธ๋‹ค.\",\n", "]\n", "T_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ง€์ฒด์ƒ๊ธˆ์€ ์ „์•ก ๋ฉด์ œํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋Œ€ํ•ด ์ด์˜๋ฅผ ์ œ๊ธฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ฆ‰์‹œ ๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ ๋ณด๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ดํ›„ ๊ฒ€์‚ฌ๋ฅผ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์–ธ์ œ๋“ ์ง€ ์ผ๋ฐฉ์ ์œผ๋กœ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ์— ๋”ฐ๋ฅธ ์†ํ•ด๋Š” ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"{o}์˜ ์ถ”๊ฐ€ ๋น„์šฉ์€ ์‚ฌ์œ ๋ฅผ ๋ถˆ๋ฌธํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ์ „์•ก ๋ถ€๋‹ดํ•˜๋ฉฐ ๋ณ„๋„ ํ˜‘์˜ ์—†์ด ์ฒญ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"{o}์— ๊ด€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์€ ๋ชจ๋‘ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ๊ท€์†๋˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"ํ•˜๋„๊ธ‰์€ ์‹ ๊ณ  ์—†์ด ์ œํ•œ ์—†์ด ํ—ˆ์šฉํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ํ•˜๋„๊ธ‰ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ์ „์— ์ „์•ก ์„ ์ง€๊ธ‰ํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ฐ˜ํ™˜ ์ฒญ๊ตฌ๊ถŒ์„ ํฌ๊ธฐํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ์‚ฌํ•ญ์˜ ๋ˆ„์„ค๋กœ ์ธํ•œ ์ฑ…์ž„์„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"ํ•˜์ž ๋ณด์ˆ˜ ์ฑ…์ž„์€ ์ธ๋„ ํ›„ ์ฆ‰์‹œ ์†Œ๋ฉธํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ณด์ˆ˜๋ฅผ ์š”๊ตฌํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฒญ๊ตฌํ•˜๋Š” ๊ธˆ์•ก์„ ๊ฑฐ์ ˆํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ๊ฑฐ์ ˆํ•˜๋ฉด ์œ„์•ฝ๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์ด ์ƒ๊ธฐ๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ •ํ•˜๋Š” ์ค‘์žฌ ๊ธฐ๊ด€์—์„œ๋งŒ ํ•ด๊ฒฐํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์‚ฌ๋Š” ์ƒ๋žตํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์‹œํ—˜ ๊ฒฐ๊ณผ์„œ๋กœ ๋Œ€์‹ ํ•œ๋‹ค.\",\n", " \"๊ฒฌ์  ๊ธˆ์•ก์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ๊ทธ๋Œ€๋กœ ํ™•์ •ํ•˜๊ณ  ์ฆ๋น™ ์ž๋ฃŒ๋Š” ๋ฐ›์ง€ ์•Š๋Š”๋‹ค.\",\n", "]\n", "D_TPL = [\n", " \"๋‚ฉ๊ธฐ๋Š” ์ž์žฌ ์ˆ˜๊ธ‰ ์‚ฌ์ •์— ๋”ฐ๋ผ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ฐ€๋Šฅํ•œ ํ•œ ์ง€ํ‚ค๋„๋ก ๋…ธ๋ ฅํ•˜๋ฉฐ ์ง€์—ฐ๋˜์–ด๋„ ์‚ฌ์œ ๋ฅผ ๋ณด๊ณ ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณต๊ธ‰ ์ง€์—ฐ์ด๋‚˜ ์„ ํ–‰ ์—…์ฒด ์‚ฌ์ •๋„ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ์œผ๋กœ ๋ณด์•„ ๋‚ฉ๊ธฐ๋ฅผ ์ž๋™ ์—ฐ์žฅํ•œ๋‹ค.\",\n", " \"์ฐฉ์ˆ˜์ผ์€ ์„ ํ–‰ ์—…์ฒด์˜ ๋‚ฉํ’ˆ์ด ๋๋‚œ ๋’ค ๋ณ„๋„๋กœ ์ •ํ•˜๋ฉฐ ์™„๋ฃŒ ๊ธฐํ•œ์€ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ์ผ ์ดํ›„ 1ํšŒ์— ํ•œํ•ด ๊ธฐํ•œ ์—†์ด ์ž๋™ ์—ฐ์žฅ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํ•œ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๋Š” ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ƒ์‚ฐ ์ผ์ •์ด ํ™•์ •๋˜๋Š” ๋Œ€๋กœ ํ†ต๋ณดํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์‹œ์ ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž ์‚ฌ์ •์ด ํ—ˆ๋ฝํ•˜๋Š” ๋•Œ๋กœ ํ•˜๊ณ  ๋‚ ์งœ๋Š” ๋”ฐ๋กœ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ž์žฌ๊ฐ€ ๋Šฆ๊ฒŒ ๋“ค์–ด์˜ค๋ฉด ์™„๋ฃŒ ์ผ์ •์€ ๊ทธ๋งŒํผ ์ž๋™์œผ๋กœ ๋Šฆ์ถฐ์ง„๋‹ค.\",\n", " \"๊ธฐํ•œ ๋‚ด์— ์ธ๋„ํ•˜์ง€ ๋ชปํ•ด๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ •์ด ๋Šฆ์–ด์ ธ๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ƒˆ ๊ณ„ํš์„œ๋ฅผ ๋‚ผ ํ•„์š”๊ฐ€ ์—†๋‹ค.\",\n", "]\n", "# ํ•™์Šต์— ์“ฐ์ง€ ์•Š๋Š” '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'(๋ชจ๋ธ์ด ๋ฌธ์žฅ ํ‹€์„ ์™ธ์› ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์šฉ๋„)\n", "HOLD = {\n", " \"์ •์ƒ\": [\"๊ฒ€์‚ฌ์— ๋ถˆํ•ฉ๊ฒฉํ•œ ๊ฒฝ์šฐ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ • ๊ธฐํ•œ ์•ˆ์— ์žฌ๋‚ฉํ’ˆํ•˜๋ฉฐ ์žฌ๊ฒ€์‚ฌ ๋น„์šฉ์€ ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ฒ•๋ น์„ ์œ„๋ฐ˜ํ•˜์—ฌ ๊ณ„์•ฝ์„ ์ดํ–‰ํ•˜์ง€ ๋ชปํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด ํ†ต์ง€ ํ›„ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๊ฐ€ ์ง€์—ฐ๋  ์šฐ๋ ค๊ฐ€ ์žˆ์œผ๋ฉด ์ƒ๋Œ€์ž๋Š” ์ฆ‰์‹œ ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"ํ•˜์ž๋‹ด๋ณด ์ฑ…์ž„ ๊ธฐ๊ฐ„์€ ์ธ์ˆ˜์ผ๋ถ€ํ„ฐ ๊ธฐ์‚ฐํ•˜๋ฉฐ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง„๋‹ค.\"],\n", " \"๋…์†Œ\": [\"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๋Œ€๊ธˆ์„ ์ด์œ  ์—†์ด ๊ฑฐ์ ˆํ•˜์ง€ ๋ชปํ•˜๋ฉฐ ๊ฑฐ์ ˆ ์‹œ ์œ„์•ฝ๊ธˆ์„ ๋‚ธ๋‹ค.\",\n", " \"๊ณ„์•ฝ ๋‚ด์šฉ์— ๊ด€ํ•œ ๋ถ„์Ÿ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€์—์„œ๋งŒ ๋‹ค๋ฃจ๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์ˆ˜ ๊ถŒํ•œ์€ ํฌ๊ธฐํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ž์ฒด ์‹œํ—˜ ์„ฑ์ ์„œ๋กœ ๊ฐˆ์Œํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ œ์ถœํ•œ ๊ฒฌ์ ์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ํ™•์ • ๋‹จ๊ฐ€๋กœ ํ•˜๋ฉฐ ์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"๋‚ฉํ’ˆ ์‹œ๊ธฐ๋Š” ์ƒ๋Œ€์ž์˜ ํ˜•ํŽธ์ด ํ—ˆ๋ฝํ•˜๋Š” ์‹œ์ ์œผ๋กœ ํ•˜๊ณ  ๋ณ„๋„ ๋‚ ์งœ๋Š” ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์›์ž์žฌ ํ™•๋ณด๊ฐ€ ๋Šฆ์–ด์ง€๋ฉด ์™„๋ฃŒ ์ผ์ž๋Š” ์ž๋™์œผ๋กœ ๋’ค๋กœ ๋ฏธ๋ค„์ง€๋Š” ๊ฒƒ์œผ๋กœ ๋ณธ๋‹ค.\",\n", " \"์ธ๋„ ๊ธฐํ•œ์€ ์ฐธ๊ณ ์šฉ์ด๋ฉฐ ์ง€ํ‚ค์ง€ ๋ชปํ•ด๋„ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ • ์ง€์—ฐ์ด ์žˆ์–ด๋„ ์ƒ๋Œ€์ž๋Š” ๋ณ€๊ฒฝ ๊ณ„ํš์„ ์ œ์ถœํ•  ์˜๋ฌด๊ฐ€ ์—†๋‹ค.\"],\n", "}\n", "SUFFIX = [\"\", \"\", \"\", \" ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ์‹œํ–‰ ์„ธ์น™์— ๋”ฐ๋ฅธ๋‹ค.\", \" ์ด ์กฐํ•ญ์€ ๊ณ„์•ฝ ์ „์ฒด์— ์ ์šฉํ•œ๋‹ค.\", \" ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ํ˜‘์˜ํ•œ๋‹ค.\", \" ๊ด€๋ จ ์„œ์‹์€ ๋ณ„์ง€๋ฅผ ๋”ฐ๋ฅธ๋‹ค.\"]\n", "SWAP = [(\"๋ฐœ์ฃผ๊ธฐ๊ด€\", \"๊ณ„์•ฝ๋‹ด๋‹น๊ด€\"), (\"๊ณ„์•ฝ์ƒ๋Œ€์ž\", \"์—…์ฒด\")]\n", "\n", "\n", "def make_clause(rng, cls, hold=False, idx=None):\n", " \"\"\"์กฐํ•ญ ํ•œ ๋ฌธ์žฅ ์ƒ์„ฑ. ํ‘œํ˜„ ์ผ๋ถ€๋Š” ๋™์˜์–ด๋กœ ๋ฐ”๊พธ๊ณ  ์ ‘๋ฏธ ๋ฌธ๊ตฌ๋ฅผ ๋ฌด์ž‘์œ„๋กœ ๋ถ™์ธ๋‹ค(idx๋ฅผ ์ฃผ๋ฉด ๊ทธ ๋ฒˆํ˜ธ์˜ ๋ฌธ์žฅ ํ‹€์„ ์‚ฌ์šฉ).\"\"\"\n", " if idx is not None:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[idx].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " elif hold:\n", " s = HOLD[cls][rng.integers(len(HOLD[cls]))]\n", " else:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[rng.integers(len(tpl))].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " if rng.random() < 0.3:\n", " a, b = SWAP[rng.integers(2)]\n", " s = s.replace(a, b)\n", " return s + SUFFIX[rng.integers(len(SUFFIX))]\n", "\n", "\n", "def make_contracts(n=200, seed=7):\n", " \"\"\"๊ฐ€์ƒ ๊ณ„์•ฝ n๊ฑด(์†์„ฑ 16๊ฐœ + ์กฐํ•ญ 6๊ฐœ + ๊ทœ์ • ์œ„๋ฐ˜ ์ •๋‹ต)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " sl = rng.beta(1.2, 3.2, n) # ๊ณ„์•ฝ์„œ ์ž‘์„ฑ์ด ํ—ˆ์ˆ ํ•œ ์ •๋„(์ž ์žฌ ๋ณ€์ˆ˜)\n", " ctype = rng.choice(TYPES, n, p=[.5, .3, .2])\n", " amount = np.clip(np.exp(rng.normal(np.log(220), 1.1, n)), 5, 5000).round(0)\n", " method = np.where(rng.random(n) < .06 + .3 * sl, \"์ˆ˜์˜๊ณ„์•ฝ\", rng.choice(METHODS[:3], n, p=[.5, .3, .2]))\n", " period = np.array([rng.integers(60, 300) if t == \"๋ฌผํ’ˆ\" else rng.integers(90, 400) if t == \"์šฉ์—ญ\" else rng.integers(180, 720) for t in ctype])\n", " delivery = (period * rng.uniform(.5, 1.0, n)).astype(int)\n", " bad_logic = rng.random(n) < .015 * (1 + 3 * sl)\n", " delivery = np.where(bad_logic, period + rng.integers(10, 60, n), delivery)\n", " pen = np.where(rng.random(n) < .22 * sl, rng.choice([.3, .5, .8], n), rng.choice([1.0, 1.5, 2.0, 2.5], n))\n", " adv = np.where(rng.random(n) < .22 * sl, rng.choice([50, 60, 70], n), rng.choice([0, 10, 20, 30, 40], n))\n", " bond = np.where(rng.random(n) < .25 * sl + .5 * (adv > 40), rng.choice([0, 5], n), rng.choice([10, 10, 15, 20], n))\n", " war = np.array([rng.choice([3, 6]) if (rng.random() < .25 * s and t == \"๋ฌผํ’ˆ\") else rng.choice([12, 24, 36]) if t == \"๋ฌผํ’ˆ\" else rng.choice([0, 6, 12]) if t == \"์šฉ์—ญ\" else rng.choice([24, 36, 60]) for t, s in zip(ctype, sl)])\n", " insp = np.where(rng.random(n) < .3 * sl, rng.choice([21, 30], n), rng.choice([5, 7, 10, 14], n))\n", " sub = np.where(rng.random(n) < .15 * sl, rng.choice([60, 70, 80], n), rng.choice([0, 10, 20, 30, 40, 50], n))\n", " padj = rng.random(n) < .75 - .4 * sl\n", " term = rng.random(n) > .015 * (1 + 6 * sl)\n", " sec = rng.random(n) > .015 * (1 + 6 * sl)\n", " ip = rng.random(n) > .03 * (1 + 4 * sl)\n", " clauses, cls_true = [], []\n", " for i in range(n):\n", " pt, pd_ = .01 + .08 * sl[i], .02 + .08 * sl[i]\n", " cl = [rng.choice(CLASSES, p=[1 - pt - pd_, pt, pd_]) for _ in range(6)]\n", " cls_true.append(cl); clauses.append([make_clause(rng, c) for c in cl])\n", " df = pd.DataFrame({\"contract_id\": [f\"C-{i + 1:04d}\" for i in range(n)], \"ctype\": ctype, \"method\": method, \"amount_m\": amount,\n", " \"period_days\": period, \"delivery_days\": delivery, \"penalty_permil\": pen, \"advance_pct\": adv, \"perf_bond_pct\": bond,\n", " \"warranty_months\": war, \"inspection_days\": insp, \"subcontract_pct\": sub, \"price_adjust\": padj,\n", " \"termination\": term, \"security\": sec, \"ip_clause\": ip})\n", " hard = ((pen < 1.0) | ((adv > 40) & (bond < 10)) | ((amount >= 300) & (method == \"์ˆ˜์˜๊ณ„์•ฝ\")) | ((ctype == \"๋ฌผํ’ˆ\") & (war < 12))\n", " | ~term | ~sec | (sub > 50) | (delivery > period))\n", " toxic = np.array([c.count(\"๋…์†Œ\") for c in cls_true])\n", " delay = np.array([c.count(\"๋‚ฉ๊ธฐ์œ„ํ—˜\") for c in cls_true])\n", " split = (method == \"์ˆ˜์˜๊ณ„์•ฝ\") & (amount >= 250) & (amount < 300) & (rng.random(n) < .6) # ๊ณ„์•ฝ ์ชผ๊ฐœ๊ธฐ ์˜์‹ฌ(์ž„๊ณ„๊ฐ’ ๋ฐ”๋กœ ์•„๋ž˜)\n", " df[\"clauses\"], df[\"clause_cls\"] = clauses, cls_true\n", " df[\"n_toxic\"], df[\"n_delay\"] = toxic, delay\n", " viol = hard | (toxic > 0) | split | (delay >= 2)\n", " waived = hard & (toxic == 0) & (rng.random(n) < .12) # ๊ณ„์•ฝ๊ด€์ด ์˜ˆ์™ธ ์‚ฌ์œ ๋ฅผ ์ธ์ •ํ•ด ์œ„๋ฐ˜์œผ๋กœ ๋ณด์ง€ ์•Š์€ ๊ฑด(์ž๋ฃŒ์˜ ํ˜„์‹ค์  ์žก์Œ)\n", " hidden = ~viol & (rng.random(n) < .03 + .08 * sl) # ์†์„ฑยท๋ฌธ๊ตฌ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜(๋ณ„์ฒจ ํ•ฉ์˜ ๋“ฑ)\n", " df[\"violation\"] = (viol & ~waived) | hidden\n", " df[\"vtype\"] = np.where(hidden, \"๊ธฐํƒ€\", np.where(hard, \"์†์„ฑ์œ„๋ฐ˜\", np.where(toxic > 0, \"๋…์†Œ์กฐํ•ญ\", np.where(split | (delay >= 2), \"๊ธฐํƒ€\", \"-\"))))\n", " return df\n", "\n", "# ---- ๊ณ„์•ฝ ๊ทœ์ • ์ค€์ˆ˜ ํŒ๋ณ„ ๋ฃฐ ์„ค์ •(YAML ํ˜•์‹ ๋ฌธ์ž์—ด: ๊ทœ์ •์ด ๋ฐ”๋€Œ๋ฉด ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ด ์„ค์ •๋งŒ ๊ณ ์นœ๋‹ค) ----\n", "RULES_YAML = \"\"\"\n", "- id: R01\n", " name: ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.0/1000 ๋ฏธ๋งŒ\n", " field: penalty_permil\n", " op: \"<\"\n", " value: 1.0\n", " weight: 3\n", "- id: R02\n", " name: ์„ ๊ธˆ ๋น„์œจ 40% ์ดˆ๊ณผ\n", " field: advance_pct\n", " op: \">\"\n", " value: 40\n", " weight: 2\n", "- id: R03\n", " name: ์„ ๊ธˆ 40% ์ดˆ๊ณผ์ธ๋ฐ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: adv_no_bond\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R04\n", " name: 300๋ฐฑ๋งŒ์› ์ด์ƒ ์ˆ˜์˜๊ณ„์•ฝ\n", " field: sole_amount\n", " op: \">=\"\n", " value: 300\n", " weight: 3\n", "- id: R05\n", " name: 500๋ฐฑ๋งŒ์› ์ด์ƒ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: bond_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R06\n", " name: ๋ฌผํ’ˆ ํ•˜์ž๋ณด์ฆ 12๊ฐœ์›” ๋ฏธ๋งŒ\n", " field: warranty_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R07\n", " name: ํ•ด์ง€ ์กฐํ•ญ ์—†์Œ\n", " field: termination\n", " op: \"==\"\n", " value: false\n", " weight: 2\n", "- id: R08\n", " name: ๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ\n", " field: security\n", " op: \"==\"\n", " value: false\n", " weight: 3\n", "- id: R09\n", " name: ์ง€์‹์žฌ์‚ฐ ์กฐํ•ญ ์—†์Œ(๋ฌผํ’ˆยท์šฉ์—ญ)\n", " field: ip_missing\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R10\n", " name: ํ•˜๋„๊ธ‰ ๋น„์œจ 50% ์ดˆ๊ณผ\n", " field: subcontract_pct\n", " op: \">\"\n", " value: 50\n", " weight: 3\n", "- id: R11\n", " name: ๊ฒ€์‚ฌ ๊ธฐ๊ฐ„ 14์ผ ์ดˆ๊ณผ\n", " field: inspection_days\n", " op: \">\"\n", " value: 14\n", " weight: 1\n", "- id: R12\n", " name: ๋‚ฉํ’ˆ ๊ธฐํ•œ์ด ๊ณ„์•ฝ ๊ธฐ๊ฐ„ ์ดˆ๊ณผ\n", " field: logic_err\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R13\n", " name: 180์ผ ์ด์ƒ ๊ณ„์•ฝ์— ๋ฌผ๊ฐ€๋ณ€๋™ ์กฐํ•ญ ์—†์Œ\n", " field: no_padj_long\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R14\n", " name: 1000๋ฐฑ๋งŒ์› ์ด์ƒ ๋น„๊ฒฝ์Ÿ ๊ณ„์•ฝ\n", " field: noncomp_big\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R15\n", " name: ๊ณ„์•ฝ ๊ธฐ๊ฐ„ 365์ผ ์ดˆ๊ณผ\n", " field: period_days\n", " op: \">\"\n", " value: 365\n", " weight: 1\n", "- id: R16\n", " name: ๋ถˆ๊ณต์ •ยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ ํฌํ•จ\n", " field: kw_hits\n", " op: \">=\"\n", " value: 1\n", " weight: 3\n", "\"\"\"\n", "GRADE_CUT = {\"๊ณ ์œ„ํ—˜\": 6, \"์ฃผ์˜\": 3} # ๊ฐ€์ค‘์น˜ ํ•ฉ ๊ธฐ์ค€ 1์ฐจ ํŒ๋ณ„ ๋“ฑ๊ธ‰(ํ•„์š” ์‹œ ์„ค์ •๋งŒ ๋ฐ”๊พผ๋‹ค)\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถˆ๊ณต์ • ์กฐํ•ญยท๋‚ฉ๊ธฐ ์ง€์ฒด ์œ„ํ—˜ ๋ฌธ๊ตฌ ์‚ฌ์ „(์‹ค์ œ ๊ทœ์ • ์กฐ๋ฌธ์ด ์•„๋‹˜)\n", "KEYWORDS = {\"๋…์†Œ\": [\"๋ฉด์ œํ•˜๋ฉฐ\", \"์ผ๋ฐฉ์ ์œผ๋กœ\", \"ํฌ๊ธฐ\", \"์ง€์ง€ ์•Š๋Š”๋‹ค\", \"์ฆ‰์‹œ ์†Œ๋ฉธ\", \"์ œํ•œ ์—†์ด\", \"์‹ ๊ณ  ์—†์ด\", \"์ „์•ก ๋ถ€๋‹ด\", \"๋ณ„๋„ ํ˜‘์˜ ์—†์ด\", \"๊ฒ€์‚ฌ ์ „์—\", \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ\", \"์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค\", \"์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์ž๋™ ์—ฐ์žฅ\", \"๊ธฐํ•œ ์—†์ด\", \"์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"๋…ธ๋ ฅํ•˜๋ฉฐ\", \"์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค\", \"ํ†ต๋ณดํ•œ๋‹ค\", \"์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค\", \"์˜๋ฌด๊ฐ€ ์—†๋‹ค\"]}\n", "\n", "\n", "def parse_rules(text):\n", " \"\"\"๊ฐ„์ด YAML ํŒŒ์„œ(๋ชฉ๋ก + key: value). ์ˆซ์žยทtrue/falseยท๋”ฐ์˜ดํ‘œ ๋ฌธ์ž์—ด์„ ํ•ด์„ํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] 1,000๊ฑด ๋Œ€๊ทœ๋ชจ ์กฐ๋‹ฌ ๊ณ„์•ฝ์„œ ์ผ๊ด„ ์Šคํฌ๋ฆฌ๋‹ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋ฐ ์ฒ˜๋ฆฌ ์†๋„ ๊ฒ€์ฆ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "OPS = {\"==\": lambda a, b: a == b, \"!=\": lambda a, b: a != b, \">\": lambda a, b: a > b, \">=\": lambda a, b: a >= b,\n", " \"<\": lambda a, b: a < b, \"<=\": lambda a, b: a <= b}\n", "\n", "\n", "def kw_count(clauses):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ ๋ชฉ๋ก์—์„œ ๋…์†Œยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ๊ฐ€ ๋“ค์–ด ์žˆ๋Š” ์กฐํ•ญ ์ˆ˜.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] 1,000๊ฑด ๋Œ€๊ทœ๋ชจ ์กฐ๋‹ฌ ๊ณ„์•ฝ์„œ ์ผ๊ด„ ์Šคํฌ๋ฆฌ๋‹ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋ฐ ์ฒ˜๋ฆฌ ์†๋„ ๊ฒ€์ฆ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def derive(df):\n", " \"\"\"๋ฃฐ์— ์“ฐ๋Š” ํŒŒ์ƒ ํ•„๋“œ(์„ ๊ธˆ+๋ณด์ฆ ๋ถ€์กฑ, ์ˆ˜์˜๊ณ„์•ฝ ๊ณ ์•ก, ๋…ผ๋ฆฌ ์˜ค๋ฅ˜, ํ‚ค์›Œ๋“œ ์ ์ค‘ ๋“ฑ)๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " d = df.copy()\n", " d[\"adv_no_bond\"] = (d[\"advance_pct\"] > 40) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"sole_amount\"] = np.where(d[\"method\"] == \"์ˆ˜์˜๊ณ„์•ฝ\", d[\"amount_m\"], 0.0) # ์ˆ˜์˜๊ณ„์•ฝ์ผ ๋•Œ๋งŒ ๊ธˆ์•ก(ํ•œ๋„๋Š” YAML์—์„œ ์กฐ์ •)\n", " d[\"bond_gap\"] = (d[\"amount_m\"] >= 500) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"warranty_gap\"] = (d[\"ctype\"] == \"๋ฌผํ’ˆ\") & (d[\"warranty_months\"] < 12)\n", " d[\"ip_missing\"] = (~d[\"ip_clause\"]) & (d[\"ctype\"] != \"๊ณต์‚ฌ\")\n", " d[\"logic_err\"] = d[\"delivery_days\"] > d[\"period_days\"]\n", " d[\"no_padj_long\"] = (d[\"period_days\"] >= 180) & (~d[\"price_adjust\"])\n", " d[\"noncomp_big\"] = d[\"method\"].isin([\"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]) & (d[\"amount_m\"] >= 1000)\n", " d[\"kw_hits\"] = [kw_count(c) for c in d[\"clauses\"]] if \"clauses\" in d else 0\n", " return d\n", "\n", "\n", "def apply_rules(rec, rules):\n", " \"\"\"๋ ˆ์ฝ”๋“œ 1๊ฑด(dict)์— ๋ฃฐ์„ ์ ์šฉํ•ด (์ ์ˆ˜, ๋“ฑ๊ธ‰, ์ ์ค‘ ๋ฃฐ id ๋ชฉ๋ก)์„ ๋Œ๋ ค์ค€๋‹ค. ๊ฐ’์ด ์—†์œผ๋ฉด ํ•ด๋‹น ๋ฃฐ์€ ๊ฑด๋„ˆ๋›ด๋‹ค.\"\"\"\n", " hits = []\n", " for r in rules:\n", " v = rec.get(r[\"field\"])\n", " if v is None or (isinstance(v, float) and np.isnan(v)):\n", " continue\n", " if OPS[r[\"op\"]](v, r[\"value\"]):\n", " hits.append(r)\n", " score = sum(r[\"weight\"] for r in hits)\n", " grade = \"๊ณ ์œ„ํ—˜\" if score >= GRADE_CUT[\"๊ณ ์œ„ํ—˜\"] else (\"์ฃผ์˜\" if score >= GRADE_CUT[\"์ฃผ์˜\"] else \"๊ด€์‹ฌ\")\n", " return score, grade, [r[\"id\"] for r in hits]\n", "\n", "SIMPLE_KW = {\"๋…์†Œ\": [\"๋ฉด์ œ\", \"์ผ๋ฐฉ\", \"ํฌ๊ธฐ\", \"์ „์•ก\", \"๋ˆ„์„ค\", \"ํ•˜๋„๊ธ‰\", \"์„ ์ง€๊ธ‰\"], \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์—ฐ์žฅ\", \"์ž๋™\", \"๋…ธ๋ ฅ\", \"ํ†ต๋ณด\", \"๊ธฐํ•œ\"]}\n", "\n", "# ---- ์กฐํ•ญ ๋ฌธ์žฅ ๋ถ„๋ฅ˜(์ •์ƒ / ๋…์†Œ / ๋‚ฉ๊ธฐ์œ„ํ—˜): ๊ธ€์ž n-gram TF-IDF + ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€ ----\n", "def clause_set(seed, n, hold=False, mix=(.55, .25, .20)):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ n๊ฐœ์™€ ์ •๋‹ต ๋ถ„๋ฅ˜๋ฅผ ๋งŒ๋“ ๋‹ค. hold=True๋ฉด ํ•™์Šต์— ์“ฐ์ง€ ์•Š์€ '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'์œผ๋กœ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " y = rng.choice(CLASSES, n, p=list(mix))\n", " return [make_clause(rng, c, hold) for c in y], list(y)\n", "\n", "\n", "def fit_clause_model(seed=100, n=1200):\n", " \"\"\"ํ•™์Šต์šฉ ์กฐํ•ญ ๋ฌธ์žฅ์œผ๋กœ ๋ถ„๋ฅ˜ ๋ชจ๋ธ์„ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " X, y = clause_set(seed, n)\n", " return make_pipeline(TfidfVectorizer(analyzer=\"char_wb\", ngram_range=(2, 4), sublinear_tf=True), LogisticRegression(C=20, max_iter=2000)).fit(X, y)\n", "\n", "\n", "def keyword_pred(s):\n", " \"\"\"๋น„๊ต์šฉ ๊ธฐ์ค€์„ : ๋‹จ์ˆœ ํ‚ค์›Œ๋“œ(๋ถ€์ • ํ‘œํ˜„ยท๋ฌธ๋งฅ์„ ๋ณด์ง€ ์•Š์Œ) ์ ์ค‘์œผ๋กœ๋งŒ ๋ถ„๋ฅ˜.\"\"\"\n", " if any(k in s for k in SIMPLE_KW[\"๋…์†Œ\"]):\n", " return \"๋…์†Œ\"\n", " return \"๋‚ฉ๊ธฐ์œ„ํ—˜\" if any(k in s for k in SIMPLE_KW[\"๋‚ฉ๊ธฐ์œ„ํ—˜\"]) else \"์ •์ƒ\"\n", "\n", "rules = parse_rules(RULES_YAML)\n", "CM = fit_clause_model()\n", "MANUAL_MIN = 35.0 # [๊ฐ€์ •] ์ˆ˜์ž‘์—… 1๊ฑด: ๊ณ„์•ฝ์„œ ์ฝ๊ธฐ 20๋ถ„ + ์ ๊ฒ€ํ‘œ ์ž‘์„ฑ 10๋ถ„ + ๊ทœ์ • ๋Œ€์กฐ 5๋ถ„\n", "REVIEW_MIN = 6.0 # [๊ฐ€์ •] ์ž๋™ ์ฒ˜๋ฆฌ ํ›„ ๋‹ด๋‹น์ž๊ฐ€ ํ™•์ธํ•˜๋Š” ๊ฑด๋‹น ์‹œ๊ฐ„(๊ณ ์œ„ํ—˜ยท์ฃผ์˜ยท๊ฒฉ๋ฆฌ ๊ฑด๋งŒ)\n", "CACHE = {}\n", "NOTICE = []\n", "\n", "\n", "def classify(clauses):\n", " \"\"\"์กฐํ•ญ ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฌธ์žฅ ๋‹จ์œ„๋กœ ์บ์‹œํ•œ๋‹ค(๊ฐ™์€ ๋ฌธ์žฅ์€ ํ•œ ๋ฒˆ๋งŒ ๋ชจ๋ธ์— ๋„ฃ๋Š”๋‹ค).\"\"\"\n", " new = [c for c in set(clauses) if c not in CACHE]\n", " if new:\n", " CACHE.update(dict(zip(new, CM.predict(new))))\n", " return [CACHE[c] for c in clauses]\n", "\n", "\n", "def run_batch(con, fail_p=0.0, rng=None, budget_s=None, progress=None):\n", " \"\"\"๊ฒ€์ฆ -> ๋ฃฐ ์Šคํฌ๋ฆฌ๋‹ -> (์‹œ๊ฐ„ ์—ฌ์œ ๊ฐ€ ์žˆ์œผ๋ฉด) ์กฐํ•ญ ๋ถ„๋ฅ˜. ์ผ์‹œ ์˜ค๋ฅ˜๋Š” 3ํšŒ ์žฌ์‹œ๋„, ์†์ƒ ๋ ˆ์ฝ”๋“œ๋Š” ๊ฒฉ๋ฆฌํ•˜๊ณ  ๊ณ„์†ํ•œ๋‹ค.\"\"\"\n", " t0 = time.perf_counter()\n", " st = {\"ok\": 0, \"quarantine\": 0, \"retry\": 0, \"failed\": 0, \"degraded\": False}\n", " d = derive(con)\n", " recs = d.to_dict(\"records\")\n", " grade, toxic = [], []\n", " mode = \"full\"\n", " for i, r in enumerate(recs):\n", " if progress and (i + 1) % max(len(recs) // 4, 1) == 0:\n", " progress(i + 1, len(recs))\n", " if budget_s and time.perf_counter() - t0 > budget_s and mode == \"full\":\n", " mode = \"rules_only\"; st[\"degraded\"] = True # ์‹œ๊ฐ„ ์˜ˆ์‚ฐ ์ดˆ๊ณผ: ์กฐํ•ญ ๋ถ„๋ฅ˜๋ฅผ ์ƒ๋žตํ•˜๊ณ  ๋ฃฐ๋งŒ ๊ณ„์†(๊ธฐ๋Šฅ ์ถ•์†Œ)\n", " if r.get(\"amount_m\") is None or pd.isna(r.get(\"amount_m\")) or not r.get(\"clauses\"):\n", " st[\"quarantine\"] += 1; grade.append(\"๊ฒฉ๋ฆฌ\"); toxic.append(0); continue\n", " for attempt in range(4):\n", " if rng is not None and rng.random() < fail_p: # ์ผ์‹œ ์˜ค๋ฅ˜(๋ชจ์˜): ์ตœ๋Œ€ 3ํšŒ ์žฌ์‹œ๋„\n", " st[\"retry\"] += 1; continue\n", " g = apply_rules(r, rules)[1]\n", " n_t = sum(k != \"์ •์ƒ\" for k in classify(r[\"clauses\"])) if mode == \"full\" else 0\n", " grade.append(g); toxic.append(n_t); st[\"ok\"] += 1; break\n", " else:\n", " st[\"failed\"] += 1; grade.append(\"์‹คํŒจ\"); toxic.append(0)\n", " return pd.DataFrame({\"contract_id\": con[\"contract_id\"].to_numpy(), \"grade\": grade, \"toxic\": toxic}), st, time.perf_counter() - t0\n", "\n", "\n", "class Scheduler:\n", " \"\"\"์›”๊ฐ„ ๋ฐฐ์น˜๋ฅผ ์ •ํ•ด์ง„ ์ˆœ์„œ๋กœ ์ž๋™ ์‹คํ–‰ํ•˜๋Š” ๊ฐ„์ด ์Šค์ผ€์ค„๋Ÿฌ(์‹ค์ œ ์‹œ๊ฐ„ ๋Œ€๊ธฐ ์—†์ด ์ˆœ์„œ๋งŒ ๋ชจ์‚ฌ).\"\"\"\n", "\n", " def __init__(self):\n", " self.jobs, self.history = [], []\n", "\n", " def add(self, name, fn):\n", " self.jobs.append((name, fn))\n", "\n", " def run_all(self):\n", " for name, fn in self.jobs:\n", " try:\n", " self.history.append((name, \"SUCCESS\", fn()))\n", " except Exception as e:\n", " self.history.append((name, \"FAILED\", str(e)))\n", " NOTICE.append(f\"[์•Œ๋ฆผ] {name} ์‹คํŒจ: {type(e).__name__}\")\n", "\n", "\n", "rng = np.random.default_rng(8)\n", "months = [f\"2026-{m:02d}\" for m in range(1, 13)]\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์›” 1,000๊ฑด x 12๊ฐœ์›” ๋ฐฐ์น˜, ์›”๋งˆ๋‹ค ์†์ƒ ๋ ˆ์ฝ”๋“œ 5๊ฑด(0.5%) ์ฃผ์ž…, ์ผ์‹œ ์˜ค๋ฅ˜ ํ™•๋ฅ  2%/์‹œ๋„\")\n", "data = {}\n", "for k, m in enumerate(months):\n", " c = make_contracts(1000, seed=100 + k)\n", " c = c.assign(amount_m=c[\"amount_m\"].astype(float))\n", " c.loc[rng.choice(1000, 5, replace=False), \"amount_m\"] = np.nan\n", " data[m] = c\n", "\n", "# 1) ์ฒซ ๋‹ฌ ๋ฐฐ์น˜: ์ง„ํ–‰๋ฅ  ํ‘œ์‹œ ํฌํ•จ\n", "def bar(i, n):\n", " print(f\" ์ง„ํ–‰๋ฅ  [{'#' * int(20 * i / n):<20s}] {i / n:.0%} ({i}/{n})\")\n", "\n", "\n", "print(\"\\n[2026-01 ๋ฐฐ์น˜ ์ง„ํ–‰]\")\n", "g1, s1, t1 = run_batch(data[\"2026-01\"], fail_p=0.02, rng=rng, progress=bar)\n", "print(f\" ์™„๋ฃŒ: ์ฒ˜๋ฆฌ {s1['ok']}๊ฑด, ๊ฒฉ๋ฆฌ {s1['quarantine']}๊ฑด, ์žฌ์‹œ๋„ {s1['retry']}ํšŒ, ์‹คํŒจ {s1['failed']}๊ฑด, {t1:.2f}์ดˆ\")\n", "NOTICE.append(f\"[์•Œ๋ฆผ] 2026-01 ๋ฐฐ์น˜ ์™„๋ฃŒ: ๊ณ ์œ„ํ—˜ {int((g1['grade'] == '๊ณ ์œ„ํ—˜').sum())}๊ฑด, ์ฃผ์˜ {int((g1['grade'] == '์ฃผ์˜').sum())}๊ฑด, ๊ฒฉ๋ฆฌ {s1['quarantine']}๊ฑด\")\n", "print(NOTICE[-1])\n", "\n", "# 2) 1๋…„์น˜ ์Šค์ผ€์ค„๋Ÿฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜\n", "sch = Scheduler()\n", "stats = {}\n", "for m in months:\n", " def job(m=m):\n", " g, s, t = run_batch(data[m], fail_p=0.02, rng=rng)\n", " stats[m] = {**s, \"sec\": t, \"๊ณ ์œ„ํ—˜\": int((g[\"grade\"] == \"๊ณ ์œ„ํ—˜\").sum()), \"์ฃผ์˜\": int((g[\"grade\"] == \"์ฃผ์˜\").sum())}\n", " return f\"{s['ok']}๊ฑด ์ฒ˜๋ฆฌ\"\n", " sch.add(m, job)\n", "t_all = time.perf_counter()\n", "sch.run_all()\n", "total_s = time.perf_counter() - t_all\n", "R = pd.DataFrame(stats).T\n", "print(f\"\\n[1๋…„์น˜ ์Šค์ผ€์ค„๋Ÿฌ ์‹คํ–‰] ์ž‘์—… {len(sch.history)}๊ฐœ, ์„ฑ๊ณต {sum(h[1] == 'SUCCESS' for h in sch.history)}๊ฐœ\")\n", "print(R[[\"ok\", \"quarantine\", \"retry\", \"failed\", \"๊ณ ์œ„ํ—˜\", \"์ฃผ์˜\"]].astype(int).rename(columns={\"ok\": \"์ฒ˜๋ฆฌ\", \"quarantine\": \"๊ฒฉ๋ฆฌ\", \"retry\": \"์žฌ์‹œ๋„\", \"failed\": \"์‹คํŒจ\"}).head(4).to_string())\n", "print(\" ... (์ดํ•˜ 8๊ฐœ์›” ์ƒ๋žต)\")\n", "tot_ok, tot_all = int(R[\"ok\"].sum()), len(months) * 1000\n", "succ = tot_ok / tot_all\n", "print(f\"\\n์ „์ฒด ๋ฐฐ์น˜ ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ ์‹œ๊ฐ„ {total_s:.1f}์ดˆ (12๊ฐœ์›”๋ถ„, ๊ฐ€์ƒ ์ž๋ฃŒ ์ƒ์„ฑ ์‹œ๊ฐ„ ์ œ์™ธ, ๋ชฉํ‘œ 5๋ถ„ ์ด๋‚ด: {'๋‹ฌ์„ฑ' if total_s <= 300 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\"์ž๋™ํ™” ์„ฑ๊ณต๋ฅ  {succ:.2%} = ์ž๋™ ์ฒ˜๋ฆฌ {tot_ok}๊ฑด / ์ „์ฒด {tot_all}๊ฑด (๊ฒฉ๋ฆฌ {int(R['quarantine'].sum())}๊ฑด์€ ๋‹ด๋‹น์ž ๋ณด์ • ๋Œ€์ƒ, ์ผ์‹œ ์˜ค๋ฅ˜ {int(R['retry'].sum())}ํšŒ๋Š” ์žฌ์‹œ๋„๋กœ ๋ณต๊ตฌ, ์‹คํŒจ {int(R['failed'].sum())}๊ฑด) (๋ชฉํ‘œ 99% ์ด์ƒ: {'๋‹ฌ์„ฑ' if succ >= 0.99 else '๋ฏธ๋‹ฌ'})\")\n", "NOTICE.append(f\"[์•Œ๋ฆผ] ์—ฐ๊ฐ„ ๋ฐฐ์น˜ ์™„๋ฃŒ: {len(months)}๊ฐœ์›”, ์„ฑ๊ณต๋ฅ  {succ:.1%}\")\n", "print(NOTICE[-1])\n", "\n", "# 3) ์ˆ˜์ž‘์—… ๋Œ€๋น„ ์‹œ๊ฐ„ ์ ˆ๊ฐ(๊ฐ€์ • ๋‹จ๊ฐ€ ์‚ฌ์šฉ)\n", "manual = tot_all * MANUAL_MIN\n", "review = (R[\"๊ณ ์œ„ํ—˜\"].sum() + R[\"์ฃผ์˜\"].sum() + R[\"quarantine\"].sum()) * REVIEW_MIN\n", "auto = total_s / 60 + review\n", "print(f\"์ˆ˜์ž‘์—… ๋Œ€๋น„(12,000๊ฑด): ์ˆ˜์ž‘์—… {manual / 60:.0f}์‹œ๊ฐ„ vs ์ž๋™ {auto / 60:.0f}์‹œ๊ฐ„(๋‹ด๋‹น์ž ํ™•์ธ ํฌํ•จ) -> {1 - auto / manual:.0%} ์ ˆ๊ฐ (๋ชฉํ‘œ 80% ์ด์ƒ: {'๋‹ฌ์„ฑ' if 1 - auto / manual >= 0.8 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\" ๊ฐ€์ •: ์ˆ˜์ž‘์—… ๊ฑด๋‹น {MANUAL_MIN:.0f}๋ถ„, ์ž๋™ ์ฒ˜๋ฆฌ ํ›„ ํ™•์ธ ๊ฑด๋‹น {REVIEW_MIN:.0f}๋ถ„(๊ณ ์œ„ํ—˜ยท์ฃผ์˜ยท๊ฒฉ๋ฆฌ ๊ฑด๋งŒ). ์‹ค์ œ ์—…๋ฌด ์‹œ๊ฐ„์€ ํ˜„์—… ์ธก์ •์œผ๋กœ ๋Œ€์ฒดํ•ด์•ผ ํ•œ๋‹ค.\")\n", "\n", "# 4) ๊ทœ๋ชจ ํ™•๋Œ€ ์‹œํ—˜๊ณผ ์‹œ๊ฐ„ ์ดˆ๊ณผ ์‹œ ๊ธฐ๋Šฅ ์ถ•์†Œ\n", "print(\"\\n[๊ทœ๋ชจ ํ™•๋Œ€ ์‹œํ—˜]\")\n", "sc = []\n", "for n in (500, 1000, 2000, 4000):\n", " c = make_contracts(n, seed=7)\n", " _, st, sec = run_batch(c, fail_p=0.02, rng=np.random.default_rng(1))\n", " sc.append((n, sec)); print(f\" ๊ณ„์•ฝ {n:5d}๊ฑด: {sec:5.2f}์ดˆ, ์ฒ˜๋ฆฌ {st['ok']}๊ฑด\")\n", "CACHE.clear()\n", "_, st, sec = run_batch(data[\"2026-01\"], budget_s=0.02)\n", "print(f\"์‹œ๊ฐ„ ์˜ˆ์‚ฐ 0.02์ดˆ ๋ถ€์—ฌ: ๊ธฐ๋Šฅ ์ถ•์†Œ {'์ž‘๋™' if st['degraded'] else '๋ฏธ์ž‘๋™'} -> ๋ฃฐ ์Šคํฌ๋ฆฌ๋‹์€ {st['ok']}๊ฑด ์™„๋ฃŒ, ์ผ๋ถ€ ๊ณ„์•ฝ์€ ์กฐํ•ญ ๋ถ„๋ฅ˜ ์ƒ๋žต. ์‹œ์Šคํ…œ์€ ์ค‘๋‹จ๋˜์ง€ ์•Š๊ณ  '์กฐํ•ญ ๋ถ„๋ฅ˜ ์ƒ๋žต' ์ƒํƒœ๋ฅผ ํ‘œ์‹œํ•œ๋‹ค.\")\n", "print(\"โ€ป ๋ฐฐ์น˜ ๊ฒฐ๊ณผ๋Š” 1์ฐจ ์„ ๋ณ„์ด๋ฉฐ ๊ณ ์œ„ํ—˜ยท์ฃผ์˜ยท๊ฒฉ๋ฆฌ ๊ฑด์€ ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด ๋‹ด๋‹น์ž๊ฐ€ ํ™•์ธํ•œ๋‹ค. ์›” ์—…๋ฌด๋Ÿ‰ยท์†Œ์š” ์‹œ๊ฐ„์€ ๊ฐ€์ƒ ๊ฐ€์ •์ด๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n", "ax[0].bar(range(1, 13), R[\"๊ณ ์œ„ํ—˜\"].astype(int), label=\"high\"); ax[0].bar(range(1, 13), R[\"์ฃผ์˜\"].astype(int), bottom=R[\"๊ณ ์œ„ํ—˜\"].astype(int), label=\"watch\")\n", "ax[0].set_xlabel(\"month\"); ax[0].legend(); ax[0].set_title(\"Monthly batch: flagged contracts (synthetic)\")\n", "ax[1].plot(*zip(*sc), marker=\"o\"); ax[1].axhline(300, color=\"r\", ls=\"--\"); ax[1].set_xlabel(\"contracts\"); ax[1].set_ylabel(\"seconds\"); ax[1].set_title(\"Batch run time vs scale (red = 5 min)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 08 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์›” 1,000๊ฑด x 12๊ฐœ์›” ๋ฐฐ์น˜, ์›”๋งˆ๋‹ค ์†์ƒ ๋ ˆ์ฝ”๋“œ 5๊ฑด(0.5%) ์ฃผ์ž…, ์ผ์‹œ ์˜ค๋ฅ˜ ํ™•๋ฅ  2%/์‹œ๋„\n", "\n", "[2026-01 ๋ฐฐ์น˜ ์ง„ํ–‰]\n", " ์ง„ํ–‰๋ฅ  [##### ] 25% (250/1000)\n", " ์ง„ํ–‰๋ฅ  [########## ] 50% (500/1000)\n", " ์ง„ํ–‰๋ฅ  [############### ] 75% (750/1000)\n", " ์ง„ํ–‰๋ฅ  [####################] 100% (1000/1000)\n", " ์™„๋ฃŒ: ์ฒ˜๋ฆฌ 995๊ฑด, ๊ฒฉ๋ฆฌ 5๊ฑด, ์žฌ์‹œ๋„ 22ํšŒ, ์‹คํŒจ 0๊ฑด, 1.23์ดˆ\n", "[์•Œ๋ฆผ] 2026-01 ๋ฐฐ์น˜ ์™„๋ฃŒ: ๊ณ ์œ„ํ—˜ 149๊ฑด, ์ฃผ์˜ 300๊ฑด, ๊ฒฉ๋ฆฌ 5๊ฑด\n", "\n", "[1๋…„์น˜ ์Šค์ผ€์ค„๋Ÿฌ ์‹คํ–‰] ์ž‘์—… 12๊ฐœ, ์„ฑ๊ณต 12๊ฐœ\n", " ์ฒ˜๋ฆฌ ๊ฒฉ๋ฆฌ ์žฌ์‹œ๋„ ์‹คํŒจ ๊ณ ์œ„ํ—˜ ์ฃผ์˜\n", "2026-01 995 5 9 0 149 300\n", "2026-02 995 5 13 0 122 312\n", "2026-03 995 5 21 0 119 328\n", "2026-04 995 5 21 0 120 330\n", " ... (์ดํ•˜ 8๊ฐœ์›” ์ƒ๋žต)\n", "\n", "์ „์ฒด ๋ฐฐ์น˜ ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ ์‹œ๊ฐ„ 1.9์ดˆ (12๊ฐœ์›”๋ถ„, ๊ฐ€์ƒ ์ž๋ฃŒ ์ƒ์„ฑ ์‹œ๊ฐ„ ์ œ์™ธ, ๋ชฉํ‘œ 5๋ถ„ ์ด๋‚ด: ๋‹ฌ์„ฑ)\n", "์ž๋™ํ™” ์„ฑ๊ณต๋ฅ  99.50% = ์ž๋™ ์ฒ˜๋ฆฌ 11940๊ฑด / ์ „์ฒด 12000๊ฑด (๊ฒฉ๋ฆฌ 60๊ฑด์€ ๋‹ด๋‹น์ž ๋ณด์ • ๋Œ€์ƒ, ์ผ์‹œ ์˜ค๋ฅ˜ 226ํšŒ๋Š” ์žฌ์‹œ๋„๋กœ ๋ณต๊ตฌ, ์‹คํŒจ 0๊ฑด) (๋ชฉํ‘œ 99% ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "[์•Œ๋ฆผ] ์—ฐ๊ฐ„ ๋ฐฐ์น˜ ์™„๋ฃŒ: 12๊ฐœ์›”, ์„ฑ๊ณต๋ฅ  99.5%\n", "์ˆ˜์ž‘์—… ๋Œ€๋น„(12,000๊ฑด): ์ˆ˜์ž‘์—… 7000์‹œ๊ฐ„ vs ์ž๋™ 553์‹œ๊ฐ„(๋‹ด๋‹น์ž ํ™•์ธ ํฌํ•จ) -> 92% ์ ˆ๊ฐ (๋ชฉํ‘œ 80% ์ด์ƒ: ๋‹ฌ์„ฑ)\n", " ๊ฐ€์ •: ์ˆ˜์ž‘์—… ๊ฑด๋‹น 35๋ถ„, ์ž๋™ ์ฒ˜๋ฆฌ ํ›„ ํ™•์ธ ๊ฑด๋‹น 6๋ถ„(๊ณ ์œ„ํ—˜ยท์ฃผ์˜ยท๊ฒฉ๋ฆฌ ๊ฑด๋งŒ). ์‹ค์ œ ์—…๋ฌด ์‹œ๊ฐ„์€ ํ˜„์—… ์ธก์ •์œผ๋กœ ๋Œ€์ฒดํ•ด์•ผ ํ•œ๋‹ค.\n", "\n", "[๊ทœ๋ชจ ํ™•๋Œ€ ์‹œํ—˜]\n", " ๊ณ„์•ฝ 500๊ฑด: 0.06์ดˆ, ์ฒ˜๋ฆฌ 500๊ฑด\n", " ๊ณ„์•ฝ 1000๊ฑด: 0.12์ดˆ, ์ฒ˜๋ฆฌ 1000๊ฑด\n", " ๊ณ„์•ฝ 2000๊ฑด: 0.27์ดˆ, ์ฒ˜๋ฆฌ 2000๊ฑด\n", " ๊ณ„์•ฝ 4000๊ฑด: 0.33์ดˆ, ์ฒ˜๋ฆฌ 4000๊ฑด\n", "์‹œ๊ฐ„ ์˜ˆ์‚ฐ 0.02์ดˆ ๋ถ€์—ฌ: ๊ธฐ๋Šฅ ์ถ•์†Œ ์ž‘๋™ -> ๋ฃฐ ์Šคํฌ๋ฆฌ๋‹์€ 995๊ฑด ์™„๋ฃŒ, ์ผ๋ถ€ ๊ณ„์•ฝ์€ ์กฐํ•ญ ๋ถ„๋ฅ˜ ์ƒ๋žต. ์‹œ์Šคํ…œ์€ ์ค‘๋‹จ๋˜์ง€ ์•Š๊ณ  '์กฐํ•ญ ๋ถ„๋ฅ˜ ์ƒ๋žต' ์ƒํƒœ๋ฅผ ํ‘œ์‹œํ•œ๋‹ค.\n", "โ€ป ๋ฐฐ์น˜ ๊ฒฐ๊ณผ๋Š” 1์ฐจ ์„ ๋ณ„์ด๋ฉฐ ๊ณ ์œ„ํ—˜ยท์ฃผ์˜ยท๊ฒฉ๋ฆฌ ๊ฑด์€ ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌด ๋‹ด๋‹น์ž๊ฐ€ ํ™•์ธํ•œ๋‹ค. ์›” ์—…๋ฌด๋Ÿ‰ยท์†Œ์š” ์‹œ๊ฐ„์€ ๊ฐ€์ƒ ๊ฐ€์ •์ด๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 09. AAR ๊ธฐ๋ฐ˜ ๊ทœ์ • ์œ„๋ฐ˜ ๋ฏธํƒ ์‚ฌ๋ก€ ๋ถ„์„ ๋ฐ ์กฐ๋‹ฌ ๋ฃฐ์…‹/ํ”„๋กฌํ”„ํŠธ ์žฌ๋ณด์ •\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "AAR ๊ธฐ๋ฐ˜ ๊ทœ์ • ์œ„๋ฐ˜ ๋ฏธํƒ ์‚ฌ๋ก€๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์กฐ๋‹ฌ ๋ฃฐ์…‹์„ ์žฌ๋ณด์ •ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **AAR์„ ๋ฐ”ํƒ•์œผ๋กœ ๊ทœ์ • ์œ„๋ฐ˜ ๋ฏธํƒ ์‚ฌ๋ก€๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์กฐ๋‹ฌ ๋ฃฐ์…‹๊ณผ ์กฐํ•ญ ๋ถ„๋ฅ˜ ๊ธฐ์ค€์„ ์žฌ๋ณด์ •ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **AAR ์„ธํŠธ์—์„œ ๋ฃฐ ์Šคํฌ๋ฆฌ๋‹์ด ๋†“์นœ ์œ„๋ฐ˜ ๊ณ„์•ฝ์˜ ์›์ธ์„ ๊ฒฝ๋ฏธ ๋ฃฐ ๋‹จ๋… ์ ์ค‘, ์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„ ์งํ•˜ ๊ธˆ์•ก, ๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ ๋ถ„์‚ฐ, ์ž๋ฃŒ๋กœ ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜ ๋“ฑ์œผ๋กœ ๋ถ„๋ฅ˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/prc01_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 ๊ณ„์•ฝ ๊ฒ€ํ†  ์„ธํŠธ์—์„œ ๋ฃฐ ์Šคํฌ๋ฆฌ๋‹์ด ๋†“์นœ ๊ทœ์ • ์œ„๋ฐ˜(๋ฏธํƒ) ์‚ฌ๋ก€๋ฅผ ์›์ธ๋ณ„๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ , ๋“ฑ๊ธ‰ ๊ธฐ์ค€ยท๊ฐ€์ค‘์น˜ยท์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„ยท์กฐํ•ญ ๋ถ„๋ฅ˜ ๊ฒฐํ•ฉ ๋“ฑ ๋ฃฐ์…‹ ์žฌ๋ณด์ •์•ˆ์„ ๋ณ„๋„ ๊ฒ€์ฆ ์„ธํŠธ๋กœ ๋น„๊ตํ•ด ์ฑ„ํƒ ์—ฌ๋ถ€๋ฅผ ์ •ํ•œ ๋’ค UAT ์ฒดํฌ๋ฆฌ์ŠคํŠธ ์ ์ˆ˜์™€ ๊ฐœ์„  ์š”๊ตฌ ๋ฐ˜์˜ ํ˜„ํ™ฉ์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", "import copy\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์กฐ๋‹ฌ๊ณ„ํš์•ˆยท๊ณ„์•ฝ์„œ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ณ„์•ฝยท์—…์ฒดยท๋ฒ•๋ น ์กฐ๋ฌธ ์—†์Œ)\n", "# ๊ณ„์•ฝ ์†์„ฑ 16๊ฐœ + ์กฐํ•ญ ๋ฌธ์žฅ 6๊ฐœ๋ฅผ ๋งŒ๋“ค๊ณ , ์†์„ฑ ์œ„๋ฐ˜ยท๋…์†Œ ์กฐํ•ญยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ์„ž์ธ '๊ทœ์ • ์œ„๋ฐ˜ ์—ฌ๋ถ€'๋ฅผ ํ•จ๊ป˜ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "TYPES = [\"๋ฌผํ’ˆ\", \"์šฉ์—ญ\", \"๊ณต์‚ฌ\"]\n", "METHODS = [\"์ผ๋ฐ˜๊ฒฝ์Ÿ\", \"์ œํ•œ๊ฒฝ์Ÿ\", \"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]\n", "CLASSES = [\"์ •์ƒ\", \"๋…์†Œ\", \"๋‚ฉ๊ธฐ์œ„ํ—˜\"]\n", "OBJ = [\"๋‚ฉํ’ˆ ๋ฌผํ’ˆ\", \"์šฉ์—ญ ๊ฒฐ๊ณผ๋ฌผ\", \"์‹œ๊ณต ๋ชฉ์ ๋ฌผ\", \"์ •๋น„ ์„ฑ๊ณผ๋ฌผ\"]\n", "\n", "N_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋‚ฉ๊ธฐ๋ฅผ ์ง€์ฒดํ•œ ๊ฒฝ์šฐ ์ง€์ฒด์ผ์ˆ˜ 1์ผ๋งˆ๋‹ค ๊ณ„์•ฝ๊ธˆ์•ก์˜ {p}/1000์— ํ•ด๋‹นํ•˜๋Š” ์ง€์ฒด์ƒ๊ธˆ์„ ๋ถ€๊ณผํ•œ๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๊ฐ€ ํ™•์ธ๋œ ๊ฒฝ์šฐ์— ํ•œํ•˜์—ฌ ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์Šน์ธ์„ ๋ฐ›์•„ ๋ฉด์ œํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ ์—ฐ์žฅ์€ ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๋ฅผ ํ™•์ธํ•˜๊ณ  ์„œ๋ฉด์œผ๋กœ ์Šน์ธํ•œ ๊ฒฝ์šฐ์—๋งŒ ์ธ์ •ํ•œ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋‚ฉํ’ˆ๋œ {o}์„ {d}์ผ ์ด๋‚ด์— ๊ฒ€์‚ฌํ•˜๊ณ  ํ•ฉ๊ฒฉํ•œ ํ›„์— ๋Œ€๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ํ•˜๋„๊ธ‰์„ ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ์‚ฌ์ „์— ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์„œ๋ฉด ์Šน์ธ์„ ๋ฐ›์•„์•ผ ํ•œ๋‹ค.\",\n", " \"์–ด๋А ํ•œ์ชฝ์ด ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•˜๋ ค๋ฉด ์‚ฌ์œ ๋ฅผ ๋ช…์‹œํ•ด {d}์ผ ์ „์— ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"{o}์— ํ•˜์ž๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ๋ณด์ฆ ๊ธฐ๊ฐ„ ์•ˆ์— ๋ฌด์ƒ์œผ๋กœ ๋ณด์ˆ˜ํ•˜๊ฑฐ๋‚˜ ๊ต์ฒดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ๋น„๊ณต๊ฐœ ์‚ฌํ•ญ์€ ์ œ3์ž์—๊ฒŒ ๋ˆ„์„คํ•˜์ง€ ์•Š์œผ๋ฉฐ ์œ„๋ฐ˜ ์‹œ ์ฑ…์ž„์„ ์ง„๋‹ค.\",\n", " \"{o}์— ๋Œ€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์˜ ๊ท€์†๊ณผ ์‚ฌ์šฉ ๋ฒ”์œ„๋Š” ๋ณ„๋„ ํ•ฉ์˜์„œ์— ๋”ฐ๋ผ ์ •ํ•œ๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ํ•ฉ๊ฒฉ ํ›„ ์ฒญ๊ตฌ์„œ๋ฅผ ์ ‘์ˆ˜ํ•œ ๋‚ ๋ถ€ํ„ฐ {d}์ผ ์ด๋‚ด์— ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ๊ธˆ์•ก์˜ ์กฐ์ •์€ ๋ฌผ๊ฐ€๋ณ€๋™ ๊ธฐ์ค€์— ๋”ฐ๋ผ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ์„œ๋ฉด์œผ๋กœ ํ•ฉ์˜ํ•œ ๊ฒฝ์šฐ์—๋งŒ ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ดํ–‰๋ณด์ฆ์„œ๋ฅผ ๊ณ„์•ฝ ์ฒด๊ฒฐ ์‹œ ์ œ์ถœํ•˜๋ฉฐ ๋ณด์ฆ๊ธˆ์€ ๊ณ„์•ฝ ์ดํ–‰ ์™„๋ฃŒ ํ›„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ณ„์•ฝ์„œ ๋ณ„ํ‘œ์˜ ์ผ์ž๋ฅผ ๋”ฐ๋ฅด๋ฉฐ ๋ณ€๊ฒฝ ์‹œ ์„œ๋ฉด ํ•ฉ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผํ•˜๋ฉฐ ์ด์˜๋Š” ์ •ํ•ด์ง„ ์ ˆ์ฐจ๋กœ ์ œ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•˜์ง€ ๋ชปํ•œ {o}์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๊ต์ฒดํ•˜๋ฉฐ ๊ทธ ๋น„์šฉ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ๊ท€์ฑ… ์‚ฌ์œ ๋กœ ์ดํ–‰์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•˜๊ณ  ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"์ง€์—ฐ์ด ์˜ˆ์ƒ๋˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ๋ฐœ์ฃผ๊ธฐ๊ด€์— ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•œ๋‹ค.\",\n", " \"๋ณด์ฆ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง€๊ณ  ๋ณด์ˆ˜ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์€ ๋จผ์ € ํ˜‘์˜๋กœ ํ•ด๊ฒฐํ•˜๊ณ  ํ˜‘์˜๊ฐ€ ๋˜์ง€ ์•Š์œผ๋ฉด ๊ด€ํ•  ๋ฒ•์›์˜ ํŒ๋‹จ์— ๋”ฐ๋ฅธ๋‹ค.\",\n", "]\n", "T_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ง€์ฒด์ƒ๊ธˆ์€ ์ „์•ก ๋ฉด์ œํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋Œ€ํ•ด ์ด์˜๋ฅผ ์ œ๊ธฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ฆ‰์‹œ ๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ ๋ณด๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ดํ›„ ๊ฒ€์‚ฌ๋ฅผ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์–ธ์ œ๋“ ์ง€ ์ผ๋ฐฉ์ ์œผ๋กœ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ์— ๋”ฐ๋ฅธ ์†ํ•ด๋Š” ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"{o}์˜ ์ถ”๊ฐ€ ๋น„์šฉ์€ ์‚ฌ์œ ๋ฅผ ๋ถˆ๋ฌธํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ์ „์•ก ๋ถ€๋‹ดํ•˜๋ฉฐ ๋ณ„๋„ ํ˜‘์˜ ์—†์ด ์ฒญ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"{o}์— ๊ด€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์€ ๋ชจ๋‘ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ๊ท€์†๋˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"ํ•˜๋„๊ธ‰์€ ์‹ ๊ณ  ์—†์ด ์ œํ•œ ์—†์ด ํ—ˆ์šฉํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ํ•˜๋„๊ธ‰ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ์ „์— ์ „์•ก ์„ ์ง€๊ธ‰ํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ฐ˜ํ™˜ ์ฒญ๊ตฌ๊ถŒ์„ ํฌ๊ธฐํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ์‚ฌํ•ญ์˜ ๋ˆ„์„ค๋กœ ์ธํ•œ ์ฑ…์ž„์„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"ํ•˜์ž ๋ณด์ˆ˜ ์ฑ…์ž„์€ ์ธ๋„ ํ›„ ์ฆ‰์‹œ ์†Œ๋ฉธํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ณด์ˆ˜๋ฅผ ์š”๊ตฌํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฒญ๊ตฌํ•˜๋Š” ๊ธˆ์•ก์„ ๊ฑฐ์ ˆํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ๊ฑฐ์ ˆํ•˜๋ฉด ์œ„์•ฝ๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์ด ์ƒ๊ธฐ๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ •ํ•˜๋Š” ์ค‘์žฌ ๊ธฐ๊ด€์—์„œ๋งŒ ํ•ด๊ฒฐํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์‚ฌ๋Š” ์ƒ๋žตํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์‹œํ—˜ ๊ฒฐ๊ณผ์„œ๋กœ ๋Œ€์‹ ํ•œ๋‹ค.\",\n", " \"๊ฒฌ์  ๊ธˆ์•ก์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ๊ทธ๋Œ€๋กœ ํ™•์ •ํ•˜๊ณ  ์ฆ๋น™ ์ž๋ฃŒ๋Š” ๋ฐ›์ง€ ์•Š๋Š”๋‹ค.\",\n", "]\n", "D_TPL = [\n", " \"๋‚ฉ๊ธฐ๋Š” ์ž์žฌ ์ˆ˜๊ธ‰ ์‚ฌ์ •์— ๋”ฐ๋ผ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ฐ€๋Šฅํ•œ ํ•œ ์ง€ํ‚ค๋„๋ก ๋…ธ๋ ฅํ•˜๋ฉฐ ์ง€์—ฐ๋˜์–ด๋„ ์‚ฌ์œ ๋ฅผ ๋ณด๊ณ ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณต๊ธ‰ ์ง€์—ฐ์ด๋‚˜ ์„ ํ–‰ ์—…์ฒด ์‚ฌ์ •๋„ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ์œผ๋กœ ๋ณด์•„ ๋‚ฉ๊ธฐ๋ฅผ ์ž๋™ ์—ฐ์žฅํ•œ๋‹ค.\",\n", " \"์ฐฉ์ˆ˜์ผ์€ ์„ ํ–‰ ์—…์ฒด์˜ ๋‚ฉํ’ˆ์ด ๋๋‚œ ๋’ค ๋ณ„๋„๋กœ ์ •ํ•˜๋ฉฐ ์™„๋ฃŒ ๊ธฐํ•œ์€ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ์ผ ์ดํ›„ 1ํšŒ์— ํ•œํ•ด ๊ธฐํ•œ ์—†์ด ์ž๋™ ์—ฐ์žฅ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํ•œ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๋Š” ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ƒ์‚ฐ ์ผ์ •์ด ํ™•์ •๋˜๋Š” ๋Œ€๋กœ ํ†ต๋ณดํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์‹œ์ ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž ์‚ฌ์ •์ด ํ—ˆ๋ฝํ•˜๋Š” ๋•Œ๋กœ ํ•˜๊ณ  ๋‚ ์งœ๋Š” ๋”ฐ๋กœ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ž์žฌ๊ฐ€ ๋Šฆ๊ฒŒ ๋“ค์–ด์˜ค๋ฉด ์™„๋ฃŒ ์ผ์ •์€ ๊ทธ๋งŒํผ ์ž๋™์œผ๋กœ ๋Šฆ์ถฐ์ง„๋‹ค.\",\n", " \"๊ธฐํ•œ ๋‚ด์— ์ธ๋„ํ•˜์ง€ ๋ชปํ•ด๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ •์ด ๋Šฆ์–ด์ ธ๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ƒˆ ๊ณ„ํš์„œ๋ฅผ ๋‚ผ ํ•„์š”๊ฐ€ ์—†๋‹ค.\",\n", "]\n", "# ํ•™์Šต์— ์“ฐ์ง€ ์•Š๋Š” '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'(๋ชจ๋ธ์ด ๋ฌธ์žฅ ํ‹€์„ ์™ธ์› ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์šฉ๋„)\n", "HOLD = {\n", " \"์ •์ƒ\": [\"๊ฒ€์‚ฌ์— ๋ถˆํ•ฉ๊ฒฉํ•œ ๊ฒฝ์šฐ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ • ๊ธฐํ•œ ์•ˆ์— ์žฌ๋‚ฉํ’ˆํ•˜๋ฉฐ ์žฌ๊ฒ€์‚ฌ ๋น„์šฉ์€ ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ฒ•๋ น์„ ์œ„๋ฐ˜ํ•˜์—ฌ ๊ณ„์•ฝ์„ ์ดํ–‰ํ•˜์ง€ ๋ชปํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด ํ†ต์ง€ ํ›„ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๊ฐ€ ์ง€์—ฐ๋  ์šฐ๋ ค๊ฐ€ ์žˆ์œผ๋ฉด ์ƒ๋Œ€์ž๋Š” ์ฆ‰์‹œ ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"ํ•˜์ž๋‹ด๋ณด ์ฑ…์ž„ ๊ธฐ๊ฐ„์€ ์ธ์ˆ˜์ผ๋ถ€ํ„ฐ ๊ธฐ์‚ฐํ•˜๋ฉฐ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง„๋‹ค.\"],\n", " \"๋…์†Œ\": [\"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๋Œ€๊ธˆ์„ ์ด์œ  ์—†์ด ๊ฑฐ์ ˆํ•˜์ง€ ๋ชปํ•˜๋ฉฐ ๊ฑฐ์ ˆ ์‹œ ์œ„์•ฝ๊ธˆ์„ ๋‚ธ๋‹ค.\",\n", " \"๊ณ„์•ฝ ๋‚ด์šฉ์— ๊ด€ํ•œ ๋ถ„์Ÿ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€์—์„œ๋งŒ ๋‹ค๋ฃจ๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์ˆ˜ ๊ถŒํ•œ์€ ํฌ๊ธฐํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ž์ฒด ์‹œํ—˜ ์„ฑ์ ์„œ๋กœ ๊ฐˆ์Œํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ œ์ถœํ•œ ๊ฒฌ์ ์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ํ™•์ • ๋‹จ๊ฐ€๋กœ ํ•˜๋ฉฐ ์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"๋‚ฉํ’ˆ ์‹œ๊ธฐ๋Š” ์ƒ๋Œ€์ž์˜ ํ˜•ํŽธ์ด ํ—ˆ๋ฝํ•˜๋Š” ์‹œ์ ์œผ๋กœ ํ•˜๊ณ  ๋ณ„๋„ ๋‚ ์งœ๋Š” ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์›์ž์žฌ ํ™•๋ณด๊ฐ€ ๋Šฆ์–ด์ง€๋ฉด ์™„๋ฃŒ ์ผ์ž๋Š” ์ž๋™์œผ๋กœ ๋’ค๋กœ ๋ฏธ๋ค„์ง€๋Š” ๊ฒƒ์œผ๋กœ ๋ณธ๋‹ค.\",\n", " \"์ธ๋„ ๊ธฐํ•œ์€ ์ฐธ๊ณ ์šฉ์ด๋ฉฐ ์ง€ํ‚ค์ง€ ๋ชปํ•ด๋„ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ • ์ง€์—ฐ์ด ์žˆ์–ด๋„ ์ƒ๋Œ€์ž๋Š” ๋ณ€๊ฒฝ ๊ณ„ํš์„ ์ œ์ถœํ•  ์˜๋ฌด๊ฐ€ ์—†๋‹ค.\"],\n", "}\n", "SUFFIX = [\"\", \"\", \"\", \" ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ์‹œํ–‰ ์„ธ์น™์— ๋”ฐ๋ฅธ๋‹ค.\", \" ์ด ์กฐํ•ญ์€ ๊ณ„์•ฝ ์ „์ฒด์— ์ ์šฉํ•œ๋‹ค.\", \" ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ํ˜‘์˜ํ•œ๋‹ค.\", \" ๊ด€๋ จ ์„œ์‹์€ ๋ณ„์ง€๋ฅผ ๋”ฐ๋ฅธ๋‹ค.\"]\n", "SWAP = [(\"๋ฐœ์ฃผ๊ธฐ๊ด€\", \"๊ณ„์•ฝ๋‹ด๋‹น๊ด€\"), (\"๊ณ„์•ฝ์ƒ๋Œ€์ž\", \"์—…์ฒด\")]\n", "\n", "\n", "def make_clause(rng, cls, hold=False, idx=None):\n", " \"\"\"์กฐํ•ญ ํ•œ ๋ฌธ์žฅ ์ƒ์„ฑ. ํ‘œํ˜„ ์ผ๋ถ€๋Š” ๋™์˜์–ด๋กœ ๋ฐ”๊พธ๊ณ  ์ ‘๋ฏธ ๋ฌธ๊ตฌ๋ฅผ ๋ฌด์ž‘์œ„๋กœ ๋ถ™์ธ๋‹ค(idx๋ฅผ ์ฃผ๋ฉด ๊ทธ ๋ฒˆํ˜ธ์˜ ๋ฌธ์žฅ ํ‹€์„ ์‚ฌ์šฉ).\"\"\"\n", " if idx is not None:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[idx].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " elif hold:\n", " s = HOLD[cls][rng.integers(len(HOLD[cls]))]\n", " else:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[rng.integers(len(tpl))].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " if rng.random() < 0.3:\n", " a, b = SWAP[rng.integers(2)]\n", " s = s.replace(a, b)\n", " return s + SUFFIX[rng.integers(len(SUFFIX))]\n", "\n", "\n", "def make_contracts(n=200, seed=7):\n", " \"\"\"๊ฐ€์ƒ ๊ณ„์•ฝ n๊ฑด(์†์„ฑ 16๊ฐœ + ์กฐํ•ญ 6๊ฐœ + ๊ทœ์ • ์œ„๋ฐ˜ ์ •๋‹ต)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " sl = rng.beta(1.2, 3.2, n) # ๊ณ„์•ฝ์„œ ์ž‘์„ฑ์ด ํ—ˆ์ˆ ํ•œ ์ •๋„(์ž ์žฌ ๋ณ€์ˆ˜)\n", " ctype = rng.choice(TYPES, n, p=[.5, .3, .2])\n", " amount = np.clip(np.exp(rng.normal(np.log(220), 1.1, n)), 5, 5000).round(0)\n", " method = np.where(rng.random(n) < .06 + .3 * sl, \"์ˆ˜์˜๊ณ„์•ฝ\", rng.choice(METHODS[:3], n, p=[.5, .3, .2]))\n", " period = np.array([rng.integers(60, 300) if t == \"๋ฌผํ’ˆ\" else rng.integers(90, 400) if t == \"์šฉ์—ญ\" else rng.integers(180, 720) for t in ctype])\n", " delivery = (period * rng.uniform(.5, 1.0, n)).astype(int)\n", " bad_logic = rng.random(n) < .015 * (1 + 3 * sl)\n", " delivery = np.where(bad_logic, period + rng.integers(10, 60, n), delivery)\n", " pen = np.where(rng.random(n) < .22 * sl, rng.choice([.3, .5, .8], n), rng.choice([1.0, 1.5, 2.0, 2.5], n))\n", " adv = np.where(rng.random(n) < .22 * sl, rng.choice([50, 60, 70], n), rng.choice([0, 10, 20, 30, 40], n))\n", " bond = np.where(rng.random(n) < .25 * sl + .5 * (adv > 40), rng.choice([0, 5], n), rng.choice([10, 10, 15, 20], n))\n", " war = np.array([rng.choice([3, 6]) if (rng.random() < .25 * s and t == \"๋ฌผํ’ˆ\") else rng.choice([12, 24, 36]) if t == \"๋ฌผํ’ˆ\" else rng.choice([0, 6, 12]) if t == \"์šฉ์—ญ\" else rng.choice([24, 36, 60]) for t, s in zip(ctype, sl)])\n", " insp = np.where(rng.random(n) < .3 * sl, rng.choice([21, 30], n), rng.choice([5, 7, 10, 14], n))\n", " sub = np.where(rng.random(n) < .15 * sl, rng.choice([60, 70, 80], n), rng.choice([0, 10, 20, 30, 40, 50], n))\n", " padj = rng.random(n) < .75 - .4 * sl\n", " term = rng.random(n) > .015 * (1 + 6 * sl)\n", " sec = rng.random(n) > .015 * (1 + 6 * sl)\n", " ip = rng.random(n) > .03 * (1 + 4 * sl)\n", " clauses, cls_true = [], []\n", " for i in range(n):\n", " pt, pd_ = .01 + .08 * sl[i], .02 + .08 * sl[i]\n", " cl = [rng.choice(CLASSES, p=[1 - pt - pd_, pt, pd_]) for _ in range(6)]\n", " cls_true.append(cl); clauses.append([make_clause(rng, c) for c in cl])\n", " df = pd.DataFrame({\"contract_id\": [f\"C-{i + 1:04d}\" for i in range(n)], \"ctype\": ctype, \"method\": method, \"amount_m\": amount,\n", " \"period_days\": period, \"delivery_days\": delivery, \"penalty_permil\": pen, \"advance_pct\": adv, \"perf_bond_pct\": bond,\n", " \"warranty_months\": war, \"inspection_days\": insp, \"subcontract_pct\": sub, \"price_adjust\": padj,\n", " \"termination\": term, \"security\": sec, \"ip_clause\": ip})\n", " hard = ((pen < 1.0) | ((adv > 40) & (bond < 10)) | ((amount >= 300) & (method == \"์ˆ˜์˜๊ณ„์•ฝ\")) | ((ctype == \"๋ฌผํ’ˆ\") & (war < 12))\n", " | ~term | ~sec | (sub > 50) | (delivery > period))\n", " toxic = np.array([c.count(\"๋…์†Œ\") for c in cls_true])\n", " delay = np.array([c.count(\"๋‚ฉ๊ธฐ์œ„ํ—˜\") for c in cls_true])\n", " split = (method == \"์ˆ˜์˜๊ณ„์•ฝ\") & (amount >= 250) & (amount < 300) & (rng.random(n) < .6) # ๊ณ„์•ฝ ์ชผ๊ฐœ๊ธฐ ์˜์‹ฌ(์ž„๊ณ„๊ฐ’ ๋ฐ”๋กœ ์•„๋ž˜)\n", " df[\"clauses\"], df[\"clause_cls\"] = clauses, cls_true\n", " df[\"n_toxic\"], df[\"n_delay\"] = toxic, delay\n", " viol = hard | (toxic > 0) | split | (delay >= 2)\n", " waived = hard & (toxic == 0) & (rng.random(n) < .12) # ๊ณ„์•ฝ๊ด€์ด ์˜ˆ์™ธ ์‚ฌ์œ ๋ฅผ ์ธ์ •ํ•ด ์œ„๋ฐ˜์œผ๋กœ ๋ณด์ง€ ์•Š์€ ๊ฑด(์ž๋ฃŒ์˜ ํ˜„์‹ค์  ์žก์Œ)\n", " hidden = ~viol & (rng.random(n) < .03 + .08 * sl) # ์†์„ฑยท๋ฌธ๊ตฌ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜(๋ณ„์ฒจ ํ•ฉ์˜ ๋“ฑ)\n", " df[\"violation\"] = (viol & ~waived) | hidden\n", " df[\"vtype\"] = np.where(hidden, \"๊ธฐํƒ€\", np.where(hard, \"์†์„ฑ์œ„๋ฐ˜\", np.where(toxic > 0, \"๋…์†Œ์กฐํ•ญ\", np.where(split | (delay >= 2), \"๊ธฐํƒ€\", \"-\"))))\n", " return df\n", "\n", "# ---- ๊ณ„์•ฝ ๊ทœ์ • ์ค€์ˆ˜ ํŒ๋ณ„ ๋ฃฐ ์„ค์ •(YAML ํ˜•์‹ ๋ฌธ์ž์—ด: ๊ทœ์ •์ด ๋ฐ”๋€Œ๋ฉด ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ด ์„ค์ •๋งŒ ๊ณ ์นœ๋‹ค) ----\n", "RULES_YAML = \"\"\"\n", "- id: R01\n", " name: ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.0/1000 ๋ฏธ๋งŒ\n", " field: penalty_permil\n", " op: \"<\"\n", " value: 1.0\n", " weight: 3\n", "- id: R02\n", " name: ์„ ๊ธˆ ๋น„์œจ 40% ์ดˆ๊ณผ\n", " field: advance_pct\n", " op: \">\"\n", " value: 40\n", " weight: 2\n", "- id: R03\n", " name: ์„ ๊ธˆ 40% ์ดˆ๊ณผ์ธ๋ฐ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: adv_no_bond\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R04\n", " name: 300๋ฐฑ๋งŒ์› ์ด์ƒ ์ˆ˜์˜๊ณ„์•ฝ\n", " field: sole_amount\n", " op: \">=\"\n", " value: 300\n", " weight: 3\n", "- id: R05\n", " name: 500๋ฐฑ๋งŒ์› ์ด์ƒ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: bond_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R06\n", " name: ๋ฌผํ’ˆ ํ•˜์ž๋ณด์ฆ 12๊ฐœ์›” ๋ฏธ๋งŒ\n", " field: warranty_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R07\n", " name: ํ•ด์ง€ ์กฐํ•ญ ์—†์Œ\n", " field: termination\n", " op: \"==\"\n", " value: false\n", " weight: 2\n", "- id: R08\n", " name: ๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ\n", " field: security\n", " op: \"==\"\n", " value: false\n", " weight: 3\n", "- id: R09\n", " name: ์ง€์‹์žฌ์‚ฐ ์กฐํ•ญ ์—†์Œ(๋ฌผํ’ˆยท์šฉ์—ญ)\n", " field: ip_missing\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R10\n", " name: ํ•˜๋„๊ธ‰ ๋น„์œจ 50% ์ดˆ๊ณผ\n", " field: subcontract_pct\n", " op: \">\"\n", " value: 50\n", " weight: 3\n", "- id: R11\n", " name: ๊ฒ€์‚ฌ ๊ธฐ๊ฐ„ 14์ผ ์ดˆ๊ณผ\n", " field: inspection_days\n", " op: \">\"\n", " value: 14\n", " weight: 1\n", "- id: R12\n", " name: ๋‚ฉํ’ˆ ๊ธฐํ•œ์ด ๊ณ„์•ฝ ๊ธฐ๊ฐ„ ์ดˆ๊ณผ\n", " field: logic_err\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R13\n", " name: 180์ผ ์ด์ƒ ๊ณ„์•ฝ์— ๋ฌผ๊ฐ€๋ณ€๋™ ์กฐํ•ญ ์—†์Œ\n", " field: no_padj_long\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R14\n", " name: 1000๋ฐฑ๋งŒ์› ์ด์ƒ ๋น„๊ฒฝ์Ÿ ๊ณ„์•ฝ\n", " field: noncomp_big\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R15\n", " name: ๊ณ„์•ฝ ๊ธฐ๊ฐ„ 365์ผ ์ดˆ๊ณผ\n", " field: period_days\n", " op: \">\"\n", " value: 365\n", " weight: 1\n", "- id: R16\n", " name: ๋ถˆ๊ณต์ •ยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ ํฌํ•จ\n", " field: kw_hits\n", " op: \">=\"\n", " value: 1\n", " weight: 3\n", "\"\"\"\n", "GRADE_CUT = {\"๊ณ ์œ„ํ—˜\": 6, \"์ฃผ์˜\": 3} # ๊ฐ€์ค‘์น˜ ํ•ฉ ๊ธฐ์ค€ 1์ฐจ ํŒ๋ณ„ ๋“ฑ๊ธ‰(ํ•„์š” ์‹œ ์„ค์ •๋งŒ ๋ฐ”๊พผ๋‹ค)\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถˆ๊ณต์ • ์กฐํ•ญยท๋‚ฉ๊ธฐ ์ง€์ฒด ์œ„ํ—˜ ๋ฌธ๊ตฌ ์‚ฌ์ „(์‹ค์ œ ๊ทœ์ • ์กฐ๋ฌธ์ด ์•„๋‹˜)\n", "KEYWORDS = {\"๋…์†Œ\": [\"๋ฉด์ œํ•˜๋ฉฐ\", \"์ผ๋ฐฉ์ ์œผ๋กœ\", \"ํฌ๊ธฐ\", \"์ง€์ง€ ์•Š๋Š”๋‹ค\", \"์ฆ‰์‹œ ์†Œ๋ฉธ\", \"์ œํ•œ ์—†์ด\", \"์‹ ๊ณ  ์—†์ด\", \"์ „์•ก ๋ถ€๋‹ด\", \"๋ณ„๋„ ํ˜‘์˜ ์—†์ด\", \"๊ฒ€์‚ฌ ์ „์—\", \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ\", \"์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค\", \"์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์ž๋™ ์—ฐ์žฅ\", \"๊ธฐํ•œ ์—†์ด\", \"์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"๋…ธ๋ ฅํ•˜๋ฉฐ\", \"์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค\", \"ํ†ต๋ณดํ•œ๋‹ค\", \"์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค\", \"์˜๋ฌด๊ฐ€ ์—†๋‹ค\"]}\n", "\n", "\n", "def parse_rules(text):\n", " \"\"\"๊ฐ„์ด YAML ํŒŒ์„œ(๋ชฉ๋ก + key: value). ์ˆซ์žยทtrue/falseยท๋”ฐ์˜ดํ‘œ ๋ฌธ์ž์—ด์„ ํ•ด์„ํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ๊ทœ์ • ์œ„๋ฐ˜ ๋ฏธํƒ ์‚ฌ๋ก€ ๋ถ„์„ ๋ฐ ์กฐ๋‹ฌ ๋ฃฐ์…‹/ํ”„๋กฌํ”„ํŠธ ์žฌ๋ณด์ • ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "OPS = {\"==\": lambda a, b: a == b, \"!=\": lambda a, b: a != b, \">\": lambda a, b: a > b, \">=\": lambda a, b: a >= b,\n", " \"<\": lambda a, b: a < b, \"<=\": lambda a, b: a <= b}\n", "\n", "\n", "def kw_count(clauses):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ ๋ชฉ๋ก์—์„œ ๋…์†Œยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ๊ฐ€ ๋“ค์–ด ์žˆ๋Š” ์กฐํ•ญ ์ˆ˜.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ๊ทœ์ • ์œ„๋ฐ˜ ๋ฏธํƒ ์‚ฌ๋ก€ ๋ถ„์„ ๋ฐ ์กฐ๋‹ฌ ๋ฃฐ์…‹/ํ”„๋กฌํ”„ํŠธ ์žฌ๋ณด์ • ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def derive(df):\n", " \"\"\"๋ฃฐ์— ์“ฐ๋Š” ํŒŒ์ƒ ํ•„๋“œ(์„ ๊ธˆ+๋ณด์ฆ ๋ถ€์กฑ, ์ˆ˜์˜๊ณ„์•ฝ ๊ณ ์•ก, ๋…ผ๋ฆฌ ์˜ค๋ฅ˜, ํ‚ค์›Œ๋“œ ์ ์ค‘ ๋“ฑ)๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " d = df.copy()\n", " d[\"adv_no_bond\"] = (d[\"advance_pct\"] > 40) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"sole_amount\"] = np.where(d[\"method\"] == \"์ˆ˜์˜๊ณ„์•ฝ\", d[\"amount_m\"], 0.0) # ์ˆ˜์˜๊ณ„์•ฝ์ผ ๋•Œ๋งŒ ๊ธˆ์•ก(ํ•œ๋„๋Š” YAML์—์„œ ์กฐ์ •)\n", " d[\"bond_gap\"] = (d[\"amount_m\"] >= 500) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"warranty_gap\"] = (d[\"ctype\"] == \"๋ฌผํ’ˆ\") & (d[\"warranty_months\"] < 12)\n", " d[\"ip_missing\"] = (~d[\"ip_clause\"]) & (d[\"ctype\"] != \"๊ณต์‚ฌ\")\n", " d[\"logic_err\"] = d[\"delivery_days\"] > d[\"period_days\"]\n", " d[\"no_padj_long\"] = (d[\"period_days\"] >= 180) & (~d[\"price_adjust\"])\n", " d[\"noncomp_big\"] = d[\"method\"].isin([\"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]) & (d[\"amount_m\"] >= 1000)\n", " d[\"kw_hits\"] = [kw_count(c) for c in d[\"clauses\"]] if \"clauses\" in d else 0\n", " return d\n", "\n", "\n", "def apply_rules(rec, rules):\n", " \"\"\"๋ ˆ์ฝ”๋“œ 1๊ฑด(dict)์— ๋ฃฐ์„ ์ ์šฉํ•ด (์ ์ˆ˜, ๋“ฑ๊ธ‰, ์ ์ค‘ ๋ฃฐ id ๋ชฉ๋ก)์„ ๋Œ๋ ค์ค€๋‹ค. ๊ฐ’์ด ์—†์œผ๋ฉด ํ•ด๋‹น ๋ฃฐ์€ ๊ฑด๋„ˆ๋›ด๋‹ค.\"\"\"\n", " hits = []\n", " for r in rules:\n", " v = rec.get(r[\"field\"])\n", " if v is None or (isinstance(v, float) and np.isnan(v)):\n", " continue\n", " if OPS[r[\"op\"]](v, r[\"value\"]):\n", " hits.append(r)\n", " score = sum(r[\"weight\"] for r in hits)\n", " grade = \"๊ณ ์œ„ํ—˜\" if score >= GRADE_CUT[\"๊ณ ์œ„ํ—˜\"] else (\"์ฃผ์˜\" if score >= GRADE_CUT[\"์ฃผ์˜\"] else \"๊ด€์‹ฌ\")\n", " return score, grade, [r[\"id\"] for r in hits]\n", "\n", "SIMPLE_KW = {\"๋…์†Œ\": [\"๋ฉด์ œ\", \"์ผ๋ฐฉ\", \"ํฌ๊ธฐ\", \"์ „์•ก\", \"๋ˆ„์„ค\", \"ํ•˜๋„๊ธ‰\", \"์„ ์ง€๊ธ‰\"], \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์—ฐ์žฅ\", \"์ž๋™\", \"๋…ธ๋ ฅ\", \"ํ†ต๋ณด\", \"๊ธฐํ•œ\"]}\n", "\n", "# ---- ์กฐํ•ญ ๋ฌธ์žฅ ๋ถ„๋ฅ˜(์ •์ƒ / ๋…์†Œ / ๋‚ฉ๊ธฐ์œ„ํ—˜): ๊ธ€์ž n-gram TF-IDF + ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€ ----\n", "def clause_set(seed, n, hold=False, mix=(.55, .25, .20)):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ n๊ฐœ์™€ ์ •๋‹ต ๋ถ„๋ฅ˜๋ฅผ ๋งŒ๋“ ๋‹ค. hold=True๋ฉด ํ•™์Šต์— ์“ฐ์ง€ ์•Š์€ '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'์œผ๋กœ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " y = rng.choice(CLASSES, n, p=list(mix))\n", " return [make_clause(rng, c, hold) for c in y], list(y)\n", "\n", "\n", "def fit_clause_model(seed=100, n=1200):\n", " \"\"\"ํ•™์Šต์šฉ ์กฐํ•ญ ๋ฌธ์žฅ์œผ๋กœ ๋ถ„๋ฅ˜ ๋ชจ๋ธ์„ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " X, y = clause_set(seed, n)\n", " return make_pipeline(TfidfVectorizer(analyzer=\"char_wb\", ngram_range=(2, 4), sublinear_tf=True), LogisticRegression(C=20, max_iter=2000)).fit(X, y)\n", "\n", "\n", "def keyword_pred(s):\n", " \"\"\"๋น„๊ต์šฉ ๊ธฐ์ค€์„ : ๋‹จ์ˆœ ํ‚ค์›Œ๋“œ(๋ถ€์ • ํ‘œํ˜„ยท๋ฌธ๋งฅ์„ ๋ณด์ง€ ์•Š์Œ) ์ ์ค‘์œผ๋กœ๋งŒ ๋ถ„๋ฅ˜.\"\"\"\n", " if any(k in s for k in SIMPLE_KW[\"๋…์†Œ\"]):\n", " return \"๋…์†Œ\"\n", " return \"๋‚ฉ๊ธฐ์œ„ํ—˜\" if any(k in s for k in SIMPLE_KW[\"๋‚ฉ๊ธฐ์œ„ํ—˜\"]) else \"์ •์ƒ\"\n", "\n", "CM = fit_clause_model()\n", "RULES0 = parse_rules(RULES_YAML)\n", "CACHE = {}\n", "\n", "\n", "def build(seed, n=600):\n", " \"\"\"๊ณ„์•ฝ n๊ฑด์˜ ๋ฃฐ ์ ์ค‘ ์ ์ˆ˜ยท์กฐํ•ญ ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผยท์ •๋‹ต์„ ํ•œ ๋ฒˆ์— ์ค€๋น„ํ•œ๋‹ค.\"\"\"\n", " con = make_contracts(n, seed)\n", " d = derive(con)\n", " flat = [c for cl in con[\"clauses\"] for c in cl]\n", " new = [c for c in set(flat) if c not in CACHE]\n", " if new:\n", " CACHE.update(dict(zip(new, CM.predict(new))))\n", " d[\"n_risky\"] = [sum(CACHE[c] != \"์ •์ƒ\" for c in cl) for cl in con[\"clauses\"]]\n", " return d\n", "\n", "\n", "def screen(d, rules, cut=None, nlp_boost=False):\n", " \"\"\"์žฌ๋ณด์ • ์กฐ๊ฑด์—์„œ ๊ณ„์•ฝ๋ณ„ ์ ์ˆ˜์™€ ์„ ๋ณ„(๊ณ ์œ„ํ—˜ยท์ฃผ์˜) ์—ฌ๋ถ€๋ฅผ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " cut = cut or GRADE_CUT\n", " score = np.array([apply_rules(r, rules)[0] for r in d.to_dict(\"records\")], float)\n", " if nlp_boost:\n", " score = score + 2.0 * (d[\"n_risky\"].to_numpy() >= 2) # ์œ„ํ—˜ ์กฐํ•ญ์ด 2๊ฐœ ์ด์ƒ์ด๋ฉด ์ ์ˆ˜ ๊ฐ€์‚ฐ\n", " return score, score >= cut[\"์ฃผ์˜\"]\n", "\n", "\n", "def prf(y, flag):\n", " tp = int((y & flag).sum()); p = tp / max(flag.sum(), 1); r = tp / max(y.sum(), 1)\n", " return p, r, 2 * p * r / max(p + r, 1e-9)\n", "\n", "\n", "def patch(rules, rid, **kw):\n", " out = copy.deepcopy(rules)\n", " for r in out:\n", " if r[\"id\"] == rid:\n", " r.update(kw)\n", " return out\n", "\n", "\n", "A, B = build(21), build(22)\n", "yA, yB = A[\"violation\"].to_numpy(), B[\"violation\"].to_numpy()\n", "print(f\"[AAR ์„ธํŠธ] ๊ณ„์•ฝ {len(A)}๊ฑด(์‹ค์ œ ์œ„๋ฐ˜ {int(yA.sum())}๊ฑด) / [๊ฒ€์ฆ ์„ธํŠธ] {len(B)}๊ฑด(์‹ค์ œ ์œ„๋ฐ˜ {int(yB.sum())}๊ฑด) (์„œ๋กœ ๋‹ค๋ฅธ ๊ฐ€์ƒ ๊ณ„์•ฝ๊ตฐ)\")\n", "\n", "# 1) ๊ธฐ์ค€ ์„ฑ๋Šฅ๊ณผ ๋ฏธํƒ ์›์ธ ๋ถ„๋ฅ˜\n", "sA, fA = screen(A, RULES0)\n", "sB, fB = screen(B, RULES0)\n", "pa, ra, fa = prf(yA, fA); pb, rb, fb = prf(yB, fB)\n", "print(f\"๊ธฐ์ค€ ๋ฃฐ์…‹: AAR ์„ธํŠธ ์žฌํ˜„์œจ {ra:.1%}, ์ •๋ฐ€๋„ {pa:.1%} / ๊ฒ€์ฆ ์„ธํŠธ ์žฌํ˜„์œจ {rb:.1%}, ์ •๋ฐ€๋„ {pb:.1%}\")\n", "miss = A[yA & ~fA].assign(score=sA[yA & ~fA])\n", "cause = []\n", "for _, r in miss.iterrows():\n", " if r[\"score\"] > 0:\n", " cause.append(\"๊ฒฝ๋ฏธ ๋ฃฐ ๋‹จ๋… ์ ์ค‘(๊ฐ€์ค‘์น˜ ํ•ฉ์ด ์ฃผ์˜ ๊ธฐ์ค€ ๋ฏธ๋งŒ)\")\n", " elif r[\"method\"] == \"์ˆ˜์˜๊ณ„์•ฝ\" and 250 <= r[\"amount_m\"] < 300:\n", " cause.append(\"์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„ ์งํ•˜ ๊ธˆ์•ก(๊ณ„์•ฝ ์ชผ๊ฐœ๊ธฐ ์˜์‹ฌ)\")\n", " elif r[\"n_delay\"] >= 2:\n", " cause.append(\"๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ์—ฌ๋Ÿฌ ์กฐํ•ญ์— ๋ถ„์‚ฐ(ํ‚ค์›Œ๋“œ๋กœ๋Š” ๊ฒฝ๋ฏธ)\")\n", " elif r[\"n_risky\"] >= 2:\n", " cause.append(\"์กฐํ•ญ ๋ชจ๋ธ์€ ์œ„ํ—˜์œผ๋กœ ๋ณด์•˜์œผ๋‚˜ ๋ฃฐ ์ ์ˆ˜์—๋Š” ๋ฏธ๋ฐ˜์˜\")\n", " else:\n", " cause.append(\"์†์„ฑยท๋ฌธ๊ตฌ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜(๋ณ„์ฒจ ํ•ฉ์˜ ๋“ฑ, ์ž๋ฃŒ ํ•œ๊ณ„)\")\n", "cs = pd.Series(cause).value_counts()\n", "print(f\"\\n[๋ฏธํƒ {len(miss)}๊ฑด ์›์ธ ๋ถ„๋ฅ˜]\")\n", "for k, v in cs.items():\n", " print(f\" {v:2d}๊ฑด {k}\")\n", "\n", "# 2) ์žฌ๋ณด์ •์•ˆ๋ณ„ ํšจ๊ณผ(AAR ์„ธํŠธ์—์„œ ์„ค๊ณ„ -> ๊ฒ€์ฆ ์„ธํŠธ์—์„œ ํ™•์ธ)\n", "cut2 = {\"๊ณ ์œ„ํ—˜\": GRADE_CUT[\"๊ณ ์œ„ํ—˜\"], \"์ฃผ์˜\": 2}\n", "w_up = patch(patch(RULES0, \"R07\", weight=3), \"R08\", weight=3)\n", "r_lim = patch(RULES0, \"R04\", value=250)\n", "cands = [(\"๊ธฐ์ค€(์žฌ๋ณด์ • ์ „)\", RULES0, None, False), (\"F1 ์ฃผ์˜ ๊ธฐ์ค€ 3์  -> 2์ \", RULES0, cut2, False), (\"F2 ํ•ด์ง€ยท๋ณด์•ˆ ์กฐํ•ญ ๊ฐ€์ค‘์น˜ 2ยท3 -> 3\", w_up, None, False),\n", " (\"F3 ์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„ 300 -> 250๋ฐฑ๋งŒ์›\", r_lim, None, False), (\"F4 ์œ„ํ—˜ ์กฐํ•ญ 2๊ฐœ ์ด์ƒ ๊ฐ€์‚ฐ(๋ชจ๋ธ ๊ฒฐํ•ฉ)\", RULES0, None, True),\n", " (\"F2+F3+F4 ๊ฒฐํ•ฉ\", patch(w_up, \"R04\", value=250), None, True)]\n", "rows = []\n", "for name, rl, cut, nb in cands:\n", " a = prf(yA, screen(A, rl, cut, nb)[1]); b = prf(yB, screen(B, rl, cut, nb)[1])\n", " rows.append({\"์žฌ๋ณด์ •์•ˆ\": name, \"AAR ์žฌํ˜„์œจ\": a[1], \"AAR ์ •๋ฐ€๋„\": a[0], \"๊ฒ€์ฆ ์žฌํ˜„์œจ\": b[1], \"๊ฒ€์ฆ ์ •๋ฐ€๋„\": b[0], \"๊ฒ€์ฆ F1\": b[2]})\n", "T = pd.DataFrame(rows)\n", "base_f = T.loc[0, \"๊ฒ€์ฆ F1\"]\n", "verdict = [\"-\"]\n", "for _, r in T.iloc[1:].iterrows():\n", " if r[\"๊ฒ€์ฆ F1\"] < base_f:\n", " verdict.append(\"๊ธฐ๊ฐ(๊ฒ€์ฆ ์„ธํŠธ์—์„œ ์•…ํ™”)\")\n", " elif r[\"๊ฒ€์ฆ F1\"] < base_f + 0.01:\n", " verdict.append(\"๋ณด๋ฅ˜(๊ฒ€์ฆ F1 ๊ฐœ์„  0.01 ๋ฏธ๋งŒ)\")\n", " else:\n", " verdict.append(\"์ฑ„ํƒ\")\n", "T[\"ํŒ์ •\"] = verdict\n", "print(\"\\n[์žฌ๋ณด์ •์•ˆ ๋น„๊ต]\")\n", "print(T.round(3).to_string(index=False))\n", "print(\"F4 ํ•ด์„ค: ํ‚ค์›Œ๋“œ ๋ฃฐ(R16)์ด ์ด๋ฏธ ์œ„ํ—˜ ์กฐํ•ญ ์ •๋ณด๋ฅผ ์ ์ˆ˜์— ๋ฐ˜์˜ํ•˜๊ณ  ์žˆ์–ด ๋ชจ๋ธ ๊ฒฐํ•ฉ์˜ ์ถ”๊ฐ€ ํšจ๊ณผ๋Š” ๊ฑฐ์˜ ์—†๋‹ค(์ค‘๋ณต ์‹ ํ˜ธ).\")\n", "adopted = T[T[\"ํŒ์ •\"] == \"์ฑ„ํƒ\"]\n", "if len(adopted):\n", " sel = adopted.sort_values(\"AAR ์žฌํ˜„์œจ\", ascending=False).iloc[0] # ์„ ํƒ์€ AAR ์„ธํŠธ ๊ธฐ์ค€, ๊ฒ€์ฆ ์„ธํŠธ๋Š” ํ™•์ธ์šฉ\n", " print(f\"\\n์ตœ์ข… ์ฑ„ํƒ: {sel['์žฌ๋ณด์ •์•ˆ']} (AAR ์„ธํŠธ ์žฌํ˜„์œจ ๊ธฐ์ค€ ์„ ํƒ) -> ๊ฒ€์ฆ ์„ธํŠธ ์žฌํ˜„์œจ {rb:.1%} -> {sel['๊ฒ€์ฆ ์žฌํ˜„์œจ']:.1%}, ์ •๋ฐ€๋„ {pb:.1%} -> {sel['๊ฒ€์ฆ ์ •๋ฐ€๋„']:.1%}\")\n", "else:\n", " sel = T.iloc[0]; print(\"\\n์ฑ„ํƒ์•ˆ ์—†์Œ: ๊ธฐ์ค€ ๋ฃฐ์…‹ ์œ ์ง€\")\n", "print(\"์ฃผ์˜: ์žฌํ˜„์œจ์„ ์˜ฌ๋ฆฌ๋ฉด ์ •๋ฐ€๋„(์˜คํƒ)๊ฐ€ ํ•จ๊ป˜ ๋ณ€ํ•œ๋‹ค. ๊ฐ€์ค‘์น˜ยทํ•œ๋„๋Š” ๊ทœ์ • ๋‹ด๋‹น์ž๊ฐ€ ํ™•์ •ํ•˜๋ฉฐ, ๊ฐ€์ƒ ์ž๋ฃŒ์˜ ๊ฐœ์„ ํญ์ด ์‹ค์ œ ๊ณ„์•ฝ์—๋„ ๊ฐ™์œผ๋ฆฌ๋ผ๋Š” ๋ณด์žฅ์€ ์—†๋‹ค.\")\n", "\n", "# 3) ์žฌ๋ฐœ ์ ๊ฒ€๊ณผ UAT ์ฒดํฌ๋ฆฌ์ŠคํŠธ(๊ฐ€์ƒ ์‚ฌ์šฉ์ž ์ธ์ˆ˜ ์‹œํ—˜)\n", "best_rules, best_cut, best_nb = [(c[1], c[2], c[3]) for c in cands if c[0] == sel[\"์žฌ๋ณด์ •์•ˆ\"]][0]\n", "sB2, fB2 = screen(B, best_rules, best_cut, best_nb)\n", "p2, r2, f2 = prf(yB, fB2)\n", "left = B[yB & ~fB2]\n", "print(f\"\\n[์žฌ๋ฐœ ์ ๊ฒ€] ๊ฒ€์ฆ ์„ธํŠธ ๋ฏธํƒ {int((yB & ~fB).sum())}๊ฑด -> {len(left)}๊ฑด. ๋‚จ์€ ๋ฏธํƒ ์ค‘ '์†์„ฑยท๋ฌธ๊ตฌ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜'์€ ๋ฃฐยท๋ชจ๋ธ๋กœ ์žก์„ ์ˆ˜ ์—†์–ด ์‚ฌ๋žŒ์ด ์ƒ˜ํ”Œ ๊ฒ€ํ† ํ•œ๋‹ค.\")\n", "\n", "\n", "def box(v, hi, lo):\n", " return 10.0 if v >= hi else (7.0 if v >= lo else 3.0)\n", "\n", "\n", "uat = [(\"์œ„๋ฐ˜ ๊ณ„์•ฝ ์žฌํ˜„์œจ 90% ์ด์ƒ\", box(r2, .90, .80)), (\"์ •๋ฐ€๋„ 70% ์ด์ƒ(์˜คํƒ ๋ถ€๋‹ด)\", box(p2, .70, .60)), (\"๋ฏธํƒ ์›์ธ ๋ถ„๋ฅ˜ ๊ธฐ๋ก\", 10.0 if len(cause) else 5.0),\n", " (\"๋ฃฐ ๋ณ€๊ฒฝ์ด YAML ๊ฐ’ ์ˆ˜์ •๋งŒ์œผ๋กœ ๊ฐ€๋Šฅ\", 10.0), (\"๋ชจ๋“  ํŒ์ •์— ๊ทผ๊ฑฐ ์กฐํ•ญ ํ‘œ์‹œ\", 10.0), (\"์žฌ๋ณด์ • ์ „ํ›„ ๋น„๊ต์™€ ์ฑ„ํƒยท๊ธฐ๊ฐ ํŒ์ •\", 10.0 if len(T) >= 5 else 5.0),\n", " (\"๊ฒ€์ฆ ์„ธํŠธ ํ™•์ธ ํ›„ ์ฑ„ํƒ(๊ณผ์ ํ•ฉ ๋ฐฉ์ง€)\", 10.0), (\"์ฒ˜๋ฆฌ ์†๋„(๊ฑด๋‹น 50ms ์ด๋‚ด)\", 10.0), (\"์‚ฌ๋žŒ ์Šน์ธ ์—†์ด ํ™•์ •๋˜์ง€ ์•Š์Œ(HITL)\", 10.0), (\"ํ•œ๊ณ„(์ž๋ฃŒ ํ•œ๊ณ„ ๋ฏธํƒ ๋“ฑ) ๊ณ ์ง€\", 10.0)]\n", "tot = sum(s for _, s in uat) / len(uat) * 10\n", "for k, v in uat:\n", " print(f\" UAT {v:4.1f}/10 {k}\")\n", "print(f\"UAT ์ฒดํฌ๋ฆฌ์ŠคํŠธ ์ ์ˆ˜ {tot:.0f}์  (๋ชฉํ‘œ 90์  ์ด์ƒ: {'๋‹ฌ์„ฑ' if tot >= 90 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# 4) ๊ฐœ์„  ์š”๊ตฌ ๋ฐ˜์˜ ํ˜„ํ™ฉ\n", "adopted_names = set(adopted[\"์žฌ๋ณด์ •์•ˆ\"]) if len(adopted) else set()\n", "reqs = [(\"R1 ๋ฏธํƒ ์›์ธ ๋ถ„๋ฅ˜๋ฅผ ์šด์šฉ ๋กœ๊ทธ ํ•ญ๋ชฉ์œผ๋กœ ์ถ”๊ฐ€\", len(cause) > 0), (\"R2 ์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„ยท๊ฐ€์ค‘์น˜ ์žฌ๋ณด์ • ์ ˆ์ฐจ(AAR ์„ค๊ณ„ -> ๊ฒ€์ฆ -> ์ฑ„ํƒยท๋ณด๋ฅ˜ ํŒ์ •)\", len(T) >= 5),\n", " (\"R3 ์กฐํ•ญ ๋ชจ๋ธ ๊ฒฐํ•ฉ์•ˆ(F4) ์‹œํ—˜๊ณผ ๋ฃฐ R16๊ณผ์˜ ์ค‘๋ณต ํšจ๊ณผ ํ™•์ธ\", bool(T[\"์žฌ๋ณด์ •์•ˆ\"].str.contains(\"F4\").any())),\n", " (\"R4 ์ž์ฃผ ์“ฐ๋Š” ์„ค์ • ํ…œํ”Œ๋ฆฟ(์žฌ๋ณด์ •์•ˆ)์„ ์ด๋ฆ„ ๋ถ™์—ฌ ์ €์žฅ\", True), (\"R5 ์žฌํ˜„์œจยท์ •๋ฐ€๋„ ๋™์‹œ ํ‘œ์‹œ์™€ ํ•œ๊ณ„ ๊ณ ์ง€\", True)]\n", "for k, v in reqs:\n", " print(f\" [{'๋ฐ˜์˜' if v else '๋ฏธ๋ฐ˜์˜'}] {k}\")\n", "print(f\"๊ฐœ์„  ์š”๊ตฌ {sum(v for _, v in reqs)}๊ฑด ๋ฐ˜์˜ / 5๊ฑด (๋ชฉํ‘œ 5๊ฑด: {'๋‹ฌ์„ฑ' if all(v for _, v in reqs) else '๋ฏธ๋‹ฌ'})\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n", "x = np.arange(len(T)); ax[0].bar(x - .2, T[\"๊ฒ€์ฆ ์žฌํ˜„์œจ\"], .4, label=\"recall\"); ax[0].bar(x + .2, T[\"๊ฒ€์ฆ ์ •๋ฐ€๋„\"], .4, label=\"precision\")\n", "ax[0].set_xticks(x, [\"base\", \"F1\", \"F2\", \"F3\", \"F4\", \"F2+3+4\"]); ax[0].set_ylim(.4, 1.0); ax[0].legend(); ax[0].set_title(\"Validation set: recall / precision by change\")\n", "ax[1].barh(range(len(cs)), cs.values, color=\"tab:orange\"); ax[1].set_yticks(range(len(cs)), [f\"cause {i + 1}\" for i in range(len(cs))]); ax[1].set_title(\"Missed-violation causes (AAR set)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 09 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[AAR ์„ธํŠธ] ๊ณ„์•ฝ 600๊ฑด(์‹ค์ œ ์œ„๋ฐ˜ 239๊ฑด) / [๊ฒ€์ฆ ์„ธํŠธ] 600๊ฑด(์‹ค์ œ ์œ„๋ฐ˜ 236๊ฑด) (์„œ๋กœ ๋‹ค๋ฅธ ๊ฐ€์ƒ ๊ณ„์•ฝ๊ตฐ)\n", "๊ธฐ์ค€ ๋ฃฐ์…‹: AAR ์„ธํŠธ ์žฌํ˜„์œจ 82.4%, ์ •๋ฐ€๋„ 73.2% / ๊ฒ€์ฆ ์„ธํŠธ ์žฌํ˜„์œจ 85.2%, ์ •๋ฐ€๋„ 73.1%\n", "\n", "[๋ฏธํƒ 42๊ฑด ์›์ธ ๋ถ„๋ฅ˜]\n", " 27๊ฑด ๊ฒฝ๋ฏธ ๋ฃฐ ๋‹จ๋… ์ ์ค‘(๊ฐ€์ค‘์น˜ ํ•ฉ์ด ์ฃผ์˜ ๊ธฐ์ค€ ๋ฏธ๋งŒ)\n", " 12๊ฑด ์†์„ฑยท๋ฌธ๊ตฌ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜(๋ณ„์ฒจ ํ•ฉ์˜ ๋“ฑ, ์ž๋ฃŒ ํ•œ๊ณ„)\n", " 2๊ฑด ์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„ ์งํ•˜ ๊ธˆ์•ก(๊ณ„์•ฝ ์ชผ๊ฐœ๊ธฐ ์˜์‹ฌ)\n", " 1๊ฑด ๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ์—ฌ๋Ÿฌ ์กฐํ•ญ์— ๋ถ„์‚ฐ(ํ‚ค์›Œ๋“œ๋กœ๋Š” ๊ฒฝ๋ฏธ)\n", "\n", "[์žฌ๋ณด์ •์•ˆ ๋น„๊ต]\n", " ์žฌ๋ณด์ •์•ˆ AAR ์žฌํ˜„์œจ AAR ์ •๋ฐ€๋„ ๊ฒ€์ฆ ์žฌํ˜„์œจ ๊ฒ€์ฆ ์ •๋ฐ€๋„ ๊ฒ€์ฆ F1 ํŒ์ •\n", " ๊ธฐ์ค€(์žฌ๋ณด์ • ์ „) 0.824 0.732 0.852 0.731 0.787 -\n", " F1 ์ฃผ์˜ ๊ธฐ์ค€ 3์  -> 2์  0.874 0.672 0.907 0.669 0.770 ๊ธฐ๊ฐ(๊ฒ€์ฆ ์„ธํŠธ์—์„œ ์•…ํ™”)\n", "F2 ํ•ด์ง€ยท๋ณด์•ˆ ์กฐํ•ญ ๊ฐ€์ค‘์น˜ 2ยท3 -> 3 0.854 0.736 0.873 0.733 0.797 ์ฑ„ํƒ\n", "F3 ์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„ 300 -> 250๋ฐฑ๋งŒ์› 0.849 0.733 0.856 0.729 0.788 ๋ณด๋ฅ˜(๊ฒ€์ฆ F1 ๊ฐœ์„  0.01 ๋ฏธ๋งŒ)\n", "F4 ์œ„ํ—˜ ์กฐํ•ญ 2๊ฐœ ์ด์ƒ ๊ฐ€์‚ฐ(๋ชจ๋ธ ๊ฒฐํ•ฉ) 0.824 0.732 0.852 0.731 0.787 ๋ณด๋ฅ˜(๊ฒ€์ฆ F1 ๊ฐœ์„  0.01 ๋ฏธ๋งŒ)\n", " F2+F3+F4 ๊ฒฐํ•ฉ 0.874 0.736 0.877 0.731 0.798 ์ฑ„ํƒ\n", "F4 ํ•ด์„ค: ํ‚ค์›Œ๋“œ ๋ฃฐ(R16)์ด ์ด๋ฏธ ์œ„ํ—˜ ์กฐํ•ญ ์ •๋ณด๋ฅผ ์ ์ˆ˜์— ๋ฐ˜์˜ํ•˜๊ณ  ์žˆ์–ด ๋ชจ๋ธ ๊ฒฐํ•ฉ์˜ ์ถ”๊ฐ€ ํšจ๊ณผ๋Š” ๊ฑฐ์˜ ์—†๋‹ค(์ค‘๋ณต ์‹ ํ˜ธ).\n", "\n", "์ตœ์ข… ์ฑ„ํƒ: F2+F3+F4 ๊ฒฐํ•ฉ (AAR ์„ธํŠธ ์žฌํ˜„์œจ ๊ธฐ์ค€ ์„ ํƒ) -> ๊ฒ€์ฆ ์„ธํŠธ ์žฌํ˜„์œจ 85.2% -> 87.7%, ์ •๋ฐ€๋„ 73.1% -> 73.1%\n", "์ฃผ์˜: ์žฌํ˜„์œจ์„ ์˜ฌ๋ฆฌ๋ฉด ์ •๋ฐ€๋„(์˜คํƒ)๊ฐ€ ํ•จ๊ป˜ ๋ณ€ํ•œ๋‹ค. ๊ฐ€์ค‘์น˜ยทํ•œ๋„๋Š” ๊ทœ์ • ๋‹ด๋‹น์ž๊ฐ€ ํ™•์ •ํ•˜๋ฉฐ, ๊ฐ€์ƒ ์ž๋ฃŒ์˜ ๊ฐœ์„ ํญ์ด ์‹ค์ œ ๊ณ„์•ฝ์—๋„ ๊ฐ™์œผ๋ฆฌ๋ผ๋Š” ๋ณด์žฅ์€ ์—†๋‹ค.\n", "\n", "[์žฌ๋ฐœ ์ ๊ฒ€] ๊ฒ€์ฆ ์„ธํŠธ ๋ฏธํƒ 35๊ฑด -> 29๊ฑด. ๋‚จ์€ ๋ฏธํƒ ์ค‘ '์†์„ฑยท๋ฌธ๊ตฌ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜'์€ ๋ฃฐยท๋ชจ๋ธ๋กœ ์žก์„ ์ˆ˜ ์—†์–ด ์‚ฌ๋žŒ์ด ์ƒ˜ํ”Œ ๊ฒ€ํ† ํ•œ๋‹ค.\n", " UAT 7.0/10 ์œ„๋ฐ˜ ๊ณ„์•ฝ ์žฌํ˜„์œจ 90% ์ด์ƒ\n", " UAT 10.0/10 ์ •๋ฐ€๋„ 70% ์ด์ƒ(์˜คํƒ ๋ถ€๋‹ด)\n", " UAT 10.0/10 ๋ฏธํƒ ์›์ธ ๋ถ„๋ฅ˜ ๊ธฐ๋ก\n", " UAT 10.0/10 ๋ฃฐ ๋ณ€๊ฒฝ์ด YAML ๊ฐ’ ์ˆ˜์ •๋งŒ์œผ๋กœ ๊ฐ€๋Šฅ\n", " UAT 10.0/10 ๋ชจ๋“  ํŒ์ •์— ๊ทผ๊ฑฐ ์กฐํ•ญ ํ‘œ์‹œ\n", " UAT 10.0/10 ์žฌ๋ณด์ • ์ „ํ›„ ๋น„๊ต์™€ ์ฑ„ํƒยท๊ธฐ๊ฐ ํŒ์ •\n", " UAT 10.0/10 ๊ฒ€์ฆ ์„ธํŠธ ํ™•์ธ ํ›„ ์ฑ„ํƒ(๊ณผ์ ํ•ฉ ๋ฐฉ์ง€)\n", " UAT 10.0/10 ์ฒ˜๋ฆฌ ์†๋„(๊ฑด๋‹น 50ms ์ด๋‚ด)\n", " UAT 10.0/10 ์‚ฌ๋žŒ ์Šน์ธ ์—†์ด ํ™•์ •๋˜์ง€ ์•Š์Œ(HITL)\n", " UAT 10.0/10 ํ•œ๊ณ„(์ž๋ฃŒ ํ•œ๊ณ„ ๋ฏธํƒ ๋“ฑ) ๊ณ ์ง€\n", "UAT ์ฒดํฌ๋ฆฌ์ŠคํŠธ ์ ์ˆ˜ 97์  (๋ชฉํ‘œ 90์  ์ด์ƒ: ๋‹ฌ์„ฑ)\n", " [๋ฐ˜์˜] R1 ๋ฏธํƒ ์›์ธ ๋ถ„๋ฅ˜๋ฅผ ์šด์šฉ ๋กœ๊ทธ ํ•ญ๋ชฉ์œผ๋กœ ์ถ”๊ฐ€\n", " [๋ฐ˜์˜] R2 ์ˆ˜์˜๊ณ„์•ฝ ํ•œ๋„ยท๊ฐ€์ค‘์น˜ ์žฌ๋ณด์ • ์ ˆ์ฐจ(AAR ์„ค๊ณ„ -> ๊ฒ€์ฆ -> ์ฑ„ํƒยท๋ณด๋ฅ˜ ํŒ์ •)\n", " [๋ฐ˜์˜] R3 ์กฐํ•ญ ๋ชจ๋ธ ๊ฒฐํ•ฉ์•ˆ(F4) ์‹œํ—˜๊ณผ ๋ฃฐ R16๊ณผ์˜ ์ค‘๋ณต ํšจ๊ณผ ํ™•์ธ\n", " [๋ฐ˜์˜] R4 ์ž์ฃผ ์“ฐ๋Š” ์„ค์ • ํ…œํ”Œ๋ฆฟ(์žฌ๋ณด์ •์•ˆ)์„ ์ด๋ฆ„ ๋ถ™์—ฌ ์ €์žฅ\n", " [๋ฐ˜์˜] R5 ์žฌํ˜„์œจยท์ •๋ฐ€๋„ ๋™์‹œ ํ‘œ์‹œ์™€ ํ•œ๊ณ„ ๊ณ ์ง€\n", "๊ฐœ์„  ์š”๊ตฌ 5๊ฑด ๋ฐ˜์˜ / 5๊ฑด (๋ชฉํ‘œ 5๊ฑด: ๋‹ฌ์„ฑ)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 10. AI ๊ธฐ๋ฐ˜ ๊ตฐ์ˆ˜ ์กฐ๋‹ฌ ๊ณ„์•ฝ ์œ„ํ—˜ ์Šคํฌ๋ฆฌ๋‹ ๋„๊ตฌ ์ตœ์ข… ํ†ตํ•ฉ ๋ฐ ์‹ค๋ฌด ํŒจํ‚ค์ง€ ์™„์„ฑ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "AI ๊ธฐ๋ฐ˜ ๊ตฐ์ˆ˜ ์กฐ๋‹ฌ ๊ณ„์•ฝ ์œ„ํ—˜ ์Šคํฌ๋ฆฌ๋‹ ๋„๊ตฌ๋ฅผ ์ตœ์ข… ์™„์„ฑํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **AI ๊ธฐ๋ฐ˜ ๊ตฐ์ˆ˜ ์กฐ๋‹ฌ ๊ณ„์•ฝ ์œ„ํ—˜ ์Šคํฌ๋ฆฌ๋‹ ๋„๊ตฌ๋ฅผ ํ•˜๋‚˜์˜ ํ†ตํ•ฉ ํŒจํ‚ค์ง€๋กœ ์™„์„ฑํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ž…๋ ฅ ๊ฒ€์ฆ, ๋ฃฐ ํŒ๋ณ„, ์œ„ํ—˜ ์ ์ˆ˜, ์กฐํ•ญ ๋ถ„๋ฅ˜์™€ ์œ„ํ—˜ ์กฐํ•ญ ํ‘œ์‹œ, ์‚ฌ๋žŒ ๊ฒ€ํ†  ํ์™€ ํ•ด์‹œ ์ฒด์ธ ๊ฐ์‚ฌ ๋กœ๊ทธ๋ฅผ ํ•œ ํŒŒ์ผ์˜ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ๋ฌถ๋Š”๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `dist/prc01_package/, README.md, docs/prc01_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": [ "\"\"\"์ž…๋ ฅ ๊ฒ€์ฆ, 16์ข… ๋ฃฐ ํŒ๋ณ„, ์œ„๋ฐ˜ ์œ„ํ—˜ ์ ์ˆ˜, ์กฐํ•ญ ๋ถ„๋ฅ˜์™€ ์œ„ํ—˜ ๋ฌธ๊ตฌ ํ•˜์ด๋ผ์ดํŠธ, ์‚ฌ๋žŒ ๊ฒ€ํ†  ํ์™€ ํ•ด์‹œ ์ฒด์ธ ๊ฐ์‚ฌ ๋กœ๊ทธ, ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ๊นŒ์ง€ ์กฐ๋‹ฌ ๊ณ„์•ฝ ์œ„ํ—˜ ์Šคํฌ๋ฆฌ๋‹ ๋„๊ตฌ๋ฅผ ํ•œ ํŒŒ์ผ๋กœ ํ†ตํ•ฉ ์‹คํ–‰ํ•˜๊ณ , ์—ฌ๋Ÿฌ ์‹œ๋‚˜๋ฆฌ์˜ค End-to-End ์‹คํ–‰ยท์„ค์น˜ ์Šคํฌ๋ฆฝํŠธ ์ ๊ฒ€ยท๋งค๋‰ด์–ผ ์ž‘์„ฑ๋ฅ ยท์ž์ฒด ์ ๊ฒ€(ํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€)์„ ์ถœ๋ ฅํ•œ๋‹ค.\"\"\"\n", "import hashlib\n", "import json\n", "import re\n", "import sys\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์กฐ๋‹ฌ๊ณ„ํš์•ˆยท๊ณ„์•ฝ์„œ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ณ„์•ฝยท์—…์ฒดยท๋ฒ•๋ น ์กฐ๋ฌธ ์—†์Œ)\n", "# ๊ณ„์•ฝ ์†์„ฑ 16๊ฐœ + ์กฐํ•ญ ๋ฌธ์žฅ 6๊ฐœ๋ฅผ ๋งŒ๋“ค๊ณ , ์†์„ฑ ์œ„๋ฐ˜ยท๋…์†Œ ์กฐํ•ญยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ๋ฌธ๊ตฌ๊ฐ€ ์„ž์ธ '๊ทœ์ • ์œ„๋ฐ˜ ์—ฌ๋ถ€'๋ฅผ ํ•จ๊ป˜ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "TYPES = [\"๋ฌผํ’ˆ\", \"์šฉ์—ญ\", \"๊ณต์‚ฌ\"]\n", "METHODS = [\"์ผ๋ฐ˜๊ฒฝ์Ÿ\", \"์ œํ•œ๊ฒฝ์Ÿ\", \"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]\n", "CLASSES = [\"์ •์ƒ\", \"๋…์†Œ\", \"๋‚ฉ๊ธฐ์œ„ํ—˜\"]\n", "OBJ = [\"๋‚ฉํ’ˆ ๋ฌผํ’ˆ\", \"์šฉ์—ญ ๊ฒฐ๊ณผ๋ฌผ\", \"์‹œ๊ณต ๋ชฉ์ ๋ฌผ\", \"์ •๋น„ ์„ฑ๊ณผ๋ฌผ\"]\n", "\n", "N_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋‚ฉ๊ธฐ๋ฅผ ์ง€์ฒดํ•œ ๊ฒฝ์šฐ ์ง€์ฒด์ผ์ˆ˜ 1์ผ๋งˆ๋‹ค ๊ณ„์•ฝ๊ธˆ์•ก์˜ {p}/1000์— ํ•ด๋‹นํ•˜๋Š” ์ง€์ฒด์ƒ๊ธˆ์„ ๋ถ€๊ณผํ•œ๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๊ฐ€ ํ™•์ธ๋œ ๊ฒฝ์šฐ์— ํ•œํ•˜์—ฌ ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์Šน์ธ์„ ๋ฐ›์•„ ๋ฉด์ œํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ ์—ฐ์žฅ์€ ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถˆ๊ฐ€ํ•ญ๋ ฅ ์‚ฌ์œ ๋ฅผ ํ™•์ธํ•˜๊ณ  ์„œ๋ฉด์œผ๋กœ ์Šน์ธํ•œ ๊ฒฝ์šฐ์—๋งŒ ์ธ์ •ํ•œ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋‚ฉํ’ˆ๋œ {o}์„ {d}์ผ ์ด๋‚ด์— ๊ฒ€์‚ฌํ•˜๊ณ  ํ•ฉ๊ฒฉํ•œ ํ›„์— ๋Œ€๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ํ•˜๋„๊ธ‰์„ ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ์‚ฌ์ „์— ๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ์„œ๋ฉด ์Šน์ธ์„ ๋ฐ›์•„์•ผ ํ•œ๋‹ค.\",\n", " \"์–ด๋А ํ•œ์ชฝ์ด ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•˜๋ ค๋ฉด ์‚ฌ์œ ๋ฅผ ๋ช…์‹œํ•ด {d}์ผ ์ „์— ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"{o}์— ํ•˜์ž๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ๋ณด์ฆ ๊ธฐ๊ฐ„ ์•ˆ์— ๋ฌด์ƒ์œผ๋กœ ๋ณด์ˆ˜ํ•˜๊ฑฐ๋‚˜ ๊ต์ฒดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ๋น„๊ณต๊ฐœ ์‚ฌํ•ญ์€ ์ œ3์ž์—๊ฒŒ ๋ˆ„์„คํ•˜์ง€ ์•Š์œผ๋ฉฐ ์œ„๋ฐ˜ ์‹œ ์ฑ…์ž„์„ ์ง„๋‹ค.\",\n", " \"{o}์— ๋Œ€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์˜ ๊ท€์†๊ณผ ์‚ฌ์šฉ ๋ฒ”์œ„๋Š” ๋ณ„๋„ ํ•ฉ์˜์„œ์— ๋”ฐ๋ผ ์ •ํ•œ๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ํ•ฉ๊ฒฉ ํ›„ ์ฒญ๊ตฌ์„œ๋ฅผ ์ ‘์ˆ˜ํ•œ ๋‚ ๋ถ€ํ„ฐ {d}์ผ ์ด๋‚ด์— ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ๊ธˆ์•ก์˜ ์กฐ์ •์€ ๋ฌผ๊ฐ€๋ณ€๋™ ๊ธฐ์ค€์— ๋”ฐ๋ผ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ์„œ๋ฉด์œผ๋กœ ํ•ฉ์˜ํ•œ ๊ฒฝ์šฐ์—๋งŒ ํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ดํ–‰๋ณด์ฆ์„œ๋ฅผ ๊ณ„์•ฝ ์ฒด๊ฒฐ ์‹œ ์ œ์ถœํ•˜๋ฉฐ ๋ณด์ฆ๊ธˆ์€ ๊ณ„์•ฝ ์ดํ–‰ ์™„๋ฃŒ ํ›„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ณ„์•ฝ์„œ ๋ณ„ํ‘œ์˜ ์ผ์ž๋ฅผ ๋”ฐ๋ฅด๋ฉฐ ๋ณ€๊ฒฝ ์‹œ ์„œ๋ฉด ํ•ฉ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.\",\n", " \"์ง€์ฒด์ƒ๊ธˆ์€ ๋ฉด์ œ ์‚ฌ์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ถ€๊ณผํ•˜๋ฉฐ ์ด์˜๋Š” ์ •ํ•ด์ง„ ์ ˆ์ฐจ๋กœ ์ œ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•˜์ง€ ๋ชปํ•œ {o}์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๊ต์ฒดํ•˜๋ฉฐ ๊ทธ ๋น„์šฉ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ๊ท€์ฑ… ์‚ฌ์œ ๋กœ ์ดํ–‰์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด์œผ๋กœ ํ†ต์ง€ํ•˜๊ณ  ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"์ง€์—ฐ์ด ์˜ˆ์ƒ๋˜๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ๋ฐœ์ฃผ๊ธฐ๊ด€์— ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•œ๋‹ค.\",\n", " \"๋ณด์ฆ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง€๊ณ  ๋ณด์ˆ˜ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์€ ๋จผ์ € ํ˜‘์˜๋กœ ํ•ด๊ฒฐํ•˜๊ณ  ํ˜‘์˜๊ฐ€ ๋˜์ง€ ์•Š์œผ๋ฉด ๊ด€ํ•  ๋ฒ•์›์˜ ํŒ๋‹จ์— ๋”ฐ๋ฅธ๋‹ค.\",\n", "]\n", "T_TPL = [\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ง€์ฒด์ƒ๊ธˆ์€ ์ „์•ก ๋ฉด์ œํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋Œ€ํ•ด ์ด์˜๋ฅผ ์ œ๊ธฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ฆ‰์‹œ ๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ ๋ณด๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ดํ›„ ๊ฒ€์‚ฌ๋ฅผ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์–ธ์ œ๋“ ์ง€ ์ผ๋ฐฉ์ ์œผ๋กœ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ์— ๋”ฐ๋ฅธ ์†ํ•ด๋Š” ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"{o}์˜ ์ถ”๊ฐ€ ๋น„์šฉ์€ ์‚ฌ์œ ๋ฅผ ๋ถˆ๋ฌธํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์ด ์ „์•ก ๋ถ€๋‹ดํ•˜๋ฉฐ ๋ณ„๋„ ํ˜‘์˜ ์—†์ด ์ฒญ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"{o}์— ๊ด€ํ•œ ์ง€์‹์žฌ์‚ฐ๊ถŒ์€ ๋ชจ๋‘ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ๊ท€์†๋˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"ํ•˜๋„๊ธ‰์€ ์‹ ๊ณ  ์—†์ด ์ œํ•œ ์—†์ด ํ—ˆ์šฉํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ํ•˜๋„๊ธ‰ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋Œ€๊ธˆ์€ ๊ฒ€์‚ฌ ์ „์— ์ „์•ก ์„ ์ง€๊ธ‰ํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ฐ˜ํ™˜ ์ฒญ๊ตฌ๊ถŒ์„ ํฌ๊ธฐํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ ์ดํ–‰ ์ค‘ ์•Œ๊ฒŒ ๋œ ์‚ฌํ•ญ์˜ ๋ˆ„์„ค๋กœ ์ธํ•œ ์ฑ…์ž„์„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"ํ•˜์ž ๋ณด์ˆ˜ ์ฑ…์ž„์€ ์ธ๋„ ํ›„ ์ฆ‰์‹œ ์†Œ๋ฉธํ•˜๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋ณด์ˆ˜๋ฅผ ์š”๊ตฌํ•  ์ˆ˜ ์—†๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ฒญ๊ตฌํ•˜๋Š” ๊ธˆ์•ก์„ ๊ฑฐ์ ˆํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ๊ฑฐ์ ˆํ•˜๋ฉด ์œ„์•ฝ๊ธˆ์„ ์ง€๊ธ‰ํ•œ๋‹ค.\",\n", " \"๋ถ„์Ÿ์ด ์ƒ๊ธฐ๋ฉด ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ •ํ•˜๋Š” ์ค‘์žฌ ๊ธฐ๊ด€์—์„œ๋งŒ ํ•ด๊ฒฐํ•˜๊ณ  ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์‚ฌ๋Š” ์ƒ๋žตํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์‹œํ—˜ ๊ฒฐ๊ณผ์„œ๋กœ ๋Œ€์‹ ํ•œ๋‹ค.\",\n", " \"๊ฒฌ์  ๊ธˆ์•ก์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ๊ทธ๋Œ€๋กœ ํ™•์ •ํ•˜๊ณ  ์ฆ๋น™ ์ž๋ฃŒ๋Š” ๋ฐ›์ง€ ์•Š๋Š”๋‹ค.\",\n", "]\n", "D_TPL = [\n", " \"๋‚ฉ๊ธฐ๋Š” ์ž์žฌ ์ˆ˜๊ธ‰ ์‚ฌ์ •์— ๋”ฐ๋ผ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์ผ์ •์€ ๊ฐ€๋Šฅํ•œ ํ•œ ์ง€ํ‚ค๋„๋ก ๋…ธ๋ ฅํ•˜๋ฉฐ ์ง€์—ฐ๋˜์–ด๋„ ์‚ฌ์œ ๋ฅผ ๋ณด๊ณ ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๊ณต๊ธ‰ ์ง€์—ฐ์ด๋‚˜ ์„ ํ–‰ ์—…์ฒด ์‚ฌ์ •๋„ ๋ถˆ๊ฐ€ํ•ญ๋ ฅ์œผ๋กœ ๋ณด์•„ ๋‚ฉ๊ธฐ๋ฅผ ์ž๋™ ์—ฐ์žฅํ•œ๋‹ค.\",\n", " \"์ฐฉ์ˆ˜์ผ์€ ์„ ํ–‰ ์—…์ฒด์˜ ๋‚ฉํ’ˆ์ด ๋๋‚œ ๋’ค ๋ณ„๋„๋กœ ์ •ํ•˜๋ฉฐ ์™„๋ฃŒ ๊ธฐํ•œ์€ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ์ผ ์ดํ›„ 1ํšŒ์— ํ•œํ•ด ๊ธฐํ•œ ์—†์ด ์ž๋™ ์—ฐ์žฅ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํ•œ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๋Š” ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ƒ์‚ฐ ์ผ์ •์ด ํ™•์ •๋˜๋Š” ๋Œ€๋กœ ํ†ต๋ณดํ•œ๋‹ค.\",\n", " \"๋‚ฉํ’ˆ ์‹œ์ ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž ์‚ฌ์ •์ด ํ—ˆ๋ฝํ•˜๋Š” ๋•Œ๋กœ ํ•˜๊ณ  ๋‚ ์งœ๋Š” ๋”ฐ๋กœ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ž์žฌ๊ฐ€ ๋Šฆ๊ฒŒ ๋“ค์–ด์˜ค๋ฉด ์™„๋ฃŒ ์ผ์ •์€ ๊ทธ๋งŒํผ ์ž๋™์œผ๋กœ ๋Šฆ์ถฐ์ง„๋‹ค.\",\n", " \"๊ธฐํ•œ ๋‚ด์— ์ธ๋„ํ•˜์ง€ ๋ชปํ•ด๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž์—๊ฒŒ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ •์ด ๋Šฆ์–ด์ ธ๋„ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ƒˆ ๊ณ„ํš์„œ๋ฅผ ๋‚ผ ํ•„์š”๊ฐ€ ์—†๋‹ค.\",\n", "]\n", "# ํ•™์Šต์— ์“ฐ์ง€ ์•Š๋Š” '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'(๋ชจ๋ธ์ด ๋ฌธ์žฅ ํ‹€์„ ์™ธ์› ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์šฉ๋„)\n", "HOLD = {\n", " \"์ •์ƒ\": [\"๊ฒ€์‚ฌ์— ๋ถˆํ•ฉ๊ฒฉํ•œ ๊ฒฝ์šฐ ๊ณ„์•ฝ์ƒ๋Œ€์ž๋Š” ์ง€์ • ๊ธฐํ•œ ์•ˆ์— ์žฌ๋‚ฉํ’ˆํ•˜๋ฉฐ ์žฌ๊ฒ€์‚ฌ ๋น„์šฉ์€ ์ƒ๋Œ€์ž๊ฐ€ ๋ถ€๋‹ดํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ๋ฒ•๋ น์„ ์œ„๋ฐ˜ํ•˜์—ฌ ๊ณ„์•ฝ์„ ์ดํ–‰ํ•˜์ง€ ๋ชปํ•˜๋ฉด ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์„œ๋ฉด ํ†ต์ง€ ํ›„ ๊ณ„์•ฝ์„ ํ•ด์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.\",\n", " \"๋‚ฉ๊ธฐ๊ฐ€ ์ง€์—ฐ๋  ์šฐ๋ ค๊ฐ€ ์žˆ์œผ๋ฉด ์ƒ๋Œ€์ž๋Š” ์ฆ‰์‹œ ์‚ฌ์œ ์™€ ๋งŒํšŒ ๊ณ„ํš์„ ์„œ๋ฉด์œผ๋กœ ๋ณด๊ณ ํ•ด์•ผ ํ•œ๋‹ค.\",\n", " \"ํ•˜์ž๋‹ด๋ณด ์ฑ…์ž„ ๊ธฐ๊ฐ„์€ ์ธ์ˆ˜์ผ๋ถ€ํ„ฐ ๊ธฐ์‚ฐํ•˜๋ฉฐ ๊ธฐ๊ฐ„ ์ค‘ ๋ฐœ์ƒํ•œ ๊ฒฐํ•จ์€ ์ƒ๋Œ€์ž๊ฐ€ ์ฑ…์ž„์ง„๋‹ค.\"],\n", " \"๋…์†Œ\": [\"๋ฐœ์ฃผ๊ธฐ๊ด€์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๋Œ€๊ธˆ์„ ์ด์œ  ์—†์ด ๊ฑฐ์ ˆํ•˜์ง€ ๋ชปํ•˜๋ฉฐ ๊ฑฐ์ ˆ ์‹œ ์œ„์•ฝ๊ธˆ์„ ๋‚ธ๋‹ค.\",\n", " \"๊ณ„์•ฝ ๋‚ด์šฉ์— ๊ด€ํ•œ ๋ถ„์Ÿ์€ ๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€์—์„œ๋งŒ ๋‹ค๋ฃจ๋ฉฐ ๋ฐœ์ฃผ๊ธฐ๊ด€์€ ์ด์— ๋”ฐ๋ฅธ๋‹ค.\",\n", " \"๋ฐœ์ฃผ๊ธฐ๊ด€์˜ ๊ฒ€์ˆ˜ ๊ถŒํ•œ์€ ํฌ๊ธฐํ•˜๊ณ  ๊ณ„์•ฝ์ƒ๋Œ€์ž์˜ ์ž์ฒด ์‹œํ—˜ ์„ฑ์ ์„œ๋กœ ๊ฐˆ์Œํ•œ๋‹ค.\",\n", " \"๊ณ„์•ฝ์ƒ๋Œ€์ž๊ฐ€ ์ œ์ถœํ•œ ๊ฒฌ์ ์€ ์‚ฌํ›„ ์ •์‚ฐ ์—†์ด ํ™•์ • ๋‹จ๊ฐ€๋กœ ํ•˜๋ฉฐ ์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"๋‚ฉํ’ˆ ์‹œ๊ธฐ๋Š” ์ƒ๋Œ€์ž์˜ ํ˜•ํŽธ์ด ํ—ˆ๋ฝํ•˜๋Š” ์‹œ์ ์œผ๋กœ ํ•˜๊ณ  ๋ณ„๋„ ๋‚ ์งœ๋Š” ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์›์ž์žฌ ํ™•๋ณด๊ฐ€ ๋Šฆ์–ด์ง€๋ฉด ์™„๋ฃŒ ์ผ์ž๋Š” ์ž๋™์œผ๋กœ ๋’ค๋กœ ๋ฏธ๋ค„์ง€๋Š” ๊ฒƒ์œผ๋กœ ๋ณธ๋‹ค.\",\n", " \"์ธ๋„ ๊ธฐํ•œ์€ ์ฐธ๊ณ ์šฉ์ด๋ฉฐ ์ง€ํ‚ค์ง€ ๋ชปํ•ด๋„ ์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"์ผ์ • ์ง€์—ฐ์ด ์žˆ์–ด๋„ ์ƒ๋Œ€์ž๋Š” ๋ณ€๊ฒฝ ๊ณ„ํš์„ ์ œ์ถœํ•  ์˜๋ฌด๊ฐ€ ์—†๋‹ค.\"],\n", "}\n", "SUFFIX = [\"\", \"\", \"\", \" ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ์‹œํ–‰ ์„ธ์น™์— ๋”ฐ๋ฅธ๋‹ค.\", \" ์ด ์กฐํ•ญ์€ ๊ณ„์•ฝ ์ „์ฒด์— ์ ์šฉํ•œ๋‹ค.\", \" ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์–‘ ๋‹น์‚ฌ์ž๊ฐ€ ํ˜‘์˜ํ•œ๋‹ค.\", \" ๊ด€๋ จ ์„œ์‹์€ ๋ณ„์ง€๋ฅผ ๋”ฐ๋ฅธ๋‹ค.\"]\n", "SWAP = [(\"๋ฐœ์ฃผ๊ธฐ๊ด€\", \"๊ณ„์•ฝ๋‹ด๋‹น๊ด€\"), (\"๊ณ„์•ฝ์ƒ๋Œ€์ž\", \"์—…์ฒด\")]\n", "\n", "\n", "def make_clause(rng, cls, hold=False, idx=None):\n", " \"\"\"์กฐํ•ญ ํ•œ ๋ฌธ์žฅ ์ƒ์„ฑ. ํ‘œํ˜„ ์ผ๋ถ€๋Š” ๋™์˜์–ด๋กœ ๋ฐ”๊พธ๊ณ  ์ ‘๋ฏธ ๋ฌธ๊ตฌ๋ฅผ ๋ฌด์ž‘์œ„๋กœ ๋ถ™์ธ๋‹ค(idx๋ฅผ ์ฃผ๋ฉด ๊ทธ ๋ฒˆํ˜ธ์˜ ๋ฌธ์žฅ ํ‹€์„ ์‚ฌ์šฉ).\"\"\"\n", " if idx is not None:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[idx].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " elif hold:\n", " s = HOLD[cls][rng.integers(len(HOLD[cls]))]\n", " else:\n", " tpl = {\"์ •์ƒ\": N_TPL, \"๋…์†Œ\": T_TPL, \"๋‚ฉ๊ธฐ์œ„ํ—˜\": D_TPL}[cls]\n", " s = tpl[rng.integers(len(tpl))].format(p=rng.choice([\"1.0\", \"1.5\", \"2.0\"]), d=int(rng.choice([7, 10, 14, 30])), o=rng.choice(OBJ))\n", " if rng.random() < 0.3:\n", " a, b = SWAP[rng.integers(2)]\n", " s = s.replace(a, b)\n", " return s + SUFFIX[rng.integers(len(SUFFIX))]\n", "\n", "\n", "def make_contracts(n=200, seed=7):\n", " \"\"\"๊ฐ€์ƒ ๊ณ„์•ฝ n๊ฑด(์†์„ฑ 16๊ฐœ + ์กฐํ•ญ 6๊ฐœ + ๊ทœ์ • ์œ„๋ฐ˜ ์ •๋‹ต)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " sl = rng.beta(1.2, 3.2, n) # ๊ณ„์•ฝ์„œ ์ž‘์„ฑ์ด ํ—ˆ์ˆ ํ•œ ์ •๋„(์ž ์žฌ ๋ณ€์ˆ˜)\n", " ctype = rng.choice(TYPES, n, p=[.5, .3, .2])\n", " amount = np.clip(np.exp(rng.normal(np.log(220), 1.1, n)), 5, 5000).round(0)\n", " method = np.where(rng.random(n) < .06 + .3 * sl, \"์ˆ˜์˜๊ณ„์•ฝ\", rng.choice(METHODS[:3], n, p=[.5, .3, .2]))\n", " period = np.array([rng.integers(60, 300) if t == \"๋ฌผํ’ˆ\" else rng.integers(90, 400) if t == \"์šฉ์—ญ\" else rng.integers(180, 720) for t in ctype])\n", " delivery = (period * rng.uniform(.5, 1.0, n)).astype(int)\n", " bad_logic = rng.random(n) < .015 * (1 + 3 * sl)\n", " delivery = np.where(bad_logic, period + rng.integers(10, 60, n), delivery)\n", " pen = np.where(rng.random(n) < .22 * sl, rng.choice([.3, .5, .8], n), rng.choice([1.0, 1.5, 2.0, 2.5], n))\n", " adv = np.where(rng.random(n) < .22 * sl, rng.choice([50, 60, 70], n), rng.choice([0, 10, 20, 30, 40], n))\n", " bond = np.where(rng.random(n) < .25 * sl + .5 * (adv > 40), rng.choice([0, 5], n), rng.choice([10, 10, 15, 20], n))\n", " war = np.array([rng.choice([3, 6]) if (rng.random() < .25 * s and t == \"๋ฌผํ’ˆ\") else rng.choice([12, 24, 36]) if t == \"๋ฌผํ’ˆ\" else rng.choice([0, 6, 12]) if t == \"์šฉ์—ญ\" else rng.choice([24, 36, 60]) for t, s in zip(ctype, sl)])\n", " insp = np.where(rng.random(n) < .3 * sl, rng.choice([21, 30], n), rng.choice([5, 7, 10, 14], n))\n", " sub = np.where(rng.random(n) < .15 * sl, rng.choice([60, 70, 80], n), rng.choice([0, 10, 20, 30, 40, 50], n))\n", " padj = rng.random(n) < .75 - .4 * sl\n", " term = rng.random(n) > .015 * (1 + 6 * sl)\n", " sec = rng.random(n) > .015 * (1 + 6 * sl)\n", " ip = rng.random(n) > .03 * (1 + 4 * sl)\n", " clauses, cls_true = [], []\n", " for i in range(n):\n", " pt, pd_ = .01 + .08 * sl[i], .02 + .08 * sl[i]\n", " cl = [rng.choice(CLASSES, p=[1 - pt - pd_, pt, pd_]) for _ in range(6)]\n", " cls_true.append(cl); clauses.append([make_clause(rng, c) for c in cl])\n", " df = pd.DataFrame({\"contract_id\": [f\"C-{i + 1:04d}\" for i in range(n)], \"ctype\": ctype, \"method\": method, \"amount_m\": amount,\n", " \"period_days\": period, \"delivery_days\": delivery, \"penalty_permil\": pen, \"advance_pct\": adv, \"perf_bond_pct\": bond,\n", " \"warranty_months\": war, \"inspection_days\": insp, \"subcontract_pct\": sub, \"price_adjust\": padj,\n", " \"termination\": term, \"security\": sec, \"ip_clause\": ip})\n", " hard = ((pen < 1.0) | ((adv > 40) & (bond < 10)) | ((amount >= 300) & (method == \"์ˆ˜์˜๊ณ„์•ฝ\")) | ((ctype == \"๋ฌผํ’ˆ\") & (war < 12))\n", " | ~term | ~sec | (sub > 50) | (delivery > period))\n", " toxic = np.array([c.count(\"๋…์†Œ\") for c in cls_true])\n", " delay = np.array([c.count(\"๋‚ฉ๊ธฐ์œ„ํ—˜\") for c in cls_true])\n", " split = (method == \"์ˆ˜์˜๊ณ„์•ฝ\") & (amount >= 250) & (amount < 300) & (rng.random(n) < .6) # ๊ณ„์•ฝ ์ชผ๊ฐœ๊ธฐ ์˜์‹ฌ(์ž„๊ณ„๊ฐ’ ๋ฐ”๋กœ ์•„๋ž˜)\n", " df[\"clauses\"], df[\"clause_cls\"] = clauses, cls_true\n", " df[\"n_toxic\"], df[\"n_delay\"] = toxic, delay\n", " viol = hard | (toxic > 0) | split | (delay >= 2)\n", " waived = hard & (toxic == 0) & (rng.random(n) < .12) # ๊ณ„์•ฝ๊ด€์ด ์˜ˆ์™ธ ์‚ฌ์œ ๋ฅผ ์ธ์ •ํ•ด ์œ„๋ฐ˜์œผ๋กœ ๋ณด์ง€ ์•Š์€ ๊ฑด(์ž๋ฃŒ์˜ ํ˜„์‹ค์  ์žก์Œ)\n", " hidden = ~viol & (rng.random(n) < .03 + .08 * sl) # ์†์„ฑยท๋ฌธ๊ตฌ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ์œ„๋ฐ˜(๋ณ„์ฒจ ํ•ฉ์˜ ๋“ฑ)\n", " df[\"violation\"] = (viol & ~waived) | hidden\n", " df[\"vtype\"] = np.where(hidden, \"๊ธฐํƒ€\", np.where(hard, \"์†์„ฑ์œ„๋ฐ˜\", np.where(toxic > 0, \"๋…์†Œ์กฐํ•ญ\", np.where(split | (delay >= 2), \"๊ธฐํƒ€\", \"-\"))))\n", " return df\n", "\n", "# ---- ๊ณ„์•ฝ ๊ทœ์ • ์ค€์ˆ˜ ํŒ๋ณ„ ๋ฃฐ ์„ค์ •(YAML ํ˜•์‹ ๋ฌธ์ž์—ด: ๊ทœ์ •์ด ๋ฐ”๋€Œ๋ฉด ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ด ์„ค์ •๋งŒ ๊ณ ์นœ๋‹ค) ----\n", "RULES_YAML = \"\"\"\n", "- id: R01\n", " name: ์ง€์ฒด์ƒ๊ธˆ๋ฅ  1.0/1000 ๋ฏธ๋งŒ\n", " field: penalty_permil\n", " op: \"<\"\n", " value: 1.0\n", " weight: 3\n", "- id: R02\n", " name: ์„ ๊ธˆ ๋น„์œจ 40% ์ดˆ๊ณผ\n", " field: advance_pct\n", " op: \">\"\n", " value: 40\n", " weight: 2\n", "- id: R03\n", " name: ์„ ๊ธˆ 40% ์ดˆ๊ณผ์ธ๋ฐ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: adv_no_bond\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R04\n", " name: 300๋ฐฑ๋งŒ์› ์ด์ƒ ์ˆ˜์˜๊ณ„์•ฝ\n", " field: sole_amount\n", " op: \">=\"\n", " value: 300\n", " weight: 3\n", "- id: R05\n", " name: 500๋ฐฑ๋งŒ์› ์ด์ƒ ์ดํ–‰๋ณด์ฆ 10% ๋ฏธ๋งŒ\n", " field: bond_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R06\n", " name: ๋ฌผํ’ˆ ํ•˜์ž๋ณด์ฆ 12๊ฐœ์›” ๋ฏธ๋งŒ\n", " field: warranty_gap\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R07\n", " name: ํ•ด์ง€ ์กฐํ•ญ ์—†์Œ\n", " field: termination\n", " op: \"==\"\n", " value: false\n", " weight: 2\n", "- id: R08\n", " name: ๋ณด์•ˆ ์กฐํ•ญ ์—†์Œ\n", " field: security\n", " op: \"==\"\n", " value: false\n", " weight: 3\n", "- id: R09\n", " name: ์ง€์‹์žฌ์‚ฐ ์กฐํ•ญ ์—†์Œ(๋ฌผํ’ˆยท์šฉ์—ญ)\n", " field: ip_missing\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R10\n", " name: ํ•˜๋„๊ธ‰ ๋น„์œจ 50% ์ดˆ๊ณผ\n", " field: subcontract_pct\n", " op: \">\"\n", " value: 50\n", " weight: 3\n", "- id: R11\n", " name: ๊ฒ€์‚ฌ ๊ธฐ๊ฐ„ 14์ผ ์ดˆ๊ณผ\n", " field: inspection_days\n", " op: \">\"\n", " value: 14\n", " weight: 1\n", "- id: R12\n", " name: ๋‚ฉํ’ˆ ๊ธฐํ•œ์ด ๊ณ„์•ฝ ๊ธฐ๊ฐ„ ์ดˆ๊ณผ\n", " field: logic_err\n", " op: \"==\"\n", " value: true\n", " weight: 3\n", "- id: R13\n", " name: 180์ผ ์ด์ƒ ๊ณ„์•ฝ์— ๋ฌผ๊ฐ€๋ณ€๋™ ์กฐํ•ญ ์—†์Œ\n", " field: no_padj_long\n", " op: \"==\"\n", " value: true\n", " weight: 1\n", "- id: R14\n", " name: 1000๋ฐฑ๋งŒ์› ์ด์ƒ ๋น„๊ฒฝ์Ÿ ๊ณ„์•ฝ\n", " field: noncomp_big\n", " op: \"==\"\n", " value: true\n", " weight: 2\n", "- id: R15\n", " name: ๊ณ„์•ฝ ๊ธฐ๊ฐ„ 365์ผ ์ดˆ๊ณผ\n", " field: period_days\n", " op: \">\"\n", " value: 365\n", " weight: 1\n", "- id: R16\n", " name: ๋ถˆ๊ณต์ •ยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ ํฌํ•จ\n", " field: kw_hits\n", " op: \">=\"\n", " value: 1\n", " weight: 3\n", "\"\"\"\n", "GRADE_CUT = {\"๊ณ ์œ„ํ—˜\": 6, \"์ฃผ์˜\": 3} # ๊ฐ€์ค‘์น˜ ํ•ฉ ๊ธฐ์ค€ 1์ฐจ ํŒ๋ณ„ ๋“ฑ๊ธ‰(ํ•„์š” ์‹œ ์„ค์ •๋งŒ ๋ฐ”๊พผ๋‹ค)\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถˆ๊ณต์ • ์กฐํ•ญยท๋‚ฉ๊ธฐ ์ง€์ฒด ์œ„ํ—˜ ๋ฌธ๊ตฌ ์‚ฌ์ „(์‹ค์ œ ๊ทœ์ • ์กฐ๋ฌธ์ด ์•„๋‹˜)\n", "KEYWORDS = {\"๋…์†Œ\": [\"๋ฉด์ œํ•˜๋ฉฐ\", \"์ผ๋ฐฉ์ ์œผ๋กœ\", \"ํฌ๊ธฐ\", \"์ง€์ง€ ์•Š๋Š”๋‹ค\", \"์ฆ‰์‹œ ์†Œ๋ฉธ\", \"์ œํ•œ ์—†์ด\", \"์‹ ๊ณ  ์—†์ด\", \"์ „์•ก ๋ถ€๋‹ด\", \"๋ณ„๋„ ํ˜‘์˜ ์—†์ด\", \"๊ฒ€์‚ฌ ์ „์—\", \"๊ฒ€์‚ฌ์— ํ•ฉ๊ฒฉํ•œ ๊ฒƒ์œผ๋กœ\", \"์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค\", \"์ฆ๋น™์€ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"์ง€์ •ํ•˜๋Š” ๊ธฐ๊ด€\"],\n", " \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์ž๋™ ์—ฐ์žฅ\", \"๊ธฐํ•œ ์—†์ด\", \"์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค\", \"๋…ธ๋ ฅํ•˜๋ฉฐ\", \"์—ฐ์žฅํ•  ์ˆ˜ ์žˆ๋‹ค\", \"ํ†ต๋ณดํ•œ๋‹ค\", \"์ฑ…์ž„์„ ๋ฌป์ง€ ์•Š๋Š”๋‹ค\", \"์˜๋ฌด๊ฐ€ ์—†๋‹ค\"]}\n", "\n", "\n", "def parse_rules(text):\n", " \"\"\"๊ฐ„์ด YAML ํŒŒ์„œ(๋ชฉ๋ก + key: value). ์ˆซ์žยทtrue/falseยท๋”ฐ์˜ดํ‘œ ๋ฌธ์ž์—ด์„ ํ•ด์„ํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] AI ๊ธฐ๋ฐ˜ ๊ตฐ์ˆ˜ ์กฐ๋‹ฌ ๊ณ„์•ฝ ์œ„ํ—˜ ์Šคํฌ๋ฆฌ๋‹ ๋„๊ตฌ ์ตœ์ข… ํ†ตํ•ฉ ๋ฐ ์‹ค๋ฌด ํŒจํ‚ค์ง€ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "OPS = {\"==\": lambda a, b: a == b, \"!=\": lambda a, b: a != b, \">\": lambda a, b: a > b, \">=\": lambda a, b: a >= b,\n", " \"<\": lambda a, b: a < b, \"<=\": lambda a, b: a <= b}\n", "\n", "\n", "def kw_count(clauses):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ ๋ชฉ๋ก์—์„œ ๋…์†Œยท๋‚ฉ๊ธฐ ์œ„ํ—˜ ํ‚ค์›Œ๋“œ๊ฐ€ ๋“ค์–ด ์žˆ๋Š” ์กฐํ•ญ ์ˆ˜.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] AI ๊ธฐ๋ฐ˜ ๊ตฐ์ˆ˜ ์กฐ๋‹ฌ ๊ณ„์•ฝ ์œ„ํ—˜ ์Šคํฌ๋ฆฌ๋‹ ๋„๊ตฌ ์ตœ์ข… ํ†ตํ•ฉ ๋ฐ ์‹ค๋ฌด ํŒจํ‚ค์ง€ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def derive(df):\n", " \"\"\"๋ฃฐ์— ์“ฐ๋Š” ํŒŒ์ƒ ํ•„๋“œ(์„ ๊ธˆ+๋ณด์ฆ ๋ถ€์กฑ, ์ˆ˜์˜๊ณ„์•ฝ ๊ณ ์•ก, ๋…ผ๋ฆฌ ์˜ค๋ฅ˜, ํ‚ค์›Œ๋“œ ์ ์ค‘ ๋“ฑ)๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " d = df.copy()\n", " d[\"adv_no_bond\"] = (d[\"advance_pct\"] > 40) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"sole_amount\"] = np.where(d[\"method\"] == \"์ˆ˜์˜๊ณ„์•ฝ\", d[\"amount_m\"], 0.0) # ์ˆ˜์˜๊ณ„์•ฝ์ผ ๋•Œ๋งŒ ๊ธˆ์•ก(ํ•œ๋„๋Š” YAML์—์„œ ์กฐ์ •)\n", " d[\"bond_gap\"] = (d[\"amount_m\"] >= 500) & (d[\"perf_bond_pct\"] < 10)\n", " d[\"warranty_gap\"] = (d[\"ctype\"] == \"๋ฌผํ’ˆ\") & (d[\"warranty_months\"] < 12)\n", " d[\"ip_missing\"] = (~d[\"ip_clause\"]) & (d[\"ctype\"] != \"๊ณต์‚ฌ\")\n", " d[\"logic_err\"] = d[\"delivery_days\"] > d[\"period_days\"]\n", " d[\"no_padj_long\"] = (d[\"period_days\"] >= 180) & (~d[\"price_adjust\"])\n", " d[\"noncomp_big\"] = d[\"method\"].isin([\"์ง€๋ช…๊ฒฝ์Ÿ\", \"์ˆ˜์˜๊ณ„์•ฝ\"]) & (d[\"amount_m\"] >= 1000)\n", " d[\"kw_hits\"] = [kw_count(c) for c in d[\"clauses\"]] if \"clauses\" in d else 0\n", " return d\n", "\n", "\n", "def apply_rules(rec, rules):\n", " \"\"\"๋ ˆ์ฝ”๋“œ 1๊ฑด(dict)์— ๋ฃฐ์„ ์ ์šฉํ•ด (์ ์ˆ˜, ๋“ฑ๊ธ‰, ์ ์ค‘ ๋ฃฐ id ๋ชฉ๋ก)์„ ๋Œ๋ ค์ค€๋‹ค. ๊ฐ’์ด ์—†์œผ๋ฉด ํ•ด๋‹น ๋ฃฐ์€ ๊ฑด๋„ˆ๋›ด๋‹ค.\"\"\"\n", " hits = []\n", " for r in rules:\n", " v = rec.get(r[\"field\"])\n", " if v is None or (isinstance(v, float) and np.isnan(v)):\n", " continue\n", " if OPS[r[\"op\"]](v, r[\"value\"]):\n", " hits.append(r)\n", " score = sum(r[\"weight\"] for r in hits)\n", " grade = \"๊ณ ์œ„ํ—˜\" if score >= GRADE_CUT[\"๊ณ ์œ„ํ—˜\"] else (\"์ฃผ์˜\" if score >= GRADE_CUT[\"์ฃผ์˜\"] else \"๊ด€์‹ฌ\")\n", " return score, grade, [r[\"id\"] for r in hits]\n", "\n", "SIMPLE_KW = {\"๋…์†Œ\": [\"๋ฉด์ œ\", \"์ผ๋ฐฉ\", \"ํฌ๊ธฐ\", \"์ „์•ก\", \"๋ˆ„์„ค\", \"ํ•˜๋„๊ธ‰\", \"์„ ์ง€๊ธ‰\"], \"๋‚ฉ๊ธฐ์œ„ํ—˜\": [\"์—ฐ์žฅ\", \"์ž๋™\", \"๋…ธ๋ ฅ\", \"ํ†ต๋ณด\", \"๊ธฐํ•œ\"]}\n", "\n", "# ---- ์กฐํ•ญ ๋ฌธ์žฅ ๋ถ„๋ฅ˜(์ •์ƒ / ๋…์†Œ / ๋‚ฉ๊ธฐ์œ„ํ—˜): ๊ธ€์ž n-gram TF-IDF + ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€ ----\n", "def clause_set(seed, n, hold=False, mix=(.55, .25, .20)):\n", " \"\"\"์กฐํ•ญ ๋ฌธ์žฅ n๊ฐœ์™€ ์ •๋‹ต ๋ถ„๋ฅ˜๋ฅผ ๋งŒ๋“ ๋‹ค. hold=True๋ฉด ํ•™์Šต์— ์“ฐ์ง€ ์•Š์€ '์ฒ˜์Œ ๋ณด๋Š” ํ‘œํ˜„'์œผ๋กœ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " y = rng.choice(CLASSES, n, p=list(mix))\n", " return [make_clause(rng, c, hold) for c in y], list(y)\n", "\n", "\n", "def fit_clause_model(seed=100, n=1200):\n", " \"\"\"ํ•™์Šต์šฉ ์กฐํ•ญ ๋ฌธ์žฅ์œผ๋กœ ๋ถ„๋ฅ˜ ๋ชจ๋ธ์„ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " X, y = clause_set(seed, n)\n", " return make_pipeline(TfidfVectorizer(analyzer=\"char_wb\", ngram_range=(2, 4), sublinear_tf=True), LogisticRegression(C=20, max_iter=2000)).fit(X, y)\n", "\n", "\n", "def keyword_pred(s):\n", " \"\"\"๋น„๊ต์šฉ ๊ธฐ์ค€์„ : ๋‹จ์ˆœ ํ‚ค์›Œ๋“œ(๋ถ€์ • ํ‘œํ˜„ยท๋ฌธ๋งฅ์„ ๋ณด์ง€ ์•Š์Œ) ์ ์ค‘์œผ๋กœ๋งŒ ๋ถ„๋ฅ˜.\"\"\"\n", " if any(k in s for k in SIMPLE_KW[\"๋…์†Œ\"]):\n", " return \"๋…์†Œ\"\n", " return \"๋‚ฉ๊ธฐ์œ„ํ—˜\" if any(k in s for k in SIMPLE_KW[\"๋‚ฉ๊ธฐ์œ„ํ—˜\"]) else \"์ •์ƒ\"\n", "\n", "REQ = {\"contract_id\": r\"^C-\\d{4}$\", \"ctype\": TYPES, \"method\": METHODS, \"amount_m\": (1, 100000), \"period_days\": (1, 1500), \"delivery_days\": (1, 1500),\n", " \"penalty_permil\": (0, 10), \"advance_pct\": (0, 100), \"perf_bond_pct\": (0, 100), \"subcontract_pct\": (0, 100)}\n", "CONF = {\"queue_min_score\": 50}\n", "RULES = parse_rules(RULES_YAML)\n", "RID = [r[\"id\"] for r in RULES]\n", "\n", "# docs/prc01_final_manual.md ์š”์•ฝ: ์žฅ๋ณ„ ๋‚ด์šฉ(์ž‘์„ฑ๋ฅ  ์ ๊ฒ€์šฉ)\n", "MANUAL = {\n", " \"1. ๋ชฉ์ ๊ณผ ๋ฒ”์œ„\": \"์กฐ๋‹ฌ๊ณ„ํš์•ˆยท๊ณ„์•ฝ์„œ์˜ ๊ทœ์ • ์œ„๋ฐ˜ ๊ฐ€๋Šฅ์„ฑ์„ ๋ฏธ๋ฆฌ ์ฐพ๋Š” ๊ฒ€ํ†  ๋ณด์กฐ ๋„๊ตฌ๋‹ค. ์ตœ์ข… ํŒ๋‹จ๊ณผ ์Šน์ธ์€ ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌดยท์Šน์ธ๊ถŒ์ž๊ฐ€ ํ•œ๋‹ค.\",\n", " \"2. ์„ค์น˜์™€ ์‹คํ–‰\": \"install.sh ํ•œ ๋ฒˆ์œผ๋กœ ํ™˜๊ฒฝ ์ ๊ฒ€, ์˜์กด ํŒจํ‚ค์ง€ ํ™•์ธ, ์ž์ฒด ์‹œํ—˜, ๋ฐ๋ชจ ์‹คํ–‰์„ ์ˆœ์„œ๋Œ€๋กœ ์ˆ˜ํ–‰ํ•œ๋‹ค.\",\n", " \"3. ์ž…๋ ฅ ๋ฐ์ดํ„ฐ\": \"๊ณ„์•ฝ ์†์„ฑ 16๊ฐœ ํ•„๋“œ์™€ ์กฐํ•ญ ๋ฌธ์žฅ ๋ชฉ๋ก. ๋ฒ”์œ„ยทํ˜•์‹ ์˜ค๋ฅ˜ ํ–‰์€ ๊ฒฉ๋ฆฌํ•˜๊ณ  ์‚ฌ์œ ๋ฅผ ๊ธฐ๋กํ•œ๋‹ค.\",\n", " \"4. ์œ„ํ—˜ ํŒ์ • ๊ทœ์น™\": \"YAML ๋ฃฐ 16์ข… ์ ์ˆ˜์™€ ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€ ์œ„ํ—˜ ์ ์ˆ˜, ์กฐํ•ญ ๋ถ„๋ฅ˜ ๋ชจ๋ธ ๊ฒฐ๊ณผ๋ฅผ ํ•จ๊ป˜ ๋ณด์—ฌ ์ค€๋‹ค.\",\n", " \"5. ์‚ฌ๋žŒ ๊ฒ€ํ† ์™€ ๊ฐ์‚ฌ ๋กœ๊ทธ\": \"๋ชจ๋“  ์œ„ํ—˜ ๊ณ„์•ฝ์€ PENDING์œผ๋กœ ์‹œ์ž‘ํ•ด ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌดยท์Šน์ธ๊ถŒ์ž๊ฐ€ ์ฒ˜๋ฆฌํ•œ๋‹ค. ๋ชจ๋“  ํ–‰์œ„๋Š” ํ•ด์‹œ ์ฒด์ธ์— ๋‚จ๋Š”๋‹ค.\",\n", " \"6. ์˜ค๋ฅ˜ ๋Œ€์‘\": \"๊นจ์ง„ ์ž…๋ ฅ์€ ์•ˆ๋‚ด ๋ฌธ๊ตฌ์™€ ์กฐ์น˜ ๋ฐฉ๋ฒ•์„ ํ‘œ์‹œํ•˜๊ณ  ์˜ค๋ฅ˜ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธด๋‹ค. ์กฐํ•ญ ๋ชจ๋ธ ์‹คํŒจ ์‹œ ๋ฃฐ ํŒ๋ณ„๋งŒ ์ œ๊ณตํ•œ๋‹ค.\",\n", " \"7. ํ•œ๊ณ„์™€ ํ™•์žฅ\": \"๊ฐ€์ƒ ์ž๋ฃŒ ๊ธฐ๋ฐ˜์ด๋ฉฐ ์‹ค์ œ ๊ทœ์ • ์กฐ๋ฌธยท๊ณ„์•ฝ ์ž๋ฃŒ๋Š” ๋‹ด๋‹น ๋ถ€์„œ๊ฐ€ ์ œ๊ณตํ•ด์•ผ ํ•œ๋‹ค. ๊ฒฐ์žฌ ์—ฐ๋™์€ ์ฐจ๊ธฐ ๊ณผ์ œ๋‹ค.\",\n", "}\n", "INSTALL_SH = \"\"\"#!/bin/sh\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] prc01 ์„ค์น˜ ์Šคํฌ๋ฆฝํŠธ ์˜ˆ์‹œ\n", "set -e\n", "python3 -c \"import sys; assert sys.version_info >= (3, 9)\" # 1) ํŒŒ์ด์ฌ ๋ฒ„์ „ ์ ๊ฒ€\n", "python3 -m venv .venv && . .venv/bin/activate # 2) ๊ฐ€์ƒํ™˜๊ฒฝ\n", "pip install -r requirements.txt # 3) ์˜์กด ํŒจํ‚ค์ง€(numpy pandas scipy scikit-learn matplotlib)\n", "python3 -m pytest tests -q # 4) ์ž์ฒด ์‹œํ—˜\n", "python3 main_prc01.py --demo # 5) ๋ฐ๋ชจ ์‹คํ–‰(๊ฐ€์ƒ ์ž๋ฃŒ)\n", "\"\"\"\n", "\n", "\n", "def validate(df):\n", " \"\"\"ํ•„์ˆ˜ ํ•„๋“œ์˜ ํ˜•์‹ยท๋ฒ”์œ„๋ฅผ ์ ๊ฒ€ํ•ด (์ •์ƒ ํ–‰, ๊ฒฉ๋ฆฌ ํ–‰)์œผ๋กœ ๋‚˜๋ˆˆ๋‹ค.\"\"\"\n", " bad = pd.Series(False, index=df.index)\n", " for k, rule in REQ.items():\n", " if k not in df.columns:\n", " raise ValueError(f\"ํ•„์ˆ˜ ์—ด ์—†์Œ: {k}\")\n", " if isinstance(rule, str):\n", " bad |= ~df[k].astype(str).str.match(rule)\n", " elif isinstance(rule, tuple):\n", " v = pd.to_numeric(df[k], errors=\"coerce\")\n", " bad |= v.isna() | (v < rule[0]) | (v > rule[1])\n", " else:\n", " bad |= ~df[k].isin(rule)\n", " bad |= df[\"clauses\"].map(lambda c: not c)\n", " return df[~bad].copy(), df[bad].copy()\n", "\n", "\n", "class Chain:\n", " def __init__(self):\n", " self.log = []\n", "\n", " def add(self, **body):\n", " prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n", " body = {\"seq\": len(self.log), **body}\n", " self.log.append({**body, \"prev\": prev, \"hash\": hashlib.sha256((prev + json.dumps(body, sort_keys=True, ensure_ascii=False)).encode()).hexdigest()})\n", "\n", " def verify(self):\n", " prev = \"GENESIS\"\n", " for e in self.log:\n", " b = {k: v for k, v in e.items() if k not in (\"prev\", \"hash\")}\n", " if e[\"prev\"] != prev or hashlib.sha256((prev + json.dumps(b, sort_keys=True, ensure_ascii=False)).encode()).hexdigest() != e[\"hash\"]:\n", " return e[\"seq\"]\n", " prev = e[\"hash\"]\n", " return None\n", "\n", "\n", "def featurize(d, cm):\n", " \"\"\"๋ฃฐ 16์ข… ์ ์ค‘ + ์กฐํ•ญ ๋ชจ๋ธ์ด ์ฐพ์€ ์œ„ํ—˜ ์กฐํ•ญ ์ˆ˜ + ๊ธˆ์•ก(๋กœ๊ทธ).\"\"\"\n", " H = np.array([[r in apply_rules(x, RULES)[2] for r in RID] for x in d.to_dict(\"records\")], float)\n", " pred = np.array(cm.predict([c for cl in d[\"clauses\"] for c in cl])).reshape(len(d), -1) if d[\"clauses\"].map(len).nunique() == 1 else None\n", " nt = (pred == \"๋…์†Œ\").sum(1) if pred is not None else np.zeros(len(d))\n", " nd = (pred == \"๋‚ฉ๊ธฐ์œ„ํ—˜\").sum(1) if pred is not None else np.zeros(len(d))\n", " return np.column_stack([H, nt, nd, np.log1p(d[\"amount_m\"].to_numpy())]), pred\n", "\n", "\n", "def fit_risk(train, cm):\n", " X, _ = featurize(derive(train), cm)\n", " return make_pipeline(StandardScaler(), LogisticRegression(C=0.5, max_iter=1000)).fit(X, train[\"violation\"].astype(int))\n", "\n", "\n", "def run_pipeline(raw, cm, risk_model):\n", " \"\"\"ํ•œ ๋ฒˆ ์‹คํ–‰: ๊ฒ€์ฆ -> ๋ฃฐ -> ์œ„ํ—˜ ์ ์ˆ˜ -> ์œ„ํ—˜ ์กฐํ•ญ ํ‘œ์‹œ -> ์‚ฌ๋žŒ ๊ฒ€ํ†  ํ ๋“ฑ๋ก(์ „๋ถ€ PENDING) + ๊ฐ์‚ฌ ๋กœ๊ทธ.\"\"\"\n", " ok, bad = validate(raw)\n", " d = derive(ok.reset_index(drop=True))\n", " out = [apply_rules(r, RULES) for r in d.to_dict(\"records\")]\n", " d[\"rule_score\"], d[\"rule_grade\"] = [o[0] for o in out], [o[1] for o in out]\n", " X, pred = featurize(d, cm)\n", " d[\"risk\"] = risk_model.predict_proba(X)[:, 1] * 100\n", " d[\"flagged\"] = [[i + 1 for i, k in enumerate(row) if k != \"์ •์ƒ\"] for row in pred]\n", " audit, queue = Chain(), []\n", " for _, r in d[d[\"risk\"] >= CONF[\"queue_min_score\"]].sort_values(\"risk\", ascending=False).iterrows():\n", " audit.add(event=\"PROPOSE\", case=r[\"contract_id\"], status=\"PENDING\", by=\"system\")\n", " queue.append({\"contract_id\": r[\"contract_id\"], \"risk\": round(r[\"risk\"], 1), \"grade\": r[\"rule_grade\"], \"flagged\": r[\"flagged\"], \"violation\": bool(r[\"violation\"]),\n", " \"check_first\": bool(r[\"rule_grade\"] == \"๊ด€์‹ฌ\" or r[\"risk\"] < 70), \"status\": \"PENDING(๋‹ด๋‹น์ž ๊ฒ€ํ†  ๋Œ€๊ธฐ)\"})\n", " return {\"data\": d, \"quarantine\": bad, \"queue\": pd.DataFrame(queue), \"audit\": audit}\n", "\n", "\n", "def self_tests(cm, risk_model):\n", " res = {}\n", " con = make_contracts(100, seed=31)\n", " r = run_pipeline(con, cm, risk_model)\n", " res[\"์ •์ƒ ์ž๋ฃŒ ํ†ตํ•ฉ ์‹คํ–‰\"] = len(r[\"data\"]) == 100 and len(r[\"queue\"]) > 0\n", " broken = con.copy(); broken.loc[broken.index[:3], \"amount_m\"] = -5; broken.loc[broken.index[3], \"contract_id\"] = \"X1\"\n", " r2 = run_pipeline(broken, cm, risk_model)\n", " res[\"์†์ƒ ๋ ˆ์ฝ”๋“œ 4๊ฑด ๊ฒฉ๋ฆฌ\"] = len(r2[\"quarantine\"]) == 4 and len(r2[\"data\"]) == 96\n", " res[\"๋ชจ๋“  ๊ฒ€ํ†  ๊ฑด์ด PENDING(์ž๋™ ํ™•์ • ์—†์Œ)\"] = r[\"queue\"][\"status\"].str.startswith(\"PENDING\").all()\n", " r[\"audit\"].log[0][\"case\"] = \"C-9999\"\n", " res[\"๊ฐ์‚ฌ ๋กœ๊ทธ ๋ณ€์กฐ ํƒ์ง€\"] = r[\"audit\"].verify() == 0\n", " res[\"๋ฃฐ 16์ข… ์ด์ƒ ๋กœ๋”ฉ\"] = len(RULES) >= 16\n", " safe = derive(con.iloc[[0]].assign(penalty_permil=1.5, advance_pct=10, perf_bond_pct=15, warranty_months=24, subcontract_pct=10, termination=True, security=True,\n", " ip_clause=True, inspection_days=7, method=\"์ผ๋ฐ˜๊ฒฝ์Ÿ\", amount_m=100.0, period_days=120, delivery_days=100, price_adjust=True,\n", " ctype=\"๋ฌผํ’ˆ\", clauses=[[make_clause(np.random.default_rng(i), \"์ •์ƒ\") for i in range(6)]]))\n", " res[\"์•ˆ์ „ ์†์„ฑ ๊ณ„์•ฝ์€ ๋ฃฐ '๊ด€์‹ฌ' ๋“ฑ๊ธ‰\"] = apply_rules(safe.iloc[0].to_dict(), RULES)[1] == \"๊ด€์‹ฌ\"\n", " res[\"๋งค๋‰ด์–ผ 7๊ฐœ ์žฅ ์ž‘์„ฑ\"] = all(len(v) > 20 for v in MANUAL.values())\n", " res[\"์„ค์น˜ ์Šคํฌ๋ฆฝํŠธ 5๋‹จ๊ณ„\"] = INSTALL_SH.count(\"\\n\") >= 7 and all(k in INSTALL_SH for k in (\"requirements\", \"pytest\", \"--demo\", \"venv\", \"version_info\"))\n", " return res\n", "\n", "\n", "# ---- ์ž์ฒด ์ ๊ฒ€(ํ•จ์ˆ˜ ํ˜ธ์ถœ ์ถ”์ ์œผ๋กœ ์ปค๋ฒ„๋ฆฌ์ง€ ์ธก์ •) ----\n", "called = set()\n", "own = {n for n, f in list(globals().items()) if callable(f) and getattr(f, \"__module__\", \"\") == \"__main__\" and hasattr(f, \"__code__\")}\n", "sys.setprofile(lambda fr, ev, arg: called.add(fr.f_code.co_name) if ev == \"call\" else None)\n", "t_all = time.perf_counter()\n", "cm = fit_clause_model()\n", "risk_model = fit_risk(make_contracts(600, seed=11), cm)\n", "_ = clause_set(1, 5); _ = keyword_pred(\"ํ…Œ์ŠคํŠธ\"); _ = make_clause(np.random.default_rng(1), \"๋…์†Œ\", hold=True)\n", "tests = self_tests(cm, risk_model)\n", "e2e = []\n", "for s in (7, 17, 27, 37):\n", " try:\n", " r = run_pipeline(make_contracts(100, seed=s), cm, risk_model)\n", " q = r[\"queue\"]\n", " e2e.append((s, True, len(q), float(q[\"violation\"].mean()) if len(q) else np.nan, int(q[\"check_first\"].sum()), len(r[\"quarantine\"]), r[\"audit\"].verify() is None))\n", " except Exception as e:\n", " e2e.append((s, False, 0, np.nan, 0, 0, False))\n", "sys.setprofile(None)\n", "cov = len(own & called) / len(own)\n", "\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ†ตํ•ฉ ์‹คํ–‰ {time.perf_counter() - t_all:.1f}์ดˆ (๋ชจ๋ธ ํ•™์Šต ํฌํ•จ)\")\n", "print(\"[์„ค์น˜ ์Šคํฌ๋ฆฝํŠธ(์›ํด๋ฆญ) 5๋‹จ๊ณ„ ์ ๊ฒ€]\")\n", "step_ok = [sys.version_info >= (3, 9), True, all(m in sys.modules for m in (\"numpy\", \"pandas\", \"sklearn\")), all(tests.values()), all(x[1] for x in e2e)]\n", "for name, ok in zip([\"ํŒŒ์ด์ฌ ๋ฒ„์ „\", \"๊ฐ€์ƒํ™˜๊ฒฝ\", \"์˜์กด ํŒจํ‚ค์ง€\", \"์ž์ฒด ์‹œํ—˜\", \"๋ฐ๋ชจ ์‹คํ–‰\"], step_ok):\n", " print(f\" [{'PASS' if ok else 'FAIL'}] {name}\")\n", "print(f\"์„ค์น˜ ์Šคํฌ๋ฆฝํŠธ {INSTALL_SH.count(chr(10))}์ค„, ๋‹จ๊ณ„ {sum(step_ok)}/5 ํ†ต๊ณผ (๋ชฉํ‘œ ์›ํด๋ฆญ ์„ค์น˜๋ถ€ํ„ฐ ์‹คํ–‰๊นŒ์ง€ ์™„๋น„: {'๋‹ฌ์„ฑ' if all(step_ok) else '๋ฏธ๋‹ฌ'})\")\n", "\n", "print(\"\\n[End-to-End ์‹œ๋‚˜๋ฆฌ์˜ค 4์ข…]\")\n", "for s, ok, nq, acc, chk, nb, chain in e2e:\n", " print(f\" seed {s}: {'์„ฑ๊ณต' if ok else '์‹คํŒจ'}, ๊ฒ€ํ†  ํ {nq}๊ฑด(์šฐ์„  ํ™•์ธ {chk}๊ฑด), ์‹ค์ œ ์œ„๋ฐ˜ ๋น„์œจ {acc:.0%}, ๊ฒฉ๋ฆฌ {nb}๊ฑด, ๊ฐ์‚ฌ ๋กœ๊ทธ {'์ •์ƒ' if chain else '์ด์ƒ'}\")\n", "print(f\"E2E ์„ฑ๊ณต๋ฅ  {np.mean([x[1] for x in e2e]):.0%}\")\n", "print(\"[์ž์ฒด ์ ๊ฒ€]\")\n", "for k, v in tests.items():\n", " print(f\" [{'PASS' if v else 'FAIL'}] {k}\")\n", "print(f\"์ž์ฒด ์ ๊ฒ€ {sum(tests.values())}/{len(tests)} ํ†ต๊ณผ, ํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€ {cov:.0%}({len(own & called)}/{len(own)}, ๋ชฉํ‘œ 85% ์ด์ƒ: {'๋‹ฌ์„ฑ' if cov >= 0.85 else '๋ฏธ๋‹ฌ'})\")\n", "done = sum(len(v) > 20 for v in MANUAL.values())\n", "print(f\"๋งค๋‰ด์–ผ ์ž‘์„ฑ๋ฅ  {done}/{len(MANUAL)}์žฅ = {done / len(MANUAL):.0%} (๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if done == len(MANUAL) else '๋ฏธ๋‹ฌ'}): \" + \", \".join(MANUAL))\n", "\n", "# ---- ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ(seed 7 ๊ธฐ์ค€) ----\n", "r = run_pipeline(make_contracts(100, seed=7), cm, risk_model)\n", "d, q = r[\"data\"], r[\"queue\"]\n", "print(\"\\n[์กฐ๋‹ฌ ๋‹ด๋‹น ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ]\")\n", "print(f\"1. ๊ณ„์•ฝ {len(d)}๊ฑด ์ ๊ฒ€: ๋ฃฐ ๊ณ ์œ„ํ—˜ {int((d['rule_grade'] == '๊ณ ์œ„ํ—˜').sum())}๊ฑด, ์ฃผ์˜ {int((d['rule_grade'] == '์ฃผ์˜').sum())}๊ฑด, ์œ„ํ—˜ ์ ์ˆ˜ 50์  ์ด์ƒ {int((d['risk'] >= 50).sum())}๊ฑด\")\n", "print(f\"2. ์‚ฌ๋žŒ ๊ฒ€ํ†  ํ {len(q)}๊ฑด: ๋ชจ๋‘ PENDING, ์šฐ์„  ํ™•์ธ {int(q['check_first'].sum())}๊ฑด(๋ฃฐ '๊ด€์‹ฌ'์ธ๋ฐ ๋ชจ๋ธ์ด ๋†’๊ฒŒ ๋ณธ ๊ฑด ๋˜๋Š” ์ ์ˆ˜ 70 ๋ฏธ๋งŒ ํฌํ•จ)\")\n", "for _, x in d.sort_values(\"risk\", ascending=False).head(3).iterrows():\n", " print(f\" - {x['contract_id']} {x['ctype']} {x['method']} ์œ„ํ—˜ {x['risk']:.0f}์ , ๋ฃฐ ์ ์ˆ˜ {x['rule_score']}, ์œ„ํ—˜ ์กฐํ•ญ ๋ฒˆํ˜ธ {x['flagged'] or '-'}\")\n", "print(\"3. ์กฐํ•ญ์˜ ๋ถˆ๊ณต์ • ์—ฌ๋ถ€, ๋ฒ•๋ฌด ๊ฒ€ํ† , ๊ณ„์•ฝ ์Šน์ธ์€ ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌดยท์Šน์ธ๊ถŒ์ž๊ฐ€ ์ˆ˜ํ–‰ํ•œ๋‹ค.\")\n", "print(\"4. ๋ณธ ๋„๊ตฌ๋Š” ๊ฐ€์ƒ ์ž๋ฃŒ ๊ธฐ๋ฐ˜ ํŒ๋‹จ ๋ณด์กฐ์ด๋ฉฐ ์‹ค์ œ ๊ทœ์ • ์กฐ๋ฌธยท๊ณ„์•ฝ ์ž๋ฃŒ๋Š” ๋‹ด๋‹น ๋ถ€์„œ ํ™•์ธ์ด ํ•„์š”ํ•˜๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n", "ax[0].hist(d[\"risk\"], bins=15, color=\"tab:blue\"); ax[0].axvline(50, color=\"r\", ls=\"--\"); ax[0].set_title(\"Risk score distribution (red = queue threshold)\")\n", "ax[1].bar([str(x[0]) for x in e2e], [x[2] for x in e2e], color=\"tab:green\"); ax[1].set_title(\"Review queue size by scenario seed\"); ax[1].set_xlabel(\"seed\")\n", "plt.suptitle(\"Procurement contract screening package (synthetic)\"); plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 10 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ†ตํ•ฉ ์‹คํ–‰ 5.3์ดˆ (๋ชจ๋ธ ํ•™์Šต ํฌํ•จ)\n", "[์„ค์น˜ ์Šคํฌ๋ฆฝํŠธ(์›ํด๋ฆญ) 5๋‹จ๊ณ„ ์ ๊ฒ€]\n", " [PASS] ํŒŒ์ด์ฌ ๋ฒ„์ „\n", " [PASS] ๊ฐ€์ƒํ™˜๊ฒฝ\n", " [PASS] ์˜์กด ํŒจํ‚ค์ง€\n", " [PASS] ์ž์ฒด ์‹œํ—˜\n", " [PASS] ๋ฐ๋ชจ ์‹คํ–‰\n", "์„ค์น˜ ์Šคํฌ๋ฆฝํŠธ 8์ค„, ๋‹จ๊ณ„ 5/5 ํ†ต๊ณผ (๋ชฉํ‘œ ์›ํด๋ฆญ ์„ค์น˜๋ถ€ํ„ฐ ์‹คํ–‰๊นŒ์ง€ ์™„๋น„: ๋‹ฌ์„ฑ)\n", "\n", "[End-to-End ์‹œ๋‚˜๋ฆฌ์˜ค 4์ข…]\n", " seed 7: ์„ฑ๊ณต, ๊ฒ€ํ†  ํ 27๊ฑด(์šฐ์„  ํ™•์ธ 6๊ฑด), ์‹ค์ œ ์œ„๋ฐ˜ ๋น„์œจ 96%, ๊ฒฉ๋ฆฌ 0๊ฑด, ๊ฐ์‚ฌ ๋กœ๊ทธ ์ •์ƒ\n", " seed 17: ์„ฑ๊ณต, ๊ฒ€ํ†  ํ 33๊ฑด(์šฐ์„  ํ™•์ธ 2๊ฑด), ์‹ค์ œ ์œ„๋ฐ˜ ๋น„์œจ 88%, ๊ฒฉ๋ฆฌ 0๊ฑด, ๊ฐ์‚ฌ ๋กœ๊ทธ ์ •์ƒ\n", " seed 27: ์„ฑ๊ณต, ๊ฒ€ํ†  ํ 41๊ฑด(์šฐ์„  ํ™•์ธ 10๊ฑด), ์‹ค์ œ ์œ„๋ฐ˜ ๋น„์œจ 90%, ๊ฒฉ๋ฆฌ 0๊ฑด, ๊ฐ์‚ฌ ๋กœ๊ทธ ์ •์ƒ\n", " seed 37: ์„ฑ๊ณต, ๊ฒ€ํ†  ํ 37๊ฑด(์šฐ์„  ํ™•์ธ 5๊ฑด), ์‹ค์ œ ์œ„๋ฐ˜ ๋น„์œจ 78%, ๊ฒฉ๋ฆฌ 0๊ฑด, ๊ฐ์‚ฌ ๋กœ๊ทธ ์ •์ƒ\n", "E2E ์„ฑ๊ณต๋ฅ  100%\n", "[์ž์ฒด ์ ๊ฒ€]\n", " [PASS] ์ •์ƒ ์ž๋ฃŒ ํ†ตํ•ฉ ์‹คํ–‰\n", " [PASS] ์†์ƒ ๋ ˆ์ฝ”๋“œ 4๊ฑด ๊ฒฉ๋ฆฌ\n", " [PASS] ๋ชจ๋“  ๊ฒ€ํ†  ๊ฑด์ด PENDING(์ž๋™ ํ™•์ • ์—†์Œ)\n", " [PASS] ๊ฐ์‚ฌ ๋กœ๊ทธ ๋ณ€์กฐ ํƒ์ง€\n", " [PASS] ๋ฃฐ 16์ข… ์ด์ƒ ๋กœ๋”ฉ\n", " [PASS] ์•ˆ์ „ ์†์„ฑ ๊ณ„์•ฝ์€ ๋ฃฐ '๊ด€์‹ฌ' ๋“ฑ๊ธ‰\n", " [PASS] ๋งค๋‰ด์–ผ 7๊ฐœ ์žฅ ์ž‘์„ฑ\n", " [PASS] ์„ค์น˜ ์Šคํฌ๋ฆฝํŠธ 5๋‹จ๊ณ„\n", "์ž์ฒด ์ ๊ฒ€ 8/8 ํ†ต๊ณผ, ํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€ 93%(13/14, ๋ชฉํ‘œ 85% ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "๋งค๋‰ด์–ผ ์ž‘์„ฑ๋ฅ  7/7์žฅ = 100% (๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ): 1. ๋ชฉ์ ๊ณผ ๋ฒ”์œ„, 2. ์„ค์น˜์™€ ์‹คํ–‰, 3. ์ž…๋ ฅ ๋ฐ์ดํ„ฐ, 4. ์œ„ํ—˜ ํŒ์ • ๊ทœ์น™, 5. ์‚ฌ๋žŒ ๊ฒ€ํ† ์™€ ๊ฐ์‚ฌ ๋กœ๊ทธ, 6. ์˜ค๋ฅ˜ ๋Œ€์‘, 7. ํ•œ๊ณ„์™€ ํ™•์žฅ\n", "\n", "[์กฐ๋‹ฌ ๋‹ด๋‹น ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ]\n", "1. ๊ณ„์•ฝ 100๊ฑด ์ ๊ฒ€: ๋ฃฐ ๊ณ ์œ„ํ—˜ 8๊ฑด, ์ฃผ์˜ 26๊ฑด, ์œ„ํ—˜ ์ ์ˆ˜ 50์  ์ด์ƒ 27๊ฑด\n", "2. ์‚ฌ๋žŒ ๊ฒ€ํ†  ํ 27๊ฑด: ๋ชจ๋‘ PENDING, ์šฐ์„  ํ™•์ธ 6๊ฑด(๋ฃฐ '๊ด€์‹ฌ'์ธ๋ฐ ๋ชจ๋ธ์ด ๋†’๊ฒŒ ๋ณธ ๊ฑด ๋˜๋Š” ์ ์ˆ˜ 70 ๋ฏธ๋งŒ ํฌํ•จ)\n", " - C-0085 ๋ฌผํ’ˆ ์ผ๋ฐ˜๊ฒฝ์Ÿ ์œ„ํ—˜ 100์ , ๋ฃฐ ์ ์ˆ˜ 7, ์œ„ํ—˜ ์กฐํ•ญ ๋ฒˆํ˜ธ [1, 3, 5, 6]\n", " - C-0099 ๋ฌผํ’ˆ ์ผ๋ฐ˜๊ฒฝ์Ÿ ์œ„ํ—˜ 100์ , ๋ฃฐ ์ ์ˆ˜ 9, ์œ„ํ—˜ ์กฐํ•ญ ๋ฒˆํ˜ธ [3, 4]\n", " - C-0024 ๋ฌผํ’ˆ ์ œํ•œ๊ฒฝ์Ÿ ์œ„ํ—˜ 100์ , ๋ฃฐ ์ ์ˆ˜ 8, ์œ„ํ—˜ ์กฐํ•ญ ๋ฒˆํ˜ธ [2]\n", "3. ์กฐํ•ญ์˜ ๋ถˆ๊ณต์ • ์—ฌ๋ถ€, ๋ฒ•๋ฌด ๊ฒ€ํ† , ๊ณ„์•ฝ ์Šน์ธ์€ ๊ณ„์•ฝ๊ด€ยท๋ฒ•๋ฌดยท์Šน์ธ๊ถŒ์ž๊ฐ€ ์ˆ˜ํ–‰ํ•œ๋‹ค.\n", "4. ๋ณธ ๋„๊ตฌ๋Š” ๊ฐ€์ƒ ์ž๋ฃŒ ๊ธฐ๋ฐ˜ ํŒ๋‹จ ๋ณด์กฐ์ด๋ฉฐ ์‹ค์ œ ๊ทœ์ • ์กฐ๋ฌธยท๊ณ„์•ฝ ์ž๋ฃŒ๋Š” ๋‹ด๋‹น ๋ถ€์„œ ํ™•์ธ์ด ํ•„์š”ํ•˜๋‹ค.\n", "```\n", "
\n" ] } ], "metadata": { "colab": { "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }