Exemple #1
0
class Manager(models.Manager):
    """
    Default Boogie manager.
    """

    # Delegates
    update_dataframe = delegate_to('_queryset')
    load_dataframe = delegate_to('_queryset')
    head = delegate_to('head')
    tail = delegate_to('tail')

    def __getitem__(self, item):
        return self.get_queryset().__getitem__(item)

    def __setitem__(self, item, value):
        return self.get_queryset().__setitem__(item, value)

    def get_queryset(self):
        return QuerySet(self.model, Query(self.model), self._db, self._hints)

    _queryset = property(get_queryset)

    def new(self, *args, **kwargs):
        """
        Create a new model instance, without saving it to the database.
        """
        return self.model(*args, **kwargs)

    def save_dataframe(self, *args, **kwargs):
        return self.get_queryset().save_dataframe(*args, **kwargs)
Exemple #2
0
class Meta:
    """
    Holds meta-information about model.
    """

    # Delegated
    vars = delegate_to('model')
    aux = delegate_to('model')
    params = delegate_to('model')
    compile_diff_fn = delegate_to('compiler')
    compile_aux_fn = delegate_to('compiler')

    # Computed variables
    diff_fn = lazy(lambda self: self.compile_diff_fn())
    aux_fn = lazy(lambda self: self.compile_aux_fn())
    vars_size = lazy(lambda self: sum(v.size for v in self.vars.values()))
    aux_size = lazy(lambda self: sum(v.size for v in self.aux.values()))
    params_size = lazy(lambda self: sum(v.size for v in self.params.values()))
    t0 = 0.0
    tf = 10.0
    steps = 100
    y0 = property(
        lambda self: self.compiler.vectorize_vars(self.model.var_values()))

    @lazy
    def compiler(self):
        m = self.model
        return Compiler(m.vars, m.aux, m.equations, dtype=m.dtype)

    def __init__(self, model):
        self.model = model

    def unvectorize_vars(self, y):
        """
        Convert vector state to a dictionary.
        """
        map = self.compiler.var_map()
        return {name: y[idx] for name, idx in map.items()}

    def read_var(self, name, src):
        """
        Read named var from source array.
        """
        idx = self.compiler.var_index(name)
        return src[idx]

    def read_aux(self, name, src):
        """
        Read named auxiliary term from source array.
        """
        idx = self.compiler.aux_index(name)
        return src[idx]
Exemple #3
0
class ConversationProgress(ProgressBase):
    """
    Tracks activity in conversation.
    """

    conversation = models.OneToOneField(
        "ej_conversations.Conversation",
        related_name="progress",
        on_delete=models.CASCADE,
    )
    conversation_level = models.EnumField(ConversationLevel,
                                          default=CommenterLevel.NONE)
    max_conversation_level = models.EnumField(ConversationLevel,
                                              default=CommenterLevel.NONE)

    # Non de-normalized fields: conversations
    n_votes = delegate_to("conversation")
    n_comments = delegate_to("conversation")
    n_rejected_comments = delegate_to("conversation")
    n_participants = delegate_to("conversation")
    n_favorites = delegate_to("conversation")
    n_tags = delegate_to("conversation")

    # Clusterization
    n_clusters = delegate_to("conversation")
    n_stereotypes = delegate_to("conversation")

    # Gamification
    n_endorsements = delegate_to("conversation")

    # Signals
    level_achievement_signal = lazy(
        lambda _: signals.conversation_level_achieved, shared=True)

    class Meta:
        verbose_name_plural = _("Conversation progress list")

    def __str__(self):
        return __('Progress for "{conversation}"').format(
            conversation=self.conversation)

    def compute_score(self):
        """
        Compute the total number of points for user contribution.

        Conversation score is based on the following rules:
            * Vote: 1 points
            * Accepted comment: 2 points
            * Rejected comment: -3 points
            * Endorsement created: 3 points

        Returns:
            Total score (int)
        """
        return (self.score_bias + self.n_votes + 2 * self.n_comments -
                3 * self.n_rejected_comments + 3 * self.n_endorsements)
Exemple #4
0
class Component(BaseElement):
    """
    Component that delegates the creation of HTML tree to an .html() method.
    """

    json = delegate_to("_tree")
    dump = delegate_to("_tree")
    tag = delegate_to("_tree")
    attrs = delegate_to("_tree")
    children = delegate_to("_tree")
    requires = delegate_to("_tree")
    is_void = delegate_to("_tree")
    is_element = delegate_to("_tree")

    @lazy
    def _tree(self):
        return self.html()

    def html(self, **kwargs):
        raise NotImplementedError("must be implemented in subclasses")

    def copy(self):
        new = copy.copy(self)
        new._tree = self._tree.copy()
        return new
Exemple #5
0
class App:
    """
    Represents a Django application.
    """

    urls = delegate_to('router')
    route = delegate_to('router')

    def __init__(self, name=None, path=None):
        if name is None:
            self.name = sys._getframe(2).f_globals['__name__']
        self.router = Router()
        self.path = path
        set_last_app(self)
Exemple #6
0
class Cluster(TimeStampedModel):
    """
    Represents an opinion group.
    """

    clusterization = models.ForeignKey(
        'Clusterization',
        on_delete=models.CASCADE,
        related_name='clusters',
    )
    name = models.CharField(
        _('Name'),
        max_length=64,
    )
    description = models.TextField(
        _('Description'),
        blank=True,
        help_text=_(
            'How was this cluster conceived?'
        ),
    )
    users = models.ManyToManyField(
        get_user_model(),
        related_name='clusters',
        blank=True,
    )
    stereotypes = models.ManyToManyField(
        'Stereotype',
        related_name='clusters',
    )

    conversation = delegate_to('clusterization')
    objects = ClusterManager()

    def __str__(self):
        msg = _('{name} ("{conversation}" conversation)')
        return msg.format(name=self.name, conversation=str(self.conversation))

    def get_absolute_url(self):
        args = {
            'conversation': self.conversation,
            'cluster': self,
        }
        return reverse('cluster:detail', kwargs=args)

    def mean_stereotype(self):
        """
        Return the mean stereotype for cluster.
        """
        stereotypes = self.stereotypes.all()
        votes = (
            StereotypeVote.objects
                .filter(author__in=Subquery(stereotypes.values('id')))
                .values_list('comment', 'choice')
        )
        df = pd.DataFrame(list(votes), columns=['comment', 'choice'])
        if len(df) == 0:
            return pd.DataFrame([], columns=['choice'])
        else:
            return df.pivot_table('choice', index='comment', aggfunc='mean')
Exemple #7
0
class Profile(models.Model):
    """
    Social information about users.
    """

    user = models.OneToOneField(
        get_config('AUTH_USER_MODEL', 'auth.User'),
        on_delete=models.CASCADE,
        verbose_name=_('user'),
        related_name='profile_ref',
    )

    # Delegates and properties
    username = delegate_to('user', read_only=True)
    name = delegate_to('user', read_only=True)
    first_name = delegate_to('user', read_only=True)
    last_name = delegate_to('user', read_only=True)
    alias = delegate_to('user', read_only=True)
    email = delegate_to('user', read_only=True)

    class Meta:
        abstract = True

    def __str__(self):
        if self.user is None:
            return __('Unbound profile')
        full_name = self.user.get_full_name() or self.user.username
        return __('%(name)s\'s profile') % {'name': full_name}

    def get_absolute_url(self):
        if self.user is not None:
            return self.user.get_absolute_url()
Exemple #8
0
class QueryManager(Manager):
    """
    A manager object constructed from a queryset instance.
    """

    model = delegate_to('queryset')
    name = delegate_to('queryset')
    _db = delegate_to('queryset')
    _hints = delegate_to('queryset')

    def __init__(self, queryset):
        self._queryset = queryset
        self._used_queryset = False

    def __getattr__(self, item):
        return getattr(self._queryset, item)

    def get_queryset(self):
        if self._used_queryset:
            return self._queryset.all()
        else:
            self._used_queryset = True
            return self._queryset
Exemple #9
0
class TestFormulaSIR:
    model = sir
    K = sk.delegate_to("model")
    R0 = sk.delegate_to("model")
    R0_from_K = sk.delegate_to("model")

    def test_R0(self):
        assert self.R0(beta=2, gamma=1) == 2
        assert self.R0.formula(beta=2, gamma=1) == 2
        assert self.R0.formula(2, 1) == 2
        assert self.R0(beta=2, infectious_period=2) == 4
        assert self.R0({"beta": 2, "gamma": 1}) == 2
        assert self.R0({"beta": 2, "gamma": 1}, gamma=2) == 1

    def test_R0_from_K(self):
        assert self.R0_from_K(K=0, gamma=1) == 1.0
        assert self.R0_from_K(K=0, gamma=2) == 1.0
        assert self.R0_from_K(K=1, gamma=2) == 1.5
        assert self.R0_from_K(K=2, gamma=2) == 2.0

    def test_K(self):
        assert self.K(R0=2, gamma=2) == 2.0
        assert self.K(R0=2, gamma=1) == 1.0
        assert self.K(R0=1, gamma=2) == 0.0
        assert self.K(R0=1, gamma=3) == 0.0
        assert self.K(R0=0.5, gamma=2) == -1.0
        assert self.K(R0=0.5, gamma=1) == -0.5

    def test_initial_state(self):
        assert_almost_equal(
            self.model.state_from_cases(population=10_000,
                                        cases=1000,
                                        R0=2,
                                        gamma=0.25),
            [9000, 333 + 2 / 3, 666 + 1 / 3],
        )
