feat: SPECIAL/UNCLASSIFIED 카테고리 추가 및 WELD GAP 자동 제외
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
주요 변경사항: - SPECIAL 카테고리 추가: 특수 제작 품목 관리 (Type, Drawing, Detail1-4) - UNCLASSIFIED 카테고리 추가: 미분류 자재 원본 그대로 표시 - UNKNOWN → UNCLASSIFIED 통합: 기존 UNKNOWN 카테고리 제거 - WELD GAP 자동 제외: BOM 업로드 시 WELD GAP 항목 자동 필터링 백엔드: - integrated_classifier.py: UNKNOWN → UNCLASSIFIED 변경, SPECIAL 우선순위 분류 - files.py: parse_dataframe에서 WELD GAP 필터링, UNKNOWN 참조 제거 - exclude_classifier.py: WELD GAP 제외 로직 유지 프론트엔드: - SpecialMaterialsView.jsx: 특수 제작 품목 관리 컴포넌트 - UnclassifiedMaterialsView.jsx: 미분류 자재 관리 컴포넌트 - BOMManagementPage.jsx: 새 카테고리 추가 및 라우팅 - excelExport.js: SPECIAL/UNCLASSIFIED 엑셀 내보내기 지원 - 모든 UNKNOWN 참조를 UNCLASSIFIED로 변경 기능 개선: - 저장 기능: 모든 카테고리에 추가요청사항 저장/편집 기능 - P열 납기일 규칙: 모든 카테고리 엑셀 내보내기 통일 - UI 개선: Detail1-4 컬럼명으로 혼동 방지 - 데이터 정리: 모든 프로젝트 및 BOM 데이터 초기화
This commit is contained in:
@@ -231,6 +231,14 @@ def parse_dataframe(df):
|
||||
materials = []
|
||||
for index, row in df.iterrows():
|
||||
description = str(row.get(mapped_columns.get('description', ''), ''))
|
||||
|
||||
# WELD GAP 항목은 업로드 단계에서 제외 (불필요한 계산용 항목)
|
||||
description_upper = description.upper()
|
||||
if ('WELD GAP' in description_upper or 'WELDING GAP' in description_upper or
|
||||
'웰드갭' in description_upper or '용접갭' in description_upper):
|
||||
print(f"⚠️ WELD GAP 항목 제외: {description}")
|
||||
continue
|
||||
|
||||
quantity_raw = row.get(mapped_columns.get('quantity', ''), 0)
|
||||
|
||||
try:
|
||||
@@ -547,7 +555,7 @@ async def upload_file(
|
||||
materials_to_insert.append({
|
||||
"file_id": file_id,
|
||||
"original_description": material_data["original_description"],
|
||||
"classified_category": previous_item.get("category", "UNKNOWN"),
|
||||
"classified_category": previous_item.get("category", "UNCLASSIFIED"),
|
||||
"confidence": 1.0, # 확정된 자료이므로 신뢰도 100%
|
||||
"quantity": material_data["quantity"],
|
||||
"unit": material_data.get("unit", "EA"),
|
||||
@@ -659,7 +667,7 @@ async def upload_file(
|
||||
# 1. 통합 분류기로 자재 타입 결정
|
||||
integrated_result = classify_material_integrated(description, main_nom or "", red_nom or "", length_value)
|
||||
print(f"[분류] {description}")
|
||||
print(f"통합 분류 결과: {integrated_result.get('category', 'UNKNOWN')} (신뢰도: {integrated_result.get('confidence', 0)}, Level: {integrated_result.get('classification_level', 'NONE')})")
|
||||
print(f"통합 분류 결과: {integrated_result.get('category', 'UNCLASSIFIED')} (신뢰도: {integrated_result.get('confidence', 0)}, Level: {integrated_result.get('classification_level', 'NONE')})")
|
||||
|
||||
# 2. 제외 대상 확인
|
||||
if should_exclude_material(description):
|
||||
@@ -670,7 +678,7 @@ async def upload_file(
|
||||
}
|
||||
else:
|
||||
# 3. 타입별 상세 분류기 실행
|
||||
material_type = integrated_result.get('category', 'UNKNOWN')
|
||||
material_type = integrated_result.get('category', 'UNCLASSIFIED')
|
||||
|
||||
if material_type == "PIPE":
|
||||
from ..services.pipe_classifier import classify_pipe_for_purchase
|
||||
@@ -714,9 +722,9 @@ async def upload_file(
|
||||
}
|
||||
}
|
||||
else:
|
||||
# UNKNOWN 처리
|
||||
# UNCLASSIFIED 처리
|
||||
classification_result = {
|
||||
"category": "UNKNOWN",
|
||||
"category": "UNCLASSIFIED",
|
||||
"overall_confidence": integrated_result.get('confidence', 0.0),
|
||||
"reason": f"분류 불가: {integrated_result.get('evidence', [])}"
|
||||
}
|
||||
@@ -728,7 +736,7 @@ async def upload_file(
|
||||
integrated_result.get('confidence', 0.0) + 0.2
|
||||
)
|
||||
|
||||
print(f"최종 분류 결과: {classification_result.get('category', 'UNKNOWN')}")
|
||||
print(f"최종 분류 결과: {classification_result.get('category', 'UNCLASSIFIED')}")
|
||||
|
||||
# 전체 재질명 추출
|
||||
from ..services.material_grade_extractor import extract_full_material_grade
|
||||
@@ -758,7 +766,7 @@ async def upload_file(
|
||||
print(f"첫 번째 자재 저장:")
|
||||
print(f" size_spec: '{material_data['size_spec']}'")
|
||||
print(f" original_description: {material_data['original_description']}")
|
||||
print(f" category: {classification_result.get('category', 'UNKNOWN')}")
|
||||
print(f" category: {classification_result.get('category', 'UNCLASSIFIED')}")
|
||||
print(f" drawing_name: {material_data.get('dwg_name')}")
|
||||
print(f" line_no: {material_data.get('line_num')}")
|
||||
|
||||
@@ -774,7 +782,7 @@ async def upload_file(
|
||||
"full_material_grade": full_material_grade,
|
||||
"line_number": material_data["line_number"],
|
||||
"row_number": material_data["row_number"],
|
||||
"classified_category": classification_result.get("category", "UNKNOWN"),
|
||||
"classified_category": classification_result.get("category", "UNCLASSIFIED"),
|
||||
"classification_confidence": classification_result.get("overall_confidence", 0.0),
|
||||
"is_verified": False,
|
||||
"drawing_name": material_data.get("dwg_name"),
|
||||
@@ -889,7 +897,7 @@ async def upload_file(
|
||||
material_grade_from_classifier = material_info.get("grade", "")
|
||||
|
||||
# 분류기에서 더 상세한 재질 정보가 나왔으면 업데이트
|
||||
if material_grade_from_classifier and material_grade_from_classifier != "UNKNOWN":
|
||||
if material_grade_from_classifier and material_grade_from_classifier not in ["UNKNOWN", "UNCLASSIFIED"]:
|
||||
material_spec = material_grade_from_classifier
|
||||
|
||||
# materials 테이블의 material_grade도 업데이트
|
||||
@@ -958,7 +966,7 @@ async def upload_file(
|
||||
material_grade = material_info.get("grade", "")
|
||||
|
||||
# 분류기에서 더 상세한 재질 정보가 나왔으면 업데이트
|
||||
if material_grade and material_grade != "UNKNOWN":
|
||||
if material_grade and material_grade not in ["UNKNOWN", "UNCLASSIFIED"]:
|
||||
db.execute(text("""
|
||||
UPDATE materials
|
||||
SET material_grade = :new_material_grade
|
||||
@@ -1055,7 +1063,7 @@ async def upload_file(
|
||||
material_grade = material_info.get("grade", "")
|
||||
|
||||
# 분류기에서 더 상세한 재질 정보가 나왔으면 업데이트
|
||||
if material_grade and material_grade != "UNKNOWN":
|
||||
if material_grade and material_grade not in ["UNKNOWN", "UNCLASSIFIED"]:
|
||||
db.execute(text("""
|
||||
UPDATE materials
|
||||
SET material_grade = :new_material_grade
|
||||
@@ -1245,7 +1253,7 @@ async def upload_file(
|
||||
material_grade = material_info.get("grade", "")
|
||||
|
||||
# 분류기에서 더 상세한 재질 정보가 나왔으면 업데이트
|
||||
if material_grade and material_grade != "UNKNOWN":
|
||||
if material_grade and material_grade not in ["UNKNOWN", "UNCLASSIFIED"]:
|
||||
db.execute(text("""
|
||||
UPDATE materials
|
||||
SET material_grade = :new_material_grade
|
||||
|
||||
@@ -8,11 +8,6 @@ from typing import Dict, List, Optional
|
||||
|
||||
# ========== 제외 대상 타입 ==========
|
||||
EXCLUDE_TYPES = {
|
||||
"WELD_GAP": {
|
||||
"description_keywords": ["WELD GAP", "WELDING GAP", "GAP", "용접갭", "웰드갭"],
|
||||
"characteristics": "용접 시 수축 고려용 계산 항목",
|
||||
"reason": "실제 자재 아님 - 용접 갭 계산용"
|
||||
},
|
||||
"CUTTING_LOSS": {
|
||||
"description_keywords": ["CUTTING LOSS", "CUT LOSS", "절단로스", "컷팅로스"],
|
||||
"characteristics": "절단 시 손실 고려용 계산 항목",
|
||||
|
||||
@@ -110,6 +110,7 @@ def classify_material_integrated(description: str, main_nom: str = "",
|
||||
"reason": "스페셜 키워드 발견"
|
||||
}
|
||||
|
||||
|
||||
# VALVE 카테고리 우선 확인 (SIGHT GLASS, STRAINER)
|
||||
if ('SIGHT GLASS' in desc_upper or 'STRAINER' in desc_upper or
|
||||
'사이트글라스' in desc_upper or '스트레이너' in desc_upper):
|
||||
@@ -295,7 +296,7 @@ def classify_material_integrated(description: str, main_nom: str = "",
|
||||
|
||||
# 분류 실패
|
||||
return {
|
||||
"category": "UNKNOWN",
|
||||
"category": "UNCLASSIFIED",
|
||||
"confidence": 0.0,
|
||||
"evidence": ["NO_CLASSIFICATION_POSSIBLE"],
|
||||
"classification_level": "NONE"
|
||||
|
||||
Reference in New Issue
Block a user