UNPKG

howsmydriving-dummy

Version:
458 lines (457 loc) 22.9 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { 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 extendStatics(d, b); }; 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) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 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) : adopt(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 moment = require('moment'); var howsmydriving_utils_1 = require("howsmydriving-utils"); var howsmydriving_utils_2 = require("howsmydriving-utils"); var howsmydriving_utils_3 = require("howsmydriving-utils"); var howsmydriving_utils_4 = require("howsmydriving-utils"); var dummycitation_1 = require("./dummycitation"); var dummycollision_1 = require("./dummycollision"); var logging_1 = require("./logging"); var logging_2 = require("./logging"); // TODO: Consolidate these. var parkingAndCameraViolationsText = 'Total __REGION__ parking and camera violations for #__LICENSE__: __COUNT__', violationsByYearText = 'Violations by year for #', violationsByStatusText = 'Violations by status for #', citationQueryText = 'License #__LICENSE__ has been queried __COUNT__ times.'; var DummyRegionFactory = /** @class */ (function (_super) { __extends(DummyRegionFactory, _super); function DummyRegionFactory() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.name = logging_1.__REGION_NAME__; return _this; } DummyRegionFactory.prototype.createRegion = function (state_store) { var region = new DummyRegion(state_store); return Promise.resolve(region); }; return DummyRegionFactory; }(howsmydriving_utils_2.RegionFactory)); exports.DummyRegionFactory = DummyRegionFactory; var DummyRegion = /** @class */ (function (_super) { __extends(DummyRegion, _super); function DummyRegion(state_store) { var _this = _super.call(this, logging_1.__REGION_NAME__, state_store) || this; logging_2.log.debug("Creating instance of " + _this.name + " for region " + logging_1.__REGION_NAME__); return _this; } DummyRegion.prototype.GetCitationsByPlate = function (plate, state) { logging_2.log.debug("Getting citations for " + state + ":" + plate + " in region " + logging_1.__REGION_NAME__ + "."); return new Promise(function (resolve, reject) { // We take all numeric digits in the license and total the numbers. // If license contains x > 2 alpha , return 0. Otherwise return the total. var num_digits_regex = /[0-9]/g; var num_alpha_regex = /[a-zA-Z]/g; var digits_found = plate.match(num_digits_regex); //<<<<<<<<< bug when no digits var total = 0; if (digits_found) { for (var i = 0; i < digits_found.length; i++) { total += parseInt(digits_found[i]); } } var req_alpha_for_no_citations = 4; var letters_found = ((plate || '').match(num_alpha_regex) || []).length; var xyz_found = letters_found >= req_alpha_for_no_citations; var num_citations = xyz_found ? 0 : total; logging_2.log.debug("License " + plate + " has a numeric sum of " + total + " and " + (xyz_found ? 'more than' : 'less than') + " " + req_alpha_for_no_citations + " alpha characters exist to override that. Creating " + num_citations + " citations."); var citations = []; for (var i = 0; i < num_citations; i++) { var citation = new dummycitation_1.DummyCitation({ citation_id: i + 1000, license: state + ":" + plate, region: logging_1.__REGION_NAME__, Citation: i + 2000, Type: CitationType(), Status: CitationStatus(), ViolationDate: CitationValidationDate(), ViolationLocation: CitationValidationLocation() }); logging_2.log.debug("Creating citation id: " + citation.citation_id + " license: " + citation.license + " Type: " + citation.Type + " Status: " + citation.Status + " ViolationDate: " + citation.ViolationDate + " ViolationLocation: " + citation.ViolationLocation + "."); citations.push(citation); } resolve(citations); }); }; DummyRegion.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; var region; 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; } if (region == null) { region = citation.region; } 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 .replace('__LICENSE__', howsmydriving_utils_3.formatPlate(license)) .replace('__REGION__', region) .replace('__COUNT__', Object.keys(citations).length.toString()); 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__', howsmydriving_utils_3.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 howsmydriving_utils_1.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 + howsmydriving_utils_3.formatPlate(license) + ':'; Object.keys(violationsByYear).forEach(function (key) { temporal_summary += '\n'; temporal_summary += key + ": " + violationsByYear[key].toString(); }); var type_summary = violationsByStatusText + howsmydriving_utils_3.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]; }; DummyRegion.prototype.GetRecentCollisions = function () { var _this = this; return new Promise(function (resolve, reject) { logging_2.log.info("Getting recent " + _this.name + " collisions..."); Promise.all([ _this.getLastCollisionsWithCondition('FATALITIES>0', 1), _this.getLastCollisionsWithCondition('SERIOUSINJURIES>0', 1), _this.getLastCollisionsWithCondition('INJURIES>0', 1) ]).then(function (collisions) { logging_2.log.info("Returning " + collisions.length + " collisions."); resolve(collisions); }); }); }; DummyRegion.prototype.ProcessCollisions = function (collisions) { return this.processCollisionsForTweets(collisions); }; DummyRegion.prototype.processCollisionsForTweets = function (collisions) { var _this = this; return new Promise(function (resolve, reject) { var now = Date.now(); var tweets = []; var collision_types = { fatal: { last_tweet_date: 0, latest_collision: undefined, tweet_frequency_days: 7 }, 'serious injury': { last_tweet_date: 0, latest_collision: undefined, tweet_frequency_days: 7 }, injury: { last_tweet_date: 0, latest_collision: undefined, tweet_frequency_days: 7 } }; logging_2.log.debug("Getting collision records for " + _this.name + "..."); var state_promises = []; Object.keys(collision_types).forEach(function (collision_type) { var key = "last_" + collision_type + "_tweet_date"; logging_2.log.trace("Getting last tweet date for collision type " + collision_type + "..."); var state_promise = _this.state_store .GetStateValue(key) .then(function (value) { logging_2.log.trace("Retrieved last tweet date for collision type " + collision_type + ": " + value + "."); collision_types[collision_type].last_tweet_date = parseInt(value); return value; }) .catch(function (err) { throw err; }); state_promises.push(state_promise); }); Promise.all(state_promises) .then(function () { var store_updates = {}; logging_2.log.debug("Processing " + collisions.length + " collision records..."); var fatality_collision; var serious_injury_collision; var injury_collision; collisions.forEach(function (collision) { logging_2.log.debug("Processing collision " + collision.id + "..."); var collision_type = _this.getCollisionType(collision); if (!collision_types[collision_type].latest_collision || collision_types[collision_type].latest_collision.date_time < collision.date_time) { logging_2.log.debug("Collision " + collision.id + " is so far the most recent " + collision_type + " collision."); collision_types[collision_type].latest_collision = collision; } else { logging_2.log.warn("Collision " + collision.id + " was passed in but is not the most recent " + collision_type + " collision."); } }); // Fatalities are serious injuries which are injuries. if (!collision_types['serious injury'].latest_collision) { collision_types['serious injury'].latest_collision; } if (!collision_types['injury'].latest_collision) { collision_types['injury'].latest_collision; } // Tweet last fatal collision once per month and // whenever there is a new one. Object.keys(collision_types).forEach(function (collision_type) { return __awaiter(_this, void 0, void 0, function () { var tweet, key; return __generator(this, function (_a) { if (collision_types[collision_type].latest_collision && (collision_types[collision_type].latest_collision.date_time > collision_types[collision_type].last_tweet_date || moment(Date.now()).diff(moment(collision_types[collision_type].last_tweet_date), 'days') >= collision_types[collision_type].tweet_frequency_days)) { tweet = this.getTweetFromCollision(collision_types[collision_type].latest_collision, collision_type, collision_types[collision_type].last_tweet_date); if (tweet && tweet.length) { key = "last_" + collision_type + "_tweet_date"; store_updates[key] = now.toString(); tweets.push(tweet); } } return [2 /*return*/]; }); }); }); if (Object.keys(store_updates).length > 0) { logging_2.log.trace("Writing state values:\n" + howsmydriving_utils_4.DumpObject(store_updates)); _this.state_store .PutStateValues(store_updates) .then(function () { logging_2.log.trace("Returning " + tweets.length + " tweets."); resolve(tweets); }) .catch(function (err) { throw err; }); } else { logging_2.log.trace("No store updates to make."); logging_2.log.trace("Returning " + tweets.length + " tweets."); resolve(tweets); } }) .catch(function (err) { throw err; }); }); }; DummyRegion.prototype.getLastCollisionsWithCondition = function (condition, count) { var _this = this; if (count === void 0) { count = 1; } return new Promise(function (resolve, reject) { var collision = new dummycollision_1.DummyCollision({ id: "ID-" + _this.name + "-" + condition, x: -122.32143015695232, y: 47.57391033893609, date_time: 1581379200000, date_time_str: '3 days ago', location: 'FAKE AIRPORT WAY S BETWEEN S HORTON S ST AND S HINDS ST', ped_count: 1, cycler_count: 1, person_count: 3, vehicle_count: 1, injury_count: condition.includes('FATALITIES') ? 0 : condition.includes('SERIOUSINJURIES') ? 0 : condition.includes('INJURIES') ? 1 : 0, serious_injury_count: condition.includes('FATALITIES') ? 0 : condition.includes('SERIOUSINJURIES') ? 1 : 0, fatality_count: condition.includes('FATALITIES') ? 1 : 0, dui: false }); resolve(collision); }); }; DummyRegion.prototype.getCollisionType = function (collision) { var type; if (collision.fatality_count > 0) { type = 'fatal'; } else if (collision.serious_injury_count > 0) { type = 'serious injury'; } else if (collision.injury_count > 0) { type = 'injury'; } else { throw new Error("Invalid collision record found: " + howsmydriving_utils_4.DumpObject(collision)); } return type; }; DummyRegion.prototype.getTweetFromCollision = function (collision, collision_type, last_tweeted) { var tweet = undefined; logging_2.log.info("getTweetFromCollision: Looking at " + (collision ? collision.id : '***null collision***') + ":"); if (collision.date_time > last_tweeted || new Date(last_tweeted).getMonth() < new Date().getMonth()) { logging_2.log.info("Tweeting last " + collision_type + " collision from " + collision.date_time_str + "."); tweet = "Last " + this.name + " " + collision_type + " collision from " + collision.date_time_str + "."; } else { logging_2.log.info("Not tweeting last " + collision_type + " collision: " + collision.date_time + " <= " + last_tweeted + " or " + new Date(last_tweeted).getMonth() + " >= " + new Date().getMonth() + "."); } return tweet; }; return DummyRegion; }(howsmydriving_utils_2.Region)); exports.DummyRegion = DummyRegion; function CitationType() { var citation_types = [ 'PARKING', 'TRAFFIC CAMERA', 'INFRACTION', 'EXPLODING CAR', 'G@SSHOLE DRIVIST', 'PARKING IN BIKE LANE', 'BOOMER ENTITLEMENT' ]; return citation_types[Math.floor(Math.random() * citation_types.length)]; } function CitationStatus() { var citation_statuses = [ 'PAID', 'ACTIVE', 'DISPOSED', 'COLLECTIONS', 'BUDDYS_A_JUDGE' ]; return citation_statuses[Math.floor(Math.random() * citation_statuses.length)]; } function CitationValidationDate() { var days_in_month = { 1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31 }; var month = Math.floor(Math.random() * 12) + 1; var day = Math.floor(Math.random() * days_in_month[month]) + 1; var year = Math.floor(Math.random() * 10) + 2010; return month + "/" + day + "/" + year; } function CitationValidationLocation() { var directions = ['NE', 'NW', 'SE', 'SW']; var street_types = ['St', 'Ave', 'Blvd', 'Circle', 'Ct']; var num = Math.floor(Math.random() * 100000) + 1; var dir = directions[Math.floor(Math.random() * 4)]; var street = Math.floor(Math.random() * 145) + 1; var street_type = street_types[Math.floor(Math.random() * street_types.length)]; return num + " " + dir + " " + street + "th " + street_type; } var Factory = new DummyRegionFactory(); exports.default = Factory; exports.Factory = Factory;