Esempio n. 1
0
    def __init__(self, start_date, end_date, config):
        self.special_projects = ["Vacations", "Sick", "Courses"]
        self.config = config

        self.api_key = config.api["api_key"]
        self.ezve_rounding = config.settings["ezve_rounding"]
        self.projects = config.projects
        self.holidays = parse_holidays(config.holidays)
        self.api = Api(self.api_key)
        self.ws = self.api.workspaces
        self.start = start_date
        self.end = end_date
        self.worktimings = config.settings["worktimings"]
        self.weekends = config.settings["weekends"]
        self.productivity_mappings = config.productivity_mappings

        self.days = StrictDict(StrictList)

        self.bh = WorkingHours(self.start,
                               self.end,
                               weekends=self.weekends,
                               worktimings=self.worktimings,
                               holidays=self.holidays)
        # Workspace.working_hours = self.bh
        log.info(mk_headline(sgn="="))
        log.info(mk_headline("Resource Planner", "#"))
        log.info(mk_headline(sgn="="))
        log.info("")
        log.info(
            " Expected working hours in time span %s to %s: %s (%s days)" %
            (dt.strftime(
                self.start, "%d.%m.%Y"), dt.strftime(
                    self.end, "%d.%m.%Y"), self.bh.get_actual_working_hours(),
             self.bh.get_number_of_actual_workdays()))
        log.info(mk_headline(sgn="="))
Esempio n. 2
0
    def output_results(self):
        log.info(mk_headline(f"Resulting resource distribution", "="))
        log.info(mk_headline(sgn="-"))

        project_seconds, total_seconds = self.calc_project_and_total_seconds()

        all_hours = self.bh.get_actual_working_hours()
        log.info(f"Total hours in month {self.start.month}: {all_hours}")
        print(mk_headline(sgn="-"))
        perc_sum = 0.0
        for p in project_seconds:
            perc = round(project_seconds[p] / total_seconds * 100.)
            perc = round(perc / 5) * 5
            perc_sum += perc
            if perc > 0.0:
                print(f"==> Project {p}: {perc}%")
        print(mk_headline(sgn="-"))
        print(f"Sum: {perc_sum}%")
        print(mk_headline(sgn="="))
Esempio n. 3
0
    def calculate_percents(self):
        for ws in self.ws:
            ws_obj = Workspace(ws)

            log.info(mk_headline(f"Times in Workspace {ws}", "*"))
            # seconds = {}
            self.total_time = tdelta(0)

            self.project_seconds = {
                "Vacations": 0.0,
                "Courses": 0.0,
                "Sick": 0.0
            }

            for project in ws_obj.native_projects:
                p_name = project.name
                times = project.time_entries.list()

                for i in times:
                    if not hasattr(i, "stop"):
                        log.warn("Entry %s seems to still be running" %
                                 i.description)
                        continue
                    start = i.start.replace(tzinfo=pytz.timezone("UTC"))
                    end = i.stop.replace(tzinfo=pytz.timezone("UTC"))

                    if start.date() < self.start or end.date() > self.end:
                        continue

                    dur = end - start

                    if dur.total_seconds() / 3600. > 11:
                        log.warn("Warning: the entry seems to be too long:")
                        log.warn(
                            f"{p_name} from {start} to {end}; duration {dur}")

                    if start.date() not in self.days:
                        self.days[start.date()] = StrictList(Entry)

                    if p_name not in self.project_seconds:
                        self.project_seconds[p_name] = 0.0

                    e = None

                    tags = list(set([t.lower() for t in i.tags])) if hasattr(
                        i, "tags") else []

                    e = Entry(p_name, start, end, dur, tags)
                    add_dur = not (p_name == "Holidays" or
                                   (hasattr(i, "tags") and "Pause" in i.tags))
                    if add_dur:
                        self.project_seconds[p_name] += dur.total_seconds()

                    self.days[start.date()].append(e)
