리비전 페이지 제거 및 트랜잭션 오류 임시 수정
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- frontend/src/pages/revision/ 폴더 완전 삭제 - EnhancedRevisionPage.css 제거 - support_details 저장 시 트랜잭션 오류로 인해 임시로 상세 정보 저장 비활성화 - 리비전 기능 재설계 예정
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
from fastapi import UploadFile, HTTPException
|
||||
@@ -14,9 +16,8 @@ from sqlalchemy import text
|
||||
from ..models import File, Material
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.file_processor import parse_file_data
|
||||
from ..utils.file_validator import validate_file_extension, generate_unique_filename
|
||||
from ..utils.file_validator import file_validator
|
||||
from .database_service import DatabaseService
|
||||
from .material_classification_service import MaterialClassificationService
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -25,6 +26,16 @@ ALLOWED_EXTENSIONS = {'.xls', '.xlsx', '.csv'}
|
||||
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
|
||||
|
||||
|
||||
def generate_unique_filename(original_filename: str) -> str:
|
||||
"""고유한 파일명 생성"""
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
unique_id = str(uuid.uuid4())[:8]
|
||||
stem = Path(original_filename).stem
|
||||
suffix = Path(original_filename).suffix
|
||||
|
||||
return f"{stem}_{timestamp}_{unique_id}{suffix}"
|
||||
|
||||
|
||||
class FileUploadService:
|
||||
"""파일 업로드 서비스"""
|
||||
|
||||
@@ -36,7 +47,7 @@ class FileUploadService:
|
||||
def validate_upload_request(self, file: UploadFile, job_no: str) -> None:
|
||||
"""업로드 요청 검증"""
|
||||
|
||||
if not validate_file_extension(file.filename):
|
||||
if not file_validator.validate_file_extension(file.filename):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"지원하지 않는 파일 형식입니다. 허용된 확장자: {', '.join(ALLOWED_EXTENSIONS)}"
|
||||
@@ -129,7 +140,8 @@ class FileUploadService:
|
||||
file_id: int,
|
||||
job_no: str,
|
||||
revision: str,
|
||||
parent_file_id: Optional[int] = None
|
||||
parent_file_id: Optional[int] = None,
|
||||
is_revision: bool = False
|
||||
) -> Dict[str, Any]:
|
||||
"""자재 데이터 처리"""
|
||||
|
||||
@@ -189,8 +201,9 @@ class FileUploadService:
|
||||
# 자재 데이터 DB 저장
|
||||
inserted_count = self.db_service.bulk_insert_materials(processed_materials, file_id)
|
||||
|
||||
# 상세 정보 저장 (분류별)
|
||||
self._save_material_details(processed_materials, file_id)
|
||||
# 상세 정보 저장 (임시로 모든 업로드에서 건너뛰기)
|
||||
# TODO: 상세 정보 저장 로직 수정 필요
|
||||
logger.info(f"Skipped material details saving (temporarily disabled)")
|
||||
|
||||
logger.info(f"Processed {inserted_count} materials for file {file_id}")
|
||||
|
||||
@@ -589,19 +602,102 @@ class FileUploadService:
|
||||
|
||||
|
||||
class MaterialClassificationService:
|
||||
"""자재 분류 서비스 (임시 구현)"""
|
||||
"""자재 분류 서비스"""
|
||||
|
||||
def classify_material(self, material_data: Dict, line_number: int, row_number: int) -> Dict:
|
||||
"""자재 분류 (기존 로직 유지)"""
|
||||
"""자재 분류 (실제 분류 로직 적용)"""
|
||||
|
||||
# 기존 분류 로직을 여기에 통합
|
||||
# 현재는 기본값만 설정
|
||||
material_data.update({
|
||||
'line_number': line_number,
|
||||
'row_number': row_number,
|
||||
'classified_category': material_data.get('classified_category', 'UNKNOWN'),
|
||||
'classification_confidence': material_data.get('classification_confidence', 0.0),
|
||||
'classification_details': material_data.get('classification_details', {})
|
||||
})
|
||||
description = material_data.get('original_description', '')
|
||||
main_nom = material_data.get('main_nom', '')
|
||||
red_nom = material_data.get('red_nom', '')
|
||||
length_value = material_data.get('length', 0)
|
||||
|
||||
return material_data
|
||||
try:
|
||||
# 1. 통합 분류기로 1차 분류
|
||||
from ..services.integrated_classifier import classify_material_integrated
|
||||
integrated_result = classify_material_integrated(description, main_nom, red_nom, length_value)
|
||||
|
||||
# 2. 제외 대상 확인
|
||||
if self._should_exclude_material(description):
|
||||
classification_result = {
|
||||
"category": "EXCLUDE",
|
||||
"overall_confidence": 0.95,
|
||||
"reason": "제외 대상 자재"
|
||||
}
|
||||
else:
|
||||
# 3. 타입별 상세 분류기 실행
|
||||
material_type = integrated_result.get('category', 'UNCLASSIFIED')
|
||||
|
||||
if material_type == "PIPE":
|
||||
from ..services.pipe_classifier import classify_pipe_for_purchase
|
||||
classification_result = classify_pipe_for_purchase("", description, main_nom, length_value)
|
||||
elif material_type == "FITTING":
|
||||
from ..services.fitting_classifier import classify_fitting
|
||||
classification_result = classify_fitting("", description, main_nom, red_nom)
|
||||
elif material_type == "FLANGE":
|
||||
from ..services.flange_classifier import classify_flange
|
||||
classification_result = classify_flange("", description, main_nom, red_nom)
|
||||
elif material_type == "VALVE":
|
||||
from ..services.valve_classifier import classify_valve
|
||||
classification_result = classify_valve("", description, main_nom)
|
||||
elif material_type == "BOLT":
|
||||
from ..services.bolt_classifier import classify_bolt
|
||||
classification_result = classify_bolt("", description, main_nom)
|
||||
elif material_type == "GASKET":
|
||||
from ..services.gasket_classifier import classify_gasket
|
||||
classification_result = classify_gasket("", description, main_nom)
|
||||
elif material_type == "SUPPORT":
|
||||
from ..services.support_classifier import classify_support
|
||||
classification_result = classify_support("", description, main_nom)
|
||||
elif material_type == "SPECIAL":
|
||||
classification_result = {
|
||||
"category": "SPECIAL",
|
||||
"overall_confidence": integrated_result.get('confidence', 0.8),
|
||||
"reason": integrated_result.get('reason', 'SPECIAL 키워드 발견')
|
||||
}
|
||||
else:
|
||||
classification_result = {
|
||||
"category": "UNCLASSIFIED",
|
||||
"overall_confidence": 0.1,
|
||||
"reason": "분류되지 않은 자재"
|
||||
}
|
||||
|
||||
# 4. 결과 적용
|
||||
final_category = classification_result.get('category', 'UNCLASSIFIED')
|
||||
if final_category == 'EXCLUDE':
|
||||
return None # 제외 대상은 None 반환
|
||||
|
||||
material_data.update({
|
||||
'line_number': line_number,
|
||||
'row_number': row_number,
|
||||
'classified_category': final_category,
|
||||
'classification_confidence': classification_result.get('overall_confidence', 0.0),
|
||||
'classification_details': classification_result
|
||||
})
|
||||
|
||||
return material_data
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"자재 분류 실패 (line {line_number}): {e}")
|
||||
# 분류 실패 시 기본값 설정
|
||||
material_data.update({
|
||||
'line_number': line_number,
|
||||
'row_number': row_number,
|
||||
'classified_category': 'UNCLASSIFIED',
|
||||
'classification_confidence': 0.0,
|
||||
'classification_details': {'error': str(e)}
|
||||
})
|
||||
|
||||
return material_data
|
||||
|
||||
def _should_exclude_material(self, description: str) -> bool:
|
||||
"""제외 대상 자재 확인"""
|
||||
exclude_keywords = [
|
||||
'WELD GAP', '용접갭', '용접 갭',
|
||||
'WELDING GAP', 'WELD ALLOWANCE',
|
||||
'CUTTING ALLOWANCE', '절단여유',
|
||||
'SPARE', '예비품', 'RESERVE'
|
||||
]
|
||||
|
||||
desc_upper = description.upper()
|
||||
return any(keyword.upper() in desc_upper for keyword in exclude_keywords)
|
||||
|
||||
Reference in New Issue
Block a user