볼트 분류 개선 및 업로드 성능 최적화

- 볼트 길이 추출 로직 개선: '70.0000 LG' 형태 인식 추가
- 재질 중복 표시 수정: 'ASTM A193 ASTM A193 B7' → 'B7'
- A193/A194 등급 추출 로직 개선: 'GR B7/2H' 형태 지원
- bolt_details 테이블에 pressure_rating 컬럼 추가
- 볼트 분류기 오분류 방지: 플랜지/피팅이 볼트로 분류되지 않도록 수정
- 업로드 성능 개선: 키워드 기반 빠른 분류기 선택 로직 추가
- 분류 키워드 대폭 확장: 피팅/파이프/플랜지 키워드 추가
This commit is contained in:
Hyungi Ahn
2025-07-18 12:48:24 +09:00
parent 25ce3590ee
commit 3dd301cb57
13 changed files with 1184 additions and 106 deletions

View File

@@ -7,6 +7,125 @@ import re
from typing import Dict, List, Optional
from .material_classifier import classify_material
def classify_bolt_material(description: str) -> Dict:
"""볼트용 재질 분류 (ASTM A193, A194 등)"""
desc_upper = description.upper()
# ASTM A193 (볼트용 강재)
if any(pattern in desc_upper for pattern in ["A193", "ASTM A193"]):
# B7, B8 등 등급 추출 (GR B7/2H 형태도 지원)
grade = "UNKNOWN"
if "GR B7" in desc_upper or " B7" in desc_upper:
grade = "B7"
elif "GR B8" in desc_upper or " B8" in desc_upper:
grade = "B8"
elif "GR B16" in desc_upper or " B16" in desc_upper:
grade = "B16"
return {
"standard": "ASTM A193",
"grade": grade if grade != "UNKNOWN" else "ASTM A193",
"material_type": "ALLOY_STEEL" if "B7" in grade else "STAINLESS_STEEL",
"manufacturing": "FORGED",
"confidence": 0.95,
"evidence": ["ASTM_A193_BOLT_MATERIAL"]
}
# ASTM A194 (너트용 강재)
if any(pattern in desc_upper for pattern in ["A194", "ASTM A194"]):
grade = "UNKNOWN"
if "GR 2H" in desc_upper or " 2H" in desc_upper or "/2H" in desc_upper:
grade = "2H"
elif "GR 8" in desc_upper or " 8" in desc_upper:
grade = "8"
return {
"standard": "ASTM A194",
"grade": grade if grade != "UNKNOWN" else "ASTM A194",
"material_type": "ALLOY_STEEL" if "2H" in grade else "STAINLESS_STEEL",
"manufacturing": "FORGED",
"confidence": 0.95,
"evidence": ["ASTM_A194_NUT_MATERIAL"]
}
# ASTM A320 (저온용 볼트)
if any(pattern in desc_upper for pattern in ["A320", "ASTM A320"]):
grade = "UNKNOWN"
if "L7" in desc_upper:
grade = "L7"
elif "L43" in desc_upper:
grade = "L43"
elif "B8M" in desc_upper:
grade = "B8M"
return {
"standard": "ASTM A320",
"grade": grade if grade != "UNKNOWN" else "ASTM A320",
"material_type": "LOW_TEMP_STEEL",
"manufacturing": "FORGED",
"confidence": 0.95,
"evidence": ["ASTM_A320_LOW_TEMP_BOLT"]
}
# ASTM A325 (구조용 볼트)
if any(pattern in desc_upper for pattern in ["A325", "ASTM A325"]):
return {
"standard": "ASTM A325",
"grade": "ASTM A325",
"material_type": "STRUCTURAL_STEEL",
"manufacturing": "HEAT_TREATED",
"confidence": 0.95,
"evidence": ["ASTM_A325_STRUCTURAL_BOLT"]
}
# ASTM A490 (고강도 구조용 볼트)
if any(pattern in desc_upper for pattern in ["A490", "ASTM A490"]):
return {
"standard": "ASTM A490",
"grade": "ASTM A490",
"material_type": "HIGH_STRENGTH_STEEL",
"manufacturing": "HEAT_TREATED",
"confidence": 0.95,
"evidence": ["ASTM_A490_HIGH_STRENGTH_BOLT"]
}
# DIN 934 (DIN 너트)
if any(pattern in desc_upper for pattern in ["DIN 934", "DIN934"]):
return {
"standard": "DIN 934",
"grade": "DIN 934",
"material_type": "CARBON_STEEL",
"manufacturing": "FORGED",
"confidence": 0.90,
"evidence": ["DIN_934_NUT"]
}
# ISO 4762 (소켓 헤드 캡 스크류)
if any(pattern in desc_upper for pattern in ["ISO 4762", "ISO4762", "DIN 912", "DIN912"]):
return {
"standard": "ISO 4762",
"grade": "ISO 4762",
"material_type": "ALLOY_STEEL",
"manufacturing": "HEAT_TREATED",
"confidence": 0.90,
"evidence": ["ISO_4762_SOCKET_SCREW"]
}
# 기본 재질 분류기 호출 (materials_schema 문제가 있어도 우회)
try:
return classify_material(description)
except:
# materials_schema에 문제가 있으면 기본값 반환
return {
"standard": "UNKNOWN",
"grade": "UNKNOWN",
"material_type": "UNKNOWN",
"manufacturing": "UNKNOWN",
"confidence": 0.0,
"evidence": ["MATERIAL_SCHEMA_ERROR"]
}
# ========== 볼트 타입별 분류 ==========
BOLT_TYPES = {
"HEX_BOLT": {
@@ -26,16 +145,16 @@ BOLT_TYPES = {
},
"STUD_BOLT": {
"dat_file_patterns": ["STUD_", "STUD_BOLT"],
"description_keywords": ["STUD BOLT", "STUD", "스터드볼트", "전나사"],
"dat_file_patterns": ["STUD_", "STUD_BOLT", "_TK", "BLT_"],
"description_keywords": ["STUD BOLT", "STUD", "스터드볼트", "전나사", "BLT"],
"characteristics": "양끝 나사 스터드",
"applications": "플랜지 체결용",
"head_type": "NONE"
},
"FLANGE_BOLT": {
"dat_file_patterns": ["FLG_BOLT", "FLANGE_BOLT"],
"description_keywords": ["FLANGE BOLT", "플랜지볼트"],
"dat_file_patterns": ["FLG_BOLT", "FLANGE_BOLT", "BLT_150", "BLT_300", "BLT_600"],
"description_keywords": ["FLANGE BOLT", "플랜지볼트", "150LB", "300LB", "600LB"],
"characteristics": "플랜지 전용 볼트",
"applications": "플랜지 체결 전용",
"head_type": "HEXAGON"
@@ -183,7 +302,7 @@ BOLT_GRADES = {
}
}
def classify_bolt(dat_file: str, description: str, main_nom: str, length: float = None) -> Dict:
def classify_bolt(dat_file: str, description: str, main_nom: str, length: Optional[float] = None) -> Dict:
"""
완전한 BOLT 분류
@@ -196,8 +315,10 @@ def classify_bolt(dat_file: str, description: str, main_nom: str, length: float
완전한 볼트 분류 결과
"""
# 1. 재질 분류 (공통 모듈 사용)
material_result = classify_material(description)
# 1. 재질 분류 (볼트 전용 버전)
material_result = classify_bolt_material(description)
# 2. 체결재 타입 분류 (볼트/너트/와셔)
fastener_category = classify_fastener_category(dat_file, description)
@@ -282,7 +403,7 @@ def classify_fastener_category(dat_file: str, description: str) -> Dict:
combined_text = f"{dat_upper} {desc_upper}"
# 볼트 키워드
bolt_keywords = ["BOLT", "SCREW", "STUD", "볼트", "나사", "스크류"]
bolt_keywords = ["BOLT", "SCREW", "STUD", "BLT", "볼트", "나사", "스크류", "A193", "A194", "A320", "A325", "A490"]
if any(keyword in combined_text for keyword in bolt_keywords):
return {
"category": "BOLT",
@@ -308,11 +429,30 @@ def classify_fastener_category(dat_file: str, description: str) -> Dict:
"evidence": ["WASHER_KEYWORDS"]
}
# 기본값: BOLT
# 볼트가 아닌 것 같은 키워드들 체크
non_bolt_keywords = [
"PIPE", "TUBE", "파이프", "배관", # 파이프
"ELBOW", "TEE", "REDUCER", "CAP", "엘보", "", "리듀서", # 피팅
"VALVE", "GATE", "BALL", "GLOBE", "CHECK", "밸브", # 밸브
"FLANGE", "FLG", "플랜지", # 플랜지
"GASKET", "GASK", "가스켓", # 가스켓
"GAUGE", "METER", "TRANSMITTER", "INDICATOR", "SENSOR", "계기", "게이지", # 계기
"THERMOWELL", "ORIFICE", "MANOMETER" # 특수 계기
]
if any(keyword in combined_text for keyword in non_bolt_keywords):
return {
"category": "UNKNOWN",
"confidence": 0.1,
"evidence": ["NON_BOLT_KEYWORDS_DETECTED"]
}
# 기본값: BOLT (하지만 낮은 신뢰도)
return {
"category": "BOLT",
"confidence": 0.6,
"evidence": ["DEFAULT_BOLT"]
"confidence": 0.1, # 0.6에서 0.1로 낮춤
"evidence": ["DEFAULT_BOLT_LOW_CONFIDENCE"]
}
def classify_specific_fastener_type(dat_file: str, description: str,
@@ -429,6 +569,8 @@ def extract_bolt_dimensions(main_nom: str, description: str) -> Dict:
# 길이 정보 추출
length_patterns = [
r'(\d+(?:\.\d+)?)\s*LG', # 70.0000 LG 형태 (최우선)
r'(\d+(?:\.\d+)?)\s*MM\s*LG', # 70MM LG 형태
r'L\s*(\d+(?:\.\d+)?)\s*MM',
r'LENGTH\s*(\d+(?:\.\d+)?)\s*MM',
r'(\d+(?:\.\d+)?)\s*MM\s*LONG',
@@ -513,19 +655,41 @@ def classify_bolt_grade(description: str, thread_result: Dict) -> Dict:
}
def calculate_bolt_confidence(confidence_scores: Dict) -> float:
"""볼트 분류 전체 신뢰도 계산"""
"""볼트 분류 전체 신뢰도 계산 (개선된 버전)"""
scores = [score for score in confidence_scores.values() if score > 0]
# 기본 점수들
material_conf = confidence_scores.get("material", 0)
fastener_type_conf = confidence_scores.get("fastener_type", 0)
thread_conf = confidence_scores.get("thread", 0)
grade_conf = confidence_scores.get("grade", 0)
if not scores:
return 0.0
# 체결재 카테고리 신뢰도 (BOLT인지 확실한가?)
fastener_category_conf = confidence_scores.get("fastener_category", 0)
# 가중 평균
# 볼트 확신도 보너스
bolt_certainty_bonus = 0.0
# 1. 체결재 카테고리가 BOLT이고 신뢰도가 높으면 보너스
if fastener_category_conf >= 0.8:
bolt_certainty_bonus += 0.2
# 2. 피팅 타입이 명확하게 인식되면 보너스
if fastener_type_conf >= 0.8:
bolt_certainty_bonus += 0.1
# 3. ASTM 볼트 재질이 인식되면 큰 보너스
if material_conf >= 0.8:
bolt_certainty_bonus += 0.2
elif material_conf >= 0.5:
bolt_certainty_bonus += 0.1
# 기본 가중 평균 (재질 비중을 낮추고 체결재 타입 비중 증가)
weights = {
"material": 0.2,
"fastener_type": 0.4,
"thread": 0.3,
"grade": 0.1
"fastener_category": 0.3, # 새로 추가
"material": 0.1, # 낮춤 (0.2 -> 0.1)
"fastener_type": 0.4, # 유지
"thread": 0.15, # 낮춤 (0.3 -> 0.15)
"grade": 0.05 # 낮춤 (0.1 -> 0.05)
}
weighted_sum = sum(
@@ -533,7 +697,11 @@ def calculate_bolt_confidence(confidence_scores: Dict) -> float:
for key, weight in weights.items()
)
return round(weighted_sum, 2)
# 최종 신뢰도 = 기본 가중평균 + 보너스
final_confidence = weighted_sum + bolt_certainty_bonus
# 최대값 1.0으로 제한
return round(min(final_confidence, 1.0), 2)
# ========== 특수 기능들 ==========