Beispiel #1
0
class LanguageEnrollment(db.Model):
    """ Represents a user's enrollment in a language """
    __tablename__ = 'language_enrollments'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    language_id = db.Column(db.Integer,
                            db.ForeignKey('languages.id', ondelete="CASCADE"))
    language = db.relationship("Language")
    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.id', ondelete="CASCADE"))
    user = db.relationship("User", backref="languageEnrollments")
    default = db.Column(db.Boolean, default=False)
Beispiel #2
0
class Symbol(db.Model):
    """ Represents a symbol used in a language """
    __tablename__ = 'symbols'

    id = db.Column(db.Integer, primary_key=True)
    text = db.Column(db.UnicodeText())
    concept_id = db.Column(db.Integer,
                           db.ForeignKey('concepts.id', ondelete="CASCADE"))
    language_id = db.Column(db.Integer,
                            db.ForeignKey('languages.id', ondelete="CASCADE"))

    ambiguity_group_id = db.Column(
        db.Integer, db.ForeignKey(AmbiguityGroup.id, ondelete="SET NULL"))
    clarification = db.Column(db.UnicodeText())

    concept = db.relationship("Concept")
    language = db.relationship("Language")
    ambiguity_group = db.relationship(AmbiguityGroup, backref="symbols")

    @hybrid_method
    def ratingFor(self, masteryCache):
        """ Return the rating for the given user """
        return masteryCache[self.id].rating

    @ratingFor.expression
    def ratingFor(self, user):
        """ Return the expression to use when querying for a word's rating """
        return Mastery.rating

    @property
    def needsClarification(self):
        """ Return if this Symbol needs Clarification """
        return self.ambiguity_group_id is not None

    def ambiguousWith(self, other):
        """ Return if this Symbol is ambiguous with the other Symbol """
        return self.text == other.text or self.matchingAmbiguityGroup(other)

    def matchingAmbiguityGroup(self, other):
        """ Return whether this Symbol matches the other Symbol's Ambiguity Group """
        if self.ambiguity_group_id is not None:
            return self.ambiguity_group_id == other.ambiguity_group_id
        else:
            return False

    def __unicode__(self):
        """ Return the string representation of the Word """
        return unicode(self.text)

    def __repr__(self):
        """ Return the String representation of the Symbol """
        return "<Symbol({0}, {1})>".format(self.id, self.language.name)
Beispiel #3
0
class ConceptList(db.Model):
    """ Represents a list of concepts """
    __tablename__ = 'concept_lists'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode())
    isWords = db.Column(db.Boolean)
    concepts = db.relationship("Concept", secondary=concept_list_concepts)

    def getConceptPairs(self, conceptManager):
        """ Return the concept pairs """
        return conceptManager.getConceptPairs(
            [concept.id for concept in self.concepts])

    @hybrid_method
    def averageRatingFor(self, language, conceptFormCache, masteryCache):
        """ Return the rating for the given user """
        ratings = []
        for concept in self.concepts:
            form = conceptFormCache.get(conceptId=concept.id,
                                        languageId=language.id)
            ratings.append(masteryCache[form.id].rating)
        return round(sum(ratings, 0.0) / len(ratings), 1)

    @averageRatingFor.expression
    def averageRatingFor(self, user, language):
        """ Return the rating for the given user """
        return func.avg(
            self.entry_model.ratingFor(user)).over(partition_by=self.id)
Beispiel #4
0
class User(db.Model):
    """ Represents a user """
    __tablename__ = 'users'
    
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.Text(), nullable=False, unique=True, index=True)
    password = db.Column(db.String(128), nullable=False)
    is_admin = db.Column(db.Boolean, default=False)
    givenName = db.Column(db.UnicodeText())
    lastName = db.Column(db.UnicodeText())
    native_language_id = db.Column(db.Integer, db.ForeignKey('languages.id'))
    nativeLanguage = db.relationship("Language")
    learnedSymbols = db.relationship("Symbol", secondary=learned_symbols, lazy='dynamic')
    learnedWords = db.relationship("Word", secondary=learned_words, lazy='dynamic')
        
    def getLearnedFor(self, formInfo, language):
        """ Return the learned forms for the given Form Info """
        helper = self.helperFor(formInfo)
        return helper.formsFor(language)
        
    def tryToLearn(self, form, formInfo, learnedCache):
        """ Learn the form unless it has already been learned """
        helper = self.helperFor(formInfo)
        helper.tryToLearn(form, learnedCache)
        
    def save(self):
        """ Save the Underlying User Data Object """
        db.session.add(self)
        db.session.commit()
        
    def helperFor(self, formInfo):
        """ Return the Learn Helper for the given Form Info """
        return self.learnedSymbolsHelper if formInfo is SymbolInfo else self.learnedWordsHelper
        
    @lazy_property
    def learnedSymbolsHelper(self):
        """ Helper to manage Learned Symbols """
        return LearnHelper(self, SymbolInfo)
        
    @lazy_property
    def learnedWordsHelper(self):
        """ Helper to manage Learned Words """
        return LearnHelper(self, WordInfo)
Beispiel #5
0
class StalenessPeriod(db.Model):
    """ Represents a staleness period """
    __tablename__ = 'staleness_periods'

    id = db.Column(db.Integer, primary_key=True)
    days = db.Column(db.Integer)
    first = db.Column(db.Boolean, default=False)
    next_id = db.Column(db.Integer, db.ForeignKey('staleness_periods.id'))
    next = db.relationship("StalenessPeriod", remote_side=[id])

    @classmethod
    def getFirstStalenessPeriod(cls):
        """ Return the first staleness period """
        return cls.query.filter_by(first=True).first()
