예제 #1
0
    def setUp(self):
        super(TestTimeAccounting, self).setUp()

        # this project has no allotments!
        self.project = Project.objects.order_by('pcode').all()[0]

        # setup some periods
        self.start = datetime(2000, 1, 1, 0)
        self.end   = self.start + timedelta(hours = 12)
        times = [(datetime(2000, 1, 1, 0), 5.0, "one")
               , (datetime(2000, 1, 1, 5), 3.0, "two")
               , (datetime(2000, 1, 1, 8), 4.0, "three")
               ]
        self.ps = []
        state = Period_State.objects.get(abbreviation = 'P')
        for start, dur, name in times:
            # each session has grade 4, time = 3 
            s = create_sesshun()
            s.name = name
            s.save()
            pa = Period_Accounting(scheduled = dur)
            pa.save()
            p = Period( session    = s
                      , start      = start
                      , duration   = dur
                      , state      = state
                      , accounting = pa
                      )
            p.save()
            self.ps.append(p)

        self.ta = TimeAccounting()
예제 #2
0
    def __init__(self, start):

        self.start      = start
        self.outfile    = open("./DssWeeklyReport.txt", 'w')
        self.end        = start + timedelta(days = 7)
        self.next_start = self.end + timedelta(days = 1)
        self.next_end   = self.end + timedelta(days = 7)

        # all scheduled or completed periods
        self.periods    = [p for p in Period.objects.all() if p.state.abbreviation in ("S", "C")]

        self.ta         = TimeAccounting()

        # project lists
        self.backlog = []
        self.discharge = []

        # quantities to calculate
        self.lost_hours = {}
        self.scheduled_hours ={}
        self.backlog_hours = {}
        self.discharge_hours = {}
