- itsm/ -> workspace/guardia-itsm/ - manager/ -> workspace/guardia-manager/ - app/ -> workspace/guardia-messenger/ - manual/ -> workspace/guardia-docs/ workspace/zioinfo-web/ unchanged. git mv preserves full commit history. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
184 lines
6.1 KiB
Python
184 lines
6.1 KiB
Python
"""
|
|
GUARDiA NLU Engine — 순수 규칙 기반 자연어 이해 (외부 API 없음).
|
|
|
|
처리 흐름:
|
|
1. 정규식 패턴 매칭으로 인텐트(의도) 분류
|
|
2. 키워드/패턴으로 엔티티 추출
|
|
3. (sr_id, engineer, status, sr_type, inst_code, priority, keyword)
|
|
"""
|
|
import re
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
|
|
|
|
# ── 인텐트 정의 ───────────────────────────────────────────────────────────────
|
|
|
|
INTENT_PATTERNS: list[tuple[str, list[str]]] = [
|
|
# 우선순위 높은 인텐트부터
|
|
|
|
# SR 작업 시뮬레이션 실행
|
|
("SIMULATE", [
|
|
r"(시뮬레이션|simulate|작업\s*실행|자동\s*실행|AI\s*실행)",
|
|
r"(실행|run).*(SR|작업)",
|
|
]),
|
|
|
|
# 담당자 배정·변경
|
|
("ASSIGN_ENGINEER", [
|
|
r"담당자.*(변경|바꿔|배정|교체|지정)",
|
|
r"(배정|assign).*(engineer|엔지니어)",
|
|
r"engineer\d+.*(맡겨|담당|배정|지정)",
|
|
r"(자동\s*배정|auto\s*assign)",
|
|
]),
|
|
|
|
# 승인
|
|
("APPROVE_SR", [
|
|
r"(승인|approve)\s*(해줘|처리|완료|부탁)",
|
|
r"결재\s*(해줘|처리)",
|
|
]),
|
|
|
|
# 반려
|
|
("REJECT_SR", [
|
|
r"(반려|거절|reject)\s*(해줘|처리|할게)",
|
|
]),
|
|
|
|
# KB 검색
|
|
("SEARCH_KB", [
|
|
r"(KB|기술\s*문서|지식|매뉴얼).*(검색|찾아|조회)",
|
|
r"(검색|찾아줘|알려줘).*(오류|error|fault|장애|문제)",
|
|
r"(해결\s*방법|원인|솔루션).*(알려줘|찾아줘)",
|
|
r"(oom|outofmemory|connection\s*pool|ssl|nginx|502|undo|gc\s*overhead|디스크)",
|
|
]),
|
|
|
|
# 워크로드 조회
|
|
("QUERY_WORKLOAD", [
|
|
r"(워크로드|workload|작업\s*현황|담당\s*현황)",
|
|
r"engineer.*(몇\s*건|현황|상황|바빠)",
|
|
r"(엔지니어|engineer).*(상황|현황|보여줘)",
|
|
]),
|
|
|
|
# SR 상세 조회
|
|
("QUERY_SR_DETAIL", [
|
|
r"SR-\d{8}-[A-Z0-9]{6}.*(상태|정보|내용|어떻게|확인)",
|
|
r"(상태|정보|현황).*(알려줘|보여줘|확인).*(SR|서비스)",
|
|
]),
|
|
|
|
# SR 목록 조회
|
|
("QUERY_SR_LIST", [
|
|
r"(목록|리스트|list|조회|보여줘).*(SR|서비스|요청)",
|
|
r"(SR|서비스\s*요청).*(목록|있어|있나|몇\s*건|현황|리스트)",
|
|
r"(승인\s*대기|진행\s*중|완료|긴급|접수).*(SR|서비스|있어|있나|보여)",
|
|
r"(어떤|무슨)\s*SR",
|
|
]),
|
|
|
|
# 통계/요약
|
|
("QUERY_STATS", [
|
|
r"(통계|요약|summary|stats|현황\s*요약|전체\s*현황)",
|
|
r"(총|전체).*(SR|건수|몇\s*건)",
|
|
r"(오늘|이번\s*주).*(SR|처리|완료)",
|
|
]),
|
|
]
|
|
|
|
|
|
# ── 엔티티 추출 ───────────────────────────────────────────────────────────────
|
|
|
|
_SR_ID_RE = re.compile(r'SR-\d{8}-[A-Z0-9]{6}', re.IGNORECASE)
|
|
_ENG_USER_RE = re.compile(r'engineer\d+', re.IGNORECASE)
|
|
_PRIORITY_MAP = {"긴급": "CRITICAL", "높음": "HIGH", "보통": "MEDIUM", "낮음": "LOW",
|
|
"critical": "CRITICAL", "high": "HIGH", "medium": "MEDIUM", "low": "LOW"}
|
|
_STATUS_MAP = {
|
|
"승인": "APPROVED", "반려": "REJECTED", "완료": "COMPLETED",
|
|
"진행": "IN_PROGRESS", "접수": "RECEIVED", "대기": "PENDING_APPROVAL",
|
|
}
|
|
_TYPE_MAP = {
|
|
"배포": "DEPLOY", "deploy": "DEPLOY",
|
|
"재기동": "RESTART", "restart": "RESTART",
|
|
"로그": "LOG", "log": "LOG",
|
|
"문의": "INQUIRY", "inquiry": "INQUIRY",
|
|
}
|
|
_INST_CODES = ["MOF", "MOIS", "MSS"]
|
|
_ENG_ALIAS = {"김엔지니어": "engineer1", "이엔지니어": "engineer2"}
|
|
|
|
|
|
@dataclass
|
|
class ParseResult:
|
|
intent: str = "UNKNOWN"
|
|
sr_id: Optional[str] = None
|
|
engineer: Optional[str] = None
|
|
status: Optional[str] = None
|
|
sr_type: Optional[str] = None
|
|
inst_code: Optional[str] = None
|
|
priority: Optional[str] = None
|
|
keyword: Optional[str] = None
|
|
raw_text: str = ""
|
|
|
|
|
|
def parse(text: str) -> ParseResult:
|
|
"""자연어 텍스트를 분석해 인텐트 + 엔티티를 반환."""
|
|
r = ParseResult(raw_text=text)
|
|
lower = text.lower()
|
|
|
|
# ── 인텐트 분류 ──────────────────────────────────────
|
|
for intent_name, patterns in INTENT_PATTERNS:
|
|
for pat in patterns:
|
|
if re.search(pat, text, re.IGNORECASE):
|
|
r.intent = intent_name
|
|
break
|
|
if r.intent != "UNKNOWN":
|
|
break
|
|
|
|
# ── 엔티티 추출 ──────────────────────────────────────
|
|
# SR ID
|
|
m = _SR_ID_RE.search(text)
|
|
if m:
|
|
r.sr_id = m.group().upper()
|
|
|
|
# 엔지니어 (username 또는 한글 별칭)
|
|
m = _ENG_USER_RE.search(text)
|
|
if m:
|
|
r.engineer = m.group().lower()
|
|
else:
|
|
for alias, uname in _ENG_ALIAS.items():
|
|
if alias in text:
|
|
r.engineer = uname
|
|
break
|
|
|
|
# 상태
|
|
for k, v in _STATUS_MAP.items():
|
|
if k in lower:
|
|
r.status = v
|
|
break
|
|
|
|
# SR 유형
|
|
for k, v in _TYPE_MAP.items():
|
|
if k in lower:
|
|
r.sr_type = v
|
|
break
|
|
|
|
# 기관 코드
|
|
for inst in _INST_CODES:
|
|
if inst in text.upper():
|
|
r.inst_code = inst
|
|
break
|
|
|
|
# 우선순위
|
|
for k, v in _PRIORITY_MAP.items():
|
|
if k in lower:
|
|
r.priority = v
|
|
break
|
|
|
|
# 키워드 (KB 검색용) — 따옴표 안 또는 '검색:' 뒤
|
|
qm = re.search(r'["\'](.+?)["\']', text)
|
|
if qm:
|
|
r.keyword = qm.group(1)
|
|
else:
|
|
# fallback: 큰 단어 추출
|
|
kb_kw = re.search(
|
|
r'(oom|outofmemory|connection\s*pool|ssl|nginx|502|undo|gc overhead'
|
|
r'|디스크|메모리|커넥션|인증서|tomcat|oracle|ora-\d+)',
|
|
text, re.IGNORECASE
|
|
)
|
|
if kb_kw:
|
|
r.keyword = kb_kw.group(1)
|
|
|
|
return r
|