Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,53 @@ t_algebra = Topic(
)
```

### Create a Proficiency Score, by name

A **Proficiency Score** represents a person's level of proficiency in a specific topic.

```python
proficiency_score = ProficiencyScore(
topic_id="addition",
score=ProficiencyScoreName.PROFICIENT
)
```

### Create a Proficiency Score, numerically

All score are internally numeric from 0.0 to 1.0, even if set using the score name (above).

```python
proficiency_score = ProficiencyScore(
topic_id="arithmetic",
score=0.8 # Same as 'ProficiencyScoreName.PROFICIENT'
)
```

### Issue a Transcript Entry

A **Transcript Entry** associates a proficiency score to a user and is claimed by an issuer.

```python
from openproficiency import TranscriptEntry

# Create a transcript entry
entry = TranscriptEntry(
user_id="john-doe",
topic_id="arithmetic",
score=0.9,
issuer="university-of-example"
)

# Access the transcript entry information
print(entry.user_id) # john-doe
print(entry.proficiency_score.score) # 0.9
print(entry.issuer) # university-of-example
print(entry.timestamp) # datetime object

# Convert to JSON for storage or transmission
entry_json = entry.to_json()
```

## How to Develop

This project is open to pull requests.
Expand Down
103 changes: 103 additions & 0 deletions openproficiency/ProficiencyScore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""ProficiencyScore module for OpenProficiency library."""

import json
from enum import Enum
from typing import Union


class ProficiencyScoreName(Enum):
"""Enum for proficiency score names."""
UNAWARE = 0.0
AWARE = 0.1
FAMILIAR = 0.5
PROFICIENT = 0.8
PROFICIENT_WITH_EVIDENCE = 1.0


class ProficiencyScore:
"""Class representing a proficiency score for a topic."""

# Initializers
def __init__(
self,
# Required
topic_id: str,
score: Union[float, ProficiencyScoreName]
):
# Required
self.topic_id = topic_id
self._set_score(score)

# Properties - Score
@property
def score(self) -> float:
"""Get the score as a numeric value between 0.0 and 1.0."""
return self._score

@score.setter
def score(self, value: Union[float, ProficiencyScoreName]) -> None:
"""Set the score numerically or using a ProficiencyScoreName enum."""
self._set_score(value)

# Properties - Score
@property
def score_name(self) -> ProficiencyScoreName:
"""Get the proficiency name as an enum value."""
return self._get_name_from_score(self._score)

@score_name.setter
def score_name(self, value: ProficiencyScoreName) -> None:
"""Set the proficiency name using a ProficiencyScoreName enum."""
if not isinstance(value, ProficiencyScoreName):
raise ValueError(
f"Name must be a ProficiencyScoreName enum, got {type(value)}")
self._score = value.value

# Methods
def _set_score(self, value: Union[float, ProficiencyScoreName]) -> None:
"""Internal method to set score from numeric or enum value."""
if isinstance(value, ProficiencyScoreName):
self._score = value.value

elif isinstance(value, (int, float)):
# Validate score is between 0.0 and 1.0
if not (0.0 <= value <= 1.0):
raise ValueError(
f"Score must be between 0.0 and 1.0, got {value}")
self._score = float(value)

else:
raise ValueError(
f"Score must be numeric or ProficiencyScoreName enum. Got type: '{type(value)}'")

def _get_name_from_score(self, score: float) -> ProficiencyScoreName:
"""Internal method to determine proficiency name from numeric score."""
if score <= 0.0:
return ProficiencyScoreName.UNAWARE
elif score <= 0.1:
return ProficiencyScoreName.AWARE
elif score <= 0.5:
return ProficiencyScoreName.FAMILIAR
elif score <= 0.8:
return ProficiencyScoreName.PROFICIENT
elif score <= 1.0:
return ProficiencyScoreName.PROFICIENT_WITH_EVIDENCE
else:
raise ValueError(f"Invalid score value: {score}")

def to_dict(self) -> dict:
"""Convert to a JSON-serializable dictionary."""
return {
"topic_id": self.topic_id,
"score": self._score
}

def to_json(self) -> str:
"""Convert to a JSON string."""
return json.dumps(self.to_dict())

# Debugging

def __repr__(self) -> str:
"""String representation of ProficiencyScore."""
return f"ProficiencyScore(topic_id='{self.topic_id}', score={self._score}, name={self.score_name.name})"
14 changes: 14 additions & 0 deletions openproficiency/Topic.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Topic module for OpenProficiency library."""

import json
from typing import List, Union


Expand Down Expand Up @@ -77,6 +78,19 @@ def add_pretopics(self, pretopics: List[Union[str, "Topic"]]) -> None:
for pretopic in pretopics:
self.add_pretopic(pretopic)

def to_dict(self) -> dict:
"""Convert Topic to JSON-serializable dictionary."""
return {
"id": self.id,
"description": self.description,
"subtopics": self.subtopics,
"pretopics": self.pretopics
}

def to_json(self) -> str:
"""Convert Topic to JSON string."""
return json.dumps(self.to_dict())

# Debugging
def __repr__(self) -> str:
"""String representation of Topic."""
Expand Down
10 changes: 7 additions & 3 deletions openproficiency/TopicList.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def __init__(
owner: str,
name: str,
# Optional
description: str = "",
description: str = ""
):
# Required
self.owner = owner
Expand Down Expand Up @@ -204,7 +204,7 @@ def _add_pretopics_recursive(
topic_list.add_topic(pretopic)
current_child.add_pretopic(pretopic)

def to_json(self) -> str:
def to_dict(self) -> dict:
"""
Export the TopicList to a JSON string.
"""
Expand Down Expand Up @@ -234,4 +234,8 @@ def to_json(self) -> str:
# Store in data
data["topics"][topic_id] = topic_data

return json.dumps(data, indent=2)
return data

def to_json(self) -> str:
"""Convert TopicList to JSON string."""
return json.dumps(self.to_dict())
59 changes: 59 additions & 0 deletions openproficiency/TranscriptEntry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""TranscriptEntry module for OpenProficiency library."""

import json
from datetime import datetime
from typing import Optional
from .ProficiencyScore import ProficiencyScore


class TranscriptEntry:
"""A user's proficiency score, validated by a particular issuer."""

# Initializers
def __init__(
self,
# Required
user_id: str,
topic_id: str,
score: float,
issuer: str,

# Optional
timestamp: Optional[datetime] = None,
):
# Required
self.user_id = user_id
self._proficiency_score = ProficiencyScore(
topic_id=topic_id,
score=score
)
self.issuer = issuer

# Optional
self.timestamp = timestamp or datetime.now()

# Properties
@property
def proficiency_score(self) -> ProficiencyScore:
"""Get the topic ID from the proficiency score."""
return self._proficiency_score

# Methods
def to_dict(self) -> dict:
"""Convert Topic to JSON-serializable dictionary."""
return {
"user_id": self.user_id,
"topic_id": self._proficiency_score.topic_id,
"score": self._proficiency_score.score,
"issuer": self.issuer,
"timestamp": self.timestamp.isoformat()
}

def to_json(self) -> str:
"""Convert Topic to JSON string."""
return json.dumps(self.to_dict())

# Debugging
def __repr__(self) -> str:
"""String representation of TranscriptEntry."""
return f"TranscriptEntry(user_id='{self.user_id}', topic_id='{self._proficiency_score.topic_id}', score={self._proficiency_score.score}, issuer='{self.issuer}')"
4 changes: 3 additions & 1 deletion openproficiency/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""OpenProficiency - Library to manage proficiency scores using topics and topic lists."""

from .Topic import Topic
from .TopicList import TopicList
from .TopicList import TopicList
from .ProficiencyScore import ProficiencyScore, ProficiencyScoreName
from .TranscriptEntry import TranscriptEntry
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "openproficiency"
version = "0.1.0"
version = "0.0.3"
description = "A simple library for managing proficiency topics and topic lists"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name="openproficiency",
version="0.1.0",
version="0.0.3",
author="OpenProficiency Contributors",
description="A library for managing proficiency topics and topic lists",
long_description=long_description,
Expand Down
Loading