볼트 분류 개선 및 업로드 성능 최적화
- 볼트 길이 추출 로직 개선: '70.0000 LG' 형태 인식 추가 - 재질 중복 표시 수정: 'ASTM A193 ASTM A193 B7' → 'B7' - A193/A194 등급 추출 로직 개선: 'GR B7/2H' 형태 지원 - bolt_details 테이블에 pressure_rating 컬럼 추가 - 볼트 분류기 오분류 방지: 플랜지/피팅이 볼트로 분류되지 않도록 수정 - 업로드 성능 개선: 키워드 기반 빠른 분류기 선택 로직 추가 - 분류 키워드 대폭 확장: 피팅/파이프/플랜지 키워드 추가
This commit is contained in:
@@ -254,7 +254,9 @@ function MaterialList({ selectedProject }) {
|
||||
'VALVE': 'success',
|
||||
'FLANGE': 'warning',
|
||||
'BOLT': 'info',
|
||||
'OTHER': 'default'
|
||||
'GASKET': 'error',
|
||||
'INSTRUMENT': 'purple',
|
||||
'OTHER': 'default'
|
||||
};
|
||||
return colors[itemType] || 'default';
|
||||
};
|
||||
@@ -540,6 +542,8 @@ function MaterialList({ selectedProject }) {
|
||||
<MenuItem key="VALVE" value="VALVE">VALVE</MenuItem>
|
||||
<MenuItem key="FLANGE" value="FLANGE">FLANGE</MenuItem>
|
||||
<MenuItem key="BOLT" value="BOLT">BOLT</MenuItem>
|
||||
<MenuItem key="GASKET" value="GASKET">GASKET</MenuItem>
|
||||
<MenuItem key="INSTRUMENT" value="INSTRUMENT">INSTRUMENT</MenuItem>
|
||||
<MenuItem key="OTHER" value="OTHER">기타</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
@@ -125,8 +125,8 @@ const MaterialsPage = () => {
|
||||
spec_parts.push(fitting_subtype);
|
||||
}
|
||||
|
||||
// NIPPLE 스케줄 정보 추가 (중요!)
|
||||
const nipple_schedule = material.fitting_details?.schedule || material.pipe_details?.schedule;
|
||||
// NIPPLE 스케줄 정보 추가 (fitting_details에서 가져옴)
|
||||
const nipple_schedule = material.fitting_details?.schedule;
|
||||
if (nipple_schedule && nipple_schedule !== 'UNKNOWN') {
|
||||
spec_parts.push(nipple_schedule);
|
||||
}
|
||||
@@ -137,8 +137,8 @@ const MaterialsPage = () => {
|
||||
spec_parts.push(connection_method);
|
||||
}
|
||||
|
||||
// NIPPLE 길이 정보 추가 (mm → m 변환 또는 그대로)
|
||||
const length_mm = material.length || material.pipe_details?.length_mm;
|
||||
// NIPPLE 길이 정보 추가 (fitting_details에서 가져옴)
|
||||
const length_mm = material.fitting_details?.length_mm;
|
||||
if (length_mm && length_mm > 0) {
|
||||
if (length_mm >= 1000) {
|
||||
spec_parts.push(`${(length_mm / 1000).toFixed(2)}m`);
|
||||
@@ -190,6 +190,228 @@ const MaterialsPage = () => {
|
||||
unit: 'EA',
|
||||
isLength: false
|
||||
};
|
||||
} else if (category === 'FLANGE') {
|
||||
// FLANGE: 타입 + 압력등급 + 면가공 + 재질
|
||||
const material_spec = material.flange_details?.material_spec || material.material_grade || '';
|
||||
const main_nom = material.main_nom || '';
|
||||
const flange_type = material.flange_details?.flange_type || 'UNKNOWN';
|
||||
const pressure_rating = material.flange_details?.pressure_rating || '';
|
||||
const facing_type = material.flange_details?.facing_type || '';
|
||||
|
||||
// 플랜지 스펙 생성
|
||||
const flange_spec_parts = [];
|
||||
|
||||
// 플랜지 타입 (WN, BL, SO 등)
|
||||
if (flange_type && flange_type !== 'UNKNOWN') {
|
||||
flange_spec_parts.push(flange_type);
|
||||
}
|
||||
|
||||
// 면 가공 (RF, FF, RTJ 등)
|
||||
if (facing_type && facing_type !== 'UNKNOWN') {
|
||||
flange_spec_parts.push(facing_type);
|
||||
}
|
||||
|
||||
// 압력등급 (150LB, 300LB 등)
|
||||
if (pressure_rating && pressure_rating !== 'UNKNOWN') {
|
||||
flange_spec_parts.push(pressure_rating);
|
||||
}
|
||||
|
||||
const full_flange_spec = flange_spec_parts.join(', ');
|
||||
|
||||
specKey = `${category}|${full_flange_spec}|${material_spec}|${main_nom}`;
|
||||
specData = {
|
||||
category: 'FLANGE',
|
||||
flange_type,
|
||||
pressure_rating,
|
||||
facing_type,
|
||||
full_flange_spec,
|
||||
material_spec,
|
||||
size_display: main_nom,
|
||||
main_nom,
|
||||
unit: 'EA',
|
||||
isLength: false
|
||||
};
|
||||
} else if (category === 'GASKET') {
|
||||
// GASKET: 타입 + 소재 + 압력등급 + 사이즈
|
||||
const main_nom = material.main_nom || '';
|
||||
const gasket_type = material.gasket_details?.gasket_type || 'UNKNOWN';
|
||||
const material_type = material.gasket_details?.material_type || 'UNKNOWN';
|
||||
const pressure_rating = material.gasket_details?.pressure_rating || '';
|
||||
|
||||
// 가스켓 재질은 gasket_details에서 가져옴
|
||||
const material_spec = material_type !== 'UNKNOWN' ? material_type : (material.material_grade || 'Unknown');
|
||||
|
||||
// SWG 상세 정보 파싱 (additional_info에서)
|
||||
let detailed_construction = 'N/A';
|
||||
let face_type = '';
|
||||
let thickness = material.gasket_details?.thickness || null;
|
||||
|
||||
// API에서 gasket_details의 추가 정보를 확인 (브라우저 콘솔에서 확인용)
|
||||
if (material.gasket_details && Object.keys(material.gasket_details).length > 0) {
|
||||
console.log('Gasket details:', material.gasket_details);
|
||||
}
|
||||
|
||||
// 상세 구성 정보 생성 (Face Type + Construction)
|
||||
// H/F/I/O SS304/GRAPHITE/CS/CS 형태로 표시
|
||||
if (material.original_description) {
|
||||
const desc = material.original_description.toUpperCase();
|
||||
|
||||
// H/F/I/O 다음에 오는 재질 구성만 찾기 (H/F/I/O는 제외)
|
||||
const fullMatch = desc.match(/H\/F\/I\/O\s+([A-Z0-9]+\/[A-Z]+\/[A-Z0-9]+\/[A-Z0-9]+)/);
|
||||
if (fullMatch) {
|
||||
// H/F/I/O와 재질 구성 둘 다 있는 경우
|
||||
face_type = 'H/F/I/O';
|
||||
const construction = fullMatch[1];
|
||||
detailed_construction = `${face_type} ${construction}`;
|
||||
} else {
|
||||
// H/F/I/O만 있는 경우
|
||||
const faceMatch = desc.match(/H\/F\/I\/O/);
|
||||
if (faceMatch) {
|
||||
detailed_construction = 'H/F/I/O';
|
||||
} else {
|
||||
// 재질 구성만 있는 경우 (H/F/I/O 없이)
|
||||
const constructionOnlyMatch = desc.match(/([A-Z0-9]+\/[A-Z]+\/[A-Z0-9]+\/[A-Z0-9]+)/);
|
||||
if (constructionOnlyMatch) {
|
||||
detailed_construction = constructionOnlyMatch[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 가스켓 스펙 생성
|
||||
const gasket_spec_parts = [];
|
||||
|
||||
// 가스켓 타입 (SPIRAL_WOUND, O_RING 등)
|
||||
if (gasket_type && gasket_type !== 'UNKNOWN') {
|
||||
gasket_spec_parts.push(gasket_type.replace('_', ' '));
|
||||
}
|
||||
|
||||
// 소재 (GRAPHITE, PTFE 등)
|
||||
if (material_type && material_type !== 'UNKNOWN') {
|
||||
gasket_spec_parts.push(material_type);
|
||||
}
|
||||
|
||||
// 압력등급 (150LB, 300LB 등)
|
||||
if (pressure_rating && pressure_rating !== 'UNKNOWN') {
|
||||
gasket_spec_parts.push(pressure_rating);
|
||||
}
|
||||
|
||||
const full_gasket_spec = gasket_spec_parts.join(', ');
|
||||
|
||||
specKey = `${category}|${full_gasket_spec}|${material_spec}|${main_nom}|${detailed_construction}`;
|
||||
specData = {
|
||||
category: 'GASKET',
|
||||
gasket_type,
|
||||
material_type,
|
||||
pressure_rating,
|
||||
full_gasket_spec,
|
||||
material_spec,
|
||||
size_display: main_nom,
|
||||
main_nom,
|
||||
detailed_construction,
|
||||
thickness,
|
||||
unit: 'EA',
|
||||
isLength: false
|
||||
};
|
||||
} else if (category === 'BOLT') {
|
||||
// BOLT: 타입 + 재질 + 사이즈 + 길이
|
||||
const material_spec = material.material_grade || '';
|
||||
const main_nom = material.main_nom || '';
|
||||
const bolt_type = material.bolt_details?.bolt_type || 'BOLT';
|
||||
const material_standard = material.bolt_details?.material_standard || '';
|
||||
const material_grade = material.bolt_details?.material_grade || '';
|
||||
const thread_type = material.bolt_details?.thread_type || '';
|
||||
const diameter = material.bolt_details?.diameter || main_nom;
|
||||
const length = material.bolt_details?.length || '';
|
||||
const pressure_rating = material.bolt_details?.pressure_rating || '';
|
||||
const coating_type = material.bolt_details?.coating_type || '';
|
||||
|
||||
// 볼트 스펙 생성
|
||||
const bolt_spec_parts = [];
|
||||
|
||||
// 볼트 타입 (HEX_BOLT, STUD_BOLT 등)
|
||||
if (bolt_type && bolt_type !== 'UNKNOWN') {
|
||||
bolt_spec_parts.push(bolt_type.replace('_', ' '));
|
||||
}
|
||||
|
||||
// 재질 (ASTM A193, ASTM A194 등)
|
||||
if (material_standard) {
|
||||
bolt_spec_parts.push(material_standard);
|
||||
if (material_grade && material_grade !== material_standard) {
|
||||
bolt_spec_parts.push(material_grade);
|
||||
}
|
||||
} else if (material_spec) {
|
||||
bolt_spec_parts.push(material_spec);
|
||||
}
|
||||
|
||||
// 나사 규격 (M12, 1/2" 등)
|
||||
if (diameter) {
|
||||
bolt_spec_parts.push(diameter);
|
||||
}
|
||||
|
||||
// 코팅 타입 (ELECTRO_GALVANIZED 등)
|
||||
if (coating_type && coating_type !== 'PLAIN') {
|
||||
bolt_spec_parts.push(coating_type.replace('_', ' '));
|
||||
}
|
||||
|
||||
const full_bolt_spec = bolt_spec_parts.join(', ');
|
||||
|
||||
specKey = `${category}|${full_bolt_spec}|${diameter}|${length}|${coating_type}|${pressure_rating}`;
|
||||
specData = {
|
||||
category: 'BOLT',
|
||||
bolt_type,
|
||||
thread_type,
|
||||
full_bolt_spec,
|
||||
material_standard,
|
||||
material_grade,
|
||||
diameter,
|
||||
length,
|
||||
coating_type,
|
||||
pressure_rating,
|
||||
size_display: diameter,
|
||||
main_nom: diameter,
|
||||
unit: 'EA',
|
||||
isLength: false
|
||||
};
|
||||
} else if (category === 'INSTRUMENT') {
|
||||
// INSTRUMENT: 타입 + 연결사이즈 + 측정범위
|
||||
const main_nom = material.main_nom || '';
|
||||
const instrument_type = material.instrument_details?.instrument_type || 'INSTRUMENT';
|
||||
const measurement_range = material.instrument_details?.measurement_range || '';
|
||||
const signal_type = material.instrument_details?.signal_type || '';
|
||||
|
||||
// 계기 스펙 생성
|
||||
const instrument_spec_parts = [];
|
||||
|
||||
// 계기 타입 (PRESSURE_GAUGE, TEMPERATURE_TRANSMITTER 등)
|
||||
if (instrument_type && instrument_type !== 'UNKNOWN') {
|
||||
instrument_spec_parts.push(instrument_type.replace('_', ' '));
|
||||
}
|
||||
|
||||
// 측정 범위 (0-100 PSI, 4-20mA 등)
|
||||
if (measurement_range) {
|
||||
instrument_spec_parts.push(measurement_range);
|
||||
}
|
||||
|
||||
// 연결 사이즈 (1/4", 1/2" 등)
|
||||
if (main_nom) {
|
||||
instrument_spec_parts.push(`${main_nom} CONNECTION`);
|
||||
}
|
||||
|
||||
const full_instrument_spec = instrument_spec_parts.join(', ');
|
||||
|
||||
specKey = `${category}|${full_instrument_spec}|${main_nom}`;
|
||||
specData = {
|
||||
category: 'INSTRUMENT',
|
||||
instrument_type,
|
||||
measurement_range,
|
||||
signal_type,
|
||||
full_instrument_spec,
|
||||
size_display: main_nom,
|
||||
main_nom,
|
||||
unit: 'EA',
|
||||
isLength: false
|
||||
};
|
||||
} else {
|
||||
// 기타 자재: 기본 분류
|
||||
const material_spec = material.material_grade || '';
|
||||
@@ -385,15 +607,46 @@ const MaterialsPage = () => {
|
||||
<TableCell align="right"><strong>수량</strong></TableCell>
|
||||
</>
|
||||
)}
|
||||
{!['PIPE', 'FITTING'].includes(category) && (
|
||||
{category === 'FLANGE' && (
|
||||
<>
|
||||
<TableCell><strong>플랜지 타입</strong></TableCell>
|
||||
<TableCell><strong>재질</strong></TableCell>
|
||||
<TableCell><strong>사이즈</strong></TableCell>
|
||||
<TableCell align="right"><strong>수량</strong></TableCell>
|
||||
</>
|
||||
)}
|
||||
{!['PIPE', 'FITTING'].includes(category) && (
|
||||
{category === 'GASKET' && (
|
||||
<>
|
||||
<TableCell><strong>가스켓 타입</strong></TableCell>
|
||||
<TableCell><strong>상세 구성</strong></TableCell>
|
||||
<TableCell><strong>재질</strong></TableCell>
|
||||
<TableCell><strong>두께</strong></TableCell>
|
||||
<TableCell><strong>사이즈</strong></TableCell>
|
||||
<TableCell align="right"><strong>수량</strong></TableCell>
|
||||
</>
|
||||
)}
|
||||
{category === 'BOLT' && (
|
||||
<>
|
||||
<TableCell><strong>볼트 타입</strong></TableCell>
|
||||
<TableCell><strong>재질</strong></TableCell>
|
||||
<TableCell><strong>사이즈</strong></TableCell>
|
||||
<TableCell><strong>길이</strong></TableCell>
|
||||
<TableCell><strong>코팅</strong></TableCell>
|
||||
<TableCell><strong>압력등급</strong></TableCell>
|
||||
<TableCell align="right"><strong>수량</strong></TableCell>
|
||||
</>
|
||||
)}
|
||||
{category === 'INSTRUMENT' && (
|
||||
<>
|
||||
<TableCell><strong>계기 타입</strong></TableCell>
|
||||
<TableCell><strong>측정범위</strong></TableCell>
|
||||
<TableCell><strong>연결사이즈</strong></TableCell>
|
||||
<TableCell align="right"><strong>수량</strong></TableCell>
|
||||
</>
|
||||
)}
|
||||
{!['PIPE', 'FITTING', 'FLANGE', 'GASKET', 'BOLT', 'INSTRUMENT'].includes(category) && (
|
||||
<>
|
||||
<TableCell><strong>재질</strong></TableCell>
|
||||
<TableCell><strong>사이즈</strong></TableCell>
|
||||
<TableCell align="right"><strong>수량</strong></TableCell>
|
||||
</>
|
||||
@@ -437,8 +690,13 @@ const MaterialsPage = () => {
|
||||
</TableCell>
|
||||
</>
|
||||
)}
|
||||
{(!['PIPE', 'FITTING'].includes(category)) && (
|
||||
{category === 'FLANGE' && (
|
||||
<>
|
||||
<TableCell>
|
||||
<Typography variant="body2" sx={{ maxWidth: 300, fontWeight: 'bold' }}>
|
||||
{spec.full_flange_spec || spec.flange_type || 'UNKNOWN'}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{spec.material_spec || 'Unknown'}</TableCell>
|
||||
<TableCell>{spec.size_display || 'Unknown'}</TableCell>
|
||||
<TableCell align="right">
|
||||
@@ -448,8 +706,90 @@ const MaterialsPage = () => {
|
||||
</TableCell>
|
||||
</>
|
||||
)}
|
||||
{(!['PIPE', 'FITTING'].includes(category)) && (
|
||||
{category === 'GASKET' && (
|
||||
<>
|
||||
<TableCell>
|
||||
<Typography variant="body2" sx={{ fontWeight: 'bold' }}>
|
||||
{spec.gasket_type?.replace('_', ' ') || 'UNKNOWN'}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2" sx={{ maxWidth: 200, fontSize: '0.85rem' }}>
|
||||
{spec.detailed_construction || 'N/A'}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{spec.material_spec || 'Unknown'}</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2">
|
||||
{spec.thickness ? `${spec.thickness}mm` : 'N/A'}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{spec.size_display || 'Unknown'}</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
{spec.totalQuantity} {spec.unit}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</>
|
||||
)}
|
||||
{category === 'BOLT' && (
|
||||
<>
|
||||
<TableCell>
|
||||
<Typography variant="body2" sx={{ fontWeight: 'bold' }}>
|
||||
{spec.bolt_type?.replace('_', ' ') || 'UNKNOWN'}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2">
|
||||
{spec.material_standard || 'Unknown'} {spec.material_grade || ''}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2">
|
||||
{spec.diameter ? (spec.diameter.includes('"') ? spec.diameter : spec.diameter.replace('0.5', '1/2"').replace('0.75', '3/4"').replace('1.0', '1"').replace('1.5', '1 1/2"')) : 'Unknown'}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2">
|
||||
{spec.length || 'N/A'}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2">
|
||||
{spec.coating_type ? spec.coating_type.replace('_', ' ') : 'N/A'}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2">
|
||||
{spec.pressure_rating || 'N/A'}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
{spec.totalQuantity} {spec.unit}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</>
|
||||
)}
|
||||
{category === 'INSTRUMENT' && (
|
||||
<>
|
||||
<TableCell>
|
||||
<Typography variant="body2" sx={{ fontWeight: 'bold' }}>
|
||||
{spec.instrument_type?.replace('_', ' ') || 'UNKNOWN'}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{spec.measurement_range || 'Unknown'}</TableCell>
|
||||
<TableCell>{spec.size_display || 'Unknown'}</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
{spec.totalQuantity} {spec.unit}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</>
|
||||
)}
|
||||
{(!['PIPE', 'FITTING', 'FLANGE', 'GASKET', 'BOLT', 'INSTRUMENT'].includes(category)) && (
|
||||
<>
|
||||
<TableCell>{spec.material_spec || 'Unknown'}</TableCell>
|
||||
<TableCell>{spec.size_display || 'Unknown'}</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
|
||||
Reference in New Issue
Block a user