Skip to content

add Proxy Vote Application Deadline and Replacement Pack Start Date to calendar - #2624

Draft
chris48s wants to merge 10 commits into
masterfrom
moardates20260813
Draft

add Proxy Vote Application Deadline and Replacement Pack Start Date to calendar#2624
chris48s wants to merge 10 commits into
masterfrom
moardates20260813

Conversation

@chris48s

@chris48s chris48s commented Aug 13, 2026

Copy link
Copy Markdown
Member

Refs https://app.asana.com/1/1204880536137786/project/1204880927741389/task/1216553673590691?focus=true

The core point of this PR is to add 2 new dates to the election timetable:

  • proxy_vote_application_deadline
  • replacement_pack_start_date

TODO: Before this is merge-able, there is also some downstream work to do on the response builder and API consumers. I've not had time to look at that yet.
As such, lets leave this as draft for the moment, but what is here is worth reviewing.

Proxy Vote Application Deadline

Using our handy matrix, I think the correct values for this are:

Screenshot at 2026-08-13 15-13-53

For most election types (all the ones where the unit test says "reference election: blabla.date"), I've validated this against a real election using the Electoral Commission's timetables from past elections.
So for example:

There are some election types where I haven't been able to check this. For example, the EC don't publish this for City of London or there isn't an example of the relevant election type in the last few years.

One of the issues here is that the EC issue basically issue no guidance for Northern Ireland, and there is nothing on

https://www.eoni.org.uk/voting/voting-by-post-or-proxy/
https://www.eoni.org.uk/faqs/absent-voting-faqs/

I was able to verify NI for local elections using

https://www.eoni.org.uk/media/r2miujg4/final-eoni-digital-registration-number-review-january-2024-paper.pdf#page=10

but I don't have anything I can check against for NIA or Parl.

For Westminster elections, my assumption is that NI has its own date rather than aligning with GB. I think https://www.electoralcommission.org.uk/media/11454 is implicitly GB only based on some of the other dates.
We could double-check this with a contact at EONI if we want.

Replacement Pack Start Date

Replacement packs are a bit messy. I think the correct values for this are:

Basically:

  • 4 working days everywhere, except..
  • For Scottish Parliament specifically, you can request a replacement pack any time.
  • For Northern Ireland, this date doesn't really make sense.

I've explained both of these points more in an inline comment here:

if territory_code == "NIR":
"""
Northern Ireland has different rules around replacement packs
In general, absent ballots are harder to obtain due to elevated
concerns about electoral fraud,
and this particularly applies to replacement packs.
Whereas in England/Wales/Scotland you can basically say
"My postal vote didn't arrive. Can I have another one please"
this process doesn't really exist in Northern Ireland.
A replacement can be obtained but you have to prove you
accidentally spoiled your original one.
Treat the concept of a "replacement_pack_start_date"
as not applicable for Northern Ireland.
"""
pass
elif self.election_id.startswith("sp."):
"""
Replacement packs are allwed in Scottish Parliament elections
But unlike Local and Parliamentary elections there
is no explicit start date from which you're allowed to request one
The Scottish Parliament (Elections etc.) Order 2015
https://www.legislation.gov.uk/ssi/2015/425/schedule/4
This date is also not applicable for Scottish Parliament,
but for different reasons than Northern Ireland.
"""
pass

So we essentially have two different flavours of "not applicable" (in the case of Scotland, that is to be more permissive, and in the case of NI it is loosely related to being less permissive).

Screenshot at 2026-08-13 15-16-24

My sources for what I am saying about NI are:

For Scottish Parliament, it is:

Again, I've checked everything off against guidance and historic timetables where those are published.

The main issue with this one is: I could see at least 3 different ways to possibly implement this in the code:

  1. If country=NI or sp.*: return None
  2. If country=NI or sp.*: raise NotImplemented
  3. Just return a value, and leave it up to the consumer to know when to use it: This is the approach we already have with the VAC deadline: We return a VAC deadline for all election types, and it is up to the caller to know if the election requires ID or not.

I don't think any of these options are obviously correct. I ended up implementing option 3 just because picking something meant I could keep moving. That said, you can also make a decent argument that I am wrong.

My current thinking on this is:

Currently there are no methods that return None at the library layer. Everything either returns a dt.date() or raises NotImplemented. I think if we are going to also introduce a sentinel, we need a decent reason for that.
However, we're going to represent it as null in the API anyway, so maybe just cut out the middleman?
There are two situations where we raise NotImplementedError, and that is the 2 SOPN dates (close of nominations and SOPN deadline) for referenda. I think in that case, its pretty clear to say "this conceptually does not make sense in this situation". I feel like these two flavours of "not applicable" aren't really quite the same. There's no set date, but the concept of a replacement pack does make sense in this context on some level. So I am reluctant to say they're the same.
What I've done is consistent with what we do for the VAC deadline, but is what we're doing with that VAC deadline actually good?
Also, it feels like encoding the rules around this is more explicitly in the remit of the TTs library.
Anyway, I've done option 3: .replacement_pack_start_date always returns a date. I've only written tests for the cases where it makes sense, and EE has the logic to know when to use/not use that value. Its a starting point, and I can change it if we want.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the consequences of storing these dates in the DB is: Every time we add a new date or fix a bug, we have to write one of these backfill migrations.

@chris48s

Copy link
Copy Markdown
Member Author

While I was working on the tests for this PR, I really felt like a lot of them are quite repetitive. I think we could refactor a lot of this to use @pytest.mark.parametrize more extensively and cut out a lot of test boilerplate. I decided not to shave that yak right now. It is a refactor for another day

@chris48s

Copy link
Copy Markdown
Member Author

Interestingly, I seem to have a test failing in CI that does not fail locally.
Yay
I think I am not going to debug that today

@chris48s
chris48s marked this pull request as draft August 13, 2026 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant