예제 #1
0
class PrintDashBoardMain:
    def __init__(self, service: annofabapi.Resource):
        self.service = service
        self.facade = AnnofabApiFacade(service)

    def get_task_phase_statistics(self, project_id: str) -> List[TaskPhaseStatistics]:
        """
        フェーズごとの累積作業時間をCSVに出力するための dict 配列を作成する。

        Args:
            project_id:

        Returns:
            フェーズごとの累積作業時間に対応するdict配列

        """
        task_phase_statistics = self.service.wrapper.get_task_phase_statistics(project_id)
        row_list: List[TaskPhaseStatistics] = []
        for stat_by_date in task_phase_statistics:
            elm = {"date": stat_by_date["date"]}
            for phase_stat in stat_by_date["phases"]:
                phase = phase_stat["phase"]
                worktime_hour = isoduration_to_hour(phase_stat["worktime"])
                elm[f"{phase}_worktime"] = worktime_hour
            row_list.append(TaskPhaseStatistics.from_dict(elm))
        return row_list

    def get_actual_worktime_dict(self, project_id: str, date: str) -> Dict[str, float]:
        """
        日毎のプロジェクト全体の実績作業時間を取得する。

        Args:
            project_id:
            date: 対象期間の終了日

        Returns:
            key:date, value:実績作業時間のdict

        """
        labor_list, _ = self.service.api.get_labor_control({"project_id": project_id, "to": date})
        actual_worktime_dict: Dict[str, float] = defaultdict(float)
        for labor in labor_list:
            date = labor["date"]
            actual_worktime = _get_actual_worktime_hour_from_labor(labor)
            actual_worktime_dict[date] += actual_worktime

        return actual_worktime_dict

    def get_plan_worktime_dict(self, project_id: str, today: str, days: Optional[int] = None) -> Dict[str, float]:
        """
        日毎のプロジェクト全体の予定作業時間を取得する。

        Args:
            project_id:
            date: 対象期間の終了日

        Returns:
            key:date, value: 予定作業時間のdict

        """
        dt_today = datetime.datetime.strptime(today, "%Y-%m-%d").date()
        dt_from_date = dt_today + datetime.timedelta(days=1)
        end_date = str(dt_today + datetime.timedelta(days=days)) if days is not None else None

        labor_list, _ = self.service.api.get_labor_control(
            {"project_id": project_id, "from": str(dt_from_date), "to": end_date}
        )
        plan_worktime_dict: Dict[str, float] = defaultdict(float)
        for labor in labor_list:
            date = labor["date"]
            plan_worktime = _get_plan_worktime_hour_from_labor(labor)
            plan_worktime_dict[date] += plan_worktime

        return plan_worktime_dict

    def create_result_values(self, project_id: str, date: str, task_list: List[Task]) -> ResultValues:
        """
        実績情報を生成する。

        Args:
            task_list:
            date: タスク数 集計対象日

        Returns:

        """
        for task in task_list:
            task["status_for_summary"] = TaskStatusForSummary.from_task(task).value
            task["worktime_hour"] = millisecond_to_hour(task["work_time_span"])

        actual_worktime_dict = self.get_actual_worktime_dict(project_id, date)
        task_phase_statistics = self.get_task_phase_statistics(project_id)

        cumulation_info = _get_cumulation_info(task_list, actual_worktime_dict)
        today_info = _get_today_info(
            today=date,
            task_list=task_list,
            actual_worktime_dict=actual_worktime_dict,
            task_phase_statistics=task_phase_statistics,
        )
        week_info = _get_week_info(
            today=date,
            task_list=task_list,
            actual_worktime_dict=actual_worktime_dict,
            task_phase_statistics=task_phase_statistics,
        )
        return ResultValues(cumulation=cumulation_info, today=today_info, week=week_info)

    def create_dashboard_data(self, project_id: str, date: str, task_list: List[Task]) -> DashboardData:
        project_title = self.facade.get_project_title(project_id)
        result = self.create_result_values(project_id=project_id, date=date, task_list=task_list)
        remaining_task_count = get_remaining_task_count_info_from_task_list(task_list)
        plan_worktime_dict = self.get_plan_worktime_dict(project_id, today=date)
        velocity_per_task = result.week.velocity_per_task

        task_exhaustation_date = (
            get_task_exhaustation_date(
                plan_worktime_dict=plan_worktime_dict,
                annotation_not_started=remaining_task_count.annotation_not_started,
                velocity_per_task=velocity_per_task,
            )
            if velocity_per_task is not None
            else None
        )

        dashboard_info = DashboardData(
            project_id=project_id,
            project_title=project_title,
            date=date,
            measurement_datetime=str_now(),
            remaining_task_count=remaining_task_count,
            result=result,
            plan=create_plan_values(plan_worktime_dict, velocity_per_task=velocity_per_task, today=date),
            task_exhaustation_date=task_exhaustation_date,
        )
        return dashboard_info
예제 #2
0
def main(args):
    service = annofabapi.build_from_netrc()
    facade = AnnofabApiFacade(service)
    RegisterAnnotation(service, facade).main(args)
