""" GASKET 분류 시스템 플랜지용 가스켓 및 씰링 제품 분류 """ import re from typing import Dict, List, Optional from .material_classifier import classify_material # ========== 가스켓 타입별 분류 ========== GASKET_TYPES = { "SPIRAL_WOUND": { "dat_file_patterns": ["SWG_", "SPIRAL_"], "description_keywords": ["SPIRAL WOUND", "SPIRAL", "스파이럴", "SWG"], "characteristics": "금속 스트립과 필러의 나선형 조합", "pressure_range": "150LB ~ 2500LB", "temperature_range": "-200°C ~ 800°C", "applications": "고온고압, 일반 산업용" }, "RING_JOINT": { "dat_file_patterns": ["RTJ_", "RJ_", "RING_"], "description_keywords": ["RING JOINT", "RTJ", "RING TYPE JOINT", "링조인트"], "characteristics": "금속 링 형태의 고압용 가스켓", "pressure_range": "600LB ~ 2500LB", "temperature_range": "-100°C ~ 650°C", "applications": "고압 플랜지 전용" }, "FULL_FACE": { "dat_file_patterns": ["FF_", "FULL_"], "description_keywords": ["FULL FACE", "FF", "풀페이스"], "characteristics": "플랜지 전면 커버 가스켓", "pressure_range": "150LB ~ 300LB", "temperature_range": "-50°C ~ 400°C", "applications": "평면 플랜지용" }, "RAISED_FACE": { "dat_file_patterns": ["RF_", "RAISED_"], "description_keywords": ["RAISED FACE", "RF", "레이즈드"], "characteristics": "볼록한 면 전용 가스켓", "pressure_range": "150LB ~ 600LB", "temperature_range": "-50°C ~ 450°C", "applications": "일반 볼록면 플랜지용" }, "O_RING": { "dat_file_patterns": ["OR_", "ORING_"], "description_keywords": ["O-RING", "O RING", "ORING", "오링"], "characteristics": "원형 단면의 씰링 링", "pressure_range": "저압 ~ 고압 (재질별)", "temperature_range": "-60°C ~ 300°C (재질별)", "applications": "홈 씰링, 회전축 씰링" }, "SHEET_GASKET": { "dat_file_patterns": ["SHEET_", "SHT_"], "description_keywords": ["SHEET GASKET", "SHEET", "시트"], "characteristics": "판 형태의 가스켓", "pressure_range": "150LB ~ 600LB", "temperature_range": "-50°C ~ 500°C", "applications": "일반 플랜지, 맨홀" }, "KAMMPROFILE": { "dat_file_patterns": ["KAMM_", "KP_"], "description_keywords": ["KAMMPROFILE", "KAMM", "캄프로파일"], "characteristics": "파형 금속에 소프트 코팅", "pressure_range": "150LB ~ 1500LB", "temperature_range": "-200°C ~ 700°C", "applications": "고온고압, 화학공정" }, "CUSTOM_GASKET": { "dat_file_patterns": ["CUSTOM_", "SPEC_"], "description_keywords": ["CUSTOM", "SPECIAL", "특주", "맞춤"], "characteristics": "특수 제작 가스켓", "pressure_range": "요구사항별", "temperature_range": "요구사항별", "applications": "특수 형상, 특수 조건" } } # ========== 가스켓 재질별 분류 ========== GASKET_MATERIALS = { "GRAPHITE": { "keywords": ["GRAPHITE", "그라파이트", "흑연"], "characteristics": "고온 내성, 화학 안정성", "temperature_range": "-200°C ~ 650°C", "applications": "고온 스팀, 화학공정" }, "PTFE": { "keywords": ["PTFE", "TEFLON", "테프론"], "characteristics": "화학 내성, 낮은 마찰", "temperature_range": "-200°C ~ 260°C", "applications": "화학공정, 식품용" }, "VITON": { "keywords": ["VITON", "FKM", "바이톤"], "characteristics": "유류 내성, 고온 내성", "temperature_range": "-20°C ~ 200°C", "applications": "유류, 고온 가스" }, "EPDM": { "keywords": ["EPDM", "이피디엠"], "characteristics": "일반 고무, 스팀 내성", "temperature_range": "-50°C ~ 150°C", "applications": "스팀, 일반용" }, "NBR": { "keywords": ["NBR", "NITRILE", "니트릴"], "characteristics": "유류 내성", "temperature_range": "-30°C ~ 100°C", "applications": "유압, 윤활유" }, "METAL": { "keywords": ["METAL", "SS", "STAINLESS", "금속"], "characteristics": "고온고압 내성", "temperature_range": "-200°C ~ 800°C", "applications": "극한 조건" }, "COMPOSITE": { "keywords": ["COMPOSITE", "복합재", "FIBER"], "characteristics": "다층 구조", "temperature_range": "재질별 상이", "applications": "특수 조건" } } # ========== 압력 등급별 분류 ========== GASKET_PRESSURE_RATINGS = { "patterns": [ r"(\d+)LB", r"CLASS\s*(\d+)", r"CL\s*(\d+)", r"(\d+)#" ], "standard_ratings": { "150LB": {"max_pressure": "285 PSI", "typical_gasket": "SHEET, SPIRAL_WOUND"}, "300LB": {"max_pressure": "740 PSI", "typical_gasket": "SPIRAL_WOUND, SHEET"}, "600LB": {"max_pressure": "1480 PSI", "typical_gasket": "SPIRAL_WOUND, RTJ"}, "900LB": {"max_pressure": "2220 PSI", "typical_gasket": "SPIRAL_WOUND, RTJ"}, "1500LB": {"max_pressure": "3705 PSI", "typical_gasket": "RTJ, SPIRAL_WOUND"}, "2500LB": {"max_pressure": "6170 PSI", "typical_gasket": "RTJ"} } } # ========== 사이즈 표기법 ========== GASKET_SIZE_PATTERNS = { "flange_size": r"(\d+(?:\.\d+)?)\s*[\"\'']?\s*(?:INCH|IN|인치)?", "inner_diameter": r"ID\s*(\d+(?:\.\d+)?)", "outer_diameter": r"OD\s*(\d+(?:\.\d+)?)", "thickness": r"THK?\s*(\d+(?:\.\d+)?)\s*MM" } def classify_gasket(dat_file: str, description: str, main_nom: str) -> Dict: """ 완전한 GASKET 분류 Args: dat_file: DAT_FILE 필드 description: DESCRIPTION 필드 main_nom: MAIN_NOM 필드 (플랜지 사이즈) Returns: 완전한 가스켓 분류 결과 """ # 1. 재질 분류 (공통 모듈 + 가스켓 전용) material_result = classify_material(description) gasket_material_result = classify_gasket_material(description) # 2. 가스켓 타입 분류 gasket_type_result = classify_gasket_type(dat_file, description) # 3. 압력 등급 분류 pressure_result = classify_gasket_pressure_rating(dat_file, description) # 4. 사이즈 정보 추출 size_result = extract_gasket_size_info(main_nom, description) # 5. 온도 범위 추출 temperature_result = extract_temperature_range(description, gasket_type_result, gasket_material_result) # 6. 최종 결과 조합 return { "category": "GASKET", # 재질 정보 (공통 + 가스켓 전용) "material": { "standard": material_result.get('standard', 'UNKNOWN'), "grade": material_result.get('grade', 'UNKNOWN'), "material_type": material_result.get('material_type', 'UNKNOWN'), "confidence": material_result.get('confidence', 0.0) }, "gasket_material": { "material": gasket_material_result.get('material', 'UNKNOWN'), "characteristics": gasket_material_result.get('characteristics', ''), "temperature_range": gasket_material_result.get('temperature_range', ''), "confidence": gasket_material_result.get('confidence', 0.0) }, # 가스켓 분류 정보 "gasket_type": { "type": gasket_type_result.get('type', 'UNKNOWN'), "characteristics": gasket_type_result.get('characteristics', ''), "confidence": gasket_type_result.get('confidence', 0.0), "evidence": gasket_type_result.get('evidence', []), "pressure_range": gasket_type_result.get('pressure_range', ''), "applications": gasket_type_result.get('applications', '') }, "pressure_rating": { "rating": pressure_result.get('rating', 'UNKNOWN'), "confidence": pressure_result.get('confidence', 0.0), "max_pressure": pressure_result.get('max_pressure', ''), "typical_gasket": pressure_result.get('typical_gasket', '') }, "size_info": { "flange_size": size_result.get('flange_size', main_nom), "inner_diameter": size_result.get('inner_diameter', ''), "outer_diameter": size_result.get('outer_diameter', ''), "thickness": size_result.get('thickness', ''), "size_description": size_result.get('size_description', main_nom) }, "temperature_info": { "range": temperature_result.get('range', ''), "max_temp": temperature_result.get('max_temp', ''), "min_temp": temperature_result.get('min_temp', ''), "confidence": temperature_result.get('confidence', 0.0) }, # 전체 신뢰도 "overall_confidence": calculate_gasket_confidence({ "gasket_type": gasket_type_result.get('confidence', 0), "gasket_material": gasket_material_result.get('confidence', 0), "pressure": pressure_result.get('confidence', 0), "size": size_result.get('confidence', 0.8) # 기본 신뢰도 }) } def classify_gasket_type(dat_file: str, description: str) -> Dict: """가스켓 타입 분류""" dat_upper = dat_file.upper() desc_upper = description.upper() # 1. DAT_FILE 패턴으로 1차 분류 for gasket_type, type_data in GASKET_TYPES.items(): for pattern in type_data["dat_file_patterns"]: if pattern in dat_upper: return { "type": gasket_type, "characteristics": type_data["characteristics"], "confidence": 0.95, "evidence": [f"DAT_FILE_PATTERN: {pattern}"], "pressure_range": type_data["pressure_range"], "temperature_range": type_data["temperature_range"], "applications": type_data["applications"] } # 2. DESCRIPTION 키워드로 2차 분류 for gasket_type, type_data in GASKET_TYPES.items(): for keyword in type_data["description_keywords"]: if keyword in desc_upper: return { "type": gasket_type, "characteristics": type_data["characteristics"], "confidence": 0.85, "evidence": [f"DESCRIPTION_KEYWORD: {keyword}"], "pressure_range": type_data["pressure_range"], "temperature_range": type_data["temperature_range"], "applications": type_data["applications"] } # 3. 분류 실패 return { "type": "UNKNOWN", "characteristics": "", "confidence": 0.0, "evidence": ["NO_GASKET_TYPE_IDENTIFIED"], "pressure_range": "", "temperature_range": "", "applications": "" } def classify_gasket_material(description: str) -> Dict: """가스켓 전용 재질 분류""" desc_upper = description.upper() # 가스켓 전용 재질 확인 for material_type, material_data in GASKET_MATERIALS.items(): for keyword in material_data["keywords"]: if keyword in desc_upper: return { "material": material_type, "characteristics": material_data["characteristics"], "temperature_range": material_data["temperature_range"], "confidence": 0.9, "matched_keyword": keyword, "applications": material_data["applications"] } # 일반 재질 키워드 확인 if any(keyword in desc_upper for keyword in ["RUBBER", "고무"]): return { "material": "RUBBER", "characteristics": "일반 고무계", "temperature_range": "-50°C ~ 100°C", "confidence": 0.7, "matched_keyword": "RUBBER", "applications": "일반용" } return { "material": "UNKNOWN", "characteristics": "", "temperature_range": "", "confidence": 0.0, "matched_keyword": "", "applications": "" } def classify_gasket_pressure_rating(dat_file: str, description: str) -> Dict: """가스켓 압력 등급 분류""" combined_text = f"{dat_file} {description}".upper() # 패턴 매칭으로 압력 등급 추출 for pattern in GASKET_PRESSURE_RATINGS["patterns"]: match = re.search(pattern, combined_text) if match: rating_num = match.group(1) rating = f"{rating_num}LB" # 표준 등급 정보 확인 rating_info = GASKET_PRESSURE_RATINGS["standard_ratings"].get(rating, {}) if rating_info: confidence = 0.95 else: confidence = 0.8 rating_info = { "max_pressure": "확인 필요", "typical_gasket": "확인 필요" } return { "rating": rating, "confidence": confidence, "matched_pattern": pattern, "matched_value": rating_num, "max_pressure": rating_info.get("max_pressure", ""), "typical_gasket": rating_info.get("typical_gasket", "") } return { "rating": "UNKNOWN", "confidence": 0.0, "matched_pattern": "", "max_pressure": "", "typical_gasket": "" } def extract_gasket_size_info(main_nom: str, description: str) -> Dict: """가스켓 사이즈 정보 추출""" desc_upper = description.upper() size_info = { "flange_size": main_nom, "inner_diameter": "", "outer_diameter": "", "thickness": "", "size_description": main_nom, "confidence": 0.8 } # 내경(ID) 추출 id_match = re.search(GASKET_SIZE_PATTERNS["inner_diameter"], desc_upper) if id_match: size_info["inner_diameter"] = f"{id_match.group(1)}mm" # 외경(OD) 추출 od_match = re.search(GASKET_SIZE_PATTERNS["outer_diameter"], desc_upper) if od_match: size_info["outer_diameter"] = f"{od_match.group(1)}mm" # 두께(THK) 추출 thk_match = re.search(GASKET_SIZE_PATTERNS["thickness"], desc_upper) if thk_match: size_info["thickness"] = f"{thk_match.group(1)}mm" # 사이즈 설명 조합 size_parts = [main_nom] if size_info["inner_diameter"] and size_info["outer_diameter"]: size_parts.append(f"ID{size_info['inner_diameter']}") size_parts.append(f"OD{size_info['outer_diameter']}") if size_info["thickness"]: size_parts.append(f"THK{size_info['thickness']}") size_info["size_description"] = " ".join(size_parts) return size_info def extract_temperature_range(description: str, gasket_type_result: Dict, gasket_material_result: Dict) -> Dict: """온도 범위 정보 추출""" desc_upper = description.upper() # DESCRIPTION에서 직접 온도 추출 temp_patterns = [ r'(\-?\d+(?:\.\d+)?)\s*°?C\s*~\s*(\-?\d+(?:\.\d+)?)\s*°?C', r'(\-?\d+(?:\.\d+)?)\s*TO\s*(\-?\d+(?:\.\d+)?)\s*°?C', r'MAX\s*(\-?\d+(?:\.\d+)?)\s*°?C', r'MIN\s*(\-?\d+(?:\.\d+)?)\s*°?C' ] for pattern in temp_patterns: match = re.search(pattern, desc_upper) if match: if len(match.groups()) == 2: # 범위 return { "range": f"{match.group(1)}°C ~ {match.group(2)}°C", "min_temp": f"{match.group(1)}°C", "max_temp": f"{match.group(2)}°C", "confidence": 0.95, "source": "DESCRIPTION_RANGE" } else: # 단일 온도 temp_value = match.group(1) if "MAX" in pattern: return { "range": f"~ {temp_value}°C", "max_temp": f"{temp_value}°C", "confidence": 0.9, "source": "DESCRIPTION_MAX" } # 가스켓 재질 기반 온도 범위 material_temp = gasket_material_result.get('temperature_range', '') if material_temp: return { "range": material_temp, "confidence": 0.8, "source": "MATERIAL_BASED" } # 가스켓 타입 기반 온도 범위 type_temp = gasket_type_result.get('temperature_range', '') if type_temp: return { "range": type_temp, "confidence": 0.7, "source": "TYPE_BASED" } return { "range": "", "confidence": 0.0, "source": "NO_TEMPERATURE_INFO" } def calculate_gasket_confidence(confidence_scores: Dict) -> float: """가스켓 분류 전체 신뢰도 계산""" scores = [score for score in confidence_scores.values() if score > 0] if not scores: return 0.0 # 가중 평균 weights = { "gasket_type": 0.4, "gasket_material": 0.3, "pressure": 0.2, "size": 0.1 } weighted_sum = sum( confidence_scores.get(key, 0) * weight for key, weight in weights.items() ) return round(weighted_sum, 2) # ========== 특수 기능들 ========== def get_gasket_purchase_info(gasket_result: Dict) -> Dict: """가스켓 구매 정보 생성""" gasket_type = gasket_result["gasket_type"]["type"] gasket_material = gasket_result["gasket_material"]["material"] pressure = gasket_result["pressure_rating"]["rating"] # 공급업체 타입 결정 if gasket_type == "CUSTOM_GASKET": supplier_type = "특수 가스켓 제작업체" elif gasket_material in ["GRAPHITE", "PTFE"]: supplier_type = "고급 씰링 전문업체" elif gasket_type in ["SPIRAL_WOUND", "RING_JOINT"]: supplier_type = "산업용 가스켓 전문업체" else: supplier_type = "일반 가스켓 업체" # 납기 추정 if gasket_type == "CUSTOM_GASKET": lead_time = "4-8주 (특수 제작)" elif gasket_type in ["SPIRAL_WOUND", "KAMMPROFILE"]: lead_time = "2-4주 (제작품)" else: lead_time = "1-2주 (재고품)" # 구매 단위 if gasket_type == "O_RING": purchase_unit = "EA (개별)" elif gasket_type == "SHEET_GASKET": purchase_unit = "SHEET (시트)" else: purchase_unit = "SET (세트)" return { "supplier_type": supplier_type, "lead_time_estimate": lead_time, "purchase_category": f"{gasket_type} {pressure}", "purchase_unit": purchase_unit, "material_note": gasket_result["gasket_material"]["characteristics"], "temperature_note": gasket_result["temperature_info"]["range"], "applications": gasket_result["gasket_type"]["applications"] } def is_high_temperature_gasket(gasket_result: Dict) -> bool: """고온용 가스켓 여부 판단""" temp_range = gasket_result.get("temperature_info", {}).get("range", "") return any(indicator in temp_range for indicator in ["500°C", "600°C", "700°C", "800°C"]) def is_high_pressure_gasket(gasket_result: Dict) -> bool: """고압용 가스켓 여부 판단""" pressure_rating = gasket_result.get("pressure_rating", {}).get("rating", "") high_pressure_ratings = ["600LB", "900LB", "1500LB", "2500LB"] return any(pressure in pressure_rating for pressure in high_pressure_ratings)