def _patch_conversation_app():
    from ej.components import register_menu
    from ej_conversations.models import Conversation
    from django.utils.translation import ugettext as _
    from sidekick import delegate_to, lazy
    from hyperpython import a

    not_given = object()

    def get_clusterization(conversation, default=not_given):
        """
        Initialize a clusterization object for the given conversation, if it does
        not exist.
        """
        try:
            return conversation.clusterization
        except (AttributeError, Clusterization.DoesNotExist):
            if default is not_given:
                mgm, _ = Clusterization.objects.get_or_create(
                    conversation=conversation)
                return mgm
            else:
                return default

    Conversation.get_clusterization = get_clusterization
    Conversation._clusterization = lazy(get_clusterization)
    Conversation.clusters = delegate_to("_clusterization")

    @register_menu("conversations:detail-admin")
    def _detail_links(request, conversation):
        if request.user.has_perm("ej.can_edit_conversation", conversation):
            return [
                a(_("Edit groups"), href=conversation.url("cluster:edit")),
                a(_("Manage personas"),
                  href=conversation.url("cluster:stereotype-votes")),
            ]
        else:
            return []

    @register_menu("conversations:detail-actions")
    def _detail_links(request, conversation):
        return [a(_("Opinion groups"), href=conversation.url("cluster:index"))]
Exemple #11
0
class RCConfigWrapper:
    @property
    def config(self):
        return self.configs.default_config()

    @property
    def url(self):
        return self.config.url

    @lazy
    def accounts(self):
        from .models import RCAccount
        return RCAccount.objects

    @lazy
    def configs(self):
        from .models import RCConfig
        return RCConfig.objects

    @property
    def has_config(self):
        return self.configs.default_config(raises=False) is not None

    admin_username = delegate_to('config')
    admin_password = delegate_to('config')
    admin_id = delegate_to('config')
    admin_token = delegate_to('config')
    save = delegate_to('config')

    def register(self, user, username):
        """
        Call Rocket.Chat API to register a new user, if not already registered.
        """
        password = random_password(30)
        result = self.api_call(
            'users.create',
            auth='admin',
            raises=False,
            payload={
                'email': user.email,
                'name': user.name,
                'username': username,
                'password': password,
            },
        )

        if not result.get('success'):
            error = result.get('error', result)
            log.warning(f'Could not create Rocket.Chat user: {error}')
            raise ApiError(result)

        # Account created successfully in Rocket.Chat, create a replica in
        # Django
        return self.accounts.create(
            config=self.config,
            user=user,
            username=username,
            password=password,
            user_rc_id=result['user']['_id'],
            is_active=True,
            account_data=result,
        )

    def login(self, user):
        """
        Force Django user to login at Rocket.Chat.

        User must already been registered and provided a RC username.
        """
        account = self.accounts.get(user=user)
        if not account.is_active:
            return account

        response = self.password_login(account.username, account.password)
        account.auth_token = response['data']['authToken']
        account.save()
        return account

    def password_login(self, username=None, password=None):
        """
        Login with explicit credentials.
        """
        if username is None and password is None:
            username = self.admin_username
            password = self.admin_password
        payload = {'username': username, 'password': password}
        try:
            response = self.api_call('login', payload=payload, auth='admin')
        except ApiError as exc:
            if exc.response['error'].lower() == 'unauthorized':
                raise PermissionError('invalid credentials')
            raise
        log.info(f'{username} successfully logged in at Rocket.Chat')
        return response

    def logout(self, user):
        """
        Force logout of Django user.
        """
        users = list(self.accounts.filter(user=user))
        if not users:
            return

        account = users[0]
        user_id, auth_token = account.user_rc_id, account.auth_token
        if auth_token:
            auth = {'user_id': user_id, 'auth_token': auth_token}
            self.api_call('logout', auth=auth, method='post')
            account.auth_token = ''
            account.save()
            log.info(f'{user} successfully logged out from Rocket.Chat')

    def get_auth_token(self, user):
        """
        Return the login auth token for the given user.
        """
        return self.accounts.get(user=user).auth_token

    def api_call(self, *args, **kwargs):
        """
        Calls Rocket.Chat API and return the response.

        Raise ApiError if response code is not 200.
        """
        return self.config.api_call(*args, **kwargs)

    def api_user_update(self, id, **kwargs):
        """
        Update user with parameters.

        Example:
            >>> rocket.api_user_update(user_id, password='******')
        """
        payload = {'userId': id, 'data': kwargs}
        result = self.api_call(f'users.update', payload=payload, auth='admin')
        return result['user']

    def find_or_create_account(self, user):
        """
        Tries to find and create a rocket user account for the given user.

        This method does not check if user has permission and can create a
        RC account even for a user that does not have the proper permission.

        Return None if no account is found.
        """
        try:
            return self.accounts.get(user=user)
        except self.accounts.model.DoesNotExist:
            pass
        return None
class ParticipationProgress(ProgressBase):
    """
    Tracks user evolution in conversation.
    """

    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        related_name="participation_progresses",
        on_delete=models.CASCADE,
        editable=False,
    )
    conversation = models.ForeignKey(
        "ej_conversations.Conversation",
        related_name="participation_progresses",
        on_delete=models.CASCADE,
        editable=False,
    )
    voter_level = models.EnumField(
        VoterLevel,
        default=VoterLevel.NONE,
        verbose_name=_("voter level"),
        help_text=_("Measure how many votes user has given in conversation"),
    )
    max_voter_level = models.EnumField(
        VoterLevel,
        default=VoterLevel.NONE,
        editable=False,
        verbose_name=_("maximum achieved voter level"))
    is_owner = models.BooleanField(
        _("owner?"),
        default=False,
        help_text=_(
            "Total score is computed differently if user owns conversation."),
    )
    is_focused = models.BooleanField(
        _("focused?"),
        default=False,
        help_text=_(
            "User received a focused badge (i.e., voted on all comments)"),
    )

    # Non de-normalized fields: conversations
    is_favorite = lazy(
        lambda p: p.conversation.favorites.filter(user=p.user).exists())
    n_final_votes = lazy(
        lambda p: p.user.votes.filter(comment__conversation=p.conversation
                                      ).exclude(choice=Choice.SKIP).count())
    n_approved_comments = lazy(lambda p: p.user.approved_comments.filter(
        conversation=p.conversation).count())
    n_rejected_comments = lazy(lambda p: p.user.rejected_comments.filter(
        conversation=p.conversation).count())
    n_conversation_comments = delegate_to("conversation",
                                          name="n_approved_comments")
    n_conversation_approved_comments = delegate_to("conversation",
                                                   name="n_approved_comments")
    n_conversation_rejected_comments = delegate_to("conversation",
                                                   name="n_rejected_comments")
    votes_ratio = lazy(this.n_final_votes /
                       (this.n_conversation_comments + 1e-50))

    # FIXME: Gamification
    # n_endorsements = lazy(lambda p: Endorsement.objects.filter(comment__author=p.user).count())
    # n_given_opinion_bridge_powers = delegate_to('user')
    # n_given_minority_activist_powers = delegate_to('user')
    n_endorsements = 0
    n_given_opinion_bridge_powers = 0
    n_given_minority_activist_powers = 0

    # Points
    VOTE_POINTS = 10
    APPROVED_COMMENT_POINTS = 30
    REJECTED_COMMENT_POINTS = -15
    ENDORSEMENT_POINTS = 15
    OPINION_BRIDGE_POINTS = 50
    MINORITY_ACTIVIST_POINTS = 50
    IS_FOCUSED_POINTS = 50

    pts_final_votes = compute_points(VOTE_POINTS)
    pts_approved_comments = compute_points(APPROVED_COMMENT_POINTS)
    pts_rejected_comments = compute_points(REJECTED_COMMENT_POINTS)
    pts_endorsements = compute_points(ENDORSEMENT_POINTS)
    pts_given_opinion_bridge_powers = compute_points(OPINION_BRIDGE_POINTS)
    pts_given_minority_activist_powers = compute_points(
        MINORITY_ACTIVIST_POINTS)
    pts_is_focused = compute_points(IS_FOCUSED_POINTS, name="is_focused")

    # Leaderboard
    @lazy
    def n_conversation_scores(self):
        db = ParticipationProgress.objects
        return db.filter(conversation=self.conversation).count()

    @lazy
    def n_higher_scores(self):
        db = ParticipationProgress.objects
        return db.filter(conversation=self.conversation,
                         score__gt=self.score).count()

    n_lower_scores = lazy(this.n_conversation_scores - this.n_higher_scores)

    # Signals
    level_achievement_signal = lazy(
        lambda _: signals.participation_level_achieved, shared=True)

    objects = ProgressQuerySet.as_manager()

    class Meta:
        verbose_name = _("User score (per conversation)")
        verbose_name_plural = _("User scores (per conversation)")

    def __str__(self):
        msg = __("Progress for user: {user} at {conversation}")
        return msg.format(user=self.user, conversation=self.conversation)

    def sync(self):
        self.is_owner = self.conversation.author == self.user

        # You cannot receive a focused achievement in your own conversation!
        if not self.is_owner:
            n_comments = self.conversation.n_approved_comments
            self.is_focused = (self.n_final_votes >=
                               20) and (n_comments == self.n_final_votes)

        return super().sync()

    def compute_score(self):
        """
        Compute the total number of points earned by user.

        User score is based on the following rules:
            * Vote: 10 points
            * Accepted comment: 30 points
            * Rejected comment: -30 points
            * Endorsement received: 15 points
            * Opinion bridge: 50 points
            * Minority activist: 50 points
            * Plus the total score of created conversations.
            * Got a focused badge: 50 points.

        Observation:
            Owner do not receive any points for its own conversations

        Returns:
            Total score (int)
        """
        return max(
            0,
            self.score_bias + self.pts_final_votes +
            self.pts_approved_comments + self.pts_rejected_comments +
            self.pts_endorsements + self.pts_given_opinion_bridge_powers +
            self.pts_given_minority_activist_powers + self.pts_is_focused,
        )
