Skip to content

Commit

Permalink
Merge pull request #127 from cfpb/debt-burden
Browse files Browse the repository at this point in the history
Display debt burden
  • Loading branch information
niqjohnson committed Feb 26, 2016
2 parents 3d4ca09 + 02e2a6b commit 728250d
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 17 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ We follow the [Semantic Versioning 2.0.0](http://semver.org/) format.
- Changed url for "about this tool" page to /about-this-tool/
- Added "About this tool" section and content
- Updated content to pre-clearance version for settlement students
- Added debt burden calculations and notifications
- Added script for sending test notifications


## 2.0.0
### Added/updated
- New url for handling querystring offer data
Expand Down
15 changes: 9 additions & 6 deletions paying_for_college/templates/worksheet.html
Original file line number Diff line number Diff line change
Expand Up @@ -2173,8 +2173,10 @@ <h4 class="metric_heading">
</div>
<div
class="debt-burden_projection-value">
$XX,XXX / yr.<br>
$X,XXX / mo.
<span data-debt-burden="annual-salary"></span>
/ yr.<br>
<span data-debt-burden="monthly-salary"></span>
/ mo.
</div>
</div>
<div class="debt-burden_projection">
Expand All @@ -2184,14 +2186,15 @@ <h4 class="metric_heading">
</div>
<div
class="debt-burden_projection-value">
$XXX / mo.
<span data-debt-burden="monthly-payment"></span>
/ mo.
</div>
</div>
<div class="debt-equation u-clearfix">
<div class="debt-equation_part
debt-equation_part__loan">
<div class="debt-equation_number">
$XXX
<span data-debt-burden="monthly-payment"></span>
</div>
<div class="debt-equation_label">
loan payment
Expand All @@ -2203,7 +2206,7 @@ <h4 class="metric_heading">
<div class="debt-equation_part
debt-equation_part__income">
<div class="debt-equation_number">
$X,XXX
<span data-debt-burden="monthly-salary"></span>
</div>
<div class="debt-equation_label">
monthly salary
Expand All @@ -2215,7 +2218,7 @@ <h4 class="metric_heading">
<div class="debt-equation_part
debt-equation_part__percent">
<div class="debt-equation_number">
XX%
<span data-debt-burden="debt-burden"></span>
</div>
<div class="debt-equation_label">
of your income
Expand Down
2 changes: 2 additions & 0 deletions src/disclosures/js/views/financial-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var stringToNum = require( '../utils/handle-string-input' );
var formatUSD = require( 'format-usd' );
var numberToWords = require( 'number-to-words' );
var linksView = require( '../views/links-view' );
var metricView = require( '../views/metric-view' );

var financialView = {
$elements: $( '[data-financial]' ),
Expand Down Expand Up @@ -135,6 +136,7 @@ var financialView = {
this.updateLeftovers( values, $leftovers );
this.updatePrivateLoans( values, $privateLoans );
this.updateRemainingCostContent();
metricView.updateDebtBurdenDisplay( values, window.nationalData );
},

/**
Expand Down
67 changes: 64 additions & 3 deletions src/disclosures/js/views/metric-view.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

var schoolModel = require( '../models/school-model' );
var getModelValues = require( '../dispatchers/get-model-values' );
var formatUSD = require( 'format-usd' );

var metricView = {
Expand All @@ -10,9 +10,11 @@ var metricView = {
*/
init: function() {
var $graphs = $( '.bar-graph' ),
schoolValues = schoolModel.values,
schoolValues = getModelValues.financial(),
nationalValues = window.nationalData || {};
this.initGraphs( $graphs, schoolValues, nationalValues );
// updateDebtBurdenDisplay is called in financialView.updateView, not here,
// since the debt burden needs to refresh when loan amounts are modified
},

/**
Expand Down Expand Up @@ -167,7 +169,9 @@ var metricView = {
* @param {string} notificationClasses Classes to add to the notification
*/
setNotificationClasses: function( $notification, notificationClasses ) {
$notification.addClass( notificationClasses );
$notification
.attr( 'class', 'metric_notification' )
.addClass( notificationClasses );
},

/**
Expand Down Expand Up @@ -198,6 +202,63 @@ var metricView = {
metricView.fixOverlap( $graph, schoolAverageFormatted, nationalAverageFormatted, $schoolPoint, $nationalPoint );
metricView.setNotificationClasses( $notification, notificationClasses );
} );
},

/**
* Calculates the student's debt burden
* @param {number} monthlyLoanPayment Student's monthly loan payment after
* graduation
* @param {monthlySalary} monthlySalary Student's estimated monthly salary
* after graduation
* @returns {number} Student's debt burden
*/
calculateDebtBurden: function( monthlyLoanPayment, monthlySalary ) {
var debtBurden = monthlyLoanPayment / monthlySalary;
return debtBurden;
},

/**
* Calculates a monthly salary from an annual salary
* @param {number} annualSalary Annual salary
* @returns {number} Monthly salary
*/
calculateMonthlySalary: function( annualSalary ) {
var monthlySalary = annualSalary / 12;
return monthlySalary;
},

/**
* Populates the debt burden numbers and shows the corresponding notification
* on the page
* @param {object} schoolValues Values reported by the school
* @param {object} nationalValues National average values
*/
updateDebtBurdenDisplay: function( schoolValues, nationalValues ) {
var annualSalary = Number( schoolValues.medianSalary ) || Number( nationalValues.earningsMedian ),
monthlySalary = this.calculateMonthlySalary( annualSalary ),
monthlyLoanPayment = schoolValues.loanMonthly || 0,
debtBurden = this.calculateDebtBurden( monthlyLoanPayment, monthlySalary ),
annualSalaryFormatted = this.formatValue( 'currency', annualSalary ),
monthlySalaryFormatted = this.formatValue( 'currency', monthlySalary ),
monthlyLoanPaymentFormatted = this.formatValue( 'currency', monthlyLoanPayment ),
debtBurdenFormatted = this.formatValue( 'decimal-percent', debtBurden ),
$annualSalaryElement = $( '[data-debt-burden="annual-salary"]' ),
$monthlySalaryElement = $( '[data-debt-burden="monthly-salary"]' ),
$monthlyPaymentElement = $( '[data-debt-burden="monthly-payment"]' ),
$debtBurdenElement = $( '[data-debt-burden="debt-burden"]' ),
$notification = $( '.debt-burden .metric_notification' ),
// We're using 8% or below as the recommended debt burden
debtBurdenLimit = 0.08,
// Debt burdens that round to 8% are considered "the same as" the
// recommendation
debtBurdenLow = debtBurdenLimit - 0.005,
debtBurdenHigh = debtBurdenLimit + 0.005,
notificationClasses = this.getNotificationClasses( debtBurden, debtBurdenLimit, debtBurdenLow, debtBurdenHigh, 'lower' );
$annualSalaryElement.text( annualSalaryFormatted );
$monthlySalaryElement.text( monthlySalaryFormatted );
$monthlyPaymentElement.text( monthlyLoanPaymentFormatted );
$debtBurdenElement.text( debtBurdenFormatted );
this.setNotificationClasses( $notification, notificationClasses );
}

};
Expand Down
14 changes: 7 additions & 7 deletions test/functional/dd-functional-settlement-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ fdescribe( 'A dynamic financial aid disclosure that\'s required by settlement',
expect( page.remainingCostFinal.getText() ).toEqual( '526' );
// expect( page.totalProgramDebt.getText() ).toEqual( '3000' );
// expect( page.totalRepayment.getText() ).toEqual( '10000' );
// TODO: expect the estimated debt burden is recalculated
expect( page.debtBurdenPercent.getText() ).toEqual( '15%' );
// TODO: expect the est. monthly student loan expense is recalculated
} );

