コード例 #1
0
def test_group_member_employment_start_end(person, grpname, expected):
    try:
        actual = group_member_employment_start_end(person, grpname)
        assert actual == expected
    except:
        with pytest.raises(RuntimeError) as excinfo:
            actual = group_member_employment_start_end(person, grpname)
        assert str(excinfo.value) == expected
コード例 #2
0
    def sout(self):
        rc = self.rc
        outdated, depleted, underspent, overspent = [], [], [], []
        people = list(self.gtx['people'])
        all_appts = collect_appts(people,
                                  filter_key='type',
                                  filter_value='gra')
        all_appts.extend(
            collect_appts(people, filter_key='type', filter_value='ss'))
        all_appts.extend(
            collect_appts(people, filter_key='type', filter_value='pd'))
        if rc.projection_from_date:
            projection_from_date = date_parser.parse(
                rc.projection_from_date).date()
        else:
            projection_from_date = date.today()

        # collecting amounts and time interval for all grants
        all_grants = {}
        grants_end, grants_begin = None, None
        for grant in self.gtx['grants']:
            if grant.get('_id') in BLACKLIST or grant.get(
                    'alias') in BLACKLIST:
                if rc.verbose:
                    print(
                        f"skipping {grant.get('alias')} since it is in the blacklist"
                    )
                continue
            prop = rc.client.find_one(rc.database, "proposals",
                                      {"_id": grant.get("proposal_id")})
            if prop.get('year'):
                del prop['year']
            grant_begin = get_dates(grant)['begin_date'] if grant.get('begin_date') or grant.get('begin_year') \
                else get_dates(prop)['begin_date']
            grant_end = get_dates(grant)['end_date'] if grant.get('end_date') or grant.get('end_year') \
                else get_dates(prop)['end_date']
            all_grants.update({
                grant.get('alias'): {
                    'begin_date':
                    grant_begin,
                    'end_date':
                    grant_end,
                    'budget':
                    grant.get('budget', [{
                        'begin_date': grant_begin,
                        'end_date': grant_end
                    }]),
                    'burn':
                    grant_burn(grant, all_appts)
                }
            })
            if not grants_begin or grant_begin < grants_begin:
                grants_begin = grant_begin
            if not grants_end or grant_end > grants_end:
                grants_end = grant_end

        # checking appointments
        grants_with_appts = []
        cum_months_to_cover = 0
        for person in self.gtx['people']:
            person_dates = group_member_employment_start_end(person, "bg")
            last_emp, months_to_cover = 0, 0
            emps = [
                person_date for person_date in person_dates
                if not person_date.get("permanent")
            ]
            emps.sort(key=lambda x: x.get('end_date', 0))
            if emps:
                date_last_emp = emps[-1].get('end_date', 0)
                months_to_cover = round(
                    (date_last_emp - projection_from_date).days / 30.5, 2)
            if months_to_cover > 0 and emps[-1].get("status") in [
                    "phd", "postdoc"
            ]:
                print(
                    f"{person['_id']} needs to be covered for {months_to_cover} months"
                )
                cum_months_to_cover += months_to_cover

            appts = collect_appts([person],
                                  filter_key='type',
                                  filter_value='gra')
            appts.extend(
                collect_appts([person], filter_key='type', filter_value='ss'))
            appts.extend(
                collect_appts([person], filter_key='type', filter_value='pd'))
            if not appts:
                continue
            is_fully_appointed(
                person, min(get_dates(appt)['begin_date'] for appt in appts),
                max(get_dates(appt)['end_date'] for appt in appts))
            for appt in appts:
                if appt.get("grant") in BLACKLIST:
                    continue
                grants_with_appts.append(appt.get("grant"))
                this_grant = all_grants.get(appt.get('grant'))
                if not this_grant:
                    raise RuntimeError(
                        "    grant: {}, person: {}, appointment: {}, grant not found in grants database"
                        .format(appt.get("grant"), person.get("_id"),
                                appt.get("_id")))
                appt_begin, appt_end = get_dates(
                    appt)['begin_date'], get_dates(appt)['end_date']
                outdated_period, depleted_period = False, False
                for x in range((appt_end - appt_begin).days + 1):
                    day = appt_begin + relativedelta(days=x)
                    if not outdated_period:
                        if not this_grant['burn'].get(day):
                            outdated_period = True
                            outdated.append(
                                "    person: {}, appointment: {}, grant: {},\n"
                                "            from {} until {}".format(
                                    person.get('_id'), appt.get('_id'),
                                    appt.get('grant'),
                                    str(day) if day < this_grant['begin_date']
                                    else this_grant['end_date'] +
                                    relativedelta(days=1),
                                    str(min(appt_end,
                                            this_grant['begin_date']))
                                    if day < this_grant['begin_date'] else
                                    str(day)))
                    else:
                        if this_grant['burn'].get(day):
                            outdated_period = False
                    if not (depleted_period or outdated_period):
                        day_burn, this_burn = 0, this_grant['burn']
                        if appt.get('type') == 'gra':
                            day_burn = this_burn[day]['student_days']
                        elif appt.get('type') == 'pd':
                            day_burn = this_burn[day]['postdoc_days']
                        elif appt.get('type') == 'ss':
                            day_burn = this_burn[day]['ss_days']
                        if day_burn < -5:
                            # FIXME change to display depleted until next >-5 amt instead of appt_end
                            depleted.append(
                                "    person: {}, appointment: {}, grant: {},\n"
                                "            from {} until {}".format(
                                    person['_id'], appt['_id'],
                                    appt.get('grant'), str(day),
                                    str(appt_end)))
                            depleted_period = True

        # setup for plotting grants
        grants_with_appts = list(set(grants_with_appts))
        datearray, cum_student, cum_pd, cum_ss = [], None, None, None
        if not rc.no_plot:
            for x in range((grants_end - grants_begin).days + 1):
                datearray.append(grants_begin + relativedelta(days=x))
            cum_student, cum_pd, cum_ss = [0.0] * len(datearray), [
                0.0
            ] * len(datearray), [0.0] * len(datearray)
        plots = []

        # calculating grant surplus and deficit
        cum_underspend = 0
        for grant in all_grants.keys():
            if grant not in grants_with_appts:
                if rc.verbose:
                    print(
                        f"skipping {grant} since it it does not support any appointments"
                    )
                continue
            budget_begin = min(
                get_dates(period)['begin_date']
                for period in all_grants[grant].get('budget'))
            budget_end = max(
                get_dates(period)['end_date']
                for period in all_grants[grant].get('budget'))
            if all_grants[grant]['begin_date'] != budget_begin:
                raise RuntimeError(
                    f"grant {grant} does not have a correct budget begin date. "
                    f"grant begin: {all_grants[grant]['begin_date']} budget begin: {budget_begin}"
                )
            elif all_grants[grant]['end_date'] != budget_end:
                raise RuntimeError(
                    f"grant {grant} does not have a correct budget end date."
                    f" grant end: {all_grants[grant]['end_date']} budget end: {budget_end}"
                )
            days_to_go = (all_grants[grant]['end_date'] -
                          projection_from_date).days
            this_burn = all_grants[grant]['burn']
            end_amount = this_burn.get(all_grants[grant]['end_date'])['student_days'] + \
                         this_burn.get(all_grants[grant]['end_date'])['ss_days'] + \
                        this_burn.get(all_grants[grant]['end_date'])['postdoc_days']
            if end_amount > 15.25:
                underspent.append(
                    "    end: {}, grant: {}, underspend amount: {} months,\n"
                    "      required ss+gra burn: {}".format(
                        str(all_grants[grant]['end_date']), grant,
                        round(end_amount / 30.5, 2),
                        round(end_amount / days_to_go, 2)))
                cum_underspend += end_amount
            elif end_amount < -30.5:
                overspent.append(
                    "    end: {}, grant: {}, overspend amount: {} months".
                    format(str(all_grants[grant]['end_date']), grant,
                           round(end_amount / 30.5, 2)))
            # values for individual and cumulative grant burn plots
            if not rc.no_plot:
                grant_dates = [
                    all_grants[grant]['begin_date'] + relativedelta(days=x)
                    for x in range((all_grants[grant]['end_date'] -
                                    all_grants[grant]['begin_date']).days + 1)
                ]
                this_student, this_pd, this_ss = [0.0] * len(grant_dates), [0.0] * len(grant_dates), \
                                                 [0.0] * len(grant_dates)
                counter = 0
                for x in range(len(datearray)):
                    day_burn = this_burn.get(datearray[x])
                    if day_burn:
                        this_student[counter] = day_burn['student_days']
                        this_pd[counter] = day_burn['postdoc_days']
                        this_ss[counter] = day_burn['ss_days']
                        cum_student[x] += day_burn['student_days']
                        cum_pd[x] += day_burn['postdoc_days']
                        cum_ss[x] += day_burn['ss_days']
                        counter += 1
                plots.append(
                    plotter(grant_dates,
                            student=this_student,
                            pd=this_pd,
                            ss=this_ss,
                            title=grant)[0])

        if outdated:
            print("appointments on outdated grants:")
            for appt in outdated:
                print(appt)
        if depleted:
            print("appointments on depleted grants:")
            for appt in depleted:
                print(appt)
        if underspent:
            print("underspent grants:")
            for grant in underspent:
                print(grant)
            print(
                f"cumulative underspend = {round(cum_underspend/30.5, 2)} months, cumulative months to support = {round(cum_months_to_cover, 2)}"
            )
        if overspent:
            print("overspent grants:")
            for grant in overspent:
                print(grant)

        if not rc.no_plot:
            for plot in plots:
                if not rc.no_gui:
                    plt.show()
            cum_plot, cum_ax, outp = plotter(datearray,
                                             student=cum_student,
                                             pd=cum_pd,
                                             ss=cum_ss,
                                             title="Cumulative burn")
            if not rc.no_gui:
                plt.show()

            print(outp)

        return
