Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
97 changes: 97 additions & 0 deletions openproficiency/ProficiencyScore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""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.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."""
# If numeric, directly store it.
if isinstance(value, (int, float)):
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)

# If enum, store as numeric value.
elif isinstance(value, ProficiencyScoreName):
self._score = value.value

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

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

# Methods
def to_dict(self) -> dict[str, float]:
"""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())

# Static Methods
@staticmethod
def get_score_name(score: float) -> ProficiencyScoreName:
"""Internal method to determine proficiency name from numeric score."""
if score == 1.0:
return ProficiencyScoreName.PROFICIENT_WITH_EVIDENCE

elif score >= 0.8:
return ProficiencyScoreName.PROFICIENT

elif score >= 0.5:
return ProficiencyScoreName.FAMILIAR

elif score >= 0.1:
return ProficiencyScoreName.AWARE

elif score >= 0.0:
return ProficiencyScoreName.UNAWARE

else:
raise ValueError(f"Invalid score value: {score}")

# 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())
80 changes: 80 additions & 0 deletions openproficiency/TranscriptEntry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""TranscriptEntry module for OpenProficiency library."""

import json
from datetime import datetime
from typing import Optional, Union
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[str, Union[str, int, float]]:
"""Convert TranscriptEntry 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 TranscriptEntry to JSON string."""
return json.dumps(self.to_dict())

# Methods - Static
@staticmethod
def from_dict(data: dict[str, Union[str, int, float]]) -> "TranscriptEntry":
"""Create a TranscriptEntry from a dictionary."""
return TranscriptEntry(
user_id=data["user_id"],
topic_id=data["topic_id"],
score=data["score"],
issuer=data["issuer"],
timestamp=datetime.fromisoformat(data["timestamp"]),
)

@staticmethod
def from_json(json_str: str) -> "TranscriptEntry":
"""Create a TranscriptEntry from a JSON string."""
return TranscriptEntry.from_dict(json.loads(json_str))

# Debugging
def __repr__(self) -> str:
"""String representation of TranscriptEntry."""
return (
f"TranscriptEntry(user_id='{self.user_id}', "
f"topic_id='{self._proficiency_score.topic_id}', "
f"score={self._proficiency_score.score}, "
f"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