feat: include checkins at the registration desk in nomcom eligibility calculations (#4519)
* chore: rename utility functions to reflect rfc guiding them * feat: include new checkedin flag in nomcom calculations * fix: reflect history a bit more accurately. * fix: address review comment on readability * fix: finish what c44000d started
This commit is contained in:
parent
cb9e576fb4
commit
97d21800d0
|
@ -2569,6 +2569,59 @@ class rfc8989EligibilityTests(TestCase):
|
|||
self.assertEqual(set(list_eligible(nomcom=nomcom)),set(eligible))
|
||||
Person.objects.filter(pk__in=[p.pk for p in eligible.union(ineligible)]).delete()
|
||||
|
||||
class rfc8989bisEligibilityTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.nomcom = NomComFactory(group__acronym='nomcom2023', populate_personnel=False, first_call_for_volunteers=datetime.date(2023,5,15))
|
||||
self.meetings = [
|
||||
MeetingFactory(number=number, date=date, type_id='ietf') for number,date in [
|
||||
('115', datetime.date(2022, 11, 5)),
|
||||
('114', datetime.date(2022, 7, 23)),
|
||||
('113', datetime.date(2022, 3, 19)),
|
||||
('112', datetime.date(2021, 11, 8)),
|
||||
('111', datetime.date(2021, 7, 26)),
|
||||
]
|
||||
]
|
||||
# make_immutable_test_data makes things this test does not want
|
||||
Role.objects.filter(name_id__in=('chair','secr')).delete()
|
||||
|
||||
def test_registration_is_not_enough(self):
|
||||
p = PersonFactory()
|
||||
for meeting in self.meetings:
|
||||
MeetingRegistrationFactory(person=p, meeting=meeting, checkedin=False)
|
||||
self.assertFalse(is_eligible(p, self.nomcom))
|
||||
|
||||
def test_elig_by_meetings(self):
|
||||
eligible_people = list()
|
||||
ineligible_people = list()
|
||||
attendance_methods = ('checkedin', 'session', 'both')
|
||||
for combo_len in range(0,6): # Someone might register for 0 to 5 previous meetings
|
||||
for combo in combinations(self.meetings, combo_len):
|
||||
# Cover cases where someone
|
||||
# - checked in, but attended no sessions
|
||||
# - checked in _and_ attended sessions
|
||||
# - didn't check_in but attended sessions
|
||||
# (Intentionally not covering the permutations of those cases)
|
||||
for method in attendance_methods:
|
||||
p = PersonFactory()
|
||||
for meeting in combo:
|
||||
MeetingRegistrationFactory(person=p, meeting=meeting, reg_type='onsite', checkedin=(method in ('checkedin', 'both')))
|
||||
if method in ('session', 'both'):
|
||||
AttendedFactory(session__meeting=meeting, session__type_id='plenary',person=p)
|
||||
if combo_len<3:
|
||||
ineligible_people.append(p)
|
||||
else:
|
||||
eligible_people.append(p)
|
||||
|
||||
self.assertEqual(set(eligible_people),set(list_eligible(self.nomcom)))
|
||||
|
||||
for person in eligible_people:
|
||||
self.assertTrue(is_eligible(person,self.nomcom))
|
||||
|
||||
for person in ineligible_people:
|
||||
self.assertFalse(is_eligible(person,self.nomcom))
|
||||
|
||||
class VolunteerTests(TestCase):
|
||||
|
||||
def test_volunteer(self):
|
||||
|
|
|
@ -10,6 +10,7 @@ import os
|
|||
import re
|
||||
import tempfile
|
||||
|
||||
from collections import defaultdict
|
||||
from email import message_from_string, message_from_bytes
|
||||
from email.header import decode_header
|
||||
from email.iterators import typed_subpart_iterator
|
||||
|
@ -28,7 +29,7 @@ from ietf.doc.models import DocEvent, NewRevisionDocEvent
|
|||
from ietf.group.models import Group, Role
|
||||
from ietf.person.models import Email, Person
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.meeting.models import Meeting
|
||||
from ietf.meeting.models import Meeting, Attended
|
||||
from ietf.utils.pipe import pipe
|
||||
from ietf.utils.mail import send_mail_text, send_mail, get_payload_text
|
||||
from ietf.utils.log import log
|
||||
|
@ -510,6 +511,8 @@ def list_eligible(nomcom=None, date=None, base_qs=None):
|
|||
return list_eligible_8788(date=eligibility_date, base_qs=base_qs)
|
||||
elif eligibility_date.year in (2021,2022):
|
||||
return list_eligible_8989(date=eligibility_date, base_qs=base_qs)
|
||||
elif eligibility_date.year > 2022:
|
||||
return list_eligible_8989bis(date=eligibility_date, base_qs=base_qs)
|
||||
else:
|
||||
return Person.objects.none()
|
||||
|
||||
|
@ -536,20 +539,26 @@ def list_eligible_8713(date, base_qs=None):
|
|||
if not base_qs:
|
||||
base_qs = Person.objects.all()
|
||||
previous_five = previous_five_meetings(date)
|
||||
return remove_disqualified(three_of_five_eligible(previous_five=previous_five, queryset=base_qs))
|
||||
return remove_disqualified(three_of_five_eligible_8713(previous_five=previous_five, queryset=base_qs))
|
||||
|
||||
def list_eligible_8788(date, base_qs=None):
|
||||
if not base_qs:
|
||||
base_qs = Person.objects.all()
|
||||
previous_five = Meeting.objects.filter(number__in=['102','103','104','105','106'])
|
||||
return remove_disqualified(three_of_five_eligible(previous_five=previous_five, queryset=base_qs))
|
||||
return remove_disqualified(three_of_five_eligible_8713(previous_five=previous_five, queryset=base_qs))
|
||||
|
||||
def get_8989_eligibility_querysets(date, base_qs):
|
||||
return get_threerule_eligibility_querysets(date, base_qs, three_of_five_callable=three_of_five_eligible_8713)
|
||||
|
||||
def get_8989bis_eligibility_querysets(date, base_qs):
|
||||
return get_threerule_eligibility_querysets(date, base_qs, three_of_five_callable=three_of_five_eligible_8989bis)
|
||||
|
||||
def get_threerule_eligibility_querysets(date, base_qs, three_of_five_callable):
|
||||
if not base_qs:
|
||||
base_qs = Person.objects.all()
|
||||
|
||||
previous_five = previous_five_meetings(date)
|
||||
three_of_five_qs = new_three_of_five_eligible(previous_five=previous_five, queryset=base_qs)
|
||||
three_of_five_qs = three_of_five_callable(previous_five=previous_five, queryset=base_qs)
|
||||
|
||||
# If date is Feb 29, neither 3 nor 5 years ago has a Feb 29. Use Feb 28 instead.
|
||||
if date.month == 2 and date.day == 29:
|
||||
|
@ -564,7 +573,7 @@ def get_8989_eligibility_querysets(date, base_qs):
|
|||
Q(role__name_id__in=('chair','secr'),
|
||||
role__group__state_id='active',
|
||||
role__group__type_id='wg',
|
||||
role__group__time__lte=date,
|
||||
role__group__time__lte=date, ## TODO - inspect - lots of things affect group__time...
|
||||
)
|
||||
# was an officer since the given date (I think this is wrong - it looks at when roles _start_, not when roles end)
|
||||
| Q(rolehistory__group__time__gte=three_years_ago,
|
||||
|
@ -589,7 +598,15 @@ def list_eligible_8989(date, base_qs=None):
|
|||
if not base_qs:
|
||||
base_qs = Person.objects.all()
|
||||
three_of_five_qs, officer_qs, author_qs = get_8989_eligibility_querysets(date, base_qs)
|
||||
# Would be nice to use queryset union here, but the annotations in the three existing querysets make that difficult
|
||||
three_of_five_pks = three_of_five_qs.values_list('pk',flat=True)
|
||||
officer_pks = officer_qs.values_list('pk',flat=True)
|
||||
author_pks = author_qs.values_list('pk',flat=True)
|
||||
return remove_disqualified(Person.objects.filter(pk__in=set(three_of_five_pks).union(set(officer_pks)).union(set(author_pks))))
|
||||
|
||||
def list_eligible_8989bis(date, base_qs=None):
|
||||
if not base_qs:
|
||||
base_qs = Person.objects.all()
|
||||
three_of_five_qs, officer_qs, author_qs = get_8989bis_eligibility_querysets(date, base_qs)
|
||||
three_of_five_pks = three_of_five_qs.values_list('pk',flat=True)
|
||||
officer_pks = officer_qs.values_list('pk',flat=True)
|
||||
author_pks = author_qs.values_list('pk',flat=True)
|
||||
|
@ -624,28 +641,34 @@ def previous_five_meetings(date = None):
|
|||
date = datetime.date.today()
|
||||
return Meeting.objects.filter(type='ietf',date__lte=date).order_by('-date')[:5]
|
||||
|
||||
def three_of_five_eligible(previous_five, queryset=None):
|
||||
def three_of_five_eligible_8713(previous_five, queryset=None):
|
||||
""" Return a list of Person records who attended at least
|
||||
3 of the 5 type_id='ietf' meetings before the given
|
||||
date. Does not disqualify anyone based on held roles.
|
||||
This variant bases the calculation on MeetingRegistration.attended
|
||||
"""
|
||||
if queryset is None:
|
||||
queryset = Person.objects.all()
|
||||
return queryset.filter(meetingregistration__meeting__in=list(previous_five),meetingregistration__attended=True).annotate(mtg_count=Count('meetingregistration')).filter(mtg_count__gte=3)
|
||||
|
||||
def new_three_of_five_eligible(previous_five, queryset=None):
|
||||
def three_of_five_eligible_8989bis(previous_five, queryset=None):
|
||||
""" Return a list of Person records who attended at least
|
||||
3 of the 5 type_id='ietf' meetings before the given
|
||||
date. Does not disqualify anyone based on held roles.
|
||||
This 'new' variant bases the calculation on the Meeting.Session model rather than Stats.MeetingRegistration
|
||||
This variant bases the calculation on Meeting.Session and MeetingRegistration.checked_in
|
||||
Leadership will have to create a new RFC specifying eligibility (RFC8989 is timing out) before it can be used.
|
||||
"""
|
||||
if queryset is None:
|
||||
queryset = Person.objects.all()
|
||||
return queryset.filter(
|
||||
Q(attended__session__meeting__in=list(previous_five)),
|
||||
Q(attended__session__type='plenary')|Q(attended__session__group__type__in=['wg','rg'])
|
||||
).annotate(mtg_count=Count('attended__session__meeting',distinct=True)).filter(mtg_count__gte=3)
|
||||
|
||||
counts = defaultdict(lambda: 0)
|
||||
for meeting in previous_five:
|
||||
checked_in = meeting.meetingregistration_set.filter(reg_type='onsite', checkedin=True).values_list('person', flat=True)
|
||||
sessions = meeting.session_set.filter(Q(type='plenary') | Q(group__type__in=['wg', 'rg']))
|
||||
attended = Attended.objects.filter(session__in=sessions).values_list('person', flat=True)
|
||||
for id in set(checked_in) | set(attended):
|
||||
counts[id] += 1
|
||||
return queryset.filter(pk__in=[id for id, count in counts.items() if count >= 3])
|
||||
|
||||
def suggest_affiliation(person):
|
||||
recent_meeting = person.meetingregistration_set.order_by('-meeting__date').first()
|
||||
|
|
|
@ -12,6 +12,7 @@ class MeetingRegistrationFactory(factory.django.DjangoModelFactory):
|
|||
|
||||
meeting = factory.SubFactory(MeetingFactory)
|
||||
person = factory.SubFactory(PersonFactory)
|
||||
reg_type = 'onsite'
|
||||
first_name = factory.LazyAttribute(lambda obj: obj.person.first_name())
|
||||
last_name = factory.LazyAttribute(lambda obj: obj.person.last_name())
|
||||
attended = True
|
Loading…
Reference in a new issue