예제 #3
0
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    PutProjectMembers(service, facade, args).main()
예제 #4
0
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    ListOutOfRangeAnnotationForMovie(service, facade, args).main()
class ListWorktimeByUserMain:
    def __init__(self, service: annofabapi.Resource):
        self.service = service
        self.facade = AnnofabApiFacade(service)

    DATE_FORMAT = "%Y-%m-%d"
    MONTH_FORMAT = "%Y-%m"

    _dict_account_statistics: Dict[str, List[Dict[str, Any]]] = {}
    """project_idごとの統計情報dict"""

    @allow_404_error
    def _get_account_statistics(self, project_id) -> Optional[List[Any]]:
        account_statistics = self.service.wrapper.get_account_statistics(project_id)
        return account_statistics

    def _get_worktime_monitored_hour_from_project_id(
        self, project_id: str, account_id: str, date: str
    ) -> Optional[float]:
        account_statistics = self._dict_account_statistics.get(project_id)
        if account_statistics is None:
            result = self._get_account_statistics(project_id)
            if result is not None:
                account_statistics = result
            else:
                logger.warning(f"project_id={project_id}: プロジェクトにアクセスできないため、アカウント統計情報を取得できませんでした。")
                account_statistics = []

            self._dict_account_statistics[project_id] = account_statistics

        return self._get_worktime_monitored_hour(account_statistics, account_id=account_id, date=date)

    @staticmethod
    def _get_worktime_monitored_hour(
        account_statistics: List[Dict[str, Any]], account_id: str, date: str
    ) -> Optional[float]:
        """
        AnnoFabの作業時間を取得する。
        """
        stat = first_true(account_statistics, pred=lambda e: e["account_id"] == account_id)
        if stat is None:
            return None
        histories = stat["histories"]
        hist = first_true(histories, pred=lambda e: e["date"] == date)
        if hist is None:
            return None
        return isoduration_to_hour(hist["worktime"])

    @staticmethod
    def create_required_columns(df, prior_columns):
        remained_columns = list(df.columns.difference(prior_columns))
        all_columns = prior_columns + remained_columns
        return all_columns

    @staticmethod
    def get_member_from_user_id(
        organization_member_list: List[OrganizationMember], user_id: str
    ) -> Optional[OrganizationMember]:
        member = more_itertools.first_true(organization_member_list, pred=lambda e: e["user_id"] == user_id)
        return member

    @staticmethod
    def get_project_title(project_list: List[Project], project_id: str) -> str:
        project = more_itertools.first_true(project_list, pred=lambda e: e["project_id"] == project_id)
        if project is not None:
            return project["title"]
        else:
            return ""

    @staticmethod
    def get_worktime_hour(working_time_by_user: Optional[Dict[str, Any]], key: str) -> float:
        if working_time_by_user is None:
            return 0

        value = working_time_by_user.get(key)
        if value is None:
            return 0
        else:
            return value / 3600 / 1000

    @staticmethod
    def _get_working_description(working_time_by_user: Optional[Dict[str, Any]]) -> Optional[str]:
        if working_time_by_user is None:
            return None

        return working_time_by_user.get("description")

    @staticmethod
    def _get_labor_worktime(
        labor: Dict[str, Any],
        member: Optional[ProjectMember],
        project_title: str,
        organization_name: str,
        worktime_monitored_hour: Optional[float],
    ) -> LaborWorktime:
        new_labor = LaborWorktime(
            date=labor["date"],
            organization_id=labor["organization_id"],
            organization_name=organization_name,
            project_id=labor["project_id"],
            project_title=project_title,
            account_id=labor["account_id"],
            user_id=member["user_id"] if member is not None else labor["account_id"],
            username=member["username"] if member is not None else labor["account_id"],
            biography=member["biography"] if member is not None else None,
            worktime_plan_hour=labor["plan_worktime"] if labor["plan_worktime"] is not None else 0,
            worktime_result_hour=labor["actual_worktime"] if labor["actual_worktime"] is not None else 0,
            working_description=labor["working_description"],
            worktime_monitored_hour=worktime_monitored_hour,
        )
        return new_labor

    def get_labor_availability_list_dict(
        self,
        user_list: List[User],
        start_date: str,
        end_date: str,
    ) -> Dict[str, List[LaborAvailability]]:
        """
        予定稼働時間を取得する
        Args:
            user_list:
            start_date:
            end_date:

        Returns:

        """
        labor_availability_dict = {}
        for user in user_list:
            # 予定稼働時間を取得するには、特殊な組織IDを渡す
            labor_list = self.service.wrapper.get_labor_control_availability(
                from_date=start_date,
                to_date=end_date,
                account_id=user.account_id,
            )
            new_labor_list = []
            for labor in labor_list:
                new_labor = LaborAvailability(
                    date=labor["date"],
                    account_id=labor["account_id"],
                    user_id=user.user_id,
                    username=user.username,
                    availability_hour=labor["availability"] if labor["availability"] is not None else 0,
                )
                new_labor_list.append(new_labor)
            labor_availability_dict[user.user_id] = new_labor_list
        return labor_availability_dict

    def get_labor_list_from_project_id(
        self,
        project_id: str,
        account_id_list: Optional[List[str]],
        start_date: Optional[str],
        end_date: Optional[str],
        add_monitored_worktime: bool = False,
    ) -> List[LaborWorktime]:
        organization, _ = self.service.api.get_organization_of_project(project_id)
        organization_name = organization["organization_name"]

        if account_id_list is None:
            logger.debug(f"project_id={project_id}の、すべての労務管理情報を取得しています。")
            labor_list = self.service.wrapper.get_labor_control_worktime(
                project_id=project_id,
                organization_id=organization["organization_id"],
                from_date=start_date,
                to_date=end_date,
            )
        else:
            labor_list = []
            for account_id in account_id_list:
                logger.debug(f"project_id={project_id}の、ユーザ{len(account_id_list)}件分の労務管理情報を取得しています。")
                tmp_labor_list = self.service.wrapper.get_labor_control_worktime(
                    project_id=project_id, from_date=start_date, to_date=end_date, account_id=account_id
                )
                labor_list.extend(tmp_labor_list)

        project_title = self.service.api.get_project(project_id)[0]["title"]
        labor_list = [
            e
            for e in labor_list
            if (e["actual_worktime"] is not None and e["actual_worktime"] > 0)
            or (e["plan_worktime"] is not None and e["plan_worktime"] > 0)
        ]
        logger.info(f"'{project_title}'プロジェクト('{project_id}')の労務管理情報の件数: {len(labor_list)}")

        new_labor_list = []
        for labor in labor_list:
            # 個人に紐付かないデータの場合
            if labor["account_id"] is None:
                continue

            member = self.facade.get_project_member_from_account_id(labor["project_id"], labor["account_id"])
            if add_monitored_worktime:
                try:
                    worktime_monitored_hour = self._get_worktime_monitored_hour_from_project_id(
                        project_id=project_id, account_id=labor["account_id"], date=labor["date"]
                    )
                except Exception:  # pylint: disable=broad-except
                    logger.warning(f"project_id={project_id}: 計測作業時間を取得できませんでした。", exc_info=True)
                    worktime_monitored_hour = None
            else:
                worktime_monitored_hour = None

            new_labor = self._get_labor_worktime(
                labor,
                member=member,
                project_title=project_title,
                organization_name=organization_name,
                worktime_monitored_hour=worktime_monitored_hour,
            )
            new_labor_list.append(new_labor)

        return new_labor_list

    def get_labor_list_from_organization_name(
        self,
        organization_name: str,
        account_id_list: Optional[List[str]],
        start_date: Optional[str],
        end_date: Optional[str],
        add_monitored_worktime: bool = False,
    ) -> List[LaborWorktime]:
        organization, _ = self.service.api.get_organization(organization_name)
        organization_id = organization["organization_id"]

        if account_id_list is None:
            logger.debug(f"organization_name={organization_name}の、すべての労務管理情報を取得しています。")
            labor_list = self.service.wrapper.get_labor_control_worktime(
                organization_id=organization_id, from_date=start_date, to_date=end_date
            )
        else:
            labor_list = []
            logger.debug(f"organization_name={organization_name}の、ユーザ{len(account_id_list)}件分の労務管理情報を取得しています。")
            for account_id in account_id_list:
                tmp_labor_list = self.service.wrapper.get_labor_control_worktime(
                    organization_id=organization_id, from_date=start_date, to_date=end_date, account_id=account_id
                )
                labor_list.extend(tmp_labor_list)

        labor_list = [
            e
            for e in labor_list
            if (e["actual_worktime"] is not None and e["actual_worktime"] > 0)
            or (e["plan_worktime"] is not None and e["plan_worktime"] > 0)
        ]

        logger.info(f"'{organization_name}'組織の労務管理情報の件数: {len(labor_list)}")
        project_list = self.service.wrapper.get_all_projects_of_organization(organization_name)

        new_labor_list = []
        for labor in labor_list:
            try:
                member = self.facade.get_project_member_from_account_id(labor["project_id"], labor["account_id"])
            except Exception:  # pylint: disable=broad-except
                logger.warning(f"project_id={labor['project_id']}: メンバ一覧を取得できませんでした。", exc_info=True)
                member = None

            project_title = self.get_project_title(project_list, labor["project_id"])
            if add_monitored_worktime:
                try:
                    worktime_monitored_hour = self._get_worktime_monitored_hour_from_project_id(
                        project_id=labor["project_id"], account_id=labor["account_id"], date=labor["date"]
                    )
                except Exception:  # pylint: disable=broad-except
                    logger.warning(f"project_id={labor['project_id']}: 計測作業時間を取得できませんでした。", exc_info=True)
                    worktime_monitored_hour = None
            else:
                worktime_monitored_hour = None

            new_labor = self._get_labor_worktime(
                labor,
                member=member,
                project_title=project_title,
                organization_name=organization_name,
                worktime_monitored_hour=worktime_monitored_hour,
            )
            new_labor_list.append(new_labor)

        return new_labor_list

    @staticmethod
    def get_sum_worktime_list(
        labor_list: List[LaborWorktime], user_id: str, start_date: str, end_date: str
    ) -> List[SumLaborWorktime]:
        sum_labor_list = []
        for date in pandas.date_range(start=start_date, end=end_date):
            str_date = date.strftime(ListWorktimeByUserMain.DATE_FORMAT)
            filtered_list = [e for e in labor_list if e.user_id == user_id and e.date == str_date]
            worktime_plan_hour = sum([e.worktime_plan_hour for e in filtered_list])
            worktime_result_hour = sum([e.worktime_result_hour for e in filtered_list])

            labor = SumLaborWorktime(
                user_id=user_id,
                date=date,
                worktime_plan_hour=worktime_plan_hour,
                worktime_result_hour=worktime_result_hour,
            )
            sum_labor_list.append(labor)

        return sum_labor_list

    @staticmethod
    @_catch_exception
    def write_sum_worktime_list(sum_worktime_df: pandas.DataFrame, output_dir: Path):
        sum_worktime_df.round(3).to_csv(str(output_dir / "ユーザごとの作業時間.csv"), encoding="utf_8_sig", index=False)

    @staticmethod
    @_catch_exception
    def write_sum_plan_worktime_list(sum_worktime_df: pandas.DataFrame, output_dir: Path) -> None:
        """
        出勤予定かどうかを判断するため、作業予定時間が"0"のときは"☓", そうでないときは"○"で出力する
        Args:
            sum_worktime_df:
            output_dir:

        """

        def create_mark(value) -> str:
            if value == 0:
                return "×"
            else:
                return "○"

        def is_plan_column(c) -> bool:
            c1, c2 = c
            if c1 in ["date", "dayofweek"]:
                return False
            return c2 == "作業予定"

        username_list = [e[0] for e in sum_worktime_df.columns if is_plan_column(e)]

        for username in username_list:
            # SettingWithCopyWarning を避けるため、暫定的に値をコピーする
            sum_worktime_df[(username, "作業予定_記号")] = sum_worktime_df[(username, "作業予定")].map(create_mark)

        output_columns = [("date", ""), ("dayofweek", "")] + [(e, "作業予定_記号") for e in username_list]
        sum_worktime_df[output_columns].to_csv(str(output_dir / "ユーザごとの作業予定_記号.csv"), encoding="utf_8_sig", index=False)

    @staticmethod
    @_catch_exception
    def write_worktime_list(worktime_df: pandas.DataFrame, output_dir: Path, add_monitored_worktime: bool = False):
        worktime_df = worktime_df.rename(
            columns={
                "worktime_plan_hour": "作業予定時間",
                "worktime_result_hour": "作業実績時間",
                "worktime_monitored_hour": "計測時間",
                "working_description": "備考",
            }
        )
        columns = [
            "date",
            "organization_name",
            "project_title",
            "project_id",
            "username",
            "biography",
            "user_id",
            "作業予定時間",
            "作業実績時間",
            "計測時間",
            "備考",
        ]
        if not add_monitored_worktime:
            columns.remove("計測時間")

        worktime_df[columns].round(3).to_csv(str(output_dir / "作業時間の詳細一覧.csv"), encoding="utf_8_sig", index=False)

    @staticmethod
    @_catch_exception
    def write_worktime_per_user_date(worktime_df_per_date_user: pandas.DataFrame, output_dir: Path):
        add_availabaility = "availability_hour" in worktime_df_per_date_user.columns
        target_renamed_columns = {"worktime_plan_hour": "作業予定時間", "worktime_result_hour": "作業実績時間"}
        if add_availabaility:
            target_renamed_columns.update({"availability_hour": "予定稼働時間"})

        df = worktime_df_per_date_user.rename(columns=target_renamed_columns)
        columns = [
            "date",
            "user_id",
            "username",
            "biography",
            "予定稼働時間",
            "作業予定時間",
            "作業実績時間",
        ]
        if not add_availabaility:
            columns.remove("予定稼働時間")

        df[columns].round(3).to_csv(str(output_dir / "日ごとの作業時間の一覧.csv"), encoding="utf_8_sig", index=False)

    @staticmethod
    @_catch_exception
    def write_worktime_per_user(worktime_df_per_user: pandas.DataFrame, output_dir: Path, add_availability: bool):
        target_renamed_columns = {
            "worktime_plan_hour": "作業予定時間",
            "worktime_result_hour": "作業実績時間",
            "result_working_days": "実績稼働日数",
        }
        if add_availability:
            target_renamed_columns.update({"availability_hour": "予定稼働時間"})
            target_renamed_columns.update({"availability_days": "予定稼働日数"})

        df = worktime_df_per_user.rename(columns=target_renamed_columns)
        columns = [
            "user_id",
            "username",
            "biography",
            "予定稼働時間",
            "作業予定時間",
            "作業実績時間",
            "予定稼働日数",
            "実績稼働日数",
        ]
        if not add_availability:
            columns.remove("予定稼働時間")
            columns.remove("予定稼働日数")

        df[columns].round(3).to_csv(str(output_dir / "summary.csv"), encoding="utf_8_sig", index=False)

    def get_organization_member_list(
        self, organization_name_list: Optional[List[str]], project_id_list: Optional[List[str]]
    ) -> List[OrganizationMember]:
        member_list: List[OrganizationMember] = []

        if project_id_list is not None:
            tmp_organization_name_list = []
            for project_id in project_id_list:
                organization, _ = self.service.api.get_organization_of_project(project_id)
                tmp_organization_name_list.append(organization["organization_name"])

            organization_name_list = list(set(tmp_organization_name_list))

        if organization_name_list is not None:
            for organization_name in organization_name_list:
                member_list.extend(self.service.wrapper.get_all_organization_members(organization_name))

        return member_list

    def _get_user_from_organization_name_list(self, organization_name_list: List[str], user_id: str) -> Optional[User]:
        for organization_name in organization_name_list:
            organization_member = self.service.wrapper.get_organization_member_or_none(organization_name, user_id)
            if organization_member is not None:
                return User(
                    user_id=organization_member["user_id"],
                    account_id=organization_member["account_id"],
                    username=organization_member["username"],
                    biography=organization_member["biography"],
                )
        return None

    def _get_user_from_project_id_list(self, project_id_list: List[str], user_id: str) -> Optional[User]:
        for project_id in project_id_list:
            project_member = self.service.wrapper.get_project_member_or_none(project_id, user_id)
            if project_member is not None:
                return User(
                    user_id=project_member["user_id"],
                    account_id=project_member["account_id"],
                    username=project_member["username"],
                    biography=project_member["biography"],
                )
        return None

    def get_user_list(
        self,
        labor_list: List[LaborWorktime],
        organization_name_list: Optional[List[str]] = None,
        project_id_list: Optional[List[str]] = None,
        user_id_list: Optional[List[str]] = None,
    ) -> List[User]:
        """
        summary.csvに出力するユーザ一覧を取得する。

        Args:
            labor_list:
            organization_name_list:
            project_id_list:
            user_id_list:

        Returns:
            ユーザ一覧

        """
        df = (
            pandas.DataFrame(labor_list, columns=["account_id", "user_id", "username", "biography"])
            .drop_duplicates()
            .set_index("user_id")
        )

        user_list: List[User] = []

        if user_id_list is None:
            for user_id, row in df.iterrows():
                user_list.append(
                    User(
                        user_id=str(user_id),
                        account_id=row["account_id"],
                        username=row["username"],
                        biography=row["biography"],
                    )
                )
            return user_list

        if organization_name_list is not None:
            for user_id in user_id_list:
                if user_id in df.index:
                    row = df.loc[user_id]
                    user_list.append(
                        User(
                            user_id=str(user_id),
                            account_id=row["account_id"],
                            username=row["username"],
                            biography=row["biography"],
                        )
                    )
                    continue

                user = self._get_user_from_organization_name_list(organization_name_list, user_id)
                if user is not None:
                    user_list.append(user)
                else:
                    logger.warning(f"user_id={user_id} のユーザは、組織メンバに存在しませんでした。")

        if project_id_list is not None:
            for user_id in user_id_list:
                if user_id in df.index:
                    row = df.loc[user_id]
                    user_list.append(
                        User(
                            user_id=str(user_id),
                            account_id=row["account_id"],
                            username=row["username"],
                            biography=row["biography"],
                        )
                    )
                    continue

                user = self._get_user_from_project_id_list(project_id_list, user_id)
                if user is not None:
                    user_list.append(user)
                else:
                    logger.warning(f"user_id={user_id} のユーザは、プロジェクトメンバに存在しませんでした。")

        return user_list

    @staticmethod
    def get_availability_list(
        labor_availability_list: List[LaborAvailability],
        start_date: str,
        end_date: str,
    ) -> List[Optional[float]]:
        def get_availability_hour(str_date: str) -> Optional[float]:
            labor = more_itertools.first_true(labor_availability_list, pred=lambda e: e.date == str_date)
            if labor is not None:
                return labor.availability_hour
            else:
                return None

        availability_list: List[Optional[float]] = []
        for date in pandas.date_range(start=start_date, end=end_date):
            str_date = date.strftime(ListWorktimeByUserMain.DATE_FORMAT)
            availability_list.append(get_availability_hour(str_date))

        return availability_list

    def get_account_id_list_from_project_id(self, user_id_list: List[str], project_id_list: List[str]) -> List[str]:
        """
        project_idのリストから、対象ユーザのaccount_id を取得する。

        Args:
            user_id_list:
            organization_name_list:

        Returns:
            account_idのリスト
        """
        account_id_list = []
        not_exists_user_id_list = []
        for user_id in user_id_list:
            member_exists = False
            for project_id in project_id_list:
                member = self.facade.get_project_member_from_user_id(project_id, user_id)
                if member is not None:
                    account_id_list.append(member["account_id"])
                    member_exists = True
                    break
            if not member_exists:
                not_exists_user_id_list.append(user_id)

        if len(not_exists_user_id_list) == 0:
            return account_id_list
        else:
            raise ValueError(f"以下のユーザは、指定されたプロジェクトのプロジェクトメンバではありませんでした。\n{not_exists_user_id_list}")

    def get_account_id_list_from_organization_name(
        self, user_id_list: List[str], organization_name_list: List[str]
    ) -> List[str]:
        """
        組織名のリストから、対象ユーザのaccount_id を取得する。

        Args:
            user_id_list:
            organization_name_list:

        Returns:
            account_idのリスト
        """

        def _get_account_id(fuser_id: str) -> Optional[str]:
            # 脱退した可能性のあるユーザの組織メンバ情報を取得する
            for organization_name in organization_name_list:
                member = self.service.wrapper.get_organization_member_or_none(organization_name, fuser_id)
                if member is not None:
                    return member["account_id"]
            return None

        # 組織メンバの一覧をする(ただし脱退したメンバは取得できない)
        all_organization_member_list = []
        for organization_name in organization_name_list:
            all_organization_member_list.extend(self.service.wrapper.get_all_organization_members(organization_name))

        user_id_dict = {e["user_id"]: e["account_id"] for e in all_organization_member_list}
        account_id_list = []
        not_exists_user_id_list = []
        for user_id in user_id_list:
            if user_id in user_id_dict:
                account_id_list.append(user_id_dict[user_id])
            else:
                account_id = _get_account_id(user_id)
                if account_id is not None:
                    account_id_list.append(account_id)
                else:
                    not_exists_user_id_list.append(user_id)

        if len(not_exists_user_id_list) == 0:
            return account_id_list
        else:
            raise ValueError(f"以下のユーザは、指定された組織の組織メンバではありませんでした。\n{not_exists_user_id_list}")

    def get_labor_list(
        self,
        organization_name_list: Optional[List[str]],
        project_id_list: Optional[List[str]],
        user_id_list: Optional[List[str]],
        start_date: Optional[str],
        end_date: Optional[str],
        add_monitored_worktime: bool = False,
    ) -> List[LaborWorktime]:

        labor_list: List[LaborWorktime] = []
        account_id_list: Optional[List[str]] = None

        logger.info(f"労務管理情報を取得します。")
        if project_id_list is not None:
            if user_id_list is not None:
                account_id_list = self.get_account_id_list_from_project_id(
                    user_id_list, project_id_list=project_id_list
                )

            for project_id in project_id_list:
                labor_list.extend(
                    self.get_labor_list_from_project_id(
                        project_id,
                        account_id_list=account_id_list,
                        start_date=start_date,
                        end_date=end_date,
                        add_monitored_worktime=add_monitored_worktime,
                    )
                )

        elif organization_name_list is not None:
            if user_id_list is not None:
                account_id_list = self.get_account_id_list_from_organization_name(
                    user_id_list, organization_name_list=organization_name_list
                )
            for organization_name in organization_name_list:
                labor_list.extend(
                    self.get_labor_list_from_organization_name(
                        organization_name,
                        account_id_list=account_id_list,
                        start_date=start_date,
                        end_date=end_date,
                        add_monitored_worktime=add_monitored_worktime,
                    )
                )

        else:
            raise RuntimeError(f"organization_name_list or project_id_list を指定してください。")

        # 集計対象ユーザで絞り込む
        if user_id_list is not None:
            return [e for e in labor_list if e.user_id in user_id_list]
        else:
            return labor_list

    @staticmethod
    def create_sum_worktime_df(
        labor_list: List[LaborWorktime],
        user_list: List[User],
        start_date: str,
        end_date: str,
        labor_availability_list_dict: Optional[Dict[str, List[LaborAvailability]]] = None,
    ):
        reform_dict = {
            ("date", ""): [
                e.strftime(ListWorktimeByUserMain.DATE_FORMAT)
                for e in pandas.date_range(start=start_date, end=end_date)
            ],
            ("dayofweek", ""): [e.strftime("%a") for e in pandas.date_range(start=start_date, end=end_date)],
        }

        username_list = []
        for user in user_list:
            sum_worktime_list = ListWorktimeByUserMain.get_sum_worktime_list(
                labor_list, user_id=user.user_id, start_date=start_date, end_date=end_date
            )

            username_list.append(user.username)
            reform_dict.update(
                {
                    (user.username, "作業予定"): [e.worktime_plan_hour for e in sum_worktime_list],
                    (user.username, "作業実績"): [e.worktime_result_hour for e in sum_worktime_list],
                }
            )

            if labor_availability_list_dict is not None:
                labor_availability_list = labor_availability_list_dict.get(user.user_id, [])
                reform_dict.update(
                    {
                        (user.username, "予定稼働"): ListWorktimeByUserMain.get_availability_list(
                            labor_availability_list, start_date, end_date
                        )
                    }
                )

        key_list = ["作業予定", "作業実績", "予定稼働"] if labor_availability_list_dict else ["作業予定", "作業実績"]
        for key in key_list:
            data = numpy.array([reform_dict[(username, key)] for username in username_list], dtype=float)
            data = numpy.nan_to_num(data)
            reform_dict[("合計", key)] = list(numpy.sum(data, axis=0))  # type: ignore

        columns = (
            [("date", ""), ("dayofweek", "")]
            + [("合計", key) for key in key_list]
            + [(username, key) for username in username_list for key in key_list]
        )

        sum_worktime_df = pandas.DataFrame(reform_dict, columns=columns)
        return sum_worktime_df

    @staticmethod
    def create_worktime_df_per_date_user(
        worktime_df: pandas.DataFrame,
        user_df: pandas.DataFrame,
        labor_availability_list_dict: Optional[Dict[str, List[LaborAvailability]]] = None,
    ) -> pandas.DataFrame:
        value_df = (
            worktime_df.pivot_table(
                values=["worktime_plan_hour", "worktime_result_hour"], index=["date", "user_id"], aggfunc=numpy.sum
            )
            .fillna(0)
            .reset_index()
        )
        if len(value_df) == 0:
            value_df = pandas.DataFrame(columns=["date", "user_id", "worktime_plan_hour", "worktime_result_hour"])

        if labor_availability_list_dict is not None:
            all_availability_list = []
            for availability_list in labor_availability_list_dict.values():
                all_availability_list.extend(availability_list)

            if len(all_availability_list) > 0:
                availability_df = pandas.DataFrame([e.to_dict() for e in all_availability_list])
            else:
                availability_df = pandas.DataFrame(columns=["date", "user_id", "availability_hour"])

            value_df = (
                value_df.merge(
                    availability_df[["date", "user_id", "availability_hour"]], how="outer", on=["date", "user_id"]
                )
                .fillna(0)
                .reset_index()
            )

        if len(value_df) > 0:
            return user_df.reset_index().merge(value_df, how="left", on=["user_id"]).reset_index()
        else:
            return pandas.DataFrame()

    @staticmethod
    def set_day_count_to_dataframe(
        worktime_df_per_date_user: pandas.DataFrame, value_df: pandas.DataFrame, worktime_column: str, days_column: str
    ):
        df_filter = worktime_df_per_date_user[worktime_df_per_date_user[worktime_column] > 0]
        if len(df_filter) > 0:
            value_df[days_column] = (
                df_filter.pivot_table(index=["user_id"], values="worktime_result_hour", aggfunc="count")
                .fillna(0)
                .astype(pandas.Int64Dtype())
            )
        else:
            value_df[days_column] = 0

    @staticmethod
    def get_value_columns(columns: pandas.Series) -> pandas.Series:
        """
        ユーザ情報以外のcolumnsを取得する
        """
        return columns.drop(["user_id", "username", "biography"])

    @staticmethod
    def create_worktime_df_per_user(
        worktime_df_per_date_user: pandas.DataFrame, user_df: pandas.DataFrame, add_availability: bool = False
    ) -> pandas.DataFrame:
        if len(worktime_df_per_date_user) > 0:
            value_df = worktime_df_per_date_user.pivot_table(index=["user_id"], aggfunc=numpy.sum).fillna(0)
            ListWorktimeByUserMain.set_day_count_to_dataframe(
                worktime_df_per_date_user,
                value_df,
                worktime_column="worktime_result_hour",
                days_column="result_working_days",
            )
            ListWorktimeByUserMain.set_day_count_to_dataframe(
                worktime_df_per_date_user,
                value_df,
                worktime_column="worktime_plan_hour",
                days_column="plan_working_days",
            )

            if add_availability:
                ListWorktimeByUserMain.set_day_count_to_dataframe(
                    worktime_df_per_date_user,
                    value_df,
                    worktime_column="availability_hour",
                    days_column="availability_days",
                )
            value_df.fillna(0, inplace=True)

        else:
            columns = [
                "availability_hour",
                "worktime_plan_hour",
                "worktime_result_hour",
                "availability_days",
                "plan_working_days",
                "result_working_days",
            ]
            if not add_availability:
                columns.remove("availability_hour")
                columns.remove("availability_days")
            value_df = pandas.DataFrame(columns=columns)

        user_df.set_index("user_id", inplace=True)

        df = user_df.join(value_df, how="left").reset_index()
        value_columns = ListWorktimeByUserMain.get_value_columns(df.columns)
        df[value_columns] = df[value_columns].fillna(0)
        return df

    def write_labor_list(
        self,
        labor_list: List[LaborWorktime],
        user_list: List[User],
        start_date: str,
        end_date: str,
        output_dir: Path,
        labor_availability_list_dict: Optional[Dict[str, List[LaborAvailability]]] = None,
        add_monitored_worktime: bool = False,
    ):
        if len(labor_list) > 0:
            worktime_df = pandas.DataFrame([e.to_dict() for e in labor_list])
            self.write_worktime_list(worktime_df, output_dir, add_monitored_worktime)
        else:
            worktime_df = pandas.DataFrame(columns=["date", "user_id", "worktime_plan_hour", "worktime_result_hour"])
            logger.info("予定作業時間または実績作業時間が入力されているデータは見つからなかったので、'作業時間の詳細一覧.csv' は出力しません。")

        # 日ごとの作業時間の一覧.csv を出力
        user_df = pandas.DataFrame(user_list)
        worktime_df_per_date_user = self.create_worktime_df_per_date_user(
            worktime_df=worktime_df, user_df=user_df, labor_availability_list_dict=labor_availability_list_dict
        )
        if len(worktime_df_per_date_user) > 0:
            self.write_worktime_per_user_date(worktime_df_per_date_user, output_dir)
        else:
            logger.info("日ごとの作業時間情報に関するデータが見つからなかったので、 '日ごとの作業時間の一覧.csv' は出力しません。")

        add_availability = labor_availability_list_dict is not None
        worktime_df_per_user = self.create_worktime_df_per_user(
            worktime_df_per_date_user=worktime_df_per_date_user, user_df=user_df, add_availability=add_availability
        )
        self.write_worktime_per_user(worktime_df_per_user, output_dir, add_availability=add_availability)

        # 行方向に日付、列方向にユーザを表示したDataFrame
        sum_worktime_df = self.create_sum_worktime_df(
            labor_list=labor_list,
            user_list=user_list,
            start_date=start_date,
            end_date=end_date,
            labor_availability_list_dict=labor_availability_list_dict,
        )
        self.write_sum_worktime_list(sum_worktime_df, output_dir)
        self.write_sum_plan_worktime_list(sum_worktime_df, output_dir)

    def main(
        self,
        organization_name_list: Optional[List[str]],
        project_id_list: Optional[List[str]],
        user_id_list: Optional[List[str]],
        start_date: Optional[str],
        end_date: Optional[str],
        output_dir: Path,
        add_availability: bool = False,
        add_monitored_worktime: bool = False,
    ) -> None:
        """
        作業時間の一覧を出力する
        """
        labor_list = self.get_labor_list(
            organization_name_list=organization_name_list,
            project_id_list=project_id_list,
            user_id_list=user_id_list,
            start_date=start_date,
            end_date=end_date,
            add_monitored_worktime=add_monitored_worktime,
        )

        if len(labor_list) == 0:
            logger.info(f"予定/実績に関する労務管理情報が0件です。")
            if start_date is None or end_date is None or user_id_list is None:
                logger.info(f"後続の処理を続けることができないので終了します。")
                return

        if start_date is None or end_date is None:
            sorted_date_list = sorted({e.date for e in labor_list if e.worktime_result_hour > 0})
            if start_date is None:
                start_date = sorted_date_list[0]
            if end_date is None:
                end_date = sorted_date_list[-1]

        logger.info(f"集計期間: start_date={start_date}, end_date={end_date}")
        user_list = self.get_user_list(
            labor_list,
            organization_name_list=organization_name_list,
            project_id_list=project_id_list,
            user_id_list=user_id_list,
        )
        logger.info(f"集計対象ユーザの数: {len(user_list)}")

        labor_availability_list_dict: Optional[Dict[str, List[LaborAvailability]]] = None
        if add_availability:
            labor_availability_list_dict = self.get_labor_availability_list_dict(
                user_list=user_list,
                start_date=start_date,
                end_date=end_date,
            )

        self.write_labor_list(
            labor_list=labor_list,
            user_list=user_list,
            start_date=start_date,
            end_date=end_date,
            output_dir=output_dir,
            labor_availability_list_dict=labor_availability_list_dict,
            add_monitored_worktime=add_monitored_worktime,
        )

    def get_user_id_list_from_project_id_list(self, project_id_list: List[str]) -> List[str]:
        """
        プロジェクトメンバ一覧からuser_id_listを取得する。
        Args:
            project_id_list:

        Returns:
            user_id_list

        """
        member_list: List[Dict[str, Any]] = []
        for project_id in project_id_list:
            member_list.extend(self.service.wrapper.get_all_project_members(project_id))
        user_id_list = [e["user_id"] for e in member_list]
        return list(set(user_id_list))

    @staticmethod
    def get_first_and_last_date(str_month: str) -> Tuple[str, str]:
        """
        年月("YYYY-MM")から、月初と月末の日付を返す。

        Args:
            str_month: 年月("YYYY-MM")

        Returns:
            月初と月末の日付が格納されたタプル

        """
        dt_first_date = datetime.datetime.strptime(str_month, ListWorktimeByUserMain.MONTH_FORMAT)
        _, days = calendar.monthrange(dt_first_date.year, dt_first_date.month)
        dt_last_date = dt_first_date + datetime.timedelta(days=(days - 1))
        return (
            dt_first_date.strftime(ListWorktimeByUserMain.DATE_FORMAT),
            dt_last_date.strftime(ListWorktimeByUserMain.DATE_FORMAT),
        )

    @staticmethod
    def get_start_and_end_date_from_month(start_month: str, end_month: str) -> Tuple[str, str]:
        """
        開始月、終了月から、開始日付、終了日付を取得する。

        Args:
            start_month:
            end_month:

        Returns:


        """
        first_date, _ = ListWorktimeByUserMain.get_first_and_last_date(start_month)
        _, end_date = ListWorktimeByUserMain.get_first_and_last_date(end_month)
        return first_date, end_date
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    SummarizeTaskCountByUser(service, facade, args).main()
예제 #7
0
def main(args):
    service = annofabapi.build_from_netrc()
    facade = AnnofabApiFacade(service)
    WriteAnnotationImage(service, facade).main(args)
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    ListSubmittedTaskCountArgs(service, facade, args).main()
예제 #9
0
def main(args):
    service = annofabapi.build_from_netrc()
    facade = AnnofabApiFacade(service)
    RejectTasks(service, facade).main(args)
