리비전 페이지 제거 및 트랜잭션 오류 임시 수정
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

- frontend/src/pages/revision/ 폴더 완전 삭제
- EnhancedRevisionPage.css 제거
- support_details 저장 시 트랜잭션 오류로 인해 임시로 상세 정보 저장 비활성화
- 리비전 기능 재설계 예정
This commit is contained in:
Hyungi Ahn
2025-10-21 12:11:57 +09:00
parent 8f42a1054e
commit 1dc735f362
29 changed files with 1728 additions and 6987 deletions

View File

@@ -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)