Switch from actual applications to popularity
authorSverre Rabbelier <srabbelier@gmail.com>
Sun, 08 Mar 2009 16:26:17 +0000
changeset 1751 17c7a7a48dc7
parent 1750 1ac2d27fdb6b
child 1752 255117ccd6a0
Switch from actual applications to popularity Patch by: Sverre Rabbelier
app/soc/logic/allocations.py
app/soc/views/models/program.py
tests/app/soc/logic/test_allocations.py
--- a/app/soc/logic/allocations.py	Sun Mar 08 15:00:59 2009 +0000
+++ b/app/soc/logic/allocations.py	Sun Mar 08 16:26:17 2009 +0000
@@ -47,24 +47,19 @@
   # the convenience of any mathematicians that happen to read this
   # piece of code ;).
 
-  def __init__(self, orgs, applications, mentors, slots,
+  def __init__(self, orgs, popularity, mentors, slots,
                max_slots_per_org, min_slots_per_org, iterative):
     """Initializes the allocator.
 
     Args:
       orgs: a list of all the orgs that need to be allocated
-      applications: a dictionary with for each org a list of applicants
+      popularity: the amount of applications per org
       mentors: the amount of assigned mentors per org
       slots: the total amount of available slots
       max_slots_per_org: how many slots an org should get at most
       min_slots_per_org: how many slots an org should at least get
     """
 
-    all_applications = []
-
-    for _, value in applications.iteritems():
-      all_applications += value
-    
     self.locked_slots = {}
     self.adjusted_slots = {}
     self.adjusted_orgs = []
@@ -74,9 +69,8 @@
     self.max_slots_per_org = max_slots_per_org
     self.min_slots_per_org = min_slots_per_org
     self.orgs = set(orgs)
-    self.applications = applications
+    self.initial_popularity = popularity
     self.mentors = mentors
-    self.all_applications = set(all_applications)
     self.iterative = iterative
 
   def allocate(self, locked_slots, adjusted_slots):
@@ -92,6 +86,9 @@
 
     self.buildSets()
 
+    if not sum(self.popularity.values()) or not sum(self.mentors.values()):
+      return {}
+
     if self.iterative:
       return self.iterativeAllocation()
     else:
@@ -101,8 +98,9 @@
     """Allocates slots with the specified constraints
     """
 
+    popularity = self.initial_popularity.copy()
+
     # set s
-    all_applications = self.all_applications
     locked_slots = self.locked_slots
     adjusted_slots = self.adjusted_slots
 
@@ -112,17 +110,16 @@
 
     # set a' and b'
     unlocked_orgs = self.orgs.difference(locked_orgs)
-    # unadjusted_orgs = self.orgs.difference(adjusted_orgs)
 
     # set a*b and a'*b'
     locked_and_adjusted_orgs = locked_orgs.intersection(adjusted_orgs)
-    
-    # unlocked_and_unadjusted_orgs = unlocked_orgs.intersection(unadjusted_orgs)
 
     # a+o and b+o should be o
     locked_orgs_or_orgs = self.orgs.union(locked_orgs)
     adjusted_orgs_or_orgs = self.orgs.union(adjusted_orgs)
 
+    total_popularity = sum(popularity.values())
+
     # an item can be only a or b, so a*b should be empty
     if locked_and_adjusted_orgs:
       raise Error("Cannot have an org locked and adjusted")
@@ -135,17 +132,11 @@
     if len(adjusted_orgs_or_orgs) != len(self.orgs):
       raise Error("Unknown org as adjusted slot")
 
-    # set l and l'
-    locked_applications = set(itertools.chain(*locked_slots.keys()))
-    unlocked_applications = all_applications.difference(locked_applications)
-
     self.adjusted_orgs = adjusted_orgs
     self.unlocked_orgs = unlocked_orgs
     self.locked_orgs = locked_orgs
-    self.unlocked_applications = unlocked_applications
-
-    popularity = ((k, len(v)) for k, v in self.applications.iteritems())
-    self.popularity = dict(popularity)
+    self.popularity = popularity
+    self.total_popularity = total_popularity
 
   def rangeSlots(self, slots, org):
     """Returns the amount of slots for the org within the required bounds.
@@ -166,23 +157,20 @@
     adjusted_slots = self.adjusted_slots
     locked_orgs = self.locked_orgs
     locked_slots = self.locked_slots
-    unlocked_applications = self.unlocked_applications
 
-    unlocked_applications_count = len(unlocked_applications)
-    unallocated_applications_count = unlocked_applications_count
+    unallocated_popularity = self.total_popularity - len(locked_slots)
 
     available_slots = self.slots
     allocations = {}
 
     for org in self.orgs:
-      org_applications = self.applications[org]
-      org_applications_count = len(org_applications)
+      popularity = self.popularity[org]
       mentors = self.mentors[org]
 
       if org in locked_orgs:
         slots = locked_slots[org]
-      elif unallocated_applications_count:
-        weight = float(org_applications_count) / float(unallocated_applications_count)
+      elif unallocated_popularity:
+        weight = float(popularity) / float(unallocated_popularity)
         slots = int(math.floor(weight*available_slots))
 
       if org in adjusted_orgs:
@@ -194,7 +182,7 @@
 
       allocations[org] = slots
       available_slots -= slots
-      unallocated_applications_count -= org_applications_count
+      unallocated_popularity -= popularity
 
     return allocations
 
@@ -207,9 +195,7 @@
     locked_orgs = self.locked_orgs
     locked_slots = self.locked_slots
     unlocked_orgs = self.unlocked_orgs
-    unlocked_applications = self.unlocked_applications
-
-    total_popularity = sum(self.popularity.values())
+    total_popularity = self.total_popularity
 
     available_slots = self.slots
     allocations = {}
@@ -223,6 +209,7 @@
       total_popularity -= popularity
       available_slots -= slots
       allocations[org] = slots
+      del self.popularity[org]
 
     # adjust the orgs in need of adjusting
     for org in adjusted_orgs:
@@ -231,6 +218,7 @@
       adjustment = (float(total_popularity)/float(available_slots))*slots
       adjustment = int(math.ceil(adjustment))
       self.popularity[org] += adjustment
+      total_popularity += adjustment
 
     # adjust the popularity so that the invariants are always met
     for org in unlocked_orgs:
@@ -248,9 +236,6 @@
 
     # do the actual calculation
     for org in unlocked_orgs:
-      org_applications = self.applications[org]
-      org_applications_count = len(org_applications)
-
       popularity = self.popularity[org]
       raw_slots = (float(popularity)/float(total_popularity))*available_slots
       slots = int(math.floor(raw_slots))
--- a/app/soc/views/models/program.py	Sun Mar 08 15:00:59 2009 +0000
+++ b/app/soc/views/models/program.py	Sun Mar 08 16:26:17 2009 +0000
@@ -38,6 +38,7 @@
 from soc.logic.models import mentor as mentor_logic
 from soc.logic.models import organization as org_logic
 from soc.logic.models import org_admin as org_admin_logic
+from soc.logic.models import student_proposal as student_proposal_logic
 from soc.logic.models import program as program_logic
 from soc.logic.models import student as student_logic
 from soc.logic.models.document import logic as document_logic
@@ -171,33 +172,37 @@
     program = program_logic.logic.getFromKeyFields(kwargs)
     slots = program.slots
 
+    filter = {
+          'scope': program,
+          'status': 'active',
+          }
+
+    query = org_logic.logic.getQueryForFields(filter=filter)
+    organizations = org_logic.logic.getAll(query)
+
+    locked_slots = adjusted_slots = {}
+
     if request.method == 'POST' and 'result' in request.POST:
       result = request.POST['result']
 
-      from_json = simplejson.loads(result).iteritems()
-
-      # filter out all orgs where the link_id is 'undefined'
-      orgs = dict( ((k,v) for k, v in from_json if k != 'undefined'))
+      from_json = simplejson.loads(result)
 
-      locked_slots = dicts.groupDictBy(orgs, 'locked', 'slots')
-      adjusted_slots = dicts.groupDictBy(orgs, 'adjustment')
-    else:
-      filter = {
-          'scope': program,
-          }
+      locked_slots = dicts.groupDictBy(from_json, 'locked', 'slots')
+      adjusted_slots = dicts.groupDictBy(from_json, 'adjustment')
+
+    orgs = [i.link_id for i in organizations]
+    applications = {}
+    mentors = {}
 
-      query = org_logic.logic.getQueryForFields(filter=filter)
-      entities = [i.toDict() for i in org_logic.logic.getAll(query)]
-
-      # group orgs by link_id
-      orgs = dict( ((i['link_id'], i) for i in entities) )
-
-      # default to no orgs locked nor adjusted
-      locked_slots = adjusted_slots = {}
-
-    # TODO(Lennard): use real data here
-    applications = dict( ((i, [1, 2]) for i in orgs.keys()) )
-    mentors = dict( ((i, 1000) for i in orgs.keys()) )
+    for org in organizations:
+      filter = {
+          'org': org,
+          'status': ['new', 'pending']
+          }
+      query = student_proposal_logic.logic.getQueryForFields(filter=filter)
+      proposals = student_proposal_logic.logic.getAll(query)
+      applications[org.link_id] = len(proposals)
+      mentors[org.link_id] = len([i for i in proposals if i.mentor != None])
 
     # TODO: Use configuration variables here
     max_slots_per_org = 40
--- a/tests/app/soc/logic/test_allocations.py	Sun Mar 08 15:00:59 2009 +0000
+++ b/tests/app/soc/logic/test_allocations.py	Sun Mar 08 16:26:17 2009 +0000
@@ -69,58 +69,22 @@
     self.iterative = False
 
     apps = {
-        'asf': self.allocate(20, 20),
-        'gcc': self.allocate(15, 30),
-        'git': self.allocate(6, 6),
-        'google': self.allocate(3, 10),
-        'melange': self.allocate(100, 3),
+        'asf': (20, 20),
+        'gcc': (15, 50),
+        'git': (6, 6),
+        'google': (3, 10),
+        'melange': (100, 3),
         }
 
-    self.applications = dict([(k,a) for k, (m, a) in apps.iteritems()])
-    self.mentors = dict([(k,m) for k, (m, a) in apps.iteritems()])
+    self.popularity = dict([(k,a) for k, (a, m) in apps.iteritems()])
+    self.mentors = dict([(k,m) for k, (a, m) in apps.iteritems()])
 
-    self.orgs = self.applications.keys()
+    self.orgs = self.popularity.keys()
 
     self.allocater = allocations.Allocator(
-        self.orgs, self.applications, self.mentors, self.slots,
+        self.orgs, self.popularity, self.mentors, self.slots,
         self.max_slots_per_org, self.min_slots_per_org, self.iterative)
 
-  def allocate(self, count, max):
-    """Returns a list with count new student objects.
-    """
-
-    i = self.allocated
-    j = i + count
-    self.allocated += count
-
-    return max, [Student(i) for i in range(i,j)]
-
-  def testAllocate(self):
-    """Test that the allocate helper works properly.
-
-    A meta-test, it never hurts to be certain.
-    """
-
-    stash = self.allocated
-    self.allocated = 0
-
-    expected = [Student(0), Student(1), Student(2)]
-    count, actual = self.allocate(3, 0)
-    self.failUnlessEqual(expected, actual)
-    self.failUnlessEqual(count, 0)
-
-    expected = []
-    count, actual = self.allocate(0, 10)
-    self.failUnlessEqual(expected, actual)
-    self.failUnlessEqual(count, 10)
-
-    expected = [Student(3)]
-    count, actual = self.allocate(1, 5)
-    self.failUnlessEqual(expected, actual)
-    self.failUnlessEqual(count, 5)
-
-    self.allocated = stash
-
   def testInitialAllocation(self):
     """Test that an allocation with no arguments does not crash.
     """
@@ -220,7 +184,7 @@
     with_adjusting = self.allocater.allocate(locked_slots, adjusted_slots)
     without_adjusting = self.allocater.allocate(locked_slots, {})
 
-    expected = without_adjusting['gcc']
+    expected = without_adjusting['gcc'] + 10
     actual = with_adjusting['gcc']
 
-    self.failUnless(actual > expected)
+    self.failIf(actual < expected, "%d < %d" % (actual, expected))