Skip to content

Commit

Permalink
fix: #2609 - audit report subaward (#2781)
Browse files Browse the repository at this point in the history
* add a subaward tab for the audit report
* add sheets option for filtering records by project

fix: #2609 - audit report subaward

* fixed bad log
  • Loading branch information
tzinckgraf authored Mar 14, 2024
1 parent e9c180f commit 8f6b29d
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 4 deletions.
96 changes: 94 additions & 2 deletions packages/server/src/arpa_reporter/lib/audit-report.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,88 @@ async function createReportsGroupedByProject(periodId, tenantId, dateFormat = RE
return rows;
}

/**
* Audit report sheet grouped by subaward.
* All data comes from the Awards 50k or Expenditures 50k sheet, so we can filter those out.
* We group the data, then aggregate on a couple fields ordered by date.
*/
async function createReportsGroupedBySubAward(periodId, tenantId, dateFormat = REPORTING_DATE_FORMAT, logger = log) {
logger.info('building rows for spreadsheet');
const sheets = ['awards50k', 'expenditures50k'];
const records = await recordsForProject(periodId, tenantId, sheets);
logger.fields.sheet.totalRecords = records.length;
logger.info('retrieved records for projects');
const recordsBySubAward = records.reduce((groupBySubAward, item) => {
let subAward = 'Missing SubAward';
if (item.type === 'expenditures50k') {
subAward = item.content.Sub_Award_Lookup__c || subAward;
} else if (item.type === 'awards50k') {
subAward = item.content.Award_No__c || subAward;
}
const group = (groupBySubAward[subAward] || []);
group.push(item);
groupBySubAward[subAward] = group;
return groupBySubAward;
}, {});
logger.fields.sheet.totalProjects = Object.keys(recordsBySubAward).length;
logger.info('grouped records by subaward');

const allReportingPeriods = await getAllReportingPeriods(undefined, tenantId);
logger.fields.sheet.totalReportingPeriods = allReportingPeriods.length;
logger.info('retrieved all reporting periods for tenant');

// index project end dates by project
const endDatesByReportingPeriodId = Object.fromEntries(allReportingPeriods.map((reportingPeriod) => [
reportingPeriod.id, moment(reportingPeriod.end_date, 'yyyy-MM-DD').format(dateFormat),
]));

// create a row for each subAward, populated from the records related to that subAward
const rows = Object.entries(recordsBySubAward).map(([subAwardId, subAwardRecords]) => {
const subAwardLogger = logger.child({
subAward: { id: subAwardId, totalRecords: subAwardRecords.length },
});

subAwardLogger.debug('populating row from records in subaward');

// set values for columns that are common across all records of subAwardId
const row = {
'SubAward ID': subAwardId,
};

// get all reporting periods related to the subAward
const subAwardReportingPeriodIds = new Set(
subAwardRecords.map((r) => r.upload.reporting_period_id),
);
subAwardLogger.fields.subAward.totalReportingPeriods = subAwardReportingPeriodIds.length;
subAwardLogger.debug('determined unique reporting periods for the current subAward');

// for each reporting period related to the subAward, add 4 new columns to the row where:
// - the name (row key) of each column is prefixed by the reporting period's end date
// - the initial value for each column in this row is zero
subAwardReportingPeriodIds.forEach((id) => {
const endDate = endDatesByReportingPeriodId[id];
row[`${endDate} Awards > 50000 SubAward Amount`] = 0;
row[`${endDate} Awards > 50000 SubAward Expenditure`] = 0;
});

// Sum the total value of each initialized column from the corresponding subtotal
// provided by each subAward record
subAwardRecords.forEach((record) => {
const endDate = endDatesByReportingPeriodId[record.upload.reporting_period_id];
row[`${endDate} Awards > 50000 SubAward Amount`] += (record.content.Award_Amount__c || 0);
row[`${endDate} Awards > 50000 SubAward Expenditure`] += (record.content.Expenditure_Amount__c || 0);
});

subAwardLogger.fields.subAward.totalColumns = Object.keys(row).length;
subAwardLogger.info('finished populating row');
return row;
});

logger.fields.sheet.rowCount = rows.length;
logger.info('finished building rows for spreadsheet');
return rows;
}

async function createKpiDataGroupedByProject(periodId, tenantId, logger = log) {
logger.info('building rows for spreadsheet');
const records = await recordsForProject(periodId, tenantId);
Expand Down Expand Up @@ -469,6 +551,13 @@ async function generate(requestHost, tenantId, periodId) {
REPORTING_DATE_FORMAT,
logger.child({ sheet: { name: 'Project Summaries V2' } }),
));
const projectSummaryGroupedBySubAward = await tracer.trace('createReportsGroupedBySubAward',
async () => createReportsGroupedBySubAward(
periodId,
tenantId,
REPORTING_DATE_FORMAT,
logger.child({ sheet: { name: 'SubAward Summaries' } }),
));
const KPIDataGroupedByProject = await tracer.trace('createKpiDataGroupedByProject',
async () => createKpiDataGroupedByProject(
periodId,
Expand All @@ -492,7 +581,9 @@ async function generate(requestHost, tenantId, periodId) {
const sheet3 = jsonToSheet(projectSummaryGroupedByProject, 'Project Summaries V2', {
header: createHeadersProjectSummariesV2(projectSummaryGroupedByProject),
});
const sheet4 = jsonToSheet(KPIDataGroupedByProject, 'KPI');
// FIXME need to sort
const sheet4 = jsonToSheet(projectSummaryGroupedBySubAward, 'SubAward Summaries');
const sheet5 = jsonToSheet(KPIDataGroupedByProject, 'KPI');
log.info('finished building sheets from aggregated data');

// create the workbook and add sheet data
Expand All @@ -505,7 +596,8 @@ async function generate(requestHost, tenantId, periodId) {
addSheetToWorkbook(sheet1, 'Obligations & Expenditures');
addSheetToWorkbook(sheet2, 'Project Summaries');
addSheetToWorkbook(sheet3, 'Project Summaries V2');
addSheetToWorkbook(sheet4, 'KPI');
addSheetToWorkbook(sheet4, 'SubAward Summaries');
addSheetToWorkbook(sheet5, 'KPI');
logger.info('finished making new workbook');

logger.info('finished composing workbook');
Expand Down
6 changes: 4 additions & 2 deletions packages/server/src/arpa_reporter/services/records.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ async function mostRecentProjectRecords(periodId, tenantId) {
return Object.values(latestProjectRecords);
}

async function recordsForProject(periodId, tenantId) {
async function recordsForProject(periodId, tenantId, sheets = undefined) {
log(`recordsForProject`);
requiredArgument(periodId, 'must specify periodId in mostRecentProjectRecords');

Expand All @@ -251,10 +251,12 @@ async function recordsForProject(periodId, tenantId) {
reportingPeriods.map(({ id }) => recordsForReportingPeriod(id, tenantId)),
);

const sheetsToFilter = sheets || [...Object.values(EC_SHEET_TYPES), 'awards50k', 'expenditures50k', 'awards'];

const projectRecords = allRecords
.flat()
// exclude non-project records
.filter((record) => ([...Object.values(EC_SHEET_TYPES), 'awards50k', 'expenditures50k', 'awards']).includes(record.type));
.filter((record) => (sheetsToFilter).includes(record.type));

// expenditures do not have projects on them, but awards do under Project_Identification_Number__c
// we can map the two by Subawards
Expand Down

0 comments on commit 8f6b29d

Please sign in to comment.