-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5687 from ustaxcourt/opex-cbj-2025
OpEx: more reports for 2025 Congressional Budget Justification
- Loading branch information
Showing
6 changed files
with
515 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
#!/usr/bin/env -S npx ts-node --transpile-only | ||
|
||
import { type RawPractitioner } from '@shared/business/entities/Practitioner'; | ||
import { | ||
type ScriptConfig, | ||
parseArgsAndEnvVars, | ||
} from '../helpers/parseArgsAndEnvVars'; | ||
import { | ||
type ServerApplicationContext, | ||
createApplicationContext, | ||
} from '@web-api/applicationContext'; | ||
import { generateCsv } from '../helpers/generate-csv'; | ||
import { pick } from 'lodash'; | ||
import { searchAll } from '@web-api/persistence/elasticsearch/searchClient'; | ||
|
||
const scriptConfig: ScriptConfig = { | ||
description: | ||
'attorneys-admitted-in-year - Generates a CSV of attorneys admitted in the given year.', | ||
environment: { | ||
elasticsearchEndpoint: 'ELASTICSEARCH_ENDPOINT', | ||
env: 'ENV', | ||
}, | ||
parameters: { | ||
fiscal: { | ||
short: 'f', | ||
type: 'boolean', | ||
}, | ||
year: { | ||
position: 0, | ||
required: true, | ||
transform: 'number', | ||
type: 'string', | ||
}, | ||
}, | ||
requireActiveAwsSession: true, | ||
}; | ||
|
||
const OUTPUT_DIR = `${process.env.HOME}/Documents`; | ||
|
||
const getAttorneysAdmittedInYear = async ({ | ||
applicationContext, | ||
fiscal, | ||
year, | ||
}: { | ||
applicationContext: ServerApplicationContext; | ||
fiscal: boolean; | ||
year: number; | ||
}): Promise<RawPractitioner[]> => { | ||
const { results } = await searchAll({ | ||
applicationContext, | ||
searchParameters: { | ||
body: { | ||
query: { | ||
bool: { | ||
must: [ | ||
{ | ||
term: { | ||
'practitionerType.S': 'Attorney', | ||
}, | ||
}, | ||
{ | ||
range: { | ||
'admissionsDate.S': { | ||
gte: fiscal | ||
? `${year - 1}-10-01T05:00:00Z` | ||
: `${year}-01-01T04:00:00Z`, | ||
lt: fiscal | ||
? `${year}-10-01T05:00:00Z` | ||
: `${year + 1}-01-01T04:00:00Z`, | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
sort: [{ 'admissionsDate.S': 'asc' }], | ||
}, | ||
index: 'efcms-user', | ||
}, | ||
}); | ||
return results; | ||
}; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
(async () => { | ||
const applicationContext = createApplicationContext({}); | ||
const { fiscal, year } = parseArgsAndEnvVars(scriptConfig) as { | ||
fiscal: boolean; | ||
year: number; | ||
}; | ||
const attorneys: RawPractitioner[] = await getAttorneysAdmittedInYear({ | ||
applicationContext, | ||
fiscal, | ||
year, | ||
}); | ||
console.log( | ||
`Found ${attorneys.length} attorneys admitted in ${fiscal ? 'fiscal' : 'calendar'} year ${year}.`, | ||
); | ||
const columns = [ | ||
{ header: 'Bar Number', key: 'barNumber' }, | ||
{ header: 'Name', key: 'name' }, | ||
{ header: 'Practice Type', key: 'practiceType' }, | ||
{ header: 'Firm', key: 'firmName' }, | ||
{ header: 'Admissions Date', key: 'admissionsDate' }, | ||
]; | ||
const rows = attorneys.map(attorney => ({ | ||
...pick(attorney, ['barNumber', 'firmName', 'name', 'practiceType']), | ||
admissionsDate: attorney.admissionsDate.split('T')[0], | ||
})); | ||
const filename = `${OUTPUT_DIR}/attorneys-admitted-in${fiscal ? '-fiscal-year' : ''}-${year}.csv`; | ||
generateCsv({ columns, filename, rows }); | ||
console.log(`Generated ${filename}`); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
#!/usr/bin/env -S npx ts-node --transpile-only | ||
|
||
import { | ||
type ScriptConfig, | ||
parseArgsAndEnvVars, | ||
} from '../helpers/parseArgsAndEnvVars'; | ||
import { | ||
type ServerApplicationContext, | ||
createApplicationContext, | ||
} from '@web-api/applicationContext'; | ||
import { generateCsv } from '../helpers/generate-csv'; | ||
import { pick } from 'lodash'; | ||
import { searchAll } from '@web-api/persistence/elasticsearch/searchClient'; | ||
|
||
const scriptConfig: ScriptConfig = { | ||
description: | ||
'documents-filed-by-non-attorneys - Generates a CSV of documents filed by non-attorneys.', | ||
environment: { | ||
elasticsearchEndpoint: 'ELASTICSEARCH_ENDPOINT', | ||
env: 'ENV', | ||
}, | ||
parameters: { | ||
eventCode: { | ||
default: 'P', | ||
long: 'event-code', | ||
required: false, | ||
short: 'e', | ||
type: 'string', | ||
}, | ||
fiscal: { | ||
default: false, | ||
short: 'f', | ||
type: 'boolean', | ||
}, | ||
year: { | ||
default: '2024', | ||
required: false, | ||
short: 'y', | ||
transform: 'number', | ||
type: 'string', | ||
}, | ||
}, | ||
requireActiveAwsSession: true, | ||
}; | ||
|
||
const OUTPUT_DIR = `${process.env.HOME}/Documents`; | ||
|
||
const getNonAttorneys = async ({ | ||
applicationContext, | ||
}: { | ||
applicationContext: ServerApplicationContext; | ||
}): Promise<{ [k: string]: string }> => { | ||
const nonAttorneys = {}; | ||
const { results } = await searchAll({ | ||
applicationContext, | ||
searchParameters: { | ||
body: { | ||
query: { | ||
bool: { | ||
must: [ | ||
{ | ||
term: { | ||
'admissionsStatus.S': 'Active', | ||
}, | ||
}, | ||
{ | ||
term: { | ||
'practitionerType.S': 'Non-Attorney', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
index: 'efcms-user', | ||
}, | ||
}); | ||
for (const result of results) { | ||
nonAttorneys[result.pk.replace('user|', '')] = result.name; | ||
} | ||
return nonAttorneys; | ||
}; | ||
|
||
const getDocuments = async ({ | ||
applicationContext, | ||
eventCode, | ||
fiscal, | ||
userIds, | ||
year, | ||
}: { | ||
applicationContext: ServerApplicationContext; | ||
eventCode: string; | ||
fiscal: boolean; | ||
userIds: string[]; | ||
year: number; | ||
}): Promise<RawDocketEntry[]> => { | ||
const { results } = await searchAll({ | ||
applicationContext, | ||
searchParameters: { | ||
body: { | ||
query: { | ||
bool: { | ||
must: [ | ||
{ | ||
term: { | ||
'entityName.S': 'DocketEntry', | ||
}, | ||
}, | ||
{ | ||
term: { | ||
'eventCode.S': eventCode, | ||
}, | ||
}, | ||
{ | ||
terms: { | ||
'userId.S': userIds, | ||
}, | ||
}, | ||
{ | ||
range: { | ||
'receivedAt.S': { | ||
gte: fiscal | ||
? `${year - 1}-10-01T05:00:00Z` | ||
: `${year}-01-01T04:00:00Z`, | ||
lt: fiscal | ||
? `${year}-10-01T05:00:00Z` | ||
: `${year + 1}-01-01T04:00:00Z`, | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
sort: [{ 'receivedAt.S': 'asc' }], | ||
}, | ||
index: 'efcms-docket-entry', | ||
}, | ||
}); | ||
return results; | ||
}; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
(async () => { | ||
const applicationContext = createApplicationContext({}); | ||
const { eventCode, fiscal, year } = parseArgsAndEnvVars(scriptConfig) as { | ||
eventCode: string; | ||
fiscal: boolean; | ||
year: number; | ||
}; | ||
console.log( | ||
`Looking for documents with event code ${eventCode} filed by non-attorneys ` + | ||
`in ${fiscal ? 'fiscal' : 'calendar'} year ${year}...`, | ||
); | ||
const nonAttorneys = await getNonAttorneys({ applicationContext }); | ||
console.log( | ||
`Found ${Object.keys(nonAttorneys).length} non-attorneys with active admissions status.`, | ||
); | ||
const documents = await getDocuments({ | ||
applicationContext, | ||
eventCode, | ||
fiscal, | ||
userIds: Object.keys(nonAttorneys), | ||
year, | ||
}); | ||
if (!documents.length) { | ||
console.log( | ||
`Found 0 documents with event code ${eventCode} filed by non-attorneys ` + | ||
`in ${fiscal ? 'fiscal' : 'calendar'} year ${year}.`, | ||
); | ||
return; | ||
} | ||
console.log( | ||
`Found ${documents.length} ${documents[0].documentType}` + | ||
`${documents.length === 1 ? '' : 's'} filed by non-attorneys ` + | ||
`in ${fiscal ? 'fiscal' : 'calendar'} year ${year}.`, | ||
); | ||
const columns = [ | ||
{ header: 'Docket Number', key: 'docketNumber' }, | ||
{ header: 'Document Title', key: 'documentTitle' }, | ||
{ header: 'Filed On', key: 'filedOn' }, | ||
{ header: 'Filed By', key: 'filedBy' }, | ||
]; | ||
const rows = documents.map(de => ({ | ||
...pick(de, ['docketNumber', 'documentTitle']), | ||
filedBy: nonAttorneys[de.userId!], | ||
filedOn: de.receivedAt.split('T')[0], | ||
})); | ||
const docType = documents[0].documentType.replace(' ', '-').toLowerCase(); | ||
const filename = `${OUTPUT_DIR}/${docType}s-filed-by-non-attorneys-in${fiscal ? '-fiscal-year' : ''}-${year}.csv`; | ||
generateCsv({ columns, filename, rows }); | ||
console.log(`Generated ${filename}`); | ||
})(); |
Oops, something went wrong.