Files
TK-BOM-Project/backend/app/schemas/response_models.py
Hyungi Ahn 4f8e395f87
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
feat: SWG 가스켓 전체 구성 정보 표시 개선
- H/F/I/O SS304/GRAPHITE/CS/CS 패턴에서 4개 구성요소 모두 표시
- 기존 SS304 + GRAPHITE → SS304/GRAPHITE/CS/CS로 완전한 구성 표시
- 외부링/필러/내부링/추가구성 모든 정보 포함
- 구매수량 계산 모달에서 정확한 재질 정보 확인 가능
2025-08-30 14:23:01 +09:00

355 lines
13 KiB
Python

"""
API 응답 모델 정의
타입 안정성 및 API 문서화를 위한 Pydantic 모델들
"""
from pydantic import BaseModel, Field, ConfigDict
from typing import List, Optional, Dict, Any, Union
from datetime import datetime
from enum import Enum
# ================================
# 기본 응답 모델
# ================================
class BaseResponse(BaseModel):
"""기본 응답 모델"""
success: bool = Field(description="요청 성공 여부")
message: Optional[str] = Field(None, description="응답 메시지")
timestamp: datetime = Field(default_factory=datetime.now, description="응답 시간")
class ErrorResponse(BaseResponse):
"""에러 응답 모델"""
success: bool = Field(False, description="요청 성공 여부")
error: Dict[str, Any] = Field(description="에러 정보")
model_config = ConfigDict(
json_schema_extra={
"example": {
"success": False,
"message": "요청 처리 중 오류가 발생했습니다",
"error": {
"code": "VALIDATION_ERROR",
"details": "입력 데이터가 올바르지 않습니다"
},
"timestamp": "2025-01-01T12:00:00"
}
}
)
class SuccessResponse(BaseResponse):
"""성공 응답 모델"""
success: bool = Field(True, description="요청 성공 여부")
data: Optional[Any] = Field(None, description="응답 데이터")
# ================================
# 열거형 정의
# ================================
class FileStatus(str, Enum):
"""파일 상태"""
ACTIVE = "active"
INACTIVE = "inactive"
PROCESSING = "processing"
ERROR = "error"
class MaterialCategory(str, Enum):
"""자재 카테고리"""
PIPE = "PIPE"
FITTING = "FITTING"
VALVE = "VALVE"
FLANGE = "FLANGE"
BOLT = "BOLT"
GASKET = "GASKET"
INSTRUMENT = "INSTRUMENT"
EXCLUDE = "EXCLUDE"
class JobStatus(str, Enum):
"""작업 상태"""
ACTIVE = "active"
COMPLETED = "completed"
ON_HOLD = "on_hold"
CANCELLED = "cancelled"
# ================================
# 파일 관련 모델
# ================================
class FileInfo(BaseModel):
"""파일 정보 모델"""
id: int = Field(description="파일 ID")
filename: str = Field(description="파일명")
original_filename: str = Field(description="원본 파일명")
job_no: Optional[str] = Field(None, description="작업 번호")
bom_name: Optional[str] = Field(None, description="BOM 이름")
revision: str = Field(default="Rev.0", description="리비전")
parsed_count: int = Field(default=0, description="파싱된 자재 수")
bom_type: str = Field(default="unknown", description="BOM 타입")
status: FileStatus = Field(description="파일 상태")
file_size: Optional[int] = Field(None, description="파일 크기 (bytes)")
upload_date: datetime = Field(description="업로드 일시")
description: Optional[str] = Field(None, description="파일 설명")
model_config = ConfigDict(
json_schema_extra={
"example": {
"id": 1,
"filename": "BOM_Rev1.xlsx",
"original_filename": "BOM_Rev1.xlsx",
"job_no": "TK-2025-001",
"bom_name": "메인 BOM",
"revision": "Rev.1",
"parsed_count": 150,
"bom_type": "excel",
"status": "active",
"file_size": 2048576,
"upload_date": "2025-01-01T12:00:00",
"description": "파일: BOM_Rev1.xlsx"
}
}
)
class FileListResponse(BaseResponse):
"""파일 목록 응답 모델"""
success: bool = Field(True, description="요청 성공 여부")
data: List[FileInfo] = Field(description="파일 목록")
total_count: int = Field(description="전체 파일 수")
cache_hit: bool = Field(default=False, description="캐시 히트 여부")
class FileDeleteResponse(BaseResponse):
"""파일 삭제 응답 모델"""
success: bool = Field(True, description="삭제 성공 여부")
message: str = Field(description="삭제 결과 메시지")
deleted_file_id: int = Field(description="삭제된 파일 ID")
# ================================
# 자재 관련 모델
# ================================
class MaterialInfo(BaseModel):
"""자재 정보 모델"""
id: int = Field(description="자재 ID")
file_id: int = Field(description="파일 ID")
line_number: Optional[int] = Field(None, description="엑셀 행 번호")
original_description: str = Field(description="원본 품명")
classified_category: Optional[MaterialCategory] = Field(None, description="분류된 카테고리")
classified_subcategory: Optional[str] = Field(None, description="세부 분류")
material_grade: Optional[str] = Field(None, description="재질 등급")
schedule: Optional[str] = Field(None, description="스케줄")
size_spec: Optional[str] = Field(None, description="사이즈 규격")
quantity: float = Field(description="수량")
unit: str = Field(description="단위")
classification_confidence: Optional[float] = Field(None, description="분류 신뢰도")
is_verified: bool = Field(default=False, description="검증 여부")
model_config = ConfigDict(
json_schema_extra={
"example": {
"id": 1,
"file_id": 1,
"line_number": 5,
"original_description": "PIPE, SEAMLESS, A333-6, 6\", SCH40",
"classified_category": "PIPE",
"classified_subcategory": "SEAMLESS",
"material_grade": "A333-6",
"schedule": "SCH40",
"size_spec": "6\"",
"quantity": 12.5,
"unit": "EA",
"classification_confidence": 0.95,
"is_verified": False
}
}
)
class MaterialListResponse(BaseResponse):
"""자재 목록 응답 모델"""
success: bool = Field(True, description="요청 성공 여부")
data: List[MaterialInfo] = Field(description="자재 목록")
total_count: int = Field(description="전체 자재 수")
file_info: Optional[FileInfo] = Field(None, description="파일 정보")
cache_hit: bool = Field(default=False, description="캐시 히트 여부")
# ================================
# 작업 관련 모델
# ================================
class JobInfo(BaseModel):
"""작업 정보 모델"""
job_no: str = Field(description="작업 번호")
job_name: str = Field(description="작업명")
client_name: Optional[str] = Field(None, description="고객사명")
end_user: Optional[str] = Field(None, description="최종 사용자")
epc_company: Optional[str] = Field(None, description="EPC 회사")
status: JobStatus = Field(description="작업 상태")
created_at: datetime = Field(description="생성 일시")
file_count: int = Field(default=0, description="파일 수")
material_count: int = Field(default=0, description="자재 수")
model_config = ConfigDict(
json_schema_extra={
"example": {
"job_no": "TK-2025-001",
"job_name": "석유화학 플랜트 배관 프로젝트",
"client_name": "한국석유화학",
"end_user": "울산공장",
"epc_company": "현대엔지니어링",
"status": "active",
"created_at": "2025-01-01T09:00:00",
"file_count": 3,
"material_count": 450
}
}
)
class JobListResponse(BaseResponse):
"""작업 목록 응답 모델"""
success: bool = Field(True, description="요청 성공 여부")
data: List[JobInfo] = Field(description="작업 목록")
total_count: int = Field(description="전체 작업 수")
cache_hit: bool = Field(default=False, description="캐시 히트 여부")
# ================================
# 분류 관련 모델
# ================================
class ClassificationResult(BaseModel):
"""분류 결과 모델"""
category: MaterialCategory = Field(description="분류된 카테고리")
subcategory: Optional[str] = Field(None, description="세부 분류")
confidence: float = Field(description="분류 신뢰도 (0.0-1.0)")
material_grade: Optional[str] = Field(None, description="재질 등급")
size_spec: Optional[str] = Field(None, description="사이즈 규격")
schedule: Optional[str] = Field(None, description="스케줄")
details: Optional[Dict[str, Any]] = Field(None, description="분류 상세 정보")
model_config = ConfigDict(
json_schema_extra={
"example": {
"category": "PIPE",
"subcategory": "SEAMLESS",
"confidence": 0.95,
"material_grade": "A333-6",
"size_spec": "6\"",
"schedule": "SCH40",
"details": {
"matched_keywords": ["PIPE", "SEAMLESS", "A333-6"],
"size_detected": True,
"material_detected": True
}
}
}
)
class ClassificationResponse(BaseResponse):
"""분류 응답 모델"""
success: bool = Field(True, description="분류 성공 여부")
data: ClassificationResult = Field(description="분류 결과")
processing_time: float = Field(description="처리 시간 (초)")
cache_hit: bool = Field(default=False, description="캐시 히트 여부")
# ================================
# 통계 관련 모델
# ================================
class MaterialStatistics(BaseModel):
"""자재 통계 모델"""
category: MaterialCategory = Field(description="자재 카테고리")
count: int = Field(description="개수")
percentage: float = Field(description="비율 (%)")
total_quantity: float = Field(description="총 수량")
unique_items: int = Field(description="고유 항목 수")
class ProjectStatistics(BaseModel):
"""프로젝트 통계 모델"""
job_no: str = Field(description="작업 번호")
total_materials: int = Field(description="총 자재 수")
total_files: int = Field(description="총 파일 수")
category_breakdown: List[MaterialStatistics] = Field(description="카테고리별 분석")
classification_accuracy: float = Field(description="분류 정확도")
verified_percentage: float = Field(description="검증 완료율")
model_config = ConfigDict(
json_schema_extra={
"example": {
"job_no": "TK-2025-001",
"total_materials": 450,
"total_files": 3,
"category_breakdown": [
{
"category": "PIPE",
"count": 180,
"percentage": 40.0,
"total_quantity": 1250.5,
"unique_items": 45
}
],
"classification_accuracy": 0.92,
"verified_percentage": 0.75
}
}
)
class StatisticsResponse(BaseResponse):
"""통계 응답 모델"""
success: bool = Field(True, description="요청 성공 여부")
data: ProjectStatistics = Field(description="통계 데이터")
cache_hit: bool = Field(default=False, description="캐시 히트 여부")
# ================================
# 시스템 관련 모델
# ================================
class CacheInfo(BaseModel):
"""캐시 정보 모델"""
status: str = Field(description="캐시 상태")
used_memory: str = Field(description="사용 메모리")
connected_clients: int = Field(description="연결된 클라이언트 수")
hit_rate: float = Field(description="캐시 히트율 (%)")
total_commands: int = Field(description="총 명령 수")
class SystemHealthResponse(BaseResponse):
"""시스템 상태 응답 모델"""
success: bool = Field(True, description="요청 성공 여부")
data: Dict[str, Any] = Field(description="시스템 상태 정보")
cache_info: Optional[CacheInfo] = Field(None, description="캐시 정보")
database_status: str = Field(description="데이터베이스 상태")
api_version: str = Field(description="API 버전")
# ================================
# 유니온 타입 (여러 응답 타입)
# ================================
# API 응답으로 사용할 수 있는 모든 타입
APIResponse = Union[
SuccessResponse,
ErrorResponse,
FileListResponse,
FileDeleteResponse,
MaterialListResponse,
JobListResponse,
ClassificationResponse,
StatisticsResponse,
SystemHealthResponse
]