Exemplo n.º 1
0
 private void SetStartingState(string line, DateTime timestamp, PollPadState state = PollPadState.STARTED)
 {
     this.ClearState();
     this.state     = state;
     this.startTime = timestamp;
     if (line.Contains(s_selectCheckinIdMethod))
     {
         this.scanIdLookup = line.Contains(s_scanId);
     }
 }
Exemplo n.º 2
0
 private void ClearState()
 {
     this.state                  = PollPadState.INIT;
     this.startTime              = Util.nullTimestamp;
     this.endTime                = Util.nullTimestamp;
     this.vbmCancelled           = false;
     this.assistanceRequired     = false;
     this.searches               = 0;
     this.durationHighConfidence = true;
     this.lastTimestamp          = Util.nullTimestamp;
 }
Exemplo n.º 3
0
        public void ReadLine(string line)
        {
            MatchCollection matches = PollPad_Importer.timestampRegex.Matches(line);

            if (matches.Count == 0)
            {
                // System.Diagnostics.Debug.WriteLine("Timestamp-less line passed to PollPad_Processor!");
                return;
            }
            // Extract and parse timestamp
            DateTime timestamp = DateTime.ParseExact(matches[0].Value, @"MMM d, yyyy \a\t h:mm:ss tt", CultureInfo.InvariantCulture);

            if (timestamp.Year < 2015)
            {
                System.Diagnostics.Debug.WriteLine("Anomalous timestamp at line:");
                System.Diagnostics.Debug.WriteLine(line);
            }
            // Reset the state if there's a large (> 15 minute) gap between the previous and the current timestamp
            if (this.lastTimestamp != Util.nullTimestamp && (timestamp - this.lastTimestamp).TotalMinutes > 15)
            {
                // Add an "abandoned" record if there is something to add
                this.AddAbandonedRecord(this.lastTimestamp);
                this.ClearState();
            }

            if (this.state == PollPadState.INIT)
            {
                if (line.Contains(s_selectCheckinIdMethod))
                {
                    // This is the normal start checkin flow. Set the state and other state variables.
                    this.SetStartingState(line, timestamp);
                }
                else if (line.Contains(s_registrationTapped))
                {
                    // If registration button was tapped while IDLE, the poll worker is trying to register
                    // a voter without looking them up first. Set state appropriately.
                    this.SetStartingState(line, timestamp, PollPadState.REGISTERING);
                }
                else if (line.Contains(s_voterSearch))
                {
                    // Read unexpected search log (before select checkin method log). The inferred duration
                    // might not be accurate.
                    this.SetStartingState(line, timestamp);
                    this.searches += 1;
                    this.durationHighConfidence = false;
                }
            }
            else if (this.state == PollPadState.STARTED)
            {
                if (line.Contains(s_selectCheckinIdMethod))
                {
                    // Previous checkin process ended without anything meaningful happening, so reset state.
                    this.SetStartingState(line, timestamp);
                }
                else if (line.Contains(s_toIdSelectionScreen))
                {
                    // The user went back, so reset state.
                    if (this.searches > 0)
                    {
                        // If some searches were done after the start, it's better to record an event.
                        this.AddAbandonedRecord(timestamp);
                    }
                    this.ClearState();
                }
                else if (line.Contains(s_lookupTableSelection))
                {
                    // We set an 'end time' value at this moment so that we get a better value for event duration
                    // in case the poll worker cancels the check-in process. (Apparently the log doesn't explicitly
                    // indicate such a cancellation and we have to infer it from the next check-in being started).
                    this.endTime = timestamp;
                    this.state   = PollPadState.SELECTED;
                }
                else if (line.Contains(s_checkinVoter))
                {
                    // Check-in completes right after it has started when checking in with an ID (as opposed to
                    // manual lookup).
                    this.AddRecord(RecordType.CHECKED_IN_NORMAL, timestamp, null, this.vbmCancelled, this.assistanceRequired);
                    this.ClearState();
                }
                else if (line.Contains(s_spoiledBallot))
                {
                    // The voter's ballot has been spoiled.
                    this.AddRecord(RecordType.BALLOT_SPOILED, timestamp);
                    this.ClearState();
                }
                else if (line.Contains(s_cancelCheckin))
                {
                    // A previous check-in was cancelled.
                    this.AddRecord(RecordType.CANCEL_CHECK_IN, timestamp);
                    this.ClearState();
                }
                else if (line.Contains(s_assistanceRequired))
                {
                    // THe presence of this string indicates that the voter required assistance.
                    this.assistanceRequired = true;
                }
                else if (line.Contains(s_registrationTapped))
                {
                    // Registration (voter add) process was started after the start of check-in process.
                    this.state = PollPadState.REGISTERING;
                }
                else if (line.Contains(s_voterSearch))
                {
                    // Search query for voter detected, increment count.
                    this.searches += 1;
                    if ((timestamp - this.startTime).TotalMinutes > 3)
                    {
                        // If there's more than 3 minutes of difference between 'start' and search, assume
                        // that the "start" happened before the voter arrived and set a new start time.
                        this.durationHighConfidence = false;
                        this.startTime = timestamp;
                    }
                }
            }
            else if (state == PollPadState.SELECTED)
            {
                if (line.Contains(s_selectCheckinIdMethod))
                {
                    // Previous check-in process was abandoned and a new one started. Write a record of the
                    // previous one. Again, the duration here will be unreliable.
                    this.AddAbandonedRecord(this.endTime);
                    this.SetStartingState(line, timestamp);
                }
                else if (line.Contains(s_toIdSelectionScreen))
                {
                    // The user went back, so reset state after writing a record.
                    this.AddAbandonedRecord(this.endTime);
                    this.ClearState();
                }
                else if (line.Contains(s_checkinVoter))
                {
                    // Voter checked in normally
                    this.AddRecord(RecordType.CHECKED_IN_NORMAL, timestamp, null, this.vbmCancelled,
                                   this.assistanceRequired);
                    this.ClearState();
                }
                else if (line.Contains(s_spoiledBallot))
                {
                    // The voter's ballot has been spoiled.
                    this.AddRecord(RecordType.BALLOT_SPOILED, timestamp);
                    this.ClearState();
                }
                else if (line.Contains(s_cancelCheckin))
                {
                    // A previous check-in was cancelled.
                    this.AddRecord(RecordType.CANCEL_CHECK_IN, timestamp);
                    this.ClearState();
                }
                else if (line.Contains(s_voterSearch))
                {
                    // A re-search can happen even after selecting, so detect that.
                    this.searches += 1;
                }
                else if (line.Contains(s_cancelVbm))
                {
                    // Presence of this string indicates that the voter was a VBM voter and their mail
                    // ballot was cancelled.
                    this.vbmCancelled = true;
                }
                else if (line.Contains(s_assistanceRequired))
                {
                    // The presence of this string indicates the voter required assistance.
                    this.assistanceRequired = true;
                }
                else if (line.Contains(s_registrationTapped))
                {
                    // Registration (voter add) process was started after a voter was selected from search
                    // results.
                    this.state = PollPadState.REGISTERING;
                }
                else if (line.Contains(s_addedVoter))
                {
                    this.state = PollPadState.JUST_ADDED;
                }
                else if (line.Contains(s_editedVoter))
                {
                    this.state = PollPadState.JUST_EDITED;
                }
            }
            else if (this.state == PollPadState.REGISTERING)
            {
                if (line.Contains(s_addedVoter))
                {
                    // Voter data was added to the database.
                    this.state = PollPadState.JUST_ADDED;
                }
                else if (line.Contains(s_editedVoter))
                {
                    // Voter's data was edited in the database.
                    this.state = PollPadState.JUST_EDITED;
                }
                else if (line.Contains(s_selectCheckinIdMethod))
                {
                    // The registration process was abandoned and a new check-in process is starting.
                    // Add a record of the abandoned process. Note that the duration may be inaccurate
                    // in this case.
                    this.durationHighConfidence = false;
                    this.AddRecord(RecordType.REGISTRATION_INCOMPLETE, timestamp);
                    this.SetStartingState(line, timestamp);
                }
                else if (line.Contains(s_toIdSelectionScreen))
                {
                    // The user went back, so reset state after writing a record.
                    this.durationHighConfidence = false;
                    this.AddRecord(RecordType.REGISTRATION_INCOMPLETE, timestamp);
                    this.ClearState();
                }
            }
            else if (this.state == PollPadState.JUST_ADDED || this.state == PollPadState.JUST_EDITED)
            {
                if (line.Contains(s_checkinVoter))
                {
                    // A check-in after adding the voter's data.
                    RecordType recordType = this.state == PollPadState.JUST_ADDED ?
                                            RecordType.CHECKED_IN_AFTER_ADD : RecordType.CHECKED_IN_AFTER_EDIT;
                    this.AddRecord(recordType, timestamp, null, this.assistanceRequired);
                    this.ClearState();
                }
                else if (line.Contains(s_assistanceRequired))
                {
                    // The 'assistance required' line may appear at this stage too so we check for it.
                    this.assistanceRequired = true;
                }
                else if (line.Contains(s_selectCheckinIdMethod))
                {
                    // A start of checkin lin here means the add/edit process was abandoned. Write record
                    // and reset state. Again, the duration may very well be inaccurate.
                    this.AddAbandonedRecord(timestamp);
                    this.SetStartingState(line, timestamp);
                }
                else if (line.Contains(s_toIdSelectionScreen))
                {
                    // The user went back, so reset state after writing a record.
                    this.AddAbandonedRecord(timestamp);
                    this.ClearState();
                }
                else if (line.Contains(s_voterSearch))
                {
                    this.searches += 1;
                }
            }
            this.lastTimestamp = timestamp;
        }