Expand All @@ -312,7 +312,7 @@ fdescribe( 'A dynamic financial aid disclosure that\'s required by settlement',
expect( page.remainingCostFinal.getText() ).toEqual( '-2974' );
// expect( page.totalProgramDebt.getText() ).toEqual( '3000' );
// expect( page.totalRepayment.getText() ).toEqual( '10000' );
// TODO: expect the estimated debt burden is recalculated
expect( page.debtBurdenPercent.getText() ).toEqual( '19%' );
// TODO: expect the est. monthly student loan expense is recalculated
} );

Expand All @@ -325,7 +325,7 @@ fdescribe( 'A dynamic financial aid disclosure that\'s required by settlement',
expect( page.remainingCostFinal.getText() ).toEqual( '26' );
// expect( page.totalProgramDebt.getText() ).toEqual( '3000' );
// expect( page.totalRepayment.getText() ).toEqual( '10000' );
// TODO: expect the estimated debt burden is recalculated
expect( page.debtBurdenPercent.getText() ).toEqual( '16%' );
// TODO: expect the est. monthly student loan expense is recalculated
} );

Expand All @@ -340,7 +340,7 @@ fdescribe( 'A dynamic financial aid disclosure that\'s required by settlement',
expect( page.remainingCostFinal.getText() ).toEqual( '-474' );
// expect( page.totalProgramDebt.getText() ).toEqual( '3000' );
// expect( page.totalRepayment.getText() ).toEqual( '10000' );
// TODO: expect the estimated debt burden is recalculated
expect( page.debtBurdenPercent.getText() ).toEqual( '16%' );
// TODO: expect the est. monthly student loan expense is recalculated
} );

