@codetanzania/ewea-case
Version:
A representation of an entity which define and track cases during an emergency event.
1,723 lines (1,639 loc) • 44.3 kB
JavaScript
;
const eweaInternals = require('@codetanzania/ewea-internals');
const common = require('@lykmapipo/common');
const env = require('@lykmapipo/env');
const mongooseCommon = require('@lykmapipo/mongoose-common');
const expressCommon = require('@lykmapipo/express-common');
const expressRestActions = require('@lykmapipo/express-rest-actions');
const file = require('@lykmapipo/file');
const lodash = require('lodash');
require('@lykmapipo/mongoose-sequenceable');
const actions = require('mongoose-rest-actions');
const exportable = require('@lykmapipo/mongoose-exportable');
const eweaCommon = require('@codetanzania/ewea-common');
const moment = require('moment');
const predefine = require('@lykmapipo/predefine');
const eweaEvent = require('@codetanzania/ewea-event');
require('mongoose-geojson-schemas');
const emisStakeholder = require('@codetanzania/emis-stakeholder');
// common constants
const DEFAULT_COUNTRY_CODE = env.getString('DEFAULT_COUNTRY_CODE', 'TZ');
const COUNTRY_CODE = env.getString('COUNTRY_CODE', DEFAULT_COUNTRY_CODE);
// case schema
const CASE_SCHEMA_OPTIONS = {
collection: eweaInternals.COLLECTION_NAME_CASE,
};
// case options
const CASE_OPTION_SELECT = {
number: 1,
};
const CASE_OPTION_AUTOPOPULATE = {
select: CASE_OPTION_SELECT,
maxDepth: eweaInternals.POPULATION_MAX_DEPTH,
};
// relation options
// TODO: refactor to ewea-internals
const AUTOPOPULATE_OPTION_PREDEFINE = {
select: {
'strings.name': 1,
'strings.color': 1,
'strings.code': 1,
},
maxDepth: eweaInternals.POPULATION_MAX_DEPTH,
};
// TODO: refactor to ewea-internals
const AUTOPOPULATE_OPTION_AREA = {
select: {
'strings.name': 1,
'strings.color': 1,
'strings.code': 1,
'relations.level': 1,
},
maxDepth: 2,
};
// TODO: refactor to ewea-internals
const AUTOPOPULATE_OPTION_PARTY = {
select: { name: 1, email: 1, mobile: 1, abbreviation: 1, role: 1 },
maxDepth: 2,
};
/**
* @name group
* @description Event group underwhich a case belongs to.
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} required - mark required
* @property {boolean} index - ensure database index
* @property {boolean} exists - ensure ref exists before save
* @property {object} autopopulate - auto populate(eager loading) options
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {boolean} aggregatable - allow field use for aggregation
* @property {boolean} default - default value set when none provided
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* {
* _id: '5dde6ca23631a92c2d616253',
* strings: { name: { en: 'Meteorological' }, code: 'MAT' },
* }
*/
const group = {
type: mongooseCommon.ObjectId,
ref: predefine.Predefine.MODEL_NAME,
// required: true,
index: true,
exists: true,
aggregatable: { unwind: true },
autopopulate: AUTOPOPULATE_OPTION_PREDEFINE,
taggable: true,
exportable: {
format: (v) => lodash.get(v, 'strings.name.en'),
default: 'NA',
},
default: undefined,
};
/**
* @name type
* @description Event type underwhich a case belongs to.
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} required - mark required
* @property {boolean} index - ensure database index
* @property {boolean} exists - ensure ref exists before save
* @property {object} autopopulate - auto populate(eager loading) options
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {boolean} aggregatable - allow field use for aggregation
* @property {boolean} default - default value set when none provided
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* {
* _id: '5dde6ca33631a92c2d616298',
* strings: { name: { en: 'Flood' }, code: 'FL' },
* }
*/
const type = {
type: mongooseCommon.ObjectId,
ref: predefine.Predefine.MODEL_NAME,
// required: true,
index: true,
exists: true,
aggregatable: { unwind: true },
autopopulate: AUTOPOPULATE_OPTION_PREDEFINE,
taggable: true,
exportable: {
format: (v) => lodash.get(v, 'strings.name.en'),
default: 'NA',
},
default: undefined,
};
/**
* @name event
* @description Event underwhich a case belongs to.
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} required - mark required
* @property {boolean} index - ensure database index
* @property {boolean} exists - ensure ref exists before save
* @property {object} autopopulate - auto populate(eager loading) options
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {boolean} aggregatable - allow field use for aggregation
* @property {boolean} default - default value set when none provided
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* {
* _id: '5dde6ca33631a92c2d616298',
* strings: { name: { en: 'Flood' }, code: 'FL' },
* }
*/
const event = {
type: mongooseCommon.ObjectId,
ref: eweaEvent.Event.MODEL_NAME,
// required: true,
index: true,
exists: true,
aggregatable: { unwind: true },
autopopulate: eweaEvent.Event.OPTION_AUTOPOPULATE,
taggable: true,
exportable: {
format: (v) => lodash.get(v, 'strings.name.en'),
default: 'NA',
},
default: undefined,
};
/**
* @name stage
* @description Currently assigned stage of a case.
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} required - mark required
* @property {boolean} index - ensure database index
* @property {boolean} exists - ensure ref exists before save
* @property {object} autopopulate - auto populate(eager loading) options
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {boolean} aggregatable - allow field use for aggregation
* @property {boolean} default - default value set when none provided
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.4.0
* @version 0.1.0
* @instance
* @example
* {
* _id: '5dde6ca23631a92c2d616250',
* strings: { name: { en: 'Confirmed' } },
* }
*/
const stage = {
type: mongooseCommon.ObjectId,
ref: predefine.Predefine.MODEL_NAME,
// required: true,
index: true,
exists: true,
aggregatable: { unwind: true },
autopopulate: AUTOPOPULATE_OPTION_PREDEFINE,
taggable: true,
exportable: {
format: (v) => lodash.get(v, 'strings.name.en'),
default: 'NA',
},
default: eweaCommon.DEFAULT_SEEDS[eweaInternals.PREDEFINE_NAMESPACE_CASESTAGE],
};
/**
* @name severity
* @description Currently assigned severity of a case.
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} required - mark required
* @property {boolean} index - ensure database index
* @property {boolean} exists - ensure ref exists before save
* @property {object} autopopulate - auto populate(eager loading) options
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {boolean} aggregatable - allow field use for aggregation
* @property {boolean} default - default value set when none provided
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.4.0
* @version 0.1.0
* @instance
* @example
* {
* _id: '5dde6ca23631a92c2d616250',
* strings: { name: { en: 'Extreme' } },
* }
*/
const severity = {
type: mongooseCommon.ObjectId,
ref: predefine.Predefine.MODEL_NAME,
// required: true,
index: true,
exists: true,
aggregatable: { unwind: true },
autopopulate: AUTOPOPULATE_OPTION_PREDEFINE,
taggable: true,
exportable: {
format: (v) => lodash.get(v, 'strings.name.en'),
default: 'NA',
},
default: eweaCommon.DEFAULT_SEEDS[eweaInternals.PREDEFINE_NAMESPACE_CASESEVERITY],
};
/**
* @name number
* @description Human readable, unique identifier of a case.
*
* It consist of two letters to identify the emergency event type
* (e.g. FL - Flood); the year of the event; a six-digit, sequential
* event number; and the three-letter ISO code for country of occurrence
* e.g FL-2001-000033-TZA.
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} uppercase - force value to uppercase
* @property {boolean} required - mark required
* @property {boolean} index - ensure database index
* @property {boolean} unique - ensure unique database index
* @property {boolean} searchable - allow searching
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* FL-2018-000033-TZA
*/
const number = {
type: String,
trim: true,
uppercase: true,
required: true,
index: true,
// unique: true,
searchable: true,
taggable: true,
exportable: true,
sequenceable: {
prefix: function prefix() {
const year = moment(new Date()).format('YYYY-MM');
return year;
},
suffix: COUNTRY_CODE,
length: 4,
pad: '0',
separator: '-',
},
fake: {
generator: 'random',
type: 'uuid',
},
};
/**
* @name description
* @description A brief summary about a case i.e additional
* details that clarify more about a case.
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} required - mark required
* @property {boolean} index - ensure database index
* @property {boolean} searchable - allow for searching
* @property {boolean} exportable - allow field use for exporting
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* Provide medical assistance on rescue mission.
*/
const description = {
type: String,
trim: true,
// required: true,
index: true,
searchable: true,
exportable: true,
fake: {
generator: 'lorem',
type: 'sentence',
},
};
/**
* @name remarks
* @description A brief human readable comments and feedbacks
* about a case.
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} index - ensure database index
* @property {boolean} searchable - allow for searching
* @property {boolean} exportable - allow field use for exporting
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* Requested first aid were provided to the victim immediately.
*/
const remarks = {
type: String,
trim: true,
index: true,
searchable: true,
exportable: true,
fake: {
generator: 'lorem',
type: 'sentence',
},
};
/**
* @name outcome
* @description An outcome of about a case followup.
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} index - ensure database index
* @property {boolean} searchable - allow for searching
* @property {boolean} exportable - allow field use for exporting
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* Home
*/
const outcome = {
type: String,
trim: true,
index: true,
searchable: true,
exportable: true,
fake: {
generator: 'lorem',
type: 'sentence',
},
};
/**
* @name reporter
* @description A party(i.e call center or EOC operator) who
* recorded a case.
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} required - mark required
* @property {boolean} index - ensure database index
* @property {boolean} exists - ensure ref exists before save
* @property {object} autopopulate - auto populate(eager loading) options
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {boolean} aggregatable - allow field use for aggregation
* @property {boolean} default - default value set when none provided
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* {
* _id: "5bcda2c073dd0700048fb846",
* name: "Jane Doe",
* mobile: "+255715463739",
* email: "jane.doe@example.com",
* }
*/
const reporter = {
type: mongooseCommon.ObjectId,
ref: emisStakeholder.Party.MODEL_NAME,
// required: true,
index: true,
exists: true,
autopopulate: AUTOPOPULATE_OPTION_PARTY,
taggable: true,
exportable: {
format: (v) => lodash.get(v, 'name'),
default: 'NA',
},
aggregatable: { unwind: true },
default: undefined,
};
/**
* @name resolver
* @description A party(i.e call center or EOC operator) who
* resolve(i.e discharge) a case.
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} required - mark required
* @property {boolean} index - ensure database index
* @property {boolean} exists - ensure ref exists before save
* @property {object} autopopulate - auto populate(eager loading) options
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {boolean} aggregatable - allow field use for aggregation
* @property {boolean} default - default value set when none provided
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* {
* _id: "5bcda2c073dd0700048fb846",
* name: "Jane Doe",
* mobile: "+255715463739",
* email: "jane.doe@example.com",
* }
*/
const resolver = {
type: mongooseCommon.ObjectId,
ref: emisStakeholder.Party.MODEL_NAME,
// required: true,
index: true,
exists: true,
autopopulate: AUTOPOPULATE_OPTION_PARTY,
taggable: true,
exportable: {
format: (v) => lodash.get(v, 'name'),
default: 'NA',
},
aggregatable: { unwind: true },
default: undefined,
};
/**
* @name follower
* @description A party(i.e call center or EOC operator) who
* made a latest follow-up on a case.
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} required - mark required
* @property {boolean} index - ensure database index
* @property {boolean} exists - ensure ref exists before save
* @property {object} autopopulate - auto populate(eager loading) options
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {boolean} aggregatable - allow field use for aggregation
* @property {boolean} default - default value set when none provided
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* {
* _id: "5bcda2c073dd0700048fb846",
* name: "Jane Doe",
* mobile: "+255715463739",
* email: "jane.doe@example.com",
* }
*/
const follower = {
type: mongooseCommon.ObjectId,
ref: emisStakeholder.Party.MODEL_NAME,
// required: true,
index: true,
exists: true,
autopopulate: AUTOPOPULATE_OPTION_PARTY,
taggable: true,
exportable: {
format: (v) => lodash.get(v, 'name'),
default: 'NA',
},
aggregatable: { unwind: true },
default: undefined,
};
// TODO: map them against case statuses
/**
* @name reportedAt
* @description Date when a case was recorded(or requested).
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} index - ensure database index
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* 2018-10-17T07:53:32.831Z
*/
const reportedAt = {
type: Date,
index: true,
exportable: true,
fake: {
generator: 'date',
type: 'past',
},
};
/**
* @name resolvedAt
* @description Date when a case resolved(i.e discharged).
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} index - ensure database index
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* 2018-10-19T07:55:32.831Z
*/
const resolvedAt = {
type: Date,
index: true,
exportable: true,
fake: {
generator: 'date',
type: 'recent',
},
};
/**
* @name followedAt
* @description Latest date when a case followed up after discharged.
*
* @memberof Case
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} index - ensure database index
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.3.0
* @version 0.1.0
* @instance
* @example
* 2018-10-19T07:55:32.831Z
*/
const followedAt = {
type: Date,
index: true,
exportable: true,
fake: {
generator: 'date',
type: 'recent',
},
};
const DEFAULT_LOCALE = env.getString('DEFAULT_LOCALE', 'en');
const LOCALES = env.getStrings('LOCALES', DEFAULT_LOCALE);
/**
* @name properties
* @description A map of key value pairs to allow to associate
* other meaningful information to a case.
*
* @type {object}
* @property {object} type - schema(data) type
* @property {object} fake - fake data generator options
*
* @since 0.2.0
* @version 0.1.0
* @instance
* @example
* {
* "population": {
* "male": 1700000,
* "female": 2700000
* }
* }
*/
const properties = {
type: Map,
of: mongooseCommon.Mixed,
fake: (f) => f.helpers.createTransaction(),
};
/**
* @name locale
* @description Defines the party's language, region and any
* special variant preferences.
*
* @see {@link https://en.wikipedia.org/wiki/Locale_(computer_software)}
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} enum - list of acceptable values
* @property {boolean} index - ensure database index
* @property {boolean} searchable - allow for searching
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} default - default value set when none provided
* @property {object} fake - fake data generator options
*
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* en
*/
const locale = {
type: String,
trim: true,
enum: LOCALES,
index: true,
searchable: true,
taggable: true,
default: DEFAULT_LOCALE,
fake: true,
};
/**
* @name name
* @description Full name name of the party(i.e individual).
*
*
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} startcase - ensure start case(or title case)
* @property {boolean} index - ensure database index
* @property {boolean} searchable - allow for searching
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* Jane Doe
*/
const name = {
type: String,
trim: true,
startcase: true,
index: true,
searchable: true,
taggable: true,
exportable: true,
fake: {
generator: 'name',
type: 'findName',
},
};
/**
* @name age
* @description Current age of the party(i.e individual).
*
*
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} index - ensure database index
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* 23
*/
const age = {
type: Number,
index: true,
exportable: true,
fake: (f) => f.random.number({ min: 1, max: 100 }),
};
/**
* @name weight
* @description Current weight of the party(i.e individual).
*
*
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} index - ensure database index
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* 53
*/
const weight = {
type: Number,
index: true,
exportable: true,
fake: (f) => f.random.number({ min: 1, max: 250 }),
};
/**
* @name score
* @description Current score of the case followup.
*
* It used to derive case severity.
*
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} index - ensure database index
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* 4
*/
const score = {
type: Number,
index: true,
exportable: true,
fake: (f) => f.random.number({ min: 0, max: 5 }),
};
/**
* @name mobile
* @description A mobile phone number of the party(i.e individual).
*
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} index - ensure database index
* @property {boolean} searchable - allow for searching
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* 255714989796
*/
const mobile = {
type: String,
trim: true,
index: true,
searchable: true,
taggable: true,
exportable: true,
fake: (f) => f.helpers.replaceSymbolWithNumber('255714######'),
};
/**
* @name email
* @description Email address of the party(i.e individual).
*
* @type {object}
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} lowercase - force lower-casing
* @property {boolean} email - force to be a valid email address
* @property {boolean} index - ensure database index
* @property {boolean} searchable - allow for searching
* @property {boolean} taggable - allow field use for tagging
* @property {object} fake - fake data generator options
*
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* jane.doe@example.com
*/
const email = {
type: String,
trim: true,
lowercase: true,
email: true,
index: true,
searchable: true,
taggable: true,
exportable: true,
fake: (f) => f.internet.email(),
};
/**
* @name address
* @description A human readable description of location.
*
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} index - ensure database index
* @property {boolean} searchable - allow for searching
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* Tandale
*/
const address = {
type: String,
trim: true,
index: true,
searchable: true,
taggable: true,
exportable: true,
fake: {
generator: 'address',
type: 'county',
},
};
/**
* @name referral
* @description Referral number of the party(i.e patient or victim).
*
*
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} index - ensure database index
* @property {boolean} searchable - allow for searching
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* AMN-5657
*/
const referral = {
type: String,
trim: true,
index: true,
searchable: true,
taggable: true,
exportable: true,
fake: {
generator: 'finance',
type: 'account',
},
};
/**
* @name pcr
* @description Patient care record number of the
* party(i.e patient or victim).
*
*
* @property {object} type - schema(data) type
* @property {boolean} trim - force trimming
* @property {boolean} index - ensure database index
* @property {boolean} searchable - allow for searching
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} exportable - allow field use for exporting
* @property {object} fake - fake data generator options
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* PTN-8687
*/
const pcr = {
type: String,
trim: true,
index: true,
searchable: true,
taggable: true,
exportable: true,
fake: {
generator: 'finance',
type: 'account',
},
};
/**
* @name area
* @description Assignable administrative area of a party.
*
* @type {object}
* @property {object} type - schema(data) type
* @property {string} ref - referenced model(or collection)
* @property {boolean} index - ensure database index
* @property {boolean} exists - ensure ref exists before save
* @property {object} autopopulate - auto population(eager loading) options
* @property {boolean} taggable - allow field use for tagging
*
* @since 1.1.0
* @version 0.1.0
* @instance
* @example
* {
* "name": {"en": "Dar es Salaam"}
* }
*/
const area = {
type: mongooseCommon.ObjectId,
ref: predefine.Predefine.MODEL_NAME,
index: true,
exists: true,
aggregatable: { unwind: true },
autopopulate: AUTOPOPULATE_OPTION_AREA,
taggable: true,
exportable: {
header: 'Area',
format: (v) => {
return v && v.strings && common.compact([v.strings.name.en]).join(' - ');
},
order: 3,
default: 'NA',
},
default: eweaCommon.DEFAULT_SEEDS[eweaInternals.PREDEFINE_NAMESPACE_ADMINISTRATIVEAREA],
};
/**
* @name facility
* @description Assignable treatment facility(i.e Hospital etc).
*
* @type {object}
* @property {object} type - schema(data) type
* @property {string} ref - referenced model(or collection)
* @property {boolean} index - ensure database index
* @property {boolean} exists - ensure ref exists before save
* @property {object} autopopulate - auto population(eager loading) options
* @property {boolean} taggable - allow field use for tagging
*
* @since 1.1.0
* @version 0.1.0
* @instance
* @example
* {
* "name": {"en": "Amana Hospital"}
* }
*/
const facility = {
type: mongooseCommon.ObjectId,
ref: predefine.Predefine.MODEL_NAME,
index: true,
exists: true,
aggregatable: { unwind: true },
autopopulate: AUTOPOPULATE_OPTION_PREDEFINE,
taggable: true,
exportable: {
header: 'Facility',
format: (v) => {
return v && v.strings && common.compact([v.strings.name.en]).join(' - ');
},
order: 3,
default: 'NA',
},
};
/**
* @name gender
* @description Assignable or given gender to a party.
*
* @type {object}
* @property {object} type - schema(data) type
* @property {string} ref - referenced collection
* @property {boolean} index - ensure database index
* @property {boolean} exists - ensure ref exists before save
* @property {object} autopopulate - population options
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} default - default value set when none provided
*
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* {
* "name": { "en" : "Female" }
* }
*/
const gender = {
type: mongooseCommon.ObjectId,
ref: predefine.Predefine.MODEL_NAME,
index: true,
exists: true,
aggregatable: { unwind: true },
autopopulate: AUTOPOPULATE_OPTION_PREDEFINE,
taggable: true,
exportable: {
header: 'Gender',
format: (v) => {
return v && v.strings && common.compact([v.strings.name.en]).join(' - ');
},
order: 2,
default: 'NA',
},
default: eweaCommon.DEFAULT_SEEDS[eweaInternals.PREDEFINE_NAMESPACE_PARTYGENDER],
};
/**
* @name occupation
* @description Assignable or given occupation to a party.
*
* @type {object}
* @property {object} type - schema(data) type
* @property {string} ref - referenced collection
* @property {boolean} index - ensure database index
* @property {boolean} exists - ensure ref exists before save
* @property {object} autopopulate - population options
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} default - default value set when none provided
*
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* {
* "name": { "en" : "Unknown" }
* }
*/
const occupation = {
type: mongooseCommon.ObjectId,
ref: predefine.Predefine.MODEL_NAME,
index: true,
exists: true,
aggregatable: { unwind: true },
autopopulate: AUTOPOPULATE_OPTION_PREDEFINE,
taggable: true,
exportable: {
header: 'Occupation',
format: (v) => {
return v && v.strings && common.compact([v.strings.name.en]).join(' - ');
},
order: 2,
default: 'NA',
},
default: eweaCommon.DEFAULT_SEEDS[eweaInternals.PREDEFINE_NAMESPACE_PARTYOCCUPATION],
};
/**
* @name nationality
* @description Assignable or given nationality to a party.
*
* @type {object}
* @property {object} type - schema(data) type
* @property {string} ref - referenced collection
* @property {boolean} index - ensure database index
* @property {boolean} exists - ensure ref exists before save
* @property {object} autopopulate - population options
* @property {boolean} taggable - allow field use for tagging
* @property {boolean} default - default value set when none provided
*
* @since 0.2.0
* @version 0.1.0
* @instance
* @example
* {
* "name": { "en" : "Tanzanian" }
* }
*/
const nationality = {
type: mongooseCommon.ObjectId,
ref: predefine.Predefine.MODEL_NAME,
index: true,
exists: true,
aggregatable: { unwind: true },
autopopulate: AUTOPOPULATE_OPTION_PREDEFINE,
taggable: true,
exportable: {
header: 'Nationality',
format: (v) => {
return v && v.strings && common.compact([v.strings.name.en]).join(' - ');
},
order: 2,
default: 'NA',
},
default: eweaCommon.DEFAULT_SEEDS[eweaInternals.PREDEFINE_NAMESPACE_PARTYNATIONALITY],
};
/**
* @name nextOfKin
* @description A party who closest to a victim.
*
* @type {object}
* @property {string} name - Full name of the next of kin
* @property {string} mobile - Mobile phone number of the next of kin
* @property {string} email - Email address of the next of kin
* @property {string} locale - Locale of the next of kin
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
* @example
* {
* name: "Jane Doe",
* mobile: "+255715463739"
* email: "jane.doe@example.com"
* locale: "en"
* }
*/
const nextOfKin = mongooseCommon.createSubSchema({
name,
mobile,
email,
locale,
// csids,
});
/**
* @name victim
* @description A party(i.e patient or victim) whom a case is for.
*
* @type {object}
* @property {string} referral - Valid referral number
* @property {string} pcr - Valid patient care number
* @property {string} name - Full name of the victim
* @property {string} mobile - Mobile phone number of the victim
* @property {string} email - Email address of the victim
* @property {object} gender - Gender of the victim
* @property {number} age - Age of the victim
* @property {number} weight - Weight of the victim
* @property {object} occupation - Occupation of the victim
* @property {object} nationality - Nationality of the victim
* @property {string} address - Address of the victim
* @property {string} locale - Locale of the victim
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.2.0
* @instance
* @example
* {
* referral: "AMN-5657",
* pcr: "PTN-8687",
* name: "Jane Doe",
* mobile: "+255715463739",
* email: "jane.doe@example.com",
* gender: { name: { en: "Female"} },
* age: 23,
* weight: 53,
* occupation: { name: { en: "Businessman"} },
* nationality: { name: { en: "Tanzanian"} },
* address: "Tandale",
* area: { name: { en: "Dar es Salaam"} },
* locale: "en",
* nextOfKin: { name: "Halima Mdoe", mobile: "+255715463740" }
* }
*/
const victim = mongooseCommon.createSubSchema({
referral,
pcr,
name,
mobile,
email,
gender,
age,
// dob
weight,
occupation,
nationality,
address,
area,
locale,
nextOfKin,
// TODO: csids.
// TODO: lastKnownLocation
// TODO: lastKnownAddress
});
/**
* @name followup
* @description A party(i.e doctor or nurse) who followed case victim.
*
* @type {object}
* @property {object} follower - Following party
* @property {Date} followedAt - Latest date followed
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.2.0
* @instance
* @example
* {
* follower: {_id: "5bcda2c073dd0700048fb846", name: "Jane Doe" }
* followedAt: '2018-10-19T07:55:32.831Z',
* symptoms: { cough: 5 },
* outcome: 'Hospital',
* remarks: 'Handled'
* }
*/
const followup = mongooseCommon.createSubSchema({
follower,
followedAt,
symptoms: properties, // TODO: rename to data/instance/results
score, // TODO: systemScore vs followerScore
outcome,
remarks,
});
const SCHEMA = common.mergeObjects(
{ number }, // TODO: occured location
{ stage, severity },
{ victim },
{ description },
{ reportedAt, reporter },
{ resolvedAt, resolver },
{ followup },
{ remarks }
);
/**
* @module Case
* @namespace Case
* @name Case
* @description A representation of an entity which define and track cases
* during an emergency event.
*
* @see {@link https://en.wikipedia.org/wiki/Emergency}
*
* @author lally elias <lallyelias87@gmail.com>
* @license MIT
* @since 0.1.0
* @version 0.1.0
* @public
* @example
* const { Case } = require('@codetanzania/ewea-case');
* Case.create(case, (error, created) => { ... });
*/
const CaseSchema = mongooseCommon.createSchema(
SCHEMA,
CASE_SCHEMA_OPTIONS,
actions,
exportable
);
/*
*------------------------------------------------------------------------------
* Hooks
*------------------------------------------------------------------------------
*/
/**
* @name validate
* @function validate
* @description Case schema pre validation hook
* @param {Function} done callback to invoke on success or error
* @returns {object|Error} valid instance or error
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @private
*/
CaseSchema.pre('validate', function onPreValidate(done) {
return this.preValidate(done);
});
/*
*------------------------------------------------------------------------------
* Instance
*------------------------------------------------------------------------------
*/
/**
* @name preValidate
* @function preValidate
* @description Case schema pre validation hook logic
* @param {Function} done callback to invoke on success or error
* @returns {object|Error} valid instance or error
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @instance
*/
CaseSchema.methods.preValidate = function preValidate(done) {
// ensure reported date
this.reportedAt = this.reportedAt || this.createdAt || new Date();
// ensure case stage
if (!lodash.get(this, 'stage')) {
const staje = eweaInternals.DEFAULT_SEEDS[eweaInternals.PREDEFINE_NAMESPACE_CASESTAGE];
lodash.set(this, 'stage', staje);
}
// TODO: compute score
// TODO: move score to case base schema
// always: ensure case severity from followup score
const score = lodash.get(this, 'followup.score');
this.severity = eweaCommon.caseSeverityFor({ score });
// ensure victim gender
if (!lodash.get(this, 'victim.gender')) {
const gender = eweaInternals.DEFAULT_SEEDS[eweaInternals.PREDEFINE_NAMESPACE_PARTYGENDER];
lodash.set(this, 'victim.gender', gender);
}
// ensure victim default occupation
if (!lodash.get(this, 'victim.occupation')) {
const occupation = eweaInternals.DEFAULT_SEEDS[eweaInternals.PREDEFINE_NAMESPACE_PARTYOCCUPATION];
lodash.set(this, 'victim.occupation', occupation);
}
// ensure victim default nationality
if (!lodash.get(this, 'victim.nationality')) {
const nationality = eweaInternals.DEFAULT_SEEDS[eweaInternals.PREDEFINE_NAMESPACE_PARTYNATIONALITY];
lodash.set(this, 'victim.nationality', nationality);
}
// ensure victim default area
if (!lodash.get(this, 'victim.area')) {
const area = eweaInternals.DEFAULT_SEEDS[eweaInternals.PREDEFINE_NAMESPACE_ADMINISTRATIVEAREA];
lodash.set(this, 'victim.area', area);
}
// TODO: ensure event group and type
// TODO: ensure default values
// TODO: ensure case status
// return
return done(null, this);
};
/*
*------------------------------------------------------------------------------
* Statics
*------------------------------------------------------------------------------
*/
/* static constants */
CaseSchema.statics.MODEL_NAME = eweaInternals.MODEL_NAME_CASE;
CaseSchema.statics.COLLECTION_NAME = eweaInternals.COLLECTION_NAME_CASE;
CaseSchema.statics.OPTION_SELECT = CASE_OPTION_SELECT;
CaseSchema.statics.OPTION_AUTOPOPULATE = CASE_OPTION_AUTOPOPULATE;
/**
* @name prepareSeedCriteria
* @function prepareSeedCriteria
* @description Define seed data criteria
* @param {object} seed event to be seeded
* @returns {object} packed criteria for seeding
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @static
*/
CaseSchema.statics.prepareSeedCriteria = (seed) => {
const copyOfSeed = mongooseCommon.copyInstance(seed);
const criteria = common.idOf(copyOfSeed)
? lodash.pick(copyOfSeed, '_id')
: lodash.pick(copyOfSeed, 'number');
return criteria;
};
/* export case model */
const Case = mongooseCommon.model(eweaInternals.MODEL_NAME_CASE, CaseSchema);
/**
* @name ensureReporter
* @description Set reporter on request body
* @author lally elias <lallyelias87@gmail.com>
*
* @param {object} request valid http request
* @param {object} response valid http response
* @param {Function} next next middlware to invoke
* @returns {Function} next middlware to invoke
*
* @license MIT
* @since 0.1.0
* @version 1.0.0
* @public
*/
const ensureReporter = (request, response, next) => {
if (request.body && request.party) {
request.body.reporter = request.body.reporter || request.party;
}
return next();
};
/* constants */
const API_VERSION = env.getString('API_VERSION', '1.0.0');
const PATH_SINGLE = '/cases/:id';
const PATH_LIST = '/cases';
const PATH_EXPORT = '/cases/export';
const PATH_SCHEMA = '/cases/schema/';
/**
* @name CaseHttpRouter
* @namespace CaseHttpRouter
*
* @description A representation of an entity which define and track cases
* during an emergency event.
*
* @see {@link https://en.wikipedia.org/wiki/Emergency}
*
* @author lally elias <lallyelias87@gmail.com>
* @license MIT
* @since 0.1.0
* @version 1.0.0
* @public
*/
const router = new expressRestActions.Router({
version: API_VERSION,
});
/**
* @name GetCasees
* @memberof CaseHttpRouter
* @description Returns a list of cases
*/
router.get(
PATH_LIST,
expressRestActions.getFor({
get: (options, done) => Case.get(options, done),
})
);
/**
* @name GetCaseSchema
* @memberof CaseHttpRouter
* @description Returns case json schema definition
*/
router.get(
PATH_SCHEMA,
expressRestActions.schemaFor({
getSchema: (query, done) => {
const jsonSchema = Case.jsonSchema();
return done(null, jsonSchema);
},
})
);
/**
* @name ExportCasees
* @memberof CaseHttpRouter
* @description Export cases as csv
*/
router.get(
PATH_EXPORT,
expressRestActions.downloadFor({
download: (options, done) => {
const fileName = `cases_exports_${Date.now()}.csv`;
const readStream = Case.exportCsv(options);
return done(null, { fileName, readStream });
},
})
);
/**
* @name PostCase
* @memberof CaseHttpRouter
* @description Create new case
*/
router.post(
PATH_LIST,
ensureReporter,
expressRestActions.postFor({
post: (body, done) => Case.post(body, done),
})
);
/**
* @name GetCase
* @memberof CaseHttpRouter
* @description Get existing case
*/
router.get(
PATH_SINGLE,
expressRestActions.getByIdFor({
getById: (options, done) => Case.getById(options, done),
})
);
/**
* @name PatchCase
* @memberof CaseHttpRouter
* @description Patch existing case
*/
router.patch(
PATH_SINGLE,
expressRestActions.patchFor({
patch: (options, done) => Case.patch(options, done),
})
);
/**
* @name PutCase
* @memberof CaseHttpRouter
* @description Put existing case
*/
router.put(
PATH_SINGLE,
expressRestActions.putFor({
put: (options, done) => Case.put(options, done),
})
);
/**
* @name DeleteCase
* @memberof CaseHttpRouter
* @description Delete existing case
*/
router.delete(
PATH_SINGLE,
expressRestActions.deleteFor({
del: (options, done) => Case.del(options, done),
soft: true,
})
);
/**
* @module Case
* @namespace Case
* @name Case
* @description A representation of an entity which define and track cases
* during an emergency event.
*
* @see {@link https://en.wikipedia.org/wiki/Emergency}
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
* @license MIT
* @example
*
* const { Case, start } = require('@codetanzania/ewea-case');
* start(error => { ... });
*
*/
/**
* @name info
* @description package information
* @type {object}
*
* @author lally elias <lallyelias87@gmail.com>
* @since 1.0.0
* @version 0.1.0
*/
const info = common.pkg(
`${__dirname}/package.json`,
'name',
'description',
'version',
'license',
'homepage',
'repository',
'bugs',
'sandbox',
'contributors'
);
/**
* @name apiVersion
* @description http router api version
* @type {string}
*
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
*/
const apiVersion = env.apiVersion();
/**
* @function start
* @name start
* @description start http server
* @param {Function} done callback to invoke on success or error
* @author lally elias <lallyelias87@gmail.com>
* @since 0.1.0
* @version 0.1.0
*/
const start = (done) => {
// connect mongodb
mongooseCommon.connect((error) => {
// back-off on connect error
if (error) {
return done(error);
}
// ensure file models
file.createModels();
// mount case router
expressCommon.mount(router);
// start http server
return expressRestActions.start(done);
});
};
exports.Case = Case;
exports.apiVersion = apiVersion;
exports.caseRouter = router;
exports.info = info;
exports.start = start;