Skip to content

Instantly share code, notes, and snippets.

@taxilian
Created April 16, 2020 20:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save taxilian/730786755cdeb738b4a409182f54f645 to your computer and use it in GitHub Desktop.
Save taxilian/730786755cdeb738b4a409182f54f645 to your computer and use it in GitHub Desktop.
EBF parser / generator for Amateur Radio VEC submission
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