utils/PrecisionRecall.js
function (expectedClasses, actualClasses, logTruePositives ) {

		var ex = []
		var ac = []
		var matchlist = []

		// clean up expected list
		_.each(expectedClasses, function(expected, key, list){ 
			if ((expected.length == 2) || (expected.length == 3))
				ex.push(expected)
		}, this)

		// ac = actualClasses
		// // filtering actual classes		
		// _.each(actualClasses, function(actual, key, list){ 
		// 	var found = _.filter(ac, function(num){ return ((num[0] == actual[0]) && (this.intersection(num[1], actual[1]) == true)) }, this);
		// 	if (found.length == 0)
		// 		ac.push(actual)
		// }, this)

		// console.log(JSON.stringify(actualClasses, null, 4))

		// var ac = this.uniquecandidate(this.uniqueaggregate(actualClasses))
		var ac = actualClasses


		// filling interdependencies between labels 
		// for every candidate (actual) it looks for intersection between actual labels with different 
		// intents, intersection means that different intents came to the common substring, then arrange 
		// all the data in the hash, and mention only keyphrases.

		_.each(ac, function(actual, key, list){
		if (actual.length > 3)
			{	 
			label = actual[0]
			// keyphrase
			str = actual[2]
			if (!(label in this.dep))
				{
				this.dep[label] = {}
				this.dep[label][label] = []
				}
			this.dep[label][label].push(str)

			// intersection, different intents but actual intersection
			var found = _.filter(ac, function(num){ return ((num[0] != actual[0]) && (this.intersection(num[1], actual[1]) == true)) }, this);
			_.each(found, function(sublabel, key, list){
				if (!(sublabel[0] in this.dep[label]))
					this.dep[label][sublabel[0]] = []
				this.dep[label][sublabel[0]].push([[actual[2],actual[4]], [sublabel[2],sublabel[4]]])
			}, this)
			}
		}, this)

		var explanations = {};
		explanations['TP'] = []; explanations['FP'] = []; explanations['FN'] = [];
		
		var explanations_detail = {};
		explanations_detail['TP'] = []; explanations_detail['FP'] = []; explanations_detail['FN'] = [];
		
		var allTrue = true;
		for (var actualClassindex in ac) {
			
			if (!(ac[actualClassindex][0] in this.labels)) {
				this.labels[ac[actualClassindex][0]]={}
				this.labels[ac[actualClassindex][0]]['TP']=0
				this.labels[ac[actualClassindex][0]]['FP']=0
				this.labels[ac[actualClassindex][0]]['FN']=0
				}

			var found = false
			_.each(ex, function(exc, key, list){
				if (ac[actualClassindex][0] == exc[0])
					{
					if ((exc[1].length == 0) || (ac[actualClassindex][1][0] == -1))
						{
						found = true
						matchlist.push(ac[actualClassindex])
						}
					else
						{
						if (this.intersection(ac[actualClassindex][1], exc[1]))
							{
							found = true
							matchlist.push(ac[actualClassindex])
							}
						}
					}
			}, this)

			if (found) { 
				if (logTruePositives)
					{
						explanations['TP'].push(ac[actualClassindex][0]);
						explanations_detail['TP'].push(ac[actualClassindex]);
						this.labels[ac[actualClassindex][0]]['TP'] += 1
						this.TP++
					}
			} else {
				explanations['FP'].push(ac[actualClassindex][0]);
				explanations_detail['FP'].push(ac[actualClassindex]);
				this.labels[ac[actualClassindex][0]]['FP'] += 1
				this.FP++
				allTrue = false;
			}
		}

		for (var expectedClassindex in ex) {
			var found = false

			if (!(ex[expectedClassindex][0] in this.labels)) {
				this.labels[ex[expectedClassindex][0]]={}
				this.labels[ex[expectedClassindex][0]]['TP']=0
				this.labels[ex[expectedClassindex][0]]['FP']=0
				this.labels[ex[expectedClassindex][0]]['FN']=0
				}

			_.each(ac, function(acc, key, list){ 
				if (ex[expectedClassindex][0] == acc[0])
					{
						if ((ex[expectedClassindex][1].length == 0) || (acc[1][0] == -1))
							found = true
						else
							{
							if (this.intersection(ex[expectedClassindex][1], acc[1]))
								found = true
							}
					}
			}, this)

			if (!found)
				{
				explanations['FN'].push(ex[expectedClassindex][0]);
				explanations_detail['FN'].push(ex[expectedClassindex]);
				this.labels[ex[expectedClassindex][0]]['FN'] += 1
				this.FN++;
				allTrue = false;
				}
		}

		if (allTrue) {
			// if ((logTruePositives)&& (!only_false_cases)) explanations.push("\t\t*** ALL TRUE!");
			this.TRUE++;
		}
		this.count++;

		// _.each(explanations, function(value, key, list){ 
			// explanations[key] = _.sortBy(explanations[key], function(num){ num });
			// explanations[key].sort()
		// }, this)

		// console.log(explanations)
		// console.log(matchlist)
		
		// if (expectedClasses.length > 1)
			// process.exit(0)

		return {
				'explanations': explanations,
				'match': matchlist,
				'explanations_detail': explanations_detail
				}
	}
