From e00c033781d814f89f72776f63476f9184165a26 Mon Sep 17 00:00:00 2001 From: Anastasia Beglova Date: Mon, 30 Sep 2024 16:20:14 -0400 Subject: [PATCH] add is_incomplete_or_stale --- learning_resources/factories.py | 1 + learning_resources_search/api_test.py | 4 ++ learning_resources_search/constants.py | 2 + learning_resources_search/serializers.py | 16 ++++++-- learning_resources_search/serializers_test.py | 38 ++++++++++++++----- 5 files changed, 49 insertions(+), 12 deletions(-) diff --git a/learning_resources/factories.py b/learning_resources/factories.py index a3996cf0fc..5252d2894e 100644 --- a/learning_resources/factories.py +++ b/learning_resources/factories.py @@ -213,6 +213,7 @@ class LearningResourceFactory(DjangoModelFactory): departments = factory.PostGeneration(_post_gen_departments) topics = factory.PostGeneration(_post_gen_topics) content_tags = factory.PostGeneration(_post_gen_tags) + completeness = 1 published = True delivery = factory.List(random.choices(LearningResourceDelivery.names())) # noqa: S311 professional = factory.LazyAttribute( diff --git a/learning_resources_search/api_test.py b/learning_resources_search/api_test.py index 86b810113b..50952da6c7 100644 --- a/learning_resources_search/api_test.py +++ b/learning_resources_search/api_test.py @@ -1445,6 +1445,7 @@ def test_execute_learn_search_for_learning_resource_query(opensearch): "is_learning_material", "resource_age_date", "featured_rank", + "is_incomplete_or_stale", ] }, } @@ -1921,6 +1922,7 @@ def test_execute_learn_search_with_script_score( "is_learning_material", "resource_age_date", "featured_rank", + "is_incomplete_or_stale", ] }, } @@ -2349,6 +2351,7 @@ def test_execute_learn_search_with_min_score(mocker, settings, opensearch): "is_learning_material", "resource_age_date", "featured_rank", + "is_incomplete_or_stale", ] }, } @@ -2559,6 +2562,7 @@ def test_execute_learn_search_for_content_file_query(opensearch): "is_learning_material", "resource_age_date", "featured_rank", + "is_incomplete_or_stale", ] }, } diff --git a/learning_resources_search/constants.py b/learning_resources_search/constants.py index 6bcc47e1f0..1487edb061 100644 --- a/learning_resources_search/constants.py +++ b/learning_resources_search/constants.py @@ -115,6 +115,7 @@ class FilterConfig: }, "free": {"type": "boolean"}, "is_learning_material": {"type": "boolean"}, + "is_incomplete_or_stale": {"type": "boolean"}, "delivery": { "type": "nested", "properties": { @@ -416,6 +417,7 @@ class FilterConfig: "is_learning_material", "resource_age_date", "featured_rank", + "is_incomplete_or_stale", ] LEARNING_RESOURCE_SEARCH_SORTBY_OPTIONS = { diff --git a/learning_resources_search/serializers.py b/learning_resources_search/serializers.py index b340605169..2addd6fa4d 100644 --- a/learning_resources_search/serializers.py +++ b/learning_resources_search/serializers.py @@ -125,6 +125,9 @@ def serialize_learning_resource_for_update( dict: The serialized and transformed resource data """ + STALENESS_CUTOFF = 2010 + COMPLETENESS_CUTOFF = 0.5 + serialized_data = LearningResourceSerializer(instance=learning_resource_obj).data if learning_resource_obj.resource_type == LearningResourceType.course.name: @@ -146,15 +149,22 @@ def serialize_learning_resource_for_update( else: featured_rank = None + resource_age_date = get_resource_age_date( + learning_resource_obj, serialized_data["resource_category"] + ) + + is_incomplete_or_stale = ( + resource_age_date and resource_age_date.year <= STALENESS_CUTOFF + ) or (learning_resource_obj.completeness < COMPLETENESS_CUTOFF) + return { "resource_relations": {"name": "resource"}, "created_on": learning_resource_obj.created_on, "is_learning_material": serialized_data["resource_category"] == LEARNING_MATERIAL_RESOURCE_CATEGORY, - "resource_age_date": get_resource_age_date( - learning_resource_obj, serialized_data["resource_category"] - ), + "resource_age_date": resource_age_date, "featured_rank": featured_rank, + "is_incomplete_or_stale": is_incomplete_or_stale, **serialized_data, } diff --git a/learning_resources_search/serializers_test.py b/learning_resources_search/serializers_test.py index 92eddbe150..43d97c6154 100644 --- a/learning_resources_search/serializers_test.py +++ b/learning_resources_search/serializers_test.py @@ -615,6 +615,7 @@ def test_serialize_bulk_learning_resources(mocker): "resource_age_date": mocker.ANY, "is_learning_material": mocker.ANY, "featured_rank": None, + "is_incomplete_or_stale": mocker.ANY, **LearningResourceSerializer(instance=resource).data, } @@ -637,30 +638,47 @@ def test_serialize_bulk_learning_resources(mocker): @pytest.mark.parametrize("is_professional", [True, False]) @pytest.mark.parametrize("no_price", [True, False]) @pytest.mark.parametrize("has_featured_rank", [True, False]) -def test_serialize_learning_resource_for_bulk( - mocker, resource_type, is_professional, no_price, has_featured_rank +@pytest.mark.parametrize("is_stale", [True, False]) +@pytest.mark.parametrize("is_incomplete", [True, False]) +def test_serialize_learning_resource_for_bulk( # noqa: PLR0913 + mocker, + resource_type, + is_professional, + no_price, + has_featured_rank, + is_stale, + is_incomplete, ): """ Test that serialize_program_for_bulk yields a valid LearningResourceSerializer for resource types other than "course" The "course" resource type is tested by `test_serialize_course_numbers_for_bulk` below. """ + completeness = 0.24 if is_incomplete else 0.75 resource = factories.LearningResourceFactory.create( - resource_type=resource_type, professional=is_professional, runs=[] + resource_type=resource_type, + professional=is_professional, + runs=[], + completeness=completeness, ) + LearningResourceRunFactory.create( learning_resource=resource, prices=[Decimal(0.00 if no_price else 1.00)] ) + + resource_age_date = datetime( + 2009 if is_stale else 2024, 1, 1, 1, 1, 1, 0, tzinfo=UTC + ) + + mocker.patch( + "learning_resources_search.serializers.get_resource_age_date", + return_value=resource_age_date, + ) free_dict = { "free": resource_type not in [LearningResourceType.program.name, LearningResourceType.course.name] or (no_price and not is_professional) } - mocker.patch( - "learning_resources_search.serializers.get_resource_age_date", - return_value=datetime(2024, 1, 1, 1, 1, 1, 0, tzinfo=UTC), - ) - if has_featured_rank: mocker.patch( "learning_resources_search.serializers.random", @@ -692,8 +710,9 @@ def test_serialize_learning_resource_for_bulk( "resource_relations": {"name": "resource"}, "created_on": resource.created_on, "is_learning_material": resource.resource_type not in ["course", "program"], - "resource_age_date": datetime(2024, 1, 1, 1, 1, 1, 0, tzinfo=UTC), + "resource_age_date": resource_age_date, "featured_rank": 3.4 if has_featured_rank else None, + "is_incomplete_or_stale": is_incomplete or is_stale, **free_dict, **LearningResourceSerializer(resource).data, } @@ -818,6 +837,7 @@ def test_serialize_course_numbers_for_bulk( "is_learning_material": False, "resource_age_date": datetime(2024, 1, 1, 1, 1, 1, 0, tzinfo=UTC), "featured_rank": None, + "is_incomplete_or_stale": False, **LearningResourceSerializer(resource).data, } expected_data["course"]["course_numbers"][0] = {