UNPKG

@codetanzania/ewea-reports

Version:
544 lines (503 loc) 13.5 kB
import { PREDEFINE_NAMESPACE_ADMINISTRATIVELEVEL, PREDEFINE_NAMESPACE_ADMINISTRATIVEAREA, PREDEFINE_NAMESPACE_PARTYGROUP, PREDEFINE_NAMESPACE_PARTYROLE, } from '@codetanzania/ewea-internals'; import { isFunction } from 'lodash'; import { waterfall } from 'async'; import { safeMergeObjects } from '@lykmapipo/common'; import { Party } from '@codetanzania/emis-stakeholder'; import { DEFAULT_PREDEFINE_RELATION } from '@codetanzania/ewea-common'; // start: constants // order: base to specific const PARTY_TYPE_FOCAL = 'Focal'; const DEFAULT_RELATION_LEVEL = safeMergeObjects(DEFAULT_PREDEFINE_RELATION, { namespace: PREDEFINE_NAMESPACE_ADMINISTRATIVELEVEL, }); const DEFAULT_RELATION_AREA = safeMergeObjects(DEFAULT_PREDEFINE_RELATION, { namespace: PREDEFINE_NAMESPACE_ADMINISTRATIVEAREA, }); const DEFAULT_RELATION_GROUP = safeMergeObjects(DEFAULT_PREDEFINE_RELATION, { namespace: PREDEFINE_NAMESPACE_PARTYGROUP, }); const DEFAULT_RELATION_ROLE = safeMergeObjects(DEFAULT_PREDEFINE_RELATION, { namespace: PREDEFINE_NAMESPACE_PARTYROLE, }); // start: extra metric fields // order: base to specific /** * @constant * @name PARTY_BASE_METRIC_FIELDS * @description Adds new metric fields to the next stage in the pipeline. * @type {object} * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.2.0 * @version 0.1.0 */ export const PARTY_BASE_METRIC_FIELDS = { metrics: { focal: { $cond: { if: { $eq: ['$type', PARTY_TYPE_FOCAL] }, then: 1, else: 0 }, }, agency: { $cond: { if: { $ne: ['$type', PARTY_TYPE_FOCAL] }, then: 1, else: 0 }, }, level: { $cond: { if: { $not: '$level' }, then: 0, else: 1 }, }, area: { $cond: { if: { $not: '$area' }, then: 0, else: 1 }, }, group: { $cond: { if: { $not: '$group' }, then: 0, else: 1 }, }, role: { $cond: { if: { $not: '$role' }, then: 0, else: 1 }, }, active: { $cond: { if: { $not: '$deletedAt' }, then: 1, else: 0 }, }, inactive: { $cond: { if: { $not: '$deletedAt' }, then: 0, else: 1 }, }, }, }; // start: projections // order: base to specific /** * @constant * @name PARTY_BASE_LEVEL_PROJECTION * @description Party level fields passed to the next stage in the pipeline. * @type {object} * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.2.0 * @version 0.1.0 */ export const PARTY_BASE_LEVEL_PROJECTION = { _id: 1, namespace: 1, name: '$level.strings.name', abbreviation: '$level.strings.abbreviation', color: '$level.strings.color', weight: '$level.numbers.weight', }; /** * @constant * @name PARTY_BASE_AREA_PROJECTION * @description Party area fields passed to the next stage in the pipeline. * @type {object} * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.2.0 * @version 0.1.0 */ export const PARTY_BASE_AREA_PROJECTION = { _id: 1, namespace: 1, name: '$area.strings.name', abbreviation: '$area.strings.abbreviation', color: '$area.strings.color', weight: '$area.numbers.weight', // TODO: ensure from level }; /** * @constant * @name PARTY_BASE_GROUP_PROJECTION * @description Party group fields passed to the next stage in the pipeline. * @type {object} * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.2.0 * @version 0.1.0 */ export const PARTY_BASE_GROUP_PROJECTION = { _id: 1, namespace: 1, name: '$group.strings.name', abbreviation: '$group.strings.abbreviation', color: '$group.strings.color', weight: '$group.numbers.weight', }; /** * @constant * @name PARTY_BASE_ROLE_PROJECTION * @description Party role fields passed to the next stage in the pipeline. * @type {object} * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.2.0 * @version 0.1.0 */ export const PARTY_BASE_ROLE_PROJECTION = { _id: 1, namespace: 1, name: '$role.strings.name', abbreviation: '$role.strings.abbreviation', color: '$role.strings.color', weight: '$role.numbers.weight', }; /** * @constant * @name PARTY_DEFAULT_PROJECTION * @description Normalize Party fields with defaults before pass to * the next stage in the pipeline. * @type {object} * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 */ export const PARTY_DEFAULT_PROJECTION = { _id: 1, type: 1, createdAt: 1, updatedAt: 1, metrics: 1, level: { $ifNull: ['$level', DEFAULT_RELATION_LEVEL] }, area: { $ifNull: ['$area', DEFAULT_RELATION_AREA] }, group: { $ifNull: ['$group', DEFAULT_RELATION_GROUP] }, role: { $ifNull: ['$role', DEFAULT_RELATION_ROLE] }, }; /** * @constant * @name PARTY_BASE_PROJECTION * @description Party fields passed to the next stage in the pipeline. * @type {object} * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.2.0 * @version 0.1.0 */ export const PARTY_BASE_PROJECTION = { _id: 1, type: 1, createdAt: 1, updatedAt: 1, metrics: 1, level: PARTY_BASE_LEVEL_PROJECTION, area: PARTY_BASE_AREA_PROJECTION, group: PARTY_BASE_GROUP_PROJECTION, role: PARTY_BASE_ROLE_PROJECTION, }; // start: facets // order: base, overall to specific /** * @constant * @name PARTY_FACET_OVERVIEW * @description General `Party` overview facet. * @type {object} * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.2.0 * @version 0.1.0 */ export const PARTY_FACET_OVERVIEW = { overview: [ { $group: { _id: null, total: { $sum: 1 }, agency: { $sum: '$metrics.agency' }, focal: { $sum: '$metrics.focal' }, level: { $sum: '$metrics.level' }, area: { $sum: '$metrics.area' }, group: { $sum: '$metrics.group' }, role: { $sum: '$metrics.role' }, active: { $sum: '$metrics.active' }, inactive: { $sum: '$metrics.inactive' }, }, }, { $project: { _id: 0, }, }, ], }; /** * @constant * @name PARTY_FACET_OVERALL_LEVEL * @description Overall `Party` breakdown by administrative level facet. * @type {object} * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 */ export const PARTY_FACET_OVERALL_LEVEL = { levels: [ { $group: { _id: '$level._id', total: { $sum: 1 }, agency: { $sum: '$metrics.agency' }, focal: { $sum: '$metrics.focal' }, active: { $sum: '$metrics.active' }, inactive: { $sum: '$metrics.inactive' }, namespace: { $first: '$level.namespace' }, name: { $first: '$level.name' }, abbreviation: { $first: '$level.abbreviation' }, color: { $first: '$level.color' }, weight: { $first: '$level.weight' }, }, }, { $sort: { weight: 1, }, }, ], }; /** * @constant * @name PARTY_FACET_OVERALL_AREA * @description Overall `Party` breakdown by administrative area facet. * @type {object} * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 */ export const PARTY_FACET_OVERALL_AREA = { areas: [ { $group: { _id: '$area._id', total: { $sum: 1 }, agency: { $sum: '$metrics.agency' }, focal: { $sum: '$metrics.focal' }, active: { $sum: '$metrics.active' }, inactive: { $sum: '$metrics.inactive' }, namespace: { $first: '$area.namespace' }, name: { $first: '$area.name' }, abbreviation: { $first: '$area.abbreviation' }, color: { $first: '$area.color' }, weight: { $first: '$area.weight' }, }, }, { $sort: { weight: 1, }, }, ], }; /** * @constant * @name PARTY_FACET_OVERALL_GROUP * @description Overall `Party` breakdown by group facet. * @type {object} * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 */ export const PARTY_FACET_OVERALL_GROUP = { groups: [ { $group: { _id: '$group._id', total: { $sum: 1 }, agency: { $sum: '$metrics.agency' }, focal: { $sum: '$metrics.focal' }, active: { $sum: '$metrics.active' }, inactive: { $sum: '$metrics.inactive' }, namespace: { $first: '$group.namespace' }, name: { $first: '$group.name' }, abbreviation: { $first: '$group.abbreviation' }, color: { $first: '$group.color' }, weight: { $first: '$group.weight' }, }, }, { $sort: { weight: 1, }, }, ], }; /** * @constant * @name PARTY_FACET_OVERALL_ROLE * @description Overall `Party` breakdown by role facet. * @type {object} * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 */ export const PARTY_FACET_OVERALL_ROLE = { roles: [ { $group: { _id: '$role._id', total: { $sum: 1 }, agency: { $sum: '$metrics.agency' }, focal: { $sum: '$metrics.focal' }, active: { $sum: '$metrics.active' }, inactive: { $sum: '$metrics.inactive' }, namespace: { $first: '$role.namespace' }, name: { $first: '$role.name' }, abbreviation: { $first: '$role.abbreviation' }, color: { $first: '$role.color' }, weight: { $first: '$role.weight' }, }, }, { $sort: { weight: 1, }, }, ], }; // start: aggregations // order: base to specific /** * @function getPartyBaseAggregation * @name getPartyBaseAggregation * @description Create base `Party` aggregations with all fields looked up and * un-winded for further stages in the pipeline. * @param {object} [criteria={}] conditions which will be applied in first * aggregation stage. * @returns {object|Error} valid base aggregation for party or error * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.1.0 * @version 0.2.0 * @static * @public * @example * * getPartyBaseAggregation({ ... }); * //=> Aggregation{ ... } * */ export const getPartyBaseAggregation = (criteria = {}) => { // initialize party base aggregation const base = Party.lookup(criteria); // project default on relations base.project(PARTY_DEFAULT_PROJECTION); // add base projection base.project(PARTY_BASE_PROJECTION); // TODO: project per relations before add metrics // add extra metric fields base.addFields(PARTY_BASE_METRIC_FIELDS); // return party base aggregation return base; }; /** * @function getPartyOverview * @name getPartyOverview * @description Create `Party` overview analysis. * @param {object} [criteria={}] conditions which will be applied in analysis * @param {Function} done callback to invoke on success or error * @returns {object|Error} valid party overview analysis or error * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.4.0 * @version 0.1.0 * @static * @public * @example * * getPartyOverview({ ... }); * //=> { total: 7, ... } * */ export const getPartyOverview = (criteria, done) => { // normalize arguments const filter = isFunction(criteria) ? {} : criteria; const cb = isFunction(criteria) ? criteria : done; // obtain party base aggregation const base = getPartyBaseAggregation(filter); // add facets const facets = { ...PARTY_FACET_OVERVIEW, }; base.facet(facets); // run aggregation const aggregate = (next) => base.exec(next); const normalize = (result, next) => { // ensure data const { overview } = safeMergeObjects(...result); // normalize result const data = safeMergeObjects(...overview); // return normalize result return next(null, data); }; // return const tasks = [aggregate, normalize]; return waterfall(tasks, cb); }; /** * @function getPartyAnalysis * @name getPartyAnalysis * @description Create `Party` analysis. * @param {object} [criteria={}] conditions which will be applied in analysis * @param {Function} done callback to invoke on success or error * @returns {object|Error} valid party analysis or error * * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.2.0 * @version 0.2.0 * @static * @public * @example * * getPartyAnalysis({ ... }); * //=> { data: { overview: { ... } }, ... } * */ export const getPartyAnalysis = (criteria, done) => { // normalize arguments const filter = isFunction(criteria) ? {} : criteria; const cb = isFunction(criteria) ? criteria : done; // obtain party base aggregation const base = getPartyBaseAggregation(filter); // add facets const facets = { ...PARTY_FACET_OVERVIEW, ...PARTY_FACET_OVERALL_LEVEL, ...PARTY_FACET_OVERALL_AREA, ...PARTY_FACET_OVERALL_GROUP, ...PARTY_FACET_OVERALL_ROLE, }; base.facet(facets); // run aggregation const aggregate = (next) => base.exec(next); const normalize = (result, next) => { // TODO: extract to utils // ensure data const { overview, levels, areas, groups, roles } = safeMergeObjects( ...result ); // normalize result const data = safeMergeObjects({ overview: safeMergeObjects(...overview), overall: { levels, areas, groups, roles }, }); // return normalize result return next(null, { data }); }; // return const tasks = [aggregate, normalize]; return waterfall(tasks, cb); }; // TODO: dataset only aggregation; getPartyDatasets