LOGIN

Collaborizm Version 1 Matching Algorithm

Showing off a relic, but a good relic.

JOIN PROJECT
1 Contributions Made
0 Milestones
Software
Learning
 
Completed Successfully
OUR TEAM
Ersin AkkayaSoftware Engineering
VIEW ALL 1 MEMBERS
JOIN PROJECT
SPONSORS
Sponsor This Project
STORY

Project Overview

I have been with Collaborizm since the beginning helping as a Freelancer! I am a backend engineer with robust experience with node.js, REST API’s, algorithms, dev opps, and more.

The Project I helped with

I helped the Collaborizm team dramatically reduce the stress of their matching algorithm in version 1.0

How it Works

  1. Node.js backend application ran on isolated web server and imported user matching criteria. The node application crunched data on the fly compared to the previous design which was a chron job that implored much too much stress on the application and crashed the version 1.0 platform.

  2. The node.js application tied into the initial v1 front end, which enabled effortless crunching of matching scores between users for any users online.

The result

A polished node.js api that enabled version 1.0 to stay afloat. This was an important part of the Collaborizm Journey!

var mysql = require('./db/mysql')
,_ = require('underscore');

module.exports.index = function (req, res) {
	body = {
		success: true,
		message: "Welcome to the matching algorithm page"
	}
	res.setHeader('Access-Control-Allow-Origin','*');
	res.status(200).send(body)
};

module.exports.test = function (req, res) {
	body = {
		success: true,
	}
	res.setHeader('Access-Control-Allow-Origin','*');
	res.status(200).send(body)
};

module.exports.get_match_results = function(req, res){
	var uid = req.params.uid;

	if(typeof(req.body.uids_to_match) == 'undefined'){
		body = {
			success: false,
			message: "Form data is empty! use Content-Type:application/x-www-form-urlencoded"
		};
		res.setHeader('Access-Control-Allow-Origin','*');
		res.status(200).send(body);
	}
	else{
		var uid_array = req.body.uids_to_match.split(",");

		if(uid_array.length == 0){
			body = {
				success: false,
				message: "uids needs to be provided!"
			}
			res.setHeader('Access-Control-Allow-Origin','*');
			res.status(200).send(body);
			return;
		}
		
		for (var i=0; i < uid_array.length; i++) {
			if(uid_array[i] == ""){
				uid_array.splice(i,1);	
			}
		};
		uid_array.push(uid);

		var sql = "SELECT u.id, u.latitude, u.longitude, u.max_travel_distance, \
			CONVERT(GROUP_CONCAT(DISTINCT s.skill_id SEPARATOR ',') USING 'utf8') AS has_skill, \
			CONVERT(GROUP_CONCAT(DISTINCT c.tag_id SEPARATOR ',') USING 'utf8') AS seeking_skill \
			FROM bf_users u \
			LEFT JOIN bf_user_skills s ON s.user_id = u.id AND s.disabled=0 \
			LEFT JOIN bf_ideal_collaborator_skills c ON c.user_id = u.id \
			WHERE \
			u.id IN("+uid_array.join(",")+") \
			AND s.disabled=0 \
			GROUP BY u.id";
			console.log(sql);	
		mysql.connection(function(err, db){
			db.query(sql, function(err, results){
				if(err){
					body = {
						success: false,
						message: "error on query",
						sql: sql,
						error: err,
					};
					res.setHeader('Access-Control-Allow-Origin','*');
					res.status(200).send(body);
					return;
				}
				else{
					var user = {};
					_.each(results, function(result){
						result.latitude  = (result.latitude == null)? 0.0000 : result.latitude;
						result.longitude = (result.longitude == null)? 0.0000 : result.longitude;
						result.max_travel_distance = (result.max_travel_distance == null)? 15 : result.max_travel_distance;
						result.has_skill = (result.has_skill != null)? result.has_skill.split(',') : [];
						result.seeking_skill = (result.seeking_skill != null)? result.seeking_skill.split(',') : [];
						if(result.id == uid){
							user = result;	
						}
					});

					if(typeof(user.id) == 'undefined'){
						body = {
						success: false,
						message: "user " + uid + " not found"
					};
					res.setHeader('Access-Control-Allow-Origin','*');
					res.status(200).send(body);
					return;
					}
					var matching_scores = {};
					_.each(results, function(matching_user){
						if(matching_user.id != uid){
							console.log("user is:");
							console.log(user);
							console.log("matching_user is:");
							console.log(matching_user);
							matching_scores[matching_user.id] = getMatchScore(user, matching_user);
						}
					});
					console.log(matching_scores);
					body = {
						success: true,
						matching_scores: matching_scores
					}
					res.setHeader('Access-Control-Allow-Origin','*');
					res.status(200).send(body);
				}
			});
		});
	}
}

