# auditing

---

THIS IS WORK IN PROGRESS
Think of this as a technical concept for
https://github.com/eHealthAfrica/kazana/issues/29

---

Kazana comes with built-in auditing APIs. Each report source can be assigned with
a list of methods which get run each time a raw data report has been transformed.
Each method must return an array of issue objects, or a Promise that resolves to
an array of issues. All issues from all methods are accumulated and compared to
existing issues for the report based on its scope. All new issues get created,
if there are issues that have not been found anymore, they get closed
automatically.

Issues are stored in a [separate issues database](data.md#issues), and they do
not get deleted, but only marked as closed. That way issues can be used to
analyse data quality over time.

## Example

Sam is a director at a hospital. As part of the national health care monitoring,
Sam has to submit a monthly report of how many patients were treated, how many
treatments succeeded and how many failed. The report is due on the 6th of every
month.

All logic related to that report is implemented in the hospital-report-plugin.
In its [kazana Manifest](../api/kazana-manifest#audit), the following audit rule
is defined

```js
audit: {
  'hospital-patients': {
    reference: {
      avgPatients: function (server) {
        server.app.methods.getStore('integrated-data').db.query('avgPatientsPerMonthPerHospital')
      }
    },
    rules: {
      'patients-increase': function (server, indicators, reference) {
        if (indicators[0].data.numPatients > reference.avgPatients[indicator.report.scope.hospitalId] * 2) {
          return {
            reason: 'Increase of patients by over 200%'
          }
        }
      }
    }
  }
}
```

The rule above will create an issue for every raw data report with
`sourceId: 'hospital-patients'` where the number of patients increased by more
than 200%. The issue can be closed manually by a data reviewer with a custom
reason, or maybe it was a mistake in the raw data report which gets corrected
later, which removes all indicators and re-creates them, and afterwards compares
the issues. If the `patients-increase` does not come up again, it gets closed
automatically.

## Options

The [Kazana manifest audit option](../api/kazana-manifest.md#audit) is an object
with keys being raw data report source IDs, and values being an audit object,
with the following options

- `reference`  
  Many auditing rules need reference numbers to compare to, for things like
  "number of X increased by other 300%". These reference numbers need to be
  loaded only once for all rules, and are defined in the `reference` option.
  The `reference` object is passed to all rules once the promises or the
  methods returned values.
- `rules`  
  A key/value object where keys are unique identifiers for the respective rule,
  and values are methods which calculate if there are issues with the indicators
  from the current report. Each method is called with 3 arguments: [server](../api/server.md),
  `indicators` (array of all indicators the report was transformed into), `reference`
  (results from the reference methods)

## Time based auditing

For auditing like "Every hospital report has to be submitted by 6th each month"
[time based events](events.md#time-based-events) can be used. Example:

```js
events: [
  on: {
    // every day at 2am local Lagos time
    hour: 2,
    timezone: 'Africa/Lagos'
  }
  handler: function (server, event) {
    var currentMonthNr = new Date().getMonth() + 1

    return Promise.all(
      server.app.methods.getStore('raw-data').query('reportsByMonth', {
        startkey: ['hospitalReport', currentMonthNr],
        endkey: ['hospitalReport', currentMonthNr, {}]
      }),
      server.plugins.account.api.accounts.findAll()
    )

    .then(function (results) {
      var userIdsFromReports = _.(results[0].rows[0].value, 'createdBy.id')
      var userIds = _.(results[1], 'id')

      return _.difference(userIdsFromReports, userIds)
    })

    .then(function (userIds) {
      var issues =  userIds.map(function (id) {
        return {
          scope: {
            id: ['hospital-report', id, currentMonthNr].join('-'),
            reportSource: 'hospital-report',
            userId: id,
            month: currentMonthNr
          },
          reason: 'Hospital report not submitted by 6th'
        }
      })

      server.plugins.audit.api.add(issues)
    })
  }
]
```

Issue will get closed automatically based on `scope`, see [Closing of issues](#closing-of-issues).

## Closing of issues

Everytime a report gets transformed, issues get accumulated and compared to
existing issues for the report based on its scope. All new issues get created,
if there are issues that have not been found anymore, they get closed
automatically.

Issues can also be closed & locked manually by users with the right permissions,
with an explanation of why the issue was closed.

## Analysis

Issues can be analysed using [indices](indices.md) which can be defined in the
[Kazana Manifest](../kazana-manifest.md#indices) for the `issues` database.

_TBD_ It might make sense to create built-in indices like showing all issues by
report, user, user group (role), but that has not been discussed at this point.