Expand All @@ -353,7 +353,7 @@ fdescribe( 'A dynamic financial aid disclosure that\'s required by settlement',
expect( page.remainingCostFinal.getText() ).toEqual( '-1474' );
// expect( page.totalProgramDebt.getText() ).toEqual( '3000' );
// expect( page.totalRepayment.getText() ).toEqual( '10000' );
// TODO: expect the estimated debt burden is recalculated
expect( page.debtBurdenPercent.getText() ).toEqual( '18%' );
// TODO: expect the est. monthly student loan expense is recalculated
} );

Expand All @@ -368,7 +368,7 @@ fdescribe( 'A dynamic financial aid disclosure that\'s required by settlement',
expect( page.remainingCostFinal.getText() ).toEqual( '-4474' );
// expect( page.totalProgramDebt.getText() ).toEqual( '3000' );
// expect( page.totalRepayment.getText() ).toEqual( '10000' );
// TODO: expect the estimated debt burden is recalculated
expect( page.debtBurdenPercent.getText() ).toEqual( '21%' );
// TODO: expect the est. monthly student loan expense is recalculated
} );

Expand Down Expand Up @@ -436,7 +436,7 @@ fdescribe( 'A dynamic financial aid disclosure that\'s required by settlement',
expect( page.remainingCostFinal.getText() ).toEqual( '-1474' );
// expect( page.totalProgramDebt.getText() ).toEqual( '?' );
// expect( page.totalRepayment.getText() ).toEqual( '?' );
// TODO: expect the estimated debt burden is recalculated
expect( page.debtBurdenPercent.getText() ).toEqual( '18%' );
// TODO: expect the est. monthly student loan expense is recalculated
} );

Expand Down
13 changes: 13 additions & 0 deletions test/functional/dd-school-data-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ fdescribe( 'The dynamic financial aid disclosure', function() {
expect( page.salaryNotification.getAttribute( 'class' ) ).toEqual( 'metric_notification metric_notification__worse cf-notification cf-notification__error' );
} );

it( 'should calculate debt burden', function() {
page.confirmVerification();
expect( page.debtBurdenPayment.getText() ).toEqual( '$314' );
expect( page.debtBurdenSalary.getText() ).toEqual( '$1,917' );
expect( page.debtBurdenPercent.getText() ).toEqual( '16%' );
} );

it( 'should display the correct debt burden notification', function() {
page.confirmVerification();
expect( page.debtBurdenNotification.getText() ).toEqual( 'Loan payment is higher than recommended 8% of salary' );
expect( page.debtBurdenNotification.getAttribute( 'class' ) ).toEqual( 'metric_notification metric_notification__worse cf-notification cf-notification__error' );
} );

it( 'should graph loan default rates', function() {
page.confirmVerification();
expect( page.schoolDefaultRatePoint.getCssValue( 'bottom' ) ).toEqual( '80.5px' );
Expand Down
20 changes: 20 additions & 0 deletions test/functional/settlementAidOfferPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,26 @@ settlementAidOfferPage.prototype = Object.create({}, {
return element( by.css( '.metric.average-salary .metric_notification' ) );
}
},
debtBurdenPayment: {
get: function() {
return element( by.css( '.debt-equation [data-debt-burden="monthly-payment"]' ) );
}
},
debtBurdenSalary: {
get: function() {
return element( by.css( '.debt-equation [data-debt-burden="monthly-salary"]' ) );
}
},
debtBurdenPercent: {
get: function() {
return element( by.css( '.debt-equation [data-debt-burden="debt-burden"]' ) );
}
},
debtBurdenNotification: {
get: function() {
return element( by.css( '.metric.debt-burden .metric_notification' ) );
}
},
schoolDefaultRatePoint: {
get: function() {
return element( by.css( '.metric.loan-default-rates .bar-graph_point__you' ) );
Expand Down
13 changes: 13 additions & 0 deletions test/js-unit/metric-view-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,17 @@ describe( 'metric-view', function() {
expect( notificationClasses ).to.equal( '' );
});

it( 'calculates monthly salary', function() {
var annualSalary = 34300,
monthlySalary = metricView.calculateMonthlySalary( annualSalary );
expect( monthlySalary.toFixed( 4 ) ).to.equal( '2858.3333' );
});

it( 'calculates debt burden', function() {
var monthlyLoanPayment = 240,
monthlySalary = 2858,
debtBurden = metricView.calculateDebtBurden( monthlyLoanPayment, monthlySalary );
expect( debtBurden.toFixed( 4 ) ).to.equal( '0.0840' );
});

});

0 comments on commit 728250d

Please sign in to comment.