Esempio n. 4
0
    def output_ezve(self, outfile):
        days = sorted(self.days)
        lines = []
        for d in days:
            log.info(mk_headline(str(d)))
            day = self.days[d]

            project_seconds = DefaultDict(0.0)
            day_seconds = 0.0

            for e in day:
                if "pause" in e.tags:
                    continue

                project = self.projects.get_by_name(e.name)
                project_seconds[project.code] += e.duration.seconds
                day_seconds += e.duration.seconds

            prod_mapping_names = [m.code for m in self.productivity_mappings]
            mapped_seconds = DefaultDict(0.0)
            for s in project_seconds:
                prod_proj = self.projects.get_by_code(s)
                ps = project_seconds[s]
                if s in prod_mapping_names:
                    mappings = self.productivity_mappings[s].mappings
                    for m in mappings:
                        prod_proj = self.projects.get_by_code(
                            m.productive_project)
                        mapped_seconds[(prod_proj.code,
                                        prod_proj.ccenter)] += ps * m.fraction
                else:
                    mapped_seconds[(prod_proj.code, prod_proj.ccenter)] += ps

            day_sum = 0
            codes = list(mapped_seconds.keys())
            percents = [0] * len(codes)
            percents = dict(zip(codes, percents))

            def ezve_round(v):
                return int(self.ezve_rounding *
                           round(float(v * 100.) / self.ezve_rounding)) / 100.

            for ms in mapped_seconds:
                ps = mapped_seconds[ms]
                percents[ms] = ps / day_seconds
                percents[ms] = ezve_round(percents[ms])

            remains = DefaultDict(0.0)
            for pc in percents:
                c, cc = pc
                prj = self.projects.get_by_code(c)
                remains[pc] = min(1.0, prj.max) - percents[pc]

            underfull_projects = dict([(i, remains[i]) for i in remains
                                       if remains[i] > 0.0])
            overfull_projects = dict([(i, remains[i]) for i in remains
                                      if remains[i] < 0.0])
            for ofp in overfull_projects:
                c, cc = ofp
                prj = self.projects.get_by_code(c)
                percents[ofp] = prj.max
                if not any(underfull_projects):
                    log.warning(
                        f"There are no unfilled projects on this day that could take the remaining {remains[ofp]*-100}% of {prj.name}"
                    )
                    log.warning(
                        f"Please make sure that at least one other entry is given in Toggl that can take the overflow"
                    )
                    continue

                while remains[ofp] > 0:
                    r = remains[ofp]
                    subt = r / len(underfull_projects)
                    subt = ezve_round(subt)
                    for ufp in underfull_projects:
                        if subt > remains[ufp]:
                            subt = remains[ufp]

                        r -= subt
                        remains[ufp] -= subt
                        percents[ufp] += subt

            for c, cc in percents:
                prj = self.projects.get_by_code(c)
                if prj.ezve_ignore:
                    continue

                p = percents[(c, cc)]
                if p * 100 <= 1e-2:
                    log.info(f"{p:>8.3f}% {c} {cc} skipped")
                    continue

                day_sum += int(p * 100)
                # log.info(f"  {c:15s} -> CC: {str(cc):15s} {p*100:>8.0f}%")
                if outfile is not None:
                    lines.append(
                        f"{d.year}\t{d.month}\t{d.day}\t{cc:05d}\t{p*100}")
                else:
                    if not any(lines):
                        lines.append("Date\t\tProject\t\tPercent")
                        lines.append(mk_headline())

                    lines.append(
                        f"{dt.strftime(d, '%d.%m.%Y')}\t{c:20}\t{cc:8}\t{p*100:5.0f}%"
                    )

            if day_sum < 100:
                log.warning(f"This day is only filled to {day_sum}%!")
            elif day_sum > 100:
                log.error(f"This day is overfilled to {day_sum}%!")

            if outfile is None:
                lines.append(mk_headline())

        if outfile is not None:
            with open(outfile, "w") as f:
                for l in lines:
                    f.write(l + "\n")
        else:
            for l in lines:
                log.info(l.strip())
Esempio n. 5
0
    def output_results_old(self):
        log.info(mk_headline(f"Resulting Resource Distribution", "="))
        log.info(mk_headline(sgn="-"))
        perc_sum = 0.0

        percents = {}
        hours = {}

        for p_name in self.project_seconds:
            phours = self.project_seconds[p_name] / 3600.
            percent = phours / self.bh.get_actual_working_hours(
                self.start.month) * 100.
            perc_sum += percent

            if p_name not in self.special_projects:
                self.total_time += tdelta(hours=phours)

            percents[p_name] = percent
            hours[p_name] = phours

        bigger_5p = [
            p for p in percents
            if percents[p] >= 5.0 and p not in self.special_projects
        ]
        smallr_5p = [
            p for p in percents
            if percents[p] < 5.0 and p not in self.special_projects
        ]

        sum_bigger = sum([percents[p] for p in bigger_5p])

        for p_name in smallr_5p:
            smallr_perc = percents[p_name]
            smallr_hours = hours[p_name]
            log.info(
                f"Rebooking {smallr_perc:>8.1f}% ({smallr_hours:>8.1f} hours) of project {p_name} to the bigger projects"
            )
            for bigger_p_name in bigger_5p:
                factor = 1. - (percents[bigger_p_name] / sum_bigger)
                hours_plus = smallr_hours * factor
                percs_plus = smallr_perc * factor
                hours[bigger_p_name] += hours_plus
                percents[bigger_p_name] += percs_plus
                log.info(
                    f"  {factor*100.:>8.1f}% of {smallr_hours:>8.1f} hours of {p_name} go to {bigger_p_name}"
                )

        for p_name in bigger_5p:
            log.info(
                f"    {p_name:<20s}: {percents[p_name]:>3.0f}% (hours: {hours[p_name]:>10.1f})"
            )

        log.info(mk_headline("Total hours in the given period", "-"))
        total_hours = self.total_time.total_seconds() / 3600.
        log.info(
            f" {total_hours:>3.1f} hours => {total_hours/self.bh.get_actual_working_hours() * 100.:>3.0f}%"
        )
        log.info(mk_headline("Smaller projects", sgn="-"))

        for p_name in smallr_5p:
            log.info(
                f"    {p_name:<20s}: {percents[p_name]:>3.0f}% (hours: {hours[p_name]:>10.1f})"
            )

        log.info(mk_headline(sgn="="))