hmdwatest-seattle
Version:
Seattle region plug-in for @HowsMyDrivingWA.
528 lines (490 loc) • 22 kB
JavaScript
;
// TODO: Order things in this file
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var soap_1 = require("soap");
var hmdwatest_utils_1 = require("hmdwatest-utils");
var hmdwatest_utils_2 = require("hmdwatest-utils");
var hmdwatest_utils_3 = require("hmdwatest-utils");
var logging_1 = require("./logging");
var fs = require("fs"), path = require("path");
// TODO: Consolidate these.
var parkingAndCameraViolationsText = "Total parking and camera violations for #", violationsByYearText = "Violations by year for #", violationsByStatusText = "Violations by status for #", citationQueryText = "License #__LICENSE__ has been queried __COUNT__ times.";
// The Seattle court web service to query citations.
// This could break at any time since they don't document its availability.
var url = "https://web6.seattle.gov/Courts/ECFPortal/JSONServices/ECFControlsService.asmx?wsdl";
var SeattleCitation = /** @class */ (function (_super) {
__extends(SeattleCitation, _super);
function SeattleCitation(citation) {
var _this = _super.call(this, citation.citation_id, citation.license) || this;
// If passed an existing instance, copy over the properties.
if (arguments.length > 0) {
for (var p in citation) {
if (citation.hasOwnProperty(p)) {
_this[p] = citation[p];
}
}
}
return _this;
}
return SeattleCitation;
}(hmdwatest_utils_1.Citation));
// Classes
var SeattleRegion = /** @class */ (function () {
function SeattleRegion() {
}
SeattleRegion.prototype.GetCitationsByPlate = function (plate, state) {
var _this = this;
return new Promise(function (resolve, reject) {
var citations = [];
_this.GetVehicleIDs(plate, state).then(function (vehicles) { return __awaiter(_this, void 0, void 0, function () {
var citationsByCitationID, i, vehicle, allCitations;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
citationsByCitationID = {};
i = 0;
_a.label = 1;
case 1:
if (!(i < vehicles.length)) return [3 /*break*/, 4];
vehicle = vehicles[i];
return [4 /*yield*/, this.GetCitationsByVehicleNum(vehicle.VehicleNumber)];
case 2:
(_a.sent()).forEach(function (citation) {
// use the Citation field as the unique citation_id.
citation.citation_id = citation.Citation;
citationsByCitationID[citation.citation_id] = citation;
});
_a.label = 3;
case 3:
i++;
return [3 /*break*/, 1];
case 4:
logging_1.log.info("Found " + Object.keys(citationsByCitationID).length + " different citations for vehicle " + state + ":" + plate);
allCitations = Object.keys(citationsByCitationID).map(function (v) { return citationsByCitationID[v]; });
resolve(allCitations);
return [2 /*return*/];
}
});
}); });
});
};
SeattleRegion.prototype.ProcessCitationsForRequest = function (citations, query_count) {
var categorizedCitations = {};
// TODO: Does it work to convert Date's to string for sorting? Might have to use epoch.
var chronologicalCitations = {};
var violationsByYear = {};
var violationsByStatus = {};
if (!citations || Object.keys(citations).length == 0) {
// Should never happen. jurisdictions must return at least a dummy citation
throw new Error("Jurisdiction modules must return at least one citation, a dummy one if there are none.");
}
var license;
for (var i = 0; i < citations.length; i++) {
var citation = citations[i];
var year = 1970;
var violationDate = new Date(Date.now());
// All citations are from the same license
if (license == null) {
license = citation.license;
}
try {
violationDate = new Date(Date.parse(citation.ViolationDate));
}
catch (e) {
// TODO: refactor error handling to a separate file
throw new Error(e);
}
// TODO: Is it possible to have more than 1 citation with exact same time?
// Maybe throw an exception if we ever encounter it...
if (!(violationDate.getTime().toString() in chronologicalCitations)) {
chronologicalCitations[violationDate.getTime().toString()] = new Array();
}
chronologicalCitations[violationDate.getTime().toString()].push(citation);
if (!(citation.Type in categorizedCitations)) {
categorizedCitations[citation.Type] = 0;
}
categorizedCitations[citation.Type]++;
if (!(citation.Status in violationsByStatus)) {
violationsByStatus[citation.Status] = 0;
}
violationsByStatus[citation.Status]++;
year = violationDate.getFullYear();
if (!(year.toString() in violationsByYear)) {
violationsByYear[year.toString()] = 0;
}
violationsByYear[year.toString()]++;
}
var general_summary = parkingAndCameraViolationsText +
hmdwatest_utils_2.formatPlate(license) +
": " +
Object.keys(citations).length;
Object.keys(categorizedCitations).forEach(function (key) {
var line = key + ": " + categorizedCitations[key];
// Max twitter username is 15 characters, plus the @
general_summary += "\n";
general_summary += line;
});
general_summary += "\n\n";
general_summary += citationQueryText
.replace('__LICENSE__', hmdwatest_utils_2.formatPlate(license))
.replace('__COUNT__', query_count.toString());
var detailed_list = "";
var sortedChronoCitationKeys = Object.keys(chronologicalCitations).sort(function (a, b) {
//return new Date(a).getTime() - new Date(b).getTime();
return hmdwatest_utils_3.CompareNumericStrings(a, b); //(a === b) ? 0 : ( a < b ? -1 : 1);
});
var first = true;
for (var i = 0; i < sortedChronoCitationKeys.length; i++) {
var key = sortedChronoCitationKeys[i];
chronologicalCitations[key].forEach(function (citation) {
if (first != true) {
detailed_list += "\n";
}
first = false;
detailed_list += citation.ViolationDate + ", " + citation.Type + ", " + citation.ViolationLocation + ", " + citation.Status;
});
}
var temporal_summary = violationsByYearText + hmdwatest_utils_2.formatPlate(license) + ":";
Object.keys(violationsByYear).forEach(function (key) {
temporal_summary += "\n";
temporal_summary += key + ": " + violationsByYear[key].toString();
});
var type_summary = violationsByStatusText + hmdwatest_utils_2.formatPlate(license) + ":";
Object.keys(violationsByStatus).forEach(function (key) {
type_summary += "\n";
type_summary += key + ": " + violationsByStatus[key];
});
// Return them in the order they should be rendered.
return [
general_summary,
detailed_list,
type_summary,
temporal_summary
];
};
// TODO: If we export this class, this method must be moved out
// because there is no way to declare a function private in a class.
SeattleRegion.prototype.GetVehicleIDs = function (plate, state) {
var args = {
Plate: plate,
State: state
};
return new Promise(function (resolve, reject) {
soap_1.createClient(url, function (err, client) {
if (err) {
throw err;
}
// GetVehicleByPlate returns all vehicles with plates that
// start with the specified plate. So we have to filter the
// results.
client.GetVehicleByPlate(args, function (err, result) {
if (err) {
throw err;
}
var vehicle_records = [];
var jsonObj = JSON.parse(result.GetVehicleByPlateResult);
var jsonResultSet = JSON.parse(jsonObj.Data);
for (var i = 0; i < jsonResultSet.length; i++) {
var vehicle = new Vehicle(jsonResultSet[i]);
if (vehicle.Plate == plate) {
vehicle_records.push(vehicle);
}
}
resolve(vehicle_records);
});
});
});
};
SeattleRegion.prototype.GetCitationsByVehicleNum = function (vehicleID) {
var args = {
VehicleNumber: vehicleID
};
logging_1.log.debug("Getting citations for vehicle ID: " + vehicleID + ".");
return new Promise(function (resolve, reject) {
soap_1.createClient(url, function (err, client) {
if (err) {
throw err;
}
client.GetCitationsByVehicleNumber(args, function (err, citations_result) {
if (err) {
throw err;
}
var jsonObj = JSON.parse(citations_result.GetCitationsByVehicleNumberResult);
var jsonResultSet = JSON.parse(jsonObj.Data);
var citations = [];
jsonResultSet.forEach(function (item) {
var citation = item;
// Add in the citation_id field
citation.citation_id = citation.Citation;
citations.push(citation);
});
resolve(citations);
});
});
});
};
// TODO: Implement and test this.
SeattleRegion.prototype.GetCasesByVehicleNum = function (vehicleID) {
var args = {
VehicleNumber: vehicleID
};
return new Promise(function (resolve, reject) {
soap_1.createClient(url, function (err, client) {
client.GetCasesByVehicleNumber(args, function (err, cases_result) {
// TODO: This is not right. Need JSON.parse twice.
var cases = JSON.parse(cases_result.GetCasesByVehicleNumberResult);
resolve(cases);
});
});
});
};
return SeattleRegion;
}());
exports.SeattleRegion = SeattleRegion;
var Vehicle = /** @class */ (function () {
function Vehicle(veh) {
this.VehicleNumber = veh.VehicleNumber;
this.Make = veh.Make;
this.Model = veh.Model;
this.Year = veh.Year;
this.State = veh.State;
this.Plate = veh.Plate,
this.ExpirationYear = veh.ExpirationYear;
this.Color = veh.Color;
this.Style = veh.Style;
this.Dealer = veh.Dealer;
this.VIN = veh.VIN;
this.PlateType = veh.PlateType;
this.DOLReceivedDate = veh.DOLReceivedDate;
this.DOLRequestDate = veh.DOLRequestDate;
}
return Vehicle;
}());
/*
function GetCitationsByPlate(plate: string, state: string) {
return new Promise( (resolve, reject) => {
let citations: string[];
GetVehicleIDs(plate, state).then(async function(vehicles: object[]) {
// Make the calls to GetCitationsByVehicleNum soap method synchronously
// Or we could get throttled by the server.
for (let i: number = 0; i < vehicles.length; i++) {
let vehicle: object = vehicles[i];
citations.push( await GetCitationsByVehicleNum(vehicle.VehicleNumber) );
}
// citations is an array of an array of citations, one for each vehicle id
// collapse them into a hash based on
var citationsByCitationID = {};
citations.forEach( (innerArray) => {
innerArray.forEach( (citation) => {
citationsByCitationID[citation.Citation] = citation;
});
});
// Now put the unique citations back to an array
var allCitations = Object.keys(citationsByCitationID).map(function(v) { return citationsByCitationID[v]; });
resolve(allCitations);
});
});
}
function GetCitationsByVehicleNum(vehicleID) {
var args = {
VehicleNumber: vehicleID
};
log.debug(`Getting citations for vehicle ID: ${vehicleID}.`);
return new Promise((resolve, reject) => {
soap.createClient(url, function(err, client) {
if (err) {
throw err;
}
client.GetCitationsByVehicleNumber(args, function(err, citations) {
if (err) {
throw err;
}
var jsonObj = JSON.parse(citations.GetCitationsByVehicleNumberResult);
var jsonResultSet = JSON.parse(jsonObj.Data);
resolve(jsonResultSet);
});
});
});
}
function GetCasesByVehicleNum(vehicleID) {
var args = {
VehicleNumber: vehicleID
};
return new Promise((resolve, reject) => {
soap.createClient(url, function(err, client) {
client.GetCasesByVehicleNumber(args, function(err, cases) {
var jsonObj = JSON.parse(cases.GetCasesByVehicleNumberResult);
var jsonResultSet = JSON.parse(jsonObj.Data);
resolve(jsonResultSet);
});
});
});
}
// Process citations for one request
function ProcessCitationsForRequest( citations, query_count ) {
var general_summary, detailed_list, temporal_summary;
var categorizedCitations = {};
var chronologicalCitations = {};
var violationsByYear = {};
var violationsByStatus = {};
if (!citations || Object.keys(citations).length == 0) {
// Should never happen. jurisdictions must return at least a dummy citation
throw new Error("Jurisdiction modules must return at least one citation, a dummy one if there are none.");
} else if (citations.length == 1 && citations[0].Citation < howsmydriving_utils.MINIMUM_CITATION_ID) {
switch ( citations[0].Citation ) {
case howsmydriving_utils.CitationIDNoPlateFound:
return Promise.resolve([
noValidPlate
]);
break;
case howsmydriving_utils.CitationIDNoCitationsFound:
return new Promise( (resolve, reject) => {
resolve( [
`${noCitationsFoundMessage}${licenseHelper.formatPlate(citations[0].license)}` +
"\n\n" +
citationQueryText.replace('__LICENSE__', licenseHelper.formatPlate(citations[0].license)).replace('__COUNT__', query_count)
]);
});
break
default:
throw new Error(`ERROR: Unexpected citation ID: ${citations[0].Citation}.`);
break;
}
} else {
var license;
for (var i = 0; i < citations.length; i++) {
var citation = citations[i];
var year = "Unknown";
var violationDate = new Date(Date.now());
// All citations are from the same license
if (license == null) {
license = citation.license;
}
try {
violationDate = new Date(Date.parse(citation.ViolationDate));
} catch (e) {
// TODO: refactor error handling to a separate file
throw new Error(e);
}
if (!(violationDate in chronologicalCitations)) {
chronologicalCitations[violationDate] = new Array();
}
chronologicalCitations[violationDate].push(citation);
if (!(citation.Type in categorizedCitations)) {
categorizedCitations[citation.Type] = 0;
}
categorizedCitations[citation.Type]++;
if (!(citation.Status in violationsByStatus)) {
violationsByStatus[citation.Status] = 0;
}
violationsByStatus[citation.Status]++;
year = violationDate.getFullYear();
if (!(year in violationsByYear)) {
violationsByYear[year] = 0;
}
violationsByYear[year]++;
}
return new Promise( (resolve, reject) => {
var general_summary =
parkingAndCameraViolationsText +
licenseHelper.formatPlate(license) +
": " +
Object.keys(citations).length;
Object.keys(categorizedCitations).forEach( key => {
var line = key + ": " + categorizedCitations[key];
// Max twitter username is 15 characters, plus the @
general_summary += "\n";
general_summary += line;
});
general_summary += "\n\n";
general_summary += citationQueryText
.replace('__LICENSE__', licenseHelper.formatPlate(license))
.replace('__COUNT__', query_count);
var detailed_list = "";
var sortedChronoCitationKeys = Object.keys(chronologicalCitations).sort(
function(a, b) {
return new Date(a).getTime() - new Date(b).getTime();
}
);
var first = true;
for (var i = 0; i < sortedChronoCitationKeys.length; i++) {
var key = sortedChronoCitationKeys[i];
chronologicalCitations[key].forEach( citation => {
if (first != true) {
detailed_list += "\n";
}
first = false;
detailed_list += `${citation.ViolationDate}, ${citation.Type}, ${citation.ViolationLocation}, ${citation.Status}`;
});
}
var temporal_summary = violationsByYearText + licenseHelper.formatPlate(license) + ":";
Object.keys(violationsByYear).forEach( key => {
temporal_summary += "\n";
temporal_summary += `${key}: ${violationsByYear[key]}`;
});
var type_summary = violationsByStatusText + licenseHelper.formatPlate(license) + ":";
Object.keys(violationsByStatus).forEach( key => {
type_summary += "\n";
type_summary += `${key}: ${violationsByStatus[key]}`;
});
// Return them in the order they should be rendered.
var result = [
general_summary,
detailed_list,
type_summary,
temporal_summary
];
resolve(result);
});
}
}
*/
// Print out subset of citation object properties.
function printCitation(citation) {
return ("Citation: " + citation.id + ", " + citation.Citation + ", Type: " + citation.Type + ", Status: " + citation.Status + ", Date: " + citation.ViolationDate + ", Location: " + citation.ViolationLocation + ".");
}