635 lines
18 KiB
Python
635 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
데이터 모델 정의 모듈
|
|
데이터베이스 테이블에 대응하는 데이터 모델 클래스들을 정의합니다.
|
|
|
|
각 모델 클래스는 테이블 스키마를 반영하며,
|
|
데이터 유효성 검사 및 직렬화 기능을 제공합니다.
|
|
"""
|
|
|
|
import json
|
|
from dataclasses import dataclass, field, asdict
|
|
from datetime import datetime, date, time
|
|
from typing import Optional, Dict, Any, List
|
|
from enum import Enum
|
|
|
|
|
|
# ============================================================================
|
|
# 열거형 정의
|
|
# ============================================================================
|
|
|
|
class Role(Enum):
|
|
"""사용자 역할"""
|
|
ADMIN = "admin"
|
|
EDITOR = "editor"
|
|
VIEWER = "viewer"
|
|
|
|
|
|
class ShiftType(Enum):
|
|
"""근무 유형"""
|
|
DAY = "주간"
|
|
NIGHT = "야간"
|
|
|
|
|
|
class CleaningType(Enum):
|
|
"""청소 유형"""
|
|
NONE = "없음"
|
|
MEDIUM = "중청소"
|
|
LARGE = "대청소"
|
|
|
|
|
|
# ============================================================================
|
|
# 기본 모델 클래스
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class BaseModel:
|
|
"""
|
|
기본 모델 클래스
|
|
|
|
모든 데이터 모델의 기반 클래스입니다.
|
|
공통 필드 및 유틸리티 메서드를 제공합니다.
|
|
"""
|
|
id: Optional[int] = None
|
|
created_at: Optional[datetime] = None
|
|
updated_at: Optional[datetime] = None
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""
|
|
모델을 딕셔너리로 변환합니다.
|
|
|
|
Returns:
|
|
모델 데이터 딕셔너리
|
|
"""
|
|
data = asdict(self)
|
|
# datetime 객체를 문자열로 변환
|
|
for key, value in data.items():
|
|
if isinstance(value, datetime):
|
|
data[key] = value.isoformat()
|
|
elif isinstance(value, date):
|
|
data[key] = value.isoformat()
|
|
elif isinstance(value, time):
|
|
data[key] = value.isoformat()
|
|
elif isinstance(value, Enum):
|
|
data[key] = value.value
|
|
return data
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any]) -> 'BaseModel':
|
|
"""
|
|
딕셔너리에서 모델을 생성합니다.
|
|
|
|
Args:
|
|
data: 모델 데이터 딕셔너리
|
|
|
|
Returns:
|
|
모델 인스턴스
|
|
"""
|
|
# datetime 문자열을 객체로 변환
|
|
for key in ['created_at', 'updated_at']:
|
|
if key in data and isinstance(data[key], str):
|
|
try:
|
|
data[key] = datetime.fromisoformat(data[key])
|
|
except ValueError:
|
|
data[key] = None
|
|
return cls(**data)
|
|
|
|
|
|
# ============================================================================
|
|
# 사용자 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class User(BaseModel):
|
|
"""
|
|
사용자 모델
|
|
|
|
Attributes:
|
|
username: 사용자 ID (고유)
|
|
password_hash: 비밀번호 해시
|
|
name: 이름
|
|
department: 부서
|
|
role: 역할 (admin, editor, viewer)
|
|
is_active: 활성화 여부
|
|
"""
|
|
username: str = ""
|
|
password_hash: str = ""
|
|
name: str = ""
|
|
department: str = ""
|
|
role: str = "viewer"
|
|
is_active: bool = True
|
|
|
|
def has_permission(self, action: str) -> bool:
|
|
"""
|
|
특정 동작에 대한 권한이 있는지 확인합니다.
|
|
|
|
Args:
|
|
action: 동작 (create, read, update, delete)
|
|
|
|
Returns:
|
|
권한 여부
|
|
"""
|
|
if self.role == Role.ADMIN.value:
|
|
return True
|
|
elif self.role == Role.EDITOR.value:
|
|
return action in ['create', 'read', 'update']
|
|
else: # viewer
|
|
return action == 'read'
|
|
|
|
def is_admin(self) -> bool:
|
|
"""관리자 여부 확인"""
|
|
return self.role == Role.ADMIN.value or self.department == "검수팀"
|
|
|
|
|
|
# ============================================================================
|
|
# 팀 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class Team(BaseModel):
|
|
"""
|
|
팀 모델
|
|
|
|
Attributes:
|
|
name: 팀 이름 (A팀, B팀, C팀, D팀)
|
|
shift_type: 근무 유형 (주간, 야간)
|
|
is_active: 활성화 여부
|
|
"""
|
|
name: str = ""
|
|
shift_type: str = ""
|
|
is_active: bool = True
|
|
|
|
|
|
# ============================================================================
|
|
# 섹션 공통 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class SectionBase(BaseModel):
|
|
"""
|
|
섹션 기본 모델
|
|
|
|
모든 섹션(지시, 고장, 작업, 기타)의 공통 필드를 정의합니다.
|
|
"""
|
|
created_date: Optional[date] = None
|
|
created_team: str = ""
|
|
team_confirmations: str = "{}" # JSON 문자열
|
|
is_completed: bool = False
|
|
completed_at: Optional[datetime] = None
|
|
created_by: Optional[int] = None
|
|
|
|
def get_team_confirmations(self) -> Dict[str, bool]:
|
|
"""팀 확인 상태를 딕셔너리로 반환"""
|
|
try:
|
|
return json.loads(self.team_confirmations)
|
|
except json.JSONDecodeError:
|
|
return {"1팀": False, "2팀": False, "3팀": False, "4팀": False}
|
|
|
|
def set_team_confirmation(self, team: str, confirmed: bool):
|
|
"""특정 팀의 확인 상태를 설정"""
|
|
confirmations = self.get_team_confirmations()
|
|
confirmations[team] = confirmed
|
|
self.team_confirmations = json.dumps(confirmations, ensure_ascii=False)
|
|
|
|
# 모든 팀이 확인했는지 체크
|
|
if all(confirmations.values()):
|
|
self.is_completed = True
|
|
self.completed_at = datetime.now()
|
|
|
|
def all_teams_confirmed(self) -> bool:
|
|
"""모든 팀이 확인했는지 반환"""
|
|
confirmations = self.get_team_confirmations()
|
|
return all(confirmations.values())
|
|
|
|
|
|
# ============================================================================
|
|
# 지시 섹션 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class Instruction(SectionBase):
|
|
"""
|
|
지시 섹션 모델
|
|
|
|
상위부서나 상급자의 지시사항을 기록합니다.
|
|
|
|
Attributes:
|
|
instructor: 지시자
|
|
instruction_content: 지시내용
|
|
instruction_date: 지시일자
|
|
is_continuous: 지속여부
|
|
"""
|
|
instructor: str = ""
|
|
instruction_content: str = ""
|
|
instruction_date: Optional[date] = None
|
|
is_continuous: bool = False
|
|
|
|
|
|
# ============================================================================
|
|
# 고장 섹션 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class Fault(SectionBase):
|
|
"""
|
|
고장 섹션 모델
|
|
|
|
전동차 고장 정보를 기록합니다.
|
|
|
|
Attributes:
|
|
occurrence_date: 발생일자
|
|
column_number: 열번
|
|
train_number: 편성번호
|
|
car_number: 호차
|
|
fault_code: 고장코드
|
|
device_category: 장치분류
|
|
occurrence_station: 발생역
|
|
occurrence_time: 발생시간
|
|
fault_content: 고장내용
|
|
action_content: 조치내용
|
|
action_team: 조치팀
|
|
fault_source: 고장출처
|
|
severity: 심각도 (normal, high, critical)
|
|
"""
|
|
occurrence_date: Optional[date] = None
|
|
column_number: str = ""
|
|
train_number: str = ""
|
|
car_number: str = ""
|
|
fault_code: str = ""
|
|
device_category: str = ""
|
|
occurrence_station: str = ""
|
|
occurrence_time: Optional[time] = None
|
|
fault_content: str = ""
|
|
action_content: str = ""
|
|
action_team: str = ""
|
|
fault_source: str = "" # 고장출처
|
|
severity: str = "normal" # 심각도 (normal, high, critical)
|
|
|
|
|
|
# ============================================================================
|
|
# 작업 섹션 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class Work(SectionBase):
|
|
"""
|
|
작업 섹션 모델
|
|
|
|
전동차 관련 작업일정을 기록합니다.
|
|
|
|
Attributes:
|
|
work_date: 작업일정
|
|
work_entity: 작업주체
|
|
target_train: 대상편성
|
|
target_device: 대상기기
|
|
work_content: 작업내용
|
|
remarks: 특이사항
|
|
"""
|
|
work_date: Optional[date] = None
|
|
work_entity: str = ""
|
|
target_train: str = ""
|
|
target_device: str = ""
|
|
work_content: str = ""
|
|
remarks: str = ""
|
|
|
|
|
|
# ============================================================================
|
|
# 기타 섹션 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class Misc(SectionBase):
|
|
"""
|
|
기타 섹션 모델
|
|
|
|
전동차 관련 작업 외 나머지 사항을 기록합니다.
|
|
|
|
Attributes:
|
|
reporter: 전달자
|
|
report_content: 전달내용
|
|
remarks: 특이사항
|
|
related_document: 관련문서
|
|
"""
|
|
reporter: str = ""
|
|
report_content: str = ""
|
|
remarks: str = ""
|
|
related_document: str = ""
|
|
|
|
|
|
# ============================================================================
|
|
# 일상검수 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class DailyInspection(BaseModel):
|
|
"""
|
|
일상검수 모델
|
|
|
|
일일 점검 대상 편성을 기록합니다.
|
|
|
|
Attributes:
|
|
inspection_date: 검수일자
|
|
shift_type: 근무유형 (주간, 야간)
|
|
slot_number: 슬롯번호 (1~5)
|
|
train_number: 편성번호
|
|
cleaning_type: 청소유형 (없음, 중청소, 대청소)
|
|
has_work: 작업여부
|
|
"""
|
|
inspection_date: Optional[date] = None
|
|
shift_type: str = ""
|
|
slot_number: int = 0
|
|
train_number: str = ""
|
|
cleaning_type: str = "없음"
|
|
has_work: bool = False
|
|
created_by: Optional[int] = None
|
|
|
|
|
|
# ============================================================================
|
|
# Todo 모델
|
|
# ============================================================================
|
|
|
|
class TodoCategory:
|
|
"""할일 카테고리"""
|
|
GENERAL = "일반" # 일반 할일
|
|
ARRIVAL_INSPECTION = "도착검수" # 도착검수
|
|
TASK = "작업" # 작업
|
|
|
|
|
|
@dataclass
|
|
class Todo(BaseModel):
|
|
"""
|
|
할일 모델
|
|
|
|
할일 목록을 기록합니다.
|
|
|
|
Attributes:
|
|
todo_date: 할일 날짜
|
|
category: 카테고리 (일반, 도착검수, 작업)
|
|
target_train: 대상편성
|
|
schedule: 일정
|
|
content: 내용
|
|
is_completed: 완료여부
|
|
completed_at: 완료시간
|
|
"""
|
|
todo_date: Optional[date] = None
|
|
category: str = "일반"
|
|
target_train: str = ""
|
|
schedule: str = ""
|
|
content: str = ""
|
|
is_completed: bool = False
|
|
completed_at: Optional[datetime] = None
|
|
alarm_time: Optional[datetime] = None
|
|
created_by: Optional[int] = None
|
|
|
|
|
|
# ============================================================================
|
|
# 메모 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class Memo(BaseModel):
|
|
"""
|
|
메모 모델
|
|
|
|
메모를 기록합니다.
|
|
|
|
Attributes:
|
|
memo_date: 메모 날짜
|
|
content: 내용
|
|
"""
|
|
memo_date: Optional[date] = None
|
|
content: str = ""
|
|
created_by: Optional[int] = None
|
|
|
|
|
|
# ============================================================================
|
|
# 설정 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class Setting(BaseModel):
|
|
"""
|
|
설정 모델
|
|
|
|
키-값 형태의 설정을 저장합니다.
|
|
|
|
Attributes:
|
|
key: 설정 키
|
|
value: 설정 값
|
|
"""
|
|
key: str = ""
|
|
value: str = ""
|
|
|
|
|
|
# ============================================================================
|
|
# 팀 인원 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class TeamMember(BaseModel):
|
|
"""
|
|
팀 인원 모델
|
|
|
|
각 팀의 구성원 정보를 저장합니다.
|
|
|
|
Attributes:
|
|
team: 팀 (1팀, 2팀, 3팀, 4팀)
|
|
position: 직책 (부팀장, 운용)
|
|
name: 이름
|
|
order: 순서 (당무 순서)
|
|
partner_id: 짝궁 ID (함께 당무 서는 사람)
|
|
is_active: 활성화 여부
|
|
"""
|
|
team: str = ""
|
|
position: str = ""
|
|
name: str = ""
|
|
order: int = 0
|
|
partner_id: Optional[int] = None
|
|
is_active: bool = True
|
|
|
|
|
|
@dataclass
|
|
class DutySchedule(BaseModel):
|
|
"""
|
|
당무 일정 모델
|
|
|
|
일별 당무자 정보를 저장합니다.
|
|
|
|
Attributes:
|
|
duty_date: 당무 날짜
|
|
team: 팀
|
|
shift_type: 근무 유형 (주간, 야간)
|
|
vice_leader_id: 당무 부팀장 ID
|
|
operator_id: 당무 운용 ID
|
|
vice_leader_name: 당무 부팀장 이름 (조회용)
|
|
operator_name: 당무 운용 이름 (조회용)
|
|
"""
|
|
duty_date: Optional[date] = None
|
|
team: str = ""
|
|
shift_type: str = ""
|
|
vice_leader_id: Optional[int] = None
|
|
operator_id: Optional[int] = None
|
|
vice_leader_name: str = ""
|
|
operator_name: str = ""
|
|
|
|
|
|
# ============================================================================
|
|
# 날씨 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class Weather(BaseModel):
|
|
"""
|
|
날씨 모델
|
|
|
|
시간별 날씨 정보를 저장합니다.
|
|
|
|
Attributes:
|
|
datetime: 날씨 데이터 시각
|
|
location_name: 지역명
|
|
location_code: 지역코드
|
|
temp: 기온
|
|
feels_like: 체감온도
|
|
humidity: 습도
|
|
wind_speed: 풍속
|
|
wind_direction: 풍향
|
|
precipitation_prob: 강수확률
|
|
weather_condition: 날씨 상태
|
|
weather_icon: 날씨 아이콘
|
|
"""
|
|
datetime: Optional[datetime] = None
|
|
location_name: str = ""
|
|
location_code: str = ""
|
|
temp: Optional[int] = None
|
|
feels_like: Optional[int] = None
|
|
humidity: Optional[int] = None
|
|
wind_speed: str = ""
|
|
wind_direction: str = ""
|
|
precipitation_prob: Optional[int] = None
|
|
weather_condition: str = ""
|
|
weather_icon: str = ""
|
|
|
|
|
|
# ============================================================================
|
|
# 열차 다이아 시각표 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class TrainSchedule(BaseModel):
|
|
"""
|
|
열차 다이아 시각표 모델
|
|
|
|
열번과 역별 도착/출발 시각을 저장합니다.
|
|
열번과 역명으로 발생 시간을 유추할 때 사용됩니다.
|
|
|
|
Attributes:
|
|
column_number: 열번 (예: "1001", "1002")
|
|
station: 역명 (예: "신평역", "하단역")
|
|
arrival_time: 도착 시간
|
|
departure_time: 출발 시간
|
|
direction: 방향 (up: 상행, down: 하행)
|
|
is_weekday: 평일 여부 (True: 평일, False: 주말/휴일)
|
|
is_active: 활성화 여부
|
|
"""
|
|
column_number: str = ""
|
|
station: str = ""
|
|
arrival_time: Optional[time] = None
|
|
departure_time: Optional[time] = None
|
|
direction: str = "up" # up: 상행, down: 하행
|
|
is_weekday: bool = True
|
|
is_active: bool = True
|
|
|
|
|
|
# ============================================================================
|
|
# 전동차 편성 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class TrainFormation(BaseModel):
|
|
"""
|
|
전동차 편성 모델
|
|
|
|
편성번호별 전동차 정보를 관리합니다.
|
|
|
|
Attributes:
|
|
train_number: 편성번호 (예: 134a, 134b, 1A)
|
|
is_new_train: 신차 여부 (True: 신차, False: 구차)
|
|
manufacturer: 제조사
|
|
introduction_date: 도입일
|
|
depot: 배속지 (신평, 노포)
|
|
alias: 별칭
|
|
introduction_stage: 도입단계
|
|
introduction_count: 도입량
|
|
"""
|
|
train_number: str = ""
|
|
is_new_train: bool = True
|
|
manufacturer: str = ""
|
|
introduction_date: Optional[date] = None
|
|
depot: str = ""
|
|
alias: str = ""
|
|
introduction_stage: str = ""
|
|
introduction_count: int = 0
|
|
|
|
|
|
# ============================================================================
|
|
# 조치 단계 모델
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class ActionStep(BaseModel):
|
|
"""
|
|
조치 단계 모델
|
|
|
|
고장에 대한 조치를 단계별로 기록합니다.
|
|
|
|
Attributes:
|
|
fault_id: 고장 ID (외래키)
|
|
step_number: 단계 번호
|
|
action_content: 조치 내용
|
|
action_team: 조치팀
|
|
"""
|
|
fault_id: int = 0
|
|
step_number: int = 0
|
|
action_content: str = ""
|
|
action_team: str = ""
|
|
|
|
|
|
# ============================================================================
|
|
# 모델 레지스트리
|
|
# ============================================================================
|
|
|
|
# 테이블 이름과 모델 클래스 매핑
|
|
MODEL_REGISTRY: Dict[str, type] = {
|
|
"users": User,
|
|
"teams": Team,
|
|
"instructions": Instruction,
|
|
"faults": Fault,
|
|
"works": Work,
|
|
"miscs": Misc,
|
|
"daily_inspections": DailyInspection,
|
|
"todos": Todo,
|
|
"memos": Memo,
|
|
"settings": Setting,
|
|
"team_members": TeamMember,
|
|
"duty_schedules": DutySchedule,
|
|
"train_schedules": TrainSchedule,
|
|
"weather": Weather,
|
|
"train_formations": TrainFormation,
|
|
"action_steps": ActionStep,
|
|
}
|
|
|
|
|
|
def get_model_class(table_name: str) -> Optional[type]:
|
|
"""
|
|
테이블 이름에 해당하는 모델 클래스를 반환합니다.
|
|
|
|
Args:
|
|
table_name: 테이블 이름
|
|
|
|
Returns:
|
|
모델 클래스
|
|
"""
|
|
return MODEL_REGISTRY.get(table_name)
|
|
|
|
|