🎯 주요 성과: - 8개 주요 자재군 완전 분류 시스템 구축 - 재질 분류 엔진 + 개별 자재별 특화 분류 - 스풀 관리 시스템 (파이프 절단 계획용) - 실제 BOM 데이터 기반 설계 및 테스트 📁 새로 추가된 자재 분류 시스템: - app/services/pipe_classifier.py (파이프 + 스풀 관리) - app/services/fitting_classifier.py (피팅 10가지 타입) - app/services/flange_classifier.py (플랜지 SPECIAL/STANDARD) - app/services/valve_classifier.py (밸브 단조/주조 구분) - app/services/gasket_classifier.py (가스켓 8가지 타입) - app/services/bolt_classifier.py (볼트/너트/와셔 통합) - app/services/instrument_classifier.py (계기류 기본) 🔧 분류 성능: - PIPE: 제조방법, 끝가공, 스케줄, 절단계획 - FITTING: 타입, 연결방식, 압력등급, 제작방법 - FLANGE: SPECIAL(10종)/STANDARD(6종), 면가공 - VALVE: 9가지 타입, 단조/주조 구분, 작동방식 - GASKET: 8가지 타입, 재질별, 온도/압력 범위 - BOLT: 체결재 3종, 나사규격, 강도등급 📊 기술적 특징: - 정규표현식 기반 패턴 매칭 엔진 - 신뢰도 점수 시스템 (0.0-1.0) - 증거 기반 분류 추적 (evidence tracking) - 모듈화 구조로 재사용성 극대화 - 실제 DAT_FILE + DESCRIPTION 패턴 분석 🎯 분류 커버리지: - 재질: ASTM/ASME 표준 + 특수합금 (INCONEL, TITANIUM) - 제작방법: FORGED, CAST, SEAMLESS, WELDED 자동 판단 - 압력등급: 150LB ~ 9000LB 전 범위 - 연결방식: BW, SW, THD, FL 등 모든 방식 - 사이즈: 1/8" ~ 48" 전 범위 💾 데이터 통합: - 기존 materials 테이블과 완전 호환 - 프로젝트/도면 정보 자동 연결 - 스풀 정보 사용자 입력 대기 (파이프만) - 구매 정보 자동 생성 (공급업체, 납기) 🧪 테스트 완료: - 각 시스템별 10+ 테스트 케이스 - 실제 BOM 데이터 기반 검증 - 예외 상황 처리 테스트 - 신뢰도 검증 완료 Version: v2.0 (Major Release) Date: 2024-07-15 Author: hyungiahn Breaking Changes: 새로운 분류 시스템 추가 (기존 호환성 유지) Next Phase: files.py 통합 및 웹 인터페이스 연동
191 lines
6.3 KiB
Python
191 lines
6.3 KiB
Python
"""
|
|
INSTRUMENT 분류 시스템 (간단 버전)
|
|
완제품 구매용 기본 분류만
|
|
"""
|
|
|
|
import re
|
|
from typing import Dict, List, Optional
|
|
from .material_classifier import classify_material
|
|
|
|
# ========== 기본 계기 타입 ==========
|
|
INSTRUMENT_TYPES = {
|
|
"PRESSURE_GAUGE": {
|
|
"dat_file_patterns": ["PG_", "PRESS_G"],
|
|
"description_keywords": ["PRESSURE GAUGE", "압력계", "PG"],
|
|
"characteristics": "압력 측정용 게이지"
|
|
},
|
|
"TEMPERATURE_GAUGE": {
|
|
"dat_file_patterns": ["TG_", "TEMP_G"],
|
|
"description_keywords": ["TEMPERATURE GAUGE", "온도계", "TG", "THERMOMETER"],
|
|
"characteristics": "온도 측정용 게이지"
|
|
},
|
|
"FLOW_METER": {
|
|
"dat_file_patterns": ["FM_", "FLOW_"],
|
|
"description_keywords": ["FLOW METER", "유량계", "FM"],
|
|
"characteristics": "유량 측정용"
|
|
},
|
|
"LEVEL_GAUGE": {
|
|
"dat_file_patterns": ["LG_", "LEVEL_"],
|
|
"description_keywords": ["LEVEL GAUGE", "액위계", "LG", "SIGHT GLASS"],
|
|
"characteristics": "액위 측정용"
|
|
},
|
|
"TRANSMITTER": {
|
|
"dat_file_patterns": ["PT_", "TT_", "FT_", "LT_"],
|
|
"description_keywords": ["TRANSMITTER", "트랜스미터", "4-20MA"],
|
|
"characteristics": "신호 전송용"
|
|
},
|
|
"INDICATOR": {
|
|
"dat_file_patterns": ["PI_", "TI_", "FI_", "LI_"],
|
|
"description_keywords": ["INDICATOR", "지시계", "DISPLAY"],
|
|
"characteristics": "표시용"
|
|
},
|
|
"SPECIAL_INSTRUMENT": {
|
|
"dat_file_patterns": ["INST_", "SPEC_"],
|
|
"description_keywords": ["THERMOWELL", "ORIFICE PLATE", "MANOMETER", "ROTAMETER"],
|
|
"characteristics": "특수 계기류"
|
|
}
|
|
}
|
|
|
|
def classify_instrument(dat_file: str, description: str, main_nom: str) -> Dict:
|
|
"""
|
|
간단한 INSTRUMENT 분류
|
|
|
|
Args:
|
|
dat_file: DAT_FILE 필드
|
|
description: DESCRIPTION 필드
|
|
main_nom: MAIN_NOM 필드 (연결 사이즈)
|
|
|
|
Returns:
|
|
간단한 계기 분류 결과
|
|
"""
|
|
|
|
# 1. 재질 분류 (공통 모듈)
|
|
material_result = classify_material(description)
|
|
|
|
# 2. 계기 타입 분류
|
|
instrument_type_result = classify_instrument_type(dat_file, description)
|
|
|
|
# 3. 측정 범위 추출 (있다면)
|
|
measurement_range = extract_measurement_range(description)
|
|
|
|
# 4. 최종 결과
|
|
return {
|
|
"category": "INSTRUMENT",
|
|
|
|
# 재질 정보
|
|
"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)
|
|
},
|
|
|
|
# 계기 정보
|
|
"instrument_type": {
|
|
"type": instrument_type_result.get('type', 'UNKNOWN'),
|
|
"characteristics": instrument_type_result.get('characteristics', ''),
|
|
"confidence": instrument_type_result.get('confidence', 0.0)
|
|
},
|
|
|
|
"measurement_info": {
|
|
"range": measurement_range.get('range', ''),
|
|
"unit": measurement_range.get('unit', ''),
|
|
"signal_type": measurement_range.get('signal_type', '')
|
|
},
|
|
|
|
"size_info": {
|
|
"connection_size": main_nom,
|
|
"size_description": main_nom
|
|
},
|
|
|
|
"purchase_info": {
|
|
"category": "완제품 구매",
|
|
"supplier_type": "계기 전문업체",
|
|
"lead_time": "2-4주",
|
|
"note": "사양서 확인 후 주문"
|
|
},
|
|
|
|
# 간단한 신뢰도
|
|
"overall_confidence": calculate_simple_confidence([
|
|
material_result.get('confidence', 0),
|
|
instrument_type_result.get('confidence', 0)
|
|
])
|
|
}
|
|
|
|
def classify_instrument_type(dat_file: str, description: str) -> Dict:
|
|
"""계기 타입 분류"""
|
|
|
|
dat_upper = dat_file.upper()
|
|
desc_upper = description.upper()
|
|
|
|
# DAT_FILE 패턴 확인
|
|
for inst_type, type_data in INSTRUMENT_TYPES.items():
|
|
for pattern in type_data["dat_file_patterns"]:
|
|
if pattern in dat_upper:
|
|
return {
|
|
"type": inst_type,
|
|
"characteristics": type_data["characteristics"],
|
|
"confidence": 0.9,
|
|
"evidence": [f"DAT_PATTERN: {pattern}"]
|
|
}
|
|
|
|
# DESCRIPTION 키워드 확인
|
|
for inst_type, type_data in INSTRUMENT_TYPES.items():
|
|
for keyword in type_data["description_keywords"]:
|
|
if keyword in desc_upper:
|
|
return {
|
|
"type": inst_type,
|
|
"characteristics": type_data["characteristics"],
|
|
"confidence": 0.8,
|
|
"evidence": [f"KEYWORD: {keyword}"]
|
|
}
|
|
|
|
return {
|
|
"type": "UNKNOWN",
|
|
"characteristics": "분류되지 않은 계기",
|
|
"confidence": 0.0,
|
|
"evidence": ["NO_INSTRUMENT_TYPE_FOUND"]
|
|
}
|
|
|
|
def extract_measurement_range(description: str) -> Dict:
|
|
"""측정 범위 추출 (간단히)"""
|
|
|
|
desc_upper = description.upper()
|
|
|
|
# 압력 범위
|
|
pressure_match = re.search(r'(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)\s*(PSI|BAR|KPA)', desc_upper)
|
|
if pressure_match:
|
|
return {
|
|
"range": f"{pressure_match.group(1)}-{pressure_match.group(2)}",
|
|
"unit": pressure_match.group(3),
|
|
"signal_type": "PRESSURE"
|
|
}
|
|
|
|
# 온도 범위
|
|
temp_match = re.search(r'(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)\s*(°?C|°?F)', desc_upper)
|
|
if temp_match:
|
|
return {
|
|
"range": f"{temp_match.group(1)}-{temp_match.group(2)}",
|
|
"unit": temp_match.group(3),
|
|
"signal_type": "TEMPERATURE"
|
|
}
|
|
|
|
# 신호 타입
|
|
if "4-20MA" in desc_upper or "4-20 MA" in desc_upper:
|
|
return {
|
|
"range": "4-20mA",
|
|
"unit": "mA",
|
|
"signal_type": "ANALOG"
|
|
}
|
|
|
|
return {
|
|
"range": "",
|
|
"unit": "",
|
|
"signal_type": ""
|
|
}
|
|
|
|
def calculate_simple_confidence(scores: List[float]) -> float:
|
|
"""간단한 신뢰도 계산"""
|
|
valid_scores = [s for s in scores if s > 0]
|
|
return round(sum(valid_scores) / len(valid_scores), 2) if valid_scores else 0.0
|