Skip to content

Commit

Permalink
Merge pull request #80 from amos-ws16/dev
Browse files Browse the repository at this point in the history
sprint-11-release
  • Loading branch information
yvFischer committed Jan 26, 2017
2 parents ae3758d + 53157a4 commit 5d8e348
Show file tree
Hide file tree
Showing 35 changed files with 1,063 additions and 199 deletions.
13 changes: 11 additions & 2 deletions CHANGELOG.md
@@ -1,5 +1,15 @@
# Change Log
All notable changes to this project will be documented in this file.

## [Unreleased](https://github.com/amos-ws16/amos-ws16-arrowjs/compare/sprint-11-release...dev) - 2017-01-26

## Added
- Pipe for base names
- Assignees Plugin
- Automatic iput ids
- Exception handling for configuration
- Inputgroups

## [1.7.0](https://github.com/amos-ws16/amos-ws16-arrowjs/compare/sprint-10-release...dev) - 2017-01-19

## Added
Expand All @@ -9,8 +19,7 @@ All notable changes to this project will be documented in this file.
## 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
## [1.6.0](https://github.com/amos-ws16/amos-ws16-arrowjs/compare/sprint-08-release...dev) - 2017-01-12

## Added
- Added a Website for this project [(click here)](https://amos-ws16.github.io/amos-ws16-arrowjs/)
Expand Down
2 changes: 1 addition & 1 deletion config/default.js
Expand Up @@ -9,7 +9,7 @@
* context-file-description-task-description: compares keywords of file description with keywords of description of tasks
*/
var config = {

idPath: 'tasks[].id',
aggregator: {'mean': [
{'mean': [
{'max': ['context-file-title-task-description', 'similar-file-title-task-description']},
Expand Down
146 changes: 129 additions & 17 deletions docs/user-guide.md
Expand Up @@ -168,6 +168,19 @@ This configuration is used to compare the description of file and tasks (`"input

4. The __params__ is an object of parameters that is passed to used plugin.

5. The __inputGroup__ is an optional parameter. Its use and usage is described below.

###### inputGroup

If you dont want to configure multiple plugins that use the same __use__ function and the same __params__ but use different __inputs__, you can use __inputGroup__ instead. An __inputGroup__ looks like this

```js
"inputGroup": [ ["file.description"], ["tasks[].description", "tasks[].title"]]
```

and is used instead of __inputs__. This will automatically generate plugins for you that use each combination of the entries as __inputs__. For example, this __inputGroup__ would create two plugins.
The first one would compare `file.description` and `tasks[].description` and the second one `file.description` and `tasks[].title`. You can still use __params__. They will apply for all generated plugins.

#### 3.3.2 Aggregators

An aggregator is a policy that combines a set of scores that were previously assigned to a task by multiple Plugins into a single final score value. Aggregators can be nested.
Expand Down Expand Up @@ -215,7 +228,7 @@ Examples:
This holds similarly for the other aggregators.


##### 4.3.2.2 Logical Aggregators
##### 3.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:
```
Expand All @@ -240,7 +253,7 @@ More specifically the following table shows the outputs for scores 1.0 and 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
##### 3.3.2.3 Configuration

An aggregator configuration could look like this:

Expand Down Expand Up @@ -269,21 +282,22 @@ Pipes can be used to manipulate the input for plugins.

You can choose from these pipes:

| Name | for Type |
| -------------------|:-------------: |
| to-lower-case | string |
| to-upper-case | string |
| trim | string |
| trim-left | string |
| trim-right | string |
| day-of-month | timestamp |
| day-of-week | timestamp |
| hour-of-day | timestamp |
| years-since-epoch | timestamp |
| months-since-epoch | timestamp |
| weeks-since-epoch | timestamp |
| days-since-epoch | timestamp |
| hours-since-epoch | timestamp |
| Name | for Type | Description |
| -------------------|:-------------: |----------------------------------------------------------------|
| to-lower-case | string | Only lower cases in string (i.e. 'TeXt' => 'text') |
| to-upper-case | string | Only upper cases in string (i.e. 'tExT' => 'TEXT') |
| trim | string | Removes whitespaces on both sides (i.e. ' text ' => 'text') |
| trim-left | string | Removes whitespaces on left side (i.e. ' text ' => 'text ') |
| trim-right | string | Removes whitespaces on right side (i.e. ' text ' => ' text') |
| basename | string | Removes a dot-extension (i.e. 'file.ext' => 'file') |
| day-of-month | timestamp | Extracts the day of the month (1 - 31) |
| day-of-week | timestamp | Extracts the day of the week (i.e. Thursday) |
| hour-of-day | timestamp | Extracts the hour of the day (0 - 24) |
| years-since-epoch | timestamp | Extracts the passed amount of years since 1970 |
| months-since-epoch | timestamp | Extracts the passed amount of months since 1970 |
| weeks-since-epoch | timestamp | Extracts the passed amount of weeks since 1970 |
| days-since-epoch | timestamp | Extracts the passed amount of days since 1970 |
| hours-since-epoch | timestamp | Extracts the passed amount of hours since 1970 |

##### 3.3.3.2 Usage

Expand Down Expand Up @@ -322,3 +336,101 @@ The default configuration consists mainly of 3 three scores which are aggregated
The timestamp score is calculated by taking the mean score of the `File Title - Task Title`-Plugin and the `File Timestamp - Task Timestamp`-Plugin.

The default configuration can be found [here](../config/default.js).

#### 3.3.5 Identify Scores with IDs

The API supports IDs that will be returned for each score in the response. There are 3 different possibilites how to use IDs and you can specify an ID field (idPath) to match your data structure:

1. Use Default Configuration

The Default Configuration specifies the ID field (idPath) as follows:
```javascript
{
idPath: 'tasks[].id',
aggregator: { ... },
plugins: { ... }
}
```
This means that if you provide this field in your tasks array, the corresponding score will also return the ID. If you don't insert an ID in a task, then the API will generate and return a random ID:

Request:
```javascript
{
"token": "yourTokenHere",
"file": { "title": "hello" },
"tasks": [
{ "title": "taskWithID", "id": "YOUROWNID" },
{ "title": "taskWithoutID" }
]
}
```

Response:
```javascript
{
"success": true,
"result": [
{
"similar-file-title-task-title": 0,
...
"id": "YOUROWNID",
"total": 0
},
{
"similar-file-title-task-title": 0,
...
"id": 1485300049420,
"total": 0
}
]
}
```

2. Specify your own ID field

In case you will provide your own configuration. Then you can specify your own ID field so that it will match your own data structure. As we map one object against an array of objects, your ID has to be specified somewhere in an array. The following example shows how to use IDs with your own configuration.

Request:
```javascript
{
"token": "yourTokenHere",
"config": {
"idPath": "myObjects[].id.idIWantToReceive",
"aggregator": {"mean": [ "my-plugin" ] },
"plugins": {
"my-plugin": {
"use": "similar-text-plugin",
"inputs": ["myObject.name", "myObjects[].name"]
}
}
},
"myObject": { "name": "hello" },
"myObjects": [
{ "name": "taskWithID", "id": { "idIWantToReceive": "YOUROWNID" } },
{ "name": "taskWithoutID" }
]
}
```

Response:
```javascript
{
"success": true,
"result": [
{
"my-plugin": 0,
"id": "YOUROWNID",
"total": 0
},
{
"my-plugin": 0,
"id": 1485301111119,
"total": 0
}
]
}
```

3. Don't use IDs

It is not necessary to specify an idPath in the configuration. Then you will not receive an ID in the response. The order of the result scores will be the same as in the request.
37 changes: 21 additions & 16 deletions lib/aggregator-config-parser.js
@@ -1,6 +1,7 @@
const scoreSource = require('./aggregators/score-source')
const nullSource = require('./aggregators/null-source')
// const nullSource = require('./aggregators/null-source')
const loadModule = require('./utils').loadModule
const InvalidInputError = require('./invalid-input-error')

/**
* Parse the argument part of an aggregator configuration, which can either be
Expand All @@ -9,17 +10,15 @@ const loadModule = require('./utils').loadModule
* @param args - the argument to a aggregator
* @param scores - the scores of all plugins mpaped as scores[pluginId] -> val
*/
function parseArgument (args, scores) {
function parseArgument (args) {
if (args === '*') {
// * means all available plugins.
return Object.values(scores).map(pluginScore => {
return scoreSource.create(pluginScore)
})
return '*'
}
if (Array.isArray(args)) {
return args.map(element => parse(element, scores))
return args.map(element => parse(element))
}
return parse(args, scores)
return parse(args)
}

/**
Expand All @@ -30,27 +29,33 @@ function parseArgument (args, scores) {
* @param config - the configuration to be parsed
* @param scores - the scores of all plugins mapped as scores[pluginId] -> val
*/
function parse (config, scores) {
function parse (config) {
if (typeof config === 'string') {
let pluginScore = scores[config]
if (typeof pluginScore === 'undefined') {
return nullSource.create()
}
return scoreSource.create(pluginScore)
// let pluginScore = scores[config]
// if (typeof pluginScore === 'undefined') {
// return nullSource.create()
// }
return scoreSource.create(config)
}

if (typeof config === 'number') { return config }

if (Array.isArray(config)) {
return config.map((element) => parse(element, scores))
return config.map((element) => parse(element))
}

// It is not a primitive: assume it is a aggregator { 'name': [args] }.
let id = Object.keys(config)[0]
let args = config[id]

args = parseArgument(args, scores)
let aggregator = loadModule('aggregators', id)
args = parseArgument(args)
let aggregator = (() => {
try {
return loadModule('aggregators', id)
} catch (err) {
throw new InvalidInputError(`Aggregator '${id}' does not exist.`)
}
})()

return aggregator.create(args)
}
Expand Down
8 changes: 6 additions & 2 deletions lib/aggregators/and.js
@@ -1,3 +1,5 @@
const handleStar = require('../handle-star').handleStar

/**
* Representation of an Aggregator, that can calculate a generalization of the
* logical 'and' operation on floating point numbers in [0.0, 1.0], where the
Expand All @@ -16,8 +18,10 @@ class And {
/**
* Returns the score of this And Aggregator.
*/
eval () {
return this.aggregators.reduce((and, element) => and * element.eval(), 1.0)
eval (scores) {
const agList = handleStar(this.aggregators, scores)
return agList.reduce(
(and, element) => and * element.eval(scores), 1.0)
}
}

Expand Down
11 changes: 8 additions & 3 deletions lib/aggregators/max.js
@@ -1,3 +1,6 @@
const utils = require('../utils')
const handleStar = require('../handle-star').handleStar

/**
* Representation of an Aggregator, that can calculate the maximum value of
* several Aggregators that are passed as input.
Expand All @@ -8,15 +11,17 @@ class Max {
* @param aggregators - an array of aggregators
*/
constructor (aggregators) {
utils.ensureValidAggregators(aggregators)
this.aggregators = aggregators
}

/**
* Returns the score of this Max Aggregator.
*/
eval () {
return this.aggregators.reduce(
(max, element) => Math.max(max, element.eval()),
eval (scores) {
const agList = handleStar(this.aggregators, scores)
return agList.reduce(
(max, element) => Math.max(max, element.eval(scores)),
Number.NEGATIVE_INFINITY
)
}
Expand Down
25 changes: 6 additions & 19 deletions lib/aggregators/mean.js
@@ -1,29 +1,16 @@
const weightedMean = require('./weighted-mean')

/**
* Throw an error if aggregators is not a valid array of aggregators.
* @param aggregators - the array to be checked
*/
function ensureValidAggregators (aggregators) {
if (typeof aggregators === 'undefined') {
throw new Error('Aggregator specification must be provided')
}
if (!Array.isArray(aggregators)) {
throw new Error(`Aggregator specification must be an array of aggregators. Found: '${aggregators}'`)
}
aggregators.forEach(aggregator => {
if (typeof aggregator.eval === 'undefined') {
throw new Error(`Aggregator must provide eval() method: '${aggregator}'`)
}
})
}
const utils = require('../utils')

/**
* Return a WeightedMean Aggregator from an aggregator array. The parameters
* will be forwarded to WeightedMean.constructor().
*/
function create (aggregators) {
ensureValidAggregators(aggregators)
utils.ensureValidAggregators(aggregators)
if (aggregators === '*') {
return weightedMean.create('*')
}

let weighted = aggregators.map(e => [1, e])
return weightedMean.create(weighted)
}
Expand Down
9 changes: 6 additions & 3 deletions lib/aggregators/nand.js
@@ -1,3 +1,5 @@
const handleStar = require('../handle-star').handleStar

/**
* Representation of an Aggregator, that can calculate a generalization of the
* logical 'nand' operation on floating point numbers in [0.0, 1.0], where the
Expand All @@ -16,9 +18,10 @@ class Nand {
/**
* Returns the score of this Nand Aggregator.
*/
eval () {
return 1.0 - this.aggregators.reduce(
(and, element) => and * element.eval(),
eval (scores) {
const agList = handleStar(this.aggregators, scores)
return 1.0 - agList.reduce(
(and, element) => and * element.eval(scores),
1.0)
}
}
Expand Down

0 comments on commit 5d8e348

Please sign in to comment.