예제 #3
0
class TestTimeAccounting(NellTestCase):

    def setUp(self):
        super(TestTimeAccounting, self).setUp()

        # this project has no allotments!
        self.project = Project.objects.order_by('pcode').all()[0]

        # setup some periods
        self.start = datetime(2000, 1, 1, 0)
        self.end   = self.start + timedelta(hours = 12)
        times = [(datetime(2000, 1, 1, 0), 5.0, "one")
               , (datetime(2000, 1, 1, 5), 3.0, "two")
               , (datetime(2000, 1, 1, 8), 4.0, "three")
               ]
        self.ps = []
        state = Period_State.objects.get(abbreviation = 'P')
        for start, dur, name in times:
            # each session has grade 4, time = 3 
            s = create_sesshun()
            s.name = name
            s.save()
            pa = Period_Accounting(scheduled = dur)
            pa.save()
            p = Period( session    = s
                      , start      = start
                      , duration   = dur
                      , state      = state
                      , accounting = pa
                      )
            p.save()
            self.ps.append(p)

        self.ta = TimeAccounting()

    def tearDown(self):
        super(TestTimeAccounting, self).tearDown()

        for p in self.ps:
            p.session.delete()
            p.delete()

    def test_getTimeLeft(self):

        # Project has no time allotted, and 5 + 3 + 4 = 12 hrs billed
        timeLeft = self.ta.getTimeLeft(self.project)
        self.assertEqual(-12.0, timeLeft)

        names = ["one", "three", "two"]
        times = [-2.0, -1.0, 0.0]

        for i, s in enumerate(self.project.sesshun_set.order_by("name").all()):
            timeLeft = self.ta.getTimeLeft(s)
            self.assertEqual(names[i], s.name)
            self.assertEqual(times[i], timeLeft)


    def test_getTime(self):

        pScheduled = self.ta.getTime('scheduled', self.project)
        self.assertEqual(pScheduled, 12.0)

        pBilled = self.ta.getTime('time_billed', self.project)
        self.assertEqual(pBilled, 12.0)


        pNotBillable = self.ta.getTime('not_billable', self.project)
        self.assertEqual(pNotBillable, 0.0)

        # now change something and watch it bubble up
        self.ps[0].accounting.not_billable = 1.0
        self.ps[0].accounting.save()
        project = Project.objects.order_by('pcode').all()[0]

        pNotBillable = self.ta.getTime('not_billable', self.ps[0].session)
        self.assertEqual(pNotBillable, 1.0)

        pNotBillable = self.ta.getTime('not_billable', project)
        self.assertEqual(pNotBillable, 1.0)

    def test_getTime_2(self):

        # check time dependencies at the project level
        dt1   = self.start + timedelta(hours = 1)
        projCmpSchd = self.ta.getTime('scheduled', self.project, dt1, True)
        self.assertEqual(projCmpSchd, 5.0)
        projUpSchd = self.ta.getTime('scheduled',  self.project, dt1, False)
        self.assertEqual(projUpSchd, 7.0)

        dt2   = self.start + timedelta(hours = 6)
        projCmpSchd = self.ta.getTime('scheduled', self.project, dt2, True)
        self.assertEqual(projCmpSchd, 8.0)
        projUpSchd = self.ta.getTime('scheduled',  self.project, dt2, False)
        self.assertEqual(projUpSchd, 4.0)

        # check time dependencies at the session level
        s1 = self.ps[0].session
        sessCmpSchd = self.ta.getTime('scheduled', s1, dt2, True)
        self.assertEqual(sessCmpSchd, 5.0)
        sessUpSchd = self.ta.getTime('scheduled',  s1, dt2, False)
        self.assertEqual(sessUpSchd, 0.0)

    def test_getUpcomingTimeBilled(self):
        prjUpBilled = self.ta.getUpcomingTimeBilled(self.project)
        self.assertEqual(prjUpBilled, 0.0)

        # change 'now'
        dt = self.start - timedelta(hours = 1)
        prjUpBilled = self.ta.getUpcomingTimeBilled(self.project, now=dt)
        self.assertEqual(prjUpBilled, 12.0)

    def test_getTimeRemainingFromCompleted(self):
        remaining = self.ta.getTimeRemainingFromCompleted(self.project)
        self.assertEqual(remaining, -12.0) # 0 - 12

        remaining = self.ta.getTimeRemainingFromCompleted(self.ps[0].session)
        self.assertEqual(remaining, -2.0) # 3 - 5

    def test_jsondict(self):

        dct = self.ta.jsondict(self.project)
        self.assertEqual(3, len(dct['sessions']))
        self.assertEqual(1, len(dct['sessions'][0]['periods']))
        self.assertEqual(-12., dct['remaining'])
        self.assertEqual(0, len(dct['times']))
        # construct a new dict to test against
        sess = {}
        for sdct in dct['sessions']:
            sess[sdct['name']] = {'remaining' : sdct['remaining']
                                , 'grade' : sdct['grade']
                                , 'total_time' : sdct['total_time']
                                #, 'periods' : sdct['periods']
                                }
        exp = {'one' : {'grade' : 4.0
                      , 'total_time' : 3.0
                      , 'remaining' : -2.0 # 3 - 5 
                        }
             , 'two' : {'grade' : 4.0
                      , 'total_time' : 3.0
                      , 'remaining' : 0.0 # 3 - 3
                       }
             , 'three':{'grade' : 4.0
                     , 'total_time' : 3.0
                     , 'remaining' : -1.0 # 3 - 4
                       }
              }
        for key, values in sess.items():
            self.assertEqual(exp[key], values)

        # test identity
        self.ta.update_from_post(self.project, dct)
        # get it fressh from the DB
        project = Project.objects.order_by('pcode').all()[0]
        dct2 = self.ta.jsondict(project)
        self.assertEqual(dct, dct2)

        # now change something
        dct['sessions'][0]['periods'][0]['not_billable'] = 1.0
        self.ta.update_from_post(project, dct)
        # get it fressh from the DB
        project = Project.objects.order_by('pcode').all()[0]
        dct2 = self.ta.jsondict(project)
        # they're different becuase not_billable bubbles up
        self.assertNotEqual(dct, dct2)
        b = dct2['not_billable']
        self.assertEqual(b, 1.0)

    def test_report(self):

        # just make sure it doesn't blow up
        self.ta.quietReport = True
        self.ta.report(self.project)
