diff --git a/data/projects.json b/data/projects.json index f33cbe0..e1a0112 100644 --- a/data/projects.json +++ b/data/projects.json @@ -416,7 +416,7 @@ "starter_code": "starter_code/survey_form/index.html" }, { - "id": 10, + "id": 20, "title": "API ETL Pipeline", "skills": ["Python", "pandas", "requests"], "level": "Intermediate", @@ -503,7 +503,7 @@ "starter_code": "starter_code/ai_resume_analyzer.py" }, { - "id": 11, + "id": 21, "title": "Number Guessing Game", "skills": [ "Python" @@ -576,7 +576,7 @@ "starter_code": "starter_code/email_automation.py" }, { - "id": 13, + "id": 22, "title": "Quiz App", "skills": [ "HTML", diff --git a/tests/test_basic.py b/tests/test_basic.py index c52f4c1..51d6ecd 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -29,10 +29,14 @@ validate_recommendation_inputs, parse_skills, score_single_project, - WEIGHT_LEVEL, - WEIGHT_INTEREST, - WEIGHT_TIME, + SCORING_WEIGHTS, + VALID_LEVELS, + VALID_TIME_AVAILABILITY, ) + +WEIGHT_LEVEL = SCORING_WEIGHTS["level"] +WEIGHT_INTEREST = SCORING_WEIGHTS["interest"] +WEIGHT_TIME = SCORING_WEIGHTS["time"] from app import app, internal_server_error diff --git a/utils/recommender.py b/utils/recommender.py index 111dddd..8fde5f9 100644 --- a/utils/recommender.py +++ b/utils/recommender.py @@ -49,12 +49,23 @@ def parse_skills(skills_string): """ - Convert a raw comma-separated skills string into - a normalized lowercase list. + Convert a raw skills string into a normalized lowercase list. + Accepts either a JSON array (e.g. '["Python","React"]') or a + comma-separated string (e.g. "JS, HTML5, CSS3"). Example: - "JS, HTML5, CSS3" -> ["javascript", "html", "css"] + '["Python","React"]' -> ["python", "react"] + "JS, HTML5, CSS3" -> ["javascript", "html", "css"] """ + stripped = skills_string.strip() + if stripped.startswith("["): + try: + parsed = json.loads(stripped) + if isinstance(parsed, list): + raw_skills = [str(s).strip().lower() for s in parsed if str(s).strip()] + return [SKILL_ALIASES.get(skill, skill) for skill in raw_skills] + except (json.JSONDecodeError, ValueError): + pass raw_skills = [ s.strip().lower() for s in skills_string.split(",") @@ -62,7 +73,6 @@ def parse_skills(skills_string): ] return [SKILL_ALIASES.get(skill, skill) for skill in raw_skills] - # --------------------------------------------------------------------------- # Scoring # --------------------------------------------------------------------------- @@ -100,7 +110,11 @@ def score_single_project(project, user_skills, level, interest, time_availabilit # Count how many user skills overlap with the # skills required by the current project. matched_skills = sum(1 for skill in user_skills if skill in project_skills) - score += matched_skills * SCORING_WEIGHTS["skill"] + if project_skills: + coverage = matched_skills / len(project_skills) + score += matched_skills * SCORING_WEIGHTS["skill"] * coverage + else: + score += matched_skills * SCORING_WEIGHTS["skill"] if project.get("level", "").lower() == level.lower(): score += SCORING_WEIGHTS["level"] @@ -203,16 +217,12 @@ def get_recommendations(skills_string, level, interest, time_availability): cluster_data = _load_clusters() related = _get_related(top_ids, all_projects, cluster_data) if cluster_data else [] - return { - "recommendations": top_projects, - "related": related, - } + return top_projects VALID_LEVELS = ["beginner", "intermediate", "advanced"] VALID_TIME_AVAILABILITY = ["low", "medium", "high"] - def validate_recommendation_inputs(skills, level, interest, time_availability): errors = [] @@ -226,8 +236,8 @@ def validate_recommendation_inputs(skills, level, interest, time_availability): elif level.strip().lower() not in VALID_LEVELS: errors.append("Invalid experience level. Choose Beginner, Intermediate, or Advanced.") - if not interest or not isinstance(interest, str) or interest.strip().lower() not in VALID_INTERESTS: - errors.append("Please select a valid area of interest.") + if not interest or not isinstance(interest, str) or not interest.strip(): + errors.append("Please select an area of interest.") if not time_availability or not time_availability.strip(): errors.append("Please select your time availability.")