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); } }
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; }
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; }