예제 #4
0
class WeeklyReport:

    def __init__(self, start):

        self.start      = start
        self.outfile    = open("./DssWeeklyReport.txt", 'w')
        self.end        = start + timedelta(days = 7)
        self.next_start = self.end + timedelta(days = 1)
        self.next_end   = self.end + timedelta(days = 7)

        # all scheduled or completed periods
        self.periods    = [p for p in Period.objects.all() if p.state.abbreviation in ("S", "C")]

        self.ta         = TimeAccounting()

        # project lists
        self.backlog = []
        self.discharge = []

        # quantities to calculate
        self.lost_hours = {}
        self.scheduled_hours ={}
        self.backlog_hours = {}
        self.discharge_hours = {}

    def print_values(self, file, values):
        if values == []:
            file.write("\tNone\n")
        else:
            for v in values:
                file.write("\t%s\n" % v)

    def get_observed_time(self, month, periods, condition):
        """
        Returns the total time that the given periods which
        match the given condition lie within the given month.
        """

        duration = 0
        for pd in [p for p in periods if eval(condition)]:
            pstart = pd.start 
            pend   = pd.end() 
            # check typical case - don't overlap the month
            if pstart.month == pend.month:
                if pstart.month == month:
                    duration += (pend - pstart).seconds / 3600.0
            else:
                # our period spans more then a month, see if any
                # of them cover our month
                if pstart.month == month:
                    _, lastday = calendar.monthrange(pstart.year, month)
                    monthEnd = datetime(pstart.year, month, lastday) + timedelta(days = 1)
                    duration += (monthEnd - pstart).seconds / 3600.0
                if pend.month == month:
                    monthStart = datetime(pend.year, pend.month, 1)
                    duration += (pend - monthStart).seconds / 3600.0
        return duration

    def get_obs_time_tuple(self, condition):
        past = self.get_observed_time(
                            self.last_month.month
                          , self.last_periods
                          , condition) 
        present = self.get_observed_time(
                            self.start.month
                          , self.this_periods
                          , condition)
        return (past, present)                  



    def report(self):
        "Calculate all time accounting needed, then print it to report file."

        self.calculate()
        self.output()

    def calculate(self):
        "Calculate all the quantities needed for this report."

        self.currentSemester   = Semester.getCurrentSemester(self.start)
        self.previousSemesters = Semester.getPreviousSemesters(self.start)
        # TBF: watch for bug!
        self.previousSemesters = [s for s in self.previousSemesters if s != self.currentSemester]
        self.futureSemesters   = Semester.getFutureSemesters(self.start)

        # just those scheduled periods in the current week
        self.observed_periods = \
            sorted(set([p for p in self.periods \
                          if AnalogSet.overlaps((p.start, p.end()), (self.start, self.end))]))
        # just those scheduled periods in the upcoming week                  
        self.upcoming_periods = \
            sorted(set([p for p in self.periods \
                          if AnalogSet.overlaps((p.start, p.end())
                                    , (self.next_start, self.next_end))]))

        self.last_month   = self.start - timedelta(days = 31)
        # scheduled periods from last month
        self.last_periods = [p for p in self.periods \
                          if p.start.month == self.last_month.month and \
                             p.start.year  == self.last_month.year]
        # scheduled periods from this month                     
        self.this_periods = [p for p in self.periods \
                          if p.start.month == self.start.month and \
                             p.start.year  == self.start.year]  

        # how does the lost time break down for all the scheduled periods in the current week?
        self.lost_hours["total_time"] = sum([p.accounting.lost_time() for p in self.observed_periods])
        self.lost_hours["weather" ] = sum([p.accounting.lost_time_weather for p in self.observed_periods])
        self.lost_hours["RFI" ] = sum([p.accounting.lost_time_rfi for p in self.observed_periods])
        self.lost_hours["other" ] = sum([p.accounting.lost_time_other for p in self.observed_periods])
        self.lost_hours["billed_to_project" ] = sum([p.accounting.lost_time_bill_project for p in self.observed_periods])

        # how do the scheduled periods break down by type and this
        # and the previous month?
        self.scheduled_hours["astronomy"] = self.get_obs_time_tuple('p.session.project.project_type.type == "science"')
        self.scheduled_hours["maintenance"] = self.get_obs_time_tuple('p.session.project.pcode == "Maintenance"')
        # NOTE: is this the most robust way to determine if a project is a test or commisioning project?
        self.scheduled_hours["test_comm"] = self.get_obs_time_tuple('p.session.project.pcode[0] == "T"')
        self.scheduled_hours["shutdown"] = self.get_obs_time_tuple('p.session.project.pcode == "Shutdown"')

        # The distinction between 'backlog' and 'discharge' is that
        # 'backlog' is from only previous semesters, while discharge
        # inclues *all* semesters.
        # But they both only care about incomplete Astronomy projects
        self.backlog    = [p for p in Project.objects.all() \
            if p.semester in self.previousSemesters \
            and not p.complete \
            and p.get_category() == "Astronomy"] 
                       
        self.backlog_hours["total_time"] = sum([self.ta.getTimeLeft(p) for p in self.backlog])
        self.backlog_hours["years"] = {}
        for y in sorted(list(set([s.start().year for s in self.previousSemesters]))):
            projs = [p for p in self.backlog if p.semester.start().year == y]
            self.backlog_hours["years"]["%d" % y] = (sum([self.ta.getTimeLeft(p) for p in projs]), len(projs))
        self.backlog_hours["monitoring"] = sum([self.ta.getTimeLeft(p) for p in self.backlog \
                           if any([s.session_type.type == "windowed" \
                                   for s in p.sesshun_set.all()])])
        self.backlog_hours["vlbi"] = sum([self.ta.getTimeLeft(p) for p in self.backlog \
                           if any([s.observing_type.type == "vlbi" \
                                   for s in p.sesshun_set.all()])])                           

        self.discharge    = [p for p in Project.objects.all() \
            if not p.complete \
            and p.get_category() == "Astronomy"] 
          

        total_time = sum([self.ta.getTimeLeft(p) for p in self.discharge]) 
        monitoring = sum([self.ta.getTimeLeft(p) for p in self.discharge \
                           if any([s.session_type.type == "windowed" \
                                   for s in p.sesshun_set.all()]) and \
                              self.ta.getProjectTotalTime(p) <= 200.])
        vlbi       = sum([self.ta.getTimeLeft(p) for p in self.discharge \
                           if any([s.observing_type.type == "vlbi" \
                                   for s in p.sesshun_set.all()])])
        large      = sum([self.ta.getTimeLeft(p) for p in self.discharge \
                          if self.ta.getProjectTotalTime(p) > 200.])
        self.discharge_hours['total_time'] = total_time                  
        self.discharge_hours['monitoring'] = monitoring                  
        self.discharge_hours['vlbi']       = vlbi                  
        self.discharge_hours['large']      = large                  
        self.discharge_hours['rest']       = total_time - monitoring - vlbi - large


        self.nextWeekReservations = Reservation.objects.filter(
                                          end_date__gte = self.next_start
                                                       ).filter(
                                          start_date__lte = self.next_end)

    def output(self):
        "Format all the calculated quantities and print to report file."

        self.outfile.write("Last 7 days (%s to %s)\n" % (self.start.strftime("%m/%d/%Y")
                                              , self.end.strftime("%m/%d/%Y")))
        self.outfile.write("======================================\n")
    

    
        self.outfile.write("Observations for proposals\n")
        self.print_values(self.outfile
                   , set([p.session.project.pcode for p in self.observed_periods]))
    
        self.outfile.write("\nCompleted proposals\n")
        self.print_values(self.outfile, set([p.session.project.pcode \
                                   for p in self.observed_periods \
                                   if p.session.project.complete]))
    
        self.outfile.write("\nTotal Lost Time was %2.2f hr\n" % self.lost_hours["total_time"]) 
        self.outfile.write("\tweather = %2.2f hr\n" % self.lost_hours["weather"]) 
        self.outfile.write("\tRFI     = %2.2f hr\n" % self.lost_hours["RFI"]) 
        self.outfile.write("\tother   = %2.2f hr\n" % self.lost_hours["other"]) 
        self.outfile.write("\tLost Time Billed to Project = %2.2f hr\n" % self.lost_hours["billed_to_project"]) 
    
        self.outfile.write("\nNext Week\n")
        self.outfile.write("=========\n")
        self.outfile.write("Observations scheduled for %s - %s (note this only includes pre-scheduled projects):\n" % (self.next_start.strftime("%m/%d/%Y"), self.next_end.strftime("%m/%d/%Y")))
        values = ["%s, [%s], PI: %s\n\t\t%s\n\t\t%s" % \
                  (p.session.project.pcode
                 , p.session.observing_type.type
                 , p.session.project.principal_investigator()
                 , p.session.project.name
                 , p.__str__())
                  for p in self.upcoming_periods]
        self.print_values(self.outfile, set(values))
    
        projects = set([p.session.project for p in self.upcoming_periods])
        self.outfile.write("\nContact Information for pre-scheduled projects for %s - %s\n" % (self.next_start.strftime("%m/%d/%Y"), self.next_end.strftime("%m/%d/%Y")))
        self.outfile.write("==========================================================================\n")
        self.outfile.write("\tProject     PI                 Bands Email [NRAO contact]\n")
        self.outfile.write("\t----------- ------------------ ----- --------------------\n")
        values = ["%s %s %s %s [%s]" % \
                      (p.pcode.ljust(11)
                     , self.projPIname(p) 
                     , ",".join(p.rcvrs_specified())[:4].center(5)
                     , self.projPIemail(p) 
                     , ";".join([f.user.name() for f in p.friend_set.all()])
                     )
                  for p in projects]
        self.print_values(self.outfile, set(values))
    

    
        self.outfile.write("\nScheduled Hours [backup]\n")
        self.outfile.write("========================\n")
        self.outfile.write("Category/Month -> %s %s\n" % \
                      (self.last_month.strftime("%B").center(8)

                     , self.start.strftime("%B").center(8)))

        last, this = self.scheduled_hours['astronomy']             
        self.outfile.write("     Astronomy ~  %s %s\n" % \
                      (("%.1f" % last).center(8), ("%.1f" % this).center(8)))
        last, this = self.scheduled_hours['maintenance']             
        self.outfile.write("   Maintenance ~  %s %s\n" % \
                      (("%.1f" % last).center(8), ("%.1f" % this).center(8)))
        last, this = self.scheduled_hours['test_comm']             
        self.outfile.write("   Test & Comm ~  %s %s\n" % \
                      (("%.1f" % last).center(8), ("%.1f" % this).center(8)))
        last, this = self.scheduled_hours['shutdown']             
        self.outfile.write("      Shutdown ~  %s %s\n" % \
                      (("%.1f" % last).center(8), ("%.1f" % this).center(8)))

        self.outfile.write(
            "\nCurrent backlog of Reg & RSS proposals [hours prior to %s*] = %.1f\n" % \
                (self.currentSemester, self.backlog_hours["total_time"])) 
 
        self.outfile.write("\t[")
        years = sorted(self.backlog_hours["years"].keys())
        self.outfile.write(", ".join(["%s: %.1f (%d)" % \
            (y, self.backlog_hours["years"][y][0], self.backlog_hours["years"][y][1]) for y in years if self.backlog_hours["years"][y][1] != 0.0]))


        self.outfile.write("]\n")
        self.outfile.write("\tBacklog includes %.1f hours of monitoring projects\n" % \
                      self.backlog_hours["monitoring"])

        self.outfile.write("\t                 %.1f hours of vlbi projects\n" % \
                      self.backlog_hours["vlbi"])

    
        self.outfile.write("\nTotal time to discharge [hours] = %.1f\n" % self.discharge_hours['total_time'])
        self.outfile.write("\tIncludes %.1f hours of monitoring projects (not Large) after semester %s\n" % \
            (self.discharge_hours['monitoring'], self.currentSemester))
        self.outfile.write("\t         %.1f hours of Regular & RRS projects\n" % self.discharge_hours['rest'])
        self.outfile.write("\t         %.1f hours of Large projects\n" % self.discharge_hours['large'])
        self.outfile.write("\t         %.1f hours of VLBI projects\n" % self.discharge_hours['vlbi'])
        self.outfile.write("\n* Includes projects that are on hold for semester %s\n" % self.currentSemester)
    
        visitors = ["%s - %s - %s [%s] [%s]" % (r.start_date.strftime("%m/%d")
                                  , r.end_date.strftime("%m/%d")
                                  , r.user.name()
                                  , ', '.join(r.user.getProjects())
                                  , ', '.join(r.user.getFriendLastNames())) \
                    for r in self.nextWeekReservations]              
    
        self.outfile.write("\nVisitors coming for %s - %s:\n" % (self.next_start.strftime("%m/%d/%Y"), self.next_end.strftime("%m/%d/%Y")))
        self.print_values(self.outfile, visitors)

    def projPIname(self, proj):
        if proj.principal_investigator() is not None:
            name = str(proj.principal_investigator())[:17].ljust(18)
        else:
            name = "None"
        return name    
    
    def projPIemail(self, proj):        
        if proj.principal_investigator() is not None:
            emails = proj.principal_investigator().getEmails()
            email = emails[0] if len(emails) > 0 else "None"
        else:
            email = "None"
        return email