Source code for vulyk.blueprints.gamification.listeners

# coding=utf-8
from datetime import datetime
from decimal import Decimal
from typing import Iterator, Dict, List

from bson import ObjectId

from vulyk.models.stats import WorkSession
from vulyk.models.tasks import AbstractAnswer, Batch
from vulyk.signals import on_batch_done, on_task_done
from .core.events import Event
from .core.queries import MongoRuleExecutor
from .core.rules import Rule
from .core.state import UserState
from .models.events import EventModel
from .models.rules import RuleModel, ProjectAndFreeRules
from .models.state import UserStateModel
from .models.task_types import (
    AbstractGamifiedTaskType, COINS_PER_TASK_KEY, POINTS_PER_TASK_KEY)

__all__ = [
    'track_events',
    'get_actual_rules'
]


[docs]@on_task_done.connect def track_events(sender: object, answer: AbstractAnswer) -> None: """ The most important gear of the gamification module. :param sender: Sender :type sender: object :param answer: Current finished task's answer instance :type answer: AbstractAnswer :rtype: None """ from vulyk.app import TASKS_TYPES from vulyk.blueprints.gamification import gamification user = answer.created_by batch = answer.task.batch if not batch or batch.task_type not in TASKS_TYPES: return if isinstance(TASKS_TYPES[batch.task_type], AbstractGamifiedTaskType): dt = datetime.utcnow() # TODO: TZ Aware? # I. Get current/new state state = UserStateModel.get_or_create_by_user(user) # II. gather earned goods badges = list(filter( lambda rule: MongoRuleExecutor.achieved( user_id=user.id, rule=rule, collection=WorkSession.objects), get_actual_rules( state=state, task_type_name=batch.task_type, now=dt))) # type: Iterator[Rule] points = Decimal(batch.batch_meta[POINTS_PER_TASK_KEY]) for b in badges: if b.bonus: points += b.bonus coins = Decimal(batch.batch_meta[COINS_PER_TASK_KEY]) current_level = gamification.get_level(state.points) updated_level = gamification.get_level(state.points + points) # III. Alter the state and create event UserStateModel.update_state( diff=UserState( user=user, level=updated_level, points=points, actual_coins=Decimal(0), potential_coins=coins, achievements=badges, last_changed=dt)) EventModel.from_event( Event.build( timestamp=dt, user=user, answer=answer, points_given=points, coins=coins, achievements=badges, acceptor_fund=None, level_given=None if current_level == updated_level else updated_level, viewed=False) ).save()
[docs]def get_actual_rules( state: UserState, task_type_name: str, now: datetime ) -> Iterator[Rule]: """ Returns a list of eligible rules. :param state: Current state :type state: UserState :param task_type_name: The task's project :type task_type_name: str :param now: Timestamp to check for weekends :type now: datetime :return: Iterator of Rule instances :rtype: Iterator[Rule] """ return RuleModel.get_actual_rules( skip_ids=list(state.achievements.keys()), rule_filter=ProjectAndFreeRules(task_type_name), is_weekend=now.weekday() in [5, 6])
@on_batch_done.connect def materialize_coins(sender: Batch) -> None: """ Convert potential coins to active ones for every member participated upon the batch in some gamified task type has been closed. :param sender: Batch that was closed :type sender: Batch """ from vulyk.app import TASKS_TYPES if sender.task_type not in TASKS_TYPES: return task_type = TASKS_TYPES[sender.task_type] if not isinstance(task_type, AbstractGamifiedTaskType): return coins = sender.batch_meta[COINS_PER_TASK_KEY] # type: float # potentially expensive on memory task_ids = task_type.task_model.ids_in_batch(sender) # type: List[str] # potentially expensive on memory/CPU (it isn't an generator or something) group_by_count = task_type.answer_model \ .answers_numbers_by_tasks(task_ids) # type: Dict[ObjectId, int] # forgive me, Father, I have sinned so bad... for (uid, freq) in group_by_count.items(): UserStateModel.transfer_coins_to_actual( uid=uid, amount=Decimal(freq * coins))