Skip to content

Commit

Permalink
Merge branch 'staging' into 10525-story
Browse files Browse the repository at this point in the history
  • Loading branch information
jimlerza authored Dec 4, 2024
2 parents 1ef0b22 + ee0e68e commit afd1ce7
Show file tree
Hide file tree
Showing 8 changed files with 579 additions and 162 deletions.
91 changes: 91 additions & 0 deletions scripts/helpers/generate-csv.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { generateCsv } from './generate-csv';
import fs from 'fs';

const exists = jest.spyOn(fs, 'existsSync').mockImplementation(jest.fn());
const unlink = jest.spyOn(fs, 'unlinkSync').mockImplementation(jest.fn());
const append = jest.spyOn(fs, 'appendFileSync').mockImplementation(jest.fn());

const MOCK_COLUMNS = [
{ header: 'Droid name', key: 'name' },
{ header: 'Droid type', key: 'type' },
{ header: 'Alliance', key: 'alliance' },
];
const MOCK_ROWS = [
{
alliance: 'Rebellion',
name: 'C-3PO',
restrained: true,
type: 'Protocol',
},
{
alliance: 'Rebellion',
name: 'R2-D2',
restrained: false,
type: 'Astromech',
},
{
alliance: 'Rebellion',
name: 'C1-10P',
restrained: false,
type: 'Astromech',
},
{
alliance: 'Empire',
name: 'IG-88',
restrained: false,
type: 'Assassin',
},
{
name: 'MSE-6',
restrained: true,
type: 'Mouse',
},
];
const MOCK_FILENAME = `${process.env.HOME}/tmp/jest.csv`;
const MOCK_CONTENTS =
'"Droid name","Droid type","Alliance"' +
'\n"C-3PO","Protocol","Rebellion"' +
'\n"R2-D2","Astromech","Rebellion"' +
'\n"C1-10P","Astromech","Rebellion"' +
'\n"IG-88","Assassin","Empire"' +
'\n"MSE-6","Mouse",""';

describe('generateCsv', () => {
beforeEach(() => {
exists.mockReturnValue(true);
});

it('deletes the specified output file if it already exists', () => {
generateCsv({
columns: MOCK_COLUMNS,
filename: MOCK_FILENAME,
rows: MOCK_ROWS,
});

expect(unlink).toHaveBeenCalled();
expect(append).toHaveBeenCalled();
});

it('does not attempt to delete the specified output file if it does not already exist', () => {
exists.mockReturnValueOnce(false);

generateCsv({
columns: MOCK_COLUMNS,
filename: MOCK_FILENAME,
rows: MOCK_ROWS,
});

expect(unlink).not.toHaveBeenCalled();
expect(append).toHaveBeenCalled();
});

it('compiles an array of objects into a CSV with the given columns', () => {
generateCsv({
columns: MOCK_COLUMNS,
filename: MOCK_FILENAME,
rows: MOCK_ROWS,
});

expect(append).toHaveBeenCalledWith(MOCK_FILENAME, MOCK_CONTENTS);
});
});
48 changes: 48 additions & 0 deletions scripts/helpers/generate-csv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { appendFileSync, existsSync, unlinkSync } from 'fs';

const compileOutput = ({
columns,
rows,
}: {
columns: { header: string; key: string }[];
rows: { [k: string]: any }[];
}): string => {
const headers = columns.map(c => c.header);
const keys = columns.map(c => c.key);
let output = `"${headers.join('","')}"`;
for (const row of rows) {
const values: string[] = [];
for (const key of keys) {
const value = row[key] || '';
values.push(`${value}`);
}
output += `\n"${values.join('","')}"`;
}
return output;
};

const writeFile = ({
contents,
filename,
}: {
contents: string;
filename: string;
}): void => {
if (existsSync(filename)) {
unlinkSync(filename);
}
appendFileSync(filename, contents);
};

export const generateCsv = ({
columns,
filename,
rows,
}: {
columns: { header: string; key: string }[];
filename: string;
rows: { [k: string]: any }[];
}): void => {
const contents = compileOutput({ columns, rows });
writeFile({ contents, filename });
};
150 changes: 150 additions & 0 deletions scripts/reports/cases-closed-in-year.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/env npx ts-node --transpile-only

// usage: npx ts-node --transpile-only scripts/reports/cases-closed-in-year.ts 2024 [-f]

import { CASE_STATUS_TYPES } from '@shared/business/entities/EntityConstants';
import { DateTime } from 'luxon';
import {
ServerApplicationContext,
createApplicationContext,
} from '@web-api/applicationContext';
import { generateCsv } from '../helpers/generate-csv';
import { pick } from 'lodash';
import { searchAll } from '@web-api/persistence/elasticsearch/searchClient';
import { validateDateAndCreateISO } from '@shared/business/utilities/DateHandler';

const year = process.argv[2] || `${DateTime.now().toObject().year}`;
const fiscal =
!!process.argv[3] &&
(process.argv[3] === '-f' || process.argv[3] === '--fiscal');
const OUTPUT_DIR = `${process.env.HOME}/Documents`;
const CLOSED_STATUSES: string[] = [
CASE_STATUS_TYPES.closed,
CASE_STATUS_TYPES.closedDismissed,
];
const BEGIN = validateDateAndCreateISO({
day: '1',
month: fiscal ? '10' : '1',
year: fiscal ? `${Number(year) - 1}` : year,
})!;
const END = validateDateAndCreateISO({
day: '1',
month: fiscal ? '10' : '1',
year: fiscal ? year : `${Number(year) + 1}`,
})!;

