🔧 볼트 재질 정보 개선 및 A320/A194M 패턴 지원
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- bolt_classifier.py: A320/A194M 조합 패턴 처리 로직 추가 - material_grade_extractor.py: A320/A194M 패턴 추출 개선 - integrated_classifier.py: SPECIAL, U_BOLT 카테고리 우선 분류 - 데이터베이스: 492개 볼트의 material_grade를 완전한 형태로 업데이트 - A320/A194M GR B8/8: 78개 - A193/A194 GR B7/2H: 414개 - 프론트엔드: BOLT 카테고리 전용 UI (길이 표시) - Excel 내보내기: BOLT용 컬럼 순서 및 재질 정보 개선 - SPECIAL, U_BOLT 카테고리 지원 추가
This commit is contained in:
@@ -633,6 +633,17 @@ async def upload_file(
|
||||
classification_result = classify_valve("", description, main_nom or "")
|
||||
elif material_type == "BOLT":
|
||||
classification_result = classify_bolt("", description, main_nom or "")
|
||||
print(f"🔧 BOLT 분류 결과: {classification_result}")
|
||||
print(f"🔧 원본 설명: {description}")
|
||||
print(f"🔧 main_nom: {main_nom}")
|
||||
|
||||
# 길이 정보 확인
|
||||
dimensions_info = classification_result.get("dimensions", {})
|
||||
print(f"🔧 길이 정보: {dimensions_info}")
|
||||
|
||||
# 재질 정보 확인
|
||||
material_info = classification_result.get("material", {})
|
||||
print(f"🔧 재질 정보: {material_info}")
|
||||
elif material_type == "GASKET":
|
||||
classification_result = classify_gasket("", description, main_nom or "")
|
||||
elif material_type == "INSTRUMENT":
|
||||
@@ -1075,12 +1086,35 @@ async def upload_file(
|
||||
dimensions_info = classification_result.get("dimensions", {})
|
||||
material_info = classification_result.get("material", {})
|
||||
|
||||
print(f"🔧 fastener_type_info: {fastener_type_info}")
|
||||
|
||||
# 볼트 타입 (STUD_BOLT, HEX_BOLT 등)
|
||||
bolt_type = ""
|
||||
if isinstance(fastener_type_info, dict):
|
||||
bolt_type = fastener_type_info.get("type", "UNKNOWN")
|
||||
print(f"🔧 추출된 bolt_type: {bolt_type}")
|
||||
else:
|
||||
bolt_type = str(fastener_type_info) if fastener_type_info else "UNKNOWN"
|
||||
print(f"🔧 문자열 bolt_type: {bolt_type}")
|
||||
|
||||
# 특수 용도 볼트 확인 (PSV, LT, CK 등)
|
||||
special_result = classification_result.get("special_applications", {})
|
||||
print(f"🔧 special_result: {special_result}")
|
||||
|
||||
# 특수 용도가 감지되면 타입 우선 적용
|
||||
if special_result and special_result.get("detected_applications"):
|
||||
detected_apps = special_result.get("detected_applications", [])
|
||||
if "LT" in detected_apps:
|
||||
bolt_type = "LT_BOLT"
|
||||
print(f"🔧 특수 용도 감지로 bolt_type 변경: {bolt_type}")
|
||||
elif "PSV" in detected_apps:
|
||||
bolt_type = "PSV_BOLT"
|
||||
print(f"🔧 특수 용도 감지로 bolt_type 변경: {bolt_type}")
|
||||
elif "CK" in detected_apps:
|
||||
bolt_type = "CK_BOLT"
|
||||
print(f"🔧 특수 용도 감지로 bolt_type 변경: {bolt_type}")
|
||||
|
||||
print(f"🔧 최종 bolt_type: {bolt_type}")
|
||||
|
||||
# 나사 타입 (METRIC, INCH 등)
|
||||
thread_type = ""
|
||||
@@ -1553,6 +1587,9 @@ async def get_materials(
|
||||
fd.fitting_type, fd.fitting_subtype, fd.connection_method, fd.pressure_rating,
|
||||
fd.material_standard, fd.material_grade as fitting_material_grade, fd.main_size,
|
||||
fd.reduced_size, fd.length_mm as fitting_length_mm, fd.schedule as fitting_schedule,
|
||||
gd.gasket_type, gd.gasket_subtype, gd.material_type as gasket_material_type,
|
||||
gd.filler_material, gd.pressure_rating as gasket_pressure_rating, gd.size_inches as gasket_size_inches,
|
||||
gd.thickness as gasket_thickness, gd.temperature_range as gasket_temperature_range, gd.fire_safe,
|
||||
mpt.confirmed_quantity, mpt.purchase_status, mpt.confirmed_by, mpt.confirmed_at,
|
||||
-- 구매수량 계산에서 분류된 정보를 우선 사용
|
||||
CASE
|
||||
@@ -1579,6 +1616,7 @@ async def get_materials(
|
||||
LEFT JOIN pipe_end_preparations pep ON m.id = pep.material_id
|
||||
LEFT JOIN fitting_details fd ON m.id = fd.material_id
|
||||
LEFT JOIN valve_details vd ON m.id = vd.material_id
|
||||
LEFT JOIN gasket_details gd ON m.id = gd.material_id
|
||||
LEFT JOIN material_purchase_tracking mpt ON (
|
||||
m.material_hash = mpt.material_hash
|
||||
AND f.job_no = mpt.job_no
|
||||
@@ -1914,17 +1952,18 @@ async def get_materials(
|
||||
flange_groups[flange_key]["materials"].append(material_dict)
|
||||
material_dict['clean_description'] = clean_description
|
||||
elif m.classified_category == 'GASKET':
|
||||
gasket_query = text("SELECT * FROM gasket_details WHERE material_id = :material_id")
|
||||
gasket_result = db.execute(gasket_query, {"material_id": m.id})
|
||||
gasket_detail = gasket_result.fetchone()
|
||||
if gasket_detail:
|
||||
# 이미 JOIN된 gasket_details 데이터 사용
|
||||
if m.gasket_type: # gasket_details가 있는 경우
|
||||
material_dict['gasket_details'] = {
|
||||
"gasket_type": gasket_detail.gasket_type,
|
||||
"material_type": gasket_detail.material_type,
|
||||
"pressure_rating": gasket_detail.pressure_rating,
|
||||
"size_inches": gasket_detail.size_inches,
|
||||
"thickness": gasket_detail.thickness,
|
||||
"temperature_range": gasket_detail.temperature_range
|
||||
"gasket_type": m.gasket_type,
|
||||
"gasket_subtype": m.gasket_subtype,
|
||||
"material_type": m.gasket_material_type,
|
||||
"filler_material": m.filler_material,
|
||||
"pressure_rating": m.gasket_pressure_rating,
|
||||
"size_inches": m.gasket_size_inches,
|
||||
"thickness": m.gasket_thickness,
|
||||
"temperature_range": m.gasket_temperature_range,
|
||||
"fire_safe": m.fire_safe
|
||||
}
|
||||
|
||||
# 가스켓 그룹핑 - 크기, 압력, 재질로 그룹핑
|
||||
|
||||
@@ -12,6 +12,31 @@ def classify_bolt_material(description: str) -> Dict:
|
||||
|
||||
desc_upper = description.upper()
|
||||
|
||||
# A320/A194M 동시 처리 (예: "ASTM A320/A194M GR B8/8") - 저온용 볼트 조합
|
||||
if "A320" in desc_upper and "A194" in desc_upper:
|
||||
# B8/8 등급 추출
|
||||
bolt_grade = "UNKNOWN"
|
||||
nut_grade = "UNKNOWN"
|
||||
|
||||
if "B8" in desc_upper:
|
||||
bolt_grade = "B8"
|
||||
nut_grade = "8" # A320/A194M의 경우 보통 B8/8 조합
|
||||
elif "L7" in desc_upper:
|
||||
bolt_grade = "L7"
|
||||
elif "B8M" in desc_upper:
|
||||
bolt_grade = "B8M"
|
||||
|
||||
combined_grade = f"{bolt_grade}/{nut_grade}" if bolt_grade != "UNKNOWN" and nut_grade != "UNKNOWN" else f"{bolt_grade}" if bolt_grade != "UNKNOWN" else "A320/A194M"
|
||||
|
||||
return {
|
||||
"standard": "ASTM A320/A194M",
|
||||
"grade": combined_grade,
|
||||
"material_type": "LOW_TEMP_STAINLESS", # 저온용 스테인리스
|
||||
"manufacturing": "FORGED",
|
||||
"confidence": 0.95,
|
||||
"evidence": ["ASTM_A320_A194M_COMBINED"]
|
||||
}
|
||||
|
||||
# A193/A194 동시 처리 (예: "ASTM A193/A194 GR B7/2H")
|
||||
if "A193" in desc_upper and "A194" in desc_upper:
|
||||
# B7/2H 등급 추출
|
||||
@@ -136,6 +161,39 @@ def classify_bolt_material(description: str) -> Dict:
|
||||
"evidence": ["ISO_4762_SOCKET_SCREW"]
|
||||
}
|
||||
|
||||
# 일반적인 볼트 재질 패턴 추가 확인
|
||||
if "B7" in desc_upper and "2H" in desc_upper:
|
||||
return {
|
||||
"standard": "ASTM A193/A194",
|
||||
"grade": "B7/2H",
|
||||
"material_type": "ALLOY_STEEL",
|
||||
"manufacturing": "FORGED",
|
||||
"confidence": 0.85,
|
||||
"evidence": ["B7_2H_PATTERN"]
|
||||
}
|
||||
|
||||
# 단독 B7 패턴
|
||||
if "B7" in desc_upper:
|
||||
return {
|
||||
"standard": "ASTM A193",
|
||||
"grade": "B7",
|
||||
"material_type": "ALLOY_STEEL",
|
||||
"manufacturing": "FORGED",
|
||||
"confidence": 0.80,
|
||||
"evidence": ["B7_PATTERN"]
|
||||
}
|
||||
|
||||
# 단독 2H 패턴
|
||||
if "2H" in desc_upper:
|
||||
return {
|
||||
"standard": "ASTM A194",
|
||||
"grade": "2H",
|
||||
"material_type": "ALLOY_STEEL",
|
||||
"manufacturing": "FORGED",
|
||||
"confidence": 0.80,
|
||||
"evidence": ["2H_PATTERN"]
|
||||
}
|
||||
|
||||
# 기본 재질 분류기 호출 (materials_schema 문제가 있어도 우회)
|
||||
try:
|
||||
return classify_material(description)
|
||||
@@ -195,7 +253,7 @@ BOLT_TYPES = {
|
||||
|
||||
"LT_BOLT": {
|
||||
"dat_file_patterns": ["LT_BOLT", "LT_BLT"],
|
||||
"description_keywords": ["LT", "LOW TEMP", "저온용"],
|
||||
"description_keywords": ["LT BOLT", "LT BLT", "LOW TEMP", "저온용"],
|
||||
"characteristics": "저온용 특수 볼트",
|
||||
"applications": "저온 환경 체결용",
|
||||
"head_type": "HEXAGON",
|
||||
@@ -507,7 +565,8 @@ def classify_special_application_bolts(description: str) -> Dict:
|
||||
|
||||
# LT 볼트 확인 (저온용 볼트)
|
||||
lt_patterns = [
|
||||
r'\bLT\b', # 단어 경계로 LT만
|
||||
r'\bLT\s', # LT 다음에 공백이 있는 경우만 (LT BOLT, LT BLT)
|
||||
r'^LT\b', # 문장 시작의 LT만
|
||||
r'LOW\s+TEMP',
|
||||
r'저온용',
|
||||
r'CRYOGENIC',
|
||||
@@ -915,20 +974,31 @@ def extract_bolt_dimensions(main_nom: str, description: str) -> Dict:
|
||||
"dimension_description": nominal_size_fraction # 분수로 표시
|
||||
}
|
||||
|
||||
# 길이 정보 추출
|
||||
# 길이 정보 추출 (개선된 패턴)
|
||||
length_patterns = [
|
||||
r'(\d+(?:\.\d+)?)\s*LG', # 70.0000 LG 형태 (최우선)
|
||||
r'(\d+(?:\.\d+)?)\s*LG', # 70.0000 LG, 145.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',
|
||||
r'X\s*(\d+(?:\.\d+)?)\s*MM' # M8 X 20MM 형태
|
||||
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'(\d+(?:\.\d+)?)\s*MM(?:\s|,|$)', # 75MM 형태 (단독)
|
||||
r'(\d+(?:\.\d+)?)\s*mm(?:\s|,|$)' # 75mm 형태 (단독)
|
||||
]
|
||||
|
||||
for pattern in length_patterns:
|
||||
match = re.search(pattern, desc_upper)
|
||||
if match:
|
||||
dimensions["length"] = f"{match.group(1)}mm"
|
||||
length_value = match.group(1)
|
||||
# 소수점 제거 (145.0000 → 145)
|
||||
if '.' in length_value and length_value.endswith('.0000'):
|
||||
length_value = length_value.split('.')[0]
|
||||
elif '.' in length_value and all(c == '0' for c in length_value.split('.')[1]):
|
||||
length_value = length_value.split('.')[0]
|
||||
|
||||
dimensions["length"] = f"{length_value}mm"
|
||||
break
|
||||
|
||||
# 지름 정보 (이미 main_nom에 있지만 확인)
|
||||
|
||||
@@ -89,6 +89,29 @@ def classify_material_integrated(description: str, main_nom: str = "",
|
||||
|
||||
desc_upper = description.upper()
|
||||
|
||||
# 최우선: SPECIAL 키워드 확인 (도면 업로드가 필요한 특수 자재)
|
||||
special_keywords = ['SPECIAL', '스페셜', 'SPEC', 'SPL']
|
||||
for keyword in special_keywords:
|
||||
if keyword in desc_upper:
|
||||
return {
|
||||
"category": "SPECIAL",
|
||||
"confidence": 1.0,
|
||||
"evidence": [f"SPECIAL_KEYWORD: {keyword}"],
|
||||
"classification_level": "LEVEL0_SPECIAL",
|
||||
"reason": f"스페셜 키워드 발견: {keyword}"
|
||||
}
|
||||
|
||||
# U-BOLT 및 관련 부품 우선 확인 (BOLT 카테고리보다 먼저)
|
||||
if ('U-BOLT' in desc_upper or 'U BOLT' in desc_upper or '유볼트' in desc_upper or
|
||||
'URETHANE BLOCK' in desc_upper or 'BLOCK SHOE' in desc_upper or '우레탄' in desc_upper):
|
||||
return {
|
||||
"category": "U_BOLT",
|
||||
"confidence": 1.0,
|
||||
"evidence": ["U_BOLT_SYSTEM_KEYWORD"],
|
||||
"classification_level": "LEVEL0_U_BOLT",
|
||||
"reason": "U-BOLT 시스템 키워드 발견"
|
||||
}
|
||||
|
||||
# 쉼표로 구분된 각 부분을 별도로 체크 (예: "NIPPLE, SMLS, SCH 80")
|
||||
desc_parts = [part.strip() for part in desc_upper.split(',')]
|
||||
|
||||
|
||||
@@ -30,6 +30,15 @@ def extract_full_material_grade(description: str) -> str:
|
||||
# ASTM A193/A194 GR B7/2H (볼트용 조합 패턴) - 최우선
|
||||
r'ASTM\s+A193/A194\s+GR\s+[A-Z0-9/]+',
|
||||
r'ASTM\s+A193/A194\s+[A-Z0-9/]+',
|
||||
# ASTM A320/A194M GR B8/8 (저온용 볼트 조합 패턴)
|
||||
r'ASTM\s+A320[M]?/A194[M]?\s+GR\s+[A-Z0-9/]+',
|
||||
r'ASTM\s+A320[M]?/A194[M]?\s+[A-Z0-9/]+',
|
||||
# 단독 A193/A194 패턴 (ASTM 없이)
|
||||
r'\bA193/A194\s+GR\s+[A-Z0-9/]+\b',
|
||||
r'\bA193/A194\s+[A-Z0-9/]+\b',
|
||||
# 단독 A320/A194M 패턴 (ASTM 없이)
|
||||
r'\bA320[M]?/A194[M]?\s+GR\s+[A-Z0-9/]+\b',
|
||||
r'\bA320[M]?/A194[M]?\s+[A-Z0-9/]+\b',
|
||||
# ASTM A312 TP304, ASTM A312 TP316L 등
|
||||
r'ASTM\s+A\d{3,4}[A-Z]*\s+TP\d+[A-Z]*',
|
||||
# ASTM A182 F304, ASTM A182 F316L 등
|
||||
|
||||
Reference in New Issue
Block a user