コード例 #3
0
    def sout(self):
        rc = self.rc
        outdated, depleted, underspent, overspent = [], [], [], []
        people = list(self.gtx['people'])
        all_appts = collect_appts(people,
                                  filter_key='type',
                                  filter_value='gra')
        all_appts.extend(
            collect_appts(people, filter_key='type', filter_value='ss'))
        all_appts.extend(
            collect_appts(people, filter_key='type', filter_value='pd'))
        if rc.projection_from_date:
            projection_from_date = date_parser.parse(
                rc.projection_from_date).date()
        else:
            projection_from_date = date.today()

        # collecting amounts and time interval for all grants
        _future_grant["begin_date"] = projection_from_date
        _future_grant["end_date"] = projection_from_date + timedelta(days=2190)
        _future_grant["budget"][0]["begin_date"] = projection_from_date
        _future_grant["budget"][0][
            "end_date"] = projection_from_date + timedelta(days=2190)
        _future_grant["burn"] = grant_burn(_future_grant, all_appts)
        all_grants = merge_collections_superior(self.gtx["proposals"],
                                                self.gtx["grants"],
                                                "proposal_id")
        all_grants.append(_future_grant)
        most_grants_id = [
            grant for grant in all_grants if grant.get('_id') not in BLACKLIST
        ]
        most_grants = [
            grant for grant in most_grants_id
            if grant.get('alias') not in BLACKLIST
        ]
        collecting_grants_with_appts = []
        for person in self.gtx['people']:
            appts = collect_appts([person],
                                  filter_key='type',
                                  filter_value='gra')
            appts.extend(
                collect_appts([person], filter_key='type', filter_value='ss'))
            appts.extend(
                collect_appts([person], filter_key='type', filter_value='pd'))
            if len(appts) > 0:
                person.update({"appts": appts})
                collecting_grants_with_appts.extend(
                    [appt.get("grant") for appt in appts])
        grants_with_appts = list(set(collecting_grants_with_appts))
        appointed_grants = [
            grant for grant in most_grants
            if grant.get("_id") in grants_with_appts
            or grant.get("alias") in grants_with_appts
        ]
        grants_end, grants_begin = None, None
        for grant in appointed_grants:
            grant['burn'] = grant_burn(grant, all_appts)
            grant_begin = get_dates(grant)['begin_date']
            grant_end = get_dates(grant)['end_date']
            grant.update({"begin_date": grant_begin, "end_date": grant_end})
            if not grants_begin or grant_begin < grants_begin:
                grants_begin = grant_begin
            if not grants_end or grant_end > grants_end:
                grants_end = grant_end

        # checking appointments
        cum_months_to_cover = 0
        for person in self.gtx['people']:
            if not person.get("appts"):
                continue
            appts = person.get("appts")
            person_dates = group_member_employment_start_end(person, "bg")
            last_emp, months_to_cover = 0, 0
            emps = [
                person_date for person_date in person_dates
                if not person_date.get("permanent")
            ]
            emps.sort(key=lambda x: x.get('end_date', 0))
            is_fully_appointed(
                person, min(get_dates(appt)['begin_date'] for appt in appts),
                max(get_dates(appt)['end_date'] for appt in appts))
            for appt in appts:
                if appt.get("grant") in BLACKLIST:
                    continue
                this_grant = fuzzy_retrieval(appointed_grants,
                                             ["_id", "alias"],
                                             appt.get('grant'))
                if not this_grant:
                    raise RuntimeError(
                        "    grant: {}, person: {}, appointment: {}, grant not found in grants database"
                        .format(appt.get("grant"), person.get("_id"),
                                appt.get("_id")))
                appt_begin, appt_end = get_dates(
                    appt)['begin_date'], get_dates(appt)['end_date']
                outdated_period, depleted_period = False, False
                for x in range((appt_end - appt_begin).days + 1):
                    day = appt_begin + relativedelta(days=x)
                    if not outdated_period:
                        if not this_grant.get('burn'):
                            print(this_grant.get('_id'))
                        if not this_grant['burn'].get(day):
                            outdated_period = True
                            outdated.append(
                                "    person: {}, appointment: {}, grant: {},\n"
                                "            from {} until {}".format(
                                    person.get('_id'), appt.get('_id'),
                                    appt.get('grant'),
                                    str(day) if day < this_grant['begin_date']
                                    else this_grant['end_date'] +
                                    relativedelta(days=1),
                                    str(min(appt_end,
                                            this_grant['begin_date']))
                                    if day < this_grant['begin_date'] else
                                    str(day)))
                    else:
                        if this_grant['burn'].get(day):
                            outdated_period = False
                    if not (depleted_period or outdated_period):
                        day_burn, this_burn = 0, this_grant['burn']
                        if appt.get('type') == 'gra':
                            day_burn = this_burn[day]['student_days']
                        elif appt.get('type') == 'pd':
                            day_burn = this_burn[day]['postdoc_days']
                        elif appt.get('type') == 'ss':
                            day_burn = this_burn[day]['ss_days']
                        if day_burn < -5:
                            # FIXME change to display depleted until next >-5 amt instead of appt_end
                            depleted.append(
                                "    person: {}, appointment: {}, grant: {},\n"
                                "            from {} until {}".format(
                                    person['_id'], appt['_id'],
                                    appt.get('grant'), str(day),
                                    str(appt_end)))
                            depleted_period = True

        # setup for plotting grants
        datearray, cum_student, cum_pd, cum_ss = [], None, None, None
        if not rc.no_plot:
            for x in range((grants_end - grants_begin).days + 1):
                datearray.append(grants_begin + relativedelta(days=x))
            cum_student, cum_pd, cum_ss = [0.0] * len(datearray), [
                0.0
            ] * len(datearray), [0.0] * len(datearray)
        plots = []

        # calculating grant surplus and deficit
        cum_underspend = 0
        for grant in appointed_grants:
            tracking = [
                balance for balance in grant.get('tracking', []) if balance
            ]
            # if all_grants[grant]:
            #     tracking = [balance for balance in all_grants[grant].get('tracking',[]) if balance]
            # else:
            #     tracking = []
            if len(tracking) > 0:
                tracking.sort(key=lambda x: x[0])
                recent_balance = tracking[-1]
                recent_balance[1] = recent_balance[1] / MONTHLY_COST_QUANTUM
            else:
                recent_balance = [projection_from_date, 0]
            budget_begin = min(
                get_dates(period)['begin_date']
                for period in grant.get('budget'))
            budget_end = max(
                get_dates(period)['end_date']
                for period in grant.get('budget'))
            if grant['begin_date'] != budget_begin:
                raise RuntimeError(
                    f"grant {grant.get('alias')} does not have a correct budget begin date. "
                    f"grant begin: {grant['begin_date']} budget begin: {budget_begin}"
                )
            elif grant['end_date'] != budget_end:
                raise RuntimeError(
                    f"grant {grant.get('alias')} does not have a correct budget end date."
                    f" grant end: {grant['end_date']} budget end: {budget_end}"
                )
            days_to_go = (grant['end_date'] - projection_from_date).days
            this_burn = grant['burn']
            end_amount = this_burn.get(grant['end_date'])['student_days'] + \
                         this_burn.get(grant['end_date'])['ss_days'] + \
                        this_burn.get(grant['end_date'])['postdoc_days']
            if end_amount > 15.25:
                underspent.append((grant['end_date'], grant.get("alias"),
                                   round(end_amount / 30.5,
                                         2), round(end_amount / days_to_go,
                                                   2)))
                cum_underspend += end_amount
            elif end_amount < -30.5:
                overspent.append(
                    "    end: {}, grant: {}, overspend amount: {} months".
                    format(str(grant['end_date']), grant.get("alias"),
                           round(end_amount / 30.5, 2)))
            # values for individual and cumulative grant burn plots
            if not rc.no_plot:
                grant_dates = [
                    grant['begin_date'] + relativedelta(days=x)
                    for x in range((grant['end_date'] -
                                    grant['begin_date']).days + 1)
                ]
                this_student, this_pd, this_ss = [0.0] * len(grant_dates), [0.0] * len(grant_dates), \
                                                 [0.0] * len(grant_dates)
                counter = 0
                for x in range(len(datearray)):
                    day_burn = this_burn.get(datearray[x])
                    if day_burn:
                        this_student[counter] = day_burn['student_days']
                        this_pd[counter] = day_burn['postdoc_days']
                        this_ss[counter] = day_burn['ss_days']
                        cum_student[x] += day_burn['student_days']
                        cum_pd[x] += day_burn['postdoc_days']
                        cum_ss[x] += day_burn['ss_days']
                        counter += 1
                if not rc.verbose:
                    if max(grant_dates) >= projection_from_date - timedelta(
                            days=730):
                        plots.append(
                            plotter(grant_dates,
                                    student=this_student,
                                    pd=this_pd,
                                    ss=this_ss,
                                    title=grant.get("alias"))[0])
                else:
                    plots.append(
                        plotter(grant_dates,
                                student=this_student,
                                pd=this_pd,
                                ss=this_ss,
                                title=grant.get("alias"))[0])

        if outdated:
            outdated.sort(key=lambda mess: mess[-10:])
            print("appointments on outdated grants:")
            for appt in outdated:
                print(appt)
        if depleted:
            depleted.sort(key=lambda mess: mess[-10:])
            print("appointments on depleted grants:")
            for appt in depleted:
                print(appt)
        if underspent:
            underspent.sort(key=lambda x: x[0])
            print("underspent grants:")
            for grant_info in underspent:
                print(
                    f"    {grant_info[1]}: end: {grant_info[0]}\n"
                    f"      projected underspend: {grant_info[2]} months, "
                    f"balance as of {recent_balance[0]}: {recent_balance[1]}\n"
                    f"      required ss+gra burn: {grant_info[3]}")
            print(
                f"cumulative underspend = {round(cum_underspend/30.5, 2)} months, cumulative months to support = {round(cum_months_to_cover, 2)}"
            )
        if overspent:
            print("overspent grants:")
            for grant in overspent:
                print(grant)

        if not rc.no_plot:
            for plot in plots:
                if not rc.no_gui:
                    plt.show()
            cum_plot, cum_ax, outp = plotter(datearray,
                                             student=cum_student,
                                             pd=cum_pd,
                                             ss=cum_ss,
                                             title="Cumulative burn")
            if not rc.no_gui:
                plt.show()

            print(outp)

        return
