Files
TK-BOM-Project/backend/app/services/support_classifier.py
Hyungi Ahn 0f9a5ad2ea
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
🔧 재질 정보 표시 개선 및 UI 확장
 주요 수정사항:
- 재질 GRADE 전체 표기: ASTM A106 B 완전 표시 (A10 잘림 현상 해결)
- material_grade_extractor.py 정규표현식 패턴 개선
- files.py 파일 업로드 시 재질 추출 로직 수정
- CSS 그리드 너비 확장으로 텍스트 잘림 현상 해결
- 사용자 요구사항 엑셀 다운로드 기능 완료

🎯 해결된 문제:
1. ASTM A106 B → ASTM A10 잘림 문제
2. 재질 컬럼 너비 부족으로 인한 표시 문제
3. 사용자 요구사항이 엑셀에 반영되지 않는 문제

📋 다음 단계 준비:
- 파이프 끝단 정보 제외 취합 로직 개선
- 플랜지 타입 정보 확장
- 자재 분류 필터 기능 추가
2025-09-25 08:32:17 +09:00

284 lines
9.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
SUPPORT 분류 시스템
배관 지지재, 우레탄 블록, 클램프 등 지지 부품 분류
"""
import re
from typing import Dict, List, Optional
from .material_classifier import classify_material
# ========== 서포트 타입별 분류 ==========
SUPPORT_TYPES = {
"URETHANE_BLOCK": {
"dat_file_patterns": ["URETHANE", "BLOCK", "SHOE"],
"description_keywords": ["URETHANE BLOCK", "BLOCK SHOE", "우레탄 블록", "우레탄", "URETHANE"],
"characteristics": "우레탄 블록 슈",
"applications": "배관 지지, 진동 흡수",
"material_type": "URETHANE"
},
"CLAMP": {
"dat_file_patterns": ["CLAMP", "CL-"],
"description_keywords": ["CLAMP", "클램프", "CL-1", "CL-2", "CL-3"],
"characteristics": "배관 클램프",
"applications": "배관 고정, 지지",
"material_type": "STEEL"
},
"HANGER": {
"dat_file_patterns": ["HANGER", "HANG", "SUPP"],
"description_keywords": ["HANGER", "SUPPORT", "행거", "서포트", "PIPE HANGER"],
"characteristics": "배관 행거",
"applications": "배관 매달기, 지지",
"material_type": "STEEL"
},
"SPRING_HANGER": {
"dat_file_patterns": ["SPRING", "SPR_"],
"description_keywords": ["SPRING HANGER", "SPRING", "스프링", "스프링 행거"],
"characteristics": "스프링 행거",
"applications": "가변 하중 지지",
"material_type": "STEEL"
},
"GUIDE": {
"dat_file_patterns": ["GUIDE", "GD_"],
"description_keywords": ["GUIDE", "가이드", "PIPE GUIDE"],
"characteristics": "배관 가이드",
"applications": "배관 방향 제어",
"material_type": "STEEL"
},
"ANCHOR": {
"dat_file_patterns": ["ANCHOR", "ANCH"],
"description_keywords": ["ANCHOR", "앵커", "PIPE ANCHOR"],
"characteristics": "배관 앵커",
"applications": "배관 고정점",
"material_type": "STEEL"
}
}
# ========== 하중 등급 분류 ==========
LOAD_RATINGS = {
"LIGHT": {
"patterns": [r"(\d+)T", r"(\d+)TON"],
"range": (0, 5), # 5톤 이하
"description": "경하중용"
},
"MEDIUM": {
"patterns": [r"(\d+)T", r"(\d+)TON"],
"range": (5, 20), # 5-20톤
"description": "중하중용"
},
"HEAVY": {
"patterns": [r"(\d+)T", r"(\d+)TON"],
"range": (20, 100), # 20-100톤
"description": "중하중용"
}
}
def classify_support(dat_file: str, description: str, main_nom: str,
length: Optional[float] = None) -> Dict:
"""
SUPPORT 분류 메인 함수
Args:
dat_file: DAT 파일명
description: 자재 설명
main_nom: 주 사이즈
length: 길이 (옵션)
Returns:
분류 결과 딕셔너리
"""
dat_upper = dat_file.upper()
desc_upper = description.upper()
combined_text = f"{dat_upper} {desc_upper}"
# 1. 서포트 타입 분류
support_type_result = classify_support_type(dat_file, description)
# 2. 재질 분류 (공통 모듈 사용)
material_result = classify_material(description)
# 3. 하중 등급 분류
load_result = classify_load_rating(description)
# 4. 사이즈 정보 추출
size_result = extract_support_size(description, main_nom)
# 5. 최종 결과 조합
return {
"category": "SUPPORT",
# 서포트 특화 정보
"support_type": support_type_result.get("support_type", "UNKNOWN"),
"support_subtype": support_type_result.get("subtype", ""),
"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'),
"material_type": material_result.get('material_type', 'UNKNOWN'),
"confidence": material_result.get('confidence', 0.0)
},
# 사이즈 정보
"size_info": size_result,
# 전체 신뢰도
"overall_confidence": calculate_support_confidence({
"type": support_type_result.get('confidence', 0),
"material": material_result.get('confidence', 0),
"load": load_result.get('confidence', 0),
"size": size_result.get('confidence', 0)
}),
# 증거
"evidence": [
f"SUPPORT_TYPE: {support_type_result.get('support_type', 'UNKNOWN')}",
f"MATERIAL: {material_result.get('standard', 'UNKNOWN')}",
f"LOAD: {load_result.get('load_rating', 'UNKNOWN')}"
]
}
def classify_support_type(dat_file: str, description: str) -> Dict:
"""서포트 타입 분류"""
dat_upper = dat_file.upper()
desc_upper = description.upper()
combined_text = f"{dat_upper} {desc_upper}"
for support_type, type_data in SUPPORT_TYPES.items():
# DAT 파일 패턴 확인
for pattern in type_data["dat_file_patterns"]:
if pattern in dat_upper:
return {
"support_type": support_type,
"subtype": type_data["characteristics"],
"applications": type_data["applications"],
"confidence": 0.95,
"evidence": [f"DAT_PATTERN: {pattern}"]
}
# 설명 키워드 확인
for keyword in type_data["description_keywords"]:
if keyword in desc_upper:
return {
"support_type": support_type,
"subtype": type_data["characteristics"],
"applications": type_data["applications"],
"confidence": 0.9,
"evidence": [f"DESC_KEYWORD: {keyword}"]
}
return {
"support_type": "UNKNOWN",
"subtype": "",
"applications": "",
"confidence": 0.0,
"evidence": ["NO_SUPPORT_TYPE_FOUND"]
}
def classify_load_rating(description: str) -> Dict:
"""하중 등급 분류"""
desc_upper = description.upper()
# 하중 패턴 찾기 (40T, 50TON 등)
for rating, rating_data in LOAD_RATINGS.items():
for pattern in rating_data["patterns"]:
match = re.search(pattern, desc_upper)
if match:
capacity = int(match.group(1))
min_load, max_load = rating_data["range"]
if min_load <= capacity <= max_load:
return {
"load_rating": rating,
"capacity": f"{capacity}T",
"description": rating_data["description"],
"confidence": 0.9,
"evidence": [f"LOAD_PATTERN: {match.group(0)}"]
}
# 특정 하중 값이 있지만 등급을 모르는 경우
load_match = re.search(r'(\d+)\s*[T톤]', desc_upper)
if load_match:
capacity = int(load_match.group(1))
return {
"load_rating": "CUSTOM",
"capacity": f"{capacity}T",
"description": f"{capacity}톤 하중",
"confidence": 0.7,
"evidence": [f"CUSTOM_LOAD: {load_match.group(0)}"]
}
return {
"load_rating": "UNKNOWN",
"capacity": "",
"description": "",
"confidence": 0.0,
"evidence": ["NO_LOAD_RATING_FOUND"]
}
def extract_support_size(description: str, main_nom: str) -> Dict:
"""서포트 사이즈 정보 추출"""
desc_upper = description.upper()
# 파이프 사이즈 (서포트가 지지하는 파이프 크기)
pipe_size = main_nom if main_nom else ""
# 서포트 자체 치수 (길이x폭x높이 등)
dimension_patterns = [
r'(\d+)\s*[X×]\s*(\d+)\s*[X×]\s*(\d+)', # 100x50x20
r'(\d+)\s*[X×]\s*(\d+)', # 100x50
r'L\s*(\d+)', # L100 (길이)
r'W\s*(\d+)', # W50 (폭)
r'H\s*(\d+)' # H20 (높이)
]
dimensions = {}
for pattern in dimension_patterns:
match = re.search(pattern, desc_upper)
if match:
if len(match.groups()) == 3:
dimensions = {
"length": f"{match.group(1)}mm",
"width": f"{match.group(2)}mm",
"height": f"{match.group(3)}mm"
}
elif len(match.groups()) == 2:
dimensions = {
"length": f"{match.group(1)}mm",
"width": f"{match.group(2)}mm"
}
break
return {
"pipe_size": pipe_size,
"dimensions": dimensions,
"confidence": 0.8 if dimensions else 0.3
}
def calculate_support_confidence(confidence_scores: Dict) -> float:
"""서포트 분류 전체 신뢰도 계산"""
weights = {
"type": 0.4, # 타입이 가장 중요
"material": 0.2, # 재질
"load": 0.2, # 하중
"size": 0.2 # 사이즈
}
weighted_sum = sum(
confidence_scores.get(key, 0) * weight
for key, weight in weights.items()
)
return round(weighted_sum, 2)