Skip to content

Commit 6b7e157

Browse files
committed
add new functionality to select concurrent nov and varsity outrounds before pairing. Make menial fixes to bugs in outround pairing strategies
1 parent 94c8b4e commit 6b7e157

21 files changed

Lines changed: 1210 additions & 416 deletions

assets/js/outround.js

Lines changed: 62 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,61 @@ function cycleChoice(event) {
1616
}
1717

1818
function populateTabCards() {
19-
const roundNumber = $("#round-number").data("round-number");
20-
if (!roundNumber || !$(".outround-tabcard").length) {
19+
const roundNumberEl = $("#round-number");
20+
const roundNumbersAttr = roundNumberEl.attr("data-round-numbers");
21+
let roundNumbers = [];
22+
23+
if (roundNumbersAttr) {
24+
roundNumbers = roundNumbersAttr
25+
.split(",")
26+
.map((n) => n.trim())
27+
.filter((n) => n);
28+
}
29+
30+
if (!roundNumbers.length) {
31+
const fallbackRoundNumber = roundNumberEl.data("round-number");
32+
if (fallbackRoundNumber) {
33+
roundNumbers = [String(fallbackRoundNumber)];
34+
}
35+
}
36+
37+
roundNumbers = [...new Set(roundNumbers)];
38+
39+
if (!roundNumbers.length || !$(".outround-tabcard").length) {
2140
return;
2241
}
23-
$.ajax({
24-
url: `/outround/${roundNumber}/stats`,
25-
success(result) {
26-
Object.entries(result).forEach(([teamId, stats]) => {
27-
const tabCardElement = $(`.outround-tabcard[team-id=${teamId}]`);
28-
const text = [
29-
stats.effective_outround_seed,
30-
stats.outround_seed,
31-
stats.wins,
32-
stats.total_speaks.toFixed(2),
33-
stats.govs,
34-
stats.opps,
35-
stats.seed,
36-
].join(" / ");
37-
tabCardElement.attr(
38-
"title",
39-
"Effective Seed / Outround Seed / In-round Wins" +
40-
" / Speaks / Govs / Opps / Seed",
41-
);
42-
tabCardElement.attr("href", `/team/card/${teamId}`);
43-
tabCardElement.text(text);
44-
});
45-
},
42+
43+
roundNumbers.forEach((roundNumber) => {
44+
$.ajax({
45+
url: `/outround/${roundNumber}/stats`,
46+
success(result) {
47+
Object.entries(result).forEach(([teamId, stats]) => {
48+
const tabCardElement = $(`.outround-tabcard[team-id=${teamId}]`);
49+
const text = [
50+
stats.effective_outround_seed,
51+
stats.outround_seed,
52+
stats.wins,
53+
stats.total_speaks.toFixed(2),
54+
stats.govs,
55+
stats.opps,
56+
stats.seed,
57+
].join(" / ");
58+
tabCardElement.attr(
59+
"title",
60+
"Effective Seed / Outround Seed / In-round Wins" +
61+
" / Speaks / Govs / Opps / Seed",
62+
);
63+
tabCardElement.attr("href", `/team/card/${teamId}`);
64+
tabCardElement.text(text);
65+
});
66+
},
67+
});
4668
});
4769
}
4870

4971
function assignTeam(e) {
5072
e.preventDefault();
5173
const teamId = $(e.target).attr("team-id");
52-
const oldTeamId = $(e.target).attr("src-team-id");
5374
const roundId = $(e.target).attr("round-id");
5475
const position = $(e.target).attr("position");
5576
const url = `/outround/pairings/assign_team/${roundId}/${position}/${teamId}`;
@@ -67,13 +88,7 @@ function assignTeam(e) {
6788
$container.find(".team-link").text(result.team.name);
6889
$container.find(".team-link").attr("href", `/team/${result.team.id}`);
6990
$container.find(".outround-tabcard").attr("team-id", result.team.id);
70-
71-
populateTabCards($(`.outround-tabcard[team-id=${result.team.id}]`));
72-
73-
const $oldTeamTabCard = $(`.outround-tabcard[team-id=${oldTeamId}]`);
74-
if ($oldTeamTabCard) {
75-
populateTabCards($oldTeamTabCard);
76-
}
91+
populateTabCards();
7792
} else {
7893
window.alert(alertMsg);
7994
}
@@ -140,7 +155,8 @@ function populateAlternativeJudges() {
140155
const $parent = $(this).parent();
141156
const judgeId = $parent.attr("judge-id");
142157
const roundId = $parent.attr("round-id");
143-
const url = `/outround/${roundId}/alternative_judges/${judgeId || ""}`;
158+
const query = window.location.search || "";
159+
const url = `/outround/${roundId}/alternative_judges/${judgeId || ""}${query}`;
144160

145161
$.ajax({
146162
url,
@@ -154,29 +170,31 @@ function populateAlternativeJudges() {
154170
}
155171

156172
function togglePairingRelease(event) {
157-
const button = $(".outround-release");
158-
const numTeams = button.data("num_teams");
159-
const typeOfRound = button.data("type_of_round");
173+
const button = $(event.currentTarget);
174+
const numTeams = button.attr("data-num_teams");
175+
const typeOfRound = button.attr("data-type_of_round");
176+
const releaseKey = button.attr("data-release-key");
160177

161178
event.preventDefault();
162179
$.ajax({
163180
url: `/outround_pairing/release/${numTeams}/${typeOfRound}`,
164181
success(result) {
182+
const groupSelector = `.outround-release[data-release-key='${releaseKey}']`;
183+
const closeBtn = `${groupSelector}.close-pairings-btn`;
184+
const openBtn = `${groupSelector}.open-pairings-btn`;
165185
if (result.pairing_released) {
166-
$("#close-pairings").removeClass("d-none");
167-
$("#release-pairings").addClass("d-none");
186+
$(closeBtn).removeClass("d-none");
187+
$(openBtn).addClass("d-none");
168188
} else {
169-
$("#close-pairings").addClass("d-none");
170-
$("#release-pairings").removeClass("d-none");
189+
$(closeBtn).addClass("d-none");
190+
$(openBtn).removeClass("d-none");
171191
}
172192
},
173193
});
174194
}
175195

176196
$(document).ready(() => {
177-
$(".team.outround-tabcard").each((_, element) => {
178-
populateTabCards($(element));
179-
});
197+
populateTabCards();
180198
$(".choice-update").each((_, element) => {
181199
$(element).click(cycleChoice);
182200
});

assets/js/pairing.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ function assignRoom(e) {
123123
let $buttonWrapper;
124124
if (curRoomId) {
125125
$buttonWrapper = $(`span[round-id=${roundId}][room-id=${curRoomId}]`);
126+
} else {
127+
$buttonWrapper = $(`.room span[round-id=${roundId}]`).first();
128+
}
129+
if (!$buttonWrapper.length) {
130+
return;
126131
}
127132
const $button = $buttonWrapper.find(".btn-sm");
128133
$button.addClass("disabled");
@@ -148,7 +153,8 @@ function populateAlternativeRooms() {
148153
const roundId = $parent.attr("round-id");
149154
const outround = $parent.attr("outround") === "true";
150155
const baseUrl = outround ? "/outround" : "/round";
151-
const url = `${baseUrl}/${roundId}/alternative_rooms/${roomId || ""}`;
156+
const query = outround ? window.location.search || "" : "";
157+
const url = `${baseUrl}/${roundId}/alternative_rooms/${roomId || ""}${query}`;
152158

153159
$.ajax({
154160
url,

docs/Advanced-Topics.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@ Below is a reference table of available settings. Most can be modified through t
7676
+--------------------------+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
7777
| `var_panel_size` | `3` | The number of judges on a varsity outs panel. |
7878
+--------------------------+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
79-
| `var_to_nov` | `2` | The offset of the novice break to the varsity break. If novice semis happen when varsity quarters happen, the offset should be 1. If novice semis happen when varsity octofinals happen, the offset should be 2. |
80-
+--------------------------+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
8179
| `var_teams_visible` | `256` | The number of teams above which the varsity outround is visible. For example, if it were 8, quarterfinals and above would be visible, if it were 4, semifinals and above would be visible. |
8280
+--------------------------+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
8381
| `nov_teams_visible` | `256` | The number of teams above which the novice outround is visible. For example, if it were 8, quarterfinals and above would be visible, if it were 4, semifinals and above would be visible. |
8482
+--------------------------+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
83+
| `outround_judge_priority`| `0` | Controls how judges are prioritized when assigning varsity and novice outrounds together. `0` ("Varsity Wings") assigns varsity first, then novice with remaining judges. `1` ("Novice Chairs") assigns both together by panel slot and gives novice rounds the lower draft position each slot. |
84+
+--------------------------+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
8585
| `gov_opp_display` | `0` | A toggle to switch the outrounds view from displaying Gov and Opp (when checked) and Team 1 and Team 2 (when value is 2) |
8686
+--------------------------+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
8787
| `sidelock` | `0` | Check this to indicate you are side-locking outrounds. When checked, the gov opps for outrounds will not be random, but will adhere to sidelocks, and will be indicated on the pairing card, and bolded on the front-facing pairing display. |

docs/Outrounds.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ All teams have a `break_preference` field which determines which break they'd pr
1212

1313
It will also perform a number of checks to ensure you have enough rooms and judges. It does support paneled rounds (as long as they are consistently paneled) -- please consult the `nov_panel_size` and `var_panel_size` TabSettings for more information. It will let you pair if you don't have enough judges, but it will warn you.
1414

15-
The other tab setting that must be set correctly in order to ensure judges / rooms are not double booked is the `var_to_nov` variable. If you would like varsity octafinals to happen at the same time as novice quarterfinals, this value should be `2` as the quotient of number of teams in varsity break rounds to the simultaneous novice break round is 2. If they are happening at the same time, the value should be `1`, if they are octafinals at the same time as semifinals, then it should be 4, etc.
16-
1715
## Managing Pairings
1816

19-
MIT Tab supports automatic judge assignment for outrounds. On the pairing view, you can click the "Assign Judges" button to automatically assign judges to rounds based on their rankings and scratches (assuming you have set `var_to_nov` correctly - see above - AND all scratches are entered). This will respect panel sizes as configured in `var_panel_size` and `nov_panel_size`.
17+
MIT Tab supports automatic judge and room assignment for outrounds. On the pairing view, use the Varsity and Novice dropdowns to choose which active outrounds are in scope, then click "Assign Judges" and/or "Assign Rooms". Assignment runs across the selected scope as a single pool, so judges and rooms are not double-booked within the selected rounds.
18+
19+
Automatic judge assignment respects panel sizes as configured in `var_panel_size` and `nov_panel_size`, and can use the `outround_judge_priority` setting when varsity and novice rounds are assigned together.
2020

2121
**Important**: Wing-only judges (judges with the "Wing Only" checkbox enabled) will be automatically excluded from chair assignments. They can still be assigned as panel members but will never be selected as the chair during automatic assignment.
2222

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 3.2.25 on 2026-02-24 00:00
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("tab", "0035_merge_0032_rankinggroup_0034_add_motion_model"),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name="outround",
16+
name="room",
17+
field=models.ForeignKey(
18+
blank=True,
19+
null=True,
20+
on_delete=django.db.models.deletion.CASCADE,
21+
related_name="rooms_outrounds",
22+
to="tab.room",
23+
),
24+
),
25+
]

mittab/apps/tab/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,8 @@ class Outround(models.Model):
491491
(OPP_VIA_FORFEIT, "OPP via Forfeit"),
492492
)
493493
room = models.ForeignKey(Room,
494+
blank=True,
495+
null=True,
494496
on_delete=models.CASCADE,
495497
related_name="rooms_outrounds")
496498
victor = models.IntegerField(choices=VICTOR_CHOICES, default=0)

0 commit comments

Comments
 (0)