コード例 #4
0
    def sout(self):
        rc = self.rc
        outdated, depleted, underspent, overspent = [], [], [], []
        all_appts = collect_appts(self.gtx['people'])
        if rc.projection_from_date:
            projection_from_date = date_parser.parse(
                rc.projection_from_date).date()
        else:
            projection_from_date = date.today()

        grants_with_appts = []
        cum_months_to_cover = 0
        for person in self.gtx['people']:
            person_dates = group_member_employment_start_end(person, "bg")
            last_emp, months_to_cover = 0, 0
            if person_dates:
                last_emp = max(
                    [emp.get('end_date', 0) for emp in person_dates])
            if last_emp:
                months_to_cover = round(
                    (last_emp - projection_from_date).days / 30.5, 2)
            if months_to_cover > 0:
                print(
                    f"{person['_id']} needs to be covered for {months_to_cover} months"
                )
                cum_months_to_cover += months_to_cover
            appts = collect_appts([person])
            if not appts:
                continue
            this_begin = min(get_dates(appt)['begin_date'] for appt in appts)
            this_end = max(get_dates(appt)['end_date'] for appt in appts)
            is_fully_appointed(person, this_begin, this_end)
            for appt in appts:
                grants_with_appts.append(appt.get("grant"))
                if appt.get("grant") in BLACKLIST:
                    if rc.verbose:
                        print(
                            f"skipping {appt.get('grant')} since it is in the blacklist"
                        )
                    continue
                if appt.get("grant") not in grants_with_appts:
                    if rc.verbose:
                        print(
                            f"skipping {appt.get('grant')} since it has no appointments assigned to it"
                        )
                    continue
                grant = rc.client.find_one(rc.database, "grants",
                                           {"alias": appt.get("grant")})
                if not grant:
                    raise RuntimeError(
                        "    grant: {}, person: {}, appointment: {}, grant not found in grants database"
                        .format(appt.get("grant"), person.get("_id"),
                                appt.get("_id")))
                prop = rc.client.find_one(rc.database, "proposals",
                                          {"_id": grant.get("proposal_id")})
                if prop.get('year'):
                    del prop['year']
                grant_begin = get_dates(grant)['begin_date'] if grant.get('begin_date') or grant.get('begin_year') \
                    else get_dates(prop)['begin_date']
                grant_end = get_dates(grant)['end_date'] if grant.get('end_date') or grant.get('end_year') \
                    else get_dates(prop)['end_date']
                grant.update({
                    'begin_date': grant_begin,
                    'end_date': grant_end
                })
                appt_begin, appt_end = get_dates(
                    appt)['begin_date'], get_dates(appt)['end_date']
                this_burn = grant_burn(grant,
                                       all_appts,
                                       begin_date=appt_begin,
                                       end_date=appt_end)
                timespan = appt_end - appt_begin
                outdated_period, depleted_period = False, False
                for x in range(timespan.days + 1):
                    day = appt_begin + relativedelta(days=x)
                    if not outdated_period:
                        if not is_current(grant, now=day):
                            outdated.append(
                                "    person: {}, appointment: {}, grant: {},\n"
                                "            from {} until {}".format(
                                    person.get('_id'), appt.get('_id'),
                                    grant.get('_id'), str(day),
                                    str(
                                        min(appt_end,
                                            get_dates(grant)['begin_date']))))
                            outdated_period = True
                    if not depleted_period and not outdated_period:
                        day_burn = 0
                        if appt.get('type') == 'gra':
                            day_burn = this_burn[x].get('student_days')
                        elif appt.get('type') == 'pd':
                            day_burn = this_burn[x].get('postdoc_days')
                        elif appt.get('type') == 'ss':
                            day_burn = this_burn[x].get('ss_days')
                        if day_burn < -5:
                            depleted.append(
                                "    person: {}, appointment: {}, grant: {},\n"
                                "            from {} until {}".format(
                                    person.get('_id'), appt.get('_id'),
                                    grant.get('alias'), str(day),
                                    str(appt_end)))
                            depleted_period = True
            grants_with_appts = list(set(grants_with_appts))
        datearray, cum_student, cum_pd, cum_ss = [], None, None, None
        if not rc.no_plot:
            grants_begin, grants_end = None, None
            for grant in self.gtx['grants']:
                if grant.get('_id') in BLACKLIST or grant.get(
                        'alias') in BLACKLIST:
                    continue
                if grant.get('alias') not in grants_with_appts:
                    continue
                prop = rc.client.find_one(rc.database, "proposals",
                                          {"_id": grant.get("proposal_id")})
                if prop.get('year'):
                    del prop['year']
                grant_begin = get_dates(grant)['begin_date'] if grant.get('begin_date') or grant.get('begin_year') \
                    else get_dates(prop)['begin_date']
                grant_end = get_dates(grant)['end_date'] if grant.get('end_date') or grant.get('end_year') \
                    else get_dates(prop)['end_date']
                if not grants_begin or grant_begin < grants_begin:
                    grants_begin = grant_begin
                if not grants_end or grant_end > grants_end:
                    grants_end = grant_end
            grants_timespan = grants_end - grants_begin
            for x in range(grants_timespan.days + 1):
                datearray.append(grants_begin + relativedelta(days=x))
            cum_student, cum_pd, cum_ss = [0.0] * len(datearray), [
                0.0
            ] * len(datearray), [0.0] * len(datearray)
        plots = []
        cum_underspend = 0
        for grant in self.gtx["grants"]:
            if grant.get('_id') in BLACKLIST or grant.get(
                    'alias') in BLACKLIST:
                continue
            if grant.get('alias') not in grants_with_appts:
                continue
            prop = rc.client.find_one(rc.database, "proposals",
                                      {"_id": grant.get("proposal_id")})
            if prop.get('year'):
                del prop['year']
            grant_begin = get_dates(grant)['begin_date'] if grant.get('begin_date') or grant.get('begin_year') \
                else get_dates(prop)['begin_date']
            grant_end = get_dates(grant)['end_date'] if grant.get('end_date') or grant.get('end_year') \
                else get_dates(prop)['end_date']
            budget_begin = min(
                get_dates(period)['begin_date']
                for period in grant.get('budget'))
            budget_end = max(
                get_dates(period)['end_date']
                for period in grant.get('budget'))
            if grant_begin < budget_begin:
                raise RuntimeError(
                    f"grant does not have a complete budget. grant begin: {grant_begin} "
                    f"budget begin: {budget_begin}")
            elif grant_end > budget_end:
                raise RuntimeError(
                    f"grant {grant.get('alias')} does not have a complete budget. grant end: {grant_end} "
                    f"budget end: {budget_end}")
            days_to_go = (grant_end - projection_from_date).days
            grant.update({'begin_date': grant_begin, 'end_date': grant_end})
            grant_amounts = grant_burn(grant, all_appts)
            end_amount = grant_amounts[-1].get('student_days') + grant_amounts[-1].get('postdoc_days') + \
                        grant_amounts[-1].get('ss_days')
            if end_amount > 15.25:
                underspent.append(
                    "    end: {}, grant: {}, underspend amount: {} months,\n      required ss+gra burn: {}"
                    .format(str(grant_end), grant.get('alias'),
                            round(end_amount / 30.5, 2),
                            round(end_amount / days_to_go, 2)))
                cum_underspend += end_amount
            elif end_amount < -30.5:
                overspent.append(
                    "    end: {}, grant: {}, overspend amount: {} months".
                    format(str(grant_end), grant.get('alias'),
                           round(end_amount / 30.5, 2)))
            if not rc.no_plot:
                grant_dates = []
                grant_duration = (grant_end - grant_begin).days + 1
                this_student, this_pd, this_ss = [0.0] * grant_duration, [
                    0.0
                ] * grant_duration, [0.0] * grant_duration
                counter = 0
                for x in range(len(datearray)):
                    if is_current(grant, now=datearray[x]):
                        grant_dates.append(datearray[x])
                        this_student[counter] = grant_amounts[counter].get(
                            'student_days')
                        cum_student[x] += grant_amounts[counter].get(
                            'student_days')
                        this_pd[counter] = grant_amounts[counter].get(
                            'postdoc_days')
                        cum_pd[x] += grant_amounts[counter].get('postdoc_days')
                        this_ss[counter] = grant_amounts[counter].get(
                            'ss_days')
                        cum_ss[x] += grant_amounts[counter].get('ss_days')
                        counter += 1
                plots.append(
                    plotter(grant_dates,
                            student=this_student,
                            pd=this_pd,
                            ss=this_ss,
                            title=f"{grant.get('alias')}")[0])

        if outdated:
            print("appointments on outdated grants:")
            for appt in outdated:
                print(appt)
        if depleted:
            print("appointments on depleted grants:")
            for appt in depleted:
                print(appt)
        if underspent:
            print("underspent grants:")
            for grant in underspent:
                print(grant)
            print(
                f"cumulative underspend = {round(cum_underspend/30.5, 2)} months, cumulative months to support = {round(cum_months_to_cover, 2)}"
            )
        if overspent:
            print("overspent grants:")
            for grant in overspent:
                print(grant)

        if not rc.no_plot:
            for plot in plots:
                if not rc.no_gui:
                    plt.show()
            cum_plot, cum_ax, outp = plotter(datearray,
                                             student=cum_student,
                                             pd=cum_pd,
                                             ss=cum_ss,
                                             title="Cumulative burn")
            if not rc.no_gui:
                plt.show()

            print(outp)

        return