EnCiv/undebate-ssp

Election Data Object Schema

ddfridley opened this issue · 13 comments

This schema and issue is deprecated, see the new Wiki Page for the Election Object Data Schema


The definition for the electionObj that is used by other component to reference the content of an election setup.

function Component(props) {
  const {electionOM, ...}=props
  const [electionObj, electionMethods]=electionOM 
  ...
}

Whenever the content of electionObj is changed, electionOM will change, which will cause a rerender of the component
Don't depend on electionObj or electionMethods to change to cause a re-render. The change may be deeper.

It might seem efficient for components to only take properties within electionObj - like questions, and that can be done like this:

// don't do this unless it's really needed
function Component(props){
    const [electionObj, electionMethods]=props
    return <Questions={electionObj.questions} >

But if the component is going to be using helper methods like electionMethods.areQuestionsLocked() that depends on other properties in the electionObj - so the Question component wouldn't rerender in this case. For this project the number of components that would get rerendered is reasonable so we won't worry about it for now. Later, if we need to, we can look at optimizing.

Tentative/draft/work-in-progress*

const addDays = (date, days) => {
    return new Date(date.getTime() + days * 86400000)
}

const initialDate = new Date(2021, 11, 7)
const modInviteSentDate = addDays(initialDate, 2)
const modInviteDeadline = addDays(modInviteSentDate, 2)
const modInviteAcceptDate = addDays(modInviteDeadline, 2)
const modReminder1 = addDays(modInviteAcceptDate, 2)
const modReminder2 = addDays(modReminder1, 2)
const modFinal = addDays(modReminder2, 2)
const candInviteSent = addDays(modFinal, 2)
const candInviteAccepted = addDays(candInviteSent, 2)
const candReminder = addDays(candInviteAccepted, 2)
const candFinal = addDays(candReminder, 2)
const undebate = addDays(candFinal, 2)
const election = addDays(undebate, 2)

const electionsData = {
    elections: [
        {
            _id: "mongoobjid",
            electionName: "U.S Presidential Election",
            organizationName: "United States Federal Government",
            electionDate: '2022-11-07T23:59:59.999Z',
            questions: {
               0:  {
                    text: "What is your favorite color?",
                    time: "30",
                },
                1:  {
                    text: "Do you have a pet?",
                    time: "60",
                },
                2:  {
                    text: "Should we try to fix income inequality?",
                    time: "90",
                },
            },
            script: {
               0:  {
                    text: "Welcome everyone. Our first question is: What is your favorite color?",
                },
                1:  {
                    text: "Thank you. Our next Question is: Do you have a pet?",
                },
                2:  {
                    text: "Great. And our last question is: Should we try to fix income inequality?",
                },
                3:  {
                    text: "Thanks everyone for watching this!",
                },
            },
            moderator: {
                name: "Bill Smith",
                email: "billsmith@gmail.com",
                message: "Please be a moderator",
                invitations: [ // derived data, list may be empty or not present
                    {    _id: string,
                        sentDate: modInviteSentDate,
                        responseDate: modInviteAcceptDate,
                        status: "Accepted"
                    },
                ],
                submissions: [ // derived data, list may be empty or not present
                    {   _id: "",
                        url "",
                        date: "",
                    },
                ],
            },
            candidates: {
                "61e76bbefeaa4a25840d85d0":
                      {
                          uniqueId: "61e76bbefeaa4a25840d85d0",
                          name: "Sarah Jones",
                          email: "sarahjones@mail.com",
                          office: "President of the U.S.",
                          region: "United States",
                          invitations: [ // derived data - list may be empty or not present
                              {
                                  _id: ""
                                  sentDate: candInviteSent,
                                  responseDate: candInviteAccepted,
                                  status: "Declined",
                                  parentId: "",
                              },
                          ],
                          submissions: [  // derived data - list may be empty or not present
                              {    _id: "",
                                  url: "",
                                  date: "",
                                  parentId: "",
                              },
                          },
                      },
                "61e76bfc8a82733d08f0cf12":
                    {
                        uniqueId: "61e76bfc8a82733d08f0cf12",
                        name: "Michael Jefferson",
                        email: "mikejeff@mail.com",
                        office: "President of the U.S.",
                        region: "United States",
                        invitations: [
                            text: "Hi Mike, please send answers",
                            sentDate: candInviteSent,
                            responseDate: candInviteAccepted,
                            status: "Accepted",
                        ],
                        submission: [
                            link: "",
                            date: "",
                        ],
                    },
             },
            timeline: {
                moderatorDeadlineReminderEmails: {
                    0: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: true
                     },
                    1: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: false
                     }
                 },
                moderatorSubmissionDeadline: {
                    0: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: true
                     },
                 },
                candidateDeadlineReminderEmails: {
                    0: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: true
                     },
                    1: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: false
                     }
                 },
                candidateSubmissionDeadline: {
                    0: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: true
                     }
                 },
                moderatorInviteDeadline: {
                    0: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: true
                     },
                    1: {
                           date: "2022-01-07T22:09:32.952Z", 
                           sent: false
                     }
                 },
            },
            undebateDate: undebate,
        },
    ],
}
electionMethods={
    upsert: (obj)=>{}, 
    areQuestionsLocked: ()=>{},
    createNew: ()=>{},
    sendInvite: ()=>{},
    sendCandidateInvites: ()={},
    ...

Election Methods

upsert(obj)

  • obj - an object that only contains the portion of electionObj that needs to be changed.

When there is new input from the user, upsert is called to update the electionObj. It will be updated and cause a new version of electionOM to be generated, which will cause all components with it as a prop to rerender. Changes will also be accumulated and sent to the database in a timely manner.

Example:

electionMethods.upsert({moderator: {email: 'new email address', {name: 'name'})

// or if the user deletes something:

electionMethods.upsert({moderator: {email: undefined, name:  undefined})

areQuestionsLocked()

Returns true if Questions can not be changed - for example because invite has been sent to the moderator to record.

createNew()

Create a new, empty electionObj and navigate to the Configure Election page #23

sendInvite()

email an invitation to the moderator of the electionObj

sendCandidateInvites()

email invitations to the candidates in the electionObj.candidates

@gmunilla14 I am moving your slack question here so the discussion will get captured in github.

At what point do you think we should say that the timeline page is complete? Should we make them put a certain number of dates for reminders? Or just non reminder deadlines?

As I see it, and I have been kind of talking about this with @procupid (Kamal) too through comments on the UI design - we should allow the election admin to enter what they know even if it's not the linear sequence that we idealize. So my answer is that even if the timeline isn't complete yet, everything should do what it can with the available data - and indicate errors or warnings or whatever is something is missing. And we will use the "methods" that we talked about in the last call to determine things. For example, the reminder emails for the candidates can't be sent before the moderator has recorded, and so those dates can't be before the moderator submission deadline. Also, the undebate goes live date shouldn't be before the candidate submission deadline. But while UCLA had a submission deadline, Ballotpedia did not for the general elections.

Or - can you think of a specific instance where you can't do something until the timeline is complete?

@gmunilla14 can you edit this issue and put your schema in the electionObj definition? It doesn't have to be complete - just a starting point. Alternative it could all be in a .js file - and you could put that in a PR and I could link to it from here. Even if it's incomplete. Also I'm happy to consider other ways to document/format it but I want to refer to specific names in some of the Issues I write.

@ddfridley I definitely agree with allowing the admin to input information in a nonlinear fashion. The only reason for a definition of a "complete" timeline would be to know when to set the completed statusObj on the Timeline part of the Nav panel. My initial thought would be that we would need:

Moderator Invite Deadline
Moderator Submission Deadline
Candidate Submission Deadline

what do you think?

@gmunilla14 Also need Undebate Goes Live and Last Day of Election to call the timeline complete.

@gmunilla14 Thanks for adding the schema stuff. I added uniqueId to the candidates. Something I talked about in #53. Interesting that you are putting the invitation in there - I hadn't thought of that. It might need to be a list though - there's the invitation and then the reminders. It also ties in with design work yet to be done on the server side - sending out the emails. Cron will look at the timeline and figure out the next time to send out emails. When it sends out emails, it could generate an iota in the db for each instance. The iota's parentId could be the election. When the election admin comes to the portal, we will find all the children of the election id, and "join" them in. Lets discuss.

@gmunilla14 here is a counter idea to deadlines.

    timeLine: {
        moderatorDeadlineReminderEmails: [
            {number: 1, "date": 1660144211000},
            {number: 2, "date": 1660144211000}
        ],
        moderatorSubmissionDeadline: [
            {number: 1, "date": 1660144211000},
        ],
        candidateDeadlineRemiderEmails: [
            {number: 1, "date": 1660144211000},
            {number: 2, "date": 1660144211000}
        ],
        candidateSubmissionDeadline: [
            {number: 1, "date": 1660144211000},
        ],
        undebateGoesLive: [
            {number: 1, "date": 1660144211000},
        ],
        lastDayOfElection: [
            {number: 1, "date": 1660144211000},
        ]
    }

Some things only have one date - but things change. In cases like candidateSubmissionDeadline we always use the latest one.

In the above, there was name and email and date and such, but I'm thinking that information gets associated with the candidate and the moderator, rather than with the deadlines. Thoughts? Or are you thinking we need to indicate some kind of status that the deadline processing was actually done? We might need to keep that internally too.

@ddfridley I think thats a good point. One thing Im thinking about is whether we should add a sent attribute for the reminder emails like below. I think this might fit more with the admins ability to set future reminder emails and make the "Reminder Sent" status more clear.

timeLine: { moderatorDeadlineReminderEmails: [ {number: 1, "date": 1660144211000, "sent": true}, {number: 2, "date": 1660144211000, "sent": false} ], moderatorSubmissionDeadline: [ {number: 1, "date": 1660144211000}, ], candidateDeadlineRemiderEmails: [ {number: 1, "date": 1660144211000, "sent": false}, {number: 2, "date": 1660144211000, "sent": false} ], candidateSubmissionDeadline: [ {number: 1, "date": 1660144211000}, ], undebateGoesLive: [ {number: 1, "date": 1660144211000}, ], lastDayOfElection: [ {number: 1, "date": 1660144211000}, ] }

@gmunilla14 I have updated moderator.invitations, moderator.submissions, candidate.invitations, candidate.submissions to be lists and I've left a comment that this is derived data. When an invitation is sent, a different iota is created and when a submission is made another iota is created. When we search the database for the electionObj, we will search for the iotas that have this as a child, and merge them in.

@gmunilla14 I have an idea about ReminderEmails. If we did it like this:

moderatorDeadlineRemindeEmails: {
    1:  {date: "", "sent": true},
    2: {date: "", "sent": true},
}

Because it's an object, rather than a list, its easier in the upsert() to know if you are trying to add a new one, or update an existing one.

upsert({timeLine: {moderatorDeadlineReminderEmails: { 1: {date: "", sent: true}} 

is more exact than:

upsert({timeLine: {moderatorDeadlineReminderEmails: [ {number: 1, data: "", sent: true}]

because the latter would replace the existing array with the new array, where as the former would either add the 1: enter to the object, or overwrite the 1: entry if it's already there. I haven't fully vetted this but that's my thinking right now, and see this post: https://stackoverflow.com/a/21347174/6262595

Hello! Are the scripts for the moderators stored in the electionObj? I assume so, but it isn't documented here.

@kamui-fin Just changed acceptedDate to responseDate and added status to some fields.

Closed. See the wiki instead.