From bd370a93c4994c4f3ecb8ba22646fff10752777c Mon Sep 17 00:00:00 2001 From: v0lv0 Date: Sat, 24 Oct 2020 15:05:17 -0400 Subject: [PATCH 01/16] matches uses len rather than size --- src/matching/team_recommendations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matching/team_recommendations.py b/src/matching/team_recommendations.py index 22b880bba..8e4b1883f 100644 --- a/src/matching/team_recommendations.py +++ b/src/matching/team_recommendations.py @@ -116,7 +116,7 @@ def get_team_recommendations(email): # GET matches.append(m) # if there are too many matches, reduce it base on seriousness - if matches.size > 20: + if len(matches) > 20: for team in matches: if (abs(team["seriousness"] - seriousness)) > 2: matches.remove(team) From 9b3279ef0c4cd344c3211a4bc0360d950cef056a Mon Sep 17 00:00:00 2001 From: v0lv0 Date: Sat, 24 Oct 2020 15:21:20 -0400 Subject: [PATCH 02/16] remove meta from return --- src/matching/team_recommendations.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/matching/team_recommendations.py b/src/matching/team_recommendations.py index 8e4b1883f..44a70ef74 100644 --- a/src/matching/team_recommendations.py +++ b/src/matching/team_recommendations.py @@ -127,6 +127,7 @@ def get_team_recommendations(email): # GET return {"message": "No recommendations found"}, 404 for team in matches: + del team["meta"] team["team_id"] = team.pop("_id") return {"matches": matches}, 200 From bf39d26ccc3e73db0a8081a08f1acaa0ea8e2d2a Mon Sep 17 00:00:00 2001 From: Jason Cheng Date: Sat, 24 Oct 2020 15:38:17 -0400 Subject: [PATCH 03/16] User endpoint update (#37) * can now change the name of a team * added shortuuid module into requirements.txt * /users/profile returns the user's id (uuid string) if the user is not in a team the team_id field will be an empty string * merged with develop * removed double imports * standardize field names in /user/profiles /teams /teams/ * combined .pop() into 1 line * update user endpoint to return user_id for each user object and set default value of seriousness to 3 * deleted a comment --- src/flaskapp/api.py | 4 ++-- src/users/user_profile.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/flaskapp/api.py b/src/flaskapp/api.py index e3b96e5ab..c60fcd097 100644 --- a/src/flaskapp/api.py +++ b/src/flaskapp/api.py @@ -56,7 +56,7 @@ def users(email): interests = [] bio = "" github = "" - seriousness = -1 # -1 if users doesn't have one + seriousness = 3 if "prizes" in data: prizes = format_string(data["prizes"]) @@ -73,7 +73,7 @@ def users(email): try: seriousness = int(data["seriousness"]) except ValueError: - seriousness = -1 + pass return create_user_profile( email, prizes=prizes, diff --git a/src/users/user_profile.py b/src/users/user_profile.py index a2d33aa86..b48be6d34 100644 --- a/src/users/user_profile.py +++ b/src/users/user_profile.py @@ -39,6 +39,8 @@ def get_user_profiles(args): # GET else: users = list(coll("users").find({}).limit(limit)) + for user in users: + user["user_id"] = user.pop("_id") return {"user_profiles": users}, 200 @@ -56,7 +58,7 @@ def create_user_profile(email, **kwargs): # POST 6. interests (list of str) - optional (AR/VR, BlockChain, Communications, CyberSecurity, DevOps, Fintech, Gaming, Healthcare, IoT, LifeHacks, ML/AI, Music, Productivity, Social Good, Voice Skills) - 7. seriousness (enum {i.e. int - 1-5}) - optional + 7. seriousness (enum {i.e. int - 1-5}) - optional | default value is 3 (neutral) Returns: User profile object (dict) From 6016e1b90f4a9e326defe1328a748d2bb6594158 Mon Sep 17 00:00:00 2001 From: v0lv0 Date: Sat, 31 Oct 2020 13:28:30 -0400 Subject: [PATCH 04/16] changed the way skills are being added. no more nested for loops --- src/matching/team_recommendations.py | 63 ++++++++++------------------ 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/src/matching/team_recommendations.py b/src/matching/team_recommendations.py index 44a70ef74..e229c3bd0 100644 --- a/src/matching/team_recommendations.py +++ b/src/matching/team_recommendations.py @@ -16,57 +16,38 @@ def get_team_recommendations(email): # GET user = coll("users").find_one({"_id": email}) if not user: - return {"message": "Invalid user"}, 403 - user_in_a_team = coll("users").find_one({"_id": email, "hasateam": True}) - if user_in_a_team: + return {"message": "Invalid user"}, + + if user["hasateam"]: return {"message": "User in a team"}, 400 # basic info about users - if "skills" not in user or not user["skills"]: - skills = [] - else: - skills = user["skills"] - if "interests" not in user or not user["interests"]: - interests = [] - else: - interests = user["interests"] - if "prizes" not in user or not user["prizes"]: - prizes = [] - else: - prizes = user["prizes"] - if "seriousness" not in user or not user["seriousness"]: - seriousness = 0 - else: - seriousness = user["seriousness"] + skills = user["skills"] + interests = user["interests"] + prizes = user["prizes"] + seriousness = user["seriousness"] names = set() matches = [] # match for skill needed_skills = [] - # judging if the user if frontend or backend - frontend_languages = ["html", "css", "javascript", "php", "typscript"] - backend_languages = ["java", "php", "ruby", "python", "c", "c++", "sql", "node.js"] - front_or_back = "none" - for original_skill in skills: - original_skill = original_skill.lower() - if original_skill in frontend_languages: - if front_or_back == "back": - front_or_back = "none" - break - else: - front_or_back = "front" - if original_skill in backend_languages: - if front_or_back == "front": - front_or_back = "none" - break - else: - front_or_back = "front" - # give backend suggestions if only know frontend, vice versa - if front_or_back == "front": - needed_skills.append(frontend_languages) - if front_or_back == "back": + frontend_languages = set(["html", "css", "javascript", "php", "typscript"]) + backend_languages = set(["java", "php", "ruby", "python", "c", "c++", "sql", "node.js"]) + # judging if the user if frontend or backend, and give backend suggestions if only know frontend, vice versa + skill_set = set(skills) + front_num = len(skill_set.intersection(skills)) + back_num = len(skill_set.intersection(skills)) + + if front_num > (back_num * 5/8): + if back_num < 3: + needed_skills.append(backend_languages) + else: + if front_num < 3: + needed_skills.append(frontend_languages) + if len(needed_skills): needed_skills.append(backend_languages) + needed_skills.append(frontend_languages) for skill in needed_skills: # collection of all the team's skills From 1ca03bcec5a6327e32bc4b837e2c37e64ab953e4 Mon Sep 17 00:00:00 2001 From: v0lv0 Date: Sat, 31 Oct 2020 15:26:12 -0400 Subject: [PATCH 05/16] removed self, inv sent, and inv received from the result --- src/matching/team_recommendations.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/matching/team_recommendations.py b/src/matching/team_recommendations.py index e229c3bd0..926f29dc0 100644 --- a/src/matching/team_recommendations.py +++ b/src/matching/team_recommendations.py @@ -16,10 +16,8 @@ def get_team_recommendations(email): # GET user = coll("users").find_one({"_id": email}) if not user: - return {"message": "Invalid user"}, + return {"message": "Invalid user"}, 403 - if user["hasateam"]: - return {"message": "User in a team"}, 400 # basic info about users skills = user["skills"] interests = user["interests"] @@ -31,7 +29,6 @@ def get_team_recommendations(email): # GET # match for skill needed_skills = [] - frontend_languages = set(["html", "css", "javascript", "php", "typscript"]) backend_languages = set(["java", "php", "ruby", "python", "c", "c++", "sql", "node.js"]) # judging if the user if frontend or backend, and give backend suggestions if only know frontend, vice versa @@ -39,7 +36,7 @@ def get_team_recommendations(email): # GET front_num = len(skill_set.intersection(skills)) back_num = len(skill_set.intersection(skills)) - if front_num > (back_num * 5/8): + if front_num > (back_num * len(frontend_languages) / len(backend_languages)): if back_num < 3: needed_skills.append(backend_languages) else: @@ -107,8 +104,27 @@ def get_team_recommendations(email): # GET if not matches: return {"message": "No recommendations found"}, 404 + current_team = coll("teams").find_one({"_id": user["team_id"]}) + matches.remove(current_team) + + inv_in = current_team["incoming_inv"] + inv_out = current_team["outgoing_inv"] + inv_in.remove(inv_in & inv_out) + inv_out.remove(inv_in & inv_out) + for i in inv_in: + try: + matches.remove(i) + except ValueError: + pass + + for i in inv_out: + try: + matches.remove(i) + except ValueError: + pass + for team in matches: del team["meta"] team["team_id"] = team.pop("_id") - return {"matches": matches}, 200 + return {"matches": matches}, 200 From 750a2c8584d02753b6f54fdd1789e2e75bcc8b09 Mon Sep 17 00:00:00 2001 From: v0lv0 Date: Sat, 31 Oct 2020 16:01:05 -0400 Subject: [PATCH 06/16] fixed the way to remove teams exist both invin and invout --- src/matching/team_recommendations.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/matching/team_recommendations.py b/src/matching/team_recommendations.py index 926f29dc0..85b6d6766 100644 --- a/src/matching/team_recommendations.py +++ b/src/matching/team_recommendations.py @@ -109,15 +109,10 @@ def get_team_recommendations(email): # GET inv_in = current_team["incoming_inv"] inv_out = current_team["outgoing_inv"] - inv_in.remove(inv_in & inv_out) - inv_out.remove(inv_in & inv_out) - for i in inv_in: - try: - matches.remove(i) - except ValueError: - pass - for i in inv_out: + inv_sum = set(inv_in) + set(inv_out) + + for i in inv_sum: try: matches.remove(i) except ValueError: From 7fd2fa1e4fb98a63e37b8b34cb0eca1aca54ab3d Mon Sep 17 00:00:00 2001 From: v0lv0 Date: Sat, 31 Oct 2020 16:18:03 -0400 Subject: [PATCH 07/16] using set.update --- src/matching/team_recommendations.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/matching/team_recommendations.py b/src/matching/team_recommendations.py index 85b6d6766..a6f6333b3 100644 --- a/src/matching/team_recommendations.py +++ b/src/matching/team_recommendations.py @@ -110,7 +110,9 @@ def get_team_recommendations(email): # GET inv_in = current_team["incoming_inv"] inv_out = current_team["outgoing_inv"] - inv_sum = set(inv_in) + set(inv_out) + inv_sum = set() + inv_sum.update(set(inv_in)) + inv_sum.update(set(inv_out)) for i in inv_sum: try: From 9b792777487e42244478bacab5482f2c299dbb2f Mon Sep 17 00:00:00 2001 From: v0lv0 Date: Sat, 31 Oct 2020 16:39:21 -0400 Subject: [PATCH 08/16] append changed to update --- src/matching/team_recommendations.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/matching/team_recommendations.py b/src/matching/team_recommendations.py index a6f6333b3..f2388e082 100644 --- a/src/matching/team_recommendations.py +++ b/src/matching/team_recommendations.py @@ -1,5 +1,5 @@ from src.flaskapp.db import coll -from src.flaskapp.util import aggregate_team_meta +from src.flaskapp.util import format_team_object def get_team_recommendations(email): # GET @@ -28,7 +28,7 @@ def get_team_recommendations(email): # GET matches = [] # match for skill - needed_skills = [] + needed_skills = set() frontend_languages = set(["html", "css", "javascript", "php", "typscript"]) backend_languages = set(["java", "php", "ruby", "python", "c", "c++", "sql", "node.js"]) # judging if the user if frontend or backend, and give backend suggestions if only know frontend, vice versa @@ -38,13 +38,13 @@ def get_team_recommendations(email): # GET if front_num > (back_num * len(frontend_languages) / len(backend_languages)): if back_num < 3: - needed_skills.append(backend_languages) + needed_skills.update(backend_languages) else: if front_num < 3: - needed_skills.append(frontend_languages) + needed_skills.update(frontend_languages) if len(needed_skills): - needed_skills.append(backend_languages) - needed_skills.append(frontend_languages) + needed_skills.update(backend_languages) + needed_skills.update(frontend_languages) for skill in needed_skills: # collection of all the team's skills @@ -122,6 +122,5 @@ def get_team_recommendations(email): # GET for team in matches: del team["meta"] - team["team_id"] = team.pop("_id") - return {"matches": matches}, 200 + return {"matches": [format_team_object(team) for team in matches]}, 200 From 1f82f9d37ff9cd37b28780fb7aa117926b302b56 Mon Sep 17 00:00:00 2001 From: Anitej Biradar Date: Sat, 31 Oct 2020 16:48:20 -0400 Subject: [PATCH 09/16] remove .lower() on format, wiki update (#45) --- src/flaskapp/util.py | 4 ++-- teamRU.wiki | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/flaskapp/util.py b/src/flaskapp/util.py index 8a0cd4956..96a932bf7 100644 --- a/src/flaskapp/util.py +++ b/src/flaskapp/util.py @@ -19,12 +19,12 @@ def format_string(input_): a formatted list is returned if a list is provided """ if isinstance(input_, str): - return input_.strip().lower() + return input_.strip() if isinstance(input_, list): res = [] for element in input_: if isinstance(element, str): - res.append(element.strip().lower()) + res.append(element.strip()) else: # NOTE when the element is not of string typ (we can handle this case different if necessary) res.append(element) diff --git a/teamRU.wiki b/teamRU.wiki index db5b7ff37..336659c20 160000 --- a/teamRU.wiki +++ b/teamRU.wiki @@ -1 +1 @@ -Subproject commit db5b7ff37dd04ca68ae2c27f3c8323f73eae65ef +Subproject commit 336659c20403ebd9abf7a92b0f9e01bf05ecc201 From 425e5eb0c388827f591e57c7e6dcb956ae905ef7 Mon Sep 17 00:00:00 2001 From: Jason Cheng Date: Sun, 1 Nov 2020 22:45:56 -0500 Subject: [PATCH 10/16] Bugs/user leave catch (#46) * can now change the name of a team * added shortuuid module into requirements.txt * /users/profile returns the user's id (uuid string) if the user is not in a team the team_id field will be an empty string * merged with develop * removed double imports * standardize field names in /user/profiles /teams /teams/ * combined .pop() into 1 line * update user endpoint to return user_id for each user object and set default value of seriousness to 3 * deleted a comment * fixed so that user cannot invite themselves or send duplicate invites. Also after a merge takes place, the backend will proactively remove any lingering incoming and outgoing invites of the merged in team. * backend now proactively removes outgoing and incoming invites of a team once this team reaches full capacity (4 people) * users are now put back into their own team on leave and prevents users from leaving if they are alone --- requirements.txt | 1 + src/flaskapp/api.py | 6 +++++- src/teams/user_leave.py | 8 +++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index cfdfe326d..6329237b2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ certifi==2020.6.20 cfn-flip==1.2.3 chardet==3.0.4 click==7.1.2 +coolname==1.1.0 Deprecated==1.2.10 dnspython==2.0.0 durationpy==0.5 diff --git a/src/flaskapp/api.py b/src/flaskapp/api.py index 3756ee099..8f2dbbfa6 100644 --- a/src/flaskapp/api.py +++ b/src/flaskapp/api.py @@ -1,3 +1,5 @@ +from coolname import generate_slug + from flask import Flask, request from flask_cors import CORS @@ -178,7 +180,9 @@ def mark_team_complete(email, team_id): @app.route("/teams//leave", methods=["PUT"]) @authenticate def leave(email, team_id): - return user_leave(email, team_id) + response = user_leave(email, team_id) + create_team_profile(generate_slug(), email, "Edit Me :D", [], []) + return response ############################## UNIFY ############################## diff --git a/src/teams/user_leave.py b/src/teams/user_leave.py index 07f1cd26d..da70c4789 100644 --- a/src/teams/user_leave.py +++ b/src/teams/user_leave.py @@ -22,7 +22,10 @@ def user_leave(email, team_id): # POST team_size = len(team["members"]) if team_size == 1: - coll("teams").delete_one({"_id": team["_id"]}) + # coll("teams").delete_one({"_id": team["_id"]}) + + # Maybe also turn this team into complete? + return {"message": "A team must have one member"}, 400 else: coll("teams").update_one( {"_id": team["_id"]}, @@ -36,7 +39,10 @@ def user_leave(email, team_id): # POST }, }, ) + + # Reset the User coll("users").update_one( {"_id": email}, {"$set": {"hasateam": False, "team_id": ""}} ) + return {"message": "Success"}, 200 From e39c640acffea0f5a3bcd2951a59ae23b7fdd869 Mon Sep 17 00:00:00 2001 From: Anitej Biradar Date: Sat, 9 Jan 2021 16:10:02 -0500 Subject: [PATCH 11/16] Dev weekend changes (#47) * fix bug with removing unwanted matches * remove unwanted teams from /teams, add total count * revert invited teams removal from /teams, add invite field to team response * add LICENSE --- LICENSE | 21 +++++++++++++ src/flaskapp/api.py | 2 +- src/matching/team_recommendations.py | 47 ++++++++++++++++++---------- src/teams/team_profile.py | 19 ++++++++--- 4 files changed, 67 insertions(+), 22 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..fbce13486 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 - 2021 HackRU + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/flaskapp/api.py b/src/flaskapp/api.py index 8f2dbbfa6..9480adf54 100644 --- a/src/flaskapp/api.py +++ b/src/flaskapp/api.py @@ -130,7 +130,7 @@ def teams(email): except: limit = 10 - return get_team_profiles(search, offset, limit) + return get_team_profiles(email, search, offset, limit) if request.method == "POST": data = request.get_json(silent=True) diff --git a/src/matching/team_recommendations.py b/src/matching/team_recommendations.py index f2388e082..3868448d1 100644 --- a/src/matching/team_recommendations.py +++ b/src/matching/team_recommendations.py @@ -100,25 +100,38 @@ def get_team_recommendations(email): # GET matches.remove(team) names.remove(team["_id"]) - # return - if not matches: - return {"message": "No recommendations found"}, 404 - + # current_team = coll("teams").find_one({"_id": user["team_id"]}) + # try: + # matches.remove(current_team) + # except ValueError: + # pass + + # inv_in = current_team["incoming_inv"] + # inv_out = current_team["outgoing_inv"] + + # inv_sum = set() + # inv_sum.update(set(inv_in)) + # inv_sum.update(set(inv_out)) + + # for i in inv_sum: + # try: + # matches.remove(i) + # except ValueError: + # pass + + bad_match_ids = set() + bad_match_ids.add(user["team_id"]) current_team = coll("teams").find_one({"_id": user["team_id"]}) - matches.remove(current_team) - - inv_in = current_team["incoming_inv"] - inv_out = current_team["outgoing_inv"] - - inv_sum = set() - inv_sum.update(set(inv_in)) - inv_sum.update(set(inv_out)) + bad_match_ids.update(current_team["incoming_inv"]) + bad_match_ids.update(current_team["outgoing_inv"]) + good_matches = [] + for team in matches: + if team["_id"] not in bad_match_ids: + good_matches.append(team) + matches = good_matches - for i in inv_sum: - try: - matches.remove(i) - except ValueError: - pass + if not matches: + return {"message": "No recommendations found"}, 404 for team in matches: del team["meta"] diff --git a/src/teams/team_profile.py b/src/teams/team_profile.py index 010b4688d..746590770 100644 --- a/src/teams/team_profile.py +++ b/src/teams/team_profile.py @@ -22,7 +22,7 @@ def get_team_profile(email, team_id): # GET return format_team_object(team), 200 -def get_team_profiles(search, offset, limit): +def get_team_profiles(email, search, offset, limit): """Find teams that are open for new members Give a list of teams that fulfills the requirement and also still open for new members, @@ -40,11 +40,19 @@ def get_team_profiles(search, offset, limit): Return: list of open teams that pass the filter. """ + user = coll("users").find_one({"_id": email}) + team = coll("teams").find_one({"_id": user["team_id"]}, {"meta": False}) + do_not_show = set() + do_not_show.add(team["_id"]) + do_not_show.update(team["outgoing_inv"]) + do_not_show.update(team["incoming_inv"]) + total_teams = coll("teams").find({"complete": False, "_id": {"$ne": team["_id"]}}).count() + all_open_teams = [] if search is None: available_teams = ( coll("teams") - .find({"complete": False}, {"meta": False}) + .find({"complete": False, "_id": {"$ne": team["_id"]}}, {"meta": False}) .sort("_id", 1) .skip(offset) .limit(limit) @@ -56,6 +64,7 @@ def get_team_profiles(search, offset, limit): .find( { "complete": False, + "_id": {"$ne": team["_id"]}, "$or": [ {"desc": {"$regex": ".*" + search + ".*"}}, {"skills": {"$regex": ".*" + search + ".*"}}, @@ -70,10 +79,12 @@ def get_team_profiles(search, offset, limit): ) for team in available_teams: + team["invited"] = team["_id"] in do_not_show all_open_teams.append(format_team_object(team)) + if not all_open_teams: - return {"message": "No open teams"}, 400 - return {"all_open_teams": all_open_teams}, 200 + return {"message": "No open teams", "total_teams": total_teams}, 400 + return {"all_open_teams": all_open_teams, "total_teams": total_teams}, 200 def create_team_profile(team_name, email, team_desc, skills, prizes): From 18fc6a5f5237ccc0cc2b691a499c2c8767120396 Mon Sep 17 00:00:00 2001 From: v0lv0 Date: Sun, 17 Jan 2021 15:51:18 -0500 Subject: [PATCH 12/16] added fuzzy search method, not in use yet --- requirements.txt | 2 ++ src/matching/team_recommendations.py | 46 ++++++++++++++++------------ src/users/user_profile.py | 8 +++++ 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/requirements.txt b/requirements.txt index 6329237b2..04ddf1738 100644 --- a/requirements.txt +++ b/requirements.txt @@ -48,3 +48,5 @@ Werkzeug==0.16.1 wrapt==1.12.1 wsgi-request-logger==0.4.6 zappa==0.51.0 + +fuzzywuzzy~=0.18.0 \ No newline at end of file diff --git a/src/matching/team_recommendations.py b/src/matching/team_recommendations.py index 3868448d1..e6736188b 100644 --- a/src/matching/team_recommendations.py +++ b/src/matching/team_recommendations.py @@ -1,5 +1,32 @@ from src.flaskapp.db import coll from src.flaskapp.util import format_team_object +from fuzzywuzzy import fuzz + + +def parse_user_to_string(user): + if not user: + return {"message": "Invalid user or user not exist"}, 403 + skills = user["skills"] + interests = user["interests"] + prizes = user["prizes"] + bio = user["bio"] + + list_of_fields = skills + list_of_fields.append(interests) + list_of_fields.append(prizes) + list_of_fields.sort() + bio += ''.join(list_of_fields) + + return bio + + +def lv_distance(user, potential_teammates): + user_str = parse_user_to_string(user) + + for each_user in potential_teammates: + team_mate_str = parse_user_to_string(each_user) + token_sort_ratio = fuzz.token_set_ratio(user_str, team_mate_str) + return token_sort_ratio def get_team_recommendations(email): # GET @@ -100,25 +127,6 @@ def get_team_recommendations(email): # GET matches.remove(team) names.remove(team["_id"]) - # current_team = coll("teams").find_one({"_id": user["team_id"]}) - # try: - # matches.remove(current_team) - # except ValueError: - # pass - - # inv_in = current_team["incoming_inv"] - # inv_out = current_team["outgoing_inv"] - - # inv_sum = set() - # inv_sum.update(set(inv_in)) - # inv_sum.update(set(inv_out)) - - # for i in inv_sum: - # try: - # matches.remove(i) - # except ValueError: - # pass - bad_match_ids = set() bad_match_ids.add(user["team_id"]) current_team = coll("teams").find_one({"_id": user["team_id"]}) diff --git a/src/users/user_profile.py b/src/users/user_profile.py index b48be6d34..914bdd8eb 100644 --- a/src/users/user_profile.py +++ b/src/users/user_profile.py @@ -1,6 +1,14 @@ from src.flaskapp.db import coll +def get_partial_profile(email): + user_profile = coll("users").find_one({"_id": email}) + if not user_profile: + return {"message": "User not found"}, 404 + user_profile["user_id"] = user_profile.pop("_id") + return user_profile, 200 + + def get_user_profile(email): # GET """Get user profile From 8d9c1d4caee694b660abac8a4c0d5008e81e8129 Mon Sep 17 00:00:00 2001 From: v0lv0 Date: Sun, 17 Jan 2021 16:14:42 -0500 Subject: [PATCH 13/16] fixed return --- src/matching/team_recommendations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/matching/team_recommendations.py b/src/matching/team_recommendations.py index e6736188b..f2d632cbd 100644 --- a/src/matching/team_recommendations.py +++ b/src/matching/team_recommendations.py @@ -23,9 +23,10 @@ def parse_user_to_string(user): def lv_distance(user, potential_teammates): user_str = parse_user_to_string(user) + token_sort_ratio = [] for each_user in potential_teammates: team_mate_str = parse_user_to_string(each_user) - token_sort_ratio = fuzz.token_set_ratio(user_str, team_mate_str) + token_sort_ratio.append( fuzz.token_set_ratio(user_str, team_mate_str)) return token_sort_ratio From 6156ed53a9f8b594afd1fa172b03ac94bb536d3d Mon Sep 17 00:00:00 2001 From: v0lv0 Date: Sun, 7 Feb 2021 13:05:33 -0500 Subject: [PATCH 14/16] match algo complete. TODO: add seriousness? --- src/matching/team_recommendations.py | 120 ++++++++++----------------- 1 file changed, 44 insertions(+), 76 deletions(-) diff --git a/src/matching/team_recommendations.py b/src/matching/team_recommendations.py index f2d632cbd..50952a6b7 100644 --- a/src/matching/team_recommendations.py +++ b/src/matching/team_recommendations.py @@ -6,28 +6,23 @@ def parse_user_to_string(user): if not user: return {"message": "Invalid user or user not exist"}, 403 - skills = user["skills"] + interests = user["interests"] prizes = user["prizes"] bio = user["bio"] - list_of_fields = skills - list_of_fields.append(interests) - list_of_fields.append(prizes) + list_of_fields = interests + list_of_fields.extend(prizes) list_of_fields.sort() bio += ''.join(list_of_fields) return bio -def lv_distance(user, potential_teammates): +def lv_distance(user, user2): user_str = parse_user_to_string(user) - - token_sort_ratio = [] - for each_user in potential_teammates: - team_mate_str = parse_user_to_string(each_user) - token_sort_ratio.append( fuzz.token_set_ratio(user_str, team_mate_str)) - return token_sort_ratio + team_mate_str = parse_user_to_string(user2) + return fuzz.token_set_ratio(user_str, team_mate_str) def get_team_recommendations(email): # GET @@ -41,6 +36,8 @@ def get_team_recommendations(email): # GET Return: a list of recommended teams to join """ + skills_weight = 1.2 + seriousness_weight = 1.1 user = coll("users").find_one({"_id": email}) if not user: @@ -48,22 +45,36 @@ def get_team_recommendations(email): # GET # basic info about users skills = user["skills"] - interests = user["interests"] - prizes = user["prizes"] seriousness = user["seriousness"] - names = set() - matches = [] + all_open_teams = coll("teams").aggregate([{"$match": {"complete": False}}]) + all_open_members = coll("users").aggregate([{"$match": {"team_id": {"$all": [all_open_teams]}}}]) + + team_map = dict() + + # map of distances + for member in all_open_members: + team_id = member["team_id"] + dis = lv_distance(user, member) + if team_id in team_map: + team_map[team_id] = [dis] + else: + team_map[team_id].append(dis) + + # average the distance + for list_key in team_map: + member_list = team_map[list_key] + team_map[list_key] = sum(member_list) / float(len(member_list)) # match for skill needed_skills = set() frontend_languages = set(["html", "css", "javascript", "php", "typscript"]) backend_languages = set(["java", "php", "ruby", "python", "c", "c++", "sql", "node.js"]) + # judging if the user if frontend or backend, and give backend suggestions if only know frontend, vice versa skill_set = set(skills) front_num = len(skill_set.intersection(skills)) back_num = len(skill_set.intersection(skills)) - if front_num > (back_num * len(frontend_languages) / len(backend_languages)): if back_num < 3: needed_skills.update(backend_languages) @@ -74,59 +85,16 @@ def get_team_recommendations(email): # GET needed_skills.update(backend_languages) needed_skills.update(frontend_languages) - for skill in needed_skills: - # collection of all the team's skills - complementary_skills_match = coll("teams").aggregate( - [{"$match": {"complete": False, "skills": {"$all": [skill]}}}] - ) - # collections of all the team's interests - if not complementary_skills_match: - continue - for match in complementary_skills_match: - if match['_id'] not in names: - names.add(match['_id']) - matches.append(match) - - # add base on interests - # AR/VR, BlockChain, Communications, CyberSecurity, DevOps, Fintech, Gaming, - # Healthcare, IoT, LifeHacks, ML/AI, Music, Productivity, Social Good, Voice Skills - - # finding team with listed interests, if too much matches, find from teams in the matches - if len(matches) > 50: - for match in matches: - if len(matches) <= 50: - break - team_interests = match["meta"]["interests"] - # team has no common skill - if len(list(set(interests).intersection(set(team_interests)))) == 0: - matches.remove(match) - names.remove(match["_id"]) - else: - for interest in interests: - match = coll("teams").aggregate([{"$match": {"complete": False, "meta.interest": {"$all": [interest]}}}]) - if not match: - continue - for m in match: - if m["_id"] not in names: - names.add(m["_id"]) - matches.append(m) - - # add suggestions base on prize - for prize in prizes: - match = coll("teams").aggregate([{"$match": {"complete": False, "prizes": {"$all": [prize]}}}]) - if not match: - continue - for m in match: - if m["_id"] not in names: - names.add(m["_id"]) - matches.append(m) - - # if there are too many matches, reduce it base on seriousness - if len(matches) > 20: - for team in matches: - if (abs(team["seriousness"] - seriousness)) > 2: - matches.remove(team) - names.remove(team["_id"]) + # find base on skills needed && seriousness + # TODO: can we add seriousness as a field to team. + for team_id in team_map: + target_team = coll("teams").find_one({"_id": team_id}) + target_team_skills = target_team["skills"] + intersection_size = len(set(target_team_skills).intersection(needed_skills)) + team_map[team_id] *= (intersection_size * skills_weight) + + # sort the directory lol + sorted_team_list = sorted(team_map.items(), key=lambda kv: (kv[1], kv[0])) bad_match_ids = set() bad_match_ids.add(user["team_id"]) @@ -134,15 +102,15 @@ def get_team_recommendations(email): # GET bad_match_ids.update(current_team["incoming_inv"]) bad_match_ids.update(current_team["outgoing_inv"]) good_matches = [] - for team in matches: - if team["_id"] not in bad_match_ids: - good_matches.append(team) - matches = good_matches - if not matches: + for team_id in sorted_team_list[:, 0]: + if team_id in bad_match_ids: + good_matches.append(team_id) + + if not good_matches: return {"message": "No recommendations found"}, 404 - for team in matches: + for team in good_matches: del team["meta"] - return {"matches": [format_team_object(team) for team in matches]}, 200 + return {"matches": [format_team_object(team) for team in good_matches]}, 200 From a46d75e4db8e5e42e2ef9a726117ccb1b57b1530 Mon Sep 17 00:00:00 2001 From: v0lv0 Date: Sat, 13 Feb 2021 12:30:13 -0500 Subject: [PATCH 15/16] fixed skill match --- src/matching/team_recommendations.py | 33 +++++++++++++++------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/matching/team_recommendations.py b/src/matching/team_recommendations.py index 50952a6b7..dbf888e98 100644 --- a/src/matching/team_recommendations.py +++ b/src/matching/team_recommendations.py @@ -73,27 +73,30 @@ def get_team_recommendations(email): # GET # judging if the user if frontend or backend, and give backend suggestions if only know frontend, vice versa skill_set = set(skills) - front_num = len(skill_set.intersection(skills)) - back_num = len(skill_set.intersection(skills)) - if front_num > (back_num * len(frontend_languages) / len(backend_languages)): - if back_num < 3: - needed_skills.update(backend_languages) - else: - if front_num < 3: + front_num = len(skill_set.intersection(frontend_languages)) + back_num = len(skill_set.intersection(backend_languages)) + + front_pers = front_num/(front_num+back_num) + back_pers = 1-front_pers + if front_pers > back_pers: + if front_pers < 0.3: needed_skills.update(frontend_languages) - if len(needed_skills): - needed_skills.update(backend_languages) - needed_skills.update(frontend_languages) + else: + needed_skills.update(skill_set) + else: + if front_pers > 0.3: + needed_skills.update(backend_languages) + else: + needed_skills.update(skill_set) - # find base on skills needed && seriousness - # TODO: can we add seriousness as a field to team. for team_id in team_map: target_team = coll("teams").find_one({"_id": team_id}) target_team_skills = target_team["skills"] intersection_size = len(set(target_team_skills).intersection(needed_skills)) team_map[team_id] *= (intersection_size * skills_weight) - - # sort the directory lol + team_seriousness = target_team["meta"]["seriousness"] + team_map[team_id] = team_map[team_id] * (intersection_size * skills_weight) * \ + (team_seriousness * seriousness_weight) sorted_team_list = sorted(team_map.items(), key=lambda kv: (kv[1], kv[0])) bad_match_ids = set() @@ -104,7 +107,7 @@ def get_team_recommendations(email): # GET good_matches = [] for team_id in sorted_team_list[:, 0]: - if team_id in bad_match_ids: + if team_id not in bad_match_ids: good_matches.append(team_id) if not good_matches: From 3de87b8f3ae01793696e55954dbbf1c1ec829b2a Mon Sep 17 00:00:00 2001 From: v0lv0 Date: Sun, 14 Feb 2021 16:35:59 -0500 Subject: [PATCH 16/16] seriousness fixed. --- src/flaskapp/api.py | 11 +++++------ src/matching/team_recommendations.py | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/flaskapp/api.py b/src/flaskapp/api.py index 9480adf54..859378baf 100644 --- a/src/flaskapp/api.py +++ b/src/flaskapp/api.py @@ -44,7 +44,6 @@ def index(): @app.route("/users", methods=["GET", "POST"]) @authenticate def users(email): - if request.method == "GET": # Filter response using query parameters # Might need to add pagination (limit/offset) for this response @@ -136,11 +135,11 @@ def teams(email): data = request.get_json(silent=True) if ( - not data - or "name" not in data - or "desc" not in data - or not data["name"] - or not data["desc"] + not data + or "name" not in data + or "desc" not in data + or not data["name"] + or not data["desc"] ): return {"message": "Required info not found"}, 400 team_name = format_string(data["name"]) diff --git a/src/matching/team_recommendations.py b/src/matching/team_recommendations.py index dbf888e98..e5d3cd40e 100644 --- a/src/matching/team_recommendations.py +++ b/src/matching/team_recommendations.py @@ -96,7 +96,7 @@ def get_team_recommendations(email): # GET team_map[team_id] *= (intersection_size * skills_weight) team_seriousness = target_team["meta"]["seriousness"] team_map[team_id] = team_map[team_id] * (intersection_size * skills_weight) * \ - (team_seriousness * seriousness_weight) + (abs(seriousness-team_seriousness) * seriousness_weight) sorted_team_list = sorted(team_map.items(), key=lambda kv: (kv[1], kv[0])) bad_match_ids = set()