From ca6272a50fd2fa7723111f906d01b09985dc3e4c Mon Sep 17 00:00:00 2001 From: Attiya Ishaque Date: Fri, 3 Feb 2023 16:26:48 +0500 Subject: [PATCH] feat: [VAN-1260] Program recomendation based on course (#31663) --- .../learner_recommendations/serializers.py | 4 +- .../learner_recommendations/utils.py | 86 ++++++++++++++++++- .../learner_recommendations/views.py | 2 +- 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/learner_recommendations/serializers.py b/lms/djangoapps/learner_recommendations/serializers.py index e2a3d33762..c257ceae97 100644 --- a/lms/djangoapps/learner_recommendations/serializers.py +++ b/lms/djangoapps/learner_recommendations/serializers.py @@ -45,7 +45,7 @@ class RecommendedProgramSerializer(serializers.Serializer): marketingUrl = serializers.URLField(source="marketing_url") coursesCount = serializers.IntegerField(source="courses_count") pacingType = serializers.CharField(source="pacing_type") - weeksToComplete = serializers.IntegerField(source="weeks_to_complete") + weeksToComplete = serializers.CharField(source="weeks_to_complete") minHours = serializers.IntegerField(source="min_hours") maxHours = serializers.IntegerField(source="max_hours") type = serializers.CharField() @@ -57,7 +57,7 @@ class RecommendationsSerializer(serializers.Serializer): courses = serializers.ListField( child=RecommendedCourseSerializer(), allow_empty=True ) - # programUpsell = RecommendedProgramSerializer(source="program_upsell") use this for VAN-1260 + programUpsell = RecommendedProgramSerializer(source="program_upsell") isControl = serializers.BooleanField( source="is_control", default=None diff --git a/lms/djangoapps/learner_recommendations/utils.py b/lms/djangoapps/learner_recommendations/utils.py index 9dd190b668..d202848c5b 100644 --- a/lms/djangoapps/learner_recommendations/utils.py +++ b/lms/djangoapps/learner_recommendations/utils.py @@ -9,8 +9,8 @@ from algoliasearch.search_client import SearchClient from django.conf import settings from common.djangoapps.student.models import CourseEnrollment -from openedx.core.djangoapps.catalog.utils import get_course_data - +from openedx.core.djangoapps.catalog.utils import get_course_data, get_programs +from lms.djangoapps.program_enrollments.api import fetch_program_enrollments_by_student log = logging.getLogger(__name__) @@ -85,6 +85,34 @@ def _has_country_restrictions(product, user_country): return user_country in block_list or (bool(allow_list) and user_country not in allow_list) +def _get_program_duration(weeks): + """ + Helper method that returns the program duration in textual form. + """ + total_months = round(weeks / 4) + + if total_months < 1: + return f'{total_months} weeks' + + if 1 <= total_months < 12: + return f'{total_months} months' + + total_years = round(total_months / 12) + total_remainder_months = round(total_months % 12) + + if total_remainder_months == 0: + return f'{total_years} years' + + if total_years == 1 and total_remainder_months == 1: + return f'1 year {total_remainder_months} months' + + if total_remainder_months == 1: + return f'{total_years} years 1 months' + + else: + return f'{total_years} years {total_remainder_months} months' + + def get_active_course_run(course_data): """Helper method to get course active run""" active_course_runs = [ @@ -214,3 +242,57 @@ def filter_recommended_courses( filtered_recommended_courses.append(course_data) return filtered_recommended_courses + + +def get_programs_based_on_course(request_course, country_code, user): + """ + Returns a program for the course. If a course is part of multiple programs, + this function returns the program with the highest price. + """ + max_price, max_price_program = 0, {} + programs = get_programs(course=request_course) + + if not programs: + return None + + for program in programs: + if program.get('status') != 'active' or _has_country_restrictions(program, country_code): + continue + + price = program['price_ranges'][0]['total'] + if price > max_price: + if fetch_program_enrollments_by_student(program_uuids=[program.get('uuid')], user=user).exists(): + continue + + course_keys = [ + course['key'] + for course in program.get('courses', []) + if course.get('key') and course.get('key') != request_course + ] + if _remove_user_enrolled_course_keys(user, course_keys): + max_price_program = program + max_price = price + + if not max_price_program: + return None + + course_pacing_type, total_weeks_to_complete = '', 0 + for course in max_price_program.get('courses'): + for course_run in course.get('course_runs'): + if course_run.get('status') == 'published': + if not course_pacing_type: + course_pacing_type = course_run.get("pacing_type") + total_weeks_to_complete += int(course_run.get("weeks_to_complete")) + + program_upsell = { + "title": max_price_program.get('title'), + "marketing_url": max_price_program.get('marketing_url'), + "courses_count": len(max_price_program.get('courses')), + "pacing_type": course_pacing_type, + "weeks_to_complete": _get_program_duration(total_weeks_to_complete), + "min_hours": max_price_program.get('min_hours_effort_per_week'), + "max_hours": max_price_program.get('max_hours_effort_per_week'), + "type": max_price_program.get('type'), + } + + return program_upsell diff --git a/lms/djangoapps/learner_recommendations/views.py b/lms/djangoapps/learner_recommendations/views.py index 8bf7310fa5..cd5ea52bb6 100644 --- a/lms/djangoapps/learner_recommendations/views.py +++ b/lms/djangoapps/learner_recommendations/views.py @@ -122,7 +122,7 @@ class AmplitudeRecommendationsView(APIView): return Response( RecommendationsSerializer( { - # "program_upsell": program_upsell, // pass program_upsell here for VAN-1260 + "program_upsell": None, "courses": recommended_courses, "is_control": is_control, }