@@ -153,12 +153,17 @@ def convert_to_approval_favorite_viable_half(abifmodel):
153
153
154
154
approval_jabmod ['votelines' ].append (new_vline )
155
155
156
+ # Calculate total ballots processed
157
+ total_ballots = sum (vline ['qty' ] for vline in abifmodel ['votelines' ])
158
+
156
159
# Store conversion metadata for notices
157
160
approval_jabmod ['_conversion_meta' ] = {
158
161
'method' : 'favorite_viable_half' ,
159
162
'original_ballot_type' : ballot_type ,
160
163
'viable_candidates' : viable_candidates ,
161
- 'viable_candidate_maximum' : viable_candidate_maximum
164
+ 'viable_candidate_maximum' : viable_candidate_maximum ,
165
+ 'total_ballots' : total_ballots ,
166
+ 'candidate_names' : abifmodel .get ('candidates' , {})
162
167
}
163
168
164
169
return approval_jabmod
@@ -260,25 +265,44 @@ def _generate_conversion_notices(conversion_meta):
260
265
viable_candidates = conversion_meta .get ('viable_candidates' , [])
261
266
viable_candidate_maximum = conversion_meta .get ('viable_candidate_maximum' , 0 )
262
267
original_ballot_type = conversion_meta .get ('original_ballot_type' , 'unknown' )
268
+ total_ballots = conversion_meta .get ('total_ballots' , 0 )
269
+
270
+ # Get candidate display names from conversion metadata
271
+ candidate_names = conversion_meta .get ('candidate_names' , {})
272
+
273
+ # Convert viable candidates to display names
274
+ viable_names = []
275
+ for cand_token in viable_candidates :
276
+ display_name = candidate_names .get (cand_token , cand_token )
277
+ viable_names .append (display_name )
263
278
264
- short_text = f"Approval counts estimated from { original_ballot_type } ballots using favorite_viable_half method"
279
+ short_text = f"Approval counts estimated from { total_ballots :, } { original_ballot_type } ballots using favorite_viable_half method"
265
280
266
281
viable_count = len (viable_candidates )
282
+ # Format viable names list with proper "and" for last item
283
+ if len (viable_names ) > 2 :
284
+ viable_names_str = ", " .join (viable_names [:- 1 ]) + f", and { viable_names [- 1 ]} "
285
+ elif len (viable_names ) == 2 :
286
+ viable_names_str = f"{ viable_names [0 ]} and { viable_names [1 ]} "
287
+ else :
288
+ viable_names_str = viable_names [0 ] if viable_names else ""
267
289
268
290
if (viable_count % 2 ) == 0 :
269
291
viable_paren_note = f"(half of { viable_count } ). "
270
292
else :
271
293
viable_paren_note = f"(half of { viable_count } , rounded up). "
294
+
272
295
long_text = (
273
296
f"The 'favorite_viable_half' conversion algorithm: find the candidate with the most "
274
297
f"first preferences, and then determine the minimum number of figurative seats that would "
275
298
f"need to be open in order for the candidate to exceed the Hare quota with the given first-prefs. "
276
- f"We use this to estimate how many candidates are likely to be viable candidates. "
277
- f"For this election by this calculation, { viable_count } candidates are considered viable. "
299
+ f"We use this to estimate how many candidates are likely to be viable candidates.\n \n "
300
+ f"Using first-choice vote totals as a rough guide, approximately { viable_count } candidates appear viable: "
301
+ f"{ viable_names_str } . "
278
302
f"The approximation then assumes each voter approves up to { viable_candidate_maximum } "
279
303
f"of their top-ranked viable candidates { viable_paren_note } "
280
- f"All candidates ranked at or above the lowest-ranked of each voter 's top { viable_candidate_maximum } "
281
- f"viable candidates receive approval ."
304
+ f"All candidates ranked at or above the lowest-ranked of each ballot 's top viable candidates receive approval "
305
+ f"(considering up to { viable_candidate_maximum } viable candidates per ballot) ."
282
306
)
283
307
284
308
notices .append ({
0 commit comments