Skip to content

Commit

Permalink
Merge pull request #74 from amos-ws16/dev
Browse files Browse the repository at this point in the history
Sprint 10 Release
  • Loading branch information
kevinstyp committed Jan 19, 2017
2 parents 1e1794f + caf3233 commit ae3758d
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 39 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,13 @@
# Change Log
All notable changes to this project will be documented in this file.
## [1.7.0](https://github.com/amos-ws16/amos-ws16-arrowjs/compare/sprint-10-release...dev) - 2017-01-19

## Added
- Plugin 'is-assignes' Added
- Documentation for logic aggregators

## Changed
- Fix for logic aggregators

## [Unreleased](https://github.com/amos-ws16/amos-ws16-arrowjs/compare/sprint-08-release...dev)
## [1.6.0]() - 2017-01-12
Expand Down
2 changes: 1 addition & 1 deletion config/default.js
Expand Up @@ -2,7 +2,7 @@
/**
* Default config for scoreManager
*
* similar-title: compare titles if file and tasks
* similar-title: compare titles of file and tasks
* context-file-timestamp-tasks-timestamp: compares timestamps of file and tasks - timeLimit = 600s (default in plugin)
* context-file-timestamp-tasks-timestamp: compares keywords of file title with keywords of description of tasks
* context-file-description-task-title: compares keywords of file description with keywords of description of tasks
Expand Down
74 changes: 52 additions & 22 deletions docs/user-guide.md
Expand Up @@ -39,27 +39,27 @@ let scoreManager = arrow.create(config)

// Create an object that we want to get a score for
let obj = {
"file": {
"title": "Cafe abc",
"type": "jpeg",
"created_at": 1479755100,
"user": "5hj34thtr",
"description": " Great location for a meeting"
},
"tasks": [
{
"title": " find a location",
"created_at": 1479754800,
"due_date": 1479766305,
"created_by": "ikgDG94s",
"description": "Find a location for the next meeting"
"file": {
"title": "Cafe abc",
"type": "jpeg",
"created_at": 1479755100,
"user": "5hj34thtr",
"description": " Great location for a meeting"
},
"tasks": [
{
"title": " find a location",
"created_at": 1479754800,
"due_date": 1479766305,
"created_by": "ikgDG94s",
"description": "Find a location for the next meeting"
},
{
"title": " Check your mails",
"created_at": 1379754800,
"due_date": 1454353454,
"created_by": "dfgj2s334",
"description": "Check your mails before you leave."
{
"title": " Check your mails",
"created_at": 1379754800,
"due_date": 1454353454,
"created_by": "dfgj2s334",
"description": "Check your mails before you leave."
}
]
}
Expand Down Expand Up @@ -177,10 +177,14 @@ An aggregator is a policy that combines a set of scores that were previously ass
You can choose from these aggregators:

| Name | Description |
| ------------- |:-------------------------------------------------------------------------------------------------|
| ------------- | :----------------------------------------------------------------------------------------------- |
| max | returns the maximum value |
| mean | calculates mean of all scores |
| weigthed mean | calculates weighted mean of all scores <br> requires arrays of the form [weight, value] as input |
| and | calculates the generalized logical and (see below) |
| or | calculates the generalized logical or (see below) |
| nand | calculates the generalized logical nand (see below) |
| not | calculates the generalized logical not (see below) |

They can be combined in any desired way. If you want to apply an aggregator on all plugins you can use the wildcard symbol "\*".

Expand Down Expand Up @@ -210,7 +214,33 @@ Examples:

This holds similarly for the other aggregators.

##### 4.3.2.2 Configuration

##### 4.3.2.2 Logical Aggregators

In addition to the statistical reduction aggregators `mean`, `weighted mean`, and `max`, there are the logical aggregators `and`, `or`, `not`, and `nand`. These are modelled after the logical operations with the same name in that, in the case of 0.0 (false) and 1.0 (true) score values, they reproduce the same results. For example:
```
config.aggregator = {
'and': ['plugin-a', 'plugin-b']
}
// Assume the plugins 'plugin-a' and 'plugin-b' score this data with 1.0 and 0.0 respectively
let data = ...
let result = scoreManager.score(data)
// result.total === 0.0
// because (true AND false) <=> false
```
More specifically the following table shows the outputs for scores 1.0 and 0.0:

| A | B | and: [A, B] | or: [A, B] | nand: [A, B] | not: A |
| --- | --- | ----------- | ---------- | ------------ | ------ |
| 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 1.0 |
| 0.0 | 1.0 | 0.0 | 1.0 | 1.0 | 1.0 |
| 1.0 | 0.0 | 0.0 | 1.0 | 1.0 | 0.0 |
| 1.0 | 1.0 | 1.0 | 1.0 | 0.0 | 0.0 |


However, the logical aggregators generalize the logical operations so they are valid for floating point score values in [0.0,1.0]. For example, if you AND two values that are close to 1.0, you will get a value close to 1.0. If you AND a small value with a big value, you will get a small value. OR will give a big value if one of the inputs is big and NOT will produce a big value only if the input value was small.

##### 4.3.2.3 Configuration

An aggregator configuration could look like this:

Expand Down
29 changes: 21 additions & 8 deletions lib/aggregator-config-parser.js
Expand Up @@ -2,6 +2,26 @@ const scoreSource = require('./aggregators/score-source')
const nullSource = require('./aggregators/null-source')
const loadModule = require('./utils').loadModule

/**
* Parse the argument part of an aggregator configuration, which can either be
* a '*', a single aggregator config, or an array of aggregator configs, and
* return the aggregator parse tree.
* @param args - the argument to a aggregator
* @param scores - the scores of all plugins mpaped as scores[pluginId] -> val
*/
function parseArgument (args, scores) {
if (args === '*') {
// * means all available plugins.
return Object.values(scores).map(pluginScore => {
return scoreSource.create(pluginScore)
})
}
if (Array.isArray(args)) {
return args.map(element => parse(element, scores))
}
return parse(args, scores)
}

