diff --git a/api/account/test/conftest.py b/api/account/test/conftest.py index 24bb0d93d..1e5a43c4d 100644 --- a/api/account/test/conftest.py +++ b/api/account/test/conftest.py @@ -1,7 +1,19 @@ +""" +This module contains pytest fixtures and configuration for the account API tests. + +It sets up common test data, mocks, and other utilities used across multiple test files +in the account API test suite. +""" + # pylint: disable=unused-import +import pytest + +from registry.weight_models import WeightConfiguration, WeightConfigurationItem +from scorer.settings.gitcoin_passport_weights import GITCOIN_PASSPORT_WEIGHTS from scorer.test.conftest import ( access_token, scorer_account, scorer_community, scorer_user, + weight_config, ) diff --git a/api/account/test/test_community.py b/api/account/test/test_community.py index 01f8fd5cd..a5fcee46d 100644 --- a/api/account/test/test_community.py +++ b/api/account/test/test_community.py @@ -3,13 +3,16 @@ from typing import cast import pytest -from account.models import Account, Community from django.contrib.auth import get_user_model from django.contrib.auth.models import UserManager from django.db.utils import IntegrityError from django.test import Client, TestCase from ninja_jwt.schema import RefreshToken +from account.models import Account, Community +from registry.weight_models import WeightConfiguration, WeightConfigurationItem +from scorer.settings.gitcoin_passport_weights import GITCOIN_PASSPORT_WEIGHTS + # Avoids type issues in standard django models user_manager = cast(UserManager, get_user_model().objects) @@ -40,6 +43,20 @@ def setUp(self): user=self.user2, defaults={"address": "0x0"} ) + config = WeightConfiguration.objects.create( + version="v1", + threshold=5.0, + active=True, + description="Test", + ) + + for provider, weight in GITCOIN_PASSPORT_WEIGHTS.items(): + WeightConfigurationItem.objects.create( + weight_configuration=config, + provider=provider, + weight=float(weight), + ) + def test_create_community(self): """Test that creation of a community works and that attributes are saved correctly""" client = Client() diff --git a/api/account/test/test_scorer.py b/api/account/test/test_scorer.py index acc3d40a3..fd3cf3955 100644 --- a/api/account/test/test_scorer.py +++ b/api/account/test/test_scorer.py @@ -1,12 +1,15 @@ import json import pytest -from account.models import Community from django.conf import settings from django.test import Client -from scorer_weighted.models import Scorer, get_default_threshold from web3 import Web3 +from account.models import Community +from registry.weight_models import WeightConfiguration, WeightConfigurationItem +from scorer.settings.gitcoin_passport_weights import GITCOIN_PASSPORT_WEIGHTS +from scorer_weighted.models import Scorer, get_default_threshold + web3 = Web3() web3.eth.account.enable_unaudited_hdwallet_features() diff --git a/api/aws_lambdas/passport/tests/conftest.py b/api/aws_lambdas/passport/tests/conftest.py index 35462aad2..38e565e16 100644 --- a/api/aws_lambdas/passport/tests/conftest.py +++ b/api/aws_lambdas/passport/tests/conftest.py @@ -5,4 +5,5 @@ scorer_community, scorer_community_with_binary_scorer, scorer_user, + weight_config, ) diff --git a/api/aws_lambdas/scorer_api_passport/tests/conftest.py b/api/aws_lambdas/scorer_api_passport/tests/conftest.py index 41d948e17..6a0b149ed 100644 --- a/api/aws_lambdas/scorer_api_passport/tests/conftest.py +++ b/api/aws_lambdas/scorer_api_passport/tests/conftest.py @@ -1,10 +1,14 @@ import pytest + +from registry.weight_models import WeightConfiguration, WeightConfigurationItem +from scorer.settings.gitcoin_passport_weights import GITCOIN_PASSPORT_WEIGHTS from scorer.test.conftest import ( passport_holder_addresses, scorer_account, scorer_api_key, scorer_community_with_binary_scorer, scorer_user, + weight_config, ) from .helpers import address diff --git a/api/aws_lambdas/scorer_api_passport/tests/test_bulk_stamp_lambdas.py b/api/aws_lambdas/scorer_api_passport/tests/test_bulk_stamp_lambdas.py index f2e583347..ee8498cee 100644 --- a/api/aws_lambdas/scorer_api_passport/tests/test_bulk_stamp_lambdas.py +++ b/api/aws_lambdas/scorer_api_passport/tests/test_bulk_stamp_lambdas.py @@ -1,9 +1,10 @@ import json import pytest +from django.conf import settings + from aws_lambdas.scorer_api_passport.v1.stamps import bulk_DELETE, bulk_PATCH, bulk_POST from ceramic_cache.models import CeramicCache -from django.conf import settings from .helpers import MockContext, address, good_stamp, headers @@ -39,7 +40,7 @@ def test_patch( assert response["statusCode"] == 200 assert body["stamps"][0]["provider"] == "Google" - assert int(body["score"]["evidence"]["rawScore"]) > 0 + assert body["score"]["evidence"]["rawScore"] > 0 assert body["score"]["status"] == "DONE" assert body["success"] is True @@ -72,7 +73,7 @@ def test_delete( assert response["statusCode"] == 200 assert len(body["stamps"]) == 0 - assert int(body["score"]["evidence"]["rawScore"]) == 0 + assert body["score"]["evidence"]["rawScore"] == 0 assert body["score"]["status"] == "DONE" assert body["success"] is True @@ -105,6 +106,6 @@ def test_post( assert response["statusCode"] == 200 assert body["stamps"][0]["provider"] == "Google" - assert int(body["score"]["evidence"]["rawScore"]) > 0 + assert body["score"]["evidence"]["rawScore"] > 0 assert body["score"]["status"] == "DONE" assert body["success"] is True diff --git a/api/aws_lambdas/submit_passport/tests/conftest.py b/api/aws_lambdas/submit_passport/tests/conftest.py index d3a23f9ff..8cb569942 100644 --- a/api/aws_lambdas/submit_passport/tests/conftest.py +++ b/api/aws_lambdas/submit_passport/tests/conftest.py @@ -4,4 +4,5 @@ scorer_api_key, scorer_community_with_binary_scorer, scorer_user, + weight_config, ) diff --git a/api/aws_lambdas/submit_passport/tests/test_submit_passport_lambda.py b/api/aws_lambdas/submit_passport/tests/test_submit_passport_lambda.py index 919aba0d9..2b695220e 100644 --- a/api/aws_lambdas/submit_passport/tests/test_submit_passport_lambda.py +++ b/api/aws_lambdas/submit_passport/tests/test_submit_passport_lambda.py @@ -3,11 +3,11 @@ from copy import deepcopy import pytest -from account.models import AccountAPIKeyAnalytics -from registry.test.test_passport_submission import mock_passport +from account.models import AccountAPIKeyAnalytics from aws_lambdas.scorer_api_passport.tests.helpers import MockContext from aws_lambdas.scorer_api_passport.utils import strip_event +from registry.test.test_passport_submission import mock_passport from ..submit_passport import _handler @@ -110,14 +110,15 @@ def test_successful_authentication( assert body["address"] == address assert body["score"] == "0" assert body["status"] == "DONE" + assert body["evidence"] == { "type": "ThresholdScoreCheck", "success": False, - "rawScore": 2, - "threshold": 75.0, + "rawScore": 0.9329999999999999, + "threshold": 20.0, } assert body["error"] is None - assert body["stamp_scores"] == {"Ens": 1.0, "Google": 1.0} + assert body["stamp_scores"] == {"Ens": 0.408, "Google": 0.525} # We just check that something != None was recorded for the last timestamp assert body["last_score_timestamp"] is not None @@ -158,11 +159,11 @@ def test_successful_authentication_and_base64_encoded_body( assert body["evidence"] == { "type": "ThresholdScoreCheck", "success": False, - "rawScore": 2, - "threshold": 75.0, + "rawScore": 0.9329999999999999, + "threshold": 20.0, } assert body["error"] is None - assert body["stamp_scores"] == {"Ens": 1.0, "Google": 1.0} + assert body["stamp_scores"] == {"Ens": 0.408, "Google": 0.525} # We just check that something != None was recorded for the last timestamp assert body["last_score_timestamp"] is not None diff --git a/api/aws_lambdas/tests/conftest.py b/api/aws_lambdas/tests/conftest.py index 50857a152..38e565e16 100644 --- a/api/aws_lambdas/tests/conftest.py +++ b/api/aws_lambdas/tests/conftest.py @@ -1,8 +1,9 @@ from scorer.test.conftest import ( + passport_holder_addresses, scorer_account, + scorer_api_key, scorer_community, scorer_community_with_binary_scorer, scorer_user, - scorer_api_key, - passport_holder_addresses, + weight_config, ) diff --git a/api/ceramic_cache/test/conftest.py b/api/ceramic_cache/test/conftest.py index 6f2a2808b..a0049a6e6 100644 --- a/api/ceramic_cache/test/conftest.py +++ b/api/ceramic_cache/test/conftest.py @@ -13,6 +13,7 @@ scorer_user, # noqa ui_scorer, # noqa verifiable_credential, # noqa + weight_config, # noqa ) diff --git a/api/ceramic_cache/test/test_cmd_scorer_dump_data.py b/api/ceramic_cache/test/test_cmd_scorer_dump_data.py index 23ded85dd..4631614cd 100644 --- a/api/ceramic_cache/test/test_cmd_scorer_dump_data.py +++ b/api/ceramic_cache/test/test_cmd_scorer_dump_data.py @@ -3,15 +3,18 @@ from datetime import datetime import pytest -from account.models import Community from django.core.management import call_command + +from account.models import Community from registry.models import Passport, Score from scorer_weighted.models import BinaryWeightedScorer, Scorer class TestGetStamps: @pytest.mark.django_db - def test_export_filtered_scored_for_scorer(self, scorer_account, mocker): + def test_export_filtered_scored_for_scorer( + self, scorer_account, mocker, weight_config + ): """Make sure that it is not possible to have duplicate stamps in the DB""" scorer = BinaryWeightedScorer.objects.create(type=Scorer.Type.WEIGHTED_BINARY) @@ -71,7 +74,6 @@ def upload_file(self, file_name, *args, **kwargs): "ceramic_cache.management.commands.scorer_dump_data.boto3.client", return_value=MockS3(), ): - call_command( "scorer_dump_data", *[], diff --git a/api/ceramic_cache/test/test_cmd_scorer_dump_data_parquet_for_oso.py b/api/ceramic_cache/test/test_cmd_scorer_dump_data_parquet_for_oso.py index 43987d0e2..f641649bf 100644 --- a/api/ceramic_cache/test/test_cmd_scorer_dump_data_parquet_for_oso.py +++ b/api/ceramic_cache/test/test_cmd_scorer_dump_data_parquet_for_oso.py @@ -14,7 +14,9 @@ class TestGetStamps: @pytest.mark.django_db - def test_export_filtered_scored_for_scorer(self, scorer_account, mocker): + def test_export_filtered_scored_for_scorer( + self, scorer_account, mocker, weight_config + ): """Make sure that it is not possible to have duplicate stamps in the DB""" scorer = BinaryWeightedScorer.objects.create(type=Scorer.Type.WEIGHTED_BINARY) diff --git a/api/ceramic_cache/test/test_get_score.py b/api/ceramic_cache/test/test_get_score.py index 711b205b9..398f8764c 100644 --- a/api/ceramic_cache/test/test_get_score.py +++ b/api/ceramic_cache/test/test_get_score.py @@ -1,11 +1,12 @@ -from datetime import datetime, timezone, timedelta +from datetime import datetime, timedelta, timezone +from decimal import Decimal import pytest -from ceramic_cache.models import CeramicCache -from registry.models import Passport, Score from django.test import Client + +from ceramic_cache.models import CeramicCache from registry.api.schema import DetailedScoreResponse -from decimal import Decimal +from registry.models import Passport, Score pytestmark = pytest.mark.django_db @@ -44,7 +45,7 @@ def test_get_score_when_no_stamps( "type": "ThresholdScoreCheck", "success": False, "rawScore": "0", - "threshold": "75.00000", + "threshold": "20.00000", }, "error": None, "stamp_scores": {}, diff --git a/api/ceramic_cache/test/test_weights.py b/api/ceramic_cache/test/test_weights.py index def94cae3..393fd3196 100644 --- a/api/ceramic_cache/test/test_weights.py +++ b/api/ceramic_cache/test/test_weights.py @@ -1,8 +1,10 @@ -from account.models import Community -from scorer_weighted.models import Scorer, BinaryWeightedScorer -from django.test import Client -from django.conf import settings import pytest +from django.conf import settings +from django.test import Client + +from account.models import Community +from registry.weight_models import WeightConfiguration, WeightConfigurationItem +from scorer_weighted.models import BinaryWeightedScorer, Scorer pytestmark = pytest.mark.django_db # noqa: F821 @@ -16,7 +18,20 @@ def test_get_weights( self, scorer_account, ): + config = WeightConfiguration.objects.create( + version="v1", + threshold=20.0, + active=True, + description="Test", + ) scorer_weights = {"provider-1": "0.5", "provider-2": "0.5"} + for provider, weight in scorer_weights.items(): + WeightConfigurationItem.objects.create( + weight_configuration=config, + provider=provider, + weight=float(weight), + ) + scorer = BinaryWeightedScorer.objects.create( type=Scorer.Type.WEIGHTED_BINARY, weights=scorer_weights ) diff --git a/api/passport_admin/tests/conftest.py b/api/passport_admin/tests/conftest.py index 4d0e0a771..2132e785b 100644 --- a/api/passport_admin/tests/conftest.py +++ b/api/passport_admin/tests/conftest.py @@ -1,9 +1,10 @@ from scorer.test.conftest import ( - api_key, - sample_address, - sample_provider, - sample_token, - scorer_account, - scorer_user, - verifiable_credential, + api_key, # noqa + sample_address, # noqa + sample_provider, # noqa + sample_token, # noqa + scorer_account, # noqa + scorer_user, # noqa + verifiable_credential, # noqa + weight_config, # noqa ) diff --git a/api/passport_admin/tests/test_notifications.py b/api/passport_admin/tests/test_notifications.py index 11fbe5c86..0605ac705 100644 --- a/api/passport_admin/tests/test_notifications.py +++ b/api/passport_admin/tests/test_notifications.py @@ -116,7 +116,7 @@ def custom_notifications(current_date, sample_address, community): @pytest.fixture -def community(scorer_account): +def community(scorer_account, weight_config): scorer = BinaryWeightedScorer.objects.create( type=Scorer.Type.WEIGHTED_BINARY, weights=scorer_weights ) diff --git a/api/registry/admin.py b/api/registry/admin.py index 3e0f19822..b8e6dbbd3 100644 --- a/api/registry/admin.py +++ b/api/registry/admin.py @@ -1,3 +1,5 @@ +import csv +import io from datetime import UTC, datetime import boto3 @@ -5,6 +7,8 @@ from django import forms from django.conf import settings from django.contrib import admin, messages +from django.core.exceptions import ValidationError +from django.http import HttpResponseRedirect from django.shortcuts import redirect, render from django.urls import path @@ -20,6 +24,7 @@ Score, Stamp, ) +from registry.weight_models import WeightConfiguration, WeightConfigurationItem from scorer.scorer_admin import ScorerModelAdmin from scorer.settings import ( BULK_MODEL_SCORE_REQUESTS_RESULTS_FOLDER, @@ -247,3 +252,108 @@ def import_csv(self, request): "registry/batch_model_scoring_request_csv_import_form.html", payload, ) + + +class WeightConfigurationItemInline(admin.TabularInline): + model = WeightConfigurationItem + extra = 1 + + +class WeightConfigurationCsvImportForm(forms.Form): + threshold = forms.FloatField( + required=True, help_text="Threshold for Passport uniqueness" + ) + description = forms.CharField( + required=False, help_text="Description of the weight configuration change" + ) + csv_file = forms.FileField( + required=True, help_text="Upload a CSV file with weight configuration data" + ) + + +@admin.register(WeightConfiguration) +class WeightConfigurationAdmin(admin.ModelAdmin): + change_list_template = "registry/batch_model_scoring_request_changelist.html" + list_display = ( + "version", + "threshold", + "description", + "active", + "created_at", + "updated_at", + ) + search_fields = ("version", "description") + readonly_fields = ("created_at", "updated_at") + inlines = [WeightConfigurationItemInline] + + def get_urls(self): + return [ + path("import-csv/", self.import_csv), + ] + super().get_urls() + + def import_csv(self, request): + if request.method == "POST": + form = WeightConfigurationCsvImportForm(request.POST, request.FILES) + if form.is_valid(): + csv_file = request.FILES["csv_file"] + threshold = request.POST.get("threshold") + description = request.POST.get("description") + try: + version = 0 + weight_config = WeightConfiguration.objects.order_by( + "-created_at" + ).first() + if weight_config: + version = int(weight_config.version) + 1 + + WeightConfiguration.objects.filter(active=True).update(active=False) + + weight_config = WeightConfiguration.objects.create( + threshold=threshold, + version=version, + active=True, + description=description, + ) + + decoded_file = csv_file.read().decode("utf-8") + io_string = io.StringIO(decoded_file) + reader = csv.reader(io_string) + for row in reader: + if len(row) != 2: + raise ValueError(f"Invalid row format: {row}") + + provider, weight = row + WeightConfigurationItem.objects.create( + weight_configuration=weight_config, + provider=provider, + weight=float(weight), + ) + + self.message_user( + request, + f"Successfully imported WeightConfiguration: {weight_config.version}", + ) + return redirect("..") + except Exception as e: + self.message_user( + request, f"Error importing CSV: {str(e)}", level=messages.ERROR + ) + else: + self.message_user( + request, "Invalid form submission", level=messages.ERROR + ) + else: + form = WeightConfigurationCsvImportForm() + + context = { + "form": form, + "title": "Import Weight Configuration CSV", + } + return render( + request, + "registry/batch_model_scoring_request_csv_import_form.html", + context, + ) + + +admin.site.register(WeightConfigurationItem) diff --git a/api/registry/management/commands/recalculate_scores.py b/api/registry/management/commands/recalculate_scores.py index f46768c82..010741521 100644 --- a/api/registry/management/commands/recalculate_scores.py +++ b/api/registry/management/commands/recalculate_scores.py @@ -1,12 +1,14 @@ import json from datetime import datetime -from account.models import Community from django.conf import settings from django.core.management.base import BaseCommand from django.db.models import QuerySet + +from account.models import Community from registry.models import Passport, Score, Stamp from registry.utils import get_utc_time +from registry.weight_models import WeightConfiguration, WeightConfigurationItem from scorer_weighted.models import BinaryWeightedScorer, RescoreRequest, WeightedScorer @@ -78,8 +80,8 @@ def handle(self, *args, **kwargs): return recalculate_scores(communities, batch_size, self.stdout) def update_scorers(self, communities: QuerySet[Community]): - weights = settings.GITCOIN_PASSPORT_WEIGHTS - threshold = settings.GITCOIN_PASSPORT_THRESHOLD + weights = WeightConfigurationItem.get_active_weights() + threshold = WeightConfiguration.get_active_threshold() filter = {"scorer_ptr__community__in": communities} diff --git a/api/registry/migrations/0036_weightconfiguration_weightconfigurationitem_and_more_squashed_0040_weightconfiguration_threshold.py b/api/registry/migrations/0036_weightconfiguration_weightconfigurationitem_and_more_squashed_0040_weightconfiguration_threshold.py new file mode 100644 index 000000000..157e8b329 --- /dev/null +++ b/api/registry/migrations/0036_weightconfiguration_weightconfigurationitem_and_more_squashed_0040_weightconfiguration_threshold.py @@ -0,0 +1,129 @@ +# Generated by Django 4.2.6 on 2024-08-15 14:39 + +import django.core.validators +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + replaces = [ + ("registry", "0036_weightconfiguration_weightconfigurationitem_and_more"), + ("registry", "0037_alter_weightconfigurationitem_weight"), + ("registry", "0038_remove_weightconfiguration_description_and_more"), + ("registry", "0039_remove_weightconfiguration_threshold"), + ("registry", "0040_weightconfiguration_threshold"), + ] + + dependencies = [ + ("registry", "0035_batchmodelscoringrequest_progress_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="WeightConfiguration", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("version", models.CharField(max_length=50, unique=True)), + ( + "threshold", + models.FloatField( + validators=[ + django.core.validators.MinValueValidator(0.0), + django.core.validators.MaxValueValidator(1.0), + ] + ), + ), + ("description", models.TextField(blank=True)), + ("active", models.BooleanField(default=False)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ], + options={ + "ordering": ["-created_at"], + }, + ), + migrations.CreateModel( + name="WeightConfigurationItem", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("provider", models.CharField(max_length=100)), + ( + "weight", + models.FloatField( + validators=[ + django.core.validators.MinValueValidator(0.0), + django.core.validators.MaxValueValidator(1.0), + ] + ), + ), + ( + "weight_configuration", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="weights", + to="registry.weightconfiguration", + ), + ), + ], + options={ + "ordering": ["provider"], + }, + ), + migrations.AddConstraint( + model_name="weightconfiguration", + constraint=models.UniqueConstraint( + condition=models.Q(("active", True)), + fields=("active",), + name="unique_active_weight_configuration", + ), + ), + migrations.AlterUniqueTogether( + name="weightconfigurationitem", + unique_together={("weight_configuration", "provider")}, + ), + migrations.AlterField( + model_name="weightconfigurationitem", + name="weight", + field=models.FloatField( + validators=[ + django.core.validators.MinValueValidator(0.0), + django.core.validators.MaxValueValidator(100), + ] + ), + ), + migrations.RemoveField( + model_name="weightconfiguration", + name="description", + ), + migrations.RemoveField( + model_name="weightconfiguration", + name="threshold", + ), + migrations.AddField( + model_name="weightconfiguration", + name="threshold", + field=models.FloatField( + default=20.0, + validators=[ + django.core.validators.MinValueValidator(0.0), + django.core.validators.MaxValueValidator(1000), + ], + ), + ), + ] diff --git a/api/registry/migrations/0041_weightconfiguration_description.py b/api/registry/migrations/0041_weightconfiguration_description.py new file mode 100644 index 000000000..fdb594a62 --- /dev/null +++ b/api/registry/migrations/0041_weightconfiguration_description.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.6 on 2024-08-16 20:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ( + "registry", + "0036_weightconfiguration_weightconfigurationitem_and_more_squashed_0040_weightconfiguration_threshold", + ), + ] + + operations = [ + migrations.AddField( + model_name="weightconfiguration", + name="description", + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/api/registry/models.py b/api/registry/models.py index 805cd65c6..6f2ec5fcc 100644 --- a/api/registry/models.py +++ b/api/registry/models.py @@ -6,6 +6,9 @@ from account.models import Community, EthAddressField +# to avoid circular import issue +from .weight_models import WeightConfiguration, WeightConfigurationItem + class Passport(models.Model): address = EthAddressField(null=True, blank=False, max_length=100, db_index=True) diff --git a/api/registry/test/conftest.py b/api/registry/test/conftest.py index 35ca8a95b..84701d872 100644 --- a/api/registry/test/conftest.py +++ b/api/registry/test/conftest.py @@ -1,3 +1,8 @@ +import pytest + +from registry.weight_models import WeightConfiguration, WeightConfigurationItem +from scorer.config.gitcoin_passport_weights import GITCOIN_PASSPORT_WEIGHTS + # pylint: disable=unused-import from scorer.test.conftest import ( gtc_staking_response, @@ -13,3 +18,24 @@ scorer_score, scorer_user, ) + + +@pytest.fixture(autouse=True) +def weight_config(request): + weights_to_use = getattr(request, "param", GITCOIN_PASSPORT_WEIGHTS) + + config = WeightConfiguration.objects.create( + version="v1", + threshold=20.0, + active=True, + description="Test", + ) + + for provider, weight in weights_to_use.items(): + WeightConfigurationItem.objects.create( + weight_configuration=config, + provider=provider, + weight=float(weight), + ) + + return config diff --git a/api/registry/test/test_address_validation.py b/api/registry/test/test_address_validation.py index c2219b596..7d330a398 100644 --- a/api/registry/test/test_address_validation.py +++ b/api/registry/test/test_address_validation.py @@ -1,28 +1,35 @@ import pytest + from registry.api.v1 import is_valid_address address = "0x71Ad3e3057Ca74967239C66ca6D3A9C2A43a58fC" +@pytest.mark.django_db def test_good_checksum(): assert is_valid_address(address) is True +@pytest.mark.django_db def test_good_lowercase(): assert is_valid_address(address.lower()) is True +@pytest.mark.django_db def test_good_uppercase(): assert is_valid_address(address.upper()) is True +@pytest.mark.django_db def test_bad_checksum(): assert is_valid_address(address.replace("A", "a")) is False +@pytest.mark.django_db def test_bad_length(): assert is_valid_address(address.lower() + "a") is False +@pytest.mark.django_db def test_bad_length_checksummed(): assert is_valid_address(address + "a") is False diff --git a/api/registry/test/test_command_delete_user_data.py b/api/registry/test/test_command_delete_user_data.py index bcf9452e1..1e6a2bce3 100644 --- a/api/registry/test/test_command_delete_user_data.py +++ b/api/registry/test/test_command_delete_user_data.py @@ -2,18 +2,16 @@ from unittest import mock import pytest -from ceramic_cache.models import CeramicCache, CeramicCacheLegacy from django.conf import settings from django.core.management import call_command -from passport_admin.models import DismissedBanners, PassportBanner +from ceramic_cache.models import CeramicCache, CeramicCacheLegacy +from passport_admin.models import DismissedBanners, PassportBanner from registry.models import Event, HashScorerLink, Passport, Score, Stamp from registry.utils import get_utc_time pytestmark = pytest.mark.django_db -current_weights = settings.GITCOIN_PASSPORT_WEIGHTS - @pytest.fixture(name="user_data") def user_data(passport_holder_addresses, scorer_community_with_binary_scorer): diff --git a/api/registry/test/test_command_recalculate_scores.py b/api/registry/test/test_command_recalculate_scores.py index d636a8af3..e592942f2 100644 --- a/api/registry/test/test_command_recalculate_scores.py +++ b/api/registry/test/test_command_recalculate_scores.py @@ -1,16 +1,16 @@ import json +from decimal import Decimal import pytest -from account.models import Community from django.conf import settings from django.core.management import call_command from django.test import override_settings + +from account.models import Community from registry.models import Passport, Score, Stamp pytestmark = pytest.mark.django_db -current_weights = settings.GITCOIN_PASSPORT_WEIGHTS - @pytest.fixture(name="binary_weighted_scorer_passports") def fixture_binaty_weighted_scorer_passports( @@ -174,7 +174,7 @@ def test_rescoring_binary_scorer( scorer = community.get_scorer() # Check the initial threshold - assert scorer.threshold == 75 + assert scorer.threshold == Decimal(20) assert len(scores) == 0 call_command("recalculate_scores", *args, **opts) @@ -187,18 +187,23 @@ def test_rescoring_binary_scorer( assert s.status == "DONE" assert s.error is None - updated_weights = { - "FirstEthTxnProvider": "75", - "Google": "1", - "Ens": "1", - } - - @override_settings(GITCOIN_PASSPORT_WEIGHTS=updated_weights) + @pytest.mark.parametrize( + "weight_config", + [ + { + "FirstEthTxnProvider": "75", + "Google": "1", + "Ens": "1", + } + ], + indirect=True, + ) def test_rescoring_binary_scorer_w_updated_settings( self, binary_weighted_scorer_passports, passport_holder_addresses, scorer_community_with_binary_scorer, + weight_config, ): community = scorer_community_with_binary_scorer args = [] @@ -209,7 +214,7 @@ def test_rescoring_binary_scorer_w_updated_settings( scorer = community.get_scorer() # Check the initial threshold - assert scorer.threshold == 75 + assert scorer.threshold == 20 assert len(scores) == 0 call_command("recalculate_scores", *args, **opts) @@ -240,6 +245,17 @@ def test_rescoring_binary_scorer_w_updated_settings( assert "Google" in s3.stamp_scores assert "Ens" in s3.stamp_scores + @pytest.mark.parametrize( + "weight_config", + [ + { + "FirstEthTxnProvider": "1", + "Google": "1", + "Ens": "1", + } + ], + indirect=True, + ) def test_rescoring_weighted_scorer( self, weighted_scorer_passports, @@ -283,18 +299,23 @@ def test_rescoring_weighted_scorer( assert "Google" in s3.stamp_scores assert "Ens" in s3.stamp_scores - updated_weights = { - "FirstEthTxnProvider": "75", - "Google": "1", - "Ens": "1", - } - - @override_settings(GITCOIN_PASSPORT_WEIGHTS=updated_weights) + @pytest.mark.parametrize( + "weight_config", + [ + { + "FirstEthTxnProvider": "75", + "Google": "1", + "Ens": "1", + } + ], + indirect=True, + ) def test_rescoring_weighted_scorer_w_updated_settings( self, weighted_scorer_passports, passport_holder_addresses, scorer_community_with_weighted_scorer, + weight_config, ): """Change weights and rescore ...""" community = scorer_community_with_weighted_scorer diff --git a/api/registry/test/test_passport_submission.py b/api/registry/test/test_passport_submission.py index c94414630..019793ce0 100644 --- a/api/registry/test/test_passport_submission.py +++ b/api/registry/test/test_passport_submission.py @@ -4,16 +4,19 @@ from decimal import Decimal from unittest.mock import patch -from account.models import Account, AccountAPIKey, Community, Nonce -from ceramic_cache.models import CeramicCache +import pytest from django.conf import settings from django.contrib.auth.models import User from django.test import Client, TransactionTestCase from eth_account.messages import encode_defunct +from web3 import Web3 + +from account.models import Account, AccountAPIKey, Community, Nonce +from ceramic_cache.models import CeramicCache from registry.models import Passport, Stamp from registry.tasks import score_passport from registry.utils import get_signer, get_signing_message, verify_issuer -from web3 import Web3 +from registry.weight_models import WeightConfiguration, WeightConfigurationItem web3 = Web3() web3.eth.account.enable_unaudited_hdwallet_features() @@ -266,19 +269,11 @@ def setUp(self): user=self.user, address=account.address ) - # Mock the default weights for new communities that are created - with patch( - "scorer_weighted.models.settings.GITCOIN_PASSPORT_WEIGHTS", - { - "Google": 1, - "Ens": 1, - }, - ): - self.community = Community.objects.create( - name="My Community", - description="My Community description", - account=self.user_account, - ) + self.community = Community.objects.create( + name="My Community", + description="My Community description", + account=self.user_account, + ) self.nonce = Nonce.create_nonce().nonce self.nonce_2 = Nonce.create_nonce().nonce @@ -579,10 +574,10 @@ def test_submit_passport_multiple_times( "evidence": None, "last_score_timestamp": "2023-01-11T16:35:23.938006+00:00", "expiration_date": mock_passport_expiration_date.isoformat(), - "score": Decimal("2.000000000"), + "score": Decimal("0.9329999999999999960031971113"), "status": "DONE", "error": None, - "stamp_scores": {"Ens": 1.0, "Google": 1.0}, + "stamp_scores": {"Ens": 0.408, "Google": 0.525}, } expected2ndResponse = { @@ -590,10 +585,10 @@ def test_submit_passport_multiple_times( "evidence": None, "last_score_timestamp": "2023-01-11T16:35:23.938006+00:00", "expiration_date": mock_passport_expiration_date.isoformat(), - "score": Decimal("2.000000000"), + "score": Decimal("0.9329999999999999960031971113"), "status": "DONE", "error": None, - "stamp_scores": {"Ens": 1.0, "Google": 1.0}, + "stamp_scores": {"Ens": 0.408, "Google": 0.525}, } # First submission diff --git a/api/registry/test/test_score_passport.py b/api/registry/test/test_score_passport.py index bf811da9d..57e8dbfad 100644 --- a/api/registry/test/test_score_passport.py +++ b/api/registry/test/test_score_passport.py @@ -1,18 +1,19 @@ +import copy import json import re +from datetime import datetime, timedelta, timezone from decimal import Decimal from unittest.mock import call, patch -from account.models import Account, AccountAPIKey, Community from django.conf import settings from django.contrib.auth import get_user_model from django.test import Client, TransactionTestCase +from web3 import Web3 + +from account.models import Account, AccountAPIKey, Community from registry.api.v2 import SubmitPassportPayload, a_submit_passport, get_score from registry.models import Event, HashScorerLink, Passport, Score, Stamp from registry.tasks import score_passport_passport, score_registry_passport -from web3 import Web3 -from datetime import datetime, timezone, timedelta -import copy User = get_user_model() my_mnemonic = settings.TEST_MNEMONIC @@ -106,20 +107,11 @@ def setUp(self): account=self.user_account, name="Token for user 1" ) - # Mock the default weights for new communities that are created - with patch( - "scorer_weighted.models.settings.GITCOIN_PASSPORT_WEIGHTS", - { - "Google": 1, - "Ens": 2, - "POAP": 4, - }, - ): - self.community = Community.objects.create( - name="My Community", - description="My Community description", - account=self.user_account, - ) + self.community = Community.objects.create( + name="My Community", + description="My Community description", + account=self.user_account, + ) self.client = Client() @@ -347,7 +339,9 @@ def test_lifo_duplicate_stamp_scoring(self): original_stamps = Stamp.objects.filter(passport=passport) assert len(original_stamps) == 3 - assert (Score.objects.get(passport=passport).score) == Decimal("3") + assert (Score.objects.get(passport=passport).score) == Decimal( + "0.933000000" + ) assert ( Event.objects.filter(action=Event.Action.LIFO_DEDUPLICATION).count() @@ -361,7 +355,7 @@ def test_lifo_duplicate_stamp_scoring(self): assert ( Score.objects.get(passport=passport_with_duplicates).score - ) == Decimal("1") + ) == Decimal("0.525000000") passport.requires_calculation = True passport.save() @@ -371,7 +365,9 @@ def test_lifo_duplicate_stamp_scoring(self): ): score_registry_passport(self.community.pk, passport.address) - assert (Score.objects.get(passport=passport).score) == Decimal("3") + assert (Score.objects.get(passport=passport).score) == Decimal( + "0.933000000" + ) assert ( Event.objects.filter(action=Event.Action.LIFO_DEDUPLICATION).count() == 2 diff --git a/api/registry/weight_models.py b/api/registry/weight_models.py new file mode 100644 index 000000000..01e78c456 --- /dev/null +++ b/api/registry/weight_models.py @@ -0,0 +1,66 @@ +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist +from django.core.validators import MaxValueValidator, MinValueValidator +from django.db import models + + +class WeightConfiguration(models.Model): + version = models.CharField(max_length=50, unique=True) + threshold = models.FloatField( + validators=[MinValueValidator(0.0), MaxValueValidator(1000)], default=20.0 + ) + active = models.BooleanField(default=False) + description = models.TextField(null=True, blank=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + ordering = ["-created_at"] + constraints = [ + models.UniqueConstraint( + fields=["active"], + condition=models.Q(active=True), + name="unique_active_weight_configuration", + ) + ] + + @classmethod + def get_active_threshold(cls): + try: + active_config = cls.objects.filter(active=True).get() + except Exception as e: + raise Exception(f"Failed to load active threshold: {str(e)}") + + return active_config.threshold + + def __str__(self): + return f"v:{self.version}" + + +class WeightConfigurationItem(models.Model): + weight_configuration = models.ForeignKey( + WeightConfiguration, on_delete=models.CASCADE, related_name="weights" + ) + provider = models.CharField(max_length=100) + weight = models.FloatField( + validators=[MinValueValidator(0.0), MaxValueValidator(100)] + ) + + class Meta: + ordering = ["provider"] + unique_together = ["weight_configuration", "provider"] + + def __str__(self): + return f"{self.provider} - {self.weight}" + + @classmethod + def get_active_weights(cls): + try: + active_config = WeightConfiguration.objects.filter(active=True).get() + except Exception as e: + raise Exception(f"Failed to load active weights: {str(e)}") + + weight_items = cls.objects.filter(weight_configuration=active_config) + + weights = {item.provider: item.weight for item in weight_items} + return weights diff --git a/api/scorer/config/gitcoin_passport_weights.py b/api/scorer/config/gitcoin_passport_weights.py new file mode 100644 index 000000000..27a1fb5a7 --- /dev/null +++ b/api/scorer/config/gitcoin_passport_weights.py @@ -0,0 +1,56 @@ +"""Configuration for the gitcoin scorer""" + +# Weight values for each stamp based on its perceived significance in assessing the unique humanity of the Passport holder +GITCOIN_PASSPORT_WEIGHTS = { + "BeginnerCommunityStaker": "1.513", + "Brightid": "0.802", + "CivicCaptchaPass": "1.014", + "CivicLivenessPass": "3.004", + "CivicUniquenessPass": "6.005", + "CoinbaseDualVerification": "16.042", + "Discord": "0.516", + "Ens": "0.408", + "ETHDaysActive#50": "0.507", + "ETHGasSpent#0.25": "1.003", + "ETHnumTransactions#100": "0.51", + "ETHScore#50": "10.012", + "ETHScore#75": "2.001", + "ETHScore#90": "2.009", + "ExperiencedCommunityStaker": "2.515", + "GitcoinContributorStatistics#totalContributionAmountGte#1000": "5.018", + "GitcoinContributorStatistics#totalContributionAmountGte#100": "2.017", + "GitcoinContributorStatistics#totalContributionAmountGte#10": "0.523", + "githubContributionActivityGte#120": "3.019", + "githubContributionActivityGte#30": "2.020", + "githubContributionActivityGte#60": "2.021", + "GnosisSafe": "0.822", + "Google": "0.525", + "GuildAdmin": "0.724", + "GuildPassportMember": "0.54", + "HolonymGovIdProvider": "16.026", + "IdenaState#Human": "2.027", + "IdenaState#Newbie": "6.028", + "IdenaState#Verified": "2.029", + "Lens": "0.93", + "Linkedin": "1.531", + "NFT": "1.032", + "NFTScore#50": "10.033", + "NFTScore#75": "2.034", + "NFTScore#90": "2.035", + "SelfStakingBronze": "1.036", + "SelfStakingGold": "3.037", + "SelfStakingSilver": "2.038", + "SnapshotProposalsProvider": "0.839", + "TrustaLabs": "0.511", + "TrustedCitizen": "4.041", + "ZkSyncEra": "0.606", + "zkSyncScore#20": "1.67", + "zkSyncScore#50": "1.67", + "zkSyncScore#5": "1.67", + "Outdid": "10", + "BinanceBABT": "16.021", +} + + +# The Boolean scorer deems Passport holders unique humans if they meet or exceed the below thresholdold +GITCOIN_PASSPORT_THRESHOLD = "20" diff --git a/api/scorer/settings/__init__.py b/api/scorer/settings/__init__.py index 228e619f8..624b67659 100644 --- a/api/scorer/settings/__init__.py +++ b/api/scorer/settings/__init__.py @@ -2,7 +2,6 @@ from .base import * from .celery import * from .feature_flags import * -from .gitcoin_passport_weights import * from .model_config import * from .ninja_jwt import * from .ratelimit import * diff --git a/api/scorer/test/conftest.py b/api/scorer/test/conftest.py index b979cc17c..b813d9507 100644 --- a/api/scorer/test/conftest.py +++ b/api/scorer/test/conftest.py @@ -1,13 +1,16 @@ # pylint: disable=redefined-outer-name import pytest -from account.models import Account, AccountAPIKey, Community -from ceramic_cache.api.v1 import DbCacheToken from django.conf import settings from django.contrib.auth import get_user_model from ninja_jwt.schema import RefreshToken +from web3 import Web3 + +from account.models import Account, AccountAPIKey, Community +from ceramic_cache.api.v1 import DbCacheToken from registry.models import GTCStakeEvent, Passport, Score +from registry.weight_models import WeightConfiguration, WeightConfigurationItem +from scorer.config.gitcoin_passport_weights import GITCOIN_PASSPORT_WEIGHTS from scorer_weighted.models import BinaryWeightedScorer, Scorer, WeightedScorer -from web3 import Web3 User = get_user_model() @@ -82,18 +85,26 @@ def scorer_api_key_no_permissions(scorer_account): @pytest.fixture -def scorer_community_with_binary_scorer(mocker, scorer_account): - mock_settings = {"FirstEthTxnProvider": 1, "Google": 1, "Ens": 1} - # Mock gitcoin scoring settings - mocker.patch( - "scorer_weighted.models.settings.GITCOIN_PASSPORT_WEIGHTS", - mock_settings, - ) - mocker.patch( - "scorer_weighted.models.settings.GITCOIN_PASSPORT_THRESHOLD", - 75, +def weight_config(): + config = WeightConfiguration.objects.create( + version="v1", + threshold=20.0, + active=True, + description="Test", ) + for provider, weight in GITCOIN_PASSPORT_WEIGHTS.items(): + WeightConfigurationItem.objects.create( + weight_configuration=config, + provider=provider, + weight=float(weight), + ) + + return config + + +@pytest.fixture +def scorer_community_with_binary_scorer(mocker, scorer_account, weight_config): scorer = BinaryWeightedScorer.objects.create(type=Scorer.Type.WEIGHTED_BINARY) community = Community.objects.create( @@ -112,14 +123,7 @@ def ui_scorer(scorer_community_with_binary_scorer): @pytest.fixture -def scorer_community_with_weighted_scorer(mocker, scorer_account): - mock_settings = {"FirstEthTxnProvider": 1, "Google": 1, "Ens": 1} - # Mock gitcoin scoring settings - mocker.patch( - "scorer_weighted.models.settings.GITCOIN_PASSPORT_WEIGHTS", - mock_settings, - ) - +def scorer_community_with_weighted_scorer(mocker, scorer_account, weight_config): scorer = WeightedScorer.objects.create(type=Scorer.Type.WEIGHTED) community = Community.objects.create( @@ -132,7 +136,7 @@ def scorer_community_with_weighted_scorer(mocker, scorer_account): @pytest.fixture -def scorer_community(scorer_account): +def scorer_community(scorer_account, weight_config): community = Community.objects.create( name="My Community", description="My Community description", @@ -160,17 +164,7 @@ def scorer_score(scorer_passport): @pytest.fixture -def scorer_community_with_gitcoin_default(mocker, scorer_account): - mock_settings = { - "Google": 1234, - "Ens": 1000000, - } - # Mock gitcoin scoring settings - mocker.patch( - "scorer_weighted.models.settings.GITCOIN_PASSPORT_WEIGHTS", - mock_settings, - ) - +def scorer_community_with_gitcoin_default(mocker, scorer_account, weight_config): community = Community.objects.create( name="My Community", description="My Community description", diff --git a/api/scorer/test/test_choose_binary_scorer.py b/api/scorer/test/test_choose_binary_scorer.py index 6bb08c141..5e717bd67 100644 --- a/api/scorer/test/test_choose_binary_scorer.py +++ b/api/scorer/test/test_choose_binary_scorer.py @@ -6,11 +6,13 @@ from unittest.mock import patch import pytest -from account.models import Community from django.test import Client from pytest_bdd import given, scenario, then, when + +from account.models import Community from registry.tasks import score_passport_passport from registry.test.test_passport_submission import mock_passport, mock_utc_timestamp +from registry.weight_models import WeightConfiguration from scorer_weighted.models import BinaryWeightedScorer pytestmark = pytest.mark.django_db @@ -50,7 +52,8 @@ def _(scorersPutResponse): @then("it automatically becomes the new rule in the respective community") def _(scorer_community_with_binary_scorer): scorer = Community.objects.get(id=scorer_community_with_binary_scorer.id).scorer - scorer.binaryweightedscorer.threshold = 1 + + scorer.binaryweightedscorer.threshold = 0.9 scorer.binaryweightedscorer.save() assert scorer.type == "WEIGHTED_BINARY" assert scorer.binaryweightedscorer @@ -119,7 +122,7 @@ def _(scoreResponse): def _(scoreResponse): """the threshold should be returned.""" assert ( - scoreResponse.json()["evidence"]["threshold"] == "1.00000" + scoreResponse.json()["evidence"]["threshold"] == "0.90000" ) # That is the mocked value @@ -137,6 +140,11 @@ def test_get_score_of_000000(): ) def _(scorer_community_with_binary_scorer, scorer_api_key): """I submit a passport that yields a weighted score less than the threshold.""" + scorer = Community.objects.get(id=scorer_community_with_binary_scorer.id).scorer + + scorer.binaryweightedscorer.threshold = 75 + scorer.binaryweightedscorer.save() + with patch( "scorer_weighted.computation.acalculate_weighted_score", return_value=[ @@ -202,6 +210,11 @@ def test_get_score_of_1000000000(): def _(scorer_community_with_binary_scorer, scorer_api_key): """I submit a passport that yields a weighted score greater or equal than the threshold.""" + scorer = Community.objects.get(id=scorer_community_with_binary_scorer.id).scorer + + scorer.binaryweightedscorer.threshold = 75 + scorer.binaryweightedscorer.save() + with patch("registry.atasks.get_utc_time", return_value=mock_utc_timestamp): with patch( "scorer_weighted.computation.acalculate_weighted_score", diff --git a/api/scorer/test/test_create_community_id.py b/api/scorer/test/test_create_community_id.py index 07dbc22e7..99a7d0db0 100644 --- a/api/scorer/test/test_create_community_id.py +++ b/api/scorer/test/test_create_community_id.py @@ -4,13 +4,14 @@ from decimal import Decimal import pytest -from account.models import Community -from account.test.test_community import mock_community_body from django.test import Client from ninja_jwt.schema import RefreshToken from pytest_bdd import given, scenario, then, when from web3 import Web3 +from account.models import Community +from account.test.test_community import mock_community_body + web3 = Web3() web3.eth.account.enable_unaudited_hdwallet_features() @@ -32,7 +33,7 @@ def _(scorer_account, mocker): "I enter a name for this Community that is unique among the Community registered under my account", target_fixture="community_response", ) -def _(scorer_user): +def _(scorer_user, weight_config): """I enter a name for this Community that is unique among the Community registered under my account.""" refresh = RefreshToken.for_user(scorer_user) refresh["ip_address"] = "127.0.0.1" @@ -72,4 +73,4 @@ def _(): community = Community.objects.all()[0] scorer = community.scorer.binaryweightedscorer assert scorer.threshold == Decimal("20.00") - assert scorer.weights["Discord"] == "0.516" + assert scorer.weights["Discord"] == 0.516 diff --git a/api/scorer/test/test_deduplication.py b/api/scorer/test/test_deduplication.py index a97a2f982..d16e57a34 100644 --- a/api/scorer/test/test_deduplication.py +++ b/api/scorer/test/test_deduplication.py @@ -5,10 +5,12 @@ from decimal import Decimal import pytest -from account.models import Nonce from django.test import Client from eth_account.messages import encode_defunct from pytest_bdd import given, scenario, then, when +from web3 import Web3 + +from account.models import Nonce from registry.models import HashScorerLink, Passport, Stamp # from registry.tasks import score_passport @@ -18,7 +20,6 @@ mock_utc_timestamp, ) from registry.utils import get_signing_message -from web3 import Web3 pytestmark = pytest.mark.django_db @@ -120,7 +121,7 @@ def _( response_data = submitResponse.json() assert response_data["address"] == passport_holder_addresses[1]["address"].lower() - assert Decimal(response_data["score"]) == Decimal("1234.000000000") + assert Decimal(response_data["score"]) == Decimal("0.5250000000000000222044604925") assert response_data["status"] == "DONE" assert response_data["evidence"] is None assert response_data["error"] is None @@ -159,7 +160,7 @@ def _(passport_holder_addresses, submit_passport_response): == passport_holder_addresses[1]["address"].lower() ) assert ( - submit_passport_response_data["score"] == "1234.000000000" + submit_passport_response_data["score"] == "0.525000000" ) # we expect a score only for the ENS stamp assert submit_passport_response_data["evidence"] is None last_score_timestamp = datetime.fromisoformat( diff --git a/api/scorer/test/test_submit_passport.py b/api/scorer/test/test_submit_passport.py index f85204c05..4c5a8e1e1 100644 --- a/api/scorer/test/test_submit_passport.py +++ b/api/scorer/test/test_submit_passport.py @@ -4,17 +4,18 @@ from decimal import Decimal import pytest -from account.models import Nonce from django.test import Client from eth_account.messages import encode_defunct from pytest_bdd import given, scenario, then, when +from web3 import Web3 + +from account.models import Nonce from registry.test.test_passport_submission import ( mock_passport, - mock_utc_timestamp, mock_passport_expiration_date, + mock_utc_timestamp, ) from registry.utils import get_signing_message -from web3 import Web3 web3 = Web3() web3.eth.account.enable_unaudited_hdwallet_features() @@ -100,17 +101,18 @@ def _(scorer_community_with_gitcoin_default, submit_passport_response): # TODO change PROCESSING => DONE above returned_json = submit_passport_response.json() returned_json["score"] = Decimal(returned_json["score"]) + assert returned_json == { "address": scorer_community_with_gitcoin_default.account.address.lower(), - "score": Decimal("1001234.000000000"), + "score": Decimal("0.9329999999999999960031971113"), "status": "DONE", "last_score_timestamp": mock_utc_timestamp.isoformat(), "expiration_date": mock_passport_expiration_date.isoformat(), "evidence": None, "error": None, "stamp_scores": { - "Ens": 1000000.0, - "Google": 1234.0, + "Ens": 0.408, + "Google": 0.525, }, } @@ -166,15 +168,15 @@ def _(scorer_community_with_gitcoin_default, score_response): assert score_response.json() == { "address": scorer_community_with_gitcoin_default.account.address.lower(), - "score": "1001234.000000000", + "score": "0.933000000", "status": "DONE", "last_score_timestamp": mock_utc_timestamp.isoformat(), "expiration_date": mock_passport_expiration_date.isoformat(), "evidence": None, "error": None, "stamp_scores": { - "Ens": 1000000.0, - "Google": 1234.0, + "Ens": 0.408, + "Google": 0.525, }, } @@ -233,8 +235,8 @@ def _(scorer_community_with_gitcoin_default, scoring_failed_score_response): "evidence": None, "error": "something bad", "stamp_scores": { - "Ens": 1000000.0, - "Google": 1234.0, + "Ens": 0.408, + "Google": 0.525, }, } @@ -271,7 +273,7 @@ def _(scorer_community_with_gitcoin_default, score_response, mocker): response_data["address"] == scorer_community_with_gitcoin_default.account.address.lower() ) - assert response_data["score"] == "1001234.000000000" + assert response_data["score"] == "0.933000000" assert response_data["status"] == "DONE" assert response_data["last_score_timestamp"] == mock_utc_timestamp.isoformat() assert response_data["evidence"] is None @@ -342,15 +344,15 @@ def _(scorer_community_with_gitcoin_default, score_response): score_response_data = score_response.json() assert score_response_data == { "address": scorer_community_with_gitcoin_default.account.address.lower(), - "score": "1001234.000000000", + "score": "0.933000000", "status": "DONE", "last_score_timestamp": mock_utc_timestamp.isoformat(), "expiration_date": mock_passport_expiration_date.isoformat(), "evidence": None, "error": None, "stamp_scores": { - "Ens": 1000000.0, - "Google": 1234.0, + "Ens": 0.408, + "Google": 0.525, }, } diff --git a/api/scorer_weighted/models.py b/api/scorer_weighted/models.py index 0e1bc2944..7d852849d 100644 --- a/api/scorer_weighted/models.py +++ b/api/scorer_weighted/models.py @@ -1,12 +1,14 @@ # TODO: remove pylint skip once circular dependency removed # pylint: disable=import-outside-toplevel +from datetime import datetime from decimal import Decimal from typing import List, Optional -import api_logging as logging from django.conf import settings from django.db import models -from datetime import datetime + +import api_logging as logging +from registry.weight_models import WeightConfiguration, WeightConfigurationItem log = logging.getLogger(__name__) @@ -57,14 +59,14 @@ def get_default_weights(): This function shall provide the default weights for the default scorer. It will load the weights from the settings """ - return settings.GITCOIN_PASSPORT_WEIGHTS + return WeightConfigurationItem.get_active_weights() def get_default_threshold(): """ This function shall provide the default threshold for the default binary scorer from the settings. """ - return round(Decimal(settings.GITCOIN_PASSPORT_THRESHOLD), THRESHOLD_DECIMAL_PLACES) + return WeightConfiguration.get_active_threshold() class Scorer(models.Model): diff --git a/api/scorer_weighted/tests/conftest.py b/api/scorer_weighted/tests/conftest.py index 33efe4239..6bb5cbe02 100644 --- a/api/scorer_weighted/tests/conftest.py +++ b/api/scorer_weighted/tests/conftest.py @@ -4,4 +4,5 @@ scorer_account, scorer_community_with_binary_scorer, scorer_user, + weight_config, )