function (expectedClasses, actualClasses, logTruePositives ) {

		var ex = []
		var ac = []
		var matchlist = []

		// clean up expected list
		_.each(expectedClasses, function(expected, key, list){ 
			if ((expected.length == 2) || (expected.length == 3))
				ex.push(expected)
		}, this)

		// ac = actualClasses
		// // filtering actual classes		
		// _.each(actualClasses, function(actual, key, list){ 
		// 	var found = _.filter(ac, function(num){ return ((num[0] == actual[0]) && (this.intersection(num[1], actual[1]) == true)) }, this);
		// 	if (found.length == 0)
		// 		ac.push(actual)
		// }, this)

		// console.log(JSON.stringify(actualClasses, null, 4))

		// var ac = this.uniquecandidate(this.uniqueaggregate(actualClasses))
		var ac = actualClasses


		// filling interdependencies between labels 
		// for every candidate (actual) it looks for intersection between actual labels with different 
		// intents, intersection means that different intents came to the common substring, then arrange 
		// all the data in the hash, and mention only keyphrases.

		_.each(ac, function(actual, key, list){
		if (actual.length > 3)
			{	 
			label = actual[0]
			// keyphrase
			str = actual[2]
			if (!(label in this.dep))
				{
				this.dep[label] = {}
				this.dep[label][label] = []
				}
			this.dep[label][label].push(str)

			// intersection, different intents but actual intersection
			var found = _.filter(ac, function(num){ return ((num[0] != actual[0]) && (this.intersection(num[1], actual[1]) == true)) }, this);
			_.each(found, function(sublabel, key, list){
				if (!(sublabel[0] in this.dep[label]))
					this.dep[label][sublabel[0]] = []
				this.dep[label][sublabel[0]].push([[actual[2],actual[4]], [sublabel[2],sublabel[4]]])
			}, this)
			}
		}, this)

		var explanations = {};
		explanations['TP'] = []; explanations['FP'] = []; explanations['FN'] = [];
		
		var explanations_detail = {};
		explanations_detail['TP'] = []; explanations_detail['FP'] = []; explanations_detail['FN'] = [];
		
		var allTrue = true;
		for (var actualClassindex in ac) {
			
			if (!(ac[actualClassindex][0] in this.labels)) {
				this.labels[ac[actualClassindex][0]]={}
				this.labels[ac[actualClassindex][0]]['TP']=0
				this.labels[ac[actualClassindex][0]]['FP']=0
				this.labels[ac[actualClassindex][0]]['FN']=0
				}

			var found = false
			_.each(ex, function(exc, key, list){
				if (ac[actualClassindex][0] == exc[0])
					{
					if ((exc[1].length == 0) || (ac[actualClassindex][1][0] == -1))
						{
						found = true
						matchlist.push(ac[actualClassindex])
						}
					else
						{
						if (this.intersection(ac[actualClassindex][1], exc[1]))
							{
							found = true
							matchlist.push(ac[actualClassindex])
							}
						}
					}
			}, this)

			if (found) { 
				if (logTruePositives)
					{
						explanations['TP'].push(ac[actualClassindex][0]);
						explanations_detail['TP'].push(ac[actualClassindex]);
						this.labels[ac[actualClassindex][0]]['TP'] += 1
						this.TP++
					}
			} else {
				explanations['FP'].push(ac[actualClassindex][0]);
				explanations_detail['FP'].push(ac[actualClassindex]);
				this.labels[ac[actualClassindex][0]]['FP'] += 1
				this.FP++
				allTrue = false;
			}
		}

		for (var expectedClassindex in ex) {
			var found = false

			if (!(ex[expectedClassindex][0] in this.labels)) {
				this.labels[ex[expectedClassindex][0]]={}
				this.labels[ex[expectedClassindex][0]]['TP']=0
				this.labels[ex[expectedClassindex][0]]['FP']=0
				this.labels[ex[expectedClassindex][0]]['FN']=0
				}

			_.each(ac, function(acc, key, list){ 
				if (ex[expectedClassindex][0] == acc[0])
					{
						if ((ex[expectedClassindex][1].length == 0) || (acc[1][0] == -1))
							found = true
						else
							{
							if (this.intersection(ex[expectedClassindex][1], acc[1]))
								found = true
							}
					}
			}, this)

			if (!found)
				{
				explanations['FN'].push(ex[expectedClassindex][0]);
				explanations_detail['FN'].push(ex[expectedClassindex]);
				this.labels[ex[expectedClassindex][0]]['FN'] += 1
				this.FN++;
				allTrue = false;
				}
		}

		if (allTrue) {
			// if ((logTruePositives)&& (!only_false_cases)) explanations.push("\t\t*** ALL TRUE!");
			this.TRUE++;
		}
		this.count++;

		// _.each(explanations, function(value, key, list){ 
			// explanations[key] = _.sortBy(explanations[key], function(num){ num });
			// explanations[key].sort()
		// }, this)

		// console.log(explanations)
		// console.log(matchlist)
		
		// if (expectedClasses.length > 1)
			// process.exit(0)

		return {
				'explanations': explanations,
				'match': matchlist,
				'explanations_detail': explanations_detail
				}
	}
example of usage see in test