Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- H/F/I/O SS304/GRAPHITE/CS/CS 패턴에서 4개 구성요소 모두 표시 - 기존 SS304 + GRAPHITE → SS304/GRAPHITE/CS/CS로 완전한 구성 표시 - 외부링/필러/내부링/추가구성 모든 정보 포함 - 구매수량 계산 모달에서 정확한 재질 정보 확인 가능
355 lines
13 KiB
Python
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
|
|
]
|