""" 재질 분류를 위한 공통 함수 materials_schema.py의 데이터를 사용하여 재질을 분류 """ import re from typing import Dict, List, Optional, Tuple from .materials_schema import ( MATERIAL_STANDARDS, SPECIAL_MATERIALS, MANUFACTURING_MATERIAL_MAP, GENERIC_MATERIAL_KEYWORDS ) def classify_material(description: str) -> Dict: """ 공통 재질 분류 함수 Args: description: 자재 설명 (DESCRIPTION 필드) Returns: 재질 분류 결과 딕셔너리 """ desc_upper = str(description).upper().strip() if description is not None else "" # 1단계: 특수 재질 우선 확인 (가장 구체적) special_result = check_special_materials(desc_upper) if special_result['confidence'] > 0.9: return special_result # 2단계: ASTM/ASME 규격 확인 astm_result = check_astm_materials(desc_upper) if astm_result['confidence'] > 0.8: return astm_result # 3단계: KS 규격 확인 ks_result = check_ks_materials(desc_upper) if ks_result['confidence'] > 0.8: return ks_result # 4단계: JIS 규격 확인 jis_result = check_jis_materials(desc_upper) if jis_result['confidence'] > 0.8: return jis_result # 5단계: 일반 키워드 확인 generic_result = check_generic_materials(desc_upper) return generic_result def check_special_materials(description: str) -> Dict: """특수 재질 확인""" # SUPER ALLOYS 확인 for alloy_family, alloy_data in SPECIAL_MATERIALS["SUPER_ALLOYS"].items(): for pattern in alloy_data["patterns"]: match = re.search(pattern, description) if match: grade = match.group(1) if match.groups() else "STANDARD" grade_info = alloy_data["grades"].get(grade, {}) return { "standard": f"{alloy_family}", "grade": f"{alloy_family} {grade}", "material_type": "SUPER_ALLOY", "manufacturing": alloy_data.get("manufacturing", "SPECIAL"), "composition": grade_info.get("composition", ""), "applications": grade_info.get("applications", ""), "confidence": 0.95, "evidence": [f"SPECIAL_MATERIAL: {alloy_family} {grade}"] } # TITANIUM 확인 titanium_data = SPECIAL_MATERIALS["TITANIUM"] for pattern in titanium_data["patterns"]: match = re.search(pattern, description) if match: grade = match.group(1) if match.groups() else "2" grade_info = titanium_data["grades"].get(grade, {}) return { "standard": "TITANIUM", "grade": f"Titanium Grade {grade}", "material_type": "TITANIUM", "manufacturing": "FORGED_OR_SEAMLESS", "composition": grade_info.get("composition", f"Ti Grade {grade}"), "confidence": 0.95, "evidence": [f"TITANIUM: Grade {grade}"] } return {"confidence": 0.0} def check_astm_materials(description: str) -> Dict: """ASTM/ASME 규격 확인""" astm_data = MATERIAL_STANDARDS["ASTM_ASME"] # FORGED 등급 확인 for standard, standard_data in astm_data["FORGED_GRADES"].items(): result = check_astm_standard(description, standard, standard_data) if result["confidence"] > 0.8: return result # WELDED 등급 확인 for standard, standard_data in astm_data["WELDED_GRADES"].items(): result = check_astm_standard(description, standard, standard_data) if result["confidence"] > 0.8: return result # CAST 등급 확인 for standard, standard_data in astm_data["CAST_GRADES"].items(): result = check_astm_standard(description, standard, standard_data) if result["confidence"] > 0.8: return result # PIPE 등급 확인 for standard, standard_data in astm_data["PIPE_GRADES"].items(): result = check_astm_standard(description, standard, standard_data) if result["confidence"] > 0.8: return result return {"confidence": 0.0} def check_astm_standard(description: str, standard: str, standard_data: Dict) -> Dict: """개별 ASTM 규격 확인""" # 직접 패턴이 있는 경우 (A105 등) if "patterns" in standard_data: for pattern in standard_data["patterns"]: match = re.search(pattern, description) if match: grade_code = match.group(1) if match.groups() else "" full_grade = f"ASTM {standard}" + (f" {grade_code}" if grade_code else "") return { "standard": f"ASTM {standard}", "grade": full_grade, "material_type": determine_material_type(standard, grade_code), "manufacturing": standard_data.get("manufacturing", "UNKNOWN"), "confidence": 0.9, "evidence": [f"ASTM_{standard}: {grade_code if grade_code else 'Direct Match'}"] } # 하위 분류가 있는 경우 (A182, A234 등) else: for subtype, subtype_data in standard_data.items(): for pattern in subtype_data["patterns"]: match = re.search(pattern, description) if match: grade_code = match.group(1) if match.groups() else "" grade_info = subtype_data["grades"].get(grade_code, {}) # A312의 경우 TP304 형태로 전체 grade 표시 if standard == "A312" and grade_code and not grade_code.startswith("TP"): full_grade = f"ASTM {standard} TP{grade_code}" elif grade_code.startswith("TP"): full_grade = f"ASTM {standard} {grade_code}" # A403의 경우 WP304 형태로 전체 grade 표시 elif standard == "A403" and grade_code and not grade_code.startswith("WP"): full_grade = f"ASTM {standard} WP{grade_code}" elif grade_code.startswith("WP"): full_grade = f"ASTM {standard} {grade_code}" # A420의 경우 WPL3 형태로 전체 grade 표시 elif standard == "A420" and grade_code and not grade_code.startswith("WPL"): full_grade = f"ASTM {standard} WPL{grade_code}" elif grade_code.startswith("WPL"): full_grade = f"ASTM {standard} {grade_code}" else: full_grade = f"ASTM {standard} {grade_code}" if grade_code else f"ASTM {standard}" return { "standard": f"ASTM {standard}", "grade": full_grade, "material_type": determine_material_type(standard, grade_code), "manufacturing": subtype_data.get("manufacturing", "UNKNOWN"), "composition": grade_info.get("composition", ""), "applications": grade_info.get("applications", ""), "confidence": 0.9, "evidence": [f"ASTM_{standard}: {grade_code}"] } return {"confidence": 0.0} def check_ks_materials(description: str) -> Dict: """KS 규격 확인""" ks_data = MATERIAL_STANDARDS["KS"] for category, standards in ks_data.items(): for standard, standard_data in standards.items(): for pattern in standard_data["patterns"]: match = re.search(pattern, description) if match: return { "standard": f"KS {standard}", "grade": f"KS {standard}", "material_type": determine_material_type_from_description(description), "manufacturing": standard_data.get("manufacturing", "UNKNOWN"), "description": standard_data["description"], "confidence": 0.85, "evidence": [f"KS_{standard}"] } return {"confidence": 0.0} def check_jis_materials(description: str) -> Dict: """JIS 규격 확인""" jis_data = MATERIAL_STANDARDS["JIS"] for category, standards in jis_data.items(): for standard, standard_data in standards.items(): for pattern in standard_data["patterns"]: match = re.search(pattern, description) if match: return { "standard": f"JIS {standard}", "grade": f"JIS {standard}", "material_type": determine_material_type_from_description(description), "manufacturing": standard_data.get("manufacturing", "UNKNOWN"), "description": standard_data["description"], "confidence": 0.85, "evidence": [f"JIS_{standard}"] } return {"confidence": 0.0} def check_generic_materials(description: str) -> Dict: """일반 재질 키워드 확인""" for material_type, keywords in GENERIC_MATERIAL_KEYWORDS.items(): for keyword in keywords: if keyword in description: return { "standard": "GENERIC", "grade": keyword, "material_type": material_type, "manufacturing": "UNKNOWN", "confidence": 0.6, "evidence": [f"GENERIC: {keyword}"] } return { "standard": "UNKNOWN", "grade": "UNKNOWN", "material_type": "UNKNOWN", "manufacturing": "UNKNOWN", "confidence": 0.0, "evidence": ["NO_MATERIAL_FOUND"] } def determine_material_type(standard: str, grade: str) -> str: """규격과 등급으로 재질 타입 결정""" # 스테인리스 등급 stainless_patterns = ["304", "316", "321", "347", "F304", "F316", "WP304", "CF8"] if any(pattern in grade for pattern in stainless_patterns): return "STAINLESS_STEEL" # 합금강 등급 alloy_patterns = ["F1", "F5", "F11", "F22", "F91", "WP1", "WP5", "WP11", "WP22", "WP91"] if any(pattern in grade for pattern in alloy_patterns): return "ALLOY_STEEL" # 주조품 if standard in ["A216", "A351"]: return "CAST_STEEL" # 기본값은 탄소강 return "CARBON_STEEL" def determine_material_type_from_description(description: str) -> str: """설명에서 재질 타입 추정""" desc_upper = description.upper() if any(keyword in desc_upper for keyword in ["SS", "STS", "STAINLESS", "304", "316"]): return "STAINLESS_STEEL" elif any(keyword in desc_upper for keyword in ["ALLOY", "합금", "CR", "MO"]): return "ALLOY_STEEL" elif any(keyword in desc_upper for keyword in ["CAST", "주조"]): return "CAST_STEEL" else: return "CARBON_STEEL" def get_manufacturing_method_from_material(material_result: Dict) -> str: """재질 정보로부터 제작방법 추정""" if material_result.get("confidence", 0) < 0.5: return "UNKNOWN" material_standard = material_result.get('standard', '') # 직접 매핑 if 'A182' in material_standard or 'A105' in material_standard: return 'FORGED' elif 'A234' in material_standard or 'A403' in material_standard or 'A420' in material_standard: return 'WELDED_FABRICATED' elif 'A216' in material_standard or 'A351' in material_standard: return 'CAST' elif 'A106' in material_standard or 'A312' in material_standard: return 'SEAMLESS' elif 'A53' in material_standard: return 'WELDED_OR_SEAMLESS' # manufacturing 필드가 있으면 직접 사용 manufacturing = material_result.get("manufacturing", "UNKNOWN") if manufacturing != "UNKNOWN": return manufacturing return "UNKNOWN" def get_material_confidence_factors(material_result: Dict) -> List[str]: """재질 분류 신뢰도 영향 요소 반환""" factors = [] confidence = material_result.get("confidence", 0) if confidence >= 0.9: factors.append("HIGH_CONFIDENCE") elif confidence >= 0.7: factors.append("MEDIUM_CONFIDENCE") else: factors.append("LOW_CONFIDENCE") if material_result.get("standard") == "UNKNOWN": factors.append("NO_STANDARD_FOUND") if material_result.get("manufacturing") == "UNKNOWN": factors.append("MANUFACTURING_UNCLEAR") return factors