@cityssm/mpac-yetf
Version:
Parses the MPAC Year-End Tax File (YETF) into a usable object.
217 lines (198 loc) • 6.84 kB
text/typescript
// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair
/* eslint-disable @typescript-eslint/no-unsafe-type-assertion */
import events from 'node:events'
import fs from 'node:fs'
import readline from 'node:readline'
import {
parseRollNumber,
rollNumberMunicipalities
} from '@cityssm/mpac-tools/rollNumbers'
import { formatBB } from './formatters/bbFormatter.js'
import { formatCC } from './formatters/ccFormatter.js'
import { formatDD } from './formatters/ddFormatter.js'
import { formatGG } from './formatters/ggFormatter.js'
import { formatKK } from './formatters/kkFormatter.js'
import { formatPA } from './formatters/paFormatter.js'
import { formatPB } from './formatters/pbFormatter.js'
import { formatPC } from './formatters/pcFormatter.js'
import { formatPD } from './formatters/pdFormatter.js'
import { formatPI } from './formatters/piFormatter.js'
import { parseYetfRecordString } from './parsers.js'
import type * as types from './types.js'
interface ParseYetfOptions {
addFormattedFields?: boolean
callbacks: {
all?: (
record: types.FormattedYetfRecord | types.RawYetfRecord,
lineNumber?: number
) => void
AA?: (
record: types.FormattedYetfRecordAA | types.RawYetfRecordAA,
lineNumber?: number
) => void
BB?: (
record: types.FormattedYetfRecordBB | types.RawYetfRecordBB,
lineNumber?: number
) => void
CC?: (
record: types.FormattedYetfRecordCC | types.RawYetfRecordCC,
lineNumber?: number
) => void
DD?: (
record: types.FormattedYetfRecordDD | types.RawYetfRecordDD,
lineNumber?: number
) => void
GG?: (
record: types.FormattedYetfRecordGG | types.RawYetfRecordGG,
lineNumber?: number
) => void
HH?: (
record: types.FormattedYetfRecordHH | types.RawYetfRecordHH,
lineNumber?: number
) => void
JJ?: (
record: types.FormattedYetfRecordJJ | types.RawYetfRecordJJ,
lineNumber?: number
) => void
KK?: (
record: types.FormattedYetfRecordKK | types.RawYetfRecordKK,
lineNumber?: number
) => void
LL?: (
record: types.FormattedYetfRecordLL | types.RawYetfRecordLL,
lineNumber?: number
) => void
MM?: (
record: types.FormattedYetfRecordMM | types.RawYetfRecordMM,
lineNumber?: number
) => void
PA?: (
record: types.FormattedYetfRecordPA | types.RawYetfRecordPA,
lineNumber?: number
) => void
PB?: (
record: types.FormattedYetfRecordPB | types.RawYetfRecordPB,
lineNumber?: number
) => void
PC?: (
record: types.FormattedYetfRecordPC | types.RawYetfRecordPC,
lineNumber?: number
) => void
PD?: (
record: types.FormattedYetfRecordPD | types.RawYetfRecordPD,
lineNumber?: number
) => void
PI?: (
record: types.FormattedYetfRecordPI | types.RawYetfRecordPI,
lineNumber?: number
) => void
}
}
/**
* Parses a YETF file line-by-line, calling the appropriate callback for each record type.
* @param filePath - Path to the YETF file
* @param options - Parsing options
* @returns Promise which resolves to `true` when parsing is complete
*/
export async function parseYetf(
filePath: string,
options: ParseYetfOptions
): Promise<boolean> {
const rl = readline.createInterface({
// eslint-disable-next-line security/detect-non-literal-fs-filename
input: fs.createReadStream(filePath)
})
let lineNumber = 0
rl.on('line', (recordString) => {
lineNumber += 1
let record = parseYetfRecordString(recordString)
if (options.addFormattedFields ?? false) {
const parsedRollNumber = parseRollNumber(record.rollNumber)
// Roll Number Pieces
;(record as types.FormattedYetfRecord).rollNumberCounty =
parsedRollNumber.county ?? ''
;(record as types.FormattedYetfRecord).rollNumberMunicipality =
parsedRollNumber.municipality ?? ''
;(record as types.FormattedYetfRecord).rollNumberMapArea =
parsedRollNumber.mapArea
;(record as types.FormattedYetfRecord).rollNumberMapDivision =
parsedRollNumber.mapDivision
;(record as types.FormattedYetfRecord).rollNumberMapSubdivision =
parsedRollNumber.mapSubdivision
;(record as types.FormattedYetfRecord).rollNumberParcel =
parsedRollNumber.parcel
;(record as types.FormattedYetfRecord).rollNumberParcelSub =
parsedRollNumber.parcelSub
;(record as types.FormattedYetfRecord).rollNumberPrimarySubordinate =
parsedRollNumber.primarySubordinate
// Related Roll Number Data
;(record as types.FormattedYetfRecord).rollNumberMunicipalityName =
rollNumberMunicipalities[
(parsedRollNumber.county ?? '') +
(parsedRollNumber.municipality ?? '')
]
;(record as types.FormattedYetfRecord).rollNumberIsPrimary =
parsedRollNumber.primarySubordinate === '0000'
switch (record.recordType) {
case 'BB': {
record = formatBB(record as types.RawYetfRecordBB)
break
}
case 'CC': {
record = formatCC(record as types.RawYetfRecordCC)
break
}
case 'DD': {
record = formatDD(record as types.RawYetfRecordDD)
break
}
case 'GG': {
record = formatGG(record as types.RawYetfRecordGG)
break
}
case 'KK': {
record = formatKK(record as types.RawYetfRecordKK)
break
}
case 'PA': {
record = formatPA(record as types.RawYetfRecordPA)
break
}
case 'PB': {
record = formatPB(record as types.RawYetfRecordPB)
break
}
case 'PC': {
record = formatPC(record as types.RawYetfRecordPC)
break
}
case 'PD': {
record = formatPD(record as types.RawYetfRecordPD)
break
}
case 'PI': {
record = formatPI(record as types.RawYetfRecordPI)
break
}
default: {
// No formatting available
break
}
}
}
if (options.callbacks.all !== undefined) {
options.callbacks.all(record, lineNumber)
}
if (options.callbacks[record.recordType] !== undefined) {
;(
options.callbacks[record.recordType] as (
record: types.FormattedYetfRecord | types.RawYetfRecord,
lineNumber?: number
) => void
)(record, lineNumber)
}
})
await events.once(rl, 'close')
return true
}
export default parseYetf