/**
* Parse the configuration given in config and return an aggregator tree, that
* can be evaluated using the tree's eval() method. The scores object provides
Expand Down Expand Up @@ -29,14 +49,7 @@ function parse (config, scores) {
let id = Object.keys(config)[0]
let args = config[id]

if (args === '*') {
// * means all available plugins.
args = Object.values(scores).map(pluginScore => {
return scoreSource.create(pluginScore)
})
} else {
args = args.map(element => parse(element, scores))
}
args = parseArgument(args, scores)
let aggregator = loadModule('aggregators', id)

return aggregator.create(args)
Expand Down
4 changes: 2 additions & 2 deletions lib/aggregators/nand.js
Expand Up @@ -17,8 +17,8 @@ class Nand {
* Returns the score of this Nand Aggregator.
*/
eval () {
return this.aggregators.reduce(
(and, element) => and * (1.0 - element.eval()),
return 1.0 - this.aggregators.reduce(
(and, element) => and * element.eval(),
1.0)
}
}
Expand Down
26 changes: 26 additions & 0 deletions lib/plugins/is-assignee-plugin.js
@@ -0,0 +1,26 @@
/**
*
* @param user - an username which indicates the user who uploaded the file
* @param assignee - a list of names of the assignees of the tasks
* @return array with objects with the key ´assignees´ and the score of 1.0 if the user is found in the specific task or 0.0 if not found
*/
function isAssigneePlugin (user, task) {
let found = false
let result = []
for (let x of task) {
for (let y of x.assignees) {
if (y === user) {
found = true
break
}
}
if (found) {
result.push({'assignees': 1.0})
} else {
result.push({'assignees': 0.0})
}
}
return result
}

