자재 분류 시스템 개선 및 통합 분류기 구현
- 통합 분류기 구현으로 키워드 우선순위 체계 적용 - HEX.PLUG → FITTING 분류 수정 (기존 VALVE 오분류 해결) - 플랜지/밸브가 볼트로 오분류되는 문제 해결 (A193, A194 재질 키워드 우선순위 적용) - 피팅 재질(A234, A403, A420) 기반 분류 추가 - 니플 길이 정보 보존 로직 개선 - 파이프 끝단 가공 정보를 구매 단계에서 제외 - PostgreSQL 사용으로 RULES.md 업데이트 - 상호 배타적 키워드 시스템 구현 (Level 1 키워드 우선)
This commit is contained in:
@@ -13,6 +13,7 @@ import json
|
||||
|
||||
from ..database import get_db
|
||||
from app.services.material_classifier import classify_material
|
||||
from app.services.integrated_classifier import classify_material_integrated, should_exclude_material
|
||||
from app.services.bolt_classifier import classify_bolt
|
||||
from app.services.flange_classifier import classify_flange
|
||||
from app.services.fitting_classifier import classify_fitting
|
||||
@@ -316,110 +317,50 @@ async def upload_file(
|
||||
main_nom = material_data.get("main_nom")
|
||||
red_nom = material_data.get("red_nom")
|
||||
|
||||
classification_results = []
|
||||
try:
|
||||
# EXCLUDE 분류기 우선 호출 (제외 대상 먼저 걸러냄)
|
||||
from app.services.exclude_classifier import classify_exclude
|
||||
exclude_result = classify_exclude("", description, main_nom or "")
|
||||
print(f"EXCLUDE 분류 결과: {exclude_result.get('category', 'UNKNOWN')} (신뢰도: {exclude_result.get('overall_confidence', 0)})")
|
||||
|
||||
# EXCLUDE가 높은 신뢰도로 제외 대상이라고 하면 바로 사용
|
||||
if exclude_result.get("overall_confidence", 0) >= 0.8:
|
||||
classification_result = exclude_result
|
||||
else:
|
||||
# 키워드 기반 빠른 분류기 선택 (성능 개선)
|
||||
classification_results = []
|
||||
|
||||
# 키워드 기반으로 우선 분류기 결정
|
||||
desc_lower = description.lower()
|
||||
primary_classifiers = []
|
||||
|
||||
# 볼트 관련 키워드
|
||||
if any(keyword in desc_lower for keyword in ['bolt', 'stud', 'nut', 'screw', 'washer', '볼트', '너트', 'a193', 'a194']):
|
||||
primary_classifiers.append(('bolt', classify_bolt))
|
||||
|
||||
# 파이프 관련 키워드 (확장)
|
||||
pipe_keywords = [
|
||||
'pipe', 'tube', 'smls', '파이프', '배관',
|
||||
'a106', 'a333', 'a312', 'a53', 'seamless', 'sch', 'schedule',
|
||||
'boe', 'poe', 'bbe', 'pbe' # end preparation
|
||||
]
|
||||
if any(keyword in desc_lower for keyword in pipe_keywords):
|
||||
primary_classifiers.append(('pipe', classify_pipe))
|
||||
|
||||
# 피팅 관련 키워드 (확장)
|
||||
fitting_keywords = [
|
||||
'elbow', 'ell', 'tee', 'reducer', 'red', 'cap', 'coupling', 'nipple', 'swage', 'olet',
|
||||
'엘보', '티', '리듀서', '캡', '니플', '커플링',
|
||||
'90l_', '45l_', 'socket', 'sw', 'equal', 'reducing', 'concentric', 'eccentric',
|
||||
'sockolet', 'weldolet', 'threadolet', 'socklet', 'plug'
|
||||
]
|
||||
if any(keyword in desc_lower for keyword in fitting_keywords):
|
||||
primary_classifiers.append(('fitting', classify_fitting))
|
||||
|
||||
# 플랜지 관련 키워드 (확장)
|
||||
flange_keywords = [
|
||||
'flg', 'flange', '플랜지', 'weld neck', 'blind', 'slip on', 'socket weld',
|
||||
'threaded', 'lap joint', 'orifice', 'spectacle', 'paddle', 'spacer',
|
||||
'wn', 'so', 'bl', 'sw', 'thd', 'lj', 'rf', 'ff', 'rtj',
|
||||
'raised face', 'flat face', 'ring joint'
|
||||
]
|
||||
if any(keyword in desc_lower for keyword in flange_keywords):
|
||||
primary_classifiers.append(('flange', classify_flange))
|
||||
|
||||
# 밸브 관련 키워드
|
||||
if any(keyword in desc_lower for keyword in ['valve', 'gate', 'ball', 'globe', 'check', '밸브']):
|
||||
primary_classifiers.append(('valve', classify_valve))
|
||||
|
||||
# 가스켓 관련 키워드
|
||||
if any(keyword in desc_lower for keyword in ['gasket', 'gask', '가스켓', 'swg', 'spiral']):
|
||||
primary_classifiers.append(('gasket', classify_gasket))
|
||||
|
||||
# 계기 관련 키워드
|
||||
if any(keyword in desc_lower for keyword in ['gauge', 'transmitter', 'sensor', 'thermometer', '계기', '게이지']):
|
||||
primary_classifiers.append(('instrument', classify_instrument))
|
||||
|
||||
# 우선 분류기만 실행 (1-2개)
|
||||
if primary_classifiers:
|
||||
for name, classifier in primary_classifiers:
|
||||
try:
|
||||
if name in ['fitting', 'flange']:
|
||||
result = classifier("", description, main_nom or "", red_nom)
|
||||
elif name == 'pipe':
|
||||
result = classifier("", description, main_nom or "", length_value)
|
||||
else:
|
||||
result = classifier("", description, main_nom or "")
|
||||
classification_results.append(result)
|
||||
except Exception as e:
|
||||
print(f"분류기 {name} 오류: {e}")
|
||||
continue
|
||||
|
||||
# 우선 분류기로 결과가 없으면 모든 분류기 실행
|
||||
if not classification_results or max(r.get('overall_confidence', 0) for r in classification_results) < 0.3:
|
||||
# 볼트는 항상 확인 (매우 일반적)
|
||||
if not any('bolt' in str(r) for r in primary_classifiers):
|
||||
bolt_result = classify_bolt("", description, main_nom or "")
|
||||
classification_results.append(bolt_result)
|
||||
|
||||
# 가장 높은 신뢰도의 결과 선택 (UNKNOWN 제외)
|
||||
valid_results = [r for r in classification_results if r.get('category') != 'UNKNOWN' and r.get('overall_confidence', 0) > 0]
|
||||
|
||||
if valid_results:
|
||||
classification_result = max(valid_results, key=lambda x: x.get('overall_confidence', 0))
|
||||
print(f"최종 선택: {classification_result.get('category')} (신뢰도: {classification_result.get('overall_confidence', 0)})")
|
||||
else:
|
||||
# 모든 분류기가 UNKNOWN이면 가장 높은 신뢰도의 UNKNOWN 선택
|
||||
classification_result = max(classification_results, key=lambda x: x.get('overall_confidence', 0))
|
||||
print(f"모든 분류기 실패, 최고 신뢰도 UNKNOWN 선택: (신뢰도: {classification_result.get('overall_confidence', 0)})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"분류기 실행 중 오류 발생: {e}")
|
||||
# 기본 분류 결과 생성
|
||||
# 1. 통합 분류기로 자재 타입 결정
|
||||
integrated_result = classify_material_integrated(description, main_nom or "", red_nom or "", length_value)
|
||||
print(f"[분류] {description}")
|
||||
print(f"통합 분류 결과: {integrated_result.get('category', 'UNKNOWN')} (신뢰도: {integrated_result.get('confidence', 0)}, Level: {integrated_result.get('classification_level', 'NONE')})")
|
||||
|
||||
# 2. 제외 대상 확인
|
||||
if should_exclude_material(description):
|
||||
classification_result = {
|
||||
"category": "UNKNOWN",
|
||||
"overall_confidence": 0.0,
|
||||
"reason": f"분류기 오류: {str(e)}"
|
||||
"category": "EXCLUDE",
|
||||
"overall_confidence": 0.95,
|
||||
"reason": "제외 대상 자재"
|
||||
}
|
||||
else:
|
||||
# 3. 타입별 상세 분류기 실행
|
||||
material_type = integrated_result.get('category', 'UNKNOWN')
|
||||
|
||||
if material_type == "PIPE":
|
||||
classification_result = classify_pipe("", description, main_nom or "", length_value)
|
||||
elif material_type == "FITTING":
|
||||
classification_result = classify_fitting("", description, main_nom or "", red_nom)
|
||||
elif material_type == "FLANGE":
|
||||
classification_result = classify_flange("", description, main_nom or "", red_nom)
|
||||
elif material_type == "VALVE":
|
||||
classification_result = classify_valve("", description, main_nom or "")
|
||||
elif material_type == "BOLT":
|
||||
classification_result = classify_bolt("", description, main_nom or "")
|
||||
elif material_type == "GASKET":
|
||||
classification_result = classify_gasket("", description, main_nom or "")
|
||||
elif material_type == "INSTRUMENT":
|
||||
classification_result = classify_instrument("", description, main_nom or "")
|
||||
else:
|
||||
# UNKNOWN 처리
|
||||
classification_result = {
|
||||
"category": "UNKNOWN",
|
||||
"overall_confidence": integrated_result.get('confidence', 0.0),
|
||||
"reason": f"분류 불가: {integrated_result.get('evidence', [])}"
|
||||
}
|
||||
|
||||
# 통합 분류기의 신뢰도가 더 낮으면 조정
|
||||
if integrated_result.get('confidence', 0) < 0.5:
|
||||
classification_result['overall_confidence'] = min(
|
||||
classification_result.get('overall_confidence', 1.0),
|
||||
integrated_result.get('confidence', 0.0) + 0.2
|
||||
)
|
||||
|
||||
print(f"최종 분류 결과: {classification_result.get('category', 'UNKNOWN')}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user