# configs/config.yaml
defaults:
  - data: ravdess
  - model: wav2vec
  - _self_

paths:
  data_dir: ${oc.env:PROJECT_ROOT}/data
  checkpoints_dir: ${oc.env:PROJECT_ROOT}/checkpoints

training:
  seed: 42
  batch_size: 32
  num_workers: 4
  max_epochs: 10
  
logger:
  wandb:
    project: "multimodal-emotion"
    entity: null  # your wandb username
    
# configs/data/ravdess.yaml
dataset:
  name: "ravdess"
  urls:
    small: "https://www.kaggleusercontent.com/datasets/uwrfkaggler/ravdess-emotional-speech-audio.zip"
    full: "https://zenodo.org/api/records/1188976/files-archive"
  audio:
    sample_rate: 16000
    duration: 5.0
  transcribe:
    enable: false
    model: "openai/whisper-base"

# src/data/base.py
from abc import ABC, abstractmethod
from typing import Dict, Any
import torch.utils.data as data

class BaseDataset(data.Dataset, ABC):
    def __init__(self, config: Dict[str, Any]):
        self.config = config
        
    @abstractmethod
    def prepare_data(self):
        """Download and prepare dataset if necessary"""
        pass
        
    @abstractmethod
    def __len__(self):
        pass
        
    @abstractmethod
    def __getitem__(self, idx):
        pass

# src/data/ravdess.py
from typing import Dict, Any
import torch
from .base import BaseDataset

class RavdessDataset(BaseDataset):
    def __init__(self, config: Dict[str, Any]):
        super().__init__(config)
        self.data_dir = config.paths.data_dir
        self.prepare_data()
        
    def prepare_data(self):
        """Download and prepare RAVDESS dataset"""
        if not self.data_dir.exists():
            # Download and extract dataset
            # Prepare metadata
            pass
            
    def __len__(self):
        return len(self.metadata)
        
    def __getitem__(self, idx):
        # Load and preprocess audio file
        # Return audio tensor and label
        pass

# src/model/factory.py
from typing import Dict, Any
from .audio import Wav2VecModel
from .text import RobertaModel
from .fusion import FusionModel

class ModelFactory:
    @staticmethod
    def create_model(config: Dict[str, Any]):
        model_name = config.model.name
        
        if model_name == "wav2vec":
            return Wav2VecModel(config)
        elif model_name == "roberta":
            return RobertaModel(config)
        elif model_name == "fusion":
            return FusionModel(config)
        else:
            raise ValueError(f"Unknown model: {model_name}")

# src/model/base.py
import pytorch_lightning as pl
from abc import abstractmethod
from typing import Dict, Any

class BaseModel(pl.LightningModule):
    def __init__(self, config: Dict[str, Any]):
        super().__init__()
        self.config = config
        self.save_hyperparameters()
        
    @abstractmethod
    def forward(self, x):
        pass
        
    @abstractmethod
    def training_step(self, batch, batch_idx):
        pass
        
    @abstractmethod
    def configure_optimizers(self):
        pass
        
    def get_embeddings(self, x):
        """Get intermediate embeddings for fusion"""
        raise NotImplementedError

# src/trainer.py
import pytorch_lightning as pl
from pytorch_lightning.loggers import WandbLogger
from .data import RavdessDataset
from .model import ModelFactory

def train(config):
    pl.seed_everything(config.training.seed)
    
    # Initialize dataset and dataloader
    dataset = RavdessDataset(config)
    dataloader = torch.utils.data.DataLoader(
        dataset,
        batch_size=config.training.batch_size,
        num_workers=config.training.num_workers
    )
    
    # Initialize model
    model = ModelFactory.create_model(config)
    
    # Setup logger
    logger = WandbLogger(**config.logger.wandb)
    
    # Initialize trainer
    trainer = pl.Trainer(
        max_epochs=config.training.max_epochs,
        logger=logger,
        # Add other training configurations
    )
    
    # Train model
    trainer.fit(model, dataloader)

https://news.hada.io/topic?id=16575

단일 책임 원칙(Single Responsibility Principle)

클래스 분리 및 책임 명확화:

# 1. TextProcessor 클래스 분리
class TextProcessor:
    @staticmethod
    def clean_text(text: str) -> str:
        return re.sub(r'\s+', ' ', text).strip()
    
    @staticmethod
    def split_into_chunks(text: str, max_length: int) -> List[str]:
        # Utils.split_text_into_chunks 메서드를 여기로 이동
        pass

# 2. NotionBlockBuilder 클래스 분리
class NotionBlockBuilder:
    @staticmethod
    def create_text_block(content: str, block_type: str = "paragraph", keywords: List[str] = None) -> Dict:
        pass
    
    @staticmethod
    def create_bulleted_list_item(content: str, keywords: List[str] = None) -> Dict:
        pass

인터페이스 정의 및 추상화:

from abc import ABC, abstractmethod

class ContentFetcher(ABC):
    @abstractmethod
    def fetch_items(self, **kwargs):
        pass
    
    @abstractmethod
    def get_content(self, item):
        pass

class ContentProcessor(ABC):
    @abstractmethod
    def process_content(self, content: str) -> Dict:
        pass

설정 관리 개선:

from dataclasses import dataclass

@dataclass
class SummarizerConfig:
    max_chunks_per_chapter: int
    gpt_model: str
    temperature: float
    output_language: str
    enable_chapters: bool
    include_keywords: bool

에러 처리 및 로깅 개선:

class SummarizerException(Exception):
    pass

class ContentProcessingError(SummarizerException):
    pass

def process_with_error_handling(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            logger.error(f"Error in {func.__name__}: {str(e)}")
            raise ContentProcessingError(f"처리 중 오류 발생: {str(e)}")
    return wrapper

테스트 용이성 개선:

class MockContentFetcher(ContentFetcher):
    def __init__(self, mock_data: Dict):
        self.mock_data = mock_data
    
    def fetch_items(self, **kwargs):
        return self.mock_data.get('items', [])
    
    def get_content(self, item):
        return self.mock_data.get('content', {})

유틸리티 함수 모듈화:

# utils/text.py
def clean_text(text: str) -> str:
    pass

# utils/file.py
def save_file(data, filepath: str) -> None:
    pass

# utils/notion.py
def create_notion_block(content: str, block_type: str) -> Dict:
    pass

7. 의존성 주입 패턴 적용:

class Summarizer:
    def __init__(self, 
                 content_fetcher: ContentFetcher,
                 content_processor: ContentProcessor,
                 config: SummarizerConfig):
        self.fetcher = content_fetcher
        self.processor = content_processor
        self.config = config