""" Service de configuration pour le domaine métier. Fournit l'accès à la configuration de l'application (valeurs spéciales, signification des scores, etc.) sans dépendance directe à la base de données. """ from typing import Dict, Any, Optional, List from dataclasses import dataclass @dataclass class SpecialValueConfig: """Configuration d'une valeur spéciale.""" label: str description: str color: str counts: bool # Compte dans le total value: Optional[float] # Valeur numérique (None = dispensé) def to_dict(self) -> dict: return { "label": self.label, "description": self.description, "color": self.color, "counts": self.counts, "value": self.value } @dataclass class ScoreMeaning: """Signification d'un score de compétence (0-3).""" label: str color: str description: str = "" def to_dict(self) -> dict: return { "label": self.label, "color": self.color, "description": self.description } @dataclass class GradingTypeConfig: """Configuration d'un type de notation.""" label: str description: str input_type: str max_value: Optional[int] = None def to_dict(self) -> Dict[str, Any]: result: Dict[str, Any] = { "label": self.label, "description": self.description, "input_type": self.input_type } if self.max_value is not None: result["max_value"] = self.max_value return result class ConfigService: """ Service de configuration pour le domaine métier. Fournit des valeurs par défaut et peut être étendu pour charger la configuration depuis une source externe (DB, fichier, etc.) """ # Configuration par défaut des valeurs spéciales DEFAULT_SPECIAL_VALUES: Dict[str, SpecialValueConfig] = { ".": SpecialValueConfig( label="Pas de réponse", description="Aucune réponse fournie", color="#6b7280", counts=True, value=0.0 ), "d": SpecialValueConfig( label="Dispensé", description="Élève dispensé de cet exercice", color="#c0bfbc", counts=False, value=None ), "a": SpecialValueConfig( label="Absent", description="Élève absent lors de l'évaluation", color="#f87171", counts=True, value=0.0 ) } @staticmethod def _get_default_score_meanings() -> Dict[int, ScoreMeaning]: """Retourne les significations par défaut des scores.""" return { 0: ScoreMeaning( label="Non acquis", color="#ef4444", description="Compétence non maîtrisée" ), 1: ScoreMeaning( label="En cours d'acquisition", color="#f6d32d", description="Compétence en cours d'apprentissage" ), 2: ScoreMeaning( label="Acquis", color="#22c55e", description="Compétence maîtrisée" ), 3: ScoreMeaning( label="Expert", color="#059669", description="Compétence parfaitement maîtrisée" ) } # Types de notation DEFAULT_GRADING_TYPES: Dict[str, GradingTypeConfig] = { "notes": GradingTypeConfig( label="Notes numériques", description="Valeurs décimales (ex: 15.5/20)", input_type="number" ), "score": GradingTypeConfig( label="Échelle de compétences (0-3)", description="Échelle fixe : 0=Non acquis, 1=En cours, 2=Acquis, 3=Expert", input_type="select", max_value=3 ) } def __init__( self, special_values: Optional[Dict[str, SpecialValueConfig]] = None, score_meanings: Optional[Dict[int, ScoreMeaning]] = None, grading_types: Optional[Dict[str, GradingTypeConfig]] = None ): """ Initialise le service avec une configuration optionnelle. Args: special_values: Valeurs spéciales personnalisées score_meanings: Significations des scores personnalisées grading_types: Types de notation personnalisés """ self._special_values = special_values or self.DEFAULT_SPECIAL_VALUES.copy() self._score_meanings = score_meanings or self._get_default_score_meanings() self._grading_types = grading_types or self.DEFAULT_GRADING_TYPES.copy() def get_special_values(self) -> Dict[str, SpecialValueConfig]: """Retourne les valeurs spéciales configurées.""" return self._special_values def get_special_values_dict(self) -> Dict[str, Dict[str, Any]]: """Retourne les valeurs spéciales en format dict.""" return { key: config.to_dict() for key, config in self._special_values.items() } def get_score_meanings(self) -> Dict[int, ScoreMeaning]: """Retourne les significations des scores.""" return self._score_meanings def get_score_meanings_dict(self) -> Dict[str, Dict[str, str]]: """Retourne les significations en format dict (clés string pour JSON).""" return { str(score): meaning.to_dict() for score, meaning in self._score_meanings.items() } def get_grading_types(self) -> Dict[str, GradingTypeConfig]: """Retourne les types de notation.""" return self._grading_types def get_grading_types_dict(self) -> Dict[str, Dict[str, Any]]: """Retourne les types en format dict.""" return { key: config.to_dict() for key, config in self._grading_types.items() } def is_special_value(self, value: str) -> bool: """Vérifie si une valeur est spéciale.""" return value in self._special_values def get_special_value_numeric(self, value: str) -> Optional[float]: """ Retourne la valeur numérique d'une valeur spéciale. Args: value: Valeur spéciale (., d, a) Returns: Valeur numérique ou None si dispensé ou non trouvée """ config = self._special_values.get(value) if config: return config.value return None def get_score_color(self, score: int) -> str: """ Retourne la couleur associée à un score de compétence. Args: score: Score (0-3) Returns: Code couleur hexadécimal """ meaning = self._score_meanings.get(score) return meaning.color if meaning else "#374151" def get_score_label(self, score: int) -> str: """ Retourne le label d'un score de compétence. Args: score: Score (0-3) Returns: Label du score """ meaning = self._score_meanings.get(score) return meaning.label if meaning else str(score) @classmethod def from_database_config(cls, db_config: Dict[str, Any]) -> "ConfigService": """ Crée une instance à partir d'une configuration de base de données. Args: db_config: Configuration extraite de la DB Returns: Instance de ConfigService """ special_values = {} score_meanings = {} # Parser les valeurs spéciales for key, config in db_config.get("special_values", {}).items(): special_values[key] = SpecialValueConfig( label=config.get("label", key), description=config.get("description", ""), color=config.get("color", "#6b7280"), counts=config.get("counts", True), value=config.get("value") ) # Parser les significations des scores for score_str, meaning in db_config.get("score_meanings", {}).items(): try: score = int(score_str) score_meanings[score] = ScoreMeaning( label=meaning.get("label", str(score)), color=meaning.get("color", "#374151"), description=meaning.get("description", "") ) except (ValueError, TypeError): continue return cls( special_values=special_values if special_values else None, score_meanings=score_meanings if score_meanings else None )