Skip to content
This repository has been archived by the owner on Feb 1, 2019. It is now read-only.

Commit

Permalink
Merge pull request #121 from cfpb/milestone9
Browse files Browse the repository at this point in the history
Milestone9
  • Loading branch information
doelleri committed Apr 13, 2015
2 parents c312b86 + b156778 commit e4e3d7c
Show file tree
Hide file tree
Showing 27 changed files with 945 additions and 262 deletions.
142 changes: 112 additions & 30 deletions engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@
/* global -Promise */
'use strict';

var EngineBaseConditions = require('./lib/engineBaseConditions'),
var CSVProcessor = require('./lib/csvProcessor'),
StringStreamPromise = require('./lib/stringStreamPromise'),
EngineBaseConditions = require('./lib/engineBaseConditions'),
EngineCustomConditions = require('./lib/engineCustomConditions'),
EngineCustomDataLookupConditions = require('./lib/engineCustomDataLookupConditions'),
EngineApiInterface = require('./lib/engineApiInterface'),
EngineLocalDB = require('./lib/engineLocalDB'),
RuleParseAndExec = require('./lib/ruleParseAndExec'),
RuleProgress = require('./lib/ruleProgress'),
utils = require('./lib/utils'),
hmdajson = require('./lib/hmdajson'),
hmdaRuleSpec = require('hmda-rule-spec'),
_ = require('underscore'),
_ = require('lodash'),
stream = require('stream'),
EventEmitter = require('events').EventEmitter,
Promise = require('bluebird');

function Errors() {
Expand All @@ -25,6 +29,14 @@ function Errors() {
};
}

function Progress() {
return {
events: new EventEmitter(),
count: 0,
estimate: 0
};
}

/**
* Construct a new HMDAEngine instance
* @constructs HMDAEngine
Expand All @@ -33,9 +45,10 @@ function HMDAEngine() {
this.apiURL;
this.currentYear;
this.errors = new Errors();
this.progress = new Progress();
this._DEBUG_LEVEL = 0;
this._HMDA_JSON = {};
this._CONCURRENT_RULES = 10;
this._CONCURRENT_LARS = 100;
this._LOCAL_DB = null;
this._USE_LOCAL_DB = false;
}
Expand Down Expand Up @@ -95,6 +108,23 @@ HMDAEngine.prototype.getErrors = function() {
return this.errors;
};

/**
* clears out the counts and estimates for the progress object
*/
HMDAEngine.prototype.clearProgress = function() {
this.progress.count = 0;
this.progress.estimate = 0;
};

/**
* Get the progress object used for task completion events displayed on the progress bar
* @return {object} Progress object containing an eventemitter for progress notification
*/
HMDAEngine.prototype.getProgress = function() {
return this.progress;
};