예제 #10
0
def main(args):
    service = annofabapi.build_from_netrc()
    facade = AnnofabApiFacade(service)
    CancelAcceptance(service, facade).main(args)
class ListSubmittedTaskCountMain:
    def __init__(self, service: annofabapi.Resource):
        self.service = service
        self.facade = AnnofabApiFacade(service)

    @staticmethod
    def from_datetime_to_date(datetime: str) -> str:
        return datetime[0:10]

    @staticmethod
    def to_formatted_dataframe(
        submitted_task_count_df: pandas.DataFrame,
        account_statistics_df: pandas.DataFrame,
        user_df: pandas.DataFrame,
    ):
        df = (submitted_task_count_df.merge(account_statistics_df,
                                            how="outer",
                                            on=["date",
                                                "account_id"]).fillna(0).merge(
                                                    user_df,
                                                    how="inner",
                                                    on="account_id"))
        df.sort_values(["date", "user_id"], inplace=True)
        columns = [
            "date",
            "user_id",
            "username",
            "biography",
            "worktime_hour",
            "annotation_submitted_task_count",
            "inspection_submitted_task_count",
            "acceptance_submitted_task_count",
            "rejected_task_count",
        ]

        return df[columns]

    def get_task_history_dict(
        self, project_id: str, task_history_json_path: Optional[Path]
    ) -> Dict[str, List[TaskHistory]]:
        if task_history_json_path is not None:
            json_path = task_history_json_path
        else:
            cache_dir = annofabcli.utils.get_cache_dir()
            json_path = cache_dir / f"task-history-{project_id}.json"
            downloading_obj = DownloadingFile(self.service)
            downloading_obj.download_task_history_json(
                project_id, dest_path=str(json_path))

        logger.debug(f"タスク履歴全件ファイルを読み込み中。{json_path}")
        with json_path.open(encoding="utf-8") as f:
            task_history_dict = json.load(f)
            return task_history_dict

    def create_user_df(self, project_id: str):
        member_list = self.facade.get_organization_members_from_project_id(
            project_id)
        return pandas.DataFrame(
            member_list,
            columns=["account_id", "user_id", "username", "biography"])

    @staticmethod
    def _is_contained_daterange(target_date: str,
                                start_date: Optional[str] = None,
                                end_date: Optional[str] = None):
        if start_date is not None:
            if target_date < start_date:
                return False
        if end_date is not None:
            if target_date > end_date:
                return False
        return True

    def create_account_statistics_df(
            self,
            project_id: str,
            start_date: Optional[str] = None,
            end_date: Optional[str] = None) -> pandas.DataFrame:
        account_statistics = self.service.wrapper.get_account_statistics(
            project_id)
        data_list: List[Dict[str, Any]] = []
        for stat_by_user in account_statistics:
            account_id = stat_by_user["account_id"]
            histories = stat_by_user["histories"]
            for stat in histories:
                data = {
                    "account_id": account_id,
                    "worktime_hour": isoduration_to_hour(stat["worktime"]),
                    "rejected_task_count": stat["tasks_rejected"],
                    "date": stat["date"],
                }
                if not self._is_contained_daterange(data["date"],
                                                    start_date=start_date,
                                                    end_date=end_date):
                    continue

                if data["worktime_hour"] == 0 and data[
                        "rejected_task_count"] == 0:
                    continue

                data_list.append(data)

        if len(data_list) > 0:
            return pandas.DataFrame(data_list)
        else:
            return pandas.DataFrame(columns=[
                "date", "account_id", "monitored_worktime_hour",
                "rejected_task_count"
            ])

    def create_submitted_task_count_df(
        self,
        task_history_dict: Dict[str, List[TaskHistory]],
        start_date: Optional[str] = None,
        end_date: Optional[str] = None,
    ) -> pandas.DataFrame:
        def _set_zero_if_not_exists(df: pandas.DataFrame):
            for phase in TaskPhase:
                col = f"{phase.value}_submitted_task_count"
                if col not in df.columns:
                    df[col] = 0

        task_history_count_dict: Dict[Tuple[str, str, str],
                                      int] = defaultdict(int)
        for _, task_history_list in task_history_dict.items():
            for task_history in task_history_list:
                if task_history["ended_datetime"] is not None and task_history[
                        "account_id"] is not None:
                    ended_date = self.from_datetime_to_date(
                        task_history["ended_datetime"])
                    task_history_count_dict[(task_history["account_id"],
                                             task_history["phase"],
                                             ended_date)] += 1

        data_list = []
        for key, task_count in task_history_count_dict.items():
            account_id, phaes, date = key
            data: Dict[str, Any] = {
                "date": date,
                "phase": phaes,
                "account_id": account_id,
                "task_count": task_count
            }
            if not self._is_contained_daterange(
                    data["date"], start_date=start_date, end_date=end_date):
                continue

            data_list.append(data)

        if len(data_list) == 0:
            return pandas.DataFrame(columns=[
                "date",
                "account_id",
                "annotation_submitted_task_count",
                "inspection_submitted_task_count",
                "acceptance_submitted_task_count",
            ])

        df = pandas.DataFrame(data_list)
        df2 = df.pivot_table(columns="phase",
                             values="task_count",
                             index=["date", "account_id"]).fillna(0)
        df2.rename(
            columns={
                "annotation": "annotation_submitted_task_count",
                "inspection": "inspection_submitted_task_count",
                "acceptance": "acceptance_submitted_task_count",
            },
            inplace=True,
        )
        _set_zero_if_not_exists(df2)
        return df2

    def create_user_statistics_by_date(
        self,
        project_id: str,
        task_history_json_path: Optional[Path],
        start_date: Optional[str] = None,
        end_date: Optional[str] = None,
    ) -> pandas.DataFrame:
        task_history_dict = self.get_task_history_dict(project_id,
                                                       task_history_json_path)

        submitted_task_count_df = self.create_submitted_task_count_df(
            task_history_dict=task_history_dict,
            start_date=start_date,
            end_date=end_date)
        account_statistics_df = self.create_account_statistics_df(
            project_id, start_date=start_date, end_date=end_date)
        user_df = self.create_user_df(project_id)

        df2 = self.to_formatted_dataframe(submitted_task_count_df,
                                          account_statistics_df, user_df)
        return df2
