howsmydriving-dummy
Version:
Dummy region plug-in for @HowsMyDrivingWA.
458 lines (457 loc) • 22.9 kB
JavaScript
;
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;