From 85881698fd9811d599c291857cd80b8613f9d3af Mon Sep 17 00:00:00 2001 From: owocki Date: Wed, 23 Sep 2020 22:58:30 -0600 Subject: [PATCH] n:n matching rounds <> grants --- app/grants/clr.py | 20 +++-- .../migrations/0088_auto_20200924_0425.py | 40 +++++++++ app/grants/models.py | 86 ++++++++++++++++--- app/grants/utils.py | 3 +- app/grants/views.py | 4 +- 5 files changed, 126 insertions(+), 27 deletions(-) create mode 100644 app/grants/migrations/0088_auto_20200924_0425.py diff --git a/app/grants/clr.py b/app/grants/clr.py index 73ea16307a4..15f4b36ed9f 100644 --- a/app/grants/clr.py +++ b/app/grants/clr.py @@ -468,37 +468,39 @@ def predict_clr(save_to_db=False, from_date=None, clr_round=None, network='mainn if save_to_db: _grant = Grant.objects.get(pk=grant.pk) - _grant.clr_prediction_curve = list(zip(potential_donations, potential_clr)) - base = _grant.clr_prediction_curve[0][1] + clr_prediction_curve = list(zip(potential_donations, potential_clr)) + base = clr_prediction_curve[0][1] _grant.last_clr_calc_date = timezone.now() _grant.next_clr_calc_date = timezone.now() + timezone.timedelta(minutes=20) - can_estimate = True if base or _grant.clr_prediction_curve[1][1] or _grant.clr_prediction_curve[2][1] or _grant.clr_prediction_curve[3][1] else False + can_estimate = True if base or clr_prediction_curve[1][1] or clr_prediction_curve[2][1] or clr_prediction_curve[3][1] else False if can_estimate : - _grant.clr_prediction_curve = [[ele[0], ele[1], ele[1] - base] for ele in _grant.clr_prediction_curve ] + clr_prediction_curve = [[ele[0], ele[1], ele[1] - base] for ele in clr_prediction_curve ] else: - _grant.clr_prediction_curve = [[0.0, 0.0, 0.0] for x in range(0, 6)] + clr_prediction_curve = [[0.0, 0.0, 0.0] for x in range(0, 6)] JSONStore.objects.create( created_on=from_date, view='clr_contribution', key=f'{grant.id}', - data=_grant.clr_prediction_curve, + data=clr_prediction_curve, ) + clr_round.record_clr_prediction_curve(_grant, clr_prediction_curve) + try: - if _grant.clr_prediction_curve[0][1]: + if clr_prediction_curve[0][1]: Stat.objects.create( created_on=from_date, key=_grant.title[0:43] + "_match", - val=_grant.clr_prediction_curve[0][1], + val=clr_prediction_curve[0][1], ) max_twitter_followers = max(_grant.twitter_handle_1_follower_count, _grant.twitter_handle_2_follower_count) if max_twitter_followers: Stat.objects.create( created_on=from_date, key=_grant.title[0:43] + "_admt1", - val=int(100 * _grant.clr_prediction_curve[0][1]/max_twitter_followers), + val=int(100 * clr_prediction_curve[0][1]/max_twitter_followers), ) if _grant.positive_round_contributor_count: diff --git a/app/grants/migrations/0088_auto_20200924_0425.py b/app/grants/migrations/0088_auto_20200924_0425.py new file mode 100644 index 00000000000..ec42924faa3 --- /dev/null +++ b/app/grants/migrations/0088_auto_20200924_0425.py @@ -0,0 +1,40 @@ +# Generated by Django 2.2.4 on 2020-09-24 04:25 + +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion +import economy.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('grants', '0087_grantstat'), + ] + + operations = [ + migrations.RemoveField( + model_name='grant', + name='backup_clr_prediction_curve', + ), + migrations.AlterField( + model_name='grantstat', + name='grant', + field=models.ForeignKey(help_text='Grant to add stats for this grant', on_delete=django.db.models.deletion.CASCADE, related_name='stats', to='grants.Grant'), + ), + migrations.CreateModel( + name='GrantCLRCalculation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_on', models.DateTimeField(db_index=True, default=economy.models.get_time)), + ('modified_on', models.DateTimeField(default=economy.models.get_time)), + ('latest', models.BooleanField(db_index=True, default=False, help_text='Is this calc the latest?')), + ('clr_prediction_curve', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(), size=2), blank=True, default=list, help_text='5 point curve to predict CLR donations.', size=None)), + ('grant', models.ForeignKey(help_text='The grant', on_delete=django.db.models.deletion.CASCADE, related_name='clr_calculations', to='grants.Grant')), + ('grantclr', models.ForeignKey(help_text='The grant CLR Round', on_delete=django.db.models.deletion.CASCADE, related_name='clr_calculations', to='grants.GrantCLR')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/app/grants/models.py b/app/grants/models.py index a8a6a1eb11f..a914924c5ed 100644 --- a/app/grants/models.py +++ b/app/grants/models.py @@ -165,6 +165,18 @@ def __str__(self): def grants(self): return Grant.objects.filter(**self.grant_filters) + def record_clr_prediction_curve(self, grant, clr_prediction_curve): + for obj in self.clr_calculations.filter(grant=grant): + obj.latest = False + obj.save() + + GrantCLRCalculation.objects.create( + grantclr=self, + grant=grant, + clr_prediction_curve=clr_prediction_curve, + latest=True, + ) + class Grant(SuperModel): """Define the structure of a Grant.""" @@ -316,18 +328,6 @@ class Meta: max_digits=20, help_text=_('The fundingamount across all rounds with phantom funding'), ) - # TODO-CROSS-GRANT: [{round: fk1, value: time}] - clr_prediction_curve = ArrayField( - ArrayField( - models.FloatField(), - size=2, - ), blank=True, default=list, help_text=_('5 point curve to predict CLR donations.')) - # TODO: REMOVE - backup_clr_prediction_curve = ArrayField( - ArrayField( - models.FloatField(), - size=2, - ), blank=True, default=list, help_text=_('backup 5 point curve to predict CLR donations - used to store a secondary backup of the clr prediction curve, in the case a new identity mechanism is used')) activeSubscriptions = ArrayField(models.CharField(max_length=200), blank=True, default=list) hidden = models.BooleanField(default=False, help_text=_('Hide the grant from the /grants page?'), db_index=True) weighted_shuffle = models.PositiveIntegerField(blank=True, null=True) @@ -375,6 +375,12 @@ class Meta: funding_info = models.CharField(default='', blank=True, null=True, max_length=255, help_text=_('Is this grant VC funded?')) + clr_prediction_curve = ArrayField( + ArrayField( + models.FloatField(), + size=2, + ), blank=True, default=list, help_text=_('5 point curve to predict CLR donations.')) + weighted_risk_score = models.DecimalField( default=0, decimal_places=4, @@ -396,6 +402,32 @@ class Meta: # Grant Query Set used as manager. objects = GrantQuerySet.as_manager() + def __str__(self): + """Return the string representation of a Grant.""" + return f"id: {self.pk}, active: {self.active}, title: {self.title}, type: {self.grant_type}" + + @property + def calc_clr_round_nums(self): + roudn_nums = [ele for ele in self.in_active_clrs.values_list('round_num', flat=True)] + return ", ".join(roudn_nums) + + @property + def calc_clr_prediction_curve(self): + # [amount_donated, match amount, bonus_from_match_amount ], etc.. + # [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0] + _clr_prediction_curve = [] + for insert_clr_calc in self.clr_calculations.filter(latest=True).order_by('-created_on'): + insert_clr_calc = insert_clr_calc.clr_prediction_curve + if not _clr_prediction_curve: + _clr_prediction_curve = insert_clr_calc + else: + for j in [1,2]: + for i in [0,1,2,3,4,5]: + # add the 1 and 2 index of each clr prediction cuve + _clr_prediction_curve[i][j] += insert_clr_calc[i][j] + return _clr_prediction_curve + + def __str__(self): """Return the string representation of a Grant.""" return f"id: {self.pk}, active: {self.active}, title: {self.title}, type: {self.grant_type}" @@ -1087,8 +1119,18 @@ def successful_contribution(self, tx_id): @receiver(pre_save, sender=Grant, dispatch_uid="psave_grant") def psave_grant(sender, instance, **kwargs): + # TODO: move to grant task + instance.clr_prediction_curve = instance.calc_clr_prediction_curve + instance.clr_round_num = instance.calc_clr_round_nums + + # todo: move to grant task + active_clr_rounds = GrantCLR.objects.filter(is_active=True) + for clr_round in active_clr_rounds: + if clr_round.grants.filter(pk=instance.pk).exists(): + instance.in_active_clrs.add(clr_round) + + from grants.tasks import update_grant_metadata if instance.modified_on < (timezone.now() - timezone.timedelta(minutes=5)): - from grants.tasks import update_grant_metadata update_grant_metadata.delay(instance.pk) class DonationQuerySet(models.QuerySet): @@ -1824,3 +1866,21 @@ class GrantStat(SuperModel): def __str__(self): return f'{self.snapshot_type} {self.created_on} for {self.grant.title}' + + +class GrantCLRCalculation(SuperModel): + + latest = models.BooleanField(default=False, db_index=True, help_text="Is this calc the latest?") + grant = models.ForeignKey(Grant, on_delete=models.CASCADE, related_name='clr_calculations', + help_text=_('The grant')) + grantclr = models.ForeignKey(GrantCLR, on_delete=models.CASCADE, related_name='clr_calculations', + help_text=_('The grant CLR Round')) + + clr_prediction_curve = ArrayField( + ArrayField( + models.FloatField(), + size=2, + ), blank=True, default=list, help_text=_('5 point curve to predict CLR donations.')) + + def __str__(self): + return f'{self.created_on} for g:{self.grant.pk} / gclr:{self.grantclr.pk} : {self.clr_prediction_curve}' diff --git a/app/grants/utils.py b/app/grants/utils.py index 7229426375b..5752e0c7795 100644 --- a/app/grants/utils.py +++ b/app/grants/utils.py @@ -161,7 +161,6 @@ def add_grant_to_active_clrs(grant): active_clr_rounds = GrantCLR.objects.filter(is_active=True) for clr_round in active_clr_rounds: - grants_in_clr = Grant.objects.filter(**clr_round.grant_filters) - if grants_in_clr.filter(pk=grant.pk).count(): + if clr_round.grants.filter(pk=grant.pk).exists(): grant.in_active_clrs.add(clr_round) grant.save() diff --git a/app/grants/views.py b/app/grants/views.py index c3ef07e8d3f..b4d4b592cb0 100644 --- a/app/grants/views.py +++ b/app/grants/views.py @@ -1136,10 +1136,8 @@ def grant_details(request, grant_id, grant_slug): if clr_round: is_clr_active = True - clr_round_num = clr_round.round_num else: is_clr_active = False - clr_round_num = 'LAST' is_clr_active = True if clr_round else False title = grant.title + " | Grants" @@ -1169,7 +1167,7 @@ def grant_details(request, grant_id, grant_slug): 'activity_count': activity_count, 'contributors': contributors, 'clr_active': is_clr_active, - 'round_num': clr_round_num, + 'round_num': grant.clr_round_num, 'is_team_member': is_team_member, 'voucher_fundings': voucher_fundings, 'is_unsubscribed_from_updates_from_this_grant': is_unsubscribed_from_updates_from_this_grant,