/**
* Clear the current HMDA JSON object from the engine
*/
Expand Down Expand Up @@ -234,8 +264,10 @@ HMDAEngine.prototype.getTotalsByMSA = function(hmdaFile) {
.collect(function(value, key) {
return this.getMSAName(key).then(function(msaName) {
var result = {msaCode: key, msaName: msaName, totalLAR: 0, totalLoanAmount: 0, totalConventional: 0, totalFHA: 0, totalVA: 0, totalFSA: 0,
total1To4Family: 0, totalMFD: 0, totalMultifamily: 0, totalHomePurchase: 0, totalHomeImprovement: 0, totalRefinance: 0};
_.each(value, function(element) {
total1To4Family: 0, totalMFD: 0, totalMultifamily: 0, totalHomePurchase: 0, totalHomeImprovement: 0, totalRefinance: 0},
len = value.length;
for (var i=0; i < len; i++) {
var element = value[i];
result.totalLAR++;
result.totalLoanAmount += +element.loanAmount;

Expand Down Expand Up @@ -264,7 +296,7 @@ HMDAEngine.prototype.getTotalsByMSA = function(hmdaFile) {
} else if (element.loanPurpose === '3') {
result.totalRefinance++;
}
});
}
return result;
});
}.bind(this))
Expand All @@ -283,11 +315,8 @@ HMDAEngine.prototype.runSyntactical = function(year) {
if (this.getDebug()) {
console.time('time to run syntactical rules');
}
return Promise.all([
this.runEdits(year, 'ts', 'syntactical'),
this.runEdits(year, 'lar', 'syntactical'),
this.runEdits(year, 'hmda', 'syntactical')
])
this.calcEstimatedTasks(year, ['ts','lar','hmda'], 'syntactical');
return this.getEditRunPromise(year, 'syntactical')
.then(function() {
/* istanbul ignore if */
if (this.getDebug()) {
Expand All @@ -306,19 +335,14 @@ HMDAEngine.prototype.runSyntactical = function(year) {
*/
HMDAEngine.prototype.runValidity = function(year) {
var validityPromise;
this.calcEstimatedTasks(year, ['ts','lar'], 'validity');
if (this.shouldUseLocalDB()) {
validityPromise = this.loadCensusData()
.then(function() {
return Promise.all([
this.runEdits(year, 'ts', 'validity'),
this.runEdits(year, 'lar', 'validity')
]);
return this.getEditRunPromise(year, 'validity');
}.bind(this));
} else {
validityPromise = Promise.all([
this.runEdits(year, 'ts', 'validity'),
this.runEdits(year, 'lar', 'validity')
]);
validityPromise = this.getEditRunPromise(year, 'validity');
}
/* istanbul ignore if */
if (this.getDebug()) {
Expand Down Expand Up @@ -346,11 +370,8 @@ HMDAEngine.prototype.runQuality = function(year) {
if (this.getDebug()) {
console.time('time to run quality rules');
}
return Promise.all([
this.runEdits(year, 'ts', 'quality'),
this.runEdits(year, 'lar', 'quality'),
this.runEdits(year, 'hmda', 'quality')
])
this.calcEstimatedTasks(year, ['ts','lar','hmda'], 'quality');
return this.getEditRunPromise(year, 'quality')
.then(function() {
/* istanbul ignore if */
if (this.getDebug()) {
Expand All @@ -372,9 +393,8 @@ HMDAEngine.prototype.runMacro = function(year) {
if (this.getDebug()) {
console.time('time to run macro rules');
}
return Promise.all([
this.runEdits(year, 'hmda', 'macro')
])
this.calcEstimatedTasks(year, ['hmda'], 'macro');
return this.getEditRunPromise(year, 'macro')
.then(function() {
/* istanbul ignore if */
if (this.getDebug()) {
Expand All @@ -396,9 +416,8 @@ HMDAEngine.prototype.runSpecial = function(year) {
if (this.getDebug()) {
console.time('time to run special rules');
}
return Promise.all([
this.runEdits(year, 'hmda', 'special')
])
this.calcEstimatedTasks(year, ['hmda'], 'special');
return this.getEditRunPromise(year, 'special')
.then(function() {
/* istanbul ignore if */
if (this.getDebug()) {
Expand All @@ -410,6 +429,68 @@ HMDAEngine.prototype.runSpecial = function(year) {
});
};

/**
* Export errors in csv format for an individual edit
* @param {string} errorType The edit category. Valid values: 'syntactical', 'validity', 'quality', 'macro', 'special'
* @param {string} errorID The ID of the edit to export
* @return {object} A readable stream of the csv output
* @see {@link CSVProcessor|CSVProcessor} for more info
*/
HMDAEngine.prototype.exportIndividualStream = function(errorType, errorID) {
var csvProcessorIndividual = new CSVProcessor(this.getRuleYear(), 'individual');
if (this.getErrors()[errorType][errorID]) {
var errorsIndividual = {};
errorsIndividual[errorID] = this.getErrors()[errorType][errorID];
csvProcessorIndividual.write(errorsIndividual);
}

return csvProcessorIndividual;
};

/**
* Export errors in csv format for all errors of a specific type
* @param {string} errorType The edit category. Valid values: 'syntactical', 'validity', 'quality',
* @return {object} A readable stream of the csv output
* @see {@link CSVProcessor|CSVProcessor} for more info
*/
HMDAEngine.prototype.exportTypeStream = function(errorType) {
var csvProcessorType = new CSVProcessor(this.getRuleYear(), 'type');
if (this.getErrors()[errorType] && errorType !== 'macro' && errorType !== 'special') {
csvProcessorType.write(this.getErrors()[errorType]);
}

return csvProcessorType;
};

/**
* Export errors in csv format for an individual edit
* @param {string} errorType The edit category. Valid values: 'syntactical', 'validity', 'quality', 'macro', 'special'
* @param {string} errorID The ID of the edit to export
* @return {object} A promise for a string containing the csv output
* @see {@link CSVProcessor|CSVProcessor} for more info
*/
HMDAEngine.prototype.exportIndividualPromise = function(errorType, errorID) {
var csvProcessorIndividual = this.exportIndividualStream(errorType, errorID);

var promise = StringStreamPromise(csvProcessorIndividual);
csvProcessorIndividual.end();
return promise;
};

/**
* Export errors in csv format for all errors of a specific type
* @param {string} errorType The edit category. Valid values: 'syntactical', 'validity', 'quality',
* @return {object} A promise for a string containing the csv output
* @see {@link CSVProcessor|CSVProcessor} for more info
*/
HMDAEngine.prototype.exportTypePromise = function(errorType) {
var csvProcessorType = this.exportTypeStream(errorType);

var promise = StringStreamPromise(csvProcessorType);
csvProcessorType.end();
return promise;
};

/*
* -----------------------------------------------------
* Extend the Engine with Mixins
Expand All @@ -422,6 +503,7 @@ EngineBaseConditions.call(HMDAEngine.prototype);
EngineCustomConditions.call(HMDAEngine.prototype);
EngineCustomDataLookupConditions.call(HMDAEngine.prototype);
RuleParseAndExec.call(HMDAEngine.prototype);
RuleProgress.call(HMDAEngine.prototype);

/*
* -----------------------------------------------------
Expand Down
136 changes: 136 additions & 0 deletions lib/csvProcessor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
'use strict';

var csv = require('csv'),
utils = require('./utils'),
hmdaRuleSpec = require('hmda-rule-spec'),
_ = require('lodash');

/**
* Construct a new CSVProcessor
* @param {string} year The year for the errors being exported
* @param {string} type Processor type. Valid values: 'type', 'individual'
* @constructs CSVProcessor
* @example
* var writeStream = fs.createWriteStream('example.csv');
* var processor = new CSVProcessor('2013', 'type');
* processor.pipe(writeStream);
* processor.write(engine.getErrors().syntactical);
* //Edit ID,Line Number,Loan Number
* //S270,2,ABCDEFG
* //...
* processor.end(); //Close the write stream, any further calls to the processor will result in an error
*/
var CSVProcessor = function(year, type) {
this.fileSpec = {'hmdaFile': hmdaRuleSpec.getFileSpec(year)};
this.stringifier = csv.stringify();

this[type]();

this.transformer.on('finish', function() {
this.stringifier.end();
}.bind(this));
};

CSVProcessor.prototype.write = function(input) {
this.transformer.write(input);
};

CSVProcessor.prototype.end = function() {
this.transformer.end();
};

CSVProcessor.prototype.pipe = function(outputStream) {
this.stringifier.pipe(outputStream);
};

CSVProcessor.prototype.type = function() {
this.transformer = csv.transform(function(errors) {
_.each(_.keys(errors), function(id) {
var firstError = errors[id].errors[0];
var header = ['Edit ID'];
if (firstError.lineNumber) {
header.push('Line Number');
}
if (firstError.loanNumber || firstError.properties.loanNumber) {
header.push('Loan/Application Number');
}

this.stringifier.write(header);

_.each(errors[id].errors, function(error) {
var line = [id];
if (error.lineNumber) {
line.push(error.lineNumber);
}
if (error.loanNumber || error.properties.loanNumber) {
line.push(error.loanNumber || error.properties.loanNumber);
}
this.stringifier.write(line);
}.bind(this));

this.stringifier.write([]);

}.bind(this));
}.bind(this));
};

CSVProcessor.prototype.individual = function() {
this.transformer = csv.transform(function(errors) {
_.each(_.keys(errors), function(id) {
var firstError = errors[id].errors[0];
var errorProps = _.keys(firstError.properties);
var header = ['Edit ID'];

if (firstError.lineNumber) {
header.push('Line Number');
}
if (firstError.loanNumber) {
header.push('Loan/Application Number');
}

_.each(errorProps, function(property) {
var contextList = [];
if (errors[id].scope === 'ts') {
contextList.push(this.fileSpec.hmdaFile.transmittalSheet);
}
if (errors[id].scope === 'lar') {
contextList.push(this.fileSpec.hmdaFile.loanApplicationRegister);
}
contextList.push(this.fileSpec);
if (errors[id].scope === 'hmda') {
contextList.push(this.fileSpec.hmdaFile.transmittalSheet);
contextList.push(this.fileSpec.hmdaFile.loanApplicationRegister);
}
try {
var specBody = utils.resolveArg(property, contextList);
header.push(specBody.label);
} catch (err) {
header.push(property);
}
}.bind(this));

this.stringifier.write(header);

_.each(errors[id].errors, function(error) {
var line = [id];
if (error.lineNumber) {
line.push(error.lineNumber);
}
if (error.loanNumber) {
line.push(error.loanNumber);
}

_.each(errorProps, function(property) {
line.push(error.properties[property]);
});

this.stringifier.write(line);
}.bind(this));

this.stringifier.write([]);

}.bind(this));
}.bind(this));
};

module.exports = CSVProcessor;
2 changes: 1 addition & 1 deletion lib/engineApiInterface.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

var superagent = require('superagent'),
Promise = require('bluebird'),
_ = require('underscore');
_ = require('lodash');

var EngineApiInterface = (function() {
return function() {
Expand Down
Loading

0 comments on commit e4e3d7c

Please sign in to comment.