diff --git a/backend/src/v1/services/report-service.spec.ts b/backend/src/v1/services/report-service.spec.ts index 69f404f33..7e36e8826 100644 --- a/backend/src/v1/services/report-service.spec.ts +++ b/backend/src/v1/services/report-service.spec.ts @@ -369,16 +369,44 @@ describe('getHoursGapTextSummary', () => { expect(text).toContain('overtime hours'); expect(text).toContain( Math.abs(mockCalcs[CALCULATION_CODES.MEDIAN_OT_HOURS_DIFF_W].value) + - ' less', + ' less', ); expect(text).toContain( Math.abs(mockCalcs[CALCULATION_CODES.MEDIAN_OT_HOURS_DIFF_X].value) + - ' more', + ' more', ); }); }); }); +describe('getHourlyPayQuartilesTextSummary', () => { + describe('given the ref gender category and hourly pay Q1 and Q4 data', () => { + it('returns a text summary of the upper and lower quartiles', () => { + const referenceGenderCode = GENDERS.MALE.code; + const mockHourlyPayQuartile4 = [ + { genderChartInfo: GENDERS.MALE, value: 45 }, + { genderChartInfo: GENDERS.FEMALE, value: 45 }, + { genderChartInfo: GENDERS.NON_BINARY, value: 1 }, + { genderChartInfo: GENDERS.UNKNOWN, value: 9 }, + ]; + const mockHourlyPayQuartile1 = [ + { genderChartInfo: GENDERS.MALE, value: 55 }, + { genderChartInfo: GENDERS.FEMALE, value: 35 }, + { genderChartInfo: GENDERS.UNKNOWN, value: 10 }, + ]; + const text: string = reportServicePrivate.getHourlyPayQuartilesTextSummary( + referenceGenderCode, + mockHourlyPayQuartile4, + mockHourlyPayQuartile1 + ); + console.log(text); + expect(text.toLowerCase()).toContain(`${GENDERS.FEMALE.extendedLabel} occupy 45% of the highest paid jobs and 35% of the lowest`.toLowerCase()); + expect(text.toLowerCase()).toContain(`${GENDERS.NON_BINARY.extendedLabel} occupy 1% of the highest paid jobs.`.toLowerCase()); + + }); + }); +}); + describe('dollarsToText', () => { describe('when a value less than 1 is specified', () => { it('returns a value in cents', () => { diff --git a/backend/src/v1/services/report-service.ts b/backend/src/v1/services/report-service.ts index 2eb172899..74f258ec2 100644 --- a/backend/src/v1/services/report-service.ts +++ b/backend/src/v1/services/report-service.ts @@ -245,6 +245,50 @@ const reportServicePrivate = { return result; }, + /* + Creates a text summary of the 4th and 1st hourly pay quartiles + in a form similar to: + "In this organization, women occupy 30% of the highest paid + jobs and 56% of the lowest paid jobs. Non-binary people occupy + 1% of the highest paid jobs and 2% of the lowest paid jobs." + */ + getHourlyPayQuartilesTextSummary( + referenceGenderCode: string, + hourlyPayQuartile4: ChartDataRecord[], + hourlyPayQuartile1: ChartDataRecord[]): string { + const genderCodesToSkip = [referenceGenderCode, GENDERS.UNKNOWN.code]; + const genderCodesToSummarize = Object.values(GENDERS).filter(d => genderCodesToSkip.indexOf(d.code) == -1); + + const genderSummaries = []; + genderCodesToSummarize.forEach((g, i) => { + const genderLabel = i == 0 ? g.extendedLabel.toLocaleLowerCase() : g.extendedLabel; + const q4 = hourlyPayQuartile4.filter(c => c.genderChartInfo.code == g.code); + const q1 = hourlyPayQuartile1.filter(c => c.genderChartInfo.code == g.code); + const quartileSummaries = []; + if (q4.length) { + const q4Percent = Math.round(q4[0].value); + quartileSummaries.push(`${q4Percent}% of the highest paid jobs`); + } + if (q1.length) { + const q1Percent = Math.round(q1[0].value); + quartileSummaries.push(`${q1Percent}% of the lowest paid jobs`); + } + if (quartileSummaries.length) { + genderSummaries.push(`${genderLabel} occupy ${quartileSummaries.join(' and ')}`); + } + }); + + let text = null; + if (genderSummaries.length) { + text = `In this organization, ${genderSummaries[0]}.`; + } + for (let i = 1; i < genderSummaries.length; i++) { + text += ` ${genderSummaries[i]}.` + } + + return text; + }, + /* converts a number representing an amount in dollars (such as 1.20 or 0.95) into a string according to the following rules: @@ -771,6 +815,11 @@ const reportService = { 'median', 'overtime hours', ), + hourlyPayQuartiles: reportServicePrivate.getHourlyPayQuartilesTextSummary( + referenceGenderCode, + chartData.hourlyPayQuartile4, + chartData.hourlyPayQuartile1, + ), }; const referenceGenderChartInfo = diff --git a/doc-gen-service/src/templates/report.template.html b/doc-gen-service/src/templates/report.template.html index 9ed6ebdac..ca123a202 100644 --- a/doc-gen-service/src/templates/report.template.html +++ b/doc-gen-service/src/templates/report.template.html @@ -521,38 +521,59 @@
-
+
+ |
-
-
Upper hourly pay quartile (highest paid)
- <% if (chartData.hourlyPayQuartile4.length) { %>
-
- <% } %>
+ <% var q4HasSuppressed=chartData.hourlyPayQuartile4.length < 4; %>
+ Upper hourly pay quartile (highest paid)<% if (q4HasSuppressed) { %>*<% } %>
+
+ <% if (chartData.hourlyPayQuartile4.length) { %>
+
+ <% } %>
-
Upper middle hourly pay quartile
- <% if (chartData.hourlyPayQuartile3.length) { %>
-
- <% } %>
+ <% var q3HasSuppressed=chartData.hourlyPayQuartile3.length < 4; %>
+ Upper middle hourly pay quartile<% if (q3HasSuppressed) { %>*<% } %>
+
+ <% if (chartData.hourlyPayQuartile3.length) { %>
+
+ <% } %>
-
Lower middle hourly pay quartile
- <% if (chartData.hourlyPayQuartile2.length) { %>
-
- <% } %>
+ <% var q2HasSuppressed=chartData.hourlyPayQuartile2.length < 4; %>
+ Lower middle hourly pay quartile<% if (q2HasSuppressed) { %>*<% } %>
+
+ <% if (chartData.hourlyPayQuartile2.length) { %>
+
+ <% } %>
-
Lowest hourly pay quartile (lowest paid)
- <% if (chartData.hourlyPayQuartile1.length) { %>
-
- <% } %>
+ <% var q1HasSuppressed=chartData.hourlyPayQuartile1.length < 4; %>
+ Lowest hourly pay quartile (lowest paid)<% if (q1HasSuppressed) { %>*<% } %>
+
+ <% if (chartData.hourlyPayQuartile1.length) { %>
+
+ <% } %>
-
+ |
+
+ |
+ |
+
+
+ <%= chartSummaryText.hourlyPayQuartiles %> + + <% if (q1HasSuppressed || q2HasSuppressed || q3HasSuppressed || q4HasSuppressed) { %> +* This pay quartile was reduced to suppress gender categories consisting of less than ten (10) + employees. + <% } %> + |