Created
April 16, 2020 20:29
-
-
Save taxilian/730786755cdeb738b4a409182f54f645 to your computer and use it in GitHub Desktop.
EBF parser / generator for Amateur Radio VEC submission
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
export const velist = { | |
"anchorage": "Anchorage Amateur Radio Club", | |
"arrl": "American Radio Relay League (ARRL)", | |
"cavec": "Central America CAVEC, Inc.", | |
"golden": "Golden Empire Amateur Radio Society", | |
"lagroup": "Greater L.A. Amateur Radio Group", | |
"jefferson": "Jefferson Amateur Radio Club", | |
"laurel": "Laurel Amateur Radio Club, Inc", | |
"mrac": "MRAC VEC, Inc", | |
"mo-kan": "MO-KAN VEC Coordinator", | |
"sandarc": "Sandarc-VEC", | |
"sunnyvale": "Sunnyvale VEC Amateur Radio Club, Inc", | |
"w4vec": "W4VEC Volunteer Examiners Club of America", | |
"w5yi": "W5YI-VEC", | |
"west-carolina": "Western Carolina Amateur Radio Society VEC, Inc." | |
}; | |
/** | |
* Application file definition: | |
* | |
* Filename should be: | |
* vmmddnn.dat | |
* | |
* v - VEC code | |
* mmdd - month and day the file is sent | |
* nn - unique sequential number | |
*/ | |
export const VecEbfCode = <const>{ | |
"mrac": 'A', | |
"laurel": 'B', | |
"anchorage": 'C', | |
"arrl": 'D', | |
"w4vec": 'E', | |
"lagroup": 'F', | |
"sunnyvale": 'G', | |
"west-carolina": 'H', | |
"golden": 'J', | |
"cavec": 'K', | |
"w5yi": 'L', | |
"mo-kan": 'M', | |
"sandarc": 'N', | |
"jefferson": 'Q', | |
}; | |
/** | |
* VEC record type | |
* Sample VEC Record Type - VE | |
* VE|L|02/28/2017|WOODSFIELD|OH|1|1|3|3|3 | |
*/ | |
export interface VECRecordType { | |
RecordType: 'VE'; // char(2) | |
VECCode: string; // char(1) | |
SessionDate: string; // mm/dd/yyyy | |
SessionCity: string; // char(20) | |
SessionState: string; // char(2) | |
ApplicantsTested: number; | |
ApplicantsPassed: number; | |
ApplicantsFailed: number; | |
ElementsPassed: number; | |
ElementsFailed: number; | |
} | |
const VECRecordTypeFieldMaxLengths = <const>[ | |
2, 1, | |
10, 20, 2, | |
99, 99, 99, | |
99, 99, | |
]; | |
const VECRecordTypeFieldOrder = <const>[ | |
'RecordType', 'VECCode', | |
'SessionDate', 'SessionCity', 'SessionState', | |
'ApplicantsTested', 'ApplicantsPassed', 'ApplicantsFailed', | |
'ElementsPassed', 'ElementsFailed', | |
]; | |
const ApplicationPurposes = <const>{ | |
New: 'NE', | |
RenewalOnly: 'RO', | |
WithdrawalOfApplication: 'WD', | |
Modification: 'MD', | |
RenewalAndModification: 'RM', | |
DuplicateLicense: 'DU', | |
Amendment: 'AM', | |
Cancellation: 'CA', | |
AdministrativeUpdate: 'AU', | |
}; | |
type ApplicationPurposes = typeof ApplicationPurposes[keyof typeof ApplicationPurposes]; | |
export {ApplicationPurposes}; | |
const OperatorClasses = <const>{ | |
Technician: 'T', | |
General: 'G', | |
Extra: 'E', | |
}; | |
type OperatorClasses = typeof OperatorClasses[keyof typeof OperatorClasses]; | |
type ApplicantTypeCode = 'I'|'R'|'B'|'M'; | |
type StringBool = 'Y' | 'N'; | |
const PSQOptions = <const>{ | |
'1': 'Custom PSQ', | |
'2': 'Original City of Birth', | |
'3': `Pet's Name`, | |
'4': 'Corp Internal Employee ID', | |
'5': `Mother's Maiden Name`, | |
}; | |
type PSQOptions = keyof typeof PSQOptions; | |
/** | |
* Application record type | |
* Sample Application Record Type - VA | |
* VA|||||William|A|Brown|II||5 Main Street||Hartford|CT|06335 |0000000000||billb@mynet.com|NE|N|Y|N|||N||||I|0004283453|10/06/1969||1|Favor ite Color|Blue|N | |
*/ | |
export interface ApplicationRecordType { | |
RecordType: 'VA'; | |
PendingFileNumber?: string; // char(14) // Form 605 box 4 | |
CallSign: string; // char(6) | |
SSN?: ''; // char(9) | |
EntityName?: string; // char(200) // Box 11 | |
FirstName: string; // char(20) // Box 11 | |
Middle: string; // char(1) // Box 11 | |
LastName: string; // char(20) // Box 11 | |
Suffix?: string; // char(3) // Box 11 | |
AttnTo?: string; // char(35) // Box 14 | |
StreetAddr?: string;// char(60) // Box 16 | |
POBox?: string; // char(20) // Box 15 | |
City: string; // char(20) | |
State: string; // char(2) | |
Zip: string; // char(9) | |
Phone?: string; // char(10) | |
Fax?: string; // char(10) | |
Email?: string; // char(50) | |
PurposeCode: ApplicationPurposes; // char(2) | |
OperatorClass: OperatorClasses; // char(1) | |
ValidSignature: StringBool; | |
PhysicianCertification: StringBool; | |
RequestedExpiration?: string; // char(4) mmdd | |
WaiverRequest?: StringBool; // char(1) | |
Attachments: StringBool; // char(1) | |
AttachmentFilename?: ''; | |
AttachmentToBeFaxed?: ''; | |
ChangeCallSign: StringBool; // char(1) (change call systematically?) | |
TrusteeCall?: string; // char(10) only for club | |
ApplicantType: ApplicantTypeCode; // char(1) | |
FRN: string; // char(10) | |
DateOfBirth?: string; // 'mm/dd/yyyy'; | |
LicenseNameChange?: 'N' | ''; // N if licensee name in [5] or [6..9] is different than on the license | |
PSQ?: PSQOptions; // char(4) | |
CustomPSQ?: string; // char(60) | |
PSQAnswer?: string; // char(60) | |
FelonyQ: string; // char(60) | |
} | |
const ApplicationRecordTypeFieldMaxLengths = <const>[ | |
2, 14, 6, 0, 200, | |
20, 1, 20, 3, 35, | |
60, 20, 20, 2, 9, | |
10, 10, 50, | |
2, 1, 1, | |
1, 4, | |
1, | |
1, 0, 0, | |
1, 10, | |
1, 10, 10, | |
1, | |
4, 60, 60, 1, | |
]; | |
const ApplicationRecordTypeFieldOrder = <const>[ | |
'RecordType', 'PendingFileNumber', 'CallSign', 'SSN', 'EntityName', | |
'FirstName', 'Middle', 'LastName', 'Suffix', 'AttnTo', | |
'StreetAddr', 'POBox', 'City', 'State', 'Zip', | |
'Phone', 'Fax', 'Email', | |
'PurposeCode', 'OperatorClass', 'ValidSignature', | |
'PhysicianCertification', 'RequestedExpiration', | |
'WaiverRequest', | |
'Attachments', 'AttachmentFilename', 'AttachmentToBeFaxed', | |
'ChangeCallSign', 'TrusteeCall', | |
'ApplicantType', 'FRN', 'DateOfBirth', | |
'LicenseNameChange', | |
'PSQ', 'CustomPSQ', 'PSQAnswer', 'FelonyQ', | |
]; | |
function parseRecordTo<T extends object>(recordStr: string, fieldOrder: readonly (keyof T)[]) : T { | |
const record = recordStr.split('|'); | |
let out: T = {} as any; | |
for (let i = 0; i < fieldOrder.length; ++i) { | |
out[fieldOrder[i]] = record[i] as any; | |
} | |
return out; | |
} | |
export function parseRecord(record: string) { | |
if (record.startsWith('VE')) { | |
return parseRecordTo<VECRecordType>(record, VECRecordTypeFieldOrder); | |
} else if (record.startsWith('VA')) { | |
return parseRecordTo<ApplicationRecordType>(record, ApplicationRecordTypeFieldOrder); | |
} | |
} | |
function makeRecordFor<T extends object>(record: T, fieldOrder: readonly (keyof T)[], fieldLength: readonly number[]) : string { | |
const recordArray = fieldOrder.map((val, idx) => String(record[val] ?? '').substr(0, fieldLength[idx])); | |
return recordArray.join('|'); | |
} | |
export function encodeRecord(record: ApplicationRecordType | VECRecordType) { | |
if (record.RecordType == 'VE') { | |
return makeRecordFor(record, VECRecordTypeFieldOrder, VECRecordTypeFieldMaxLengths); | |
} else if (record.RecordType == 'VA') { | |
return makeRecordFor(record, ApplicationRecordTypeFieldOrder, ApplicationRecordTypeFieldMaxLengths); | |
} | |
} | |
const StatusCodes = <const>{ | |
Granted: 'G', | |
ReturnedToApplicant: 'R', | |
Dismissed: 'D', | |
Withdrawn: 'W', | |
NonBusinessDay: '0', | |
OfflinedForProcessing: '2', | |
}; | |
type StatusCodes = typeof StatusCodes[keyof typeof StatusCodes]; | |
/** | |
* Examples: | |
* | |
* Granted | |
* RA|0000162008|Jane|Q|Public||21532|WZ6ZAA|T|000123546|G||| | |
* Filed on Weekend | |
* RA|0000162009|John|Q|Public|Jr|21403||E|000123547|0||| | |
* File Rejected | |
* RA||Milo||Public||78729|KA2FXE|||9480||| | |
*/ | |
interface StatusRecordTypeRA { | |
recordType: 'RA'; // char(2) | |
ULSFileNo: string; // char(14) | |
FirstName: string; // char(20) | |
Middle: string; // char(1) | |
LastName: string; // char(20) | |
Suffix: string; // char(3) | |
ZipCode: string; // char(9) | |
CallSign: string; // char(6) | |
OperatorClass: string; // char(1) | |
FRN: string; // char(10) | |
statusCode: StatusCodes; // char(5) | |
PaymentTypeCode: string; // char(4) | |
Quantity: number; | |
FeeAmount: number; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment