# Indices

Indices are implemented with map/reduce functions using
[CouchDB Views](https://wiki.apache.org/couchdb/Introduction_to_CouchDB_views).
The easiest way to define indices is by using the [indices option](../api/kazana-manifest.md#indices)
in the Kazana Manifest.

An example index that maps all movements in a bookkeeping app by their category
and then reduces that by returning a sum by category

```js
indices: {
  'integrated-data': {
    movementsByCategory: {
      version: '1.0.0',
      map: function (indicator) {
        emit(indicator.data.category, indicator.data.amount)
      },
      reduce: function (keys, values) {
        return {
          amount: sum(values)
        }
      }
    }
  }
}
```

The index can be retrieved at `GET /kazana/kazana/integrated-data/api/indices/movementsByCategory`
(WIP: [kazana#172](https://github.com/eHealthAfrica/kazana/issues/172)) given
that the current user is [an analyst](permissions.md#roles). Without any query
parameters, the above route will returns something like this:

```json
{
    "rows": [
        {
            "key": null,
            "value": {
                "amount": 555.66
            }
        }
    ]
}
```

To split up the amounts by category, append `?group_level=1` and the response
will look like this:


```json
{
    "rows": [
        {
            "key": "groceries",
            "value": {
                "amount": 111.11
            }
        },
        {
            "key": "utilities",
            "value": {
                "amount": 444.55
            }
        }
    ]
}
```

To access the view on the server, you can
[server.methods.getStore(options)](../api/server.md#servermethodsgetstore)

```js
server.methods.getStore({
  name: 'raw-data',
  auth: 'admin'
}).db.query('movementsByCategory')
.then(function (response) {
  // {
  //   rows: [
  //     {
  //       key: null,
  //       value: {
  //         amount: 555.66
  //       }
  //     }
  //   ]
  // }
})
```

The `db.query` methods is
[PouchDB’s query method](http://pouchdb.com/api.html#query_database) and accepts
the same arguments.

## Configurable indices

---

THIS IS WORK IN PROGRESS:
https://github.com/eHealthAfrica/kazana/issues/30

---

Let’s say we want to define thresholds for expenses per month. First we need to
adjust our map function:

```js
indices: {
  'integrated-data': {
    movementsByCategory: {
      version: '1.0.0',
      map: function (indicator) {
        emit([
          'byCategory',
          indicator.data.category
        ], indicator.data.amount)
        emit([
          'byMonth',
          indicator.dataReport.scope.year,
          indicator.dataReport.scope.month
        ], indicator.data.amount)
      },
      reduce: function (keys, values) {
        return {
          amount: sum(values)
        }
      }
    }
  }
}
```

With that, we we can retrieve all expenses by month with

```js
server.methods.getStore({
  name: 'integrated-data',
  auth: 'admin'
}).db.query('movementsByCategory', {
  group: true,
  startkey: ['byMonth'],
  endkey: ['byMonth', {}]
}).then(function (result) {
  // {
  //   rows: [
  //     {
  //       key: ['byMonth', 2016, 1],
  //       value: {
  //         amount: 555.66
  //       }
  //     }
  //   ]
  // }
})
```

Now let’s say we have Report entities, where we store meta information for
our raw data report sources. For Bookkeeping, the entity could look like this:


```json
{
   "_id": "dataReport:bookkeeping",
   "_rev": "1-0b3aee132b3b58fb977e3c71f4292ba3",
   "type": "dataReport",
   "name": "Bookkeeping",
   "thresholds": {
     "byMonth": {
       "high": 1000,
       "low": 500
     },
     "byYear": {
       "high": 10000,
       "low": 5000
     }
   }
}
```

The app admins can configure these thresholds through a custom UI. Then a
[custom route](../api/routes.md) can be configured to combine the two

```js
{
  routes: [
    {
      method: 'GET',
      path: '/api/bookkeeping',
      handler: function (request, reply) {
        var server = request.server

        var getMovementsByMonth = server.methods.getStore({
          name: 'integrated-data',
          auth: request
        }).db.query('movementsByCategory', {
          group: true,
          startkey: ['byMonth'],
          endkey: ['byMonth', {}]
        })
        .then(function (result) {
          return result.rows.map(function (row) {
            month: row.key[1] + '-' + row.key[2],
            amount: row.value.amount
          })
        })

        var getBookkeepingEntity = server.methods.getStore({
          name: 'entities',
          auth: 'admin'
        }).find('dataReport:bookkeeping')

        Promise.all([
          getMovementsByMonth,
          getBookkeepingEntity
        ]).then(function (results) {
          var threshold = results[1].thresholds.byMonth
          return getMovementsByMonth.map(function (movement) {
            if (movement > threshold.high) {
              movement.threshold = 'high'
              return movement
            }

            if (movement < threshold.low) {
              movement.threshold = 'low'
              return movement
            }

            movement.threshold = 'within'
            return movement
          })
        })

        .then(reply)

        .catch(reply)
      }
    }
  ]
}
```

An alternative would be to "combine" the values returned by indices in the UI,
given that all analyst users have read access to the respective entity.
