diff --git a/openreview/venue/invitation.py b/openreview/venue/invitation.py
index d2c3fdcf5..21b5aef06 100644
--- a/openreview/venue/invitation.py
+++ b/openreview/venue/invitation.py
@@ -1211,7 +1211,7 @@ def set_meta_review_invitation(self):
if self.venue.is_template_related_workflow():
edit_invitations_builder = openreview.workflows.EditInvitationsBuilder(self.client, self.venue_id)
- content = {
+ metareview_content = {
'recommendation_field_name': {
'value': {
'param': {
@@ -1224,7 +1224,7 @@ def set_meta_review_invitation(self):
}
edit_invitations_builder.set_edit_content_invitation(
meta_review_invitation_id,
- content,
+ metareview_content,
process_file='../workflows/workflow_process/edit_recommendation_field_name_process.py',
preprocess_file='../workflows/workflow_process/edit_recommendation_field_name_pre_process.py'
)
@@ -1313,8 +1313,16 @@ def set_meta_review_invitation(self):
if meta_review_stage.source_submissions_query:
invitation.content['source']['value']['content'] = meta_review_stage.source_submissions_query
+ if self.venue.is_template_related_workflow():
+ invitation.description = f'Set the date/time when the meta review {sac_acronym} revision period is open to senior area chairs, when meta reviews revisions are due, and when the meta review revision form is no longer available to senior area chairs.'
+
self.save_invitation(invitation, replacement=False)
+ if self.venue.is_template_related_workflow():
+ edit_invitations_builder = openreview.workflows.EditInvitationsBuilder(self.client, self.venue_id)
+ edit_invitations_builder.set_edit_dates_invitation(meta_review_sac_edit_invitation_id)
+ edit_invitations_builder.set_edit_content_invitation(meta_review_sac_edit_invitation_id)
+
return invitation
def set_recruitment_invitation(self, committee_name, options):
diff --git a/openreview/venue/matching.py b/openreview/venue/matching.py
index 8570954c4..3ca534d33 100644
--- a/openreview/venue/matching.py
+++ b/openreview/venue/matching.py
@@ -1465,6 +1465,20 @@ def setup_matching_invitations(self):
paper_number = '${{2/head}/number}'
readers = [venue_id]
+ edge_readers = readers + ['${2/tail}']
+ edge_nonreaders = [venue.get_authors_id(number=paper_number)]
+ edge_head = {
+ 'param': {
+ 'type': 'note',
+ 'withInvitation': venue.get_submission_id()
+ }
+ }
+ description = f'This step runs automatically at its "activation date", and creates "edges" between the {venue.get_committee_name(self.match_group.id, pretty=True)} group and article submissions that represent expertise. Configure which expertise model will compute affinity scores. (We find that the model "specter2+scincl" has the best performance; refer to our expertise repository for more information on the models.)'
+ content = {
+ 'committee_name': {
+ 'value': self.match_group_name
+ }
+ }
if self.is_reviewer:
if venue.use_senior_area_chairs:
@@ -1476,6 +1490,20 @@ def setup_matching_invitations(self):
if venue.use_senior_area_chairs:
readers.append(venue.get_senior_area_chairs_id(number=paper_number))
+ if self.is_senior_area_chair:
+ edge_readers = [venue_id, '${2/tail}', '${2/head}']
+ edge_nonreaders = []
+ edge_head = {
+ 'param': {
+ 'type': 'profile',
+ 'inGroup': self.alternate_matching_group
+ }
+ }
+ description = f'This step runs automatically at its "activation date", and creates "edges" between the {venue.get_committee_name(self.match_group.id, pretty=True)} group and the {venue.get_committee_name(self.alternate_matching_group, pretty=True)} group that represent expertise. Configure which expertise model will compute affinity scores. (We find that the model "specter2+scincl" has the best performance; refer to our expertise repository for more information on the models.)'
+ content['alternate_committee_id'] = {
+ 'value': self.alternate_matching_group
+ }
+
invitation = Invitation(
id = score_invitation_id,
invitees = [f'{venue_id}/Automated_Administrator'],
@@ -1483,17 +1511,13 @@ def setup_matching_invitations(self):
writers = [venue_id],
signatures = [venue_id],
responseArchiveDate = venue.get_edges_archive_date(),
- description = f'This step runs automatically at its "activation date", and creates "edges" between the {venue.get_committee_name(self.match_group.id, pretty=True)} group and article submissions that represent expertise. Configure which expertise model will compute affinity scores. (We find that the model "specter2+scincl" has the best performance; refer to our expertise repository for more information on the models.)',
+ description = description,
cdate = tools.datetime_millis(venue.submission_stage.due_date) + (60*60*1000*24*3),
date_processes = [{
'dates': ["#{4/cdate}", venue.invitation_builder.update_date_string],
'script': venue.invitation_builder.get_process_content('../workflows/process/compute_affinity_scores_process.py')
}],
- content = {
- 'committee_name': {
- 'value': self.match_group_name
- }
- },
+ content = content,
edge = {
'id': {
'param': {
@@ -1515,8 +1539,8 @@ def setup_matching_invitations(self):
'deletable': True
}
},
- 'readers': readers + ['${2/tail}'],
- 'nonreaders': [venue.get_authors_id(number=paper_number)],
+ 'readers': edge_readers,
+ 'nonreaders': edge_nonreaders,
'writers': [venue_id],
'signatures': {
'param': {
@@ -1527,12 +1551,7 @@ def setup_matching_invitations(self):
'default': [venue.get_program_chairs_id()]
}
},
- 'head': {
- 'param': {
- 'type': 'note',
- 'withInvitation': venue.get_submission_id()
- }
- },
+ 'head': edge_head,
'tail': {
'param': {
'type': 'profile',
@@ -1562,93 +1581,95 @@ def setup_matching_invitations(self):
edit_invitations_builder.set_edit_affinities_settings_invitation(score_invitation_id)
edit_invitations_builder.set_edit_dates_one_level_invitation(score_invitation_id)
- conflict_invitation_id = venue.get_conflict_score_id(self.match_group.id)
- committee_role = venue.get_standard_committee_role(committee_id=self.match_group.id)
+ if not self.is_senior_area_chair:
- invitation = Invitation(
- id = conflict_invitation_id,
- invitees = [f'{venue_id}/Automated_Administrator'],
- readers = readers,
- writers = [venue_id],
- signatures = [venue_id],
- responseArchiveDate = venue.get_edges_archive_date(),
- description = f'This step runs automatically at its "activation date", and creates "edges" between the {venue.get_committee_name(self.match_group.id, pretty=True)} group and article submissions to represent identified conflicts of interest. Configure the conflict of interest policy to be applied and specify the number of years of data to be retrieved from the OpenReview profile for conflict detection.',
- cdate = tools.datetime_millis(venue.submission_stage.due_date) + (60*60*1000*24*3),
- date_processes = [{
- 'dates': ["#{4/cdate}", "#{4/mdate} + " + str(5000)],
- 'script': venue.invitation_builder.get_process_content('../workflows/process/compute_conflicts_process.py')
- }],
- content = {
- 'committee_name': {
- 'value': self.match_group_name
- },
- 'committee_role': {
- 'value': committee_role
- }
- },
- edge = {
- 'id': {
- 'param': {
- 'withInvitation': conflict_invitation_id,
- 'optional': True
- }
- },
- 'ddate': {
- 'param': {
- 'range': [ 0, 9999999999999 ],
- 'optional': True,
- 'deletable': True
- }
- },
- 'cdate': {
- 'param': {
- 'range': [ 0, 9999999999999 ],
- 'optional': True,
- 'deletable': True
- }
- },
- 'readers': readers + ['${2/tail}'],
- 'writers': [venue_id],
- 'signatures': {
- 'param': {
- 'items': [
- { 'value': venue_id, 'optional': True },
- { 'value': venue.get_program_chairs_id(), 'optional': True }
- ],
- 'default': [venue.get_program_chairs_id()]
- }
- },
- 'head': {
- 'param': {
- 'type': 'note',
- 'withInvitation': venue.get_submission_id()
+ conflict_invitation_id = venue.get_conflict_score_id(self.match_group.id)
+ committee_role = venue.get_standard_committee_role(committee_id=self.match_group.id)
+
+ invitation = Invitation(
+ id = conflict_invitation_id,
+ invitees = [f'{venue_id}/Automated_Administrator'],
+ readers = readers,
+ writers = [venue_id],
+ signatures = [venue_id],
+ responseArchiveDate = venue.get_edges_archive_date(),
+ description = f'This step runs automatically at its "activation date", and creates "edges" between the {venue.get_committee_name(self.match_group.id, pretty=True)} group and article submissions to represent identified conflicts of interest. Configure the conflict of interest policy to be applied and specify the number of years of data to be retrieved from the OpenReview profile for conflict detection.',
+ cdate = tools.datetime_millis(venue.submission_stage.due_date) + (60*60*1000*24*3),
+ date_processes = [{
+ 'dates': ["#{4/cdate}", "#{4/mdate} + " + str(5000)],
+ 'script': venue.invitation_builder.get_process_content('../workflows/process/compute_conflicts_process.py')
+ }],
+ content = {
+ 'committee_name': {
+ 'value': self.match_group_name
+ },
+ 'committee_role': {
+ 'value': committee_role
}
},
- 'tail': {
- 'param': {
- 'type': 'profile',
- 'options': {
- 'group': self.match_group.id
+ edge = {
+ 'id': {
+ 'param': {
+ 'withInvitation': conflict_invitation_id,
+ 'optional': True
+ }
+ },
+ 'ddate': {
+ 'param': {
+ 'range': [ 0, 9999999999999 ],
+ 'optional': True,
+ 'deletable': True
+ }
+ },
+ 'cdate': {
+ 'param': {
+ 'range': [ 0, 9999999999999 ],
+ 'optional': True,
+ 'deletable': True
+ }
+ },
+ 'readers': readers + ['${2/tail}'],
+ 'writers': [venue_id],
+ 'signatures': {
+ 'param': {
+ 'items': [
+ { 'value': venue_id, 'optional': True },
+ { 'value': venue.get_program_chairs_id(), 'optional': True }
+ ],
+ 'default': [venue.get_program_chairs_id()]
+ }
+ },
+ 'head': {
+ 'param': {
+ 'type': 'note',
+ 'withInvitation': venue.get_submission_id()
+ }
+ },
+ 'tail': {
+ 'param': {
+ 'type': 'profile',
+ 'options': {
+ 'group': self.match_group.id
+ }
+ }
+ },
+ 'weight': {
+ 'param': {
+ 'minimum': -1
+ }
+ },
+ 'label': {
+ 'param': {
+ 'regex': '.*',
+ 'optional': True,
+ 'deletable': True
}
- }
- },
- 'weight': {
- 'param': {
- 'minimum': -1
- }
- },
- 'label': {
- 'param': {
- 'regex': '.*',
- 'optional': True,
- 'deletable': True
}
}
- }
- )
+ )
- invitation = self.venue.invitation_builder.save_invitation(invitation, replacement=True)
+ invitation = self.venue.invitation_builder.save_invitation(invitation, replacement=True)
- edit_invitations_builder = openreview.workflows.EditInvitationsBuilder(self.client, venue_id)
- edit_invitations_builder.set_edit_conflict_settings_invitation(conflict_invitation_id)
- edit_invitations_builder.set_edit_dates_one_level_invitation(conflict_invitation_id)
\ No newline at end of file
+ edit_invitations_builder = openreview.workflows.EditInvitationsBuilder(self.client, venue_id)
+ edit_invitations_builder.set_edit_conflict_settings_invitation(conflict_invitation_id)
+ edit_invitations_builder.set_edit_dates_one_level_invitation(conflict_invitation_id)
\ No newline at end of file
diff --git a/openreview/venue/venue.py b/openreview/venue/venue.py
index fde028024..9fa390365 100644
--- a/openreview/venue/venue.py
+++ b/openreview/venue/venue.py
@@ -152,10 +152,11 @@ def set_main_settings(self, request_note):
self.use_area_chairs = True
preferred_email_groups.append(self.get_area_chairs_id())
- if 'senior_area_chairs_name' in request_note.content: ## change this once we add support for SACs
- self.senior_area_chairs_name = request_note.content['senior_area_chairs_name']['value']
+ if request_note.content.get('senior_area_chairs_support',{}).get('value'):
+ if 'senior_area_chair_groups_names' in request_note.content:
+ self.senior_area_chair_roles = request_note.content['senior_area_chair_groups_names']['value']
+ self.senior_area_chairs_name = self.senior_area_chair_roles[0]
self.use_senior_area_chairs = True
- self.senior_area_chair_roles = request_note.content.get('senior_area_chair_roles', [self.senior_area_chairs_name])
preferred_email_groups.append(self.get_senior_area_chairs_id())
self.preferred_emails_groups = preferred_email_groups
@@ -388,6 +389,11 @@ def get_ethics_reviewers_name(self, pretty=True):
def anon_ethics_reviewers_name(self, pretty=True):
return self.get_anon_committee_name(self.ethics_reviewers_name)
+ def get_senior_area_chairs_name(self, pretty=True):
+ if pretty:
+ return self.get_committee_name(self.senior_area_chairs_name, pretty)
+ return self.senior_area_chairs_name
+
def get_area_chairs_name(self, pretty=True):
if pretty:
return self.get_committee_name(self.area_chairs_name, pretty)
@@ -1244,8 +1250,24 @@ def set_assignment_invitations(self, submission_deadline):
"""
invitation_prefix = self.support_user.replace('Support', 'Template')
+ if self.use_senior_area_chairs:
+ self.invitation_builder.set_assignment_invitation(committee_id=self.get_senior_area_chairs_id(), cdate=submission_deadline + (60*60*1000*24*7*2))
+
+ self.client.post_invitation_edit(
+ invitations=f'{invitation_prefix}/-/Reviewer_Assignment_Deployment',
+ signatures=[invitation_prefix],
+ content={
+ 'venue_id': { 'value': self.venue_id },
+ 'name': { 'value': f'{self.senior_area_chairs_name}_Assignment_Deployment' },
+ 'activation_date': { 'value': submission_deadline + (60*60*1000*24*7*2.1) },
+ 'committee_name': { 'value': self.senior_area_chairs_name },
+ 'committee_pretty_name': { 'value': self.get_senior_area_chairs_name(pretty=True) }
+ },
+ await_process=True
+ )
+
if self.use_area_chairs:
- self.invitation_builder.set_assignment_invitation(committee_id=self.get_area_chairs_id(), cdate=submission_deadline + (60*60*1000*24*7*2))
+ self.invitation_builder.set_assignment_invitation(committee_id=self.get_area_chairs_id(), cdate=submission_deadline + (60*60*1000*24*7*2.1))
self.client.post_invitation_edit(
invitations=f'{invitation_prefix}/-/Reviewer_Assignment_Deployment',
@@ -1276,11 +1298,15 @@ def set_assignment_invitations(self, submission_deadline):
def setup_matching_invitations(self):
"""Create matching configuration invitations for reviewers and area chairs (if enabled).
-
Sets up the matching invitations (affinity scores, conflicts, custom
max papers, etc.) without computing scores. Use
:meth:`setup_committee_matching` to also compute scores and conflicts.
"""
+
+ if self.use_senior_area_chairs:
+ venue_matching = matching.Matching(self, self.client.get_group(self.get_senior_area_chairs_id()), self.get_area_chairs_id())
+ venue_matching.setup_matching_invitations()
+
if self.use_area_chairs:
venue_matching = matching.Matching(self, self.client.get_group(self.get_area_chairs_id()))
venue_matching.setup_matching_invitations()
@@ -1290,10 +1316,14 @@ def setup_matching_invitations(self):
def setup_all_committees_matching(self):
"""Run full matching setup (invitations, affinity scores, conflicts) for all committees.
-
Sets up matching for area chairs (if enabled) and reviewers, including
computing affinity scores and conflicts with default settings.
"""
+
+ if self.use_senior_area_chairs:
+ venue_matching = matching.Matching(self, self.client.get_group(self.get_senior_area_chairs_id()), self.get_area_chairs_id())
+ venue_matching.setup()
+
if self.use_area_chairs:
venue_matching = matching.Matching(self, self.client.get_group(self.get_area_chairs_id()))
venue_matching.setup()
diff --git a/openreview/workflows/edit_invitations.py b/openreview/workflows/edit_invitations.py
index 6d7cbb729..2684cf264 100644
--- a/openreview/workflows/edit_invitations.py
+++ b/openreview/workflows/edit_invitations.py
@@ -547,13 +547,13 @@ def set_edit_content_invitation(self, super_invitation_id, content={}, process_f
'type': 'object[]',
'input': 'select',
'items': [
- {'value': {'value': 'CC BY 4.0', 'optional': True, 'description': 'CC BY 4.0'}, 'optional': True, 'description': 'CC BY 4.0'},
- {'value': {'value': 'CC BY-SA 4.0', 'optional': True, 'description': 'CC BY-SA 4.0'}, 'optional': True, 'description': 'CC BY-SA 4.0'},
- {'value': {'value': 'CC BY-NC 4.0', 'optional': True, 'description': 'CC BY-NC 4.0'}, 'optional': True, 'description': 'CC BY-NC 4.0'},
- {'value': {'value': 'CC BY-ND 4.0', 'optional': True, 'description': 'CC BY-ND 4.0'}, 'optional': True, 'description': 'CC BY-ND 4.0'},
- {'value': {'value': 'CC BY-NC-SA 4.0', 'optional': True, 'description': 'CC BY-NC-SA 4.0'}, 'optional': True, 'description': 'CC BY-NC-SA 4.0'},
- {'value': {'value': 'CC BY-NC-ND 4.0', 'optional': True, 'description': 'CC BY-NC-ND 4.0'}, 'optional': True, 'description': 'CC BY-NC-ND 4.0'},
- {'value': {'value': 'CC0 1.0', 'optional': True, 'description': 'CC0 1.0'}, 'optional': True, 'description': 'CC0 1.0'}
+ {'value': {'value': 'CC BY 4.0', 'description': 'CC BY 4.0'}, 'optional': True, 'description': 'CC BY 4.0'},
+ {'value': {'value': 'CC BY-SA 4.0', 'description': 'CC BY-SA 4.0'}, 'optional': True, 'description': 'CC BY-SA 4.0'},
+ {'value': {'value': 'CC BY-NC 4.0', 'description': 'CC BY-NC 4.0'}, 'optional': True, 'description': 'CC BY-NC 4.0'},
+ {'value': {'value': 'CC BY-ND 4.0', 'description': 'CC BY-ND 4.0'}, 'optional': True, 'description': 'CC BY-ND 4.0'},
+ {'value': {'value': 'CC BY-NC-SA 4.0', 'description': 'CC BY-NC-SA 4.0'}, 'optional': True, 'description': 'CC BY-NC-SA 4.0'},
+ {'value': {'value': 'CC BY-NC-ND 4.0', 'description': 'CC BY-NC-ND 4.0'}, 'optional': True, 'description': 'CC BY-NC-ND 4.0'},
+ {'value': {'value': 'CC0 1.0', 'description': 'CC0 1.0'}, 'optional': True, 'description': 'CC0 1.0'}
]
}
},
@@ -562,7 +562,7 @@ def set_edit_content_invitation(self, super_invitation_id, content={}, process_f
})
invitation.edit['invitation']['edit']['invitation']['edit']['note']['license'] = {
'param': {
- 'enum': ['${7/content/license/value}']
+ 'enum': ['${9/content/license/value}']
}
}
diff --git a/openreview/workflows/process/compute_affinity_scores_process.py b/openreview/workflows/process/compute_affinity_scores_process.py
index c5440fd40..ecaf89d63 100644
--- a/openreview/workflows/process/compute_affinity_scores_process.py
+++ b/openreview/workflows/process/compute_affinity_scores_process.py
@@ -12,6 +12,8 @@ def process(client, invitation):
venue_id = domain.id
committee_name = invitation.get_content_value('committee_name')
committee_id = f'{venue_id}/{committee_name}'
+ alternate_committee_id = invitation.get_content_value('alternate_committee_id')
+ alternate_committee_name = alternate_committee_id.split('/')[-1] if alternate_committee_id else None
status_invitation_id = domain.get_content_value('status_invitation_id')
request_form_id = domain.get_content_value('request_form_id')
@@ -39,13 +41,18 @@ def process(client, invitation):
venue = openreview.helpers.get_venue(client, venue_id, support_user)
match_group = client.get_group(committee_id)
+ alternate_match_group = client.get_group(alternate_committee_id) if alternate_committee_id else None
if not match_group.members:
- print(f'No members found in the {committee_name} group. No affinnity scores were computed')
+ print(f'No members found in the {committee_name} group. No affinity scores were computed')
+ return
+
+ if alternate_match_group and not alternate_match_group.members:
+ print(f'No members found in the {alternate_committee_name} group. No affinity scores were computed')
return
submissions, num_submissions = client.get_notes(content={ 'venueid': venue.get_submission_venue_id() }, limit=1, with_count=True, domain=venue_id)
- if not num_submissions:
+ if not num_submissions and not alternate_match_group:
print(f'No submissions found for the venue {venue_id}. No affinity scores were computed')
return
@@ -54,7 +61,8 @@ def process(client, invitation):
try:
matching_status = venue.setup_committee_matching(
committee_id=committee_id,
- compute_affinity_scores=affinity_scores_model
+ compute_affinity_scores=affinity_scores_model,
+ alternate_matching_group=alternate_committee_id
)
except Exception as e:
if 'Submissions not found.' in str(e):
@@ -63,7 +71,7 @@ def process(client, invitation):
matching_status['error'] = f'Could not compute affinity scores and conflicts since there are no {committee_name}.'
elif 'The alternate match group is empty' in str(e):
role_name = venue.get_area_chairs_name()
- matching_status['error'] = f'Could not compute affinity scores and conflicts since there are no {role_name}.'
+ matching_status['error'] = f'Could not compute affinity scores and conflicts since there are no {alternate_committee_name}.'
else:
matching_status['error'] = str(e)
diff --git a/openreview/workflows/process/compute_conflicts_process.py b/openreview/workflows/process/compute_conflicts_process.py
index 36354dd2b..19278be4b 100644
--- a/openreview/workflows/process/compute_conflicts_process.py
+++ b/openreview/workflows/process/compute_conflicts_process.py
@@ -71,11 +71,41 @@ def process(client, invitation):
else:
matching_status['error'] = str(e)
- if 'error' not in matching_status:
+ log = ''
+
+ if 'error' in matching_status:
+ print(matching_status['error'])
+ return
+ else:
if len(matching_status['no_profiles']):
num_revs = len(match_group.members) - len(matching_status['no_profiles'])
- print(f'Conflicts were successfully computed for {num_revs} users. The following users do not have a profile:', ''.join(matching_status['no_profiles']))
+ log = f'Conflicts were successfully computed for {num_revs} users. The following users do not have a profile:', ''.join(matching_status['no_profiles'])
else:
- print(f'Conflicts were successfully computed for all users in the {committee_name} group')
- else:
- print(matching_status['error'])
\ No newline at end of file
+ log = f'Conflicts were successfully computed for all users in the {committee_name} group'
+
+ # if process was triggered for ACs, and venue has SACs, post a note to the request form
+ area_chair_roles = domain.content.get('area_chair_roles', {}).get('value', [])
+ senior_area_chairs_id = domain.content.get('senior_area_chairs_id', {}).get('value')
+ assignment_inv_id = venue.get_assignment_id(senior_area_chairs_id, deployed=True) if senior_area_chairs_id else None
+ if committee_name in area_chair_roles and senior_area_chairs_id:
+ edges = client.get_edges_count(invitation=assignment_inv_id, domain=venue_id)
+ if edges == 0:
+ client.post_note_edit(
+ invitation=status_invitation_id,
+ signatures=[venue_id],
+ readers=[venue_id, support_user],
+ note=openreview.api.Note(
+ forum=request_form_id,
+ signatures=[venue_id],
+ content={
+ 'title': { 'value': f'{committee_name.replace("_", " ").title()} Conflicts Reminder' },
+ 'comment': { 'value': f'{committee_name.replace("_", " ").title()} conflicts have been successfully computed. Please note that you will need to recompute {committee_name.replace("_", " ").title()} conflicts once you deploy SAC-AC assignments to account for SAC conflicts.' }
+ }
+ )
+ )
+ log += f'Since your venue uses Senior Area Chairs, please remember to recompute {committee_name.replace("_", " ").title()} conflicts once you deploy SAC-AC assignments to account for SAC conflicts'
+
+ else:
+ print('SAC-AC assignments have been deployed, skipping posting reminder to recompute conflicts after SAC-AC deployment since there are already SAC-AC edges')
+
+ print(log)
\ No newline at end of file
diff --git a/openreview/workflows/templates.py b/openreview/workflows/templates.py
index 8406faf5b..8e05ac946 100644
--- a/openreview/workflows/templates.py
+++ b/openreview/workflows/templates.py
@@ -551,6 +551,16 @@ def setup_committee_group_recruitment_template_invitation(self):
'type': 'string'
}
}
+ },
+ 'committee_pretty_name': {
+ 'order': 3,
+ 'description': 'Committee pretty name',
+ 'value': {
+ 'param': {
+ 'type': 'string',
+ 'maxLength': 100
+ }
+ }
}
},
'domain': '${1/content/venue_id/value}',
@@ -559,7 +569,7 @@ def setup_committee_group_recruitment_template_invitation(self):
'writers': [self.template_domain],
'group': {
'id': '${2/content/committee_id/value}/Invited',
- 'description': 'Group consisting of the users who have been invited to serve as reviewers for the venue.',
+ 'description': 'Group consisting of the users who have been invited to serve as ${2/content/committee_pretty_name/value} for the venue.',
'readers': ['${3/content/venue_id/value}', '${3/content/committee_id/value}/Invited'],
'writers': ['${3/content/venue_id/value}'],
'signatures': ['${3/content/venue_id/value}'],
@@ -596,6 +606,16 @@ def setup_committee_group_recruitment_template_invitation(self):
'type': 'string'
}
}
+ },
+ 'committee_pretty_name': {
+ 'order': 3,
+ 'description': 'Committee pretty name',
+ 'value': {
+ 'param': {
+ 'type': 'string',
+ 'maxLength': 100
+ }
+ }
}
},
'domain': '${1/content/venue_id/value}',
@@ -604,7 +624,7 @@ def setup_committee_group_recruitment_template_invitation(self):
'writers': [self.template_domain],
'group': {
'id': '${2/content/committee_id/value}/Declined',
- 'description': 'Group consisting of the users who have been invited to serve as reviewers for the venue and have declined the invitation.',
+ 'description': 'Group consisting of the users who have been invited to serve as ${2/content/committee_pretty_name/value} for the venue and have declined the invitation.',
'readers': ['${3/content/venue_id/value}', '${3/content/committee_id/value}/Declined'],
'writers': ['${3/content/venue_id/value}'],
'signatures': ['${3/content/venue_id/value}'],
diff --git a/openreview/workflows/workflow_process/committee_group_template_process.py b/openreview/workflows/workflow_process/committee_group_template_process.py
index d644ea698..17e4b1749 100644
--- a/openreview/workflows/workflow_process/committee_group_template_process.py
+++ b/openreview/workflows/workflow_process/committee_group_template_process.py
@@ -61,7 +61,8 @@ def process(client, edit, invitation):
signatures=[invitation.domain],
content={
'venue_id': { 'value': venue_id },
- 'committee_id': { 'value': edit.group.id }
+ 'committee_id': { 'value': edit.group.id },
+ 'committee_pretty_name': { 'value': committee_pretty_name }
},
await_process=True
)
@@ -72,6 +73,7 @@ def process(client, edit, invitation):
content={
'venue_id': { 'value': venue_id },
'committee_id': { 'value': edit.group.id },
+ 'committee_pretty_name': { 'value': committee_pretty_name }
},
await_process=True
)
diff --git a/openreview/workflows/workflow_process/conference_review_workflow_deployment.py b/openreview/workflows/workflow_process/conference_review_workflow_deployment.py
index 8b71a69ca..50f027b9f 100644
--- a/openreview/workflows/workflow_process/conference_review_workflow_deployment.py
+++ b/openreview/workflows/workflow_process/conference_review_workflow_deployment.py
@@ -45,20 +45,35 @@ def process(client, edit, invitation):
start_date=submission_duedate + datetime.timedelta(days=3.5),
due_date=submission_duedate + datetime.timedelta(days=7)
)
- )
+ )
+
+ if venue.use_senior_area_chairs:
+ venue.bid_stages.append(
+ openreview.stages.BidStage(
+ venue.get_senior_area_chairs_id(),
+ start_date=submission_duedate + datetime.timedelta(days=3.5),
+ due_date=submission_duedate + datetime.timedelta(days=7)
+ )
+ )
venue.review_stage = openreview.stages.ReviewStage(
start_date=submission_deadline_datetime + datetime.timedelta(weeks=3.5),
due_date=submission_deadline_datetime + datetime.timedelta(weeks=5)
)
+ venue_committee = [
+ openreview.stages.CommentStage.Readers.SENIOR_AREA_CHAIRS_ASSIGNED,
+ openreview.stages.CommentStage.Readers.AREA_CHAIRS_ASSIGNED,
+ openreview.stages.CommentStage.Readers.REVIEWERS_ASSIGNED,
+ openreview.stages.CommentStage.Readers.AUTHORS
+ ]
venue.comment_stage = openreview.stages.CommentStage(
start_date=submission_deadline_datetime + datetime.timedelta(weeks=4),
end_date=submission_deadline_datetime + datetime.timedelta(weeks=6),
reader_selection=True,
check_mandatory_readers=True,
- readers=[openreview.stages.CommentStage.Readers.AREA_CHAIRS_ASSIGNED, openreview.stages.CommentStage.Readers.REVIEWERS_ASSIGNED, openreview.stages.CommentStage.Readers.AUTHORS],
- invitees=[openreview.stages.CommentStage.Readers.AREA_CHAIRS_ASSIGNED, openreview.stages.CommentStage.Readers.REVIEWERS_ASSIGNED, openreview.stages.CommentStage.Readers.AUTHORS]
+ readers=venue_committee,
+ invitees=venue_committee
)
venue.review_rebuttal_stage = openreview.stages.ReviewRebuttalStage(
@@ -66,7 +81,11 @@ def process(client, edit, invitation):
start_date=submission_deadline_datetime + datetime.timedelta(weeks=5.5),
due_date=submission_deadline_datetime + datetime.timedelta(weeks=6.5),
single_rebuttal=True,
- readers=[openreview.stages.ReviewRebuttalStage.Readers.AREA_CHAIRS_ASSIGNED, openreview.stages.ReviewRebuttalStage.Readers.REVIEWERS_ASSIGNED]
+ readers=[
+ openreview.stages.ReviewRebuttalStage.Readers.SENIOR_AREA_CHAIRS_ASSIGNED,
+ openreview.stages.ReviewRebuttalStage.Readers.AREA_CHAIRS_ASSIGNED,
+ openreview.stages.ReviewRebuttalStage.Readers.REVIEWERS_ASSIGNED
+ ]
)
if venue.use_area_chairs:
@@ -101,6 +120,10 @@ def process(client, edit, invitation):
await_process=True
)
+ venue.expertise_selection_stage = openreview.stages.ExpertiseSelectionStage(
+ due_date = venue.submission_stage.due_date
+ )
+
venue.create_submission_stage()
submission_deadline = full_submission_deadline if full_submission_deadline else note.content['submission_deadline']['value']
@@ -119,6 +142,12 @@ def process(client, edit, invitation):
venue.create_review_stage()
venue.create_comment_stage()
+ additional_readers = []
+ if venue.use_senior_area_chairs:
+ additional_readers.append(venue.get_senior_area_chairs_id(number='${5/content/noteNumber/value}'))
+ if venue.use_area_chairs:
+ additional_readers.append(venue.get_area_chairs_id(number='${5/content/noteNumber/value}'))
+
client.post_invitation_edit(
invitations=f'{invitation_prefix}/-/Note_Release',
signatures=[invitation_prefix],
@@ -130,7 +159,7 @@ def process(client, edit, invitation):
'stage_name': { 'value': 'Official_Review' },
'reviewers_name': { 'value': reviewers_name },
'authors_name': { 'value': authors_name },
- 'additional_readers': { 'value': [venue.get_area_chairs_id(number='${5/content/noteNumber/value}')] if venue.use_area_chairs else [] },
+ 'additional_readers': { 'value': additional_readers },
'description': { 'value': 'This step runs automatically at its "activation date", and releases official reviews to the specified readers.' }
},
await_process=True
@@ -165,7 +194,7 @@ def process(client, edit, invitation):
'stage_name': { 'value': 'Meta_Review' },
'reviewers_name': { 'value': reviewers_name },
'authors_name': { 'value': authors_name },
- 'additional_readers': { 'value': [venue.get_area_chairs_id(number='${5/content/noteNumber/value}')] if venue.use_area_chairs else [] },
+ 'additional_readers': { 'value': additional_readers },
'description': { 'value': 'This step runs automatically at its "activation date", and releases meta reviews to the specified readers.' }
},
await_process=True
@@ -194,7 +223,7 @@ def process(client, edit, invitation):
'stage_name': { 'value': 'Decision' },
'reviewers_name': { 'value': reviewers_name },
'authors_name': { 'value': authors_name },
- 'additional_readers': { 'value': [venue.get_area_chairs_id(number='${5/content/noteNumber/value}')] if venue.use_area_chairs else [] },
+ 'additional_readers': { 'value': additional_readers },
'description': { 'value': 'This step runs automatically at its "activation date", and releases decisions to the specified readers.' }
},
await_process=True
@@ -249,6 +278,8 @@ def process(client, edit, invitation):
'area_chairs_support': { 'readers': [support_user] },
'area_chairs_name': { 'readers': [support_user] },
'area_chair_groups_names': { 'readers': [support_user] },
+ 'senior_area_chairs_support': { 'readers': [support_user] },
+ 'senior_area_chair_groups_names': { 'readers': [support_user] },
'venue_organizer_agreement': { 'readers': [support_user] },
'program_chair_console': { 'value': f'https://openreview.net/group?id={venue_id}/Program_Chairs' },
'workflow_timeline': { 'value': f'https://openreview.net/group/edit?id={venue_id}' }
diff --git a/openreview/workflows/workflow_process/edit_recommendation_field_name_process.py b/openreview/workflows/workflow_process/edit_recommendation_field_name_process.py
index 0a8f878fb..24b636c44 100644
--- a/openreview/workflows/workflow_process/edit_recommendation_field_name_process.py
+++ b/openreview/workflows/workflow_process/edit_recommendation_field_name_process.py
@@ -17,4 +17,29 @@ def process(client, edit, invitation):
}
}
)
- )
\ No newline at end of file
+ )
+
+ senior_area_chairs_name = domain.get_content_value('senior_area_chairs_name')
+ if senior_area_chairs_name:
+ sac_acronym = ''.join([s[0].upper() for s in senior_area_chairs_name.split('_')])
+ content = edit.content['content']['value']
+
+ sac_revision_invitation_id = f'{venue_id}/-/{meta_review_name}_{sac_acronym}_Revision'
+ invitation = client.get_invitation(sac_revision_invitation_id)
+ if invitation:
+ client.post_invitation_edit(
+ invitations = meta_invitation_id,
+ signatures = [venue_id],
+ invitation = openreview.api.Invitation(
+ id = sac_revision_invitation_id,
+ edit = {
+ 'invitation': {
+ 'edit': {
+ 'note': {
+ 'content': content
+ }
+ }
+ }
+ }
+ )
+ )
\ No newline at end of file
diff --git a/openreview/workflows/workflow_process/request_form_preprocess.py b/openreview/workflows/workflow_process/request_form_preprocess.py
index 62e64549e..b21831534 100644
--- a/openreview/workflows/workflow_process/request_form_preprocess.py
+++ b/openreview/workflows/workflow_process/request_form_preprocess.py
@@ -5,6 +5,9 @@ def process(client, edit, invitation):
if len(venue_agreement) != 9:
raise openreview.OpenReviewException('Please be sure to acknowledge and agree to all terms in the Venue Organizer Agreement.')
+
+ if request_note.content.get('senior_area_chairs_support',{}).get('value') and not request_note.content.get('area_chairs_support',{}).get('value'):
+ raise openreview.OpenReviewException('If your venue has senior area chairs, it must also have area chairs.')
submission_deadline = request_note.content['submission_deadline']['value']
if submission_deadline < openreview.tools.datetime_millis(datetime.datetime.now()):
@@ -18,7 +21,12 @@ def process(client, edit, invitation):
if full_submission_deadline and full_submission_deadline < submission_deadline:
raise openreview.OpenReviewException('The full submission deadline must be after the submission deadline.')
- reviewer_groups_namess = set(request_note.content.get('reviewer_groups_names', {}).get('value', []))
- area_chair_groups_namess = set(request_note.content.get('area_chair_groups_names', {}).get('value', []))
- if reviewer_groups_namess & area_chair_groups_namess:
- raise openreview.OpenReviewException('The reviewer role name and area chair role name must be different.')
\ No newline at end of file
+ reviewer_groups_names = set(request_note.content.get('reviewer_groups_names', {}).get('value', []))
+ area_chair_groups_names = set(request_note.content.get('area_chair_groups_names', {}).get('value', []))
+ senior_area_chair_groups_names = set(request_note.content.get('senior_area_chair_groups_names', {}).get('value', []))
+ if reviewer_groups_names & area_chair_groups_names:
+ raise openreview.OpenReviewException('The reviewer role name and area chair role name must be different.')
+ if senior_area_chair_groups_names & reviewer_groups_names:
+ raise openreview.OpenReviewException('The senior area chair role name and reviewer role name must be different.')
+ if senior_area_chair_groups_names & area_chair_groups_names:
+ raise openreview.OpenReviewException('The senior area chair role name and area chair role name must be different.')
\ No newline at end of file
diff --git a/openreview/workflows/workflows.py b/openreview/workflows/workflows.py
index c47dda9ad..12b147416 100644
--- a/openreview/workflows/workflows.py
+++ b/openreview/workflows/workflows.py
@@ -264,8 +264,32 @@ def set_conference_review_request(self):
}
}
},
- 'colocated': {
+ 'senior_area_chairs_support': {
'order': 15,
+ 'description': "Does your venue have senior area chairs? Leave unchecked if your venue does not have senior area chairs. In order to have senior area chairs, your venue must also have area chairs.",
+ 'value': {
+ 'param': {
+ 'type': "boolean",
+ 'enum': [{ 'value': True, 'description': 'Yes, my venue does have Senior Area Chairs.'}],
+ 'input': 'checkbox',
+ 'optional': True,
+ 'deletable': True
+ }
+ }
+ },
+ 'senior_area_chair_groups_names': {
+ 'order': 16,
+ 'description': 'Please provide the designated name to be used for senior area chairs. Use underscores for spaces and capitalize as needed. Default is "Senior_Area_Chairs". Ignore if your venue does not have senior area chairs.',
+ 'value': {
+ 'param': {
+ 'type': 'string[]',
+ 'regex': '^[a-zA-Z_]+$',
+ 'default': ['Senior_Area_Chairs']
+ }
+ }
+ },
+ 'colocated': {
+ 'order': 17,
'description': 'Please provide the name of the conference, organization, or academic institution with which your event is colocated. If your event is independent of a conference or organization, you can leave this blank or write "independent"',
'value': {
'param': {
@@ -277,7 +301,7 @@ def set_conference_review_request(self):
}
},
'previous_venue': {
- 'order': 16,
+ 'order': 18,
'description': 'If possible, please provide a link to the previous iteration of this venue on OpenReview.',
'value': {
'param': {
@@ -289,7 +313,7 @@ def set_conference_review_request(self):
}
},
'expected_submissions': {
- 'order': 17,
+ 'order': 19,
'description': 'How many submissions do you expect to receive for this venue? Please provide a number. This will help us plan for the expected load on our servers.',
'value': {
'param': {
@@ -299,7 +323,7 @@ def set_conference_review_request(self):
}
},
'how_did_you_hear_about_us': {
- 'order': 18,
+ 'order': 20,
'description': 'How did you hear about OpenReview?',
'value': {
'param': {
@@ -312,7 +336,7 @@ def set_conference_review_request(self):
}
},
'other_important_information': {
- 'order': 19,
+ 'order': 21,
'description': 'Please provide any other important information about your venue that you would like to share with OpenReview. Please use this space to clarify any questions for which you could not use any of the provided options, and to clarify any other information that you think we may need.',
'value': {
'param': {
@@ -325,7 +349,7 @@ def set_conference_review_request(self):
}
},
'venue_organizer_agreement': {
- 'order': 20,
+ 'order': 22,
'description': 'In order to use OpenReview, venue chairs must agree to the following:',
'value': {
'param': {
diff --git a/tests/test_abstract_deadline.py b/tests/test_abstract_deadline.py
index 4c9808e66..68c57963e 100644
--- a/tests/test_abstract_deadline.py
+++ b/tests/test_abstract_deadline.py
@@ -41,6 +41,7 @@ def test_setup(self, openreview_client, helpers):
'full_submission_deadline': { 'value': openreview.tools.datetime_millis(full_submission_due_date) },
'reviewer_groups_names': { 'value': ['Reviewers'] },
'area_chair_groups_names': { 'value': ['Area_Chairs'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'expected_submissions': { 'value': 20 },
'venue_organizer_agreement': {
'value': [
diff --git a/tests/test_acs_and_reviewers.py b/tests/test_acs_and_reviewers.py
index be294138c..ccac527d7 100644
--- a/tests/test_acs_and_reviewers.py
+++ b/tests/test_acs_and_reviewers.py
@@ -50,6 +50,7 @@ def test_setup(self, openreview_client, helpers):
'reviewer_groups_names': { 'value': ['Reviewers'] },
'area_chairs_support': { 'value': True },
'area_chair_groups_names': { 'value': ['Action_Editors'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'expected_submissions': { 'value': 500 },
'venue_organizer_agreement': {
'value': [
diff --git a/tests/test_assign_committee_open_deadline.py b/tests/test_assign_committee_open_deadline.py
index 2ea5e4958..4f19ec087 100644
--- a/tests/test_assign_committee_open_deadline.py
+++ b/tests/test_assign_committee_open_deadline.py
@@ -41,6 +41,7 @@ def test_setup_venue(self, openreview_client, helpers):
'reviewer_groups_names': { 'value': ['Reviewers'] },
'area_chairs_support': { 'value': True },
'area_chair_groups_names': { 'value': ['Area_Chairs'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'colocated': { 'value': 'Independent' },
'previous_venue': { 'value': 'XYZ.cc/2024/Conference' },
'expected_submissions': { 'value': 100 },
diff --git a/tests/test_change_venue_email.py b/tests/test_change_venue_email.py
index 9c617662c..49eb43e28 100644
--- a/tests/test_change_venue_email.py
+++ b/tests/test_change_venue_email.py
@@ -44,6 +44,7 @@ def test_setup(self, openreview_client, helpers):
'submission_deadline': { 'value': openreview.tools.datetime_millis(due_date) },
'reviewer_groups_names': { 'value': ['Reviewers'] },
'area_chair_groups_names': { 'value': ['Area_Chairs'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'colocated': { 'value': 'Independent' },
'previous_venue': { 'value': 'VenueEmail.cc/2024/Conference' },
'expected_submissions': { 'value': 1000 },
diff --git a/tests/test_iclr_conference_with_templates.py b/tests/test_iclr_conference_with_templates.py
new file mode 100644
index 000000000..3120f4c58
--- /dev/null
+++ b/tests/test_iclr_conference_with_templates.py
@@ -0,0 +1,1165 @@
+import pytest
+import datetime
+import openreview
+from selenium.webdriver.common.by import By
+
+class TestSimpleDualAnonymous():
+
+ def test_setup(self, openreview_client, helpers):
+ super_id = 'openreview.net'
+ support_group_id = super_id + '/Support'
+
+ helpers.create_user('programchair@iclr.cc', 'ProgramChair', 'ICLROne')
+ helpers.create_user('reviewer_one@iclr.cc', 'Reviewer', 'ICLROne')
+ helpers.create_user('reviewer_two@iclr.cc', 'Reviewer', 'ICLRTwo')
+ helpers.create_user('reviewer_three@iclr.cc', 'Reviewer', 'ICLRThree')
+ helpers.create_user('areachair_one@iclr.cc', 'AC', 'ICLROne')
+ helpers.create_user('areachair_two@iclr.cc', 'AC', 'ICLRTwo')
+ helpers.create_user('senioractioneditor_one@iclr.cc', 'SAE', 'ICLROne')
+ helpers.create_user('senioractioneditor_two@iclr.cc', 'SAE', 'ICLRTwo')
+ pc_client=openreview.api.OpenReviewClient(username='programchair@iclr.cc', password=helpers.strong_password)
+
+ assert openreview_client.get_invitation('openreview.net/-/Edit')
+ assert openreview_client.get_group('openreview.net/Support/Venue_Request')
+ assert openreview_client.get_invitation('openreview.net/Support/Venue_Request/-/Conference_Review_Workflow')
+ assert openreview_client.get_invitation('openreview.net/Support/Venue_Request/Conference_Review_Workflow/-/Deployment')
+ assert openreview_client.get_invitation('openreview.net/Support/Venue_Request/Conference_Review_Workflow/-/Comment')
+
+ now = datetime.datetime.now()
+ due_date = now + datetime.timedelta(days=2)
+
+ request = pc_client.post_note_edit(invitation='openreview.net/Support/Venue_Request/-/Conference_Review_Workflow',
+ signatures=['~ProgramChair_ICLROne1'],
+ note=openreview.api.Note(
+ content={
+ 'official_venue_name': { 'value': 'International Conference on Learning Representations' },
+ 'abbreviated_venue_name': { 'value': 'ICLR 2026' },
+ 'venue_website_url': { 'value': 'https://iclr.cc/Conferences/2026' },
+ 'location': { 'value': 'Minnetonka, Minnesota' },
+ 'venue_start_date': { 'value': openreview.tools.datetime_millis(now + datetime.timedelta(weeks=52)) },
+ 'program_chair_emails': { 'value': ['programchair@iclr.cc'] },
+ 'contact_email': { 'value': 'iclr2026.programchairs@gmail.com' },
+ 'submission_start_date': { 'value': openreview.tools.datetime_millis(now) },
+ 'submission_deadline': { 'value': openreview.tools.datetime_millis(due_date) },
+ 'full_submission_deadline': { 'value': openreview.tools.datetime_millis(now + datetime.timedelta(days=3)) },
+ 'reviewer_groups_names': { 'value': ['Reviewers'] },
+ 'area_chairs_support': { 'value': True },
+ 'area_chair_groups_names': { 'value': ['Action_Editors'] },
+ 'senior_area_chairs_support': { 'value': True },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Action_Editors'] },
+ 'expected_submissions': { 'value': 12000 },
+ 'venue_organizer_agreement': {
+ 'value': [
+ 'OpenReview natively supports a wide variety of reviewing workflow configurations. However, if we want significant reviewing process customizations or experiments, we will detail these requests to the OpenReview staff at least three months in advance.',
+ 'We will ask authors and reviewers to create an OpenReview Profile at least two weeks in advance of the paper submission deadlines.',
+ 'When assembling our group of reviewers, we will only include email addresses or OpenReview Profile IDs of people we know to have authored publications relevant to our venue. (We will not solicit new reviewers using an open web form, because unfortunately some malicious actors sometimes try to create "fake ids" aiming to be assigned to review their own paper submissions.)',
+ 'We acknowledge that, if our venue\'s reviewing workflow is non-standard, or if our venue is expecting more than a few hundred submissions for any one deadline, we should designate our own Workflow Chair, who will read the OpenReview documentation and manage our workflow configurations throughout the reviewing process.',
+ 'We acknowledge that OpenReview staff work Monday-Friday during standard business hours US Eastern time, and we cannot expect support responses outside those times. For this reason, we recommend setting submission and reviewing deadlines Monday through Thursday.',
+ 'We will treat the OpenReview staff with kindness and consideration.',
+ 'We acknowledge that authors and reviewers will be required to share their preferred email.',
+ 'We acknowledge that review counts will be collected for all the reviewers and publicly available in OpenReview.',
+ 'We acknowledge that metadata for accepted papers will be publicly released in OpenReview.'
+ ]
+ }
+ }
+ ))
+
+ helpers.await_queue_edit(openreview_client, edit_id=request['id'])
+
+ # deploy the venue
+ edit = openreview_client.post_note_edit(invitation=f'openreview.net/Support/Venue_Request/Conference_Review_Workflow/-/Deployment',
+ signatures=[support_group_id],
+ note=openreview.api.Note(
+ id=request['note']['id'],
+ content={
+ 'venue_id': { 'value': 'ICLR.cc/2026/Conference' }
+ }
+ ))
+
+ helpers.await_queue_edit(openreview_client, edit_id=edit['id'])
+
+ group = openreview.tools.get_group(openreview_client, 'ICLR.cc/2026/Conference')
+
+ assert 'preferred_emails_groups' in group.content and group.content['preferred_emails_groups']['value'] == [
+ 'ICLR.cc/2026/Conference/Reviewers',
+ 'ICLR.cc/2026/Conference/Authors',
+ 'ICLR.cc/2026/Conference/Action_Editors',
+ 'ICLR.cc/2026/Conference/Senior_Action_Editors'
+ ]
+ assert 'preferred_emails_id' in group.content and group.content['preferred_emails_id']['value'] == 'ICLR.cc/2026/Conference/-/Preferred_Emails'
+ invitation = openreview_client.get_invitation('ICLR.cc/2026/Conference/-/Preferred_Emails')
+ assert invitation
+
+ assert group.content['senior_area_chair_roles']['value'] == ['Senior_Action_Editors']
+ assert group.content['senior_area_chairs_id']['value'] == 'ICLR.cc/2026/Conference/Senior_Action_Editors'
+ assert group.content['senior_area_chairs_assignment_id']['value'] == 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Assignment'
+ assert group.content['senior_area_chairs_affinity_score_id']['value'] == 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Affinity_Score'
+ assert group.content['senior_area_chairs_name']['value'] == 'Senior_Action_Editors'
+ assert group.content['sac_paper_assignments']['value'] == False
+ assert group.content['senior_area_chairs_conflict_id']['value'] == 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Conflict'
+
+ group = openreview.tools.get_group(openreview_client, 'ICLR.cc/2026/Conference/Senior_Action_Editors')
+ assert group.readers == [
+ 'ICLR.cc/2026/Conference',
+ 'ICLR.cc/2026/Conference/Senior_Action_Editors'
+ ]
+ assert group.domain == 'ICLR.cc/2026/Conference'
+
+ group = openreview.tools.get_group(openreview_client, 'ICLR.cc/2026/Conference/Senior_Action_Editors/Invited')
+ assert group.readers == [
+ 'ICLR.cc/2026/Conference',
+ 'ICLR.cc/2026/Conference/Senior_Action_Editors/Invited'
+ ]
+ assert group.domain == 'ICLR.cc/2026/Conference'
+
+ group = openreview.tools.get_group(openreview_client, 'ICLR.cc/2026/Conference/Senior_Action_Editors/Declined')
+ assert group.readers == [
+ 'ICLR.cc/2026/Conference',
+ 'ICLR.cc/2026/Conference/Senior_Action_Editors/Declined'
+ ]
+ assert group.domain == 'ICLR.cc/2026/Conference'
+
+ assert openreview.tools.get_invitation(openreview_client, 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Message')
+ assert openreview.tools.get_invitation(openreview_client, 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Members')
+
+ submission_invitation = openreview_client.get_invitation('ICLR.cc/2026/Conference/-/Submission')
+ assert submission_invitation
+ assert submission_invitation.duedate
+
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Reviewers/-/Expertise_Selection')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Action_Editors/-/Expertise_Selection')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Expertise_Selection')
+
+ group = openreview.tools.get_group(openreview_client, 'ICLR.cc/2026/Conference/Reviewers')
+ assert group.domain == 'ICLR.cc/2026/Conference'
+ assert group.readers == [
+ 'ICLR.cc/2026/Conference',
+ 'ICLR.cc/2026/Conference/Reviewers',
+ 'ICLR.cc/2026/Conference/Senior_Action_Editors',
+ 'ICLR.cc/2026/Conference/Action_Editors'
+ ]
+
+ group = openreview.tools.get_group(openreview_client, 'ICLR.cc/2026/Conference/Action_Editors')
+ assert group.readers == [
+ 'ICLR.cc/2026/Conference',
+ 'ICLR.cc/2026/Conference/Action_Editors',
+ 'ICLR.cc/2026/Conference/Senior_Action_Editors'
+ ]
+
+ group = openreview.tools.get_group(openreview_client, 'ICLR.cc/2026/Conference/Senior_Action_Editors')
+ assert group.readers == [
+ 'ICLR.cc/2026/Conference',
+ 'ICLR.cc/2026/Conference/Senior_Action_Editors'
+ ]
+
+ group = openreview.tools.get_group(openreview_client, 'ICLR.cc/2026/Conference/Senior_Action_Editors/Invited')
+ assert group.readers == [
+ 'ICLR.cc/2026/Conference',
+ 'ICLR.cc/2026/Conference/Senior_Action_Editors/Invited'
+ ]
+
+ group = openreview.tools.get_group(openreview_client, 'ICLR.cc/2026/Conference/Senior_Action_Editors/Declined')
+ assert group.readers == [
+ 'ICLR.cc/2026/Conference',
+ 'ICLR.cc/2026/Conference/Senior_Action_Editors/Declined'
+ ]
+
+ domain_content = openreview.tools.get_group(openreview_client, 'ICLR.cc/2026/Conference').content
+ assert domain_content['senior_area_chair_roles']['value'] == ['Senior_Action_Editors']
+ assert domain_content['senior_area_chairs_id']['value'] == 'ICLR.cc/2026/Conference/Senior_Action_Editors'
+ assert domain_content['senior_area_chairs_assignment_id']['value'] == 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Assignment'
+ assert domain_content['senior_area_chairs_affinity_score_id']['value'] == 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Affinity_Score'
+ assert domain_content['senior_area_chairs_name']['value'] == 'Senior_Action_Editors'
+ assert domain_content['sac_paper_assignments']['value'] == False
+ assert domain_content['senior_area_chairs_conflict_id']['value'] == 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Conflict'
+
+ def test_sac_recruitment(self, client, openreview_client, helpers, request_page, selenium):
+
+ # use invitation to recruit reviewers
+ edit = openreview_client.post_group_edit(
+
+ invitation='ICLR.cc/2026/Conference/Senior_Action_Editors/-/Recruitment_Request',
+ content={
+ 'invitee_details': { 'value': 'senioractioneditor_one@iclr.cc, SAE ICLROne\nsenioractioneditor_two@iclr.cc, SAE ICLRTwo\nSAC@mail.com\nceleste_martinez1\ninvalid_emäil@iclr.cc' },
+ 'invite_message_subject_template': { 'value': '[ICLR 2026] Invitation to serve as Senior Action Editor' },
+ 'invite_message_body_template': { 'value': 'Dear {{fullname}},\n\nWe are pleased to invite you to serve as a Senior Action Editor for the ICLR 2026 Conference.\n\nPlease accept or decline the invitation using the link below:\n\n{{invitation_url}}\n\nBest regards,\nICLR 2026 Program Chairs' },
+ },
+ group=openreview.api.Group()
+ )
+ helpers.await_queue_edit(openreview_client, edit_id=edit['id'])
+ helpers.await_queue_edit(openreview_client, edit_id=edit['id'], process_index=1)
+
+ invited_group = openreview_client.get_group('ICLR.cc/2026/Conference/Senior_Action_Editors/Invited')
+ assert set(invited_group.members) == {'~SAE_ICLROne1', '~SAE_ICLRTwo1', 'sac@mail.com'}
+ assert openreview_client.get_group('ICLR.cc/2026/Conference/Senior_Action_Editors/Declined').members == []
+ assert openreview_client.get_group('ICLR.cc/2026/Conference/Senior_Action_Editors').members == []
+
+ edits = openreview_client.get_group_edits(group_id='ICLR.cc/2026/Conference/Senior_Action_Editors/Invited', sort='tcdate:desc')
+
+ messages = openreview_client.get_messages(to='programchair@iclr.cc', subject = 'Recruitment request status for ICLR 2026 Senior Action Editor Group')
+ assert len(messages) == 1
+ assert messages[0]['content']['text'] == f'''The recruitment request process for the Senior Action Editor Group has been completed.
+
+Invited: 3
+Already invited: 0
+Already member: 0
+Errors: 2
+
+For more details, please check the following links:
+
+- [recruitment request details](https://openreview.net/group/revisions?id=ICLR.cc/2026/Conference/Senior_Action_Editors&editId={edit['id']})
+- [invited list](https://openreview.net/group/revisions?id=ICLR.cc/2026/Conference/Senior_Action_Editors/Invited&editId={edits[0].id})
+- [all invited list](https://openreview.net/group/edit?id=ICLR.cc/2026/Conference/Senior_Action_Editors/Invited)'''
+
+ def test_submissions(self, client, openreview_client, helpers, test_client):
+
+ test_client = openreview.api.OpenReviewClient(token=test_client.token)
+
+ domains = ['umass.edu', 'amazon.com', 'fb.com', 'cs.umass.edu', 'google.com', 'mit.edu', 'deepmind.com', 'co.ux', 'apple.com', 'nvidia.com']
+ for domain in domains:
+ helpers.create_user(f'eddie@{domain}', 'Eddie', f'{domain.split(".")[0].capitalize()}')
+
+ for i in range(1,11):
+ note = openreview.api.Note(
+ license = 'CC BY 4.0',
+ content = {
+ 'title': { 'value': 'Paper title ' + str(i) },
+ 'abstract': { 'value': 'This is an abstract ' + str(i) },
+ 'authorids': { 'value': ['~SomeFirstName_User1', '~Eddie_' + domains[i % 10].split('.')[0].capitalize() + '1'] },
+ 'authors': { 'value': ['SomeFirstName User', 'Eddie ' + domains[i % 10].split('.')[0].capitalize()] },
+ 'keywords': { 'value': ['machine learning', 'nlp'] },
+ 'pdf': {'value': '/pdf/' + 'p' * 40 +'.pdf' },
+ 'email_sharing': { 'value': 'We authorize the sharing of all author emails with Program Chairs.' },
+ 'data_release': { 'value': 'We authorize the release of our submission and author names to the public in the event of acceptance.' }
+ }
+ )
+ if i == 1 or i == 10:
+ note.content['authors']['value'].append('SAE ICLROne')
+ note.content['authorids']['value'].append('~SAE_ICLROne1')
+
+ test_client.post_note_edit(invitation='ICLR.cc/2026/Conference/-/Submission',
+ signatures=['~SomeFirstName_User1'],
+ note=note)
+
+ helpers.await_queue_edit(openreview_client, invitation='ICLR.cc/2026/Conference/-/Submission', count=10)
+
+ submissions = openreview_client.get_notes(invitation='ICLR.cc/2026/Conference/-/Submission', sort='number:asc')
+ assert len(submissions) == 10
+ assert submissions[-1].readers == ['ICLR.cc/2026/Conference', '~SomeFirstName_User1', '~Eddie_Umass1', '~SAE_ICLROne1']
+
+ def test_post_submission(self, client, openreview_client, helpers, test_client):
+
+ pc_client=openreview.api.OpenReviewClient(username='programchair@iclr.cc', password=helpers.strong_password)
+
+ # close submission abstract
+ now = datetime.datetime.now()
+
+ edit = pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Submission/Dates',
+ content={
+ 'activation_date': { 'value': openreview.tools.datetime_millis(now - datetime.timedelta(days=1)) },
+ 'due_date': { 'value': openreview.tools.datetime_millis(now - datetime.timedelta(hours=5)) }
+ }
+ )
+
+ helpers.await_queue_edit(openreview_client, edit_id=edit['id'])
+ helpers.await_queue_edit(openreview_client, 'ICLR.cc/2026/Conference/-/Full_Submission-0-1', count=2)
+ helpers.await_queue_edit(openreview_client, 'ICLR.cc/2026/Conference/Reviewers/-/Submission_Message-0-1', count=2)
+ helpers.await_queue_edit(openreview_client, 'ICLR.cc/2026/Conference/Action_Editors/-/Submission_Message-0-1', count=2)
+
+ full_submission_inv = openreview_client.get_invitations(invitation='ICLR.cc/2026/Conference/-/Full_Submission')
+ assert len(full_submission_inv) == 10
+
+ # release submissions to the public
+ edit = pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Submission_Change_Before_Bidding/Readers',
+ content={
+ 'readers': { 'value': ['everyone'] }
+ }
+ )
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/-/Submission_Change_Before_Bidding-0-1', count=2)
+
+ edit = pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Submission_Change_Before_Bidding/Dates',
+ content={
+ 'activation_date': { 'value': openreview.tools.datetime_millis(now - datetime.timedelta(days=1)) }
+ }
+ )
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/-/Submission_Change_Before_Bidding-0-1', count=3)
+
+ submission_invitation = pc_client.get_invitation('ICLR.cc/2026/Conference/-/Submission')
+ assert submission_invitation.expdate < openreview.tools.datetime_millis(now)
+
+ submissions = pc_client.get_notes(invitation='ICLR.cc/2026/Conference/-/Submission', sort='number:asc')
+ submission = submissions[0]
+ assert len(submissions) == 10
+ assert submission.license == 'CC BY 4.0'
+ assert submission.readers == ['everyone']
+ assert submissions[0].odate
+ assert not submissions[0].pdate
+ assert '_bibtex' in submission.content
+ assert 'author={Anonymous}' in submission.content['_bibtex']['value']
+ year = datetime.datetime.now().year
+ valid_bibtex = '''@inproceedings{
+anonymous'''+str(year)+'''paper,
+title={Paper title 1},
+author={Anonymous},
+booktitle={Submitted to International Conference on Learning Representations},
+year={'''+str(year)+'''},
+url={https://openreview.net/forum?id='''
+
+ valid_bibtex = valid_bibtex + submission.forum + '''},
+note={under review}
+}'''
+ assert submission.content['_bibtex']['value'] == valid_bibtex
+
+ full_submission_inv = openreview_client.get_invitation(id='ICLR.cc/2026/Conference/-/Full_Submission')
+ # make sure pdfs remain hidden
+ content = full_submission_inv.edit['invitation']['edit']['note']['content']
+ content['pdf']['readers'] = [
+ 'ICLR.cc/2026/Conference',
+ 'ICLR.cc/2026/Conference/Submission1/Authors'
+ ]
+
+ # allow authors to edit license field
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Full_Submission/Form_Fields',
+ content={
+ 'content': {
+ 'value': content
+ },
+ 'license': {
+ 'value': [
+ {'value': 'CC BY 4.0', 'description': 'CC BY 4.0'},
+ {'value': 'CC BY-SA 4.0', 'description': 'CC BY-SA 4.0'},
+ {'value': 'CC0 1.0', 'description': 'CC0 1.0'}
+ ]
+ }
+ }
+ )
+
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/-/Full_Submission-0-1', count=3)
+
+ test_client = openreview.api.OpenReviewClient(token=test_client.token)
+
+ revision_note = test_client.post_note_edit(
+ invitation = f'ICLR.cc/2026/Conference/Submission{submission.number}/-/Full_Submission',
+ signatures = [f'ICLR.cc/2026/Conference/Submission{submission.number}/Authors'],
+ note = openreview.api.Note(
+ license = 'CC0 1.0',
+ content = {
+ 'title': { 'value': submission.content['title']['value'] + ' license revision' },
+ 'abstract': { 'value': submission.content['abstract']['value'] },
+ 'authorids': { 'value': submission.content['authorids']['value'] },
+ 'authors': { 'value': submission.content['authors']['value'] },
+ 'keywords': {'value': submission.content['keywords']['value']},
+ 'pdf': { 'value': submission.content['pdf']['value'] },
+ 'email_sharing': { 'value': submission.content['email_sharing']['value'] },
+ 'data_release': { 'value': submission.content['data_release']['value'] }
+ }
+ ))
+ helpers.await_queue_edit(openreview_client, edit_id=revision_note['id'])
+
+ assert revision_note['readers'] == ['ICLR.cc/2026/Conference', f'ICLR.cc/2026/Conference/Submission{submission.number}/Authors']
+
+ submission = pc_client.get_notes(invitation='ICLR.cc/2026/Conference/-/Submission', sort='number:asc')[0]
+ assert submission.license == 'CC0 1.0'
+ assert submission.readers == ['everyone']
+ assert 'readers' in submission.content['pdf'] and submission.content['pdf']['readers'] == ['ICLR.cc/2026/Conference', f'ICLR.cc/2026/Conference/Submission{submission.number}/Authors']
+ assert 'readers' in submission.content['authors'] and submission.content['authors']['readers'] == ['ICLR.cc/2026/Conference', f'ICLR.cc/2026/Conference/Submission{submission.number}/Authors']
+ assert 'readers' in submission.content['authorids'] and submission.content['authorids']['readers'] == ['ICLR.cc/2026/Conference', f'ICLR.cc/2026/Conference/Submission{submission.number}/Authors']
+ assert '_bibtex' in submission.content
+ assert 'author={Anonymous}' in submission.content['_bibtex']['value']
+ valid_bibtex = '''@inproceedings{
+anonymous'''+str(year)+'''paper,
+title={Paper title 1 license revision},
+author={Anonymous},
+booktitle={Submitted to International Conference on Learning Representations},
+year={'''+str(year)+'''},
+url={https://openreview.net/forum?id='''
+
+ valid_bibtex = valid_bibtex + submission.forum + '''},
+note={under review}
+}'''
+ assert submission.content['_bibtex']['value'] == valid_bibtex
+
+ def test_SAC_bidding(self, client, openreview_client, helpers, test_client, request_page, selenium):
+
+ pc_client=openreview.api.OpenReviewClient(username='programchair@iclr.cc', password=helpers.strong_password)
+
+ bid_invitation = openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Bid')
+ assert bid_invitation
+ assert bid_invitation.edit['label']['param']['enum'] == ['Very High', 'High', 'Neutral', 'Low', 'Very Low']
+ assert bid_invitation.minReplies == 50
+ assert bid_invitation.edit['head']['param']['options']['group'] == 'ICLR.cc/2026/Conference/Action_Editors'
+ assert bid_invitation.edit['tail']['param']['options']['group'] == 'ICLR.cc/2026/Conference/Senior_Action_Editors'
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Bid/Dates')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Bid/Settings')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Affinity_Score')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Affinity_Score/Model')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Affinity_Score/Dates')
+ assert not openreview.tools.get_invitation(openreview_client, 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Conflict')
+ inv = openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Assignment_Configuration')
+ assert inv and inv.content['committee_name']['value'] == 'Senior_Action_Editors'
+ assert inv.edit['note']['content']['paper_invitation']['value']['param']['default'] == 'ICLR.cc/2026/Conference/Action_Editors'
+ assert inv.edit['note']['content']['match_group']['value']['param']['default'] == 'ICLR.cc/2026/Conference/Senior_Action_Editors'
+
+ # enable bidding for SACs
+ now = datetime.datetime.now()
+ new_cdate = openreview.tools.datetime_millis(now)
+ new_duedate = openreview.tools.datetime_millis(now + datetime.timedelta(days=5))
+ edit = pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/Senior_Action_Editors/-/Bid/Dates',
+ content={
+ 'activation_date': { 'value': new_cdate },
+ 'due_date': { 'value': new_duedate },
+ 'expiration_date': { 'value': new_duedate }
+ }
+ )
+
+ openreview_client.add_members_to_group('ICLR.cc/2026/Conference/Senior_Action_Editors', ['~SAE_ICLROne1', '~SAE_ICLRTwo1'])
+ openreview_client.add_members_to_group('ICLR.cc/2026/Conference/Action_Editors', ['~AC_ICLROne1', '~AC_ICLRTwo1'])
+
+ sae_client = openreview.api.OpenReviewClient(username='senioractioneditor_one@iclr.cc', password=helpers.strong_password)
+
+ # Check that reviewers bid console loads
+ request_page(selenium, f'http://localhost:3030/invitation?id={bid_invitation.id}', sae_client, wait_for_element='header')
+ header = selenium.find_element(By.ID, 'header')
+ assert 'Senior Action Editor Bidding Console' in header.text
+
+def test_AC_conflicts(client, openreview_client, helpers):
+
+ pc_client=openreview.api.OpenReviewClient(username='programchair@iclr.cc', password=helpers.strong_password)
+
+ now = datetime.datetime.now()
+ now = openreview.tools.datetime_millis(now)
+
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/Action_Editors/-/Conflict/Policy',
+ content={
+ 'conflict_policy': { 'value': 'NeurIPS' },
+ 'conflict_n_years': { 'value': 3 }
+ }
+ )
+
+ helpers.await_queue_edit(openreview_client, edit_id=f'ICLR.cc/2026/Conference/Action_Editors/-/Conflict-0-1', count=2)
+
+ # trigger conflicts date process
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/Action_Editors/-/Conflict/Dates',
+ content={
+ 'activation_date': { 'value': now }
+ }
+ )
+
+ helpers.await_queue_edit(openreview_client, edit_id=f'ICLR.cc/2026/Conference/Action_Editors/-/Conflict-0-1', count=3)
+
+ venue = openreview_client.get_group('ICLR.cc/2026/Conference')
+ # assert status comment posted to request form
+ notes = openreview_client.get_notes(invitation='openreview.net/Support/Venue_Request/Conference_Review_Workflow/-/Status', forum=venue.content['request_form_id']['value'], sort='number:asc')
+ assert len(notes) == 1
+ assert notes[0].content['title']['value'] == 'Action Editors Conflicts Reminder'
+ assert notes[0].content['comment']['value'] == 'Action Editors conflicts have been successfully computed. Please note that you will need to recompute Action Editors conflicts once you deploy SAC-AC assignments to account for SAC conflicts.'
+
+ assert len(openreview_client.get_grouped_edges(
+ invitation='ICLR.cc/2026/Conference/Action_Editors/-/Conflict',
+ groupby='id'
+ )) == 4
+
+def test_sac_deployment(client, openreview_client, helpers):
+
+ pc_client=openreview.api.OpenReviewClient(username='programchair@iclr.cc', password=helpers.strong_password)
+
+ inv = openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Assignment')
+ assert inv and inv.content['committee_role']['value'] == 'senior_area_chairs'
+ assert inv.edit['head']['param']['inGroup'] == 'ICLR.cc/2026/Conference/Action_Editors'
+ assert inv.edit['tail']['param']['options']['group'] == 'ICLR.cc/2026/Conference/Senior_Action_Editors'
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Assignment/Dates')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Proposed_Assignment')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Aggregate_Score')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Custom_Max_Papers')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Custom_User_Demands')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/Senior_Action_Editors/-/Assignment_Configuration')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/-/Senior_Action_Editors_Assignment_Deployment')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/-/Senior_Action_Editors_Assignment_Deployment/Dates')
+ assert openreview_client.get_invitation('ICLR.cc/2026/Conference/-/Senior_Action_Editors_Assignment_Deployment/Match')
+
+ #submit Assignment_Configuration
+ config_note = openreview_client.post_note_edit(
+ invitation='ICLR.cc/2026/Conference/Senior_Action_Editors/-/Assignment_Configuration',
+ readers=['ICLR.cc/2026/Conference'],
+ writers=['ICLR.cc/2026/Conference'],
+ signatures=['ICLR.cc/2026/Conference'],
+ note=openreview.api.Note(
+ content={
+ 'title': { 'value': 'sac-matching-1'},
+ 'user_demand': { 'value': '1'},
+ 'max_papers': { 'value': '5'},
+ 'min_papers': { 'value': '1'},
+ 'alternates': { 'value': '1'},
+ 'paper_invitation': { 'value': 'ICLR.cc/2026/Conference/Action_Editors' },
+ 'match_group': { 'value': 'ICLR.cc/2026/Conference/Senior_Action_Editors' },
+ 'scores_specification': {
+ 'value': {
+ 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Affinity_Score': {
+ 'weight': 1,
+ 'default': 0
+ },
+ 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Bid': {
+ 'weight': 1,
+ 'default': 0,
+ 'translate_map': {
+ 'Very High': 1.0,
+ 'High': 0.5,
+ 'Neutral': 0.0,
+ 'Low': -0.5,
+ 'Very Low': -1.0
+ }
+ }
+ }
+ },
+ 'aggregate_score_invitation': { 'value': 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Aggregate_Score'},
+ 'conflicts_invitation': { 'value': 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Conflict'},
+ 'solver': { 'value': 'FairFlow'},
+ 'status': { 'value': 'Initialized'},
+ }
+ )
+ )
+ helpers.await_queue_edit(openreview_client, invitation=f'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Assignment_Configuration')
+
+ match_invitation = openreview_client.get_invitation('ICLR.cc/2026/Conference/-/Senior_Action_Editors_Assignment_Deployment/Match')
+ assert match_invitation.edit['content']['match_name']['value']['param']['enum'] == ['sac-matching-1']
+
+ now = datetime.datetime.now()
+ now = openreview.tools.datetime_millis(now)
+
+ # trigger deployment date process without selecting match name
+ openreview_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Senior_Action_Editors_Assignment_Deployment/Dates',
+ content={
+ 'activation_date': { 'value': now }
+ }
+ )
+
+ helpers.await_queue_edit(openreview_client, edit_id=f'ICLR.cc/2026/Conference/-/Senior_Action_Editors_Assignment_Deployment-0-1', count=2)
+
+ # assert status comment posted to request form
+ venue = openreview_client.get_group('ICLR.cc/2026/Conference')
+ notes = openreview_client.get_notes(invitation='openreview.net/Support/Venue_Request/Conference_Review_Workflow/-/Status', forum=venue.content['request_form_id']['value'], sort='number:asc')
+ assert len(notes) == 2
+ assert notes[-1].content['title']['value'] == 'Senior Action Editors Assignment Deployment Failed'
+
+ # try to deploy initialized configuration and get an error
+ with pytest.raises(openreview.OpenReviewException, match=r'The matching configuration with title "sac-matching-1" does not have status "Complete".'):
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Senior_Action_Editors_Assignment_Deployment/Match',
+ content = {
+ 'match_name': { 'value': 'sac-matching-1' }
+ }
+ )
+
+ # post proposed assignments to test deployment process
+ openreview_client.post_edge(openreview.api.Edge(
+ invitation = 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Proposed_Assignment',
+ head = '~AC_ICLROne1',
+ tail = '~SAE_ICLROne1',
+ signatures = ['ICLR.cc/2026/Conference/Program_Chairs'],
+ weight = 1,
+ label = 'sac-matching-1'
+ ))
+
+ openreview_client.post_edge(openreview.api.Edge(
+ invitation = 'ICLR.cc/2026/Conference/Senior_Action_Editors/-/Proposed_Assignment',
+ head = '~AC_ICLRTwo1',
+ tail = '~SAE_ICLRTwo1',
+ signatures = ['ICLR.cc/2026/Conference/Program_Chairs'],
+ weight = 1,
+ label = 'sac-matching-1'
+ ))
+
+ assert len(openreview_client.get_grouped_edges(
+ invitation='ICLR.cc/2026/Conference/Senior_Action_Editors/-/Proposed_Assignment',
+ groupby='id'
+ )) == 2
+
+ assert len(openreview_client.get_grouped_edges(
+ invitation='ICLR.cc/2026/Conference/Senior_Action_Editors/-/Assignment',
+ groupby='id'
+ )) == 0
+
+ #change status of configuration to complete
+ openreview_client.post_note_edit(
+ invitation='ICLR.cc/2026/Conference/-/Edit',
+ signatures=['ICLR.cc/2026/Conference'],
+ note=openreview.api.Note(
+ id=config_note['note']['id'],
+ content = {
+ 'status': {
+ 'value': 'Complete'
+ }
+ }
+ )
+ )
+
+ # deploy assignments
+ openreview_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Senior_Action_Editors_Assignment_Deployment/Match',
+ content = {
+ 'match_name': { 'value': 'sac-matching-1' }
+ }
+ )
+ helpers.await_queue_edit(openreview_client, edit_id=f'ICLR.cc/2026/Conference/-/Senior_Action_Editors_Assignment_Deployment-0-1', count=3)
+
+ grouped_edges = openreview_client.get_grouped_edges(invitation='ICLR.cc/2026/Conference/Senior_Action_Editors/-/Assignment', groupby='id')
+ assert len(grouped_edges) == 2
+
+ # retrigger AC conflicts after SAC-AC deployment
+ now = datetime.datetime.now()
+ now = openreview.tools.datetime_millis(now)
+
+ # trigger conflicts date process
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/Action_Editors/-/Conflict/Dates',
+ content={
+ 'activation_date': { 'value': now }
+ }
+ )
+
+ helpers.await_queue_edit(openreview_client, edit_id=f'ICLR.cc/2026/Conference/Action_Editors/-/Conflict-0-1', count=4)
+
+ venue = openreview_client.get_group('ICLR.cc/2026/Conference')
+ # assert status comment was not posted to the request form since SAC-AC assignemnts were already deployed
+ notes = openreview_client.get_notes(invitation='openreview.net/Support/Venue_Request/Conference_Review_Workflow/-/Status', forum=venue.content['request_form_id']['value'], sort='number:asc')
+ assert len(notes) == 2
+ assert notes[-1].content['title']['value'] == 'Senior Action Editors Assignment Deployment Failed'
+
+ assert len(openreview_client.get_grouped_edges(
+ invitation='ICLR.cc/2026/Conference/Action_Editors/-/Conflict',
+ groupby='id'
+ )) == 12
+
+def test_review_stage(client, openreview_client, helpers):
+
+ pc_client=openreview.api.OpenReviewClient(username='programchair@iclr.cc', password=helpers.strong_password)
+
+ # close submission deadline
+ now = datetime.datetime.now()
+
+ edit = pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Full_Submission/Dates',
+ content={
+ 'due_date': { 'value': openreview.tools.datetime_millis(now - datetime.timedelta(hours=2)) },
+ 'expiration_date': { 'value': openreview.tools.datetime_millis(now - datetime.timedelta(hours=1.5)) }
+ }
+ )
+
+ helpers.await_queue_edit(openreview_client, edit_id=edit['id'])
+ helpers.await_queue_edit(openreview_client, 'ICLR.cc/2026/Conference/-/Full_Submission-0-1', count=4)
+
+ edit = pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Withdrawal/Dates',
+ content={
+ 'activation_date': { 'value': openreview.tools.datetime_millis(now - datetime.timedelta(minutes=30)) },
+ 'expiration_date': { 'value': openreview.tools.datetime_millis(now + datetime.timedelta(days=31)) }
+ }
+ )
+
+ # manually trigger post submission invitations
+ helpers.await_queue_edit(openreview_client, edit_id=edit['id'])
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/-/Withdrawal-0-1', count=2)
+
+ edit = pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Desk_Rejection/Dates',
+ content={
+ 'activation_date': { 'value': openreview.tools.datetime_millis(now - datetime.timedelta(minutes=30)) },
+ 'expiration_date': { 'value': openreview.tools.datetime_millis(now + datetime.timedelta(days=31)) }
+ }
+ )
+
+ helpers.await_queue_edit(openreview_client, edit_id=edit['id'])
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/-/Desk_Rejection-0-1', count=2)
+
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/Reviewers/-/Submission_Group/Dates',
+ content={
+ 'activation_date': { 'value': openreview.tools.datetime_millis(now - datetime.timedelta(minutes=30)) }
+ }
+ )
+
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/Reviewers/-/Submission_Group-0-1', count=2)
+
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/Action_Editors/-/Submission_Group/Dates',
+ content={
+ 'activation_date': { 'value': openreview.tools.datetime_millis(now - datetime.timedelta(minutes=30)) }
+ }
+ )
+
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/Action_Editors/-/Submission_Group-0-1', count=2)
+
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/Senior_Action_Editors/-/Submission_Group/Dates',
+ content={
+ 'activation_date': { 'value': openreview.tools.datetime_millis(now - datetime.timedelta(minutes=30)) }
+ }
+ )
+
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/Senior_Action_Editors/-/Submission_Group-0-1', count=2)
+
+ submission_groups = openreview_client.get_all_groups(prefix='ICLR.cc/2026/Conference/Submission')
+ reviewer_groups = [group for group in submission_groups if group.id.endswith('/Reviewers')]
+ assert len(reviewer_groups) == 10
+ action_editor_groups = [group for group in submission_groups if group.id.endswith('/Action_Editors')]
+ assert len(action_editor_groups) == 10
+ senior_action_editor_groups = [group for group in submission_groups if group.id.endswith('/Senior_Action_Editors')]
+ assert len(senior_action_editor_groups) == 10
+
+ withdrawal_invitations = openreview_client.get_all_invitations(invitation='ICLR.cc/2026/Conference/-/Withdrawal')
+ assert len(withdrawal_invitations) == 10
+
+ desk_rejection_invitations = openreview_client.get_all_invitations(invitation='ICLR.cc/2026/Conference/-/Desk_Rejection')
+ assert len(desk_rejection_invitations) == 10
+
+ submissions = openreview_client.get_notes(invitation='ICLR.cc/2026/Conference/-/Submission', sort='number:asc')
+ assert len(submissions) == 10
+ assert submissions[0].readers == ['everyone']
+ assert submissions[0].content['pdf']['readers'] == ['ICLR.cc/2026/Conference', 'ICLR.cc/2026/Conference/Submission1/Authors']
+
+ # trigger Submission_Change_Before_Reviewing invitation
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Submission_Change_Before_Reviewing/Readers',
+ content={
+ 'readers': { 'value': ['everyone'] }
+ }
+ )
+
+ helpers.await_queue_edit(openreview_client, 'ICLR.cc/2026/Conference/-/Submission_Change_Before_Reviewing-0-1', count=3)
+
+ now = datetime.datetime.now()
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Submission_Change_Before_Reviewing/Dates',
+ content={
+ 'activation_date': { 'value': openreview.tools.datetime_millis(now) }
+ }
+ )
+
+ helpers.await_queue_edit(openreview_client, 'ICLR.cc/2026/Conference/-/Submission_Change_Before_Reviewing-0-1', count=4)
+
+ submissions = pc_client.get_notes(invitation='ICLR.cc/2026/Conference/-/Submission', sort='number:asc')
+ submission = submissions[0]
+ assert len(submissions) == 10
+ assert submission.readers == ['everyone']
+ assert submission.odate
+ assert not submission.pdate
+ assert 'readers' not in submission.content['pdf']
+ assert 'readers' in submission.content['authors'] and submission.content['authors']['readers'] == ['ICLR.cc/2026/Conference', f'ICLR.cc/2026/Conference/Submission{submission.number}/Authors']
+ assert 'readers' in submission.content['authorids'] and submission.content['authorids']['readers'] == ['ICLR.cc/2026/Conference', f'ICLR.cc/2026/Conference/Submission{submission.number}/Authors']
+
+ # create child invitations
+ now = datetime.datetime.now()
+ new_cdate = openreview.tools.datetime_millis(now)
+ new_duedate = openreview.tools.datetime_millis(now + datetime.timedelta(days=3))
+
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Official_Review/Dates',
+ content={
+ 'activation_date': { 'value': new_cdate },
+ 'due_date': { 'value': new_duedate },
+ 'expiration_date': { 'value': new_duedate }
+ }
+ )
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/-/Official_Review-0-1', count=2)
+
+ invitations = openreview_client.get_invitations(invitation='ICLR.cc/2026/Conference/-/Official_Review')
+ assert len(invitations) == 10
+
+ invitation = openreview_client.get_invitation('ICLR.cc/2026/Conference/Submission1/-/Official_Review')
+ assert invitation and invitation.edit['note']['readers'] == [
+ 'ICLR.cc/2026/Conference/Program_Chairs',
+ 'ICLR.cc/2026/Conference/Submission1/Senior_Action_Editors', ## SACs are added by default as readers of reviews
+ 'ICLR.cc/2026/Conference/Submission1/Action_Editors', ## ACs are added by default as readers of reviews
+ '${3/signatures}'
+ ]
+
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Official_Review_Release')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Official_Review_Release/Dates')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Official_Review_Release/Readers')
+
+ review_release_inv = openreview.tools.get_invitation(openreview_client, 'ICLR.cc/2026/Conference/-/Official_Review_Release')
+ assert review_release_inv.edit['invitation']['edit']['invitation']['edit']['note']['readers'] == [
+ 'ICLR.cc/2026/Conference/Program_Chairs',
+ 'ICLR.cc/2026/Conference/Submission${5/content/noteNumber/value}/Senior_Action_Editors',
+ 'ICLR.cc/2026/Conference/Submission${5/content/noteNumber/value}/Action_Editors',
+ 'ICLR.cc/2026/Conference/Submission${5/content/noteNumber/value}/Reviewers',
+ 'ICLR.cc/2026/Conference/Submission${5/content/noteNumber/value}/Authors'
+ ]
+
+def test_comment_stage(client, openreview_client, helpers):
+
+ pc_client=openreview.api.OpenReviewClient(username='programchair@iclr.cc', password=helpers.strong_password)
+
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Official_Comment')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Official_Comment/Dates')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Official_Comment/Form_Fields')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Official_Comment/Writers_and_Readers')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Official_Comment/Notifications')
+
+ # create child invitations
+ now = datetime.datetime.now()
+ new_cdate = openreview.tools.datetime_millis(now)
+ new_duedate = openreview.tools.datetime_millis(now + datetime.timedelta(days=4))
+
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Official_Comment/Dates',
+ content={
+ 'activation_date': { 'value': new_cdate },
+ 'expiration_date': { 'value': new_duedate }
+ }
+ )
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/-/Official_Comment-0-1', count=2)
+
+ invitations = openreview_client.get_invitations(invitation='ICLR.cc/2026/Conference/-/Official_Comment')
+ assert len(invitations) == 10
+
+ invitation = openreview_client.get_invitation('ICLR.cc/2026/Conference/Submission1/-/Official_Comment')
+ assert invitation.invitees == [
+ 'ICLR.cc/2026/Conference',
+ 'openreview.net/Support',
+ 'ICLR.cc/2026/Conference/Submission1/Senior_Action_Editors',
+ 'ICLR.cc/2026/Conference/Submission1/Action_Editors',
+ 'ICLR.cc/2026/Conference/Submission1/Reviewers',
+ 'ICLR.cc/2026/Conference/Submission1/Authors'
+ ]
+ assert invitation and invitation.edit['note']['readers']['param']['items'] == [
+ {
+ "value": "ICLR.cc/2026/Conference/Program_Chairs",
+ "optional": False
+ },
+ {
+ "value": "ICLR.cc/2026/Conference/Submission1/Senior_Action_Editors",
+ "optional": False
+ },
+ {
+ "value": "ICLR.cc/2026/Conference/Submission1/Action_Editors",
+ "optional": True
+ },
+ {
+ "value": "ICLR.cc/2026/Conference/Submission1/Reviewers",
+ "optional": True
+ },
+ {
+ "inGroup": "ICLR.cc/2026/Conference/Submission1/Reviewers",
+ "optional": True
+ },
+ {
+ "value": "ICLR.cc/2026/Conference/Submission1/Authors",
+ "optional": True
+ }
+ ]
+ assert invitation and invitation.edit['signatures']['param']['items'] == [
+ {
+ "value": "ICLR.cc/2026/Conference/Program_Chairs",
+ "optional": True
+ },
+ {
+ "value": "ICLR.cc/2026/Conference/Submission1/Senior_Action_Editors",
+ "optional": True
+ },
+ {
+ "prefix": "ICLR.cc/2026/Conference/Submission1/Action_Editor_.*",
+ "optional": True
+ },
+ {
+ "prefix": "ICLR.cc/2026/Conference/Submission1/Reviewer_.*",
+ "optional": True
+ },
+ {
+ "value": "ICLR.cc/2026/Conference/Submission1/Authors",
+ "optional": True
+ }
+ ]
+
+def test_rebuttal_stage(client, openreview_client, helpers):
+
+ pc_client=openreview.api.OpenReviewClient(username='programchair@iclr.cc', password=helpers.strong_password)
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Author_Rebuttal')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Author_Rebuttal/Dates')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Author_Rebuttal/Form_Fields')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Author_Rebuttal/Readers')
+
+ # create child invitations
+ now = datetime.datetime.now()
+ new_cdate = openreview.tools.datetime_millis(now)
+ new_duedate = openreview.tools.datetime_millis(now + datetime.timedelta(days=4))
+
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Author_Rebuttal/Dates',
+ content={
+ 'activation_date': { 'value': new_cdate },
+ 'due_date': { 'value': new_duedate },
+ 'expiration_date': { 'value': new_duedate }
+ }
+ )
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/-/Author_Rebuttal-0-1', count=2)
+
+ invitations = openreview_client.get_invitations(invitation='ICLR.cc/2026/Conference/-/Author_Rebuttal')
+ assert len(invitations) == 10
+
+ invitation = openreview_client.get_invitation('ICLR.cc/2026/Conference/Submission1/-/Author_Rebuttal')
+ assert invitation.invitees == [
+ 'ICLR.cc/2026/Conference',
+ 'ICLR.cc/2026/Conference/Submission1/Authors'
+ ]
+
+ assert invitation and invitation.edit['readers'] == [
+ 'ICLR.cc/2026/Conference/Program_Chairs',
+ 'ICLR.cc/2026/Conference/Submission1/Senior_Action_Editors',
+ 'ICLR.cc/2026/Conference/Submission1/Action_Editors',
+ 'ICLR.cc/2026/Conference/Submission1/Reviewers',
+ 'ICLR.cc/2026/Conference/Submission1/Authors'
+ ]
+
+def test_metareview_stage(client, openreview_client, helpers):
+
+ pc_client=openreview.api.OpenReviewClient(username='programchair@iclr.cc', password=helpers.strong_password)
+ metareview_inv = pc_client.get_invitation('ICLR.cc/2026/Conference/-/Meta_Review')
+ assert metareview_inv
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Meta_Review/Dates')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Meta_Review/Form_Fields')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Meta_Review/Readers')
+ content = metareview_inv.edit['invitation']['edit']['note']['content']
+ assert all(field in content for field in ['metareview', 'recommendation', 'confidence'])
+
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Meta_Review_SAE_Revision/Dates')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Meta_Review_SAE_Revision/Form_Fields')
+
+ metareview_sac_revision_inv = pc_client.get_invitation('ICLR.cc/2026/Conference/-/Meta_Review_SAE_Revision')
+ assert metareview_sac_revision_inv
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Meta_Review_SAE_Revision/Dates')
+ content = metareview_sac_revision_inv.edit['invitation']['edit']['note']['content']
+ assert all(field in content for field in ['metareview', 'recommendation', 'confidence'])
+
+ metareview_content = {
+ "final_metareview": {
+ "order": 1,
+ "description": "Please provide an evaluation of the quality, clarity, originality and significance of this work, including a list of its pros and cons. Your comment or reply (max 5000 characters). Add formatting using Markdown and formulas using LaTeX. For more information see https://openreview.net/faq",
+ "value": {
+ "param": {
+ "type": "string",
+ "maxLength": 5000,
+ "markdown": True,
+ "input": "textarea"
+ }
+ }
+ },
+ "final_recommendation": {
+ "order": 2,
+ "value": {
+ "param": {
+ "type": "string",
+ "enum": [
+ "Accept (Oral)",
+ "Accept (Poster)",
+ "Reject"
+ ],
+ "input": "radio"
+ }
+ }
+ },
+ "final_confidence": {
+ "order": 3,
+ "value": {
+ "param": {
+ "type": "integer",
+ "enum": [
+ {
+ "value": 5,
+ "description": "5: The area chair is absolutely certain"
+ },
+ {
+ "value": 4,
+ "description": "4: The area chair is confident but not absolutely certain"
+ },
+ {
+ "value": 3,
+ "description": "3: The area chair is somewhat confident"
+ },
+ {
+ "value": 2,
+ "description": "2: The area chair is not sure"
+ },
+ {
+ "value": 1,
+ "description": "1: The area chair's evaluation is an educated guess"
+ }
+ ],
+ "input": "radio"
+ }
+ }
+ },
+ 'metareview': {
+ 'delete': True
+ },
+ 'recommendation': {
+ 'delete': True
+ },
+ 'confidence': {
+ 'delete': True
+ }
+ }
+
+ # edit the metareview form
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Meta_Review/Form_Fields',
+ content = {
+ 'content': {
+ 'value': metareview_content
+ },
+ 'recommendation_field_name': {
+ 'value': 'final_recommendation'
+ }
+ }
+ )
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/-/Meta_Review-0-1', count=2)
+
+ # create child invitations
+ now = datetime.datetime.now()
+ new_cdate = openreview.tools.datetime_millis(now)
+ new_duedate = openreview.tools.datetime_millis(now + datetime.timedelta(days=4))
+
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Meta_Review/Dates',
+ content={
+ 'activation_date': { 'value': new_cdate },
+ 'due_date': { 'value': new_duedate },
+ 'expiration_date': { 'value': new_duedate }
+ }
+ )
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/-/Meta_Review-0-1', count=3)
+
+ invitations = openreview_client.get_invitations(invitation='ICLR.cc/2026/Conference/-/Meta_Review')
+ assert len(invitations) == 10
+
+ invitation = openreview_client.get_invitation('ICLR.cc/2026/Conference/Submission1/-/Meta_Review')
+ assert invitation.invitees == [
+ 'ICLR.cc/2026/Conference',
+ 'ICLR.cc/2026/Conference/Submission1/Action_Editors'
+ ]
+
+ assert invitation and invitation.edit['readers'] == [
+ 'ICLR.cc/2026/Conference/Submission1/Senior_Action_Editors',
+ 'ICLR.cc/2026/Conference/Submission1/Action_Editors',
+ 'ICLR.cc/2026/Conference/Program_Chairs'
+ ]
+ assert all(field in invitation.edit['note']['content'] for field in ['final_metareview', 'final_recommendation', 'final_confidence'])
+ assert not all (field in invitation.edit['note']['content'] for field in ['metareview', 'recommendation', 'confidence'])
+
+ # assert meta review revision invitation is edited with metareview fields
+ meta_review_revision_inv = openreview_client.get_invitation('ICLR.cc/2026/Conference/-/Meta_Review_SAE_Revision')
+ content = meta_review_revision_inv.edit['invitation']['edit']['note']['content']
+ assert all(field in content for field in ['final_metareview', 'final_recommendation', 'final_confidence'])
+ assert not all (field in content for field in ['metareview', 'recommendation', 'confidence'])
+
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Meta_Review_Release')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Meta_Review_Release/Dates')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Meta_Review_Release/Readers')
+
+ # allow PC to directly edit metareview revision invitation content
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Meta_Review_SAE_Revision/Form_Fields',
+ content = {
+ 'content': {
+ 'value': {
+ "private_comment_to_PCs": {
+ "order": 10,
+ "value": {
+ "param": {
+ "type": "string",
+ "maxLength": 5000,
+ "markdown": True,
+ "input": "textarea"
+ }
+ },
+ "readers": [
+ "ICLR.cc/2026/Conference/Program_Chairs",
+ "ICLR.cc/2026/Conference/Submission${7/content/noteNumber/value}/Senior_Action_Editors"
+ ]
+ }
+ }
+ }
+ }
+ )
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/-/Meta_Review_SAE_Revision-0-1', count=3)
+
+ # assert metareview revision invitation has metareview fields plus the new private comment field
+ meta_review_revision_inv = openreview_client.get_invitation('ICLR.cc/2026/Conference/-/Meta_Review_SAE_Revision')
+ content = meta_review_revision_inv.edit['invitation']['edit']['note']['content']
+ assert all(field in content for field in ['final_metareview', 'final_recommendation', 'final_confidence', 'private_comment_to_PCs'])
+ assert not all (field in content for field in ['metareview', 'recommendation', 'confidence'])
+
+ meta_review_release_inv = openreview.tools.get_invitation(openreview_client, 'ICLR.cc/2026/Conference/-/Meta_Review_Release')
+ assert meta_review_release_inv.edit['invitation']['edit']['invitation']['edit']['note']['readers'] == [
+ 'ICLR.cc/2026/Conference/Program_Chairs',
+ 'ICLR.cc/2026/Conference/Submission${5/content/noteNumber/value}/Senior_Action_Editors',
+ 'ICLR.cc/2026/Conference/Submission${5/content/noteNumber/value}/Action_Editors',
+ 'ICLR.cc/2026/Conference/Submission${5/content/noteNumber/value}/Reviewers',
+ 'ICLR.cc/2026/Conference/Submission${5/content/noteNumber/value}/Authors'
+ ]
+
+def test_decision_stage(client, openreview_client, helpers):
+
+ pc_client = openreview.api.OpenReviewClient(username='programchair@iclr.cc', password=helpers.strong_password)
+
+ invitation = pc_client.get_invitation('ICLR.cc/2026/Conference/-/Decision')
+ assert invitation
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Decision/Dates')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Decision/Readers')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Decision/Decision_Options')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Decision_Upload')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Decision_Upload/Decision_CSV')
+
+ # create child invitations
+ now = datetime.datetime.now()
+ new_cdate = openreview.tools.datetime_millis(now)
+ new_duedate = openreview.tools.datetime_millis(now + datetime.timedelta(days=4))
+
+ pc_client.post_invitation_edit(
+ invitations='ICLR.cc/2026/Conference/-/Decision/Dates',
+ content={
+ 'activation_date': { 'value': new_cdate },
+ 'due_date': { 'value': new_duedate },
+ 'expiration_date': { 'value': new_duedate }
+ }
+ )
+ helpers.await_queue_edit(openreview_client, edit_id='ICLR.cc/2026/Conference/-/Decision-0-1', count=2)
+
+ invitations = openreview_client.get_invitations(invitation='ICLR.cc/2026/Conference/-/Decision')
+ assert len(invitations) == 10
+
+ invitation = openreview_client.get_invitation('ICLR.cc/2026/Conference/Submission1/-/Decision')
+
+ assert invitation and invitation.edit['readers'] == [
+ 'ICLR.cc/2026/Conference/Program_Chairs'
+ ]
+
+ invitation = openreview_client.get_invitation('ICLR.cc/2026/Conference/-/Decision/Readers')
+ reader_values = [item['value'] for item in invitation.edit['content']['readers']['value']['param']['items']]
+ assert 'ICLR.cc/2026/Conference/Senior_Action_Editors' in reader_values
+ assert 'ICLR.cc/2026/Conference/Submission${5/content/noteNumber/value}/Senior_Action_Editors' in reader_values
+
+def test_decision_release_stage(client, openreview_client, helpers):
+
+ pc_client = openreview.api.OpenReviewClient(username='programchair@iclr.cc', password=helpers.strong_password)
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Decision_Release')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Decision_Release/Dates')
+ assert pc_client.get_invitation('ICLR.cc/2026/Conference/-/Decision_Release/Readers')
+
+ decision_release_inv = openreview.tools.get_invitation(openreview_client, 'ICLR.cc/2026/Conference/-/Decision_Release')
+ assert decision_release_inv.edit['invitation']['edit']['invitation']['edit']['note']['readers'] == [
+ 'ICLR.cc/2026/Conference/Program_Chairs',
+ 'ICLR.cc/2026/Conference/Submission${5/content/noteNumber/value}/Senior_Action_Editors',
+ 'ICLR.cc/2026/Conference/Submission${5/content/noteNumber/value}/Action_Editors',
+ 'ICLR.cc/2026/Conference/Submission${5/content/noteNumber/value}/Reviewers',
+ 'ICLR.cc/2026/Conference/Submission${5/content/noteNumber/value}/Authors'
+ ]
+ assert decision_release_inv.edit['invitation']['edit']['invitation']['edit']['note']['nonreaders'] == []
\ No newline at end of file
diff --git a/tests/test_reduced_load_new_ui.py b/tests/test_reduced_load_new_ui.py
index 7f92537d1..af3f2781d 100644
--- a/tests/test_reduced_load_new_ui.py
+++ b/tests/test_reduced_load_new_ui.py
@@ -37,6 +37,7 @@ def test_setup(self, openreview_client, helpers):
'submission_deadline': { 'value': openreview.tools.datetime_millis(due_date) },
'reviewer_groups_names': { 'value': ['Program_Committee'] },
'area_chair_groups_names': { 'value': ['Area_Chairs'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'colocated': { 'value': 'Independent' },
'previous_venue': { 'value': 'RL.cc/2024/Conference' },
'expected_submissions': { 'value': 100 },
diff --git a/tests/test_registration_step.py b/tests/test_registration_step.py
index 566e91f53..2e952830f 100644
--- a/tests/test_registration_step.py
+++ b/tests/test_registration_step.py
@@ -49,6 +49,7 @@ def test_setup(self, openreview_client, helpers):
'reviewer_groups_names': { 'value': ['Reviewers'] },
'area_chairs_support': { 'value': True },
'area_chair_groups_names': { 'value': ['Action_Editors'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'expected_submissions': { 'value': 500 },
'venue_organizer_agreement': {
'value': [
diff --git a/tests/test_reviewers_only.py b/tests/test_reviewers_only.py
index 465df3ff4..dddabfcbf 100644
--- a/tests/test_reviewers_only.py
+++ b/tests/test_reviewers_only.py
@@ -91,6 +91,7 @@ def test_setup(self, openreview_client, helpers):
'submission_deadline': { 'value': openreview.tools.datetime_millis(due_date) },
'reviewer_groups_names': { 'value': ['Program_Committee'] },
'area_chair_groups_names': { 'value': ['Area_Chairs'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'colocated': { 'value': 'Independent' },
'previous_venue': { 'value': 'ABCD.cc/2024/Conference' },
'expected_submissions': { 'value': 1000 },
@@ -612,6 +613,7 @@ def test_deployment_with_same_venue_id(self, openreview_client, helpers):
'submission_deadline': { 'value': openreview.tools.datetime_millis(due_date) },
'reviewer_groups_names': { 'value': ['Reviewers'] },
'area_chair_groups_names': { 'value': ['Area_Chairs'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'colocated': { 'value': 'Independent' },
'previous_venue': { 'value': 'ABCD.cc/2024/Conference' },
'expected_submissions': { 'value': 50 },
@@ -1705,19 +1707,19 @@ def test_review_stage(self, openreview_client, helpers):
)
pc_client.post_invitation_edit(
- invitations='ABCD.cc/2025/Conference/-/Official_Review/Form_Fields',
- content = {
- 'content': {
- 'value': review_content
- },
- 'rating_field_name': {
- 'value': 'review_rating'
- },
- 'confidence_field_name': {
- 'value': 'review_confidence'
- }
+ invitations='ABCD.cc/2025/Conference/-/Official_Review/Form_Fields',
+ content = {
+ 'content': {
+ 'value': review_content
+ },
+ 'rating_field_name': {
+ 'value': 'review_rating'
+ },
+ 'confidence_field_name': {
+ 'value': 'review_confidence'
}
- )
+ }
+ )
helpers.await_queue_edit(openreview_client, edit_id='ABCD.cc/2025/Conference/-/Official_Review-0-1', count=2)
helpers.await_queue_edit(openreview_client, invitation=f'ABCD.cc/2025/Conference/-/Official_Review/Form_Fields')
diff --git a/tests/test_submission_limits.py b/tests/test_submission_limits.py
index 08c05e746..46b04620b 100644
--- a/tests/test_submission_limits.py
+++ b/tests/test_submission_limits.py
@@ -33,6 +33,7 @@ def test_setup_venue(self, openreview_client, helpers):
'submission_deadline': { 'value': openreview.tools.datetime_millis(due_date) },
'reviewer_groups_names': { 'value': ['Reviewers'] },
'area_chair_groups_names': { 'value': ['Area_Chairs'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'colocated': { 'value': 'Independent' },
'previous_venue': { 'value': 'HVTest.cc/2024/Conference' },
'expected_submissions': { 'value': 50 },
diff --git a/tests/test_tasks.py b/tests/test_tasks.py
index 784736fde..f24864604 100644
--- a/tests/test_tasks.py
+++ b/tests/test_tasks.py
@@ -47,6 +47,7 @@ def test_setup(self, openreview_client, helpers):
'submission_deadline': { 'value': openreview.tools.datetime_millis(due_date) },
'reviewer_groups_names': { 'value': ['Program_Committee'] },
'area_chair_groups_names': { 'value': ['Area_Chairs'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'colocated': { 'value': 'Independent' },
'previous_venue': { 'value': 'Tasks.cc/2024/Conference' },
'expected_submissions': { 'value': 1000 },
diff --git a/tests/test_venue_deployment.py b/tests/test_venue_deployment.py
index cb82f5f4a..6a1f85dcd 100644
--- a/tests/test_venue_deployment.py
+++ b/tests/test_venue_deployment.py
@@ -28,6 +28,7 @@ def test_request_form_date_validation(self, openreview_client, helpers):
'submission_deadline': { 'value': openreview.tools.datetime_millis(now - datetime.timedelta(days=5)) },
'reviewer_groups_names': { 'value': ['Reviewers'] },
'area_chair_groups_names': { 'value': ['Area_Chairs'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'expected_submissions': { 'value': 50 },
'venue_organizer_agreement': {
'value': [
@@ -62,6 +63,7 @@ def test_request_form_date_validation(self, openreview_client, helpers):
'submission_deadline': { 'value': openreview.tools.datetime_millis(now + datetime.timedelta(days=2)) },
'reviewer_groups_names': { 'value': ['Reviewers'] },
'area_chair_groups_names': { 'value': ['Area_Chairs'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'expected_submissions': { 'value': 50 },
'venue_organizer_agreement': {
'value': [
@@ -97,6 +99,7 @@ def test_request_form_date_validation(self, openreview_client, helpers):
'full_submission_deadline': { 'value': openreview.tools.datetime_millis(now + datetime.timedelta(days=1)) },
'reviewer_groups_names': { 'value': ['Reviewers'] },
'area_chair_groups_names': { 'value': ['Area_Chairs'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'expected_submissions': { 'value': 50 },
'venue_organizer_agreement': {
'value': [
@@ -138,6 +141,81 @@ def test_same_role_names_rejected(self, openreview_client, helpers):
'reviewer_groups_names': { 'value': ['Reviewers'] },
'area_chairs_support': { 'value': True },
'area_chair_groups_names': { 'value': ['Reviewers'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
+ 'expected_submissions': { 'value': 50 },
+ 'venue_organizer_agreement': {
+ 'value': [
+ 'OpenReview natively supports a wide variety of reviewing workflow configurations. However, if we want significant reviewing process customizations or experiments, we will detail these requests to the OpenReview staff at least three months in advance.',
+ 'We will ask authors and reviewers to create an OpenReview Profile at least two weeks in advance of the paper submission deadlines.',
+ 'When assembling our group of reviewers, we will only include email addresses or OpenReview Profile IDs of people we know to have authored publications relevant to our venue. (We will not solicit new reviewers using an open web form, because unfortunately some malicious actors sometimes try to create "fake ids" aiming to be assigned to review their own paper submissions.)',
+ 'We acknowledge that, if our venue\'s reviewing workflow is non-standard, or if our venue is expecting more than a few hundred submissions for any one deadline, we should designate our own Workflow Chair, who will read the OpenReview documentation and manage our workflow configurations throughout the reviewing process.',
+ 'We acknowledge that OpenReview staff work Monday-Friday during standard business hours US Eastern time, and we cannot expect support responses outside those times. For this reason, we recommend setting submission and reviewing deadlines Monday through Thursday.',
+ 'We will treat the OpenReview staff with kindness and consideration.',
+ 'We acknowledge that authors and reviewers will be required to share their preferred email.',
+ 'We acknowledge that review counts will be collected for all the reviewers and publicly available in OpenReview.',
+ 'We acknowledge that metadata for accepted papers will be publicly released in OpenReview.'
+ ]
+ }
+ }
+ ))
+
+ with pytest.raises(openreview.OpenReviewException, match=r'The senior area chair role name and reviewer role name must be different'):
+ pc_client.post_note_edit(
+ invitation='openreview.net/Support/Venue_Request/-/Conference_Review_Workflow',
+ signatures=['~ProgramChair_VenueDeployment1'],
+ note=openreview.api.Note(
+ content={
+ 'official_venue_name': { 'value': 'Test Venue Deployment Conference 2026' },
+ 'abbreviated_venue_name': { 'value': 'TVD 2026' },
+ 'venue_website_url': { 'value': 'https://venue-deployment-test.cc' },
+ 'location': { 'value': 'Virtual' },
+ 'venue_start_date': { 'value': openreview.tools.datetime_millis(now + datetime.timedelta(weeks=26)) },
+ 'program_chair_emails': { 'value': ['programchair@venuedeployment.cc'] },
+ 'contact_email': { 'value': 'programchair@venuedeployment.cc' },
+ 'submission_start_date': { 'value': openreview.tools.datetime_millis(now) },
+ 'submission_deadline': { 'value': openreview.tools.datetime_millis(due_date) },
+ 'reviewer_groups_names': { 'value': ['Reviewers'] },
+ 'area_chairs_support': { 'value': True },
+ 'area_chair_groups_names': { 'value': ['Area_Chairs'] },
+ 'senior_area_chairs_support': { 'value': True },
+ 'senior_area_chair_groups_names': { 'value': ['Reviewers'] },
+ 'expected_submissions': { 'value': 50 },
+ 'venue_organizer_agreement': {
+ 'value': [
+ 'OpenReview natively supports a wide variety of reviewing workflow configurations. However, if we want significant reviewing process customizations or experiments, we will detail these requests to the OpenReview staff at least three months in advance.',
+ 'We will ask authors and reviewers to create an OpenReview Profile at least two weeks in advance of the paper submission deadlines.',
+ 'When assembling our group of reviewers, we will only include email addresses or OpenReview Profile IDs of people we know to have authored publications relevant to our venue. (We will not solicit new reviewers using an open web form, because unfortunately some malicious actors sometimes try to create "fake ids" aiming to be assigned to review their own paper submissions.)',
+ 'We acknowledge that, if our venue\'s reviewing workflow is non-standard, or if our venue is expecting more than a few hundred submissions for any one deadline, we should designate our own Workflow Chair, who will read the OpenReview documentation and manage our workflow configurations throughout the reviewing process.',
+ 'We acknowledge that OpenReview staff work Monday-Friday during standard business hours US Eastern time, and we cannot expect support responses outside those times. For this reason, we recommend setting submission and reviewing deadlines Monday through Thursday.',
+ 'We will treat the OpenReview staff with kindness and consideration.',
+ 'We acknowledge that authors and reviewers will be required to share their preferred email.',
+ 'We acknowledge that review counts will be collected for all the reviewers and publicly available in OpenReview.',
+ 'We acknowledge that metadata for accepted papers will be publicly released in OpenReview.'
+ ]
+ }
+ }
+ ))
+
+ with pytest.raises(openreview.OpenReviewException, match=r'The senior area chair role name and area chair role name must be different'):
+ pc_client.post_note_edit(
+ invitation='openreview.net/Support/Venue_Request/-/Conference_Review_Workflow',
+ signatures=['~ProgramChair_VenueDeployment1'],
+ note=openreview.api.Note(
+ content={
+ 'official_venue_name': { 'value': 'Test Venue Deployment Conference 2026' },
+ 'abbreviated_venue_name': { 'value': 'TVD 2026' },
+ 'venue_website_url': { 'value': 'https://venue-deployment-test.cc' },
+ 'location': { 'value': 'Virtual' },
+ 'venue_start_date': { 'value': openreview.tools.datetime_millis(now + datetime.timedelta(weeks=26)) },
+ 'program_chair_emails': { 'value': ['programchair@venuedeployment.cc'] },
+ 'contact_email': { 'value': 'programchair@venuedeployment.cc' },
+ 'submission_start_date': { 'value': openreview.tools.datetime_millis(now) },
+ 'submission_deadline': { 'value': openreview.tools.datetime_millis(due_date) },
+ 'reviewer_groups_names': { 'value': ['Reviewers'] },
+ 'area_chairs_support': { 'value': True },
+ 'area_chair_groups_names': { 'value': ['Area_Chairs'] },
+ 'senior_area_chairs_support': { 'value': True },
+ 'senior_area_chair_groups_names': { 'value': ['Area_Chairs'] },
'expected_submissions': { 'value': 50 },
'venue_organizer_agreement': {
'value': [
@@ -178,6 +256,7 @@ def test_new_role_name_fields_accepted(self, openreview_client, helpers):
'reviewer_groups_names': { 'value': ['Program_Committee'] },
'area_chairs_support': { 'value': True },
'area_chair_groups_names': { 'value': ['Senior_Program_Committee'] },
+ 'senior_area_chair_groups_names': { 'value': ['Senior_Area_Chairs'] },
'expected_submissions': { 'value': 50 },
'venue_organizer_agreement': {
'value': [
diff --git a/tests/test_venue_restriction.py b/tests/test_venue_restriction.py
index 43c6fce7c..bed6d35ab 100644
--- a/tests/test_venue_restriction.py
+++ b/tests/test_venue_restriction.py
@@ -38,6 +38,7 @@ def test_setup(self, openreview_client, helpers):
'submission_deadline': {'value': openreview.tools.datetime_millis(due_date)},
'reviewer_groups_names': {'value': ['Reviewers']},
'area_chair_groups_names': {'value': ['Area_Chairs']},
+ 'senior_area_chair_groups_names': {'value': ['Senior_Area_Chairs']},
'colocated': {'value': 'Independent'},
'previous_venue': {'value': 'RESTRICT.cc/2024/Conference'},
'expected_submissions': {'value': 10},