module.exports = isAssigneePlugin
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "arrow",
"version": "1.5.0",
"version": "1.7.0",
"description": "Description that needs to be changed some time soon",
"license": "AGPL-3.0",
"repository": "https://github.com/amos-ws16/amos-ws16-arrowjs",
Expand Down
8 changes: 8 additions & 0 deletions test/aggregator-config-parser-test.js
Expand Up @@ -72,6 +72,14 @@ buster.testCase('AggregatorConfigParser', {
buster.assert.near(abstractSyntaxTree.eval(), 0.86, 1e-3)
},

'should pass a single plugin as argument to an aggregator': function () {
let scores = { 'plugin-a': 0.5, 'plugin-b': 0.9 }
let config = { 'not': 'plugin-b' }
let abstractSyntaxTree = AggregatorConfigParser.parse(config, scores)

buster.assert.near(abstractSyntaxTree.eval(), 0.1, 1e-3)
},

'should take * (star) argument to mean array of all plugins': function () {
let scores = { 'plugin-a': 0.5, 'plugin-b': 0.9 }
let config = { 'mean': '*' }
Expand Down
2 changes: 1 addition & 1 deletion test/aggregators/logic-interaction-test.js
Expand Up @@ -11,7 +11,7 @@ buster.testCase('logic aggregators', {
let one = { eval: this.stub().returns(1.0) }

let A = or.create([ zero, one ])
let B = not.create(nand.create([ zero, one ]))
let B = nand.create([ not.create(zero), not.create(one) ])

buster.assert.near(A.eval(), B.eval(), 1e-3)
},
Expand Down
8 changes: 4 additions & 4 deletions test/aggregators/nand-test.js
Expand Up @@ -6,8 +6,8 @@ buster.testCase('nand.create()', {
let zero = { eval: this.stub().returns(0.0) }
let one = { eval: this.stub().returns(1.0) }
buster.assert.same(nand.create([zero, zero]).eval(), 1.0)
buster.assert.same(nand.create([zero, one]).eval(), 0.0)
buster.assert.same(nand.create([one, zero]).eval(), 0.0)
buster.assert.same(nand.create([zero, one]).eval(), 1.0)
buster.assert.same(nand.create([one, zero]).eval(), 1.0)
buster.assert.same(nand.create([one, one]).eval(), 0.0)
},

Expand All @@ -17,8 +17,8 @@ buster.testCase('nand.create()', {
buster.assert.greater(nand.create([a, b]).eval(), 0.8)
},

'should return low score when one of the inputs has a high score': function () {
let a = { eval: this.stub().returns(0.1) }
'should return low score when both of the inputs has a high score': function () {
let a = { eval: this.stub().returns(0.95) }
let b = { eval: this.stub().returns(0.95) }
buster.assert.less(nand.create([a, b]).eval(), 0.2)
}
Expand Down
42 changes: 42 additions & 0 deletions test/plugins/is-assignee-plugin-test.js
@@ -0,0 +1,42 @@
const buster = require('buster')
const plugin = require('../../lib/plugins/is-assignee-plugin.js')

buster.testCase('isAssigneePlugin', {
'Testcase 1: 2 tasks with 2 assignees each': function () {
let user = 'a'
let task = [{'assignees': ['b', 'c']}, {'assignees': ['a', 'c']}]
let testResult = [{'assignees': 0.0}, {'assignees': 1.0}]
let result = plugin(user, task)
buster.assert.equals(result, testResult)
},

'Testcase 2: 2 tasks with 1 assignee each': function () {
let user = 'a'
let task = [{'assignees': ['b']}, {'assignees': ['a']}]
let testResult = [{'assignees': 0.0}, {'assignees': 1.0}]
let result = plugin(user, task)
buster.assert.equals(result, testResult)
},

'Testcase 3: no tasks': function () {
let user = 'a'
let task = []
let testResult = []
let result = plugin(user, task)
buster.assert.equals(result, testResult)
},
'Testcase 4: ': function () {
let user = 'a'
let task = [{'assignees': ['b', 'c']}, {'assignees': ['a']}]
let testResult = [{'assignees': 0.0}, {'assignees': 1.0}]
let result = plugin(user, task)
buster.assert.equals(result, testResult)
},
'Testcase 5: ': function () {
let user = 'a'
let task = [{'assignees': ['b']}, {'assignees': ['a', 'c']}]
let testResult = [{'assignees': 0.0}, {'assignees': 1.0}]
let result = plugin(user, task)
buster.assert.equals(result, testResult)
}
})

0 comments on commit ae3758d

Please sign in to comment.