Exemple #13
0
class Profile(models.Model):
    """
    User profile
    """
    user = models.OneToOneField(User,
                                on_delete=models.CASCADE,
                                related_name='raw_profile')
    race = EnumField(Race, _('Race'), default=Race.UNFILLED)
    ethnicity = models.CharField(_('Ethnicity'), blank=True, max_length=50)
    education = models.CharField(_('Education'), blank=True, max_length=140)
    gender = EnumField(Gender, _('Gender identity'), default=Gender.UNFILLED)
    gender_other = models.CharField(_('User provided gender'),
                                    max_length=50,
                                    blank=True)
    age = models.IntegerField(_('Age'), null=True, blank=True)
    birth_date = models.DateField(_('Birth Date'), null=True, blank=True)
    country = models.CharField(_('Country'), blank=True, max_length=50)
    state = models.CharField(
        _('State'),
        blank=True,
        max_length=settings.EJ_STATE_MAX_LENGTH,
        choices=settings.EJ_STATE_CHOICES,
    )
    city = models.CharField(_('City'), blank=True, max_length=140)
    biography = models.TextField(_('Biography'), blank=True)
    occupation = models.CharField(_('Occupation'), blank=True, max_length=50)
    political_activity = models.TextField(_('Political activity'), blank=True)
    image = models.ImageField(_('Image'),
                              blank=True,
                              null=True,
                              upload_to='profile_images')

    name = delegate_to('user')
    email = delegate_to('user')
    is_active = delegate_to('user')
    is_staff = delegate_to('user')
    is_superuser = delegate_to('user')

    @property
    def age(self):
        if not self.birth_date:
            age = None
        else:
            delta = datetime.datetime.now().date() - self.birth_date
            age = abs(int(delta.days // 365.25))
        return age

    class Meta:
        ordering = ['user__email']

    def __str__(self):
        return __('{name}\'s profile').format(name=self.user.name)

    def __getattr__(self, attr):
        try:
            user = self.user
        except User.DoesNotExist:
            raise AttributeError(attr)
        return getattr(user, attr)

    @property
    def gender_description(self):
        if self.gender != Gender.UNDECLARED:
            return self.gender.description
        return self.gender_other

    @property
    def token(self):
        token = Token.objects.get_or_create(user_id=self.id)
        return token[0].key

    @property
    def image_url(self):
        try:
            return self.image.url
        except ValueError:
            for account in SocialAccount.objects.filter(user=self.user):
                picture = account.get_avatar_url()
                return picture
            return '/static/img/logo/avatar_default.svg'

    @property
    def has_image(self):
        return self.image or SocialAccount.objects.filter(user_id=self.id)

    @property
    def is_filled(self):
        fields = ('race', 'age', 'birth_date', 'education', 'ethnicity',
                  'country', 'state', 'city', 'biography', 'occupation',
                  'political_activity', 'has_image', 'gender_description')
        return bool(all(getattr(self, field) for field in fields))

    def get_absolute_url(self):
        return reverse('user-detail', kwargs={'pk': self.id})

    def profile_fields(self, user_fields=False, blacklist=None):
        """
        Return a list of tuples of (field_description, field_value) for all
        registered profile fields.
        """

        fields = [
            'city', 'country', 'occupation', 'education', 'ethnicity',
            'gender', 'race', 'political_activity', 'biography'
        ]
        field_map = {field.name: field for field in self._meta.fields}

        # Create a tuples of (attribute, human-readable name, value)
        triple_list = []
        for field in fields:
            description = field_map[field].verbose_name
            getter = getattr(self, f'get_{field}_display',
                             lambda: getattr(self, field))
            triple = (field, description.capitalize(), getter())
            triple_list.append(triple)

        # Age is not a real field, but a property. We insert it after occupation
        triple_list.insert(3, ('age', _('Age'), self.age))

        # Add fields in the user profile (e.g., e-mail)
        if user_fields:
            triple_list.insert(0, ('email', _('E-mail'), self.user.email))

        # Prepare blacklist of fields
        if blacklist is None:
            blacklist = settings.EJ_EXCLUDE_PROFILE_FIELDS

        # Remove the attribute name from the list
        return [(b, c) for a, b, c in triple_list if a not in blacklist]

    def statistics(self):
        """
        Return a dictionary with all profile statistics.
        """
        return dict(
            votes=self.user.votes.count(),
            comments=self.user.comments.count(),
            conversations=self.user.conversations.count(),
        )

    def badges(self):
        """
        Return all profile badges.
        """
        return self.user.badges_earned.all()

    def comments(self):
        """
        Return all profile comments.
        """
        return self.user.comments.all()

    def role(self):
        """
        A human-friendly description of the user role in the platform.
        """
        if self.user.is_superuser:
            return _('Root')
        if self.user.is_staff:
            return _('Administrative user')
        return _('Regular user')
Exemple #14
0
class ConversationProgress(ProgressBase):
    """
    Tracks activity in conversation.
    """

    conversation = models.OneToOneField("ej_conversations.Conversation",
                                        related_name="progress",
                                        on_delete=models.CASCADE)
    conversation_level = models.EnumField(
        ConversationLevel,
        default=CommenterLevel.NONE,
        verbose_name=_("conversation level"),
        help_text=_("Measures the level of engagement for conversation."),
    )
    max_conversation_level = models.EnumField(
        ConversationLevel,
        default=CommenterLevel.NONE,
        editable=False,
        help_text=_("maximum achieved conversation level"),
    )

    # Non de-normalized fields: conversations
    n_final_votes = delegate_to("conversation")
    n_approved_comments = delegate_to("conversation")
    n_rejected_comments = delegate_to("conversation")
    n_participants = delegate_to("conversation")
    n_favorites = delegate_to("conversation")
    n_tags = delegate_to("conversation")

    # Clusterization
    n_clusters = delegate_to("conversation")
    n_stereotypes = delegate_to("conversation")

    # Gamification
    n_endorsements = 0  # FIXME: delegate_to("conversation")

    # Signals
    level_achievement_signal = lazy(
        lambda _: signals.conversation_level_achieved, shared=True)

    # Points
    VOTE_POINTS = 1
    APPROVED_COMMENT_POINTS = 2
    REJECTED_COMMENT_POINTS = -0.125
    ENDORSEMENT_POINTS = 3

    pts_final_votes = compute_points(1)
    pts_approved_comments = compute_points(2)
    pts_rejected_comments = compute_points(-0.125)
    pts_endorsements = compute_points(3)

    objects = ProgressQuerySet.as_manager()

    class Meta:
        verbose_name = _("Conversation score")
        verbose_name_plural = _("Conversation scores")

    def __str__(self):
        return __('Progress for "{conversation}"').format(
            conversation=self.conversation)

    def compute_score(self):
        """
        Compute the total number of points for user contribution.

        Conversation score is based on the following rules:
            * Vote: 1 points
            * Accepted comment: 2 points
            * Rejected comment: -3 points
            * Endorsement created: 3 points

        Returns:
            Total score (int)
        """
        return int(
            max(
                0,
                self.score_bias + self.pts_final_votes +
                self.pts_approved_comments + self.pts_rejected_comments +
                self.pts_endorsements,
            ))
Exemple #15
0
class Profile(models.Model):
    """
    User profile
    """

    user = models.OneToOneField(User,
                                on_delete=models.CASCADE,
                                related_name="profile")
    race = EnumField(Race, _("Race"), default=Race.NOT_FILLED)
    ethnicity = models.CharField(_("Ethnicity"), blank=True, max_length=50)
    education = models.CharField(_("Education"), blank=True, max_length=140)
    gender = EnumField(Gender, _("Gender identity"), default=Gender.NOT_FILLED)
    gender_other = models.CharField(_("User provided gender"),
                                    max_length=50,
                                    blank=True)
    birth_date = models.DateField(_("Birth Date"), null=True, blank=True)
    country = models.CharField(_("Country"), blank=True, max_length=50)
    state = models.CharField(
        _("State"),
        blank=True,
        max_length=settings.EJ_STATE_MAX_LENGTH,
        choices=settings.EJ_STATE_CHOICES,
    )
    city = models.CharField(_("City"), blank=True, max_length=140)
    biography = models.TextField(_("Biography"), blank=True)
    occupation = models.CharField(_("Occupation"), blank=True, max_length=50)
    political_activity = models.TextField(_("Political activity"), blank=True)
    profile_photo = models.ImageField(_("Profile Photo"),
                                      blank=True,
                                      null=True,
                                      upload_to="profile_images")

    name = delegate_to("user")
    email = delegate_to("user")
    is_active = delegate_to("user")
    is_staff = delegate_to("user")
    is_superuser = delegate_to("user")
    limit_board_conversations = delegate_to("user")

    @property
    def age(self):
        return None if self.birth_date is None else years_from(self.birth_date)

    class Meta:
        ordering = ["user__email"]

    def __str__(self):
        return __("{name}'s profile").format(name=self.user.name)

    def __getattr__(self, attr):
        try:
            user = self.user
        except User.DoesNotExist:
            raise AttributeError(attr)
        return getattr(user, attr)

    @property
    def gender_description(self):
        if self.gender != Gender.NOT_FILLED:
            return self.gender.description
        return self.gender_other

    @property
    def token(self):
        token = Token.objects.get_or_create(user_id=self.id)
        return token[0].key

    @property
    def image_url(self):
        try:
            return self.profile_photo.url
        except ValueError:
            if apps.is_installed("allauth.socialaccount"):
                for account in SocialAccount.objects.filter(user=self.user):
                    picture = account.get_avatar_url()
                    return picture
            return staticfiles_storage.url("/img/login/avatar.svg")

    @property
    def has_image(self):
        return bool(self.profile_photo
                    or (apps.is_installed("allauth.socialaccount")
                        and SocialAccount.objects.filter(user_id=self.id)))

    @property
    def is_filled(self):
        fields = (
            "race",
            "age",
            "birth_date",
            "education",
            "ethnicity",
            "country",
            "state",
            "city",
            "biography",
            "occupation",
            "political_activity",
            "has_image",
            "gender_description",
        )
        return bool(all(getattr(self, field) for field in fields))

    def get_absolute_url(self):
        return reverse("user-detail", kwargs={"pk": self.id})

    def profile_fields(self, user_fields=False, blacklist=None):
        """
        Return a list of tuples of (field_description, field_value) for all
        registered profile fields.
        """

        fields = [
            "city",
            "state",
            "country",
            "occupation",
            "education",
            "ethnicity",
            "gender",
            "race",
            "political_activity",
            "biography",
        ]
        field_map = {field.name: field for field in self._meta.fields}
        null_values = {Gender.NOT_FILLED, Race.NOT_FILLED}

        # Create a tuples of (attribute, human-readable name, value)
        triple_list = []
        for field in fields:
            description = field_map[field].verbose_name
            value = getattr(self, field)
            if value in null_values:
                value = None
            elif hasattr(self, f"get_{field}_display"):
                value = getattr(self, f"get_{field}_display")()
            triple_list.append((field, description, value))

        # Age is not a real field, but a property. We insert it after occupation
        triple_list.insert(3, ("age", _("Age"), self.age))

        # Add fields in the user profile (e.g., e-mail)
        if user_fields:
            triple_list.insert(0, ("email", _("E-mail"), self.user.email))

        # Prepare blacklist of fields
        if blacklist is None:
            blacklist = settings.EJ_EXCLUDE_PROFILE_FIELDS

        # Remove the attribute name from the list
        return [(b, c) for a, b, c in triple_list if a not in blacklist]

    def statistics(self):
        """
        Return a dictionary with all profile statistics.
        """
        return dict(
            votes=self.user.votes.count(),
            comments=self.user.comments.count(),
            conversations=self.user.conversations.count(),
        )

    def badges(self):
        """
        Return all profile badges.
        """
        return self.user.badges_earned.all()

    def comments(self):
        """
        Return all profile comments.
        """
        return self.user.comments.all()

    def role(self):
        """
        A human-friendly description of the user role in the platform.
        """
        if self.user.is_superuser:
            return _("Root")
        if self.user.is_staff:
            return _("Administrative user")
        return _("Regular user")
Exemple #16
0
class RCConfigWrapper:
    url = delegate_to("config")
    api_url = property(lambda self: self.config.api_url or self.config.url)
    _config = None

    def __init__(self, config=None):
        if config is not None:
            self._config = config

    @lazy
    def config(self):
        return self._config or self.configs.default_config()

    @lazy
    def accounts(self):
        from .models import RCAccount

        return RCAccount.objects

    @lazy
    def configs(self):
        from .models import RCConfig

        return RCConfig.objects

    @property
    def has_config(self):
        return self.configs.default_config(raises=False) is not None

    admin_username = delegate_to("config")
    admin_id = delegate_to("config")
    admin_token = delegate_to("config")
    admin_password = property(lambda self: self.config.admin_password or
                              settings.EJ_ROCKETCHAT_ADMIN_PASSWORD)

    def clean_config(self):
        del self.config
        return self

    def register(self, user, username):
        """
        Call Rocket.Chat API to register a new user, if not already registered.
        """
        password = random_password(30)
        email = user.email
        result = self.api_call(
            "users.create",
            auth="admin",
            raises=False,
            payload={
                "email": email,
                "name": user.name,
                "username": username,
                "password": password
            },
        )

        if not result.get("success"):
            error = result.get("error", result)
            log.warning(
                f"[rocket.register] Could not create Rocket.Chat user: {error}"
            )
            raise ApiError(result)

        # Account created successfully in Rocket.Chat, create a replica in
        # Django
        return self.accounts.create(
            config=self.config,
            user=user,
            username=username,
            password=password,
            user_rc_id=result["user"]["_id"],
            user_rc_email=email,
            is_active=True,
            account_data=result,
        )

    def admin_login(self):
        response = self.password_login(self.admin_username,
                                       self.admin_password)
        config = self.config
        config.admin_token = response["data"]["authToken"]
        config.save()
        return response

    def login(self, user):
        """
        Force Django user to login at Rocket.Chat.

        User must already been registered with an RC username.
        """
        # Super-user share the global config account. Login will force login and
        # update the global config
        if user.is_superuser:
            self.admin_login()
            return record(
                config=self.config,
                username=self.admin_username,
                password=self.admin_password,
                auth_token=self.config.admin_token,
                user_rc_id=self.admin_id,
                user=user,
            )

        # Handle regular accounts
        account = self.accounts.get(user=user)
        if not account.is_active:
            raise PermissionError("cannot login with inactive accounts")

        response = self.password_login(account.username, account.password)
        account.auth_token = response["data"]["authToken"]
        account.save()
        return account

    def password_login(self, username, password):
        """
        Login with explicit credentials.
        """
        payload = {"username": username, "password": password}
        try:
            response = self.api_call("login", payload=payload)
        except ApiError as exc:
            if exc.is_permission_error:
                raise PermissionError("invalid credentials")
            raise
        log.info(f"[rocket] {username} successfully logged in at Rocket.Chat")
        return response

    def logout(self, user):
        """
        Force logout of RC user.
        """
        users = list(self.accounts.filter(user=user))
        if user.is_superuser:
            self.api_call("logout", auth="admin", method="post")
            self.config.admin_token = ""
            self.config.save()
        if not users:
            return

        account = users[0]
        user_id, auth_token = account.user_rc_id, account.auth_token
        if auth_token:
            auth = {"user_id": user_id, "auth_token": auth_token}
            self.api_call("logout", auth=auth, method="post")
            account.auth_token = ""
            account.save()
            log.info(f"{user} successfully logged out from Rocket.Chat")

    def get_auth_token(self, user):
        """
        Return the login auth token for the given user.
        """
        if user.is_superuser:
            token = self.admin_token
            if token:
                return token
            return self.renew_admin_token()
        else:
            token = self.accounts.get(user=user).auth_token
            if token:
                return token
            return self.renew_auth_token(user)

    def renew_admin_token(self):
        """
        Renew the admin user auth-token.
        """
        self.admin_login()
        return self.config.admin_token

    def renew_auth_token(self, user):
        """
        Log in with user again and renew its authentication token.
        """
        if not user.is_superuser:
            return self.login(user).auth_token
        raise PermissionError(
            "cannot renew admin token. Please call renew_admin_token()")

    def api_call(
        self,
        uri,
        payload=None,
        query_args=None,
        headers=None,
        raises=True,
        method="post",
        auth=None,
        version="v1",
    ) -> dict:
        """
        Makes a call to Rocketchat API.

        Args:
            uri:
                Used to construct Rocket.Chat url as <rocketchat>/api/<version>/<uri>
            payload (JSON):
                JSON payload for the request
            query_args (dict):
                Query dictionary appended to the url.
            headers (dict):
                An optional dictionary of HTTP headers.
            raises (bool):
                If True (default) raises an ApiError for bad responses from the
                API, otherwise, return the response dictionary.
            method ('post', 'get'):
                HTTP method used on the request.
            auth:
                Either None, a user with a registered Rocket.Chat account or
                the string 'admin' for admin access to the API.
            version:
                Version of Rocket.Chat API (only v1 is valid for now)
        """
        url = normalize_api_url(self.api_url, version, uri, query_args)
        headers = normalize_headers(headers, auth, self)
        kwargs = {}

        # Payload
        if payload is not None and method == "post":
            kwargs["data"] = json.dumps(payload)
            headers["Content-Type"] = "application/json"
        method = getattr(requests, method)

        # Makes API request
        try:
            response = method(url, headers=headers, **kwargs)
            result = json.loads(response.content, encoding="utf-8")
            result["code"] = response.status_code
            result.setdefault("error", response.status_code != 200)
        except (requests.ConnectionError, JSONDecodeError) as exc:
            result = {
                "code": None,
                "message": str(exc),
                "error": type(exc).__name__,
                "status": "error"
            }

        if DEBUG:
            print("[ROCKET] API CALL TO", url)
            print("    auth:", auth)
            print("    headers:", headers)
            print("    payload:", payload)
            pprint(result)

        # If admin auth token expires, some requests made using the admin role
        # will fail with unpredicted messages. We must try again
        if result["error"] and auth == "admin":
            headers["X-Auth-Token"] = self.admin_login()["data"]["authToken"]
            return self.api_call(uri,
                                 payload,
                                 query_args=query_args,
                                 headers=headers,
                                 version=version,
                                 raises=raises)

        if raises and result["error"] is not False:
            raise ApiError(result)
        return result
Exemple #17
0
        related_name='votes',
        on_delete=models.CASCADE,
    )
    comment = models.ForeignKey(
        'ej_conversations.Comment',
        verbose_name=_('Comment'),
        related_name='stereotype_votes',
        on_delete=models.CASCADE,
    )
    choice = EnumField(Choice, _('Choice'))
    stereotype = alias('author')
    objects = BoogieManager()

    def __str__(self):
        return f'StereotypeVote({self.author}, value={self.choice})'


#
# Auxiliary methods
#
def get_clusterization(conversation):
    try:
        return conversation.clusterization
    except Clusterization.DoesNotExist:
        mgm, _ = Clusterization.objects.get_or_create(conversation=conversation)
        return mgm


Conversation.clusters = delegate_to('clusterization')
Conversation.stereotypes = delegate_to('clusterization')
Exemple #18
0
class Clusterization(TimeStampedModel):
    """
    Manages clusterization tasks for a given conversation.
    """

    conversation = models.OneToOneField(
        "ej_conversations.Conversation",
        on_delete=models.CASCADE,
        related_name="clusterization",
    )
    cluster_status = EnumField(ClusterStatus, default=ClusterStatus.PENDING_DATA)
    pending_comments = models.ManyToManyField(
        "ej_conversations.Comment",
        related_name="pending_in_clusterizations",
        editable=False,
        blank=True,
    )
    pending_votes = models.ManyToManyField(
        "ej_conversations.Vote",
        related_name="pending_in_clusterizations",
        editable=False,
        blank=True,
    )

    unprocessed_comments = property(lambda self: self.pending_comments.count())
    unprocessed_votes = property(lambda self: self.pending_votes.count())
    comments = delegate_to("conversation")
    users = delegate_to("conversation")
    votes = delegate_to("conversation")
    owner = delegate_to("conversation", name="author")

    @property
    def stereotypes(self):
        return Stereotype.objects.filter(clusters__in=self.clusters.all())

    @property
    def stereotype_votes(self):
        return StereotypeVote.objects.filter(comment__in=self.comments.all())

    #
    # Statistics and annotated values
    #
    n_clusters = lazy(this.clusters.count())
    n_stereotypes = lazy(this.stereotypes.count())
    n_stereotype_votes = lazy(this.stereotype_votes.count())

    objects = ClusterizationManager()

    class Meta:
        ordering = ["conversation_id"]

    def __str__(self):
        clusters = self.clusters.count()
        return f"{self.conversation} ({clusters} clusters)"

    def get_absolute_url(self):
        return self.conversation.url("cluster:index")

    def update_clusterization(self, force=False, atomic=False):
        """
        Update clusters if necessary, unless force=True, in which it
        unconditionally updates the clusterization.
        """
        if force or rules.test_rule("ej.must_update_clusterization", self):
            log.info(f"[clusters] updating cluster: {self.conversation}")

            if self.clusters.count() == 0:
                if self.cluster_status == ClusterStatus.ACTIVE:
                    self.cluster_status = ClusterStatus.PENDING_DATA
                self.save()
                return

            with use_transaction(atomic=atomic):
                try:
                    self.clusters.clusterize_from_votes()
                except ValueError:
                    return
                self.pending_comments.all().delete()
                self.pending_votes.all().delete()
                if self.cluster_status == ClusterStatus.PENDING_DATA:
                    self.cluster_status = ClusterStatus.ACTIVE
                x = self.id
                y = self.conversation_id
                self.save()
Exemple #19
0
class ClinicalModel(Model, ABC):
    """
    Base class for clinical models that track the infection curve and
    models the clinical history of patients.
    """

    class Meta:
        model_name = "Clinical"
        data_aliases = {"H": "hospitalized", "D": "deaths"}
        plot_columns = ("hospitalized_cases", "hospitalized", "deaths")

    # Delegates (population parameters)
    parent_model = sk.alias("infection_model")
    population = sk.delegate_to("infection_model")
    K = sk.delegate_to("infection_model")
    R0 = sk.delegate_to("infection_model")
    age_distribution = sk.delegate_to("infection_model")
    age_pyramid = sk.delegate_to("infection_model")

    # Properties and aliases
    case_fatality_ratio = param_property(default=0.0)
    infection_fatality_ratio = param_property(default=lambda _: _.CFR)
    CFR = param_alias("case_fatality_ratio")
    IFR = param_alias("infection_fatality_ratio")

    @property
    def empirical_CFR(self):
        return self["empirical_CFR:final"]

    @property
    def empirical_IFR(self):
        return self["empirical_IFR:final"]

    @property
    def clinical_model(self):
        return self

    def __init__(self, infection_model, *args, **kwargs):
        self.infection_model = infection_model

        for k in ("disease", "disease_params", "region"):
            if k not in kwargs:
                kwargs[k] = getattr(infection_model, k)

        kwargs.setdefault("name", infection_model.name)
        super().__init__(*args, **kwargs)

    def __getattr__(self, item):
        try:
            return getattr(self.infection_model, item)
        except AttributeError:
            name = type(self).__name__
            raise AttributeError(f'"{name}" object has no "{item}" attribute')

    def copy(self, **kwargs):
        kwargs["infection_model"] = self.infection_model.copy()
        return super().copy(**kwargs)

    #
    # Data accessors
    #
    def get_column(self, name, idx):
        name = self.meta.data_aliases.get(name, name)
        try:
            return super().get_column(name, idx)
        except ValueError:
            return self.infection_model.get_column(name, idx)

    # Basic columns
    def get_data_population(self, idx):
        """
        Total population minus deaths.
        """
        return self.infection_model["population", idx] - self["deaths", idx]

    def get_data_infectious(self, idx):
        """
        Infectious population according to infectious model.

        This is usually the starting point of all clinical models.
        """
        return self.infection_model["infectious", idx]

    def get_data_cases(self, idx):
        """
        Cumulative curve of cases.

        A case is typically defined as an individual who got infected AND
        developed recognizable clinical symptoms.
        """
        return self.infection_model["cases", idx]

    def get_data_infected(self, idx):
        """
        Cumulative curve of infected individuals.

        Infected individuals might not develop clinical symptoms. They may never
        develop symptoms (asymptomatic) or develop them in a future time.
        """
        try:
            return self.infection_model["infected", idx]
        except KeyError:
            return self["cases", idx]

    # Derived methods
    def get_data_empirical_CFR(self, idx):
        """
        Empirical CFR computed as current deaths over cases.
        """
        return (self["deaths", idx] / self["cases", idx]).fillna(0.0)

    def get_data_empirical_IFR(self, idx):
        """
        Empirical IFR computed as current deaths over infected.
        """
        return (self["deaths", idx] / self["infected", idx]).fillna(0.0)

    # Abstract interface
    def get_data_death_rate(self, idx):
        """
        Daily number of deaths.
        """
        return self["deaths", idx].diff().fillna(0)

    def get_data_deaths(self, idx):
        """
        Cumulative curve of deaths.
        """
        raise NotImplementedError("must be implemented in sub-classes")

    def get_data_severe(self, idx):
        """
        Current number of severe cases.

        Severe cases usually require hospitalization, but have a low death risk.
        """
        raise NotImplementedError("must be implemented in sub-classes")

    def get_data_severe_cases(self, idx):
        """
        Cumulative number of severe cases.

        Severe cases usually require hospitalization, but have a low death risk.
        """
        raise NotImplementedError("must be implemented in sub-classes")

    def get_data_critical(self, idx):
        """
        Current number of critical cases.

        Critical cases require intensive care and are at a high risk of death.
        """
        raise NotImplementedError("must be implemented in sub-classes")

    def get_data_critical_cases(self, idx):
        """
        Cumulative number of critical cases.

        Critical cases require intensive care and are at a high risk of death.
        """
        raise NotImplementedError("must be implemented in sub-classes")

    def get_data_hospitalized(self, idx):
        """
        Cases currently occupying a hospital bed.

        In an ideal world, this would be equal to the number of severe cases.
        The default implementation assumes that.
        """
        return self["severe", idx]

    def get_data_hospitalized_cases(self, idx):
        """
        Cumulative number of hospitalizations.

        Default implementation assumes equal to the number of severe cases.
        """
        return self["severe_cases", idx]

    def get_data_icu(self, idx):
        """
        Number of ICU patients.

        In an ideal world, this would be equal to the number of critical cases.
        The default implementation assumes that.
        Default implementation assumes equal to the number of critical cases.
        """
        return self["critical", idx]

    def get_data_icu_cases(self, idx):
        """
        Cumulative number of ICU patients.

        Default implementation assumes equal to the number of critical cases.
        """
        return self["critical_cases", idx]

    def get_data_ppe(self, idx):
        """
        Requirement of several personal protection equipment estimated from
        the cumulative sum of patients x day, icu_patients x day.
        """
        severe = self["severe"]
        critical = self["critical"]
        severe_day = cumtrapz(severe, severe.index, initial=0)
        critical_day = cumtrapz(critical, critical.index, initial=0)
        out = self.disease.recommended_ppe(
            pd.Series(severe_day, index=severe.index), pd.Series(critical_day, index=critical.index)
        )
        if idx is None:
            return out
        return out[idx]

    #
    # Other functions
    #
    def plot(self, components=None, *, ax=None, logy=False, show=False, **kwargs):
        if components is None:
            self.infection_model.plot(**kwargs)
            components = self.meta.plot_columns
        super().plot(components, show=show, **kwargs)
Exemple #20
0
class UserProgress(ProgressBase):
    """
    Tracks global user evolution.
    """

    user = models.OneToOneField(settings.AUTH_USER_MODEL,
                                related_name="progress",
                                on_delete=models.CASCADE,
                                editable=False)
    commenter_level = models.EnumField(
        CommenterLevel,
        default=CommenterLevel.NONE,
        verbose_name=_("commenter level"),
        help_text=
        _("Measures how much user participated by contributing comments to conversations"
          ),
    )
    max_commenter_level = models.EnumField(
        CommenterLevel,
        default=CommenterLevel.NONE,
        editable=False,
        verbose_name=_("maximum achieved commenter level"),
    )
    host_level = models.EnumField(
        HostLevel,
        default=HostLevel.NONE,
        verbose_name=_("host level"),
        help_text=
        _("Measures how much user participated by creating engaging conversations."
          ),
    )
    max_host_level = models.EnumField(
        HostLevel,
        default=HostLevel.NONE,
        editable=False,
        verbose_name=_("maximum achieved host level"))
    profile_level = models.EnumField(
        ProfileLevel,
        default=ProfileLevel.NONE,
        verbose_name=_("profile level"),
        help_text=_("Measures how complete is the user profile."),
    )
    max_profile_level = models.EnumField(
        ProfileLevel,
        default=ProfileLevel.NONE,
        editable=False,
        verbose_name=_("maximum achieved profile level"),
    )

    # Non de-normalized fields: conversations app
    n_conversations = delegate_to("user")
    n_pending_comments = delegate_to("user")
    n_rejected_comments = delegate_to("user")

    @lazy
    def n_final_votes(self):
        return (Vote.objects.filter(author=self.user).exclude(
            comment__conversation__author=self.user).count())

    @lazy
    def n_approved_comments(self):
        return Comment.objects.filter(author=self.user).exclude(
            conversation__author=self.user).count()

    # Gamification app
    n_endorsements = 0  # FIXME: delegate_to("user")
    n_given_opinion_bridge_powers = delegate_to("user")
    n_given_minority_activist_powers = delegate_to("user")

    # Level of conversations
    def _conversation_level_checker(*args):
        *_, lvl = args  # ugly trick to make static analysis happy
        return lazy(lambda p: p.user.conversations.filter(
            progress__conversation_level__gte=lvl).count())

    def _participation_level_checker(*args):
        *_, lvl = args  # ugly trick to make static analysis happy
        return lazy(lambda p: p.user.participation_progresses.filter(
            voter_level__gte=lvl).count())

    n_conversation_lvl_1 = _conversation_level_checker(
        ConversationLevel.CONVERSATION_LVL1)
    n_conversation_lvl_2 = _conversation_level_checker(
        ConversationLevel.CONVERSATION_LVL2)
    n_conversation_lvl_3 = _conversation_level_checker(
        ConversationLevel.CONVERSATION_LVL3)
    n_conversation_lvl_4 = _conversation_level_checker(
        ConversationLevel.CONVERSATION_LVL2)
    n_participation_lvl_1 = _participation_level_checker(VoterLevel.VOTER_LVL1)
    n_participation_lvl_2 = _participation_level_checker(VoterLevel.VOTER_LVL2)
    n_participation_lvl_3 = _participation_level_checker(VoterLevel.VOTER_LVL3)
    n_participation_lvl_4 = _participation_level_checker(VoterLevel.VOTER_LVL4)

    del _conversation_level_checker
    del _participation_level_checker

    # Aggregators
    total_conversation_score = delegate_to("user")
    total_participation_score = delegate_to("user")

    # Score points
    VOTE_POINTS = 10
    APPROVED_COMMENT_POINTS = 30
    REJECTED_COMMENT_POINTS = -15
    ENDORSEMENT_POINTS = 15
    OPINION_BRIDGE_POINTS = 50
    MINORITY_ACTIVIST_POINTS = 50

    pts_final_votes = compute_points(VOTE_POINTS)
    pts_approved_comments = compute_points(APPROVED_COMMENT_POINTS)
    pts_rejected_comments = compute_points(REJECTED_COMMENT_POINTS)
    pts_endorsements = compute_points(ENDORSEMENT_POINTS)
    pts_given_opinion_bridge_powers = compute_points(OPINION_BRIDGE_POINTS)
    pts_given_minority_activist_powers = compute_points(
        MINORITY_ACTIVIST_POINTS)

    # Signals
    level_achievement_signal = lazy(lambda _: signals.user_level_achieved,
                                    shared=True)

    score_level = property(lambda self: ScoreLevel.from_score(self.score))

    @property
    def n_trophies(self):
        n = 0
        n += bool(self.score_level)
        n += bool(self.profile_level)
        n += bool(self.host_level)
        n += bool(self.commenter_level)
        n += self.n_conversation_lvl_1
        n += self.n_participation_lvl_1
        return n

    objects = ProgressQuerySet.as_manager()

    class Meta:
        verbose_name = _("User score")
        verbose_name_plural = _("User scores")

    def __str__(self):
        return str(self.user)

    def compute_score(self):
        """
        Compute the total number of points earned by user.

        User score is based on the following rules:
            * Vote: 10 points
            * Accepted comment: 30 points
            * Rejected comment: -30 points
            * Endorsement received: 15 points
            * Opinion bridge: 50 points
            * Minority activist: 50 points
            * Plus the total score of created conversations.

        Returns:
            Total score (int)
        """
        return max(
            0,
            self.score_bias + self.pts_final_votes +
            self.pts_approved_comments + self.pts_rejected_comments +
            self.pts_endorsements + self.pts_given_opinion_bridge_powers +
            self.pts_given_minority_activist_powers +
            self.total_conversation_score,
        )
Exemple #21
0
class RCConfigWrapper:
    @property
    def config(self):
        return self.configs.default_config()

    @property
    def url(self):
        return self.config.url

    @lazy
    def accounts(self):
        from .models import RCAccount

        return RCAccount.objects

    @lazy
    def configs(self):
        from .models import RCConfig

        return RCConfig.objects

    @property
    def has_config(self):
        return self.configs.default_config(raises=False) is not None

    admin_username = delegate_to("config")
    admin_password = delegate_to("config")
    admin_id = delegate_to("config")
    admin_token = delegate_to("config")

    def register(self, user, username):
        """
        Call Rocket.Chat API to register a new user, if not already registered.
        """
        password = random_password(30)
        email = user.email
        result = self.api_call(
            "users.create",
            auth="admin",
            raises=False,
            payload={
                "email": email,
                "name": user.name,
                "username": username,
                "password": password,
            },
        )

        if not result.get("success"):
            error = result.get("error", result)
            log.warning(f"Could not create Rocket.Chat user: {error}")
            raise ApiError(result)

        # Account created successfully in Rocket.Chat, create a replica in
        # Django
        return self.accounts.create(
            config=self.config,
            user=user,
            username=username,
            password=password,
            user_rc_id=result["user"]["_id"],
            user_rc_email=email,
            is_active=True,
            account_data=result,
        )

    def login(self, user):
        """
        Force Django user to login at Rocket.Chat.

        User must already been registered and provided a RC username.
        """
        # Super-user share the global config account. Login will force login and
        # update the global config
        if user.is_superuser:
            from .models import RCAccount
            response = self.password_login(self.admin_username,
                                           self.admin_password)
            self.config.admin_token = response["data"]["authToken"]
            self.config.save()
            return record(
                config=self.config,
                username=self.admin_username,
                password=self.admin_password,
                auth_token=self.config.admin_token,
                user_rc_id=self.admin_id,
                user=user,
            )

        # Handle regular accounts
        account = self.accounts.get(user=user)
        if not account.is_active:
            return account

        response = self.password_login(account.username, account.password)
        account.auth_token = response["data"]["authToken"]
        account.save()
        return account

    def password_login(self, username, password):
        """
        Login with explicit credentials.
        """
        payload = {"username": username, "password": password}
        try:
            response = self.api_call("login", payload=payload)
        except ApiError as exc:
            if exc.response["error"].lower() == "unauthorized":
                raise PermissionError("invalid credentials")
            raise
        log.info(f"{username} successfully logged in at Rocket.Chat")
        return response

    def logout(self, user):
        """
        Force logout of RC user.
        """
        users = list(self.accounts.filter(user=user))
        if not users:
            return

        account = users[0]
        user_id, auth_token = account.user_rc_id, account.auth_token
        if auth_token:
            auth = {"user_id": user_id, "auth_token": auth_token}
            self.api_call("logout", auth=auth, method="post")
            account.auth_token = ""
            account.save()
            log.info(f"{user} successfully logged out from Rocket.Chat")

    def get_auth_token(self, user, fast=False):
        """
        Return the login auth token for the given user.
        """
        if fast and user.is_superuser:
            return self.admin_token
        elif fast:
            return self.accounts.get(user=user).auth_token

        account = self.login(user)
        return account.auth_token

    def api_call(self, *args, **kwargs):
        """
        Calls Rocket.Chat API and return the response.

        Raise ApiError if response code is not 200.
        """
        return self.config.api_call(*args, **kwargs)

    def api_user_update(self, id, **kwargs):
        """
        Update user with parameters.

        Example:
            >>> rocket.api_user_update(user_id, password='******')
        """
        payload = {"userId": id, "data": kwargs}
        result = self.api_call(f"users.update", payload=payload, auth="admin")
        return result["user"]

    def find_or_create_account(self, user):
        """
        Tries to find and create a rocket user account for the given user.

        This method does not check if user has permission and can create a
        RC account even for a user that does not have the proper permission.

        Return None if no account is found.
        """
        try:
            return self.accounts.get(user=user)
        except self.accounts.model.DoesNotExist:
            pass
        return None
Exemple #22
0
class Cluster(TimeStampedModel):
    """
    Represents an opinion group.
    """

    clusterization = models.ForeignKey("Clusterization",
                                       on_delete=models.CASCADE,
                                       related_name="clusters")
    name = models.CharField(_("Name"), max_length=64)
    description = models.TextField(
        _("Description"),
        blank=True,
        help_text=_("How was this cluster conceived?"))
    users = models.ManyToManyField(get_user_model(),
                                   related_name="clusters",
                                   blank=True)
    stereotypes = models.ManyToManyField("Stereotype", related_name="clusters")
    conversation = delegate_to("clusterization")
    comments = delegate_to("clusterization")
    objects = ClusterManager()

    @property
    def votes(self):
        return self.clusterization.votes.filter(author__in=self.users.all())

    @property
    def stereotype_votes(self):
        return self.clusterization.stereotype_votes.filter(
            author__in=self.stereotypes.all())

    n_votes = lazy(this.votes.count())
    n_users = lazy(this.users.count())
    n_stereotypes = lazy(this.stereotypes.count())
    n_stereotype_votes = lazy(this.n_stereotype_votes.count())

    def __str__(self):
        msg = _('{name} ("{conversation}" conversation, {n} users)')
        return msg.format(name=self.name,
                          conversation=self.conversation,
                          n=self.users.count())

    def get_absolute_url(self):
        args = {"conversation": self.conversation, "cluster": self}
        return reverse("cluster:detail", kwargs=args)

    def mean_stereotype(self):
        """
        Return the mean stereotype for cluster.
        """
        stereotypes = self.stereotypes.all()
        votes = StereotypeVote.objects.filter(
            author__in=Subquery(stereotypes.values("id"))).values_list(
                "comment", "choice")
        df = pd.DataFrame(list(votes), columns=["comment", "choice"])
        if len(df) == 0:
            return pd.DataFrame([], columns=["choice"])
        else:
            return df.pivot_table("choice", index="comment", aggfunc="mean")

    def comments_statistics_summary_dataframe(self, normalization=1.0):
        """
        Like comments.statistics_summary_dataframe(), but restricts votes to
        users in the current clusters.
        """
        kwargs = dict(normalization=normalization, votes=self.votes)
        return self.comments.statistics_summary_dataframe(**kwargs)

    def separate_comments(self, sort=True):
        """
        Separate comments into a pair for comments that cluster agrees to and
        comments that cluster disagree.
        """
        tol = 1e-6
        table = self.votes.votes_table()

        n_agree = (table > 0).sum()
        n_disagree = (table < 0).sum()
        total = n_agree + n_disagree + (table == 0).sum() + tol

        d_agree = dict(
            ((n_agree[n_agree >= n_disagree] + tol) / total).dropna().items())
        d_disagree = dict(((n_disagree[n_disagree > n_agree] + tol) /
                           total).dropna().items())

        agree = []
        disagree = []
        for comment in Comment.objects.filter(id__in=d_agree):
            # It would accept 0% agreement since we test sfor n_agree >= n_disagree
            # We must prevent cases with 0 agrees (>= 0 disagrees) to enter in
            # the calculation
            n_agree = d_agree[comment.id]
            if n_agree:
                comment.agree = n_agree
                agree.append(comment)

        for comment in Comment.objects.filter(id__in=d_disagree):
            comment.disagree = d_disagree[comment.id]
            disagree.append(comment)

        if sort:
            agree.sort(key=lambda c: c.agree, reverse=True)
            disagree.sort(key=lambda c: c.disagree, reverse=True)

        return agree, disagree
Exemple #23
0
class ClinicalObserverModel(ClinicalModel, ABC):
    """
    A clinical model that derives all its dynamic variables from simple
    transformations from the infected population.

    Observer models do not control directly their own dynamics and simply
    delegate time evolution to the corresponding infectious model.
    """

    DATA_COLUMNS = ()

    # Delegates (times and dates)
    times = sk.delegate_to("infection_model")
    dates = sk.delegate_to("infection_model")
    iter = sk.delegate_to("infection_model")
    time = sk.delegate_to("infection_model")
    state = sk.delegate_to("infection_model")

    # Methods delegates
    set_ic = sk.delegate_to("infection_model")
    set_data = sk.delegate_to("infection_model")
    set_cases = sk.delegate_to("infection_model")
    run = sk.delegate_to("infection_model")
    run_until = sk.delegate_to("infection_model")
    trim_dates = sk.delegate_to("infection_model")
    reset = sk.delegate_to("infection_model")
    epidemic_model_name = sk.delegate_to("infection_model")

    def __init__(self, model, params=None, *, date=None, **kwargs):
        if not (date is None or date == model.date):
            raise ValueError("cannot set date")
        super().__init__(model, params, date=model.date, **kwargs)

    def _initial_state(self):
        return ()

    def run_to_fill(self, data, times):
        raise RuntimeError
Exemple #24
0
class Manager(models.Manager, BaseManager):
    """
    Default Boogie manager.
    """

    # Delegate to queryset
    _queryset_class = QuerySet
    to_pivot_table = delegate_to("_queryset")
    to_timeseries = delegate_to("_queryset")
    to_dataframe = delegate_to("_queryset")
    id_dict = delegate_to("_queryset")
    bulk_upsert = delegate_to("_queryset")
    sync = delegate_to("_queryset")
    upsert = delegate_to("_queryset")
    get_or_none = delegate_to("_queryset")
    single = delegate_to("_queryset")
    bulk_update = delegate_to("_queryset")
    index = delegate_to("_queryset")
    dataframe = delegate_to("_queryset")
    select_columns = delegate_to("_queryset")
    update_item = delegate_to("_queryset")
    update_from_dataframe = delegate_to("_queryset")
    pivot_table = delegate_to("_queryset")
    extend_dataframe = delegate_to("_queryset")
    head = delegate_to("_queryset")
    tail = delegate_to("_queryset")

    def __getitem__(self, item):
        return self.get_queryset().__getitem__(item)

    def __setitem__(self, item, value):
        return self.get_queryset().__setitem__(item, value)

    def __getattr__(self, attr):
        value = get_manager_attribute(self, attr)
        if value is NotImplemented:
            raise AttributeError(attr)
        return value

    def get_queryset(self):
        return self._queryset_class(model=self.model, using=self._db, hints=self._hints)

    _queryset = property(get_queryset)

    def new(self, *args, **kwargs):
        """
        Create a new model instance, without saving it to the database.
        """
        return self.model(*args, **kwargs)
Exemple #25
0
class UserProgress(ProgressBase):
    """
    Tracks global user evolution.
    """

    user = models.OneToOneField(settings.AUTH_USER_MODEL,
                                related_name="progress",
                                on_delete=models.CASCADE)
    commenter_level = models.EnumField(CommenterLevel,
                                       default=CommenterLevel.NONE)
    max_commenter_level = models.EnumField(CommenterLevel,
                                           default=CommenterLevel.NONE)
    host_level = models.EnumField(HostLevel, default=HostLevel.NONE)
    max_host_level = models.EnumField(HostLevel, default=HostLevel.NONE)
    profile_level = models.EnumField(ProfileLevel, default=ProfileLevel.NONE)
    max_profile_level = models.EnumField(ProfileLevel,
                                         default=ProfileLevel.NONE)

    # Non de-normalized fields: conversations app
    n_conversations = delegate_to("user")
    n_comments = delegate_to("user")
    n_rejected_comments = delegate_to("user")
    n_votes = delegate_to("user")

    # Gamification app
    n_endorsements = delegate_to("user")
    n_given_opinion_bridge_powers = delegate_to("user")
    n_given_minority_activist_powers = delegate_to("user")

    # Level of conversations
    def _level_checker(*args):
        *_, lvl = args  # ugly trick to make static analysis happy
        return lazy(lambda p: p.user.conversations.filter(
            progress__conversation_level=lvl).count())

    n_conversation_lvl_1 = _level_checker(ConversationLevel.ALIVE)
    n_conversation_lvl_2 = _level_checker(ConversationLevel.ENGAGING)
    n_conversation_lvl_3 = _level_checker(ConversationLevel.NOTEWORTHY)
    n_conversation_lvl_4 = _level_checker(ConversationLevel.ENGAGING)
    del _level_checker

    # Aggregators
    total_conversation_score = delegate_to("user")
    total_participation_score = delegate_to("user")

    # Signals
    level_achievement_signal = lazy(lambda _: signals.user_level_achieved,
                                    shared=True)

    n_trophies = 0

    class Meta:
        verbose_name_plural = _("User progress list")

    def __str__(self):
        return __("Progress for {user}").format(user=self.user)

    def compute_score(self):
        """
        Compute the total number of points earned by user.

        User score is based on the following rules:
            * Vote: 10 points
            * Accepted comment: 30 points
            * Rejected comment: -30 points
            * Endorsement received: 15 points
            * Opinion bridge: 50 points
            * Minority activist: 50 points
            * Plus the total score of created conversations.

        Returns:
            Total score (int)
        """
        return (self.score_bias + 10 * self.n_votes + 30 * self.n_comments -
                30 * self.n_rejected_comments + 15 * self.n_endorsements +
                50 * self.n_given_opinion_bridge_powers +
                50 * self.n_given_minority_activist_powers +
                self.total_conversation_score)
Exemple #26
0
class Profile(models.Model):
    """
    User profile
    """
    user = models.OneToOneField(User,
                                on_delete=models.CASCADE,
                                related_name='raw_profile')
    race = EnumField(Race, _('Race'), default=Race.UNDECLARED)
    gender = EnumField(Gender, _('Gender identity'), default=Gender.UNDECLARED)
    gender_other = models.CharField(_('User provided gender'),
                                    max_length=50,
                                    blank=True)
    age = models.IntegerField(_('Age'), null=True, blank=True)
    country = models.CharField(_('Country'), blank=True, max_length=50)
    state = models.CharField(_('State'), blank=True, max_length=140)
    city = models.CharField(_('City'), blank=True, max_length=140)
    biography = models.TextField(_('Biography'), blank=True)
    occupation = models.CharField(_('Occupation'), blank=True, max_length=50)
    political_activity = models.TextField(_('Political activity'), blank=True)
    image = models.ImageField(_('Image'),
                              blank=True,
                              null=True,
                              upload_to='profile_images')

    name = delegate_to('user')
    username = delegate_to('user')
    email = delegate_to('user')
    is_active = delegate_to('user')
    is_staff = delegate_to('user')
    is_superuser = delegate_to('user')

    class Meta:
        ordering = ['user__username']

    def __str__(self):
        return __('{name}\'s profile').format(name=self.user.name)

    def __getattr__(self, attr):
        try:
            user = self.user
        except User.DoesNotExist:
            raise AttributeError(attr)
        return getattr(user, attr)

    @property
    def gender_description(self):
        if self.gender != Gender.UNDECLARED:
            return self.gender.description
        return self.gender_other

    @property
    def image_url(self):
        try:
            return self.image.url
        except ValueError:
            for account in SocialAccount.objects.filter(user_id=self.id):
                picture = account.get_avatar_url()
                if picture:
                    return picture
            return avatar_fallback()

    @property
    def has_image(self):
        return self.image or SocialAccount.objects.filter(user_id=self.id)

    @property
    def is_filled(self):
        fields = ('race', 'age', 'country', 'state', 'city', 'biography',
                  'occupation', 'political_activity', 'has_image',
                  'gender_description')
        print([getattr(self, field) for field in fields])
        return bool(all(getattr(self, field) for field in fields))

    def get_absolute_url(self):
        return reverse('user-detail', kwargs={'pk': self.id})

    def profile_fields(self, user_fields=False):
        """
        Return a list of tuples of (field_description, field_value) for all
        registered profile fields.
        """

        fields = [
            'city', 'country', 'occupation', 'age', 'gender', 'race',
            'political_activity', 'biography'
        ]
        field_map = {field.name: field for field in self._meta.fields}
        result = []
        for field in fields:
            description = field_map[field].verbose_name
            getter = getattr(self, f'get_{field}_display',
                             lambda: getattr(self, field))
            result.append((description.capitalize(), getter()))
        if user_fields:
            result = [
                (_('E-mail'), self.user.email),
                *result,
            ]
        return result

    def statistics(self):
        """
        Return a dictionary with all profile statistics.
        """
        return dict(
            votes=self.user.votes.count(),
            comments=self.user.comments.count(),
            conversations=self.user.conversations.count(),
        )

    def badges(self):
        """
        Return all profile badges.
        """
        # FIXME remove this print
        print("See all badges")
        print(self.user.badges_earned.all())
        return self.user.badges_earned.all()

    def comments(self):
        """
        Return all profile comments.
        """
        # FIXME remove this print
        print("See all comments")
        print(self.user.comments.all())
        return self.user.comments.all()

    def role(self):
        """
        A human-friendly description of the user role in the platform.
        """
        if self.user.is_superuser:
            return _('Root')
        if self.user.is_staff:
            return _('Administrative user')
        return _('Regular user')
Exemple #27
0
class ParticipationProgress(ProgressBase):
    """
    Tracks user evolution in conversation.
    """

    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        related_name="participation_progresses",
        on_delete=models.CASCADE,
    )
    conversation = models.ForeignKey(
        "ej_conversations.Conversation",
        related_name="participation_progresses",
        on_delete=models.CASCADE,
    )
    voter_level = models.EnumField(VoterLevel, default=VoterLevel.NONE)
    max_voter_level = models.EnumField(VoterLevel, default=VoterLevel.NONE)
    is_owner = models.BooleanField(default=False)
    is_focused = models.BooleanField(default=False)

    # Non de-normalized fields: conversations
    is_favorite = lazy(
        lambda p: p.conversation.favorites.filter(user=p.user).exists())
    n_votes = lazy(lambda p: p.user.votes.filter(comment__conversation=p.
                                                 conversation).count())
    n_comments = lazy(
        lambda p: p.user.comments.filter(conversation=p.conversation).count())
    n_rejected_comments = lazy(lambda p: p.user.rejected_comments.filter(
        conversation=p.conversation).count())
    n_conversation_comments = delegate_to("conversation", name="n_comments")
    n_conversation_rejected_comments = delegate_to("conversation",
                                                   name="n_rejected_comments")
    votes_ratio = lazy(this.n_votes / (this.n_conversation_comments + 1e-50))

    # Gamification
    # n_endorsements = lazy(lambda p: Endorsement.objects.filter(comment__author=p.user).count())
    # n_given_opinion_bridge_powers = delegate_to('user')
    # n_given_minority_activist_powers = delegate_to('user')
    n_endorsements = 0
    n_given_opinion_bridge_powers = 0
    n_given_minority_activist_powers = 0

    # Leaderboard
    @lazy
    def n_conversation_scores(self):
        db = ParticipationProgress.objects
        return db.filter(conversation=self.conversation).count()

    @lazy
    def n_higher_scores(self):
        db = ParticipationProgress.objects
        return db.filter(conversation=self.conversation,
                         score__gt=self.score).count()

    n_lower_scores = lazy(this.n_conversation_scores - this.n_higher_scores)

    # Signals
    level_achievement_signal = lazy(
        lambda _: signals.participation_level_achieved, shared=True)

    def __str__(self):
        msg = __("Progress for user: {user} at {conversation}")
        return msg.format(user=self.user, conversation=self.conversation)

    def sync(self):
        self.is_owner = self.conversation.author == self.user

        # You cannot receive a focused achievement in your own conversation!
        if not self.is_owner:
            n_comments = self.conversation.n_comments
            self.is_focused = n_comments == self.n_votes >= 20

        return super().sync()

    def compute_score(self):
        """
        Compute the total number of points earned by user.

        User score is based on the following rules:
            * Vote: 10 points
            * Accepted comment: 30 points
            * Rejected comment: -30 points
            * Endorsement received: 15 points
            * Opinion bridge: 50 points
            * Minority activist: 50 points
            * Plus the total score of created conversations.
            * Got a focused badge: 50 points.

        Returns:
            Total score (int)
        """
        return (self.score_bias + 10 * self.n_votes + 30 * self.n_comments -
                30 * self.n_rejected_comments + 15 * self.n_endorsements +
                50 * self.n_given_opinion_bridge_powers +
                50 * self.n_given_minority_activist_powers +
                50 * self.is_focused)
