[DEV-2871] Added DocumentProgramFormatter to format program data by step, group and field metadata
parent
68c2704bf8
commit
9907c698d1
|
@ -0,0 +1,230 @@
|
||||||
|
/**
|
||||||
|
* Formats program bucket data
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 R-T Specialty, LLC.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework.
|
||||||
|
*
|
||||||
|
* liza is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const Class = require( 'easejs' ).Class;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats program bucket data
|
||||||
|
*
|
||||||
|
* This takes a document and formats the data in a
|
||||||
|
* structured manner.
|
||||||
|
*/
|
||||||
|
module.exports = Class( 'DocumentProgramFormatter',
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Current program
|
||||||
|
*
|
||||||
|
* @type {Program}
|
||||||
|
*/
|
||||||
|
'private _program': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs classification matching on fields
|
||||||
|
*
|
||||||
|
* A field will have a positive match for a given index if all of its
|
||||||
|
* classes match
|
||||||
|
*
|
||||||
|
* @type {FieldClassMatcher}
|
||||||
|
*/
|
||||||
|
'private _class_matcher': null,
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize document formatter
|
||||||
|
*
|
||||||
|
* @param {Program} program active program
|
||||||
|
* @param {FieldClassMatcher} class_matcher class/field matcher
|
||||||
|
*/
|
||||||
|
constructor( program, class_matcher )
|
||||||
|
{
|
||||||
|
this._program = program;
|
||||||
|
this._class_matcher = class_matcher;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns formatted document bucket data
|
||||||
|
* Calls FieldClassMatcher.match to retrieve
|
||||||
|
* index of show/hide values for each field
|
||||||
|
*
|
||||||
|
* @param {Bucket} bucket document bucket
|
||||||
|
*
|
||||||
|
* @return {Object} JSON object
|
||||||
|
*/
|
||||||
|
'public format'( bucket )
|
||||||
|
{
|
||||||
|
return new Promise( ( resolve, reject ) =>
|
||||||
|
{
|
||||||
|
const cmatch = this._program.classify( bucket.getData() );
|
||||||
|
|
||||||
|
this._class_matcher.match( cmatch, ( field_matches ) =>
|
||||||
|
{
|
||||||
|
const len = this._program.steps.length;
|
||||||
|
const data = this._parseSteps( len, bucket, field_matches );
|
||||||
|
|
||||||
|
resolve( data );
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses step data
|
||||||
|
*
|
||||||
|
* @param {Integer} len step length
|
||||||
|
* @param {Bucket} bucket document bucket
|
||||||
|
* @param {Array} field_matches array of matches for fields
|
||||||
|
*
|
||||||
|
* @return {Object} step data
|
||||||
|
*/
|
||||||
|
'private _parseSteps'( len, bucket, field_matches )
|
||||||
|
{
|
||||||
|
const data = { steps: [] };
|
||||||
|
|
||||||
|
for ( let i = 1; i < len; i++ )
|
||||||
|
{
|
||||||
|
const step = {};
|
||||||
|
const step_groups = this._program.steps[ i ].groups;
|
||||||
|
|
||||||
|
const groups = this._parseGroups( step_groups, bucket, field_matches );
|
||||||
|
|
||||||
|
step.title = this._program.steps[ i ].title;
|
||||||
|
step.groups = groups;
|
||||||
|
|
||||||
|
data.steps.push( step );
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses group data
|
||||||
|
*
|
||||||
|
* @param {Array} step_groups array of group data
|
||||||
|
* @param {Bucket} bucket document bucket
|
||||||
|
* @param {Array} field_matches array of matches for fields
|
||||||
|
*
|
||||||
|
* @return {Array} array of groups
|
||||||
|
*/
|
||||||
|
'private _parseGroups'( step_groups, bucket, field_matches )
|
||||||
|
{
|
||||||
|
const groups = [];
|
||||||
|
|
||||||
|
for ( let group in step_groups )
|
||||||
|
{
|
||||||
|
const step_group = {};
|
||||||
|
const group_id = step_groups[ group ];
|
||||||
|
const group_title = this._program.groups[ group_id ].title || "";
|
||||||
|
const fields = this._program.groupExclusiveFields[ group_id ];
|
||||||
|
|
||||||
|
const questions = this._parseFields( fields, bucket, field_matches );
|
||||||
|
|
||||||
|
step_group.title = group_title;
|
||||||
|
step_group.questions = questions;
|
||||||
|
groups.push( step_group );
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses fields/question data
|
||||||
|
*
|
||||||
|
* @param {Array} fields array of field data
|
||||||
|
* @param {Bucket} bucket document bucket
|
||||||
|
* @param {Array} field_matches array of matches for fields
|
||||||
|
*
|
||||||
|
* @return {Array} array of questions
|
||||||
|
*/
|
||||||
|
'private _parseFields'( fields, bucket, field_matches )
|
||||||
|
{
|
||||||
|
const questions = [];
|
||||||
|
|
||||||
|
const classes = field_matches.__classes;
|
||||||
|
|
||||||
|
for ( let field in fields )
|
||||||
|
{
|
||||||
|
const field_id = fields[ field ];
|
||||||
|
|
||||||
|
// Don't include fields that are not in program.fields
|
||||||
|
if ( typeof this._program.fields[ field_id ] === "undefined" )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const field_value = bucket.getDataByName( field_id );
|
||||||
|
const field_label = this._program.fields[ field_id ].label;
|
||||||
|
const question = {};
|
||||||
|
|
||||||
|
question.id = field_id;
|
||||||
|
question.label = field_label;
|
||||||
|
question.value = field_value;
|
||||||
|
question.applicable = this._getApplicable(
|
||||||
|
classes,
|
||||||
|
field_id,
|
||||||
|
field_value
|
||||||
|
);
|
||||||
|
|
||||||
|
questions.push( question );
|
||||||
|
}
|
||||||
|
|
||||||
|
return questions;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine when a field is shown by index
|
||||||
|
* Map boolean values of [0, 1] to [true, false]
|
||||||
|
*
|
||||||
|
* @param {Object} classes object of visibility classes
|
||||||
|
* @param {String} field_id id of field
|
||||||
|
* @param {Object} field_value field object
|
||||||
|
*
|
||||||
|
* @return {Array.<boolean>} array of booleans
|
||||||
|
*/
|
||||||
|
'private _getApplicable'( classes, field_id, field_value )
|
||||||
|
{
|
||||||
|
// If object is undefined, default to array of true
|
||||||
|
if ( typeof this._program.whens[ field_id ] === "undefined" )
|
||||||
|
{
|
||||||
|
return field_value.map( _ => true );
|
||||||
|
}
|
||||||
|
|
||||||
|
const class_id = this._program.whens[ field_id ][ 0 ];
|
||||||
|
const indexes = classes[ class_id ].indexes;
|
||||||
|
|
||||||
|
// Map indexes of 0, 1 to true, false
|
||||||
|
if ( Array.isArray( indexes ) )
|
||||||
|
{
|
||||||
|
return indexes.map( x => !!x );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return field_value.map( _ => !!indexes );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
} );
|
|
@ -53,6 +53,14 @@ const {
|
||||||
DataApiManager,
|
DataApiManager,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
document: {
|
||||||
|
DocumentProgramFormatter,
|
||||||
|
},
|
||||||
|
|
||||||
|
field: {
|
||||||
|
FieldClassMatcher,
|
||||||
|
},
|
||||||
|
|
||||||
server: {
|
server: {
|
||||||
DocumentServer,
|
DocumentServer,
|
||||||
|
|
||||||
|
@ -450,6 +458,22 @@ function doRoute( program, request, data, resolve, reject )
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
else if ( cmd == 'progdata' )
|
||||||
|
{
|
||||||
|
acquireReadLock( quote_id, request, function()
|
||||||
|
{
|
||||||
|
handleRequest( function( quote )
|
||||||
|
{
|
||||||
|
const response = UserResponse( request );
|
||||||
|
const bucket = quote.getBucket();
|
||||||
|
const class_matcher = FieldClassMatcher( program.whens );
|
||||||
|
|
||||||
|
DocumentProgramFormatter( program, class_matcher ).format( bucket )
|
||||||
|
.then( data => response.ok( data ) )
|
||||||
|
.catch( e => response.error( e ) );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
else if ( cmd === 'mkrev' )
|
else if ( cmd === 'mkrev' )
|
||||||
{
|
{
|
||||||
// the database operation for this is atomic and disjoint from
|
// the database operation for this is atomic and disjoint from
|
||||||
|
|
|
@ -0,0 +1,247 @@
|
||||||
|
/**
|
||||||
|
* Test of DocumentProgramFormatter
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 R-T Specialty, LLC.
|
||||||
|
*
|
||||||
|
* This file is part of liza.
|
||||||
|
*
|
||||||
|
* liza is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const chai = require( 'chai' );
|
||||||
|
const expect = chai.expect;
|
||||||
|
const sinon = require( 'sinon' );
|
||||||
|
|
||||||
|
const {
|
||||||
|
document: {
|
||||||
|
DocumentProgramFormatter: Sut,
|
||||||
|
},
|
||||||
|
|
||||||
|
field: {
|
||||||
|
FieldClassMatcher,
|
||||||
|
},
|
||||||
|
|
||||||
|
} = require( '../../' );
|
||||||
|
|
||||||
|
chai.use( require( 'chai-as-promised' ) );
|
||||||
|
|
||||||
|
|
||||||
|
describe( 'DocumentProgramFormatter', () =>
|
||||||
|
{
|
||||||
|
it( "formats bucket data", () =>
|
||||||
|
{
|
||||||
|
const bucket_data = {
|
||||||
|
sell_alcohol: [ "foo", "" ],
|
||||||
|
serve_alcohol: [ "" ],
|
||||||
|
sell_ecigs: [ "", "bar" ],
|
||||||
|
dist_ecigs: [ "" ],
|
||||||
|
field_no_label: [ "" ],
|
||||||
|
field_no_array: [ "bar" ],
|
||||||
|
field_no_vis: [ "true" ]
|
||||||
|
};
|
||||||
|
|
||||||
|
const expected_object = {
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
title: "Manage Quote",
|
||||||
|
groups: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "General Information",
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
title: "Group One",
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
id: "sell_alcohol",
|
||||||
|
label: "Does the insured sell alcohol?",
|
||||||
|
value: [ "foo", "" ],
|
||||||
|
applicable: [ true, false ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "serve_alcohol",
|
||||||
|
label: "Does the insured serve alcohol?",
|
||||||
|
value: [ "" ],
|
||||||
|
applicable: [ false ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "field_no_vis",
|
||||||
|
label: "Does this field have a visibility class?",
|
||||||
|
value: [ "true" ],
|
||||||
|
applicable: [ true ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "",
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
id: "sell_ecigs",
|
||||||
|
label: "Does the insured sell e-cigarettes?",
|
||||||
|
value: [ "", "bar" ],
|
||||||
|
applicable: [ false, true ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "dist_ecigs",
|
||||||
|
label: "Does the Insured distribute Electronic Cigarette products?",
|
||||||
|
value: [ "" ],
|
||||||
|
applicable: [ false ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "field_no_array",
|
||||||
|
label: "Does this field have an array for the visibility class?",
|
||||||
|
value: [ "bar" ],
|
||||||
|
applicable: [ true ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const bucket = createStubBucket( bucket_data );
|
||||||
|
const program = createStubProgram();
|
||||||
|
const class_matcher = createStubClassMatcher();
|
||||||
|
|
||||||
|
return expect(
|
||||||
|
Sut( program, class_matcher ).format( bucket )
|
||||||
|
).to.eventually.deep.equal( expected_object );
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
function createStubClassMatcher()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
match( _, callback )
|
||||||
|
{
|
||||||
|
callback({
|
||||||
|
__classes:
|
||||||
|
{
|
||||||
|
'--vis-sell-alcohol': { is: true, indexes: [1,0] },
|
||||||
|
'--vis-serve-alcohol': { is: false, indexes: [0] },
|
||||||
|
'--vis-sell-ecigs': { is: false, indexes: [0,1] },
|
||||||
|
'--vis-dist-ecigs': { is: true, indexes: [0] },
|
||||||
|
'--vis-no-array': { is: true, indexes: 1 },
|
||||||
|
}
|
||||||
|
}) ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createStubBucket( metadata )
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
getDataByName: name => metadata[ name ],
|
||||||
|
getData()
|
||||||
|
{
|
||||||
|
return metadata;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createStubProgram()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
title: "Index 0",
|
||||||
|
groups: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Manage Quote",
|
||||||
|
groups: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "General Information",
|
||||||
|
groups: [ 'group_one', 'group_two' ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
classify( bucket_data )
|
||||||
|
{
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
groups:
|
||||||
|
{
|
||||||
|
'group_one':
|
||||||
|
{
|
||||||
|
title: "Group One"
|
||||||
|
},
|
||||||
|
'group_two': {},
|
||||||
|
},
|
||||||
|
fields:
|
||||||
|
{
|
||||||
|
sell_alcohol:
|
||||||
|
{
|
||||||
|
label: "Does the insured sell alcohol?",
|
||||||
|
type: "noyes",
|
||||||
|
required: "true",
|
||||||
|
},
|
||||||
|
serve_alcohol:
|
||||||
|
{
|
||||||
|
label: "Does the insured serve alcohol?",
|
||||||
|
type: "noyes",
|
||||||
|
required: "true"
|
||||||
|
},
|
||||||
|
sell_ecigs:
|
||||||
|
{
|
||||||
|
label: "Does the insured sell e-cigarettes?",
|
||||||
|
type: "noyes",
|
||||||
|
required: "true"
|
||||||
|
},
|
||||||
|
dist_ecigs:
|
||||||
|
{
|
||||||
|
label: "Does the Insured distribute Electronic Cigarette products?",
|
||||||
|
type: "noyes",
|
||||||
|
required: "true"
|
||||||
|
},
|
||||||
|
field_no_array:
|
||||||
|
{
|
||||||
|
label: "Does this field have an array for the visibility class?",
|
||||||
|
type: "noyes",
|
||||||
|
required: "true"
|
||||||
|
},
|
||||||
|
field_no_vis:
|
||||||
|
{
|
||||||
|
label: "Does this field have a visibility class?",
|
||||||
|
type: "noyes",
|
||||||
|
required: "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
groupExclusiveFields:
|
||||||
|
{
|
||||||
|
'group_one': [ "sell_alcohol", "serve_alcohol", "field_no_label", "field_no_vis" ],
|
||||||
|
'group_two': [ "sell_ecigs", "dist_ecigs", "field_no_array" ],
|
||||||
|
|
||||||
|
},
|
||||||
|
whens:
|
||||||
|
{
|
||||||
|
sell_alcohol: [ "--vis-sell-alcohol" ],
|
||||||
|
serve_alcohol: [ "--vis-serve-alcohol" ],
|
||||||
|
sell_ecigs: [ "--vis-sell-ecigs" ],
|
||||||
|
dist_ecigs: [ "--vis-dist-ecigs" ],
|
||||||
|
field_no_array: [ "--vis-no-array" ],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue