@@ -298,8 +298,28 @@ export class VoteService {
298
298
299
299
// Convert IRV results to the same format as other voting modes for consistent frontend rendering
300
300
let results ;
301
- if ( irvResult . winnerIndex !== null ) {
302
- // Create results array with winner first, then others in order
301
+ if ( irvResult . isTie ) {
302
+ // Handle tie situation - mark tied candidates as winners
303
+ results = poll . options . map ( ( option , index ) => {
304
+ const isTiedWinner = irvResult . tiedCandidates && irvResult . tiedCandidates . includes ( index ) ;
305
+ return {
306
+ option,
307
+ votes : isTiedWinner ? votes . length : 0 , // Tied winners share all votes
308
+ percentage : isTiedWinner ? ( 100 / ( irvResult . tiedCandidates ?. length || 1 ) ) : 0 , // Split percentage among tied winners
309
+ isWinner : isTiedWinner ,
310
+ isTied : isTiedWinner ,
311
+ finalRound : irvResult . rounds . length
312
+ } ;
313
+ } ) ;
314
+
315
+ // Sort: tied winners first, then by original option order
316
+ results . sort ( ( a , b ) => {
317
+ if ( a . isWinner && ! b . isWinner ) return - 1 ;
318
+ if ( ! a . isWinner && b . isWinner ) return 1 ;
319
+ return poll . options . indexOf ( a . option ) - poll . options . indexOf ( b . option ) ;
320
+ } ) ;
321
+ } else if ( irvResult . winnerIndex !== null ) {
322
+ // Single winner case
303
323
results = poll . options . map ( ( option , index ) => {
304
324
if ( index === irvResult . winnerIndex ) {
305
325
return {
@@ -327,7 +347,7 @@ export class VoteService {
327
347
return poll . options . indexOf ( a . option ) - poll . options . indexOf ( b . option ) ;
328
348
} ) ;
329
349
} else {
330
- // No winner determined, show all options with 0 votes
350
+ // No winner determined (exhausted) , show all options with 0 votes
331
351
results = poll . options . map ( ( option , index ) => ( {
332
352
option,
333
353
votes : 0 ,
@@ -782,7 +802,7 @@ export class VoteService {
782
802
const allSame = firstChoiceValues . length > 0 && firstChoiceValues . every ( v => v === firstChoiceValues [ 0 ] ) ;
783
803
784
804
if ( allSame && firstChoiceValues . length > 1 ) {
785
- console . warn ( `⚠️ IRV Initial Tie: All candidates have the same number of first-choice votes (${ firstChoiceValues [ 0 ] } ). This will result in arbitrary elimination .`) ;
805
+ console . log ( `ℹ️ IRV Initial Tie: All candidates have the same number of first-choice votes (${ firstChoiceValues [ 0 ] } ). Will declare a tie .`) ;
786
806
}
787
807
788
808
// Initialize IRV process
@@ -841,6 +861,24 @@ export class VoteService {
841
861
rejectedReasons
842
862
} ;
843
863
}
864
+
865
+ // Check for tie (all remaining candidates have same votes)
866
+ const voteCounts = activeCandidates . map ( c => counts [ c ] || 0 ) ;
867
+ const allSameVotes = voteCounts . length > 0 && voteCounts . every ( v => v === voteCounts [ 0 ] ) ;
868
+
869
+ if ( allSameVotes && activeCandidates . length > 1 ) {
870
+ console . log ( `ℹ️ IRV Tie detected: All remaining candidates have ${ voteCounts [ 0 ] } votes. Declaring tie.` ) ;
871
+ return {
872
+ winnerIndex : null ,
873
+ winnerOption : undefined ,
874
+ tiedCandidates : activeCandidates ,
875
+ tiedOptions : activeCandidates . map ( i => options [ i ] ) ,
876
+ rounds,
877
+ rejectedBallots,
878
+ rejectedReasons,
879
+ isTie : true
880
+ } ;
881
+ }
844
882
845
883
// If only one candidate remains, they win
846
884
if ( activeCandidates . length === 1 ) {
@@ -860,7 +898,9 @@ export class VoteService {
860
898
winnerOption : undefined ,
861
899
rounds,
862
900
rejectedBallots,
863
- rejectedReasons
901
+ rejectedReasons,
902
+ isTie : false ,
903
+ exhausted : true
864
904
} ;
865
905
}
866
906
0 commit comments