Exemple #28
0
    )
    comment = models.ForeignKey(
        'ej_conversations.Comment',
        verbose_name=_('Comment'),
        related_name='stereotype_votes',
        on_delete=models.CASCADE,
    )
    choice = EnumField(Choice, _('Choice'))
    stereotype = alias('author')
    objects = BoogieManager()

    def __str__(self):
        return f'StereotypeVote({self.author}, value={self.choice})'


#
# Auxiliary methods
#
def get_clusterization(conversation):
    try:
        return conversation.clusterization
    except Clusterization.DoesNotExist:
        mgm, _ = Clusterization.objects.get_or_create(
            conversation=conversation)
        return mgm


Conversation.get_clusterization = get_clusterization
Conversation._clusterization = lazy(get_clusterization)
Conversation.clusters = delegate_to('_clusterization')
Exemple #29
0
class Run:
    """
    This class makes an interface between Models and Solvers.
    """

    solver: Solver
    model: Model

    t = delegate_to('solver')
    state = delegate_to('solver', name='y')
    values = property(lambda self: self._values[:self._idx].T)
    times = property(lambda self: self._times[:self._idx])
    _meta: Meta = delegate_to('model')

    @classmethod
    def from_solver(cls, solver_class, model, **kwargs):
        """
        Create solver from solver class and prepare run method.
        """
        meta = model._meta
        solver = solver_class(meta.diff_fn, y0=meta.y0, t0=meta.t0)
        return cls(solver, model, **kwargs)

    def __init__(self, solver, model, alloc_steps=1, name=None):
        meta = model._meta
        self.name = name
        self.model = model
        self.solver = solver
        self._idx = 1
        self._times = np.ones(alloc_steps, dtype='float64') * float('nan')
        self._values = np.zeros((alloc_steps, meta.vars_size),
                                dtype=model.dtype)
        # self._aux = np.zeros((alloc_steps, meta.aux_size), dtype=model.dtype)
        self._attributes = _make_attributes(self)
        self._times[0] = self.t
        self._values[0] = self.solver.y
        self.solver.callback = self._callback

    def __getattr__(self, attr):
        try:
            attr_getter = self._attributes[attr]
        except KeyError:
            raise AttributeError(attr)
        else:
            return attr_getter()

    def __repr__(self):
        name = f'Run:{self.name}' if self.name else 'Run'
        items = self.var_values().items()
        var_data = ('%s=%r' % item for item in items)
        var_data = ', '.join((f't={self.t}', *var_data))
        return f'<{name} {var_data}>'

    def _callback(self, t, y):
        self._values[self._idx] = y
        self._times[self._idx] = t
        self._idx += 1

    def var_values(self):
        """
        Return a dictionary with the variable values for the current state.
        """
        return self._meta.unvectorize_vars(self.state)

    def run(self, *args, t0=None, tf=None, steps=None, **kwargs):
        """
        Advance simulation in the given time frame.

        Has a similar interface as :meth:`Model.run`.
        """
        if kwargs:
            self.update_ic(**kwargs)

        # Compute the time points
        meta = self.model._meta
        steps = coalesce(steps, meta.steps, 100)
        t0 = coalesce(t0, self.t)
        tf = coalesce(tf, self.t + meta.tf - meta.t0)

        times = times_from_args(*args, start=t0, stop=tf, step=steps)
        self.simulate(times)
        return self

    def simulate(self, times, y0=None):
        """
        Run simulation over the given time points.
        """

        # Fill missing times
        times = np.asarray(times, dtype=float)
        if times.ndim == 0:
            times = times.reshape([1])

        n, m = self._values.shape
        missing = len(times) - (n - self._idx)
        if missing > 0:
            self._values = np.vstack([self._values, np.zeros((missing, m))])
            self._times = np.concatenate([self._times, np.zeros(missing)])

        # Set initial time and value
        if y0 is not None:
            self.solver.y[:] = y0
            self._values[self._idx] = y0
        self._times[self._idx] = times[0]

        self.solver.simulate(times)
        return self

    def step(self, dt):
        """
        Run a single step by
        """
        self.solver.step(dt)
        return self

    def update_ic(self, t0=None, *args, **kwargs):
        """
        Update initial conditions from model. User can override values by
        passing a dictionary positional argument or passing variables as
        positional arguments.
        """
        if t0 is not None:
            self.solver.t = t0
        ic = self.model.var_values(*args, **kwargs)
        self.state[:] = self.model.var_vector(ic)
        self.values[self._idx] = self.y
