Census geocoder and partial installer
authorroot <root@server1.cccp.slightlycyberpunk.com>
Wed, 26 Feb 2025 00:13:35 +0000 (00:13 +0000)
committerroot <root@server1.cccp.slightlycyberpunk.com>
Wed, 26 Feb 2025 00:13:35 +0000 (00:13 +0000)
install.php [new file with mode: 0755]
install.sql [new file with mode: 0644]
js/common.js
settings-api.php
settings.php
taskRunner.php

diff --git a/install.php b/install.php
new file mode 100755 (executable)
index 0000000..9b02d3b
--- /dev/null
@@ -0,0 +1,228 @@
+<HTML>
+  <HEAD>
+    <TITLE>Welcome! | CCCP: Cargobike Community Canvassing Platform</TITLE>
+    <link href='https://fonts.googleapis.com/css?family=Stalinist One' rel='stylesheet'>
+    <STYLE>
+      body {
+        padding: 0em 2em;
+      }
+
+      h1 {
+        font-family:   'Stalinist One';
+        text-align:    center;
+        font-size:     3.5vw;
+        font-weight:   bold;
+        margin-top:    1em;
+        margin-bottom: 0em;
+      }
+
+      h2 {
+        font-style:   italic;
+        text-align:   center;
+        margin-top:   2em;
+        font-size:    2vw;
+        color:        #600;
+      }
+
+      h3 {
+        margin-top:   3em;
+      }
+
+      input {
+        width: 60%;
+      }
+
+      label {
+        font-weight: bold;
+      }
+
+      span.error {
+        margin-left: 1em;
+        font-weight: bold;
+        color:       red;
+      }
+    </STYLE>
+    <SCRIPT>
+      var states = [
+{name: "Alabama", lat: 32.60970074, lon: -85.48083948},
+{name: "Alaska", lat: 55.33970868, lon: -160.4971908},
+{name: "Arizona", lat: 33.58194114, lon: -112.1958238},
+{name: "Arkansas", lat: 36.05708722, lon: -90.50288436},
+{name: "California", lat: 37.87390139, lon: -122.271152},
+{name: "Colorado", lat: 39.54658999, lon: -107.3247},
+{name: "Connecticut", lat: 41.3555235, lon: -72.10002832},
+{name: "Delaware", lat: 39.15808657, lon: -75.524703},
+{name: "District Of Columbia", lat: 38.89954938, lon: -77.00941858},
+{name: "Florida", lat: 28.02191876, lon: -81.73300623},
+{name: "Georgia", lat: 34.76972394, lon: -84.97030217},
+{name: "Hawaii", lat: 21.98151227, lon: -159.3710063},
+{name: "Idaho", lat: 42.53581321, lon: -113.7918763},
+{name: "Illinois", lat: 40.94777061, lon: -90.37108362},
+{name: "Indiana", lat: 39.82889833, lon: -84.89028121},
+{name: "Iowa", lat: 41.66108624, lon: -91.52997929},
+{name: "Kansas", lat: 38.06549176, lon: -97.92349085},
+{name: "Kentucky", lat: 36.86548749, lon: -87.4886239},
+{name: "Louisiana", lat: 30.27495953, lon: -89.78109379},
+{name: "Maine", lat: 44.5518917, lon: -69.64578536},
+{name: "Maryland", lat: 38.60305585, lon: -76.93893193},
+{name: "Massachusetts", lat: 41.6561253, lon: -70.94469833},
+{name: "Michigan", lat: 42.11663983, lon: -86.45419092},
+{name: "Minnesota", lat: 44.29048647, lon: -93.26801274},
+{name: "Mississippi", lat: 34.96891075, lon: -90.00345748},
+{name: "Missouri", lat: 39.09111391, lon: -94.41528121},
+{name: "Montana", lat: 45.731768, lon: -107.612486},
+{name: "Nebraska", lat: 40.70070559, lon: -99.08114628},
+{name: "Nevada", lat: 38.06699038, lon: -117.2289791},
+{name: "New Hampshire", lat: 42.99599184, lon: -71.45528731},
+{name: "New Jersey", lat: 40.91999453, lon: -74.17000533},
+{name: "New Mexico", lat: 32.26109153, lon: -107.7557848},
+{name: "New York", lat: 43.08296328, lon: -73.78501591},
+{name: "North Carolina", lat: 35.93799888, lon: -77.79076624},
+{name: "North Dakota", lat: 48.11221702, lon: -98.85968693},
+{name: "Ohio", lat: 39.94028688, lon: -82.01332503},
+{name: "Oklahoma", lat: 36.74720013, lon: -95.98058618},
+{name: "Oregon", lat: 44.05194806, lon: -122.9780339},
+{name: "Pennsylvania", lat: 40.75194277, lon: -80.31942326},
+{name: "Rhode Island", lat: 41.49039899, lon: -71.31335799},
+{name: "South Carolina", lat: 34.94942873, lon: -81.93227055},
+{name: "South Dakota", lat: 44.35084454, lon: -103.7657699},
+{name: "Tennessee", lat: 36.31332481, lon: -82.35361434},
+{name: "Texas", lat: 30.67418581, lon: -96.36968388},
+{name: "Utah", lat: 38.77247703, lon: -112.0832984},
+{name: "Vermont", lat: 44.25997154, lon: -72.57581323},
+{name: "Virginia", lat: 37.22941876, lon: -80.41419784},
+{name: "Washington", lat: 46.21137697, lon: -119.1360979},
+{name: "West Virginia", lat: 39.26665875, lon: -81.56164718},
+{name: "Wisconsin", lat: 43.75082949, lon: -87.71442407},
+{name: "Wyoming", lat: 42.83300437, lon: -108.7325985},
+];
+
+      function stateSelect() {
+        var selected = document.getElementById("state").value;
+        for(var i = 0; i < states.length; i++) {
+          if(selected == states[i].name) {
+            document.getElementById("lat").value = states[i].lat;
+            document.getElementById("lon").value = states[i].lon;
+          }
+        }
+      }
+
+      function init() {
+        var select = document.getElementById("state");
+        for(var i = 0; i < states.length; i++) {
+          var option = document.createElement("option");
+          option.innerText = states[i].name;
+          option.value     = states[i].name;
+          select.appendChild(option);
+        }
+
+        if( document.getElementById("baseurl").value == "" ) {
+          var loc = window.location.href;
+          document.getElementById("baseurl").value = loc.replace("install.php", "");
+        }
+      }
+    </SCRIPT>
+  </HEAD>
+  <?php
+  // Check if it is already installed
+  if( file_exists("./config.php") ) {
+    echo "ERROR: Already installed! (If you believe you are seeing this in error or need to reinstall, please delete the config.php file)"
+         .PHP_EOL."</BODY></HTML>".PHP_EOL;
+    die();
+  }
+  ?>
+  <BODY>
+<?php
+$dbbase = "CCCP";
+
+if( isset($_POST['baseurl']) ) {
+  $baseurl= $_POST['baseurl'];
+
+  // Database
+  $dbuser = $_POST['dbuser'];
+  $dbpass = $_POST['dbpass'];
+  $dbbase = $_POST['dbbase'];
+  $dbtres = "";
+  $dbcres = "";
+
+  try {
+    $dbh = new PDO("mysql:host=localhost;dbname=".$dbbase, $dbuser, $dbpass);
+    print_r($dbh);
+    print_r($dbh->errorInfo());
+  } catch(Exception $ex) {
+    $dbtres = $ex->errorInfo[2];
+    $dbcres = "error";
+  }
+
+
+}
+
+
+print_r($_POST);
+?>
+    <H1>Welcome to the CCCP<br/>Installer!</H1>
+
+    <form action="install.php" method="POST" >
+      <H2>THE BASICS</H2>
+      <p>Please enter the root domain name and directory of this server.</p>
+      <label for="baseurl">Base URL:</label>
+      <input type="text" name="baseurl" id="baseurl" value="<?php echo $baseurl; ?>"></input>
+
+      <p>Please enter your current location (this will be used as the default center for displaying maps)</p>
+      <label for="state">State:</label>
+      <select id="state" name="state" onchange="javascript: stateSelect();">
+        <option></option>
+      </select><br/>
+      <label for="lat">Latitude: </label>
+      <input type="text" name="lat" id="lat"></input><br/>
+      <label for="lon">Latitude: </label>
+      <input type="text" name="lon" id="lon"></input>
+      <input type="submit" />
+
+      <H2>DATABASE</H2>
+      <p>Please ensure the server has a MariaDB database configured and enter the connection details below.
+         If the given database does not exist yet it will be created.
+         Note that these connection details WILL BE STORED IN PLAINTEXT in config.php
+         Please ensure this file is not externally accessible.
+         (I recommend not allowing the database to be externally accessible either.)</p>
+
+      <input type="hidden" name="section" value="database" />
+      <label for="dbuser">Username:</label>
+      <input type="text" name="dbuser" id="dbuser" value="<?php echo $dbuser;?>"></input><br/>
+      <label for="dbpass">Username:</label>
+      <input type="password" name="dbpass" id="dbpass" value="<?php echo $dbpass;?>"></input><br/>
+      <label for="dbbase">Database Name:</label>
+      <input type="text" name="dbbase" id="dbbase" value="<?php echo $dbbase;?>"></input>
+      <input type="submit" value="Save and Test" name="dbsubmit" />
+      <span class="dbtres <?php echo $dbcres; ?>"><?php echo $dbtres; ?></span>
+    </form>
+
+    <H2>WEB SERVER</H2>
+    <p>If you are reading this, the web server seems to be up and running!
+       However, there are a few changes you may want to make in the configuration.
+       These are assuming you are using Apache2 -- there are probably similar settings
+         on Nginx or others.</p>
+    <ul>In your php.ini (ex: /etc/php/8.2/apache2/php.ini)
+      <li>max_input_time - Increase this to prevent large queries, data fetches, and file uploads from timing out</li>
+      <li>post_max_size - Increase the maximum upload size for voter and census data</li>
+      <li>upload_max_filesize - Increase the maximum file upload size for voter and census data uploads</li>
+    </ul>
+    <p>Also ensure to configure SSL, as most browsers won't allow GPS without it. Your base URL above should start with https!</p>
+
+    <H2>ADDITIONAL</H2>
+    <p>Configure a cron job or other scheduled task to execute the following command once per hour:</p>
+    <pre>
+# m h  dom mon dow   command
+0 * * * * nohup php <?php echo dirname(__FILE__); ?>/taskRunner.php 1>><?php echo dirname(__FILE__); ?>/taskRunner.log 2>&1
+    </pre>
+    <script>
+    init();
+    </script>
+  </BODY>
+</HTML>
+
+<?php
+/*
+mariadb-dump -u root -p -A --no-data > install.sql
+*/
+?>
diff --git a/install.sql b/install.sql
new file mode 100644 (file)
index 0000000..db2e9f1
--- /dev/null
@@ -0,0 +1,1109 @@
+-- MariaDB dump 10.19  Distrib 10.11.6-MariaDB, for debian-linux-gnu (x86_64)
+--
+-- Host: localhost    Database: 
+-- ------------------------------------------------------
+-- Server version      10.11.6-MariaDB-0+deb12u1
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8mb4 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Current Database: `CCCP`
+--
+
+CREATE DATABASE /*!32312 IF NOT EXISTS*/ `CCCP` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */;
+
+USE `CCCP`;
+
+--
+-- Table structure for table `canvassGroups`
+--
+
+DROP TABLE IF EXISTS `canvassGroups`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `canvassGroups` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `userId` int(11) NOT NULL,
+  `leadId` int(11) DEFAULT NULL,
+  `canvassId` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=82 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `canvassResults`
+--
+
+DROP TABLE IF EXISTS `canvassResults`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `canvassResults` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `voterId` int(11) NOT NULL,
+  `canvassId` int(11) DEFAULT NULL,
+  `userId` int(11) DEFAULT NULL,
+  `timestamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `contactStatus` int(11) DEFAULT NULL,
+  `contactMethod` varchar(128) DEFAULT NULL,
+  `estSupportPct` int(11) DEFAULT 0,
+  `notes` longtext DEFAULT NULL,
+  `correctionsNeeded` int(11) DEFAULT 0,
+  `priority` int(11) DEFAULT 0,
+  `noContact` int(11) DEFAULT 0,
+  `totalContacts` int(11) DEFAULT 0,
+  `lastLoc` text DEFAULT NULL,
+  `voterString` text DEFAULT NULL,
+  `json` longtext DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_cr_vid` (`voterId`),
+  KEY `fk_cr_cid` (`canvassId`),
+  KEY `fk_cr_uid` (`userId`),
+  CONSTRAINT `fk_cr_cid` FOREIGN KEY (`canvassId`) REFERENCES `canvasses` (`id`),
+  CONSTRAINT `fk_cr_uid` FOREIGN KEY (`userId`) REFERENCES `users` (`id`),
+  CONSTRAINT `fk_cr_vid` FOREIGN KEY (`voterId`) REFERENCES `voters` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=79 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `canvasses`
+--
+
+DROP TABLE IF EXISTS `canvasses`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `canvasses` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `turfId` int(11) NOT NULL,
+  `name` varchar(128) DEFAULT NULL,
+  `start` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `end` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `totalContacts` int(11) DEFAULT 0,
+  `lastLoc` text DEFAULT NULL,
+  `voterString` text DEFAULT NULL,
+  `json` longtext DEFAULT NULL,
+  `script` text DEFAULT NULL,
+  `hidden` int(1) DEFAULT 0,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `config`
+--
+
+DROP TABLE IF EXISTS `config`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `config` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `configName` varchar(128) DEFAULT NULL,
+  `configValue` varchar(512) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `configName` (`configName`)
+) ENGINE=InnoDB AUTO_INCREMENT=5693 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `customLists`
+--
+
+DROP TABLE IF EXISTS `customLists`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `customLists` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(128) DEFAULT NULL COMMENT 'List Name',
+  `priority` int(11) DEFAULT NULL COMMENT 'List Priority',
+  `icon` varchar(128) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `customVoter`
+--
+
+DROP TABLE IF EXISTS `customVoter`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `customVoter` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `voterId` int(11) DEFAULT NULL,
+  `addressId` int(11) DEFAULT NULL,
+  `listId` int(11) DEFAULT NULL,
+  `extras` text DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_cv_vid` (`voterId`),
+  KEY `fk_cv_aid` (`addressId`),
+  KEY `fk_cv_lid` (`listId`),
+  CONSTRAINT `fk_cv_aid` FOREIGN KEY (`addressId`) REFERENCES `voterAddresses` (`id`),
+  CONSTRAINT `fk_cv_lid` FOREIGN KEY (`listId`) REFERENCES `customLists` (`id`),
+  CONSTRAINT `fk_cv_vid` FOREIGN KEY (`voterId`) REFERENCES `voters` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=10367 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `demographicArea`
+--
+
+DROP TABLE IF EXISTS `demographicArea`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `demographicArea` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `type` varchar(128) DEFAULT NULL COMMENT 'Area type (precinct, block, etc)',
+  `typeId` int(11) DEFAULT NULL COMMENT 'Area numeric ID',
+  `precinctId` int(11) DEFAULT NULL COMMENT 'Precinct ID (if applicable)',
+  `minLat` float DEFAULT NULL COMMENT 'Minimum Latitude',
+  `minLon` float DEFAULT NULL COMMENT 'Minimum Longitude',
+  `maxLat` float DEFAULT NULL COMMENT 'Maximum Latitude',
+  `maxLon` float DEFAULT NULL COMMENT 'Maximum Longitude',
+  `json` longtext DEFAULT NULL,
+  `updatedBy` int(11) DEFAULT NULL,
+  `geometry` geometry DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `type` (`type`,`typeId`),
+  KEY `idx_da_t` (`type`)
+) ENGINE=InnoDB AUTO_INCREMENT=627972 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `demographicDefinitions`
+--
+
+DROP TABLE IF EXISTS `demographicDefinitions`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `demographicDefinitions` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `code` varchar(128) DEFAULT NULL COMMENT 'Definition type code',
+  `type` varchar(1) DEFAULT NULL COMMENT 'Definition type (total, relative, absolute)',
+  `description` text DEFAULT NULL COMMENT 'Definition description',
+  `sourceName` varchar(128) DEFAULT NULL COMMENT 'Descriptive name of the source file',
+  `sourceId` varchar(128) DEFAULT NULL COMMENT 'Filename/ID of the source file',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=4297 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `demographicStats`
+--
+
+DROP TABLE IF EXISTS `demographicStats`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `demographicStats` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `areaId` int(11) DEFAULT NULL COMMENT 'Area ID foreign key',
+  `definitionId` int(11) DEFAULT NULL COMMENT 'Definition ID foreign key',
+  `value` varchar(128) DEFAULT NULL COMMENT 'Statistic value',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `areaId` (`areaId`,`definitionId`),
+  KEY `fk_ds_did` (`definitionId`),
+  KEY `idx_ds_v` (`value`),
+  CONSTRAINT `fk_ds_aid` FOREIGN KEY (`areaId`) REFERENCES `demographicArea` (`id`),
+  CONSTRAINT `fk_ds_did` FOREIGN KEY (`definitionId`) REFERENCES `demographicDefinitions` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=24823503 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `geocodeResults`
+--
+
+DROP TABLE IF EXISTS `geocodeResults`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `geocodeResults` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `engine` varchar(128) NOT NULL,
+  `addressId` int(11) NOT NULL,
+  `latitude` float DEFAULT NULL,
+  `longitude` float DEFAULT NULL,
+  `updateDate` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
+  `response` longtext DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_gr_aid` (`addressId`)
+) ENGINE=InnoDB AUTO_INCREMENT=1233726 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `invites`
+--
+
+DROP TABLE IF EXISTS `invites`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `invites` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `userId` int(11) NOT NULL,
+  `canvassId` int(11) DEFAULT NULL,
+  `token` text NOT NULL,
+  `expiry` timestamp NULL DEFAULT (current_timestamp() + interval 24 hour),
+  PRIMARY KEY (`id`),
+  KEY `fk_iv_uid` (`userId`),
+  KEY `fk_iv_cid` (`canvassId`),
+  CONSTRAINT `fk_iv_cid` FOREIGN KEY (`canvassId`) REFERENCES `canvasses` (`id`),
+  CONSTRAINT `fk_iv_uid` FOREIGN KEY (`userId`) REFERENCES `users` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=86 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `scriptMap`
+--
+
+DROP TABLE IF EXISTS `scriptMap`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `scriptMap` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `canvassId` int(11) NOT NULL,
+  `promptId` int(11) DEFAULT NULL,
+  `position` int(11) DEFAULT 0,
+  PRIMARY KEY (`id`),
+  KEY `fk_sm_cid` (`canvassId`),
+  KEY `fk_sm_pid` (`promptId`),
+  CONSTRAINT `fk_sm_cid` FOREIGN KEY (`canvassId`) REFERENCES `canvasses` (`id`),
+  CONSTRAINT `fk_sm_pid` FOREIGN KEY (`promptId`) REFERENCES `scriptPrompts` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=79 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `scriptPrompts`
+--
+
+DROP TABLE IF EXISTS `scriptPrompts`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `scriptPrompts` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `text` longtext NOT NULL,
+  `notes` longtext DEFAULT NULL,
+  `inputJson` longtext DEFAULT NULL,
+  `resourcesJson` longtext DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=79 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `tasks`
+--
+
+DROP TABLE IF EXISTS `tasks`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `tasks` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `taskName` varchar(128) DEFAULT NULL,
+  `taskArgs` varchar(256) DEFAULT NULL,
+  `taskStart` timestamp NULL DEFAULT NULL,
+  `taskUpdate` timestamp NULL DEFAULT NULL,
+  `taskComplete` timestamp NULL DEFAULT NULL,
+  `taskStep` int(11) DEFAULT NULL,
+  `taskLastStep` int(11) DEFAULT NULL,
+  `taskPercent` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=778 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `turf`
+--
+
+DROP TABLE IF EXISTS `turf`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `turf` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(128) DEFAULT NULL,
+  `json` longtext DEFAULT NULL,
+  `geometry` geometry DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `users`
+--
+
+DROP TABLE IF EXISTS `users`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `users` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `username` varchar(128) NOT NULL,
+  `passhash` varchar(512) NOT NULL,
+  `permissions` int(11) NOT NULL,
+  `realName` text DEFAULT NULL,
+  `phone` text DEFAULT NULL,
+  `email` text DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `voterAddresses`
+--
+
+DROP TABLE IF EXISTS `voterAddresses`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `voterAddresses` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `addressLine1` varchar(128) DEFAULT NULL COMMENT 'Address Line 1',
+  `addressLine2` varchar(128) DEFAULT NULL COMMENT 'Address Line 2',
+  `city` varchar(128) DEFAULT NULL COMMENT 'City',
+  `zip` varchar(5) DEFAULT NULL COMMENT 'Zipcode',
+  `zip4` varchar(4) DEFAULT NULL COMMENT 'Additional zip (+4)',
+  `precinct` varchar(32) DEFAULT NULL COMMENT 'Registered Precinct',
+  `natConDistrict` varchar(32) DEFAULT NULL COMMENT 'Congressional District',
+  `stSenDistrict` varchar(32) DEFAULT NULL COMMENT 'State Senate District',
+  `stHouDistrict` varchar(32) DEFAULT NULL COMMENT 'State House District',
+  `wardCouncil` varchar(32) DEFAULT NULL COMMENT 'Ward / Council',
+  `wardDistrict` varchar(32) DEFAULT NULL COMMENT 'Ward District',
+  `schoolDistrict` varchar(32) DEFAULT NULL COMMENT 'School Committee District',
+  `specialDistrict` varchar(32) DEFAULT NULL COMMENT 'Special District',
+  `lastUpdated` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE current_timestamp(),
+  `latitude` float DEFAULT NULL,
+  `longitude` float DEFAULT NULL,
+  `state` varchar(2) DEFAULT NULL COMMENT 'State code',
+  `parentGeocode` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=8312181 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `voterHistory`
+--
+
+DROP TABLE IF EXISTS `voterHistory`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `voterHistory` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `voterId` int(11) DEFAULT NULL,
+  `year` int(11) DEFAULT NULL,
+  `election` varchar(128) DEFAULT NULL,
+  `voted` int(11) DEFAULT NULL,
+  `lastUpdated` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE current_timestamp(),
+  PRIMARY KEY (`id`),
+  KEY `fk_vh_vid` (`voterId`),
+  CONSTRAINT `fk_vh_vid` FOREIGN KEY (`voterId`) REFERENCES `voters` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2890942 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `voters`
+--
+
+DROP TABLE IF EXISTS `voters`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `voters` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `firstName` varchar(128) DEFAULT NULL COMMENT 'First Name',
+  `middleName` varchar(128) DEFAULT NULL COMMENT 'Middle Name',
+  `lastName` varchar(128) DEFAULT NULL COMMENT 'Last Name',
+  `birthYear` int(11) DEFAULT NULL COMMENT 'Birth Year',
+  `status` varchar(32) DEFAULT NULL COMMENT 'Registration Status',
+  `party` varchar(32) DEFAULT NULL COMMENT 'Registered Party',
+  `phone` varchar(16) DEFAULT NULL COMMENT 'Phone Number',
+  `email` varchar(256) DEFAULT NULL COMMENT 'Email Address',
+  `stateVoterId` varchar(32) DEFAULT NULL COMMENT 'Voter ID',
+  `addressId` int(11) DEFAULT NULL,
+  `registrationDate` datetime DEFAULT NULL,
+  `lastUpdated` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE current_timestamp(),
+  PRIMARY KEY (`id`),
+  KEY `fk_v_vaid` (`addressId`),
+  CONSTRAINT `fk_v_vaid` FOREIGN KEY (`addressId`) REFERENCES `voterAddresses` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4751506 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Current Database: `mysql`
+--
+
+CREATE DATABASE /*!32312 IF NOT EXISTS*/ `mysql` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */;
+
+USE `mysql`;
+
+--
+-- Table structure for table `general_log`
+--
+
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE IF NOT EXISTS `general_log` (
+  `event_time` timestamp(6) NOT NULL DEFAULT current_timestamp(6) ON UPDATE current_timestamp(6),
+  `user_host` mediumtext NOT NULL,
+  `thread_id` bigint(21) unsigned NOT NULL,
+  `server_id` int(10) unsigned NOT NULL,
+  `command_type` varchar(64) NOT NULL,
+  `argument` mediumtext NOT NULL
+) ENGINE=CSV DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci COMMENT='General log';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `slow_log`
+--
+
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE IF NOT EXISTS `slow_log` (
+  `start_time` timestamp(6) NOT NULL DEFAULT current_timestamp(6) ON UPDATE current_timestamp(6),
+  `user_host` mediumtext NOT NULL,
+  `query_time` time(6) NOT NULL,
+  `lock_time` time(6) NOT NULL,
+  `rows_sent` int(11) NOT NULL,
+  `rows_examined` int(11) NOT NULL,
+  `db` varchar(512) NOT NULL,
+  `last_insert_id` int(11) NOT NULL,
+  `insert_id` int(11) NOT NULL,
+  `server_id` int(10) unsigned NOT NULL,
+  `sql_text` mediumtext NOT NULL,
+  `thread_id` bigint(21) unsigned NOT NULL,
+  `rows_affected` int(11) NOT NULL
+) ENGINE=CSV DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci COMMENT='Slow log';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `column_stats`
+--
+
+DROP TABLE IF EXISTS `column_stats`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `column_stats` (
+  `db_name` varchar(64) NOT NULL,
+  `table_name` varchar(64) NOT NULL,
+  `column_name` varchar(64) NOT NULL,
+  `min_value` varbinary(255) DEFAULT NULL,
+  `max_value` varbinary(255) DEFAULT NULL,
+  `nulls_ratio` decimal(12,4) DEFAULT NULL,
+  `avg_length` decimal(12,4) DEFAULT NULL,
+  `avg_frequency` decimal(12,4) DEFAULT NULL,
+  `hist_size` tinyint(3) unsigned DEFAULT NULL,
+  `hist_type` enum('SINGLE_PREC_HB','DOUBLE_PREC_HB','JSON_HB') DEFAULT NULL,
+  `histogram` longblob DEFAULT NULL,
+  PRIMARY KEY (`db_name`,`table_name`,`column_name`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin PAGE_CHECKSUM=1 TRANSACTIONAL=0 COMMENT='Statistics on Columns';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `columns_priv`
+--
+
+DROP TABLE IF EXISTS `columns_priv`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `columns_priv` (
+  `Host` char(255) NOT NULL DEFAULT '',
+  `Db` char(64) NOT NULL DEFAULT '',
+  `User` char(128) NOT NULL DEFAULT '',
+  `Table_name` char(64) NOT NULL DEFAULT '',
+  `Column_name` char(64) NOT NULL DEFAULT '',
+  `Timestamp` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
+  `Column_priv` set('Select','Insert','Update','References') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '',
+  PRIMARY KEY (`Host`,`Db`,`User`,`Table_name`,`Column_name`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Column privileges';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `db`
+--
+
+DROP TABLE IF EXISTS `db`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `db` (
+  `Host` char(255) NOT NULL DEFAULT '',
+  `Db` char(64) NOT NULL DEFAULT '',
+  `User` char(128) NOT NULL DEFAULT '',
+  `Select_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Insert_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Update_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Delete_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Create_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Drop_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Grant_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `References_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Index_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Alter_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Create_tmp_table_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Lock_tables_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Create_view_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Show_view_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Create_routine_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Alter_routine_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Execute_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Event_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Trigger_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  `Delete_history_priv` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  PRIMARY KEY (`Host`,`Db`,`User`),
+  KEY `User` (`User`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Database privileges';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `event`
+--
+
+DROP TABLE IF EXISTS `event`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `event` (
+  `db` char(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL DEFAULT '',
+  `name` char(64) NOT NULL DEFAULT '',
+  `body` longblob NOT NULL,
+  `definer` varchar(384) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL DEFAULT '',
+  `execute_at` datetime DEFAULT NULL,
+  `interval_value` int(11) DEFAULT NULL,
+  `interval_field` enum('YEAR','QUARTER','MONTH','DAY','HOUR','MINUTE','WEEK','SECOND','MICROSECOND','YEAR_MONTH','DAY_HOUR','DAY_MINUTE','DAY_SECOND','HOUR_MINUTE','HOUR_SECOND','MINUTE_SECOND','DAY_MICROSECOND','HOUR_MICROSECOND','MINUTE_MICROSECOND','SECOND_MICROSECOND') DEFAULT NULL,
+  `created` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
+  `modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `last_executed` datetime DEFAULT NULL,
+  `starts` datetime DEFAULT NULL,
+  `ends` datetime DEFAULT NULL,
+  `status` enum('ENABLED','DISABLED','SLAVESIDE_DISABLED') NOT NULL DEFAULT 'ENABLED',
+  `on_completion` enum('DROP','PRESERVE') NOT NULL DEFAULT 'DROP',
+  `sql_mode` set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES','IGNORE_SPACE','IGNORE_BAD_TABLE_OPTIONS','ONLY_FULL_GROUP_BY','NO_UNSIGNED_SUBTRACTION','NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB','NO_KEY_OPTIONS','NO_TABLE_OPTIONS','NO_FIELD_OPTIONS','MYSQL323','MYSQL40','ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES','STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES','ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER','HIGH_NOT_PRECEDENCE','NO_ENGINE_SUBSTITUTION','PAD_CHAR_TO_FULL_LENGTH','EMPTY_STRING_IS_NULL','SIMULTANEOUS_ASSIGNMENT','TIME_ROUND_FRACTIONAL') NOT NULL DEFAULT '',
+  `comment` char(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL DEFAULT '',
+  `originator` int(10) unsigned NOT NULL,
+  `time_zone` char(64) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'SYSTEM',
+  `character_set_client` char(32) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL,
+  `collation_connection` char(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL,
+  `db_collation` char(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL,
+  `body_utf8` longblob DEFAULT NULL,
+  PRIMARY KEY (`db`,`name`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Events';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `func`
+--
+
+DROP TABLE IF EXISTS `func`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `func` (
+  `name` char(64) NOT NULL DEFAULT '',
+  `ret` tinyint(1) NOT NULL DEFAULT 0,
+  `dl` char(128) NOT NULL DEFAULT '',
+  `type` enum('function','aggregate') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
+  PRIMARY KEY (`name`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='User defined functions';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `global_priv`
+--
+
+DROP TABLE IF EXISTS `global_priv`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `global_priv` (
+  `Host` char(255) NOT NULL DEFAULT '',
+  `User` char(128) NOT NULL DEFAULT '',
+  `Priv` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '{}' CHECK (json_valid(`Priv`)),
+  PRIMARY KEY (`Host`,`User`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Users and global privileges';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `gtid_slave_pos`
+--
+
+DROP TABLE IF EXISTS `gtid_slave_pos`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `gtid_slave_pos` (
+  `domain_id` int(10) unsigned NOT NULL,
+  `sub_id` bigint(20) unsigned NOT NULL,
+  `server_id` int(10) unsigned NOT NULL,
+  `seq_no` bigint(20) unsigned NOT NULL,
+  PRIMARY KEY (`domain_id`,`sub_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci COMMENT='Replication slave GTID position';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `help_category`
+--
+
+DROP TABLE IF EXISTS `help_category`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `help_category` (
+  `help_category_id` smallint(5) unsigned NOT NULL,
+  `name` char(64) NOT NULL,
+  `parent_category_id` smallint(5) unsigned DEFAULT NULL,
+  `url` text NOT NULL,
+  PRIMARY KEY (`help_category_id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci PAGE_CHECKSUM=1 TRANSACTIONAL=0 COMMENT='help categories';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `help_keyword`
+--
+
+DROP TABLE IF EXISTS `help_keyword`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `help_keyword` (
+  `help_keyword_id` int(10) unsigned NOT NULL,
+  `name` char(64) NOT NULL,
+  PRIMARY KEY (`help_keyword_id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci PAGE_CHECKSUM=1 TRANSACTIONAL=0 COMMENT='help keywords';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `help_relation`
+--
+
+DROP TABLE IF EXISTS `help_relation`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `help_relation` (
+  `help_topic_id` int(10) unsigned NOT NULL,
+  `help_keyword_id` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`help_keyword_id`,`help_topic_id`),
+  KEY `help_topic_id` (`help_topic_id`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci PAGE_CHECKSUM=1 TRANSACTIONAL=0 COMMENT='keyword-topic relation';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `help_topic`
+--
+
+DROP TABLE IF EXISTS `help_topic`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `help_topic` (
+  `help_topic_id` int(10) unsigned NOT NULL,
+  `name` char(64) NOT NULL,
+  `help_category_id` smallint(5) unsigned NOT NULL,
+  `description` text NOT NULL,
+  `example` text NOT NULL,
+  `url` text NOT NULL,
+  PRIMARY KEY (`help_topic_id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci PAGE_CHECKSUM=1 TRANSACTIONAL=0 COMMENT='help topics';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `index_stats`
+--
+
+DROP TABLE IF EXISTS `index_stats`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `index_stats` (
+  `db_name` varchar(64) NOT NULL,
+  `table_name` varchar(64) NOT NULL,
+  `index_name` varchar(64) NOT NULL,
+  `prefix_arity` int(11) unsigned NOT NULL,
+  `avg_frequency` decimal(12,4) DEFAULT NULL,
+  PRIMARY KEY (`db_name`,`table_name`,`index_name`,`prefix_arity`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin PAGE_CHECKSUM=1 TRANSACTIONAL=0 COMMENT='Statistics on Indexes';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `innodb_index_stats`
+--
+
+DROP TABLE IF EXISTS `innodb_index_stats`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `innodb_index_stats` (
+  `database_name` varchar(64) NOT NULL,
+  `table_name` varchar(199) NOT NULL,
+  `index_name` varchar(64) NOT NULL,
+  `last_update` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
+  `stat_name` varchar(64) NOT NULL,
+  `stat_value` bigint(20) unsigned NOT NULL,
+  `sample_size` bigint(20) unsigned DEFAULT NULL,
+  `stat_description` varchar(1024) NOT NULL,
+  PRIMARY KEY (`database_name`,`table_name`,`index_name`,`stat_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin STATS_PERSISTENT=0;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `innodb_table_stats`
+--
+
+DROP TABLE IF EXISTS `innodb_table_stats`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `innodb_table_stats` (
+  `database_name` varchar(64) NOT NULL,
+  `table_name` varchar(199) NOT NULL,
+  `last_update` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
+  `n_rows` bigint(20) unsigned NOT NULL,
+  `clustered_index_size` bigint(20) unsigned NOT NULL,
+  `sum_of_other_index_sizes` bigint(20) unsigned NOT NULL,
+  PRIMARY KEY (`database_name`,`table_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin STATS_PERSISTENT=0;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `plugin`
+--
+
+DROP TABLE IF EXISTS `plugin`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `plugin` (
+  `name` varchar(64) NOT NULL DEFAULT '',
+  `dl` varchar(128) NOT NULL DEFAULT '',
+  PRIMARY KEY (`name`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='MySQL plugins';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `proc`
+--
+
+DROP TABLE IF EXISTS `proc`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `proc` (
+  `db` char(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL DEFAULT '',
+  `name` char(64) NOT NULL DEFAULT '',
+  `type` enum('FUNCTION','PROCEDURE','PACKAGE','PACKAGE BODY') NOT NULL,
+  `specific_name` char(64) NOT NULL DEFAULT '',
+  `language` enum('SQL') NOT NULL DEFAULT 'SQL',
+  `sql_data_access` enum('CONTAINS_SQL','NO_SQL','READS_SQL_DATA','MODIFIES_SQL_DATA') NOT NULL DEFAULT 'CONTAINS_SQL',
+  `is_deterministic` enum('YES','NO') NOT NULL DEFAULT 'NO',
+  `security_type` enum('INVOKER','DEFINER') NOT NULL DEFAULT 'DEFINER',
+  `param_list` blob NOT NULL,
+  `returns` longblob NOT NULL,
+  `body` longblob NOT NULL,
+  `definer` varchar(384) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL DEFAULT '',
+  `created` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
+  `modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `sql_mode` set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES','IGNORE_SPACE','IGNORE_BAD_TABLE_OPTIONS','ONLY_FULL_GROUP_BY','NO_UNSIGNED_SUBTRACTION','NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB','NO_KEY_OPTIONS','NO_TABLE_OPTIONS','NO_FIELD_OPTIONS','MYSQL323','MYSQL40','ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES','STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES','ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER','HIGH_NOT_PRECEDENCE','NO_ENGINE_SUBSTITUTION','PAD_CHAR_TO_FULL_LENGTH','EMPTY_STRING_IS_NULL','SIMULTANEOUS_ASSIGNMENT','TIME_ROUND_FRACTIONAL') NOT NULL DEFAULT '',
+  `comment` text CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL,
+  `character_set_client` char(32) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL,
+  `collation_connection` char(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL,
+  `db_collation` char(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL,
+  `body_utf8` longblob DEFAULT NULL,
+  `aggregate` enum('NONE','GROUP') NOT NULL DEFAULT 'NONE',
+  PRIMARY KEY (`db`,`name`,`type`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Stored Procedures';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `procs_priv`
+--
+
+DROP TABLE IF EXISTS `procs_priv`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `procs_priv` (
+  `Host` char(255) NOT NULL DEFAULT '',
+  `Db` char(64) NOT NULL DEFAULT '',
+  `User` char(128) NOT NULL DEFAULT '',
+  `Routine_name` char(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '',
+  `Routine_type` enum('FUNCTION','PROCEDURE','PACKAGE','PACKAGE BODY') NOT NULL,
+  `Grantor` varchar(384) NOT NULL DEFAULT '',
+  `Proc_priv` set('Execute','Alter Routine','Grant') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '',
+  `Timestamp` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
+  PRIMARY KEY (`Host`,`Db`,`User`,`Routine_name`,`Routine_type`),
+  KEY `Grantor` (`Grantor`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Procedure privileges';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `proxies_priv`
+--
+
+DROP TABLE IF EXISTS `proxies_priv`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `proxies_priv` (
+  `Host` char(255) NOT NULL DEFAULT '',
+  `User` char(128) NOT NULL DEFAULT '',
+  `Proxied_host` char(255) NOT NULL DEFAULT '',
+  `Proxied_user` char(128) NOT NULL DEFAULT '',
+  `With_grant` tinyint(1) NOT NULL DEFAULT 0,
+  `Grantor` varchar(384) NOT NULL DEFAULT '',
+  `Timestamp` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
+  PRIMARY KEY (`Host`,`User`,`Proxied_host`,`Proxied_user`),
+  KEY `Grantor` (`Grantor`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='User proxy privileges';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `roles_mapping`
+--
+
+DROP TABLE IF EXISTS `roles_mapping`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `roles_mapping` (
+  `Host` char(255) NOT NULL DEFAULT '',
+  `User` char(128) NOT NULL DEFAULT '',
+  `Role` char(128) NOT NULL DEFAULT '',
+  `Admin_option` enum('N','Y') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'N',
+  UNIQUE KEY `Host` (`Host`,`User`,`Role`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Granted roles';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `servers`
+--
+
+DROP TABLE IF EXISTS `servers`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `servers` (
+  `Server_name` char(64) NOT NULL DEFAULT '',
+  `Host` varchar(2048) NOT NULL DEFAULT '',
+  `Db` char(64) NOT NULL DEFAULT '',
+  `Username` char(128) NOT NULL DEFAULT '',
+  `Password` char(64) NOT NULL DEFAULT '',
+  `Port` int(4) NOT NULL DEFAULT 0,
+  `Socket` char(64) NOT NULL DEFAULT '',
+  `Wrapper` char(64) NOT NULL DEFAULT '',
+  `Owner` varchar(512) NOT NULL DEFAULT '',
+  PRIMARY KEY (`Server_name`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='MySQL Foreign Servers table';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `table_stats`
+--
+
+DROP TABLE IF EXISTS `table_stats`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `table_stats` (
+  `db_name` varchar(64) NOT NULL,
+  `table_name` varchar(64) NOT NULL,
+  `cardinality` bigint(21) unsigned DEFAULT NULL,
+  PRIMARY KEY (`db_name`,`table_name`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin PAGE_CHECKSUM=1 TRANSACTIONAL=0 COMMENT='Statistics on Tables';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `tables_priv`
+--
+
+DROP TABLE IF EXISTS `tables_priv`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `tables_priv` (
+  `Host` char(255) NOT NULL DEFAULT '',
+  `Db` char(64) NOT NULL DEFAULT '',
+  `User` char(128) NOT NULL DEFAULT '',
+  `Table_name` char(64) NOT NULL DEFAULT '',
+  `Grantor` varchar(384) NOT NULL DEFAULT '',
+  `Timestamp` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
+  `Table_priv` set('Select','Insert','Update','Delete','Create','Drop','Grant','References','Index','Alter','Create View','Show view','Trigger','Delete versioning rows') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '',
+  `Column_priv` set('Select','Insert','Update','References') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '',
+  PRIMARY KEY (`Host`,`Db`,`User`,`Table_name`),
+  KEY `Grantor` (`Grantor`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Table privileges';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `time_zone`
+--
+
+DROP TABLE IF EXISTS `time_zone`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `time_zone` (
+  `Time_zone_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `Use_leap_seconds` enum('Y','N') NOT NULL DEFAULT 'N',
+  PRIMARY KEY (`Time_zone_id`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Time zones';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `time_zone_leap_second`
+--
+
+DROP TABLE IF EXISTS `time_zone_leap_second`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `time_zone_leap_second` (
+  `Transition_time` bigint(20) NOT NULL,
+  `Correction` int(11) NOT NULL,
+  PRIMARY KEY (`Transition_time`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Leap seconds information for time zones';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `time_zone_name`
+--
+
+DROP TABLE IF EXISTS `time_zone_name`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `time_zone_name` (
+  `Name` char(64) NOT NULL,
+  `Time_zone_id` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`Name`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Time zone names';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `time_zone_transition`
+--
+
+DROP TABLE IF EXISTS `time_zone_transition`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `time_zone_transition` (
+  `Time_zone_id` int(10) unsigned NOT NULL,
+  `Transition_time` bigint(20) NOT NULL,
+  `Transition_type_id` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`Time_zone_id`,`Transition_time`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Time zone transitions';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `time_zone_transition_type`
+--
+
+DROP TABLE IF EXISTS `time_zone_transition_type`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `time_zone_transition_type` (
+  `Time_zone_id` int(10) unsigned NOT NULL,
+  `Transition_type_id` int(10) unsigned NOT NULL,
+  `Offset` int(11) NOT NULL DEFAULT 0,
+  `Is_DST` tinyint(3) unsigned NOT NULL DEFAULT 0,
+  `Abbreviation` char(8) NOT NULL DEFAULT '',
+  PRIMARY KEY (`Time_zone_id`,`Transition_type_id`)
+) ENGINE=Aria DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Time zone transition types';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Temporary table structure for view `user`
+--
+
+DROP TABLE IF EXISTS `user`;
+/*!50001 DROP VIEW IF EXISTS `user`*/;
+SET @saved_cs_client     = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `user` AS SELECT
+ 1 AS `Host`,
+  1 AS `User`,
+  1 AS `Password`,
+  1 AS `Select_priv`,
+  1 AS `Insert_priv`,
+  1 AS `Update_priv`,
+  1 AS `Delete_priv`,
+  1 AS `Create_priv`,
+  1 AS `Drop_priv`,
+  1 AS `Reload_priv`,
+  1 AS `Shutdown_priv`,
+  1 AS `Process_priv`,
+  1 AS `File_priv`,
+  1 AS `Grant_priv`,
+  1 AS `References_priv`,
+  1 AS `Index_priv`,
+  1 AS `Alter_priv`,
+  1 AS `Show_db_priv`,
+  1 AS `Super_priv`,
+  1 AS `Create_tmp_table_priv`,
+  1 AS `Lock_tables_priv`,
+  1 AS `Execute_priv`,
+  1 AS `Repl_slave_priv`,
+  1 AS `Repl_client_priv`,
+  1 AS `Create_view_priv`,
+  1 AS `Show_view_priv`,
+  1 AS `Create_routine_priv`,
+  1 AS `Alter_routine_priv`,
+  1 AS `Create_user_priv`,
+  1 AS `Event_priv`,
+  1 AS `Trigger_priv`,
+  1 AS `Create_tablespace_priv`,
+  1 AS `Delete_history_priv`,
+  1 AS `ssl_type`,
+  1 AS `ssl_cipher`,
+  1 AS `x509_issuer`,
+  1 AS `x509_subject`,
+  1 AS `max_questions`,
+  1 AS `max_updates`,
+  1 AS `max_connections`,
+  1 AS `max_user_connections`,
+  1 AS `plugin`,
+  1 AS `authentication_string`,
+  1 AS `password_expired`,
+  1 AS `is_role`,
+  1 AS `default_role`,
+  1 AS `max_statement_time` */;
+SET character_set_client = @saved_cs_client;
+
+--
+-- Table structure for table `transaction_registry`
+--
+
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE IF NOT EXISTS `transaction_registry` (
+  `transaction_id` bigint(20) unsigned NOT NULL,
+  `commit_id` bigint(20) unsigned NOT NULL,
+  `begin_timestamp` timestamp(6) NOT NULL DEFAULT '0000-00-00 00:00:00.000000',
+  `commit_timestamp` timestamp(6) NOT NULL DEFAULT '0000-00-00 00:00:00.000000',
+  `isolation_level` enum('READ-UNCOMMITTED','READ-COMMITTED','REPEATABLE-READ','SERIALIZABLE') NOT NULL,
+  PRIMARY KEY (`transaction_id`),
+  UNIQUE KEY `commit_id` (`commit_id`),
+  KEY `begin_timestamp` (`begin_timestamp`),
+  KEY `commit_timestamp` (`commit_timestamp`,`transaction_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin STATS_PERSISTENT=0;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Current Database: `CCCP`
+--
+
+USE `CCCP`;
+
+--
+-- Current Database: `mysql`
+--
+
+USE `mysql`;
+
+--
+-- Final view structure for view `user`
+--
+
+/*!50001 DROP VIEW IF EXISTS `user`*/;
+/*!50001 SET @saved_cs_client          = @@character_set_client */;
+/*!50001 SET @saved_cs_results         = @@character_set_results */;
+/*!50001 SET @saved_col_connection     = @@collation_connection */;
+/*!50001 SET character_set_client      = utf8mb4 */;
+/*!50001 SET character_set_results     = utf8mb4 */;
+/*!50001 SET collation_connection      = utf8mb4_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`mariadb.sys`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `user` AS select `global_priv`.`Host` AS `Host`,`global_priv`.`User` AS `User`,if(json_value(`global_priv`.`Priv`,'$.plugin') in ('mysql_native_password','mysql_old_password'),ifnull(json_value(`global_priv`.`Priv`,'$.authentication_string'),''),'') AS `Password`,if(json_value(`global_priv`.`Priv`,'$.access') & 1,'Y','N') AS `Select_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 2,'Y','N') AS `Insert_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 4,'Y','N') AS `Update_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 8,'Y','N') AS `Delete_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 16,'Y','N') AS `Create_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 32,'Y','N') AS `Drop_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 64,'Y','N') AS `Reload_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 128,'Y','N') AS `Shutdown_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 256,'Y','N') AS `Process_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 512,'Y','N') AS `File_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 1024,'Y','N') AS `Grant_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 2048,'Y','N') AS `References_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 4096,'Y','N') AS `Index_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 8192,'Y','N') AS `Alter_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 16384,'Y','N') AS `Show_db_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 32768,'Y','N') AS `Super_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 65536,'Y','N') AS `Create_tmp_table_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 131072,'Y','N') AS `Lock_tables_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 262144,'Y','N') AS `Execute_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 524288,'Y','N') AS `Repl_slave_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 1048576,'Y','N') AS `Repl_client_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 2097152,'Y','N') AS `Create_view_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 4194304,'Y','N') AS `Show_view_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 8388608,'Y','N') AS `Create_routine_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 16777216,'Y','N') AS `Alter_routine_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 33554432,'Y','N') AS `Create_user_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 67108864,'Y','N') AS `Event_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 134217728,'Y','N') AS `Trigger_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 268435456,'Y','N') AS `Create_tablespace_priv`,if(json_value(`global_priv`.`Priv`,'$.access') & 536870912,'Y','N') AS `Delete_history_priv`,elt(ifnull(json_value(`global_priv`.`Priv`,'$.ssl_type'),0) + 1,'','ANY','X509','SPECIFIED') AS `ssl_type`,ifnull(json_value(`global_priv`.`Priv`,'$.ssl_cipher'),'') AS `ssl_cipher`,ifnull(json_value(`global_priv`.`Priv`,'$.x509_issuer'),'') AS `x509_issuer`,ifnull(json_value(`global_priv`.`Priv`,'$.x509_subject'),'') AS `x509_subject`,cast(ifnull(json_value(`global_priv`.`Priv`,'$.max_questions'),0) as unsigned) AS `max_questions`,cast(ifnull(json_value(`global_priv`.`Priv`,'$.max_updates'),0) as unsigned) AS `max_updates`,cast(ifnull(json_value(`global_priv`.`Priv`,'$.max_connections'),0) as unsigned) AS `max_connections`,cast(ifnull(json_value(`global_priv`.`Priv`,'$.max_user_connections'),0) as signed) AS `max_user_connections`,ifnull(json_value(`global_priv`.`Priv`,'$.plugin'),'') AS `plugin`,ifnull(json_value(`global_priv`.`Priv`,'$.authentication_string'),'') AS `authentication_string`,if(ifnull(json_value(`global_priv`.`Priv`,'$.password_last_changed'),1) = 0,'Y','N') AS `password_expired`,elt(ifnull(json_value(`global_priv`.`Priv`,'$.is_role'),0) + 1,'N','Y') AS `is_role`,ifnull(json_value(`global_priv`.`Priv`,'$.default_role'),'') AS `default_role`,cast(ifnull(json_value(`global_priv`.`Priv`,'$.max_statement_time'),0.0) as decimal(12,6)) AS `max_statement_time` from `global_priv` */;
+/*!50001 SET character_set_client      = @saved_cs_client */;
+/*!50001 SET character_set_results     = @saved_cs_results */;
+/*!50001 SET collation_connection      = @saved_col_connection */;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2025-02-26  0:05:32
index 67863f5..cbeea7f 100644 (file)
@@ -1,58 +1,4 @@
 /*
-TODO: Comment code more, especially:
-        - canvass.js
-        - api.php
-
-      Configurable max upload size
-
-      Cached replay of data entry while canvassing (management screen needed, convert calls to cached call, check capacity)
-
-      Absolute Demographics
-        - Number of voters, of each party, of age/sex, of vote history
-        - Population
-
-      Canvassing
-       Research
-        2) Click a district or census block, get stats
-
-        Canvassing page
-        5) Color outside of turf red? validate voters are in turf
-        6) "List voters in canvass" button?
-
-      Show stats
-        - Each DB query should collect some data for the stats panel
-
-      self-enforcing AGPL; left branding
-
-      Users/authorization on common
-        - Phonebank
-        - Users have fields for lastQuery, queryStreak, dailyQuery, lifetimeQuery
-            if query within 5s of lastQuery, queryStreak++ -- set limit on this in settings
-               also limit daily and lifetime
-               query = any call to api.php
-            nightly write daily to a file in audit/queries/year/month/dd-queryCount.log
-
-      Settings
-        - Configure map center
-        - Import elections DB
-          - Date-based areas
-        - Non-voter limit (vs new voter)
-        - Redistricting year
-
-      BUGS:
-        Precinct maps need to have years attached to them so they reflect elections correctly
-          - Election selector should also have year attached to it? And maybe a notice of redistricting? Year stored = first year in effect?
-          - Include redistricting warning on stats
-        Fix import script to block duplicate voters (or make voterId unique?)
-        Sometimes map tiles don't load (try triggering map.invalidateSize(true); )...sometimes turf doesn't persist when moving map
-
-      INVESTIGATE:
-        Find a way to map precincts to districts -- can we find a list, or just use turf.js?
-          (district jsons may need to move into DB)
-          (Can we plot other districts? Mayors, city council, etc? Find ward geojsons)
-*/
-
-/*
 * This file is part of the Cargobike Community Canvassing Program (CCCP).
 * Copyright 2023, Brian Flowers, SlightlyCyberpunk.com
 *
index 658ad44..d1a45ca 100644 (file)
@@ -288,6 +288,37 @@ if(isset($_GET['get']) && $_GET['get'] == "taskStatus") {
     $stmt->execute($params);
   }
 
+  // CENSUS
+  if( isset($_POST['censusToggle']) && $_POST['censusToggle'] == "OFF" ) {
+    // Enable geocoding
+    $query = "INSERT INTO tasks(taskName, taskStart, taskStep, taskLastStep, taskPercent) ".
+             "VALUES('geocoder.census', CURRENT_TIMESTAMP, 0, ?, 0);";
+    $params= Array($_POST['censusDailyMax']);
+    $stmt  = $dbh->prepare($query);
+    $stmt->execute($params);
+
+    // Trigger task execution
+    exec("nohup php ./taskRunner.php 1>>taskRunner.log 2>&1 &");
+
+  } else if( isset($_POST['censusToggle']) && $_POST['censusToggle'] == "ON" ) {
+    // Disable geocoding
+    $query = "UPDATE tasks SET taskComplete=CURRENT_TIMESTAMP WHERE taskName='geocoder.census'";
+    $stmt  = $dbh->prepare($query);
+    $stmt->execute();
+  } else if( isset($_POST['censusSave']) ) {
+    // Update geocoding configuration
+    $query = "REPLACE INTO config (configValue, configName) VALUES(?, 'geocoder.census.key'); ";
+    $params= Array($_POST['censusKey']);
+    $stmt  = $dbh->prepare($query);
+    $stmt->execute($params);
+
+    $query = "REPLACE INTO config (configValue, configName) VALUES (?,'geocoder.census.maxdaily');";
+    $params= Array($_POST['censusDailyMax']);
+    $stmt  = $dbh->prepare($query);
+    $stmt->execute($params);
+  }
+
+
 
   header('Location: settings.php');
   die();
index e8b41e6..3a4618f 100644 (file)
@@ -104,6 +104,22 @@ if( sizeof($rows) == 0 ) {
   $STATUS['geocoder.tomtom.last'] = $rows[0]['taskUpdate'];
 }
 
+// Census status check
+$query = "SELECT * FROM tasks WHERE taskName = 'geocoder.census' AND taskComplete IS NULL ORDER BY taskUpdate DESC;";
+$stmt  = $dbh->prepare($query);
+$stmt->execute();
+$rows  = $stmt->fetchAll();
+
+if( sizeof($rows) == 0 ) {
+  $STATUS['geocoder.census'] = "OFF";
+} else if (sizeof($rows) > 10) {
+  $STATUS['geocoder.census'] = "ERROR";
+  $STATUS['geocoder.census.last'] = $rows[0]['taskUpdate'];
+} else {
+  $STATUS['geocoder.census'] = "ON";
+  $STATUS['geocoder.census.last'] = $rows[0]['taskUpdate'];
+}
+
 
 // Global geocoder status
 $query = "SELECT count(*) cnt FROM voterAddresses va, voters v WHERE v.addressId = va.id AND va.latitude IS NULL;";
@@ -460,6 +476,43 @@ for($i = 0; $i < sizeof($rows); $i++) {
               </tr>
             </table>
 
+            <!-- Census -->
+            <table class="subcategory">
+              <tr>
+                <td><a href="https://geocoding.geo.census.gov/geocoder/Geocoding_Services_API.html">US Census</a></td>
+              </tr>
+              <tr>
+                <td>
+                  <label for="censusActive">Consus Status: </label>
+                </td><td>
+                  <input type="submit" id="censusActive" onclick="censusToggle" name="censusToggle"
+                         value="<?php echo $STATUS['geocoder.census']; ?>"></input>
+                </td>
+              </tr><tr>
+                <td>
+                  <label for="censusDailyMax">Census Daily Limit: </label>
+                </td><td>
+                  <input type="text" id="censusDailyMax" name="censusDailyMax"
+                         value="<?php echo $CONFIG['geocoder.census.maxdaily']; ?>"></input>
+                </td>
+              </tr><!--<tr>
+                <td>
+                  <label for="censusKey">Census API Key: </label>
+                </td><td>
+                  <input type="text" id="censusKey" name="censusKey"
+                         value="<?php echo $CONFIG['geocoder.census.key']; ?>"></input>
+                </td>
+              </tr>-->
+              <tr>
+                <td>Last triggered:</td>
+                <td><?php echo isset($STATUS['geocoder.census.last']) ? $STATUS['geocoder.census.last'] : "UNKNOWN";?></td>
+              </tr>
+              <tr>
+                <td><input type="submit" value="Save Changes" name="censusSave"></td>
+                <td></td>
+              </tr>
+            </table>
+
 
 
             <!-- Overall geocode -->
index b87e105..cff2e49 100644 (file)
@@ -128,7 +128,7 @@ for($i = 0; $i < sizeof($tasks); $i++) {
         if(sizeOf($header) == 0) {
           $header = $data;
         } else {
-          // Sanitize the data a bit
+          // Sanitize the data a bit -- need TODO more of this
           for($j = 0; $j < sizeof($data); $j++) {
             $data[$j] = trim($data[$j]);
           }
@@ -137,6 +137,8 @@ for($i = 0; $i < sizeof($tasks); $i++) {
           // (This check is pretty basic; geocoder will normalize and verify these a bit more)
           $query  = "SELECT * FROM voterAddresses WHERE addressLine1=? AND (addressLine2=? OR addressLine2 IS NULL) AND city=? AND zip=?;";
           $params = Array(strtoupper($data[18]), strtoupper($data[19]), strtoupper($data[20]), strtoupper($data[22]));
+echo "ADDR:";
+print_r($params);
           $stmt   = $dbh->prepare($query);
           $stmt->execute($params);
           $addresses = $stmt->fetchAll();
@@ -185,7 +187,8 @@ print_r($params);
             }
           }
           if(sizeof($aids2) == 1) {
-            $aids = $aids2[0];
+print_r($addresses[$j]);
+            $aids = [$aids2[0]];
           }
           if($aids == Array()) {
             echo "ERROR: Could not find or insert address for line number: ".$rowcount." (".$data[18].", ".$data[20].")";
@@ -208,7 +211,10 @@ print_r($params);
           $stmt = $dbh->prepare($query);
           $stmt->execute($params);
           $voters = $stmt->fetchAll();
-
+echo "AIDS:";
+print_r($aids);
+print_r($voters);
+echo "---";
           if(sizeof($voters) == 1) {
             $vid = $voters[0]['id'];
             // If this record has a newer registration date, update the voter details
@@ -222,6 +228,16 @@ print_r($params);
               $stmt   = $dbh->prepare($query);
               $stmt->execute($params);
 //              $vid = $dbh->lastInsertId();
+            // If the matching address differs, update it (ideally this shouldn't happen, but might if you have to fix data later)
+            } else if( !in_array($voters[0]['addressId'], $aids) ) {
+echo "UPDATING AID";
+              $query = "UPDATE voters SET addressId=? WHERE id=?;";
+              $params= Array($aids[0], $vid);
+echo $query;
+print_r($params);
+              $stmt   = $dbh->prepare($query);
+              $stmt->execute($params);
+print_r($dbh->errorInfo());
             }
           } else {
             $query = "INSERT INTO voters(firstName, middleName, lastName, birthYear, ".
@@ -811,7 +827,9 @@ print_r($params);
   ///////////////////////////////////////////////
   } else if($tasks[$i]['taskName'] == "geocoder.geoapify" ||
             $tasks[$i]['taskName'] == "geocoder.maps.co" ||
-            $tasks[$i]['taskName'] == "geocoder.tomtom") {
+            $tasks[$i]['taskName'] == "geocoder.tomtom" ||
+            $tasks[$i]['taskName'] == "geocoder.census" ) {
+
     // Fetch geocoder config
     $query = "SELECT * FROM config WHERE configName LIKE 'geocoder.%'";
     $stmt  = $dbh->prepare($query);
@@ -823,13 +841,14 @@ print_r($params);
       $CONFIG[$rows[$j]['configName']] = $rows[$j]['configValue'];
     }
 
-    if($tasks[$i]['taskName'] == "geocoder.geoapify") {
+/*    if($tasks[$i]['taskName'] == "geocoder.geoapify") {
       $maxDaily = (int)($CONFIG['geocoder.geoapify.maxdaily']);
     } else if($tasks[$i]['taskName'] == "geocoder.maps.co") {
       $maxDaily = (int)($CONFIG['geocoder.maps.co.maxdaily']);
     } else if($tasks[$i]['taskName'] == "geocoder.tomtom") {
       $maxDaily = (int)($CONFIG['geocoder.tomtom.maxdaily']);
-    }
+    }*/
+    $maxDaily = (int)($CONFIG[$tasks[$i]['taskName'].'.maxdaily']);
     $maxCount = $maxDaily / 20;
 
     // If no geocode task for today, close out any existing and create one
@@ -871,7 +890,7 @@ print_r($params);
     $updated = $tasks[$i]['taskStep'];
     // Select the next max addresses; just don't worry about parents being in the same batch
       $mode     = "scan";
-      $query    = "select va.id as id, va.addressLine1, va.addressLine2, va.city, va.state, va.zip ".
+      $query    = "select distinct va.id as id, va.addressLine1, va.addressLine2, va.city, va.state, va.zip ".
                   "from voterAddresses va WHERE va.latitude IS NULL ".
                   "AND (SELECT id FROM geocodeResults WHERE engine=? AND addressId=va.id LIMIT 1) IS NULL ".
                   "order by va.id desc limit ?;";
@@ -882,7 +901,7 @@ print_r($params);
       if( sizeof($geocodes) <= 100 ) {
         echo "No urgent geocodes.".PHP_EOL;
         $mode = "verify";
-        $query   = "select va.id as id, va.addressLine1, va.addressLine2, va.city, va.state, va.zip ".
+        $query   = "select distinct va.id as id, va.addressLine1, va.addressLine2, va.city, va.state, va.zip ".
                   "from voterAddresses va WHERE ".
                   "(SELECT id FROM geocodeResults WHERE engine=? AND addressId=va.id LIMIT 1) IS NULL ".
                   "order by va.id desc limit ?;";
@@ -939,6 +958,9 @@ print_r($params);
         $lat = $resultsObj->features[0]->properties->lat;
         $lon = $resultsObj->features[0]->properties->lon;
         $resAddr = $resultsObj->features[0]->properties->address_line1;
+        if( isset($resultsObj->features[0]->properties->unit) ) {
+          $resAddr .= " ".$resultsObj->features[0]->properties->unit;
+        }
         $resCity = $resultsObj->features[0]->properties->city;
         $resState= $resultsObj->features[0]->properties->state_code;
         $resZip  = $resultsObj->features[0]->properties->postcode;
@@ -1041,6 +1063,59 @@ print_r($params);
         $resCity = $resultsObj->results[0]->address->municipality;
         $resState= $resultsObj->results[0]->address->countrySubdivision;
         $resZip  = $resultsObj->results[0]->address->postalCode;
+      } else if($tasks[$i]['taskName'] == "geocoder.census") {
+        $url = "https://geocoding.geo.census.gov/geocoder/locations/onelineaddress?address=".urlencode($addr." ".$city." ".$state." ".$zip).
+               "&format=json&benchmark=4";
+        sleep(.5);
+        $results = file_get_contents($url);
+        $resultsObj = json_decode($results);
+
+        // If no results...
+        if( !is_array($resultsObj->result->addressMatches) || sizeof($resultsObj->result->addressMatches) == 0) {
+          echo "No Census results for address ID: ".$geocodes[$g]['id'];
+
+          // Log the geocode result
+          $query = "INSERT INTO geocodeResults(engine, addressId, latitude, longitude, response) VALUES(?,?,?,?,?);";
+          $params= Array($tasks[$i]['taskName'], $geocodes[$g]['id'], null, null, json_encode($resultsObj));
+          $stmt  = $dbh->prepare($query);
+          $stmt->execute($params);
+          continue;
+        }
+
+        if( !isset($resultsObj->result->addressMatches[0]->coordinates->x) ) {
+          echo "ERROR: Invalid response returned from ".$tasks[$i]['taskName'].PHP_EOL;
+          print_r($resultsObj);
+          print_r($geocodes[$g]);
+
+          // Log the geocode result
+          $query = "INSERT INTO geocodeResults(engine, addressId, latitude, longitude, response) VALUES(?,?,?,?,?);";
+          $params= Array($tasks[$i]['taskName'], $geocodes[$g]['id'], null, null, json_encode($resultsObj));
+          $stmt  = $dbh->prepare($query);
+          $stmt->execute($params);
+          continue;
+        }
+
+        if( !isset($resultsObj->result->addressMatches[0]->addressComponents->streetName) ) {
+          echo "ERROR: Invalid address returned from ".$tasks[$i]['taskName'].PHP_EOL;
+          print_r($resultsObj);
+          print_r($geocodes[$g]);
+
+          // Log the geocode result
+          $query = "INSERT INTO geocodeResults(engine, addressId, latitude, longitude, response) VALUES(?,?,?,?,?);";
+          $params= Array($tasks[$i]['taskName'], $geocodes[$g]['id'], null, null, json_encode($resultsObj));
+          $stmt  = $dbh->prepare($query);
+          $stmt->execute($params);
+          continue;
+        }
+
+        $lat = $resultsObj->result->addressMatches[0]->coordinates->x;
+        $lon = $resultsObj->result->addressMatches[0]->coordinates->y;
+        // Census geocoder doesn't really return an address, it returns a range
+/*        $resAddr = $resultsObj->results[0]->address->streetNumber.
+                   " ".$resultsObj->results[0]->address->streetName;
+        $resCity = $resultsObj->results[0]->address->municipality;
+        $resState= $resultsObj->results[0]->address->countrySubdivision;
+        $resZip  = $resultsObj->results[0]->address->postalCode;*/
       }
 
 
@@ -1050,6 +1125,56 @@ print_r($params);
       $stmt  = $dbh->prepare($query);
       $stmt->execute($params);
 
+      // If we have more than two results, compute a best fit
+      $query = "SELECT latitude, longitude from geocodeResults where latitude is not null and addressId = ?;";
+      $params= Array($geocodes[$i]['id']);
+      $stmt  = $dbh->prepare($query);
+      $stmt->execute($params);
+      $res   = $stmt->fetchAll(PDO::FETCH_ASSOC);
+      // Need at least three, to know if any are outliers
+      if( sizeof($res) > 2) {
+        $lats = Array();
+        $lons = Array();
+        for($r = 0; $r < sizeof($res); $r++) {
+          array_push($lats, $res[$r]['latitude']);
+          array_push($lons, $res[$r]['longitude']);
+        }
+
+        // Standard deviations require some PECL extensions
+        //   and doing that in 2D is gonna require some extra effort anyway...
+        // So we'll pull a mean position and a mean distance from that
+        // Then drop any points over the mean distance
+        // And then calculate a new mean
+        $latMean = array_sum($lats) / sizeof($lats);
+        $lonMean = array_sum($lons) / sizeof($lons);
+        $distances = Array();
+        for($r = 0; $r < sizeof($lats); $r++) {
+          // You also can't really do distance this way with lat and lon, but....meh. TODO
+          $dist = sqrt(pow($latMean - $lats[$r],2)+pow($lonMean - $lons[$r],2));
+          array_push($distances, $dist);
+        }
+        $distMean = array_sum($distMean) / sizeof($distMean);
+        for($r = 0; $r < sizeof($distances); $r++) {
+          if($distances[$r] > $distMean) {
+            unset($lats[$r]);
+            unset($lons[$r]);
+          }
+        }
+
+        if(sizeof($lats) > 0) {
+          $latMean = array_sum($lats) / sizeof($lats);
+          $lonMean = array_sum($lons) / sizeof($lons);
+        }
+
+        // Log the result
+        $query = "INSERT INTO geocodeResults(engine, addressId, latitude, longitude, response) VALUES(?,?,?,?,?);";
+        $params= Array("Internal Average", $geocodes[$g]['id'], $latMean, $lonMean, json_encode($res));
+        $stmt  = $dbh->prepare($query);
+        $stmt->execute($params);
+        $lat = $latMean;
+        $lon = $lonMean;
+      }
+
       // Update the address
       $query = "UPDATE voterAddresses SET longitude=?, latitude=?, addressLine1=?, city=?, state=?, zip=? WHERE id=?;";
       $params= Array($lon, $lat,
@@ -1061,6 +1186,7 @@ print_r($params);
       $stmt = $dbh->prepare($query);
       if( $dbh->errorInfo()[0] == "00000") {
         try {
+print_r($params);
           $stmt->execute($params);
         } catch(Exeception $ex) {
           echo "ERROR INSERTING DATA INTO voteAddresses".PHP_EOL;