예제 #12
0
def main(args):
    service = annofabapi.build_from_netrc()
    facade = AnnofabApiFacade(service)
    InviteUser(service, facade).main(args)
예제 #13
0
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    FindBreakError(service, facade, args).main()
예제 #14
0
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    UpdateMetadata(service, facade, args).main()
예제 #15
0
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    DashBoard(service, facade, args).main()
예제 #16
0
def main(args):
    service = annofabapi.build_from_netrc()
    facade = AnnofabApiFacade(service)
    PrintLabelColor(service, facade).main(args)
예제 #17
0
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    TaskProgress(service, facade, args).main()
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    PrintInspections(service, facade, args).main()
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    ListOrganizationMember(service, facade, args).main()
예제 #20
0
def main(args):
    service = annofabapi.build_from_netrc()
    facade = AnnofabApiFacade(service)
    PrintUnprocessedInspections(service, facade).main(args)
예제 #21
0
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    ListInputDataWithJson(service, facade, args).main()
예제 #22
0
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    LaborTimePerUser(service, facade, args).main()
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    ListWorktimeByUser(service, facade, args).main()
예제 #24
0
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    ChangeAttributesOfAnnotation(service, facade, args).main()
 def __init__(self, service: annofabapi.Resource):
     self.service = service
     self.facade = AnnofabApiFacade(service)
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    ListInspectionCommentWithJson(service, facade, args).main()
예제 #27
0
def main(args):
    service = build_annofabapi_resource_and_login(args)
    facade = AnnofabApiFacade(service)
    DumpAnnotation(service, facade, args).main()
예제 #28
0
def main(args):
    service = annofabapi.build_from_netrc()
    facade = AnnofabApiFacade(service)
    DiffProjecs(service, facade).main(args)