Beispiel #6
0
class AmbiguityGroup(db.Model):
    """ Represents a group of words or symbols that are ambiguous with each other """
    __tablename__ = 'ambiguity_groups'

    id = db.Column(db.Integer, primary_key=True)
    language_id = db.Column(db.Integer,
                            db.ForeignKey(Language.id),
                            nullable=False)

    language = db.relationship(Language, backref="ambiguity_groups")

    def __repr__(self):
        """ Return the String Representation of the Ambiguity Group """
        return "<AmbiguityGroup(id={})>".format(self.id)
Beispiel #7
0
class Word(db.Model):
    """ Represents a word from a particular language """
    __tablename__ = 'words'
    
    id = db.Column(db.Integer, primary_key=True)
    text = db.Column(db.UnicodeText())
    concept_id = db.Column(db.Integer, db.ForeignKey('concepts.id', ondelete="CASCADE"))
    concept = db.relationship("Concept")
    language_id = db.Column(db.Integer, db.ForeignKey('languages.id', ondelete="CASCADE"))
    language = db.relationship("Language")
    
    @hybrid_method
    def ratingFor(self, masteryCache):
        """ Return the rating for the given user """
        return masteryCache[self.id].rating
        
    @ratingFor.expression
    def ratingFor(self, user):
        """ Return the expression to use when querying for a word's rating """
        return Mastery.rating
        
    @property
    def needsClarification(self):
        """ Return if this Symbol needs Clarification """
        return False
        
    def ambiguousWith(self, other):
        """ Return if this Symbol is ambiguous with the other Symbol """
        return self.text == other.text
    
    def __unicode__(self):
        """ Return the string representation of the Word """
        return unicode(self.text)
    
    def __repr__(self):
        """ Return the string representation of the Word """
        return repr(self.text)
Beispiel #8
0
class Mastery(db.Model):
    """ Represents the mastery of some skill """
    __tablename__ = 'masteries'
    MAX_RATING = 5
    CORRECT_CHANGE = 1
    WRONG_CHANGE = -1

    id = db.Column(db.Integer, primary_key=True)

    user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
    user = db.relationship("User")

    word_id = db.Column(db.Integer,
                        db.ForeignKey('words.id', ondelete="CASCADE"))
    word = db.relationship("Word")
    symbol_id = db.Column(db.Integer,
                          db.ForeignKey('symbols.id', ondelete="CASCADE"))
    symbol = db.relationship("Symbol")

    answerRating = db.Column(db.Integer,
                             server_default=text('0'),
                             nullable=False)
    lastCorrectAnswer = db.Column(db.DateTime)

    staleness_period_id = db.Column(db.Integer,
                                    db.ForeignKey('staleness_periods.id'))
    stalenessPeriod = db.relationship("StalenessPeriod", lazy='subquery')

    def __init__(self, *args, **kwargs):
        """ Initialize the mastery """
        if 'user' in kwargs and hasattr(kwargs['user'], 'user'):
            kwargs['user'] = kwargs['user'].user
        if 'stalenessPeriod' not in kwargs:
            kwargs[
                'stalenessPeriod'] = StalenessPeriod.getFirstStalenessPeriod()
        db.Model.__init__(self, *args, **kwargs)

    def addAnswer(self, correct):
        """ Add an answer to this mastery """
        self.updateStalenessPeriod(correct)
        self.updateAnswerDate(correct)
        self.updateRating(correct)

        db.session.add(self)
        db.session.commit()

    def updateRating(self, correct):
        """ Update the answer rating """
        ratingChange = self.CORRECT_CHANGE if correct else self.WRONG_CHANGE

        newRating = self.answerRating + ratingChange
        newRating = min(newRating, self.MAX_RATING)
        newRating = max(newRating, 0)
        self.answerRating = newRating

    def updateAnswerDate(self, correct):
        """ Update the answer date """
        if correct:
            self.lastCorrectAnswer = datetime.now()

    def updateStalenessPeriod(self, correct):
        """ Update the staleness period based on whether the answer is correct """
        if correct and self.answerRating == self.MAX_RATING and self.isStale:
            self.moveToNextStalenessPeriod()
        elif not correct:
            self.revertToFirstStalenessPeriod()

    def moveToNextStalenessPeriod(self):
        """ Move the mastery to the next staleness period """
        if self.stalenessPeriod.next:
            self.stalenessPeriod = self.stalenessPeriod.next

    def revertToFirstStalenessPeriod(self):
        """ Revert the staleness period to the first staleness period """
        self.stalenessPeriod = StalenessPeriod.getFirstStalenessPeriod()

    @property
    def form(self):
        """ Return the Concept Form associated with the Mastery """
        return self.word if self.word_id is not None else self.symbol

    @hybrid_property
    def rating(self):
        """ Return the rating of the mastery """
        return max(0, self.answerRating - self.stalenessRating)

    @rating.expression
    def rating(self):
        """ Return the Queryable rating of the mastery """
        return func.greatest(0, self.answerRating - self.stalenessRating)

    @hybrid_property
    def stalenessRating(self):
        """ Return the staleness rating of the mastery """
        mostRecentCorrectAnswer = self.lastCorrectAnswer
        if mostRecentCorrectAnswer is None:
            return 0
        else:
            return int((datetime.now() - mostRecentCorrectAnswer).days /
                       self.stalenessPeriod.days)

    @stalenessRating.expression
    def stalenessRating(self):
        """ Return the Queryable staleness rating of the mastery """
        return func.coalesce(
            cast(
                func.floor(
                    func.extract('epoch',
                                 func.now() - self.lastCorrectAnswer) / 86400),
                db.Integer) / StalenessPeriod.days, 0)

    @hybrid_property
    def isStale(self):
        """ Return if the mastery has outlived the staleness period """
        return self.stalenessRating > 0