function getMatchScore(user, matching_user){
	if(
		user.has_skill.length == 0 || 
		user.seeking_skill.length == 0 || 
		user.latitude == 0.0000 ||
		user.longitude == 0.0000 ||
		matching_user.has_skill.length == 0 ||
		matching_user.seeking_skill.length == 0 ||
		matching_user.latitude == 0.0000 ||
		matching_user.longitude == 0.0000
	){
		return -1;
	}
	else{
		var user_matching_skill_score = getMatchingSkillScore(user, matching_user);
	    console.log("user_matching_skill_score: " + user_matching_skill_score);
	    var user_proximity_score = getProximityScore(user, matching_user);
	    console.log("user_proximity_score: " + user_proximity_score);
	    var user_matching_score = user_proximity_score * 40 + user_matching_skill_score * 60;
	    console.log("user_matching_score:" + user_matching_score);
	    var matching_user_matching_skill_score = getMatchingSkillScore(matching_user, user);
	    console.log("matching_user_matching_skill_score: " + matching_user_matching_skill_score);
	    var matching_user_proximity_score = getProximityScore(matching_user, user);
	    console.log("matching_user_proximity_score: " + matching_user_proximity_score);
	    var matching_user_matching_score = matching_user_proximity_score * 40 + matching_user_matching_skill_score * 60;
	    console.log("matching_user_matching_score:" + matching_user_matching_score);
	    var final_matching_score = (user_matching_score + matching_user_matching_score) / 2;

	    return Math.round(final_matching_score) + (matching_user.id % 8);
	}
}

function getScorePoint(no_of_matches){
    var score = 0.7;
    if(no_of_matches > 4){
        score = 1.0;
    }
    else if(no_of_matches > 3){
        score = 0.96;
    }
    else if(no_of_matches > 2){
        score = 0.95;   
    }
    else if(no_of_matches > 1){
        score = 0.9;
    }
    else if(no_of_matches > 0){
        score = 0.8;
    }
    else{
        score = 0.45;
    }
    return score;
}

function getMatchingSkillScore(user, matching_user){
	var matches = _.intersection(user.seeking_skill,matching_user.has_skill);
	var no_of_matches_for_user = matches.length;
    console.log("no_of_matches_for_user: " + no_of_matches_for_user);
    //var no_of_matches_for_matching_user = intersection_destructive(matching_user.seeking_skill, user.has_skill);
    //console.log("no_of_matches_for_matching_user: " + no_of_matches_for_matching_user);
    var user_score = getScorePoint(no_of_matches_for_user);
    //var matching_user_score = getScorePoint(no_of_matches_for_matching_user);
    //return (user_score + matching_user_score) / 2;
    return user_score;
}

/*
@todo
- test the actual distance with the real coordinates to make sure
- create fake data to test the proximity score
- make sure algorithm works properly 
- don't forget to get the average score for both user
- try to simplify this mess

- create the other one and get the actual results
- do some tests with the real mysql data
- fix some complications coming from the actual database
- test it with the http requests-response
- put some checks on no user found cases
*/

//user.latitude,longitude,max_travel_distance
function getProximityScore(user, matching_user){
	var distance = getDistance(user.latitude, user.longitude, matching_user.latitude, matching_user.longitude);
	console.log("distance is: " + distance);
	var proximity_score = 0.5;

	if(distance <= 15){
		proximity_score = 1.0;
	}
	else if(distance <= 50){
		if(user.max_travel_distance <= 15){
			proximity_score = 0.9;
			
		}
		else {
			proximity_score = 1.0;
		}
	}
	else if(distance <= 250){
		if(user.max_travel_distance <= 15){
			proximity_score = 0.8;
		}
		else if(user.max_travel_distance <= 50){
			proximity_score = 0.9;
		}
		else {
			proximity_score = 1.0;
		}
	}
	else if(distance <= 2500){
		if(user.max_travel_distance <= 15){
			proximity_score = 0.7;
		}
		else if(user.max_travel_distance <= 50){
			proximity_score = 0.8;
		}
		else if(user.max_travel_distance <= 250){
			proximity_score = 0.9;
		}
		else {
			proximity_score = 1.0;
		}
	}
	else if(distance <= 5000){
		if(user.max_travel_distance <= 15){
			proximity_score = 0.6;
		}
		else if(user.max_travel_distance <= 50){
			proximity_score = 0.7;
		}
		else if(user.max_travel_distance <= 250){
			proximity_score = 0.8;
		}
		else if(user.max_travel_distance <= 2500){
			proximity_score = 0.9;
		}
		else{
			proximity_score = 1.0;
		}
	}
	else {
		if(user.max_travel_distance <= 15){
			proximity_score = 1.0;
		}
		else if(user.max_travel_distance <= 50){
			proximity_score = 0.99;
		}
		else if(user.max_travel_distance <= 250){
			proximity_score = 0.95;
		}
		else if(user.max_travel_distance <= 2500){
			proximity_score = 0.91;
		}
		else if(user.max_travel_distance <= 5000){
			proximity_score = 0.88;
		}
		else{
			proximity_score = 0.82;
		}
	}
	return proximity_score;
}

function getDistance(lat1, lon1, lat2, lon2) {
  var R = 6371;
  var a = 
     0.5 - Math.cos((lat2 - lat1) * Math.PI / 180)/2 + 
     Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * 
     (1 - Math.cos((lon2 - lon1) * Math.PI / 180))/2;

  return R * 2 * Math.asin(Math.sqrt(a)) * 1.609;
}

Discuss Collaborizm Version 1 Matching Algorithm

Start a discussion...
DISCOVER
CHAT
HIRE
ACTIVITY
FEED
1
0
New Post
Help
Write something before you submit it!
Photo updated
Request Sent!
Updated
Copied to Clipboard