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 __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 = {}
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)
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