feat: 서포트 카테고리 전면 개선
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

- 서포트 카테고리 UI 개선: 좌우 스크롤, 헤더/본문 동기화, 가운데 정렬
- 동일 항목 합산 기능 구현 (Type + Size + Grade 기준)
- 헤더 구조 변경: 압력/스케줄 제거, 구매수량 단일화, User Requirements 추가
- 우레탄 블럭슈 두께 정보(40t, 27t) Material Grade에 포함
- 서포트 수량 계산 수정: 취합된 숫자 그대로 표시 (4의 배수 계산 제거)
- 서포트 분류 로직 개선: CLAMP, U-BOLT, URETHANE BLOCK SHOE 등 정확한 분류
- 백엔드 서포트 분류기에 User Requirements 추출 기능 추가
- 엑셀 내보내기에 서포트 카테고리 처리 로직 추가
This commit is contained in:
hyungi
2025-10-17 07:59:35 +09:00
parent a27213e0e5
commit 6b6360ecd5
19 changed files with 2452 additions and 278 deletions

View File

@@ -706,7 +706,8 @@ def classify_bolt(dat_file: str, description: str, main_nom: str, length: Option
"nominal_size_fraction": dimensions_result.get('nominal_size_fraction', main_nom),
"length": dimensions_result.get('length', ''),
"diameter": dimensions_result.get('diameter', ''),
"dimension_description": dimensions_result.get('dimension_description', '')
"dimension_description": dimensions_result.get('dimension_description', ''),
"bolts_per_flange": dimensions_result.get('bolts_per_flange', 1)
},
"grade_strength": {
@@ -966,12 +967,19 @@ def extract_bolt_dimensions(main_nom: str, description: str) -> Dict:
except:
nominal_size_fraction = actual_bolt_size
# 플랜지당 볼트 세트 수 추출 (예: (8), (4))
bolts_per_flange = 1 # 기본값
flange_bolt_pattern = re.search(r'\((\d+)\)', description)
if flange_bolt_pattern:
bolts_per_flange = int(flange_bolt_pattern.group(1))
dimensions = {
"nominal_size": actual_bolt_size, # 실제 볼트 사이즈
"nominal_size_fraction": nominal_size_fraction, # 분수 변환값
"length": "",
"diameter": "",
"dimension_description": nominal_size_fraction # 분수로 표시
"dimension_description": nominal_size_fraction, # 분수로 표시
"bolts_per_flange": bolts_per_flange # 플랜지당 볼트 세트 수
}
# 길이 정보 추출 (개선된 패턴)
@@ -984,6 +992,8 @@ def extract_bolt_dimensions(main_nom: str, description: str) -> Dict:
r'X\s*(\d+(?:\.\d+)?)\s*MM', # M8 X 20MM 형태
r',\s*(\d+(?:\.\d+)?)\s*LG', # ", 145.0000 LG" 형태 (PSV, LT 볼트용)
r',\s*(\d+(?:\.\d+)?)\s+(?:CK|PSV|LT)', # ", 140 CK" 형태 (PSV 볼트용)
r'PSV\s+(\d+(?:\.\d+)?)', # PSV 140 형태 (PSV 볼트 전용)
r'(\d+(?:\.\d+)?)\s+PSV', # 140 PSV 형태 (PSV 볼트 전용)
r'(\d+(?:\.\d+)?)\s*MM(?:\s|,|$)', # 75MM 형태 (단독)
r'(\d+(?:\.\d+)?)\s*mm(?:\s|,|$)' # 75mm 형태 (단독)
]

View File

