|
| 1 | +"""ProficiencyScore module for OpenProficiency library.""" |
| 2 | + |
| 3 | +import json |
| 4 | +from enum import Enum |
| 5 | +from typing import Union |
| 6 | + |
| 7 | + |
| 8 | +class ProficiencyScoreName(Enum): |
| 9 | + """Enum for proficiency score names.""" |
| 10 | + |
| 11 | + UNAWARE = 0.0 |
| 12 | + AWARE = 0.1 |
| 13 | + FAMILIAR = 0.5 |
| 14 | + PROFICIENT = 0.8 |
| 15 | + PROFICIENT_WITH_EVIDENCE = 1.0 |
| 16 | + |
| 17 | + |
| 18 | +class ProficiencyScore: |
| 19 | + """Class representing a proficiency score for a topic.""" |
| 20 | + |
| 21 | + # Initializers |
| 22 | + def __init__( |
| 23 | + self, |
| 24 | + # Required |
| 25 | + topic_id: str, |
| 26 | + score: Union[float, ProficiencyScoreName], |
| 27 | + ): |
| 28 | + # Required |
| 29 | + self.topic_id = topic_id |
| 30 | + self.score = score |
| 31 | + |
| 32 | + # Properties - Score |
| 33 | + @property |
| 34 | + def score(self) -> float: |
| 35 | + """Get the score as a numeric value between 0.0 and 1.0.""" |
| 36 | + return self._score |
| 37 | + |
| 38 | + @score.setter |
| 39 | + def score(self, value: Union[float, ProficiencyScoreName]) -> None: |
| 40 | + """Set the score numerically or using a ProficiencyScoreName enum.""" |
| 41 | + # If numeric, directly store it. |
| 42 | + if isinstance(value, (int, float)): |
| 43 | + if not (0.0 <= value <= 1.0): |
| 44 | + raise ValueError(f"Score must be between 0.0 and 1.0, got {value}") |
| 45 | + self._score = float(value) |
| 46 | + |
| 47 | + # If enum, store as numeric value. |
| 48 | + elif isinstance(value, ProficiencyScoreName): |
| 49 | + self._score = value.value |
| 50 | + |
| 51 | + else: |
| 52 | + raise ValueError(f"Score must be numeric or ProficiencyScoreName enum. Got type: '{type(value)}'") |
| 53 | + |
| 54 | + # Properties - Score |
| 55 | + @property |
| 56 | + def score_name(self) -> ProficiencyScoreName: |
| 57 | + """Get the proficiency name as an enum value.""" |
| 58 | + return ProficiencyScore.get_score_name(self._score) |
| 59 | + |
| 60 | + # Methods |
| 61 | + def to_dict(self) -> dict[str, float]: |
| 62 | + """Convert to a JSON-serializable dictionary.""" |
| 63 | + return { |
| 64 | + "topic_id": self.topic_id, |
| 65 | + "score": self._score, |
| 66 | + } |
| 67 | + |
| 68 | + def to_json(self) -> str: |
| 69 | + """Convert to a JSON string.""" |
| 70 | + return json.dumps(self.to_dict()) |
| 71 | + |
| 72 | + # Static Methods |
| 73 | + @staticmethod |
| 74 | + def get_score_name(score: float) -> ProficiencyScoreName: |
| 75 | + """Internal method to determine proficiency name from numeric score.""" |
| 76 | + if score == 1.0: |
| 77 | + return ProficiencyScoreName.PROFICIENT_WITH_EVIDENCE |
| 78 | + |
| 79 | + elif score >= 0.8: |
| 80 | + return ProficiencyScoreName.PROFICIENT |
| 81 | + |
| 82 | + elif score >= 0.5: |
| 83 | + return ProficiencyScoreName.FAMILIAR |
| 84 | + |
| 85 | + elif score >= 0.1: |
| 86 | + return ProficiencyScoreName.AWARE |
| 87 | + |
| 88 | + elif score >= 0.0: |
| 89 | + return ProficiencyScoreName.UNAWARE |
| 90 | + |
| 91 | + else: |
| 92 | + raise ValueError(f"Invalid score value: {score}") |
| 93 | + |
| 94 | + # Debugging |
| 95 | + def __repr__(self) -> str: |
| 96 | + """String representation of ProficiencyScore.""" |
| 97 | + return f"ProficiencyScore(topic_id='{self.topic_id}', score={self._score}, name={self.score_name.name})" |
0 commit comments