const getAllCasesClosedInFiscalYear = async ({
applicationContext,
}: {
applicationContext: ServerApplicationContext;
}): Promise<RawCase[]> => {
const { results }: { results: RawCase[] } = await searchAll({
applicationContext,
searchParameters: {
body: {
query: {
bool: {
must: [
{
term: {
'entityName.S': 'Case',
},
},
{
terms: {
'caseStatusHistory.L.M.updatedCaseStatus.S': CLOSED_STATUSES,
},
},
{
range: {
'caseStatusHistory.L.M.date.S': {
gte: BEGIN,
lt: END,
},
},
},
],
},
},
sort: [{ 'sortableDocketNumber.N': 'asc' }],
},
index: 'efcms-case',
},
});
console.log(
`Found ${results.length} cases with a "closed" status history record and` +
` a status history record generated in fiscal year ${year}`,
);
return results.filter(c => wasClosedThisFiscalYear(c));
};

const wasClosedThisFiscalYear = (c: RawCase): boolean => {
let closedThisFiscalYear = false;
for (const csh of c.caseStatusHistory) {
if (
csh.date &&
csh.date >= BEGIN &&
csh.date < END &&
csh.updatedCaseStatus &&
CLOSED_STATUSES.includes(csh.updatedCaseStatus)
) {
closedThisFiscalYear = true;
break;
}
}
return closedThisFiscalYear;
};

const outputCsv = ({
casesClosedInYear,
}: {
casesClosedInYear: RawCase[];
}): void => {
const filename = `${OUTPUT_DIR}/cases-closed-in-${fiscal ? 'fy-' : ''}${year}.csv`;
const columns = [
{ header: 'Docket Number', key: 'docketNumber' },
{ header: 'Case Title', key: 'caption' },
{ header: 'Judge', key: 'judge' },
{ header: 'Case Status', key: 'status' },
{ header: 'Closed Date', key: 'closedDateHumanized' },
];
const rows = casesClosedInYear.map(c => {
const judge =
c.associatedJudge
?.replace('Chief Special Trial ', '')
.replace('Special Trial ', '')
.replace('Judge ', '') || '';
const closedDateHumanized =
c.caseStatusHistory
.reverse()
.find(
csh =>
CLOSED_STATUSES.includes(csh.updatedCaseStatus) &&
csh.date >= BEGIN &&
csh.date < END,
)
?.date.split('T')[0] || '';
const caption = c.caseCaption.replace(/\r\n|\r|\n/g, ' ').trim();
return {
...pick(c, ['docketNumber', 'status']),
caption,
closedDateHumanized,
judge,
};
});
generateCsv({ columns, filename, rows });
console.log(`Generated ${filename}`);
};

// eslint-disable-next-line @typescript-eslint/no-floating-promises
(async () => {
const applicationContext = createApplicationContext({});
const casesClosedInYear = await getAllCasesClosedInFiscalYear({
applicationContext,
});
console.log(
`Filtered results to ${casesClosedInYear.length} cases having a "closed"` +
` status history record that was generated in fiscal year ${year}`,
);
outputCsv({ casesClosedInYear });
})();
44 changes: 20 additions & 24 deletions scripts/reports/closed-dates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
ServerApplicationContext,
createApplicationContext,
} from '@web-api/applicationContext';
import { appendFileSync } from 'fs';
import { generateCsv } from '../helpers/generate-csv';
import { searchAll } from '@web-api/persistence/elasticsearch/searchClient';
import { validateDateAndCreateISO } from '@shared/business/utilities/DateHandler';

Expand Down Expand Up @@ -58,33 +58,29 @@ const getAllCasesOpenedInYear = async ({
return results;
};

const outputCsv = ({
casesOpenedInYear,
filename,
}: {
casesOpenedInYear: RawCase[];
filename: string;
}): void => {
let output =
'"Docket Number","Date Created","Date Closed","Case Title",' +
'"Case Status","Case Type"';
for (const c of casesOpenedInYear) {
const rcvdAtHumanized = c.receivedAt.split('T')[0];
const closedHumanized = c.closedDate?.split('T')[0] || '';
output +=
`\n"${c.docketNumber}","${rcvdAtHumanized}","${closedHumanized}",` +
`"${c.caseCaption}","${c.status}","${c.caseType}"`;
}
appendFileSync(`${OUTPUT_DIR}/${filename}`, output);
};

// eslint-disable-next-line @typescript-eslint/no-floating-promises
(async () => {
const applicationContext = createApplicationContext({});
const casesOpenedInYear = await getAllCasesOpenedInYear({
applicationContext,
});
const filename = `closed-dates-of-cases-opened-in-${year}.csv`;
outputCsv({ casesOpenedInYear, filename });
console.log(`Generated ${OUTPUT_DIR}/${filename}`);
const filename = `${OUTPUT_DIR}/closed-dates-of-cases-opened-in-${year}.csv`;
const columns = [
{ header: 'Docket Number', key: 'docketNumber' },
{ header: 'Date Created', key: 'rcvdAtHumanized' },
{ header: 'Date Closed', key: 'closedHumanized' },
{ header: 'Case Title', key: 'caseCaption' },
{ header: 'Case Status', key: 'status' },
{ header: 'Case Type', key: 'caseType' },
];
const rows = casesOpenedInYear.map(c => ({
caseCaption: c.caseCaption,
caseType: c.caseType,
closedHumanized: c.closedDate?.split('T')[0] || '',
docketNumber: c.docketNumber,
rcvdAtHumanized: c.receivedAt.split('T')[0],
status: c.status,
}));
generateCsv({ columns, filename, rows });
console.log(`Generated ${filename}`);
})();
Loading

0 comments on commit afd1ce7

Please sign in to comment.