@@ -182,6 +182,14 @@ def classify_flange(dat_file: str, description: str, main_nom: str,
dat_upper = dat_file.upper()
# 1. 플랜지 키워드 확인 (재질만 있어도 통합 분류기가 이미 플랜지로 분류했으므로 진행)
# 사이트 글라스와 스트레이너는 밸브로 분류되어야 함
if 'SIGHT GLASS' in desc_upper or 'STRAINER' in desc_upper or '사이트글라스' in desc_upper or '스트레이너' in desc_upper:
return {
"category": "VALVE",
"overall_confidence": 1.0,
"reason": "SIGHT GLASS 또는 STRAINER는 밸브로 분류"
}
flange_keywords = ['FLG', 'FLANGE', '플랜지', 'ORIFICE', 'SPECTACLE', 'PADDLE', 'SPACER']
has_flange_keyword = any(keyword in desc_upper or keyword in dat_upper for keyword in flange_keywords)

View File

@@ -10,7 +10,7 @@ from .fitting_classifier import classify_fitting
# Level 1: 명확한 타입 키워드 (최우선)
LEVEL1_TYPE_KEYWORDS = {
"BOLT": ["FLANGE BOLT", "U-BOLT", "U BOLT", "BOLT", "STUD", "NUT", "SCREW", "WASHER", "볼트", "너트", "스터드", "나사", "와셔", "유볼트"],
"VALVE": ["VALVE", "GATE", "BALL", "GLOBE", "CHECK", "BUTTERFLY", "NEEDLE", "RELIEF", "밸브", "게이트", "", "글로브", "체크", "버터플라이", "니들", "릴리프"],
"VALVE": ["VALVE", "GATE", "BALL", "GLOBE", "CHECK", "BUTTERFLY", "NEEDLE", "RELIEF", "SIGHT GLASS", "STRAINER", "밸브", "게이트", "", "글로브", "체크", "버터플라이", "니들", "릴리프", "사이트글라스", "스트레이너"],
"FLANGE": ["FLG", "FLANGE", "플랜지", "프랜지", "ORIFICE", "SPECTACLE", "PADDLE", "SPACER", "BLIND", "REDUCING FLANGE", "RED FLANGE"],
"PIPE": ["PIPE", "TUBE", "파이프", "배관", "SMLS", "SEAMLESS"],
"FITTING": ["SOCK-O-LET", "WELD-O-LET", "ELL-O-LET", "THREAD-O-LET", "ELB-O-LET", "NIP-O-LET", "COUP-O-LET", "SOCKOLET", "WELDOLET", "ELLOLET", "THREADOLET", "ELBOLET", "NIPOLET", "COUPOLET", "OLET", "ELBOW", "ELL", "TEE", "REDUCER", "CAP", "COUPLING", "NIPPLE", "SWAGE", "PLUG", "엘보", "", "리듀서", "", "니플", "커플링", "플러그", "CONC", "ECC"],
@@ -110,6 +110,17 @@ def classify_material_integrated(description: str, main_nom: str = "",
"reason": "스페셜 키워드 발견"
}
# VALVE 카테고리 우선 확인 (SIGHT GLASS, STRAINER)
if ('SIGHT GLASS' in desc_upper or 'STRAINER' in desc_upper or
'사이트글라스' in desc_upper or '스트레이너' in desc_upper):
return {
"category": "VALVE",
"confidence": 1.0,
"evidence": ["VALVE_SPECIAL_KEYWORD"],
"classification_level": "LEVEL0_VALVE",
"reason": "SIGHT GLASS 또는 STRAINER 키워드 발견"
}
# SUPPORT 카테고리 우선 확인 (BOLT 카테고리보다 먼저)
# U-BOLT, CLAMP, URETHANE BLOCK 등
if ('U-BOLT' in desc_upper or 'U BOLT' in desc_upper or '유볼트' in desc_upper or

View File

@@ -108,7 +108,22 @@ def classify_support(dat_file: str, description: str, main_nom: str,
# 4. 사이즈 정보 추출
size_result = extract_support_size(description, main_nom)
# 5. 최종 결과 조합
# 5. 사용자 요구사항 추출
user_requirements = extract_support_user_requirements(description)
# 6. 우레탄 블럭슈 두께 정보 추출 및 Material Grade 보강
enhanced_material_grade = material_result.get('grade', 'UNKNOWN')
if support_type_result.get("support_type") == "URETHANE_BLOCK":
# 두께 정보 추출 (40t, 27t 등)
thickness_match = re.search(r'(\d+)\s*[tT](?![oO])', description.upper())
if thickness_match:
thickness = f"{thickness_match.group(1)}t"
if enhanced_material_grade == 'UNKNOWN' or not enhanced_material_grade:
enhanced_material_grade = thickness
elif thickness not in enhanced_material_grade:
enhanced_material_grade = f"{enhanced_material_grade} {thickness}"
# 7. 최종 결과 조합
return {
"category": "SUPPORT",
@@ -118,10 +133,10 @@ def classify_support(dat_file: str, description: str, main_nom: str,
"load_rating": load_result.get("load_rating", ""),
"load_capacity": load_result.get("capacity", ""),
# 재질 정보 (공통 모듈)
# 재질 정보 (공통 모듈) - 우레탄 블럭슈 두께 정보 포함
"material": {
"standard": material_result.get('standard', 'UNKNOWN'),
"grade": material_result.get('grade', 'UNKNOWN'),
"grade": enhanced_material_grade,
"material_type": material_result.get('material_type', 'UNKNOWN'),
"confidence": material_result.get('confidence', 0.0)
},
@@ -129,6 +144,9 @@ def classify_support(dat_file: str, description: str, main_nom: str,
# 사이즈 정보
"size_info": size_result,
# 사용자 요구사항
"user_requirements": user_requirements,
# 전체 신뢰도
"overall_confidence": calculate_support_confidence({
"type": support_type_result.get('confidence', 0),
@@ -183,6 +201,34 @@ def classify_support_type(dat_file: str, description: str) -> Dict:
"evidence": ["NO_SUPPORT_TYPE_FOUND"]
}
def extract_support_user_requirements(description: str) -> List[str]:
"""서포트 사용자 요구사항 추출"""
desc_upper = description.upper()
requirements = []
# 표면처리 관련
if 'GALV' in desc_upper or 'GALVANIZED' in desc_upper:
requirements.append('GALVANIZED')
if 'HDG' in desc_upper or 'HOT DIP' in desc_upper:
requirements.append('HOT DIP GALVANIZED')
if 'PAINT' in desc_upper or 'PAINTED' in desc_upper:
requirements.append('PAINTED')
# 재질 관련
if 'SS' in desc_upper or 'STAINLESS' in desc_upper:
requirements.append('STAINLESS STEEL')
if 'CARBON' in desc_upper:
requirements.append('CARBON STEEL')
# 특수 요구사항
if 'FIRE SAFE' in desc_upper:
requirements.append('FIRE SAFE')
if 'SEISMIC' in desc_upper or '내진' in desc_upper:
requirements.append('SEISMIC')
return requirements
def classify_load_rating(description: str) -> Dict:
"""하중 등급 분류"""

View File

@@ -89,6 +89,24 @@ VALVE_TYPES = {
"typical_connections": ["FLANGED", "THREADED"],
"pressure_range": "150LB ~ 600LB",
"special_features": ["LUBRICATED", "NON_LUBRICATED"]
},
"SIGHT_GLASS": {
"dat_file_patterns": ["SIGHT_", "SG_"],
"description_keywords": ["SIGHT GLASS", "SIGHT", "사이트글라스", "사이트 글라스"],
"characteristics": "유체 확인용 관찰창",
"typical_connections": ["FLANGED", "THREADED"],
"pressure_range": "150LB ~ 600LB",
"special_features": ["TRANSPARENT", "VISUAL_INSPECTION"]
},
"STRAINER": {
"dat_file_patterns": ["STRAINER_", "STR_"],
"description_keywords": ["STRAINER", "스트레이너", "여과기"],
"characteristics": "이물질 여과용",
"typical_connections": ["FLANGED", "THREADED"],
"pressure_range": "150LB ~ 600LB",
"special_features": ["MESH_FILTER", "Y_TYPE", "BASKET_TYPE"]
}
}
@@ -212,8 +230,13 @@ def classify_valve(dat_file: str, description: str, main_nom: str, length: float
desc_upper = description.upper()
dat_upper = dat_file.upper()
# 1. 밸브 키워드 확인 (재질만 있어도 통합 분류기가 이미 밸브로 분류했으므로 진행)
valve_keywords = ['VALVE', 'GATE', 'BALL', 'GLOBE', 'CHECK', 'BUTTERFLY', 'NEEDLE', 'RELIEF', 'SOLENOID', '밸브', '이트', '', '글로브', '체크', '버터플라이', '니들', '릴리프', '솔레노이드']
# 1. 사이트 글라스와 스트레이너 우선 확인
if 'SIGHT GLASS' in desc_upper or 'STRAINER' in desc_upper or '이트글라스' in desc_upper or '스트레이너' in desc_upper:
# 사이트 글라스와 스트레이너는 항상 밸브로 분류
pass
# 밸브 키워드 확인 (재질만 있어도 통합 분류기가 이미 밸브로 분류했으므로 진행)
valve_keywords = ['VALVE', 'GATE', 'BALL', 'GLOBE', 'CHECK', 'BUTTERFLY', 'NEEDLE', 'RELIEF', 'SOLENOID', 'SIGHT GLASS', 'STRAINER', '밸브', '게이트', '', '글로브', '체크', '버터플라이', '니들', '릴리프', '솔레노이드', '사이트글라스', '스트레이너']
has_valve_keyword = any(keyword in desc_upper or keyword in dat_upper for keyword in valve_keywords)
# 밸브 재질 확인 (A216, A217, A351, A352)