Exemple #30
0
class Clinical:
    """
    Implements the ``.clinical`` attribute of models.
    """

    __slots__ = ("_model",)

    _default = sk.lazy(lambda x: x())
    info = sk.delegate_to("_default")
    params = sk.delegate_to("_default")
    summary = sk.delegate_to("_default")

    @property
    def _models(self):
        from .. import clinical_models

        return clinical_models

    def __init__(self, model):
        self._model = model

    def __call__(self, *args, **kwargs):
        params = {**self._model.clinical_params, **kwargs}
        if args:
            (cls,) = args
            if isinstance(cls, str):
                return self.clinical_model(cls, **params)
        else:
            cls = self._model.clinical_model or self._models.CrudeFR
        return self.clinical_model(cls, **params)

    def __getitem__(self, item):
        return self._default[item]

    def clinical_model(self, cls, **kwargs) -> "ClinicalModel":
        """
        Create a clinical model from model infectious model instance.
        """
        if isinstance(cls, str):
            method = getattr(self, cls.lower() + "_model", None)
            if method is None:
                raise ValueError(f"invalid model: {cls!r}")
            return method(**kwargs)

        return cls(self._model, **kwargs)

    def crude_model(self, **kwargs) -> "CrudeFR":
        """
        Create a clinical model from model infectious model instance.
        """
        cls = self._models.CrudeFR
        return self.clinical_model(cls, **kwargs)

    def delay_model(self, **kwargs) -> "HospitalizationWithDelay":
        """
        A simple clinical model in which hospitalization occurs with some
        fixed delay.
        """
        cls = self._models.HospitalizationWithDelay
        return self.clinical_model(cls, **kwargs)

    def overflow_model(self, **kwargs) -> "HospitalizationWithOverflow":
        """
        A clinical model that considers the overflow of a healthcare system
        in order to compute the total death toll.
        """
        cls = self._models.HospitalizationWithOverflow
        return self.clinical_model(cls, **kwargs)