Repository: TabbycatDebate/tabbycat

Issue:
Preformed panels supporting outrounds
I have been having a play with preformed panels and from my quick attempt to generate them for outrounds, it seems to generate preformed panels as if it was generating panels for an additional preliminary round rather than a break round. 

For example this is the preformed panels that generated when I generated preformed panels for quarter finals for one of our tournaments.

![image](https://user-images.githubusercontent.com/3196468/81673981-97c26880-94a0-11ea-8197-2a018682d9bf.png)

We did end up changing some thing to do with the round sequence for these rounds (we added 2 additional in rounds, deleted the octo finals and edited the sequence numbers, but this round is set up as per the settings below:

![image](https://user-images.githubusercontent.com/3196468/81674165-df48f480-94a0-11ea-873d-6239181c248d.png)



Discussion:
Good catch! Yes, the preformed panels does not count the number of debates in elimination rounds.

Resolving patch:
diff --git a/tabbycat/adjallocation/preformed/anticipated.py b/tabbycat/adjallocation/preformed/anticipated.py
--- a/tabbycat/adjallocation/preformed/anticipated.py
+++ b/tabbycat/adjallocation/preformed/anticipated.py
@@ -3,6 +3,7 @@
 import itertools
 
 from breakqual.utils import calculate_live_thresholds, determine_liveness
+from draw.generator.utils import ispow2, partial_break_round_split
 from participants.prefetch import populate_win_counts
 
 
@@ -26,13 +27,35 @@ def calculate_anticipated_draw(round):
 
     nteamsindebate = 4 if round.tournament.pref('teams_in_debate') == 'bp' else 2
 
-    if round.prev is None or not round.prev.debate_set.exists():
-        # Special case: If this is the first round, everyone will be on zero.
+    if round.prev is None or not round.prev.debate_set.exists() or round.is_break_round:
+        # Special cases: If this is the first round, everyone will be on zero.
         # Just take all teams, rounded down -- if this is done, it'll typically
         # be done before availability is locked down. Also do this if the last
         # round hasn't yet been drawn, since that's premature for bracket
         # predictions.
-        npanels = round.tournament.team_set.count() // nteamsindebate
+        #
+        # Also occurs for elimination rounds as everyone is just as live.
+
+        nteams = 0
+        if round.is_break_round:
+            break_size = round.break_category.break_size
+            nprev_rounds = round.break_category.round_set.filter(seq__lt=round.seq).count()
+            partial_two = nteamsindebate == 2 and not ispow2(break_size)
+            partial_bp = nteamsindebate == 4 and ispow2(break_size // 6)
+            if nprev_rounds > 0 and (partial_two or partial_bp):
+                # If using partial elimination rounds, the second round is the first for
+                # the powers of two, so start counting from here.
+                nprev_rounds -= 1
+
+            if nprev_rounds == 0 and nteamsindebate == 2:
+                nteams = partial_break_round_split(break_size)[0] * 2
+            else:
+                # Subsequent rounds are half the previous, but always a power of 2
+                nteams = 1 << (break_size.bit_length() - 1 - nprev_rounds)
+        else:
+            nteams = round.tournament.team_set.count()
+
+        npanels = nteams // nteamsindebate
         return [(0, 0, 0) for i in range(npanels)]
 
     # 1. Take the (actual) draw of the last round, with team points
diff --git a/tabbycat/draw/generator/utils.py b/tabbycat/draw/generator/utils.py
--- a/tabbycat/draw/generator/utils.py
+++ b/tabbycat/draw/generator/utils.py
@@ -11,8 +11,8 @@ def nextpow2(n):
 
 
 def partial_break_round_split(break_size):
-    """Returns a tuple `(debating, bypassing)`, where `debating` is how many
-    teams will debate in the first break round, and `bypassing` is how many
+    """Returns a tuple `(debates, bypassing)`, where `debating` is how many
+    debates there is in the first break round, and `bypassing` is how many
     teams will bypass the first break round, qualifying directly for the
     second."""
 


Test patch: