UNPKG

hmdwatest-seattle

Version:
528 lines (490 loc) 22 kB
"use strict"; // 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 + "."); }