Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions data/projects.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -503,7 +503,7 @@
"starter_code": "starter_code/ai_resume_analyzer.py"
},
{
"id": 11,
"id": 21,
"title": "Number Guessing Game",
"skills": [
"Python"
Expand Down Expand Up @@ -576,7 +576,7 @@
"starter_code": "starter_code/email_automation.py"
},
{
"id": 13,
"id": 22,
"title": "Quiz App",
"skills": [
"HTML",
Expand Down
10 changes: 7 additions & 3 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
34 changes: 22 additions & 12 deletions utils/recommender.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,30 @@

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(",")
if s.strip()
]
return [SKILL_ALIASES.get(skill, skill) for skill in raw_skills]


# ---------------------------------------------------------------------------
# Scoring
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -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"]
Expand Down Expand Up @@ -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 = []

Expand All @@ -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.")
Expand Down
Loading