From 0775a163a435b964415190417a574da06915f347 Mon Sep 17 00:00:00 2001 From: niqjohnson Date: Thu, 25 Feb 2016 10:29:03 -0500 Subject: [PATCH 1/4] Display debt burden calculation and notification --- CHANGELOG.md | 2 +- paying_for_college/templates/worksheet.html | 15 +++-- src/disclosures/js/views/financial-view.js | 2 + src/disclosures/js/views/metric-view.js | 62 +++++++++++++++++++++ test/functional/dd-school-data-spec.js | 13 +++++ test/functional/settlementAidOfferPage.js | 20 +++++++ test/js-unit/metric-view-spec.js | 13 +++++ 7 files changed, 120 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46c6427..de2de94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ 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 ## 2.0.0 ### Added/updated diff --git a/paying_for_college/templates/worksheet.html b/paying_for_college/templates/worksheet.html index e6d6d8b..30b4890 100644 --- a/paying_for_college/templates/worksheet.html +++ b/paying_for_college/templates/worksheet.html @@ -2173,8 +2173,10 @@

- $XX,XXX / yr.
- $X,XXX / mo. + + / yr.
+ + / mo.
@@ -2184,14 +2186,15 @@

- $XXX / mo. + + / mo.
- $XXX +
loan payment @@ -2203,7 +2206,7 @@

- $X,XXX +
monthly salary @@ -2215,7 +2218,7 @@

- XX% +
of your income diff --git a/src/disclosures/js/views/financial-view.js b/src/disclosures/js/views/financial-view.js index d7cdf43..528349c 100644 --- a/src/disclosures/js/views/financial-view.js +++ b/src/disclosures/js/views/financial-view.js @@ -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]' ), @@ -135,6 +136,7 @@ var financialView = { this.updateLeftovers( values, $leftovers ); this.updatePrivateLoans( values, $privateLoans ); this.updateRemainingCostContent(); + metricView.updateDebtBurdenDisplay( values, window.nationalData ); }, /** diff --git a/src/disclosures/js/views/metric-view.js b/src/disclosures/js/views/metric-view.js index fdf3992..e47615f 100644 --- a/src/disclosures/js/views/metric-view.js +++ b/src/disclosures/js/views/metric-view.js @@ -1,6 +1,7 @@ 'use strict'; var schoolModel = require( '../models/school-model' ); +var getModelValues = require( '../dispatchers/get-model-values' ); var formatUSD = require( 'format-usd' ); var metricView = { @@ -13,6 +14,8 @@ var metricView = { schoolValues = schoolModel.values, 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 }, /** @@ -198,6 +201,65 @@ 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 ), + financialValues = getModelValues.financial(), + monthlyLoanPayment = financialValues.loanMonthly, + 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 ); + $notification.attr( 'class', 'metric_notification' ); + this.setNotificationClasses( $notification, notificationClasses ); } }; diff --git a/test/functional/dd-school-data-spec.js b/test/functional/dd-school-data-spec.js index 41f79cf..f0df782 100644 --- a/test/functional/dd-school-data-spec.js +++ b/test/functional/dd-school-data-spec.js @@ -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' ); diff --git a/test/functional/settlementAidOfferPage.js b/test/functional/settlementAidOfferPage.js index 80e27ea..b993d6b 100644 --- a/test/functional/settlementAidOfferPage.js +++ b/test/functional/settlementAidOfferPage.js @@ -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' ) ); diff --git a/test/js-unit/metric-view-spec.js b/test/js-unit/metric-view-spec.js index 2ddc0d8..8f3ac90 100644 --- a/test/js-unit/metric-view-spec.js +++ b/test/js-unit/metric-view-spec.js @@ -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' ); + }); + }); From 29d7ce2e1e3f5cd8b9100d7b91084443d8450dab Mon Sep 17 00:00:00 2001 From: niqjohnson Date: Thu, 25 Feb 2016 11:19:26 -0500 Subject: [PATCH 2/4] Get all graph values from get-model-values --- src/disclosures/js/views/metric-view.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/disclosures/js/views/metric-view.js b/src/disclosures/js/views/metric-view.js index e47615f..9775892 100644 --- a/src/disclosures/js/views/metric-view.js +++ b/src/disclosures/js/views/metric-view.js @@ -1,6 +1,5 @@ 'use strict'; -var schoolModel = require( '../models/school-model' ); var getModelValues = require( '../dispatchers/get-model-values' ); var formatUSD = require( 'format-usd' ); @@ -11,7 +10,7 @@ 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, @@ -170,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 ); }, /** @@ -235,8 +236,7 @@ var metricView = { updateDebtBurdenDisplay: function( schoolValues, nationalValues ) { var annualSalary = Number( schoolValues.medianSalary ) || Number( nationalValues.earningsMedian ), monthlySalary = this.calculateMonthlySalary( annualSalary ), - financialValues = getModelValues.financial(), - monthlyLoanPayment = financialValues.loanMonthly, + monthlyLoanPayment = schoolValues.loanMonthly, debtBurden = this.calculateDebtBurden( monthlyLoanPayment, monthlySalary ), annualSalaryFormatted = this.formatValue( 'currency', annualSalary ), monthlySalaryFormatted = this.formatValue( 'currency', monthlySalary ), @@ -258,7 +258,6 @@ var metricView = { $monthlySalaryElement.text( monthlySalaryFormatted ); $monthlyPaymentElement.text( monthlyLoanPaymentFormatted ); $debtBurdenElement.text( debtBurdenFormatted ); - $notification.attr( 'class', 'metric_notification' ); this.setNotificationClasses( $notification, notificationClasses ); } From eb3dcd9799390471d7e26a4f624f730c2b73d014 Mon Sep 17 00:00:00 2001 From: niqjohnson Date: Thu, 25 Feb 2016 11:51:07 -0500 Subject: [PATCH 3/4] Add functional tests for updating debt burden --- test/functional/dd-functional-settlement-spec.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/functional/dd-functional-settlement-spec.js b/test/functional/dd-functional-settlement-spec.js index b325929..b573307 100644 --- a/test/functional/dd-functional-settlement-spec.js +++ b/test/functional/dd-functional-settlement-spec.js @@ -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 } ); @@ -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 } ); @@ -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 } ); @@ -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 } ); @@ -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 } ); @@ -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 } ); @@ -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 } ); From 03632ce6fa3dc7e190d2d1a67964f421f41eb8d5 Mon Sep 17 00:00:00 2001 From: niqjohnson Date: Thu, 25 Feb 2016 16:26:48 -0500 Subject: [PATCH 4/4] Handle debt burden if monthly payment is missing --- src/disclosures/js/views/metric-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/disclosures/js/views/metric-view.js b/src/disclosures/js/views/metric-view.js index 9775892..a3849f1 100644 --- a/src/disclosures/js/views/metric-view.js +++ b/src/disclosures/js/views/metric-view.js @@ -236,7 +236,7 @@ var metricView = { updateDebtBurdenDisplay: function( schoolValues, nationalValues ) { var annualSalary = Number( schoolValues.medianSalary ) || Number( nationalValues.earningsMedian ), monthlySalary = this.calculateMonthlySalary( annualSalary ), - monthlyLoanPayment = schoolValues.loanMonthly, + monthlyLoanPayment = schoolValues.loanMonthly || 0, debtBurden = this.calculateDebtBurden( monthlyLoanPayment, monthlySalary ), annualSalaryFormatted = this.formatValue( 'currency', annualSalary ), monthlySalaryFormatted = this.formatValue( 'currency', monthlySalary ),