annofabcli 1.111.1__py3-none-any.whl → 1.112.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- annofabcli/__main__.py +1 -2
- annofabcli/annotation/annotation_query.py +10 -10
- annofabcli/annotation/change_annotation_attributes.py +9 -9
- annofabcli/annotation/change_annotation_attributes_per_annotation.py +3 -4
- annofabcli/annotation/change_annotation_properties.py +13 -13
- annofabcli/annotation/copy_annotation.py +5 -5
- annofabcli/annotation/create_classification_annotation.py +6 -6
- annofabcli/annotation/delete_annotation.py +8 -8
- annofabcli/annotation/download_annotation_zip.py +1 -3
- annofabcli/annotation/dump_annotation.py +7 -7
- annofabcli/annotation/import_annotation.py +12 -12
- annofabcli/annotation/list_annotation.py +8 -8
- annofabcli/annotation/list_annotation_count.py +1 -2
- annofabcli/annotation/merge_segmentation.py +5 -5
- annofabcli/annotation/remove_segmentation_overlap.py +4 -4
- annofabcli/annotation/restore_annotation.py +6 -6
- annofabcli/annotation/subcommand_annotation.py +1 -2
- annofabcli/annotation_specs/add_attribute_restriction.py +4 -5
- annofabcli/annotation_specs/attribute_restriction.py +8 -8
- annofabcli/annotation_specs/export_annotation_specs.py +4 -5
- annofabcli/annotation_specs/get_annotation_specs_with_attribute_id_replaced.py +3 -4
- annofabcli/annotation_specs/get_annotation_specs_with_choice_id_replaced.py +3 -4
- annofabcli/annotation_specs/get_annotation_specs_with_label_id_replaced.py +3 -4
- annofabcli/annotation_specs/list_annotation_specs_attribute.py +9 -10
- annofabcli/annotation_specs/list_annotation_specs_choice.py +9 -10
- annofabcli/annotation_specs/list_annotation_specs_history.py +2 -2
- annofabcli/annotation_specs/list_annotation_specs_label.py +8 -9
- annofabcli/annotation_specs/list_annotation_specs_label_attribute.py +10 -11
- annofabcli/annotation_specs/list_attribute_restriction.py +2 -4
- annofabcli/annotation_specs/list_label_color.py +2 -3
- annofabcli/annotation_specs/put_label_color.py +3 -4
- annofabcli/annotation_specs/subcommand_annotation_specs.py +1 -3
- annofabcli/annotation_zip/list_annotation_3d_bounding_box.py +365 -0
- annofabcli/annotation_zip/list_annotation_bounding_box_2d.py +11 -12
- annofabcli/annotation_zip/list_range_annotation.py +24 -14
- annofabcli/annotation_zip/list_single_point_annotation.py +11 -12
- annofabcli/annotation_zip/subcommand_annotation_zip.py +3 -2
- annofabcli/annotation_zip/validate_annotation.py +8 -7
- annofabcli/comment/delete_comment.py +4 -6
- annofabcli/comment/download_comment_json.py +4 -6
- annofabcli/comment/list_all_comment.py +5 -6
- annofabcli/comment/list_comment.py +3 -4
- annofabcli/comment/put_comment.py +9 -10
- annofabcli/comment/put_comment_simply.py +5 -6
- annofabcli/comment/put_inspection_comment.py +1 -3
- annofabcli/comment/put_inspection_comment_simply.py +1 -3
- annofabcli/comment/put_onhold_comment.py +1 -3
- annofabcli/comment/put_onhold_comment_simply.py +1 -3
- annofabcli/comment/subcommand_comment.py +1 -3
- annofabcli/common/bokeh.py +4 -4
- annofabcli/common/cli.py +17 -17
- annofabcli/common/download.py +28 -29
- annofabcli/common/facade.py +37 -38
- annofabcli/common/image.py +14 -14
- annofabcli/common/utils.py +8 -8
- annofabcli/common/visualize.py +13 -13
- annofabcli/experimental/list_out_of_range_annotation_for_movie.py +3 -4
- annofabcli/experimental/subcommand_experimental.py +1 -3
- annofabcli/filesystem/draw_annotation.py +26 -26
- annofabcli/filesystem/filter_annotation.py +9 -10
- annofabcli/filesystem/mask_user_info.py +14 -14
- annofabcli/filesystem/merge_annotation.py +8 -8
- annofabcli/filesystem/subcommand_filesystem.py +1 -3
- annofabcli/input_data/copy_input_data.py +8 -9
- annofabcli/input_data/delete_input_data.py +2 -2
- annofabcli/input_data/delete_metadata_key_of_input_data.py +3 -5
- annofabcli/input_data/download_input_data_json.py +4 -6
- annofabcli/input_data/list_all_input_data.py +8 -8
- annofabcli/input_data/list_all_input_data_merged_task.py +4 -4
- annofabcli/input_data/list_input_data.py +4 -4
- annofabcli/input_data/put_input_data.py +5 -5
- annofabcli/input_data/put_input_data_with_zip.py +2 -3
- annofabcli/input_data/subcommand_input_data.py +1 -3
- annofabcli/input_data/update_input_data.py +6 -8
- annofabcli/input_data/update_metadata_of_input_data.py +3 -5
- annofabcli/instruction/copy_instruction.py +4 -5
- annofabcli/instruction/download_instruction.py +4 -5
- annofabcli/instruction/list_instruction_history.py +2 -2
- annofabcli/instruction/subcommand_instruction.py +1 -3
- annofabcli/instruction/upload_instruction.py +2 -3
- annofabcli/job/delete_job.py +1 -2
- annofabcli/job/list_job.py +4 -4
- annofabcli/job/list_last_job.py +3 -3
- annofabcli/job/subcommand_job.py +1 -3
- annofabcli/job/wait_job.py +4 -5
- annofabcli/my_account/get_my_account.py +1 -2
- annofabcli/my_account/subcommand_my_account.py +1 -3
- annofabcli/organization/list_organization.py +1 -2
- annofabcli/organization/subcommand_organization.py +1 -3
- annofabcli/organization_member/change_organization_member.py +3 -4
- annofabcli/organization_member/delete_organization_member.py +3 -4
- annofabcli/organization_member/invite_organization_member.py +1 -3
- annofabcli/organization_member/list_organization_member.py +2 -2
- annofabcli/organization_member/subcommand_organization_member.py +1 -3
- annofabcli/project/change_organization_of_project.py +3 -3
- annofabcli/project/change_project_status.py +3 -3
- annofabcli/project/copy_project.py +4 -4
- annofabcli/project/create_project.py +7 -7
- annofabcli/project/diff_projects.py +4 -5
- annofabcli/project/list_project.py +4 -4
- annofabcli/project/put_project.py +1 -2
- annofabcli/project/subcommand_project.py +1 -2
- annofabcli/project/update_configuration.py +3 -3
- annofabcli/project/update_project.py +6 -8
- annofabcli/project_member/change_project_members.py +7 -7
- annofabcli/project_member/copy_project_members.py +3 -3
- annofabcli/project_member/drop_project_members.py +1 -2
- annofabcli/project_member/invite_project_members.py +1 -3
- annofabcli/project_member/list_users.py +1 -2
- annofabcli/project_member/put_project_members.py +5 -5
- annofabcli/project_member/subcommand_project_member.py +1 -3
- annofabcli/stat_visualization/mask_visualization_dir.py +8 -9
- annofabcli/stat_visualization/merge_visualization_dir.py +6 -7
- annofabcli/stat_visualization/subcommand_stat_visualization.py +1 -2
- annofabcli/stat_visualization/summarize_whole_performance_csv.py +1 -2
- annofabcli/stat_visualization/write_graph.py +2 -3
- annofabcli/stat_visualization/write_performance_rating_csv.py +20 -27
- annofabcli/statistics/histogram.py +5 -6
- annofabcli/statistics/linegraph.py +13 -14
- annofabcli/statistics/list_annotation_area.py +38 -13
- annofabcli/statistics/list_annotation_attribute.py +9 -10
- annofabcli/statistics/list_annotation_attribute_filled_count.py +30 -31
- annofabcli/statistics/list_annotation_count.py +57 -58
- annofabcli/statistics/list_annotation_duration.py +33 -34
- annofabcli/statistics/list_video_duration.py +4 -5
- annofabcli/statistics/list_worktime.py +3 -3
- annofabcli/statistics/scatter.py +9 -8
- annofabcli/statistics/subcommand_statistics.py +1 -4
- annofabcli/statistics/summarize_task_count.py +4 -6
- annofabcli/statistics/summarize_task_count_by_task_id_group.py +2 -4
- annofabcli/statistics/summarize_task_count_by_user.py +1 -3
- annofabcli/statistics/visualization/dataframe/annotation_count.py +5 -4
- annofabcli/statistics/visualization/dataframe/annotation_duration.py +6 -7
- annofabcli/statistics/visualization/dataframe/cumulative_productivity.py +15 -17
- annofabcli/statistics/visualization/dataframe/productivity_per_date.py +17 -19
- annofabcli/statistics/visualization/dataframe/project_performance.py +3 -12
- annofabcli/statistics/visualization/dataframe/task.py +11 -12
- annofabcli/statistics/visualization/dataframe/task_worktime_by_phase_user.py +9 -10
- annofabcli/statistics/visualization/dataframe/user_performance.py +21 -19
- annofabcli/statistics/visualization/dataframe/whole_performance.py +3 -4
- annofabcli/statistics/visualization/dataframe/whole_productivity_per_date.py +12 -14
- annofabcli/statistics/visualization/dataframe/worktime_per_date.py +11 -13
- annofabcli/statistics/visualization/filtering_query.py +7 -7
- annofabcli/statistics/visualization/project_dir.py +27 -14
- annofabcli/statistics/visualization/visualization_source_files.py +49 -0
- annofabcli/statistics/visualize_annotation_count.py +22 -23
- annofabcli/statistics/visualize_annotation_duration.py +21 -22
- annofabcli/statistics/visualize_statistics.py +126 -69
- annofabcli/statistics/visualize_video_duration.py +18 -20
- annofabcli/supplementary/delete_supplementary_data.py +4 -4
- annofabcli/supplementary/list_supplementary_data.py +3 -3
- annofabcli/supplementary/put_supplementary_data.py +8 -8
- annofabcli/supplementary/subcommand_supplementary.py +1 -3
- annofabcli/task/cancel_acceptance.py +16 -17
- annofabcli/task/change_operator.py +10 -12
- annofabcli/task/change_status_to_break.py +7 -9
- annofabcli/task/change_status_to_on_hold.py +10 -12
- annofabcli/task/complete_tasks.py +17 -18
- annofabcli/task/copy_tasks.py +3 -5
- annofabcli/task/delete_metadata_key_of_task.py +4 -6
- annofabcli/task/delete_tasks.py +6 -6
- annofabcli/task/download_task_json.py +4 -6
- annofabcli/task/list_all_tasks.py +7 -7
- annofabcli/task/list_all_tasks_added_task_history.py +12 -12
- annofabcli/task/list_tasks.py +6 -6
- annofabcli/task/list_tasks_added_task_history.py +9 -9
- annofabcli/task/put_tasks.py +4 -5
- annofabcli/task/put_tasks_by_count.py +1 -2
- annofabcli/task/reject_tasks.py +18 -20
- annofabcli/task/subcommand_task.py +1 -3
- annofabcli/task/update_metadata_of_task.py +5 -6
- annofabcli/task_history/download_task_history_json.py +4 -6
- annofabcli/task_history/list_all_task_history.py +5 -6
- annofabcli/task_history/list_task_history.py +3 -4
- annofabcli/task_history/subcommand_task_history.py +1 -3
- annofabcli/task_history_event/download_task_history_event_json.py +4 -6
- annofabcli/task_history_event/list_all_task_history_event.py +6 -6
- annofabcli/task_history_event/list_worktime.py +15 -15
- annofabcli/task_history_event/subcommand_task_history_event.py +1 -2
- {annofabcli-1.111.1.dist-info → annofabcli-1.112.0.dist-info}/METADATA +9 -15
- annofabcli-1.112.0.dist-info/RECORD +229 -0
- {annofabcli-1.111.1.dist-info → annofabcli-1.112.0.dist-info}/WHEEL +1 -1
- annofabcli-1.111.1.dist-info/RECORD +0 -228
- {annofabcli-1.111.1.dist-info → annofabcli-1.112.0.dist-info}/entry_points.txt +0 -0
- {annofabcli-1.111.1.dist-info → annofabcli-1.112.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,13 +6,14 @@ import json
|
|
|
6
6
|
import logging.handlers
|
|
7
7
|
import sys
|
|
8
8
|
import tempfile
|
|
9
|
+
from collections.abc import Callable
|
|
9
10
|
from multiprocessing import Pool
|
|
10
11
|
from pathlib import Path
|
|
11
|
-
from typing import Any
|
|
12
|
+
from typing import Any
|
|
12
13
|
|
|
13
14
|
import annofabapi
|
|
14
15
|
import pandas
|
|
15
|
-
from annofabapi.models import
|
|
16
|
+
from annofabapi.models import ProjectMemberRole, TaskPhase
|
|
16
17
|
|
|
17
18
|
import annofabcli
|
|
18
19
|
from annofabcli.common.cli import (
|
|
@@ -74,14 +75,15 @@ class WriteCsvGraph:
|
|
|
74
75
|
project_dir: ProjectDir,
|
|
75
76
|
actual_worktime: ActualWorktime,
|
|
76
77
|
*,
|
|
77
|
-
annotation_count:
|
|
78
|
-
input_data_count:
|
|
79
|
-
custom_production_volume:
|
|
78
|
+
annotation_count: AnnotationCount | None = None,
|
|
79
|
+
input_data_count: InputDataCount | None = None,
|
|
80
|
+
custom_production_volume: CustomProductionVolume | None = None,
|
|
80
81
|
minimal_output: bool = False,
|
|
81
82
|
output_only_text: bool = False,
|
|
82
|
-
production_volume_include_labels:
|
|
83
|
-
production_volume_exclude_labels:
|
|
83
|
+
production_volume_include_labels: list[str] | None = None,
|
|
84
|
+
production_volume_exclude_labels: list[str] | None = None,
|
|
84
85
|
include_annotation_duration_seconds: bool = False,
|
|
86
|
+
include_video_duration_minutes: bool = False,
|
|
85
87
|
) -> None:
|
|
86
88
|
self.service = service
|
|
87
89
|
self.project_id = project_id
|
|
@@ -98,10 +100,11 @@ class WriteCsvGraph:
|
|
|
98
100
|
self.production_volume_include_labels = production_volume_include_labels
|
|
99
101
|
self.production_volume_exclude_labels = production_volume_exclude_labels
|
|
100
102
|
self.include_annotation_duration_seconds = include_annotation_duration_seconds
|
|
103
|
+
self.include_video_duration_minutes = include_video_duration_minutes
|
|
101
104
|
|
|
102
|
-
self.task:
|
|
103
|
-
self.worktime_per_date:
|
|
104
|
-
self.task_worktime_obj:
|
|
105
|
+
self.task: Task | None = None
|
|
106
|
+
self.worktime_per_date: WorktimePerDate | None = None
|
|
107
|
+
self.task_worktime_obj: TaskWorktimeByPhaseUser | None = None
|
|
105
108
|
|
|
106
109
|
def _catch_exception(self, function: Callable[..., Any]) -> Callable[..., Any]:
|
|
107
110
|
"""
|
|
@@ -119,14 +122,18 @@ class WriteCsvGraph:
|
|
|
119
122
|
|
|
120
123
|
def _get_task(self) -> Task:
|
|
121
124
|
if self.task is None:
|
|
125
|
+
custom_production_volume = self._prepare_custom_production_volume()
|
|
126
|
+
|
|
122
127
|
if self.annotation_count is None:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
if self.visualize_source_files.annotation_zip_path.exists():
|
|
129
|
+
annotation_count = AnnotationCount.from_annotation_zip(
|
|
130
|
+
self.visualize_source_files.annotation_zip_path,
|
|
131
|
+
project_id=self.project_id,
|
|
132
|
+
include_labels=self.production_volume_include_labels,
|
|
133
|
+
exclude_labels=self.production_volume_exclude_labels,
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
annotation_count = AnnotationCount.empty()
|
|
130
137
|
else:
|
|
131
138
|
annotation_count = self.annotation_count
|
|
132
139
|
|
|
@@ -137,36 +144,6 @@ class WriteCsvGraph:
|
|
|
137
144
|
new_tasks = filter_tasks(tasks, self.task_completion_criteria, self.filtering_query, task_histories=task_histories)
|
|
138
145
|
logger.debug(f"project_id='{self.project_id}' :: 集計対象タスクは {len(new_tasks)} / {len(tasks)} 件です。")
|
|
139
146
|
|
|
140
|
-
# annotation_duration_secondsを生産量に含める場合、アノテーション時間を計算
|
|
141
|
-
custom_production_volume = self.custom_production_volume
|
|
142
|
-
if self.include_annotation_duration_seconds:
|
|
143
|
-
logger.debug(f"project_id='{self.project_id}' :: 区間アノテーションの長さ('annotation_duration_second')を計算します。")
|
|
144
|
-
annotation_duration_obj = AnnotationDuration.from_annotation_zip(
|
|
145
|
-
self.visualize_source_files.annotation_zip_path,
|
|
146
|
-
project_id=self.project_id,
|
|
147
|
-
include_labels=self.production_volume_include_labels,
|
|
148
|
-
exclude_labels=self.production_volume_exclude_labels,
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
if custom_production_volume is not None:
|
|
152
|
-
# 既存のCustomProductionVolumeのデータと結合
|
|
153
|
-
if not custom_production_volume.is_empty():
|
|
154
|
-
annotation_duration_df = pandas.merge(custom_production_volume.df, annotation_duration_obj.df, on=["project_id", "task_id"], how="outer")
|
|
155
|
-
else:
|
|
156
|
-
annotation_duration_df = annotation_duration_obj.df
|
|
157
|
-
|
|
158
|
-
# annotation_duration_secondを含む新しいProductionVolumeColumnリストを作成
|
|
159
|
-
annotation_duration_column = ProductionVolumeColumn(value="annotation_duration_second", name="区間アノテーションの長さ(秒)")
|
|
160
|
-
new_production_volume_list = list(custom_production_volume.custom_production_volume_list)
|
|
161
|
-
if annotation_duration_column not in new_production_volume_list:
|
|
162
|
-
new_production_volume_list.append(annotation_duration_column)
|
|
163
|
-
|
|
164
|
-
custom_production_volume = CustomProductionVolume(annotation_duration_df, custom_production_volume_list=new_production_volume_list)
|
|
165
|
-
else:
|
|
166
|
-
# CustomProductionVolumeが存在しない場合、新規作成
|
|
167
|
-
annotation_duration_column = ProductionVolumeColumn(value="annotation_duration_second", name="区間アノテーションの長さ(秒)")
|
|
168
|
-
custom_production_volume = CustomProductionVolume(annotation_duration_obj.df, custom_production_volume_list=[annotation_duration_column])
|
|
169
|
-
|
|
170
147
|
self.task = Task.from_api_content(
|
|
171
148
|
tasks=new_tasks,
|
|
172
149
|
task_histories=task_histories,
|
|
@@ -180,6 +157,80 @@ class WriteCsvGraph:
|
|
|
180
157
|
|
|
181
158
|
return self.task
|
|
182
159
|
|
|
160
|
+
def _prepare_custom_production_volume(self) -> CustomProductionVolume | None:
|
|
161
|
+
"""カスタム生産量の準備を行う"""
|
|
162
|
+
custom_production_volume = self.custom_production_volume
|
|
163
|
+
|
|
164
|
+
# annotation_duration_secondsを生産量に含める場合、アノテーション時間を計算
|
|
165
|
+
if self.include_annotation_duration_seconds:
|
|
166
|
+
custom_production_volume = self._add_annotation_duration(custom_production_volume)
|
|
167
|
+
|
|
168
|
+
# 動画プロジェクトの場合、動画の長さ(分)を生産量に含める
|
|
169
|
+
if self.include_video_duration_minutes:
|
|
170
|
+
custom_production_volume = self._add_video_duration(custom_production_volume)
|
|
171
|
+
|
|
172
|
+
return custom_production_volume
|
|
173
|
+
|
|
174
|
+
def _add_annotation_duration(self, custom_production_volume: CustomProductionVolume | None) -> CustomProductionVolume:
|
|
175
|
+
"""区間アノテーションの長さを生産量に追加する"""
|
|
176
|
+
logger.debug(f"project_id='{self.project_id}' :: 区間アノテーションの長さ('annotation_duration_minute')を計算します。")
|
|
177
|
+
annotation_duration_obj = AnnotationDuration.from_annotation_zip(
|
|
178
|
+
self.visualize_source_files.annotation_zip_path,
|
|
179
|
+
project_id=self.project_id,
|
|
180
|
+
include_labels=self.production_volume_include_labels,
|
|
181
|
+
exclude_labels=self.production_volume_exclude_labels,
|
|
182
|
+
)
|
|
183
|
+
annotation_duration_column = ProductionVolumeColumn(value="annotation_duration_minute", name="区間アノテーションの長さ(分)")
|
|
184
|
+
|
|
185
|
+
if custom_production_volume is not None:
|
|
186
|
+
# 既存のCustomProductionVolumeのデータと結合
|
|
187
|
+
if not custom_production_volume.is_empty():
|
|
188
|
+
annotation_duration_df = pandas.merge(custom_production_volume.df, annotation_duration_obj.df, on=["project_id", "task_id"], how="outer")
|
|
189
|
+
else:
|
|
190
|
+
annotation_duration_df = annotation_duration_obj.df
|
|
191
|
+
|
|
192
|
+
# annotation_duration_minuteを含む新しいProductionVolumeColumnリストを作成
|
|
193
|
+
new_production_volume_list = list(custom_production_volume.custom_production_volume_list)
|
|
194
|
+
if annotation_duration_column not in new_production_volume_list:
|
|
195
|
+
new_production_volume_list.append(annotation_duration_column)
|
|
196
|
+
|
|
197
|
+
return CustomProductionVolume(annotation_duration_df, custom_production_volume_list=new_production_volume_list)
|
|
198
|
+
else:
|
|
199
|
+
# CustomProductionVolumeが存在しない場合、新規作成
|
|
200
|
+
return CustomProductionVolume(annotation_duration_obj.df, custom_production_volume_list=[annotation_duration_column])
|
|
201
|
+
|
|
202
|
+
def _add_video_duration(self, custom_production_volume: CustomProductionVolume | None) -> CustomProductionVolume:
|
|
203
|
+
"""動画の長さ(分)を生産量に追加する"""
|
|
204
|
+
logger.debug(f"project_id='{self.project_id}' :: 動画の長さ('video_duration_minute')を計算します。")
|
|
205
|
+
video_duration_by_task_id = self.visualize_source_files.get_video_duration_minutes_by_task_id()
|
|
206
|
+
|
|
207
|
+
# DataFrameの作成
|
|
208
|
+
video_duration_data = [{"project_id": self.project_id, "task_id": task_id, "video_duration_minute": duration} for task_id, duration in video_duration_by_task_id.items()]
|
|
209
|
+
if len(video_duration_data) == 0:
|
|
210
|
+
video_duration_df = pandas.DataFrame(columns=["project_id", "task_id", "video_duration_minute"])
|
|
211
|
+
else:
|
|
212
|
+
video_duration_df = pandas.DataFrame(video_duration_data)
|
|
213
|
+
|
|
214
|
+
video_duration_df = video_duration_df.astype({"project_id": "string", "task_id": "string", "video_duration_minute": "float64"})
|
|
215
|
+
video_duration_column = ProductionVolumeColumn(value="video_duration_minute", name="動画の長さ(分)")
|
|
216
|
+
|
|
217
|
+
if custom_production_volume is not None:
|
|
218
|
+
# 既存のCustomProductionVolumeのデータと結合
|
|
219
|
+
if not custom_production_volume.is_empty():
|
|
220
|
+
merged_df = pandas.merge(custom_production_volume.df, video_duration_df, on=["project_id", "task_id"], how="outer")
|
|
221
|
+
else:
|
|
222
|
+
merged_df = video_duration_df
|
|
223
|
+
|
|
224
|
+
# video_duration_minuteを含む新しいProductionVolumeColumnリストを作成
|
|
225
|
+
new_production_volume_list = list(custom_production_volume.custom_production_volume_list)
|
|
226
|
+
if video_duration_column not in new_production_volume_list:
|
|
227
|
+
new_production_volume_list.append(video_duration_column)
|
|
228
|
+
|
|
229
|
+
return CustomProductionVolume(merged_df, custom_production_volume_list=new_production_volume_list)
|
|
230
|
+
else:
|
|
231
|
+
# CustomProductionVolumeが存在しない場合、新規作成
|
|
232
|
+
return CustomProductionVolume(video_duration_df, custom_production_volume_list=[video_duration_column])
|
|
233
|
+
|
|
183
234
|
def _get_task_worktime_obj(self) -> TaskWorktimeByPhaseUser:
|
|
184
235
|
if self.task_worktime_obj is None:
|
|
185
236
|
task_history = TaskHistory.from_api_content(self.visualize_source_files.read_task_histories_json())
|
|
@@ -247,7 +298,7 @@ class WriteCsvGraph:
|
|
|
247
298
|
if not self.output_only_text:
|
|
248
299
|
self.project_dir.write_user_performance_scatter_plot(user_performance)
|
|
249
300
|
|
|
250
|
-
def write_cumulative_linegraph_by_user(self, user_id_list:
|
|
301
|
+
def write_cumulative_linegraph_by_user(self, user_id_list: list[str] | None = None) -> None:
|
|
251
302
|
"""ユーザごとの累積折れ線グラフをプロットする。"""
|
|
252
303
|
task_worktime_obj = self._get_task_worktime_obj()
|
|
253
304
|
annotator_obj = AnnotatorCumulativeProductivity.from_df_wrapper(task_worktime_obj)
|
|
@@ -259,7 +310,7 @@ class WriteCsvGraph:
|
|
|
259
310
|
self.project_dir.write_cumulative_line_graph(inspector_obj, phase=TaskPhase.INSPECTION, user_id_list=user_id_list, minimal_output=self.minimal_output)
|
|
260
311
|
self.project_dir.write_cumulative_line_graph(acceptor_obj, phase=TaskPhase.ACCEPTANCE, user_id_list=user_id_list, minimal_output=self.minimal_output)
|
|
261
312
|
|
|
262
|
-
def write_worktime_per_date(self, user_id_list:
|
|
313
|
+
def write_worktime_per_date(self, user_id_list: list[str] | None = None) -> None:
|
|
263
314
|
"""日ごとの作業時間情報を出力する。"""
|
|
264
315
|
worktime_per_date_obj = self._get_worktime_per_date()
|
|
265
316
|
|
|
@@ -278,7 +329,7 @@ class WriteCsvGraph:
|
|
|
278
329
|
self.project_dir.write_whole_productivity_line_graph_per_date(productivity_per_completed_date_obj)
|
|
279
330
|
self.project_dir.write_whole_productivity_line_graph_per_annotation_started_date(productivity_per_started_date_obj)
|
|
280
331
|
|
|
281
|
-
def write_user_productivity_per_date(self, user_id_list:
|
|
332
|
+
def write_user_productivity_per_date(self, user_id_list: list[str] | None = None) -> None:
|
|
282
333
|
"""ユーザごとの日ごとの生産性情報を出力する。"""
|
|
283
334
|
task_worktime_obj = self._get_task_worktime_obj()
|
|
284
335
|
|
|
@@ -312,14 +363,14 @@ class VisualizingStatisticsMain:
|
|
|
312
363
|
# その他
|
|
313
364
|
download_latest: bool = False,
|
|
314
365
|
is_get_task_histories_one_of_each: bool = False,
|
|
315
|
-
actual_worktime:
|
|
316
|
-
annotation_count:
|
|
317
|
-
input_data_count:
|
|
318
|
-
custom_production_volume:
|
|
319
|
-
user_ids:
|
|
366
|
+
actual_worktime: ActualWorktime | None = None,
|
|
367
|
+
annotation_count: AnnotationCount | None = None,
|
|
368
|
+
input_data_count: InputDataCount | None = None,
|
|
369
|
+
custom_production_volume: CustomProductionVolume | None = None,
|
|
370
|
+
user_ids: list[str] | None = None,
|
|
320
371
|
not_download_visualization_source_files: bool = False,
|
|
321
|
-
production_volume_include_labels:
|
|
322
|
-
production_volume_exclude_labels:
|
|
372
|
+
production_volume_include_labels: list[str] | None = None,
|
|
373
|
+
production_volume_exclude_labels: list[str] | None = None,
|
|
323
374
|
) -> None:
|
|
324
375
|
self.service = service
|
|
325
376
|
self.facade = AnnofabApiFacade(service)
|
|
@@ -350,6 +401,8 @@ class VisualizingStatisticsMain:
|
|
|
350
401
|
measurement_datetime=annofabapi.utils.str_now(),
|
|
351
402
|
task_completion_criteria=self.task_completion_criteria,
|
|
352
403
|
query=self.filtering_query,
|
|
404
|
+
production_volume_include_labels=self.production_volume_include_labels,
|
|
405
|
+
production_volume_exclude_labels=self.production_volume_exclude_labels,
|
|
353
406
|
)
|
|
354
407
|
return project_summary
|
|
355
408
|
|
|
@@ -370,6 +423,9 @@ class VisualizingStatisticsMain:
|
|
|
370
423
|
# 動画プロジェクトの場合、annotation_duration_secondを生産量に含める
|
|
371
424
|
custom_production_volume = self.custom_production_volume
|
|
372
425
|
|
|
426
|
+
# 動画プロジェクトかどうかを判定
|
|
427
|
+
is_video_project = project_info.input_data_type == "movie"
|
|
428
|
+
|
|
373
429
|
project_dir = ProjectDir(
|
|
374
430
|
output_project_dir,
|
|
375
431
|
self.task_completion_criteria,
|
|
@@ -429,7 +485,8 @@ class VisualizingStatisticsMain:
|
|
|
429
485
|
output_only_text=self.output_only_text,
|
|
430
486
|
production_volume_include_labels=self.production_volume_include_labels,
|
|
431
487
|
production_volume_exclude_labels=self.production_volume_exclude_labels,
|
|
432
|
-
include_annotation_duration_seconds=
|
|
488
|
+
include_annotation_duration_seconds=is_video_project,
|
|
489
|
+
include_video_duration_minutes=is_video_project,
|
|
433
490
|
)
|
|
434
491
|
|
|
435
492
|
write_obj._catch_exception(write_obj.write_user_performance)() # noqa: SLF001
|
|
@@ -447,7 +504,7 @@ class VisualizingStatisticsMain:
|
|
|
447
504
|
self,
|
|
448
505
|
project_id: str,
|
|
449
506
|
root_output_dir: Path,
|
|
450
|
-
) ->
|
|
507
|
+
) -> Path | None:
|
|
451
508
|
try:
|
|
452
509
|
output_project_dir = root_output_dir / project_id
|
|
453
510
|
self.visualize_statistics(
|
|
@@ -464,7 +521,7 @@ class VisualizingStatisticsMain:
|
|
|
464
521
|
project_id_list: list[str],
|
|
465
522
|
root_output_dir: Path,
|
|
466
523
|
*,
|
|
467
|
-
parallelism:
|
|
524
|
+
parallelism: int | None = None,
|
|
468
525
|
) -> list[Path]:
|
|
469
526
|
output_project_dir_list: list[Path] = []
|
|
470
527
|
|
|
@@ -527,11 +584,11 @@ class VisualizeStatistics(CommandLine):
|
|
|
527
584
|
temp_dir: Path,
|
|
528
585
|
filtering_query: FilteringQuery,
|
|
529
586
|
task_completion_criteria: TaskCompletionCriteria,
|
|
530
|
-
user_id_list:
|
|
587
|
+
user_id_list: list[str] | None,
|
|
531
588
|
actual_worktime: ActualWorktime,
|
|
532
|
-
annotation_count:
|
|
533
|
-
input_data_count:
|
|
534
|
-
custom_production_volume:
|
|
589
|
+
annotation_count: AnnotationCount | None,
|
|
590
|
+
input_data_count: InputDataCount | None,
|
|
591
|
+
custom_production_volume: CustomProductionVolume | None,
|
|
535
592
|
download_latest: bool, # noqa: FBT001
|
|
536
593
|
is_get_task_histories_one_of_each: bool, # noqa: FBT001
|
|
537
594
|
minimal_output: bool, # noqa: FBT001
|
|
@@ -539,9 +596,9 @@ class VisualizeStatistics(CommandLine):
|
|
|
539
596
|
not_download_visualization_source_files: bool, # noqa: FBT001
|
|
540
597
|
project_id_list: list[str],
|
|
541
598
|
root_output_dir: Path,
|
|
542
|
-
parallelism:
|
|
543
|
-
production_volume_include_labels:
|
|
544
|
-
production_volume_exclude_labels:
|
|
599
|
+
parallelism: int | None,
|
|
600
|
+
production_volume_include_labels: list[str] | None = None,
|
|
601
|
+
production_volume_exclude_labels: list[str] | None = None,
|
|
545
602
|
) -> None:
|
|
546
603
|
main_obj = VisualizingStatisticsMain(
|
|
547
604
|
service=self.service,
|
|
@@ -596,7 +653,7 @@ class VisualizeStatistics(CommandLine):
|
|
|
596
653
|
task_completion_criteria = TaskCompletionCriteria(args.task_completion_criteria)
|
|
597
654
|
|
|
598
655
|
dict_task_query = annofabcli.common.cli.get_json_from_args(args.task_query)
|
|
599
|
-
task_query:
|
|
656
|
+
task_query: TaskQuery | None = None
|
|
600
657
|
if dict_task_query is not None:
|
|
601
658
|
task_query = TaskQuery.from_dict(dict_task_query)
|
|
602
659
|
logger.warning("引数 '--task_query' は非推奨です。代わりに '--task_completion_criteria' を指定してください。")
|
|
@@ -852,7 +909,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
852
909
|
parser.set_defaults(subcommand_func=main)
|
|
853
910
|
|
|
854
911
|
|
|
855
|
-
def add_parser(subparsers:
|
|
912
|
+
def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
|
|
856
913
|
subcommand_name = "visualize"
|
|
857
914
|
subcommand_help = "生産性に関するCSVファイルやグラフを出力します。"
|
|
858
915
|
description = "生産性に関するCSVファイルやグラフを出力します。"
|
|
@@ -11,7 +11,6 @@ from collections.abc import Collection, Sequence
|
|
|
11
11
|
from enum import Enum
|
|
12
12
|
from functools import partial
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import Optional, Union
|
|
15
14
|
|
|
16
15
|
import bokeh
|
|
17
16
|
import numpy
|
|
@@ -22,7 +21,6 @@ from bokeh.models.annotations.labels import Title
|
|
|
22
21
|
from bokeh.models.widgets.markups import PreText
|
|
23
22
|
from bokeh.plotting import ColumnDataSource, figure
|
|
24
23
|
|
|
25
|
-
import annofabcli
|
|
26
24
|
import annofabcli.common.cli
|
|
27
25
|
from annofabcli.common.cli import (
|
|
28
26
|
COMMAND_LINE_ERROR_STATUS_CODE,
|
|
@@ -49,9 +47,9 @@ def plot_video_duration(
|
|
|
49
47
|
output_file: Path,
|
|
50
48
|
*,
|
|
51
49
|
time_unit: TimeUnit,
|
|
52
|
-
bin_width:
|
|
53
|
-
project_id:
|
|
54
|
-
project_title:
|
|
50
|
+
bin_width: float | None = None,
|
|
51
|
+
project_id: str | None = None,
|
|
52
|
+
project_title: str | None = None,
|
|
55
53
|
) -> None:
|
|
56
54
|
"""
|
|
57
55
|
ラベルごとの区間アノテーションの長さのヒストグラムを出力します。
|
|
@@ -66,7 +64,7 @@ def plot_video_duration(
|
|
|
66
64
|
|
|
67
65
|
def create_figure(
|
|
68
66
|
durations: Sequence[float],
|
|
69
|
-
bins:
|
|
67
|
+
bins: int | numpy.ndarray,
|
|
70
68
|
histogram_range: tuple[float, float],
|
|
71
69
|
title: str,
|
|
72
70
|
x_axis_label: str,
|
|
@@ -75,10 +73,10 @@ def plot_video_duration(
|
|
|
75
73
|
hist, bin_edges = numpy.histogram(durations, bins=bins, range=histogram_range)
|
|
76
74
|
|
|
77
75
|
df_histogram = pandas.DataFrame({"frequency": hist, "left": bin_edges[:-1], "right": bin_edges[1:]})
|
|
78
|
-
df_histogram["interval"] = [f"{left:.1f} to {right:.1f}" for left, right in zip(df_histogram["left"], df_histogram["right"])]
|
|
76
|
+
df_histogram["interval"] = [f"{left:.1f} to {right:.1f}" for left, right in zip(df_histogram["left"], df_histogram["right"], strict=False)]
|
|
79
77
|
|
|
80
78
|
source = ColumnDataSource(df_histogram)
|
|
81
|
-
fig = figure(
|
|
79
|
+
fig = figure( # type: ignore[call-arg]
|
|
82
80
|
width=400,
|
|
83
81
|
height=300,
|
|
84
82
|
x_axis_label=x_axis_label,
|
|
@@ -108,7 +106,7 @@ def plot_video_duration(
|
|
|
108
106
|
if bins_sequence[-1] == max_duration:
|
|
109
107
|
bins_sequence = numpy.append(bins_sequence, bins_sequence[-1] + bin_width)
|
|
110
108
|
|
|
111
|
-
bins:
|
|
109
|
+
bins: int | numpy.ndarray = bins_sequence
|
|
112
110
|
else:
|
|
113
111
|
bins = BIN_COUNT
|
|
114
112
|
|
|
@@ -142,10 +140,10 @@ def get_video_duration_list(
|
|
|
142
140
|
input_data_json: Path,
|
|
143
141
|
task_json: Path,
|
|
144
142
|
*,
|
|
145
|
-
input_data_ids:
|
|
143
|
+
input_data_ids: Collection[str] | None = None,
|
|
146
144
|
from_datetime: datetime.datetime | None = None,
|
|
147
145
|
to_datetime: datetime.datetime | None = None,
|
|
148
|
-
task_ids:
|
|
146
|
+
task_ids: Collection[str] | None = None,
|
|
149
147
|
) -> list[float]:
|
|
150
148
|
"""
|
|
151
149
|
入力データである動画の長さ(単位は秒)の一覧を取得します。
|
|
@@ -214,13 +212,13 @@ class VisualizeVideoDuration(CommandLine):
|
|
|
214
212
|
output_html: Path,
|
|
215
213
|
*,
|
|
216
214
|
time_unit: TimeUnit,
|
|
217
|
-
project_id:
|
|
218
|
-
project_title:
|
|
219
|
-
bin_width:
|
|
220
|
-
input_data_ids:
|
|
221
|
-
task_ids:
|
|
222
|
-
from_date:
|
|
223
|
-
to_date:
|
|
215
|
+
project_id: str | None = None,
|
|
216
|
+
project_title: str | None = None,
|
|
217
|
+
bin_width: float | None = None,
|
|
218
|
+
input_data_ids: Collection[str] | None = None,
|
|
219
|
+
task_ids: Collection[str] | None = None,
|
|
220
|
+
from_date: str | None = None,
|
|
221
|
+
to_date: str | None = None,
|
|
224
222
|
) -> None:
|
|
225
223
|
tz_info = datetime.datetime.now().astimezone().tzinfo
|
|
226
224
|
from_datetime = datetime.datetime.fromisoformat(from_date).astimezone(tz_info) if from_date is not None else None
|
|
@@ -250,7 +248,7 @@ class VisualizeVideoDuration(CommandLine):
|
|
|
250
248
|
if not self.validate(args):
|
|
251
249
|
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
252
250
|
|
|
253
|
-
project_id:
|
|
251
|
+
project_id: str | None = args.project_id
|
|
254
252
|
if project_id is not None:
|
|
255
253
|
super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
256
254
|
project, _ = self.service.api.get_project(project_id)
|
|
@@ -392,7 +390,7 @@ def main(args: argparse.Namespace) -> None:
|
|
|
392
390
|
VisualizeVideoDuration(service, facade, args).main()
|
|
393
391
|
|
|
394
392
|
|
|
395
|
-
def add_parser(subparsers:
|
|
393
|
+
def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
|
|
396
394
|
subcommand_name = "visualize_video_duration"
|
|
397
395
|
subcommand_help = "動画の長さをヒストグラムで可視化します。"
|
|
398
396
|
epilog = "オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
|
|
@@ -3,7 +3,7 @@ import logging
|
|
|
3
3
|
import sys
|
|
4
4
|
from collections import defaultdict
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
7
7
|
|
|
8
8
|
import annofabapi
|
|
9
9
|
import pandas
|
|
@@ -36,7 +36,7 @@ def get_input_data_supplementary_data_dict_from_csv(csv_path: Path) -> InputData
|
|
|
36
36
|
dtype={"input_data_id": "string", "supplementary_data_id": "string"},
|
|
37
37
|
)
|
|
38
38
|
input_data_dict: InputDataSupplementaryDataDict = defaultdict(list)
|
|
39
|
-
for input_data_id, supplementary_data_id in zip(df["input_data_id"], df["supplementary_data_id"]):
|
|
39
|
+
for input_data_id, supplementary_data_id in zip(df["input_data_id"], df["supplementary_data_id"], strict=False):
|
|
40
40
|
input_data_dict[input_data_id].append(supplementary_data_id)
|
|
41
41
|
return input_data_dict
|
|
42
42
|
|
|
@@ -70,7 +70,7 @@ class DeleteSupplementaryDataMain(CommandLineWithConfirm):
|
|
|
70
70
|
|
|
71
71
|
"""
|
|
72
72
|
|
|
73
|
-
def _get_supplementary_data_list(supplementary_data_id: str) ->
|
|
73
|
+
def _get_supplementary_data_list(supplementary_data_id: str) -> dict[str, Any] | None:
|
|
74
74
|
return first_true(supplementary_data_list, pred=lambda e: e["supplementary_data_id"] == supplementary_data_id)
|
|
75
75
|
|
|
76
76
|
input_data = self.service.wrapper.get_input_data_or_none(project_id, input_data_id)
|
|
@@ -275,7 +275,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
275
275
|
parser.set_defaults(subcommand_func=main)
|
|
276
276
|
|
|
277
277
|
|
|
278
|
-
def add_parser(subparsers:
|
|
278
|
+
def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
|
|
279
279
|
subcommand_name = "delete"
|
|
280
280
|
subcommand_help = "補助情報を削除します。"
|
|
281
281
|
description = "補助情報を削除します。"
|
|
@@ -7,7 +7,7 @@ import logging
|
|
|
7
7
|
import multiprocessing
|
|
8
8
|
import tempfile
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Any
|
|
10
|
+
from typing import Any
|
|
11
11
|
|
|
12
12
|
import annofabapi
|
|
13
13
|
from annofabapi.models import SupplementaryData
|
|
@@ -72,7 +72,7 @@ class ListSupplementaryDataMain:
|
|
|
72
72
|
logger.warning(f"input_data_id='{input_data_index}': 補助情報の取得に失敗しました。", exc_info=True)
|
|
73
73
|
return []
|
|
74
74
|
|
|
75
|
-
def get_all_supplementary_data_list(self, input_data_id_list: list[str], *, parallelism:
|
|
75
|
+
def get_all_supplementary_data_list(self, input_data_id_list: list[str], *, parallelism: int | None = None) -> list[SupplementaryData]:
|
|
76
76
|
"""
|
|
77
77
|
補助情報一覧を取得する。
|
|
78
78
|
"""
|
|
@@ -168,7 +168,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
168
168
|
parser.set_defaults(subcommand_func=main)
|
|
169
169
|
|
|
170
170
|
|
|
171
|
-
def add_parser(subparsers:
|
|
171
|
+
def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
|
|
172
172
|
subcommand_name = "list"
|
|
173
173
|
subcommand_help = "補助情報一覧を出力します。"
|
|
174
174
|
description = "補助情報一覧を出力します。"
|
|
@@ -7,7 +7,7 @@ from dataclasses import dataclass
|
|
|
7
7
|
from functools import partial
|
|
8
8
|
from multiprocessing import Pool
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Any
|
|
10
|
+
from typing import Any
|
|
11
11
|
|
|
12
12
|
import annofabapi
|
|
13
13
|
import pandas
|
|
@@ -49,9 +49,9 @@ class CliSupplementaryData(DataClassJsonMixin):
|
|
|
49
49
|
input_data_id: str
|
|
50
50
|
supplementary_data_name: str
|
|
51
51
|
supplementary_data_path: str
|
|
52
|
-
supplementary_data_id:
|
|
53
|
-
supplementary_data_type:
|
|
54
|
-
supplementary_data_number:
|
|
52
|
+
supplementary_data_id: str | None = None
|
|
53
|
+
supplementary_data_type: str | None = None
|
|
54
|
+
supplementary_data_number: int | None = None
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
@dataclass
|
|
@@ -64,9 +64,9 @@ class SupplementaryDataForPut:
|
|
|
64
64
|
supplementary_data_id: str
|
|
65
65
|
supplementary_data_name: str
|
|
66
66
|
supplementary_data_path: str
|
|
67
|
-
supplementary_data_type:
|
|
67
|
+
supplementary_data_type: str | None
|
|
68
68
|
supplementary_data_number: int
|
|
69
|
-
last_updated_datetime:
|
|
69
|
+
last_updated_datetime: str | None
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
class SubPutSupplementaryData:
|
|
@@ -249,7 +249,7 @@ class PutSupplementaryData(CommandLine):
|
|
|
249
249
|
supplementary_data_list: list[CliSupplementaryData],
|
|
250
250
|
*,
|
|
251
251
|
overwrite: bool = False,
|
|
252
|
-
parallelism:
|
|
252
|
+
parallelism: int | None = None,
|
|
253
253
|
) -> None:
|
|
254
254
|
"""
|
|
255
255
|
補助情報を一括で登録する。
|
|
@@ -412,7 +412,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
412
412
|
parser.set_defaults(subcommand_func=main)
|
|
413
413
|
|
|
414
414
|
|
|
415
|
-
def add_parser(subparsers:
|
|
415
|
+
def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
|
|
416
416
|
subcommand_name = "put"
|
|
417
417
|
subcommand_help = "補助情報を登録します。"
|
|
418
418
|
description = "補助情報を登録します。"
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import argparse
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
|
-
import annofabcli
|
|
5
3
|
import annofabcli.common.cli
|
|
6
4
|
import annofabcli.supplementary.delete_supplementary_data
|
|
7
5
|
import annofabcli.supplementary.list_supplementary_data
|
|
@@ -17,7 +15,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
17
15
|
annofabcli.supplementary.put_supplementary_data.add_parser(subparsers)
|
|
18
16
|
|
|
19
17
|
|
|
20
|
-
def add_parser(subparsers:
|
|
18
|
+
def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
|
|
21
19
|
subcommand_name = "supplementary"
|
|
22
20
|
subcommand_help = "補助情報関係のサブコマンド"
|
|
23
21
|
description = "補助情報関係のサブコマンド"
|