|
11 | 11 | ) |
12 | 12 | from fastapi import APIRouter, Body, HTTPException |
13 | 13 | from pydantic import BaseModel |
14 | | -from typing import Any, List, Optional |
| 14 | +from typing import Any, List, Tuple |
15 | 15 |
|
16 | 16 | router = APIRouter() |
17 | 17 |
|
18 | 18 |
|
19 | | -class AccumulateTallyRequest(BaseModel): |
| 19 | +class StartTallyRequest(BaseModel): |
20 | 20 | ballots: List[Any] |
21 | | - encrypted_tally: Optional[Any] = None |
22 | 21 | description: Any |
23 | 22 | context: Any |
24 | 23 |
|
25 | 24 |
|
26 | | -@router.post("/accumulate") |
27 | | -def accumulate_tally(request: AccumulateTallyRequest = Body(...)) -> Any: |
| 25 | +class AppendTallyRequest(StartTallyRequest): |
| 26 | + encrypted_tally: Any |
| 27 | + |
| 28 | + |
| 29 | +@router.post("") |
| 30 | +def start_tally(request: StartTallyRequest = Body(...)) -> Any: |
28 | 31 | """ |
29 | | - Accumulate ballots into a new or existing tally |
| 32 | + Start a new tally of a collection of ballots |
30 | 33 | """ |
31 | 34 |
|
| 35 | + ballots, description, context = parse_request(request) |
| 36 | + tally = CiphertextTally("election-results", description, context) |
| 37 | + |
| 38 | + return tally_ballots(tally, ballots) |
| 39 | + |
| 40 | + |
| 41 | +@router.post("/append") |
| 42 | +def append_to_tally(request: AppendTallyRequest = Body(...)) -> Any: |
| 43 | + """ |
| 44 | + Append ballots into an existing tally |
| 45 | + """ |
| 46 | + |
| 47 | + ballots, description, context = parse_request(request) |
| 48 | + |
| 49 | + published_tally = PublishedCiphertextTally.from_json_object(request.encrypted_tally) |
| 50 | + tally = CiphertextTally(published_tally.object_id, description, context) |
| 51 | + tally.cast = published_tally.cast |
| 52 | + |
| 53 | + return tally_ballots(tally, ballots) |
| 54 | + |
| 55 | + |
| 56 | +def parse_request( |
| 57 | + request: StartTallyRequest, |
| 58 | +) -> Tuple[ |
| 59 | + List[CiphertextAcceptedBallot], |
| 60 | + InternalElectionDescription, |
| 61 | + CiphertextElectionContext, |
| 62 | +]: |
| 63 | + """ |
| 64 | + Deserialize common tally request values |
| 65 | + """ |
32 | 66 | ballots = [ |
33 | 67 | CiphertextAcceptedBallot.from_json_object(ballot) for ballot in request.ballots |
34 | 68 | ] |
35 | 69 | description = ElectionDescription.from_json_object(request.description) |
36 | 70 | internal_description = InternalElectionDescription(description) |
37 | 71 | context = CiphertextElectionContext.from_json_object(request.context) |
38 | | - published_tally: Optional[PublishedCiphertextTally] = ( |
39 | | - PublishedCiphertextTally.from_json_object(request.encrypted_tally) |
40 | | - if request.encrypted_tally |
41 | | - else None |
42 | | - ) |
43 | 72 |
|
44 | | - tally = _get_new_or_existing_tally(internal_description, context, published_tally) |
| 73 | + return (ballots, internal_description, context) |
45 | 74 |
|
| 75 | + |
| 76 | +def tally_ballots( |
| 77 | + tally: CiphertextTally, ballots: List[CiphertextAcceptedBallot] |
| 78 | +) -> PublishedCiphertextTally: |
46 | 79 | tally_succeeded = tally.batch_append(ballots) |
47 | 80 |
|
48 | 81 | if tally_succeeded: |
49 | 82 | published_tally = publish_ciphertext_tally(tally) |
50 | 83 | return published_tally.to_json_object() |
51 | 84 | else: |
52 | | - raise HTTPException( |
53 | | - status_code=500, detail="Tally accumulation was unsuccessful" |
54 | | - ) |
55 | | - |
56 | | - |
57 | | -def _get_new_or_existing_tally( |
58 | | - description: ElectionDescription, |
59 | | - context: CiphertextElectionContext, |
60 | | - published_tally: PublishedCiphertextTally, |
61 | | -) -> CiphertextTally: |
62 | | - if not published_tally: |
63 | | - return CiphertextTally("election-results", description, context) |
64 | | - |
65 | | - full_tally = CiphertextTally(published_tally.object_id, description, context) |
66 | | - full_tally.cast = published_tally.cast |
67 | | - |
68 | | - return full_tally |
| 85 | + raise HTTPException(status_code=500, detail="Unable to tally ballots") |
0 commit comments