annofabcli 1.114.4__py3-none-any.whl → 1.114.6__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.
@@ -32,9 +32,9 @@ logger = logging.getLogger(__name__)
32
32
  class Annotation3DBoundingBoxInfo(DataClassJsonMixin):
33
33
  project_id: str
34
34
  task_id: str
35
- task_status: str
36
35
  task_phase: str
37
36
  task_phase_stage: int
37
+ task_status: str
38
38
 
39
39
  input_data_id: str
40
40
  input_data_name: str
@@ -162,9 +162,9 @@ def create_df(
162
162
  base_columns = [
163
163
  "project_id",
164
164
  "task_id",
165
- "task_status",
166
165
  "task_phase",
167
166
  "task_phase_stage",
167
+ "task_status",
168
168
  "input_data_id",
169
169
  "input_data_name",
170
170
  "updated_datetime",
@@ -32,9 +32,9 @@ logger = logging.getLogger(__name__)
32
32
  class AnnotationBoundingBoxInfo(DataClassJsonMixin):
33
33
  project_id: str
34
34
  task_id: str
35
- task_status: str
36
35
  task_phase: str
37
36
  task_phase_stage: int
37
+ task_status: str
38
38
 
39
39
  input_data_id: str
40
40
  input_data_name: str
@@ -128,9 +128,9 @@ def create_df(
128
128
  base_columns = [
129
129
  "project_id",
130
130
  "task_id",
131
- "task_status",
132
131
  "task_phase",
133
132
  "task_phase_stage",
133
+ "task_status",
134
134
  "input_data_id",
135
135
  "input_data_name",
136
136
  "updated_datetime",
@@ -36,9 +36,9 @@ class AnnotationPolygonInfo(BaseModel):
36
36
 
37
37
  project_id: str
38
38
  task_id: str
39
- task_status: str
40
39
  task_phase: str
41
40
  task_phase_stage: int
41
+ task_status: str
42
42
 
43
43
  input_data_id: str
44
44
  input_data_name: str
@@ -183,9 +183,9 @@ def create_df(
183
183
  base_columns = [
184
184
  "project_id",
185
185
  "task_id",
186
- "task_status",
187
186
  "task_phase",
188
187
  "task_phase_stage",
188
+ "task_status",
189
189
  "input_data_id",
190
190
  "input_data_name",
191
191
  "updated_datetime",
@@ -35,9 +35,9 @@ class AnnotationPolylineInfo(BaseModel):
35
35
 
36
36
  project_id: str
37
37
  task_id: str
38
- task_status: str
39
38
  task_phase: str
40
39
  task_phase_stage: int
40
+ task_status: str
41
41
 
42
42
  input_data_id: str
43
43
  input_data_name: str
@@ -191,9 +191,9 @@ def create_df(
191
191
  base_columns = [
192
192
  "project_id",
193
193
  "task_id",
194
- "task_status",
195
194
  "task_phase",
196
195
  "task_phase_stage",
196
+ "task_status",
197
197
  "input_data_id",
198
198
  "input_data_name",
199
199
  "updated_datetime",
@@ -32,9 +32,9 @@ logger = logging.getLogger(__name__)
32
32
  class RangeAnnotationInfo(DataClassJsonMixin):
33
33
  project_id: str
34
34
  task_id: str
35
- task_status: str
36
35
  task_phase: str
37
36
  task_phase_stage: int
37
+ task_status: str
38
38
 
39
39
  input_data_id: str
40
40
  input_data_name: str
@@ -118,9 +118,9 @@ def create_df(
118
118
  base_columns = [
119
119
  "project_id",
120
120
  "task_id",
121
- "task_status",
122
121
  "task_phase",
123
122
  "task_phase_stage",
123
+ "task_status",
124
124
  "input_data_id",
125
125
  "input_data_name",
126
126
  "updated_datetime",
@@ -30,9 +30,9 @@ logger = logging.getLogger(__name__)
30
30
  class AnnotationSinglePointInfo(DataClassJsonMixin):
31
31
  project_id: str
32
32
  task_id: str
33
- task_status: str
34
33
  task_phase: str
35
34
  task_phase_stage: int
35
+ task_status: str
36
36
 
37
37
  input_data_id: str
38
38
  input_data_name: str
@@ -108,9 +108,9 @@ def create_df(
108
108
  base_columns = [
109
109
  "project_id",
110
110
  "task_id",
111
- "task_status",
112
111
  "task_phase",
113
112
  "task_phase_stage",
113
+ "task_status",
114
114
  "input_data_id",
115
115
  "input_data_name",
116
116
  "updated_datetime",
@@ -95,8 +95,8 @@ class DeleteCommentMain(CommandLineWithConfirm):
95
95
  task: dict[str, Any],
96
96
  ) -> bool:
97
97
  task_id = task["task_id"]
98
- if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.WORKING.value, TaskStatus.BREAK.value]:
99
- logger.warning(f"task_id='{task_id}' : タスクの状態が未着手,作業中,休憩中 以外の状態なので、コメントを削除できません。(task_status='{task['status']}')")
98
+ if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.BREAK.value]:
99
+ logger.warning(f"task_id='{task_id}' : タスクの状態が未着手,休憩中 以外の状態なので、コメントを削除できません。(task_status='{task['status']}')")
100
100
  return False
101
101
  return True
102
102
 
@@ -9,6 +9,7 @@ from typing import Any
9
9
  import annofabapi
10
10
  import requests
11
11
  from annofabapi.models import CommentType, TaskPhase, TaskStatus
12
+ from annofabapi.pydantic_models.input_data_type import InputDataType
12
13
  from dataclasses_json import DataClassJsonMixin
13
14
 
14
15
  from annofabcli.comment.utils import get_comment_type_name
@@ -18,6 +19,18 @@ from annofabcli.common.type_util import assert_noreturn
18
19
 
19
20
  logger = logging.getLogger(__name__)
20
21
 
22
+ SUPPORTED_ANNOTATION_TYPES_FOR_INSPECTION_DATA = frozenset(
23
+ [
24
+ "user_bounding_box",
25
+ "bounding_box",
26
+ "polygon",
27
+ "polyline",
28
+ "point",
29
+ "range",
30
+ ]
31
+ )
32
+ """検査コメントの座標情報(InspectionData形式)への変換をサポートしているアノテーションタイプ"""
33
+
21
34
 
22
35
  @dataclass
23
36
  class AddedComment(DataClassJsonMixin):
@@ -28,15 +41,18 @@ class AddedComment(DataClassJsonMixin):
28
41
  comment: str
29
42
  """コメントの中身"""
30
43
 
31
- data: dict[str, Any] | None
44
+ data: dict[str, Any] | None = None
32
45
  """コメントを付与する位置や区間"""
33
46
 
34
- annotation_id: str | None
47
+ annotation_id: str | None = None
35
48
  """コメントに紐付けるアノテーションID"""
36
49
 
37
- phrases: list[str] | None
50
+ phrases: list[str] | None = None
38
51
  """参照している定型指摘ID"""
39
52
 
53
+ comment_id: str | None = None
54
+ """コメントID。省略時はUUIDv4が自動生成される。"""
55
+
40
56
 
41
57
  AddedCommentsForTask = dict[str, list[AddedComment]]
42
58
  """
@@ -51,6 +67,71 @@ keyはtask_id
51
67
  """
52
68
 
53
69
 
70
+ def convert_annotation_body_to_inspection_data( # noqa: PLR0911
71
+ annotation_body: dict[str, Any],
72
+ annotation_type: str,
73
+ input_data_type: InputDataType,
74
+ ) -> dict[str, Any]:
75
+ """
76
+ アノテーションのbody部分を、検査コメントの座標情報(`InspectionData`形式)に変換する。
77
+
78
+ Args:
79
+ annotation_body: Annotationの座標情報。AnnotationDetailContentOutputのdict形式
80
+ annotation_type: アノテーションタイプ。`SUPPORTED_ANNOTATION_TYPES_FOR_INSPECTION_DATA`のいずれかを指定すること。
81
+ input_data_type: プロジェクトの入力データタイプ。
82
+
83
+ Returns:
84
+ 検査コメントの座標情報(`InspectionData`形式)
85
+ """
86
+ match input_data_type:
87
+ case InputDataType.MOVIE:
88
+ match annotation_type:
89
+ case "range":
90
+ # 区間の開始位置をTime形式に変換
91
+ return {"start": annotation_body["data"]["begin"], "end": annotation_body["data"]["end"], "_type": "Time"}
92
+ case _:
93
+ # "Classification"など、Time形式以外のアノテーションタイプ
94
+ return {"start": 0, "end": 100, "_type": "Time"}
95
+
96
+ case InputDataType.CUSTOM:
97
+ match annotation_type:
98
+ case "user_bounding_box":
99
+ # 3次元バウンディングボックスの場合、data.dataの文字列をパースして返す
100
+ return {"data": annotation_body["data"]["data"], "_type": "Custom"}
101
+ case _:
102
+ # セグメントなど
103
+ return {
104
+ "data": '{"kind": "CUBOID", "shape": {"dimensions": {"width": 1.0, "height": 1.0, "depth": 1.0}, "location": {"x": 0.0, "y": 0.0, "z": 0.0}, "rotation": {"x": 0.0, "y": 0.0, "z": 0.0}, "direction": {"front": {"x": 1.0, "y": 0.0, "z": 0.0}, "up": {"x": 0.0, "y": 0.0, "z": 1.0}}}, "version": "2"}', # noqa: E501
105
+ "_type": "Custom",
106
+ }
107
+
108
+ case InputDataType.IMAGE:
109
+ match annotation_type:
110
+ case "bounding_box":
111
+ # 中心点を計算
112
+ left_top = annotation_body["data"]["left_top"]
113
+ right_bottom = annotation_body["data"]["right_bottom"]
114
+ center_x = (left_top["x"] + right_bottom["x"]) / 2
115
+ center_y = (left_top["y"] + right_bottom["y"]) / 2
116
+ return {"x": center_x, "y": center_y, "_type": "Point"}
117
+ case "polygon" | "polyline":
118
+ # 先頭の点を取得
119
+ points = annotation_body["data"]["points"]
120
+ assert len(points) > 0
121
+ first_point = points[0]
122
+ return {"x": first_point["x"], "y": first_point["y"], "_type": "Point"}
123
+ case "point":
124
+ # 点の形式に変換(pointキーから取り出す)
125
+ point = annotation_body["data"]["point"]
126
+ return {"x": point["x"], "y": point["y"], "_type": "Point"}
127
+ case "range":
128
+ # 区間の開始位置をTime形式に変換
129
+ return {"start": annotation_body["data"]["begin"], "end": annotation_body["data"]["end"], "_type": "Time"}
130
+ case _:
131
+ # 塗りつぶしや分類など
132
+ return {"x": 0, "y": 0, "_type": "Point"}
133
+
134
+
54
135
  class PutCommentMain(CommandLineWithConfirm):
55
136
  def __init__(self, service: annofabapi.Resource, project_id: str, comment_type: CommentType, all_yes: bool = False) -> None: # noqa: FBT001, FBT002
56
137
  self.service = service
@@ -60,30 +141,68 @@ class PutCommentMain(CommandLineWithConfirm):
60
141
  self.comment_type = comment_type
61
142
  self.comment_type_name = get_comment_type_name(comment_type)
62
143
 
144
+ # プロジェクト情報を取得
145
+ project, _ = self.service.api.get_project(self.project_id)
146
+ self.input_data_type = InputDataType(project["input_data_type"])
147
+
148
+ # アノテーション仕様を取得
149
+ annotation_specs, _ = self.service.api.get_annotation_specs(self.project_id, query_params={"v": "3"})
150
+ self.dict_label_id_annotation_type = {label["label_id"]: label["annotation_type"] for label in annotation_specs["labels"]}
151
+
63
152
  CommandLineWithConfirm.__init__(self, all_yes)
64
153
 
65
154
  def _create_request_body(self, task: dict[str, Any], input_data_id: str, comments: list[AddedComment]) -> list[dict[str, Any]]:
66
155
  """batch_update_comments に渡すリクエストボディを作成する。"""
156
+ task_id = task["task_id"]
157
+
158
+ # annotation_idが指定されているがdataがNoneのコメントがあるか確認
159
+ need_annotation_data = any(c.annotation_id is not None and c.data is None for c in comments)
160
+
161
+ dict_annotation_id_label_id: dict[str, str] = {}
162
+ dict_annotation_id_data: dict[str, dict[str, Any]] = {}
67
163
 
68
- def _create_dict_annotation_id() -> dict[str, str]:
69
- content, _ = self.service.api.get_editor_annotation(self.project_id, task["task_id"], input_data_id, query_params={"v": "2"})
70
- details = content["details"]
71
- return {e["annotation_id"]: e["label_id"] for e in details}
164
+ if need_annotation_data:
165
+ # アノテーション詳細を取得
166
+ editor_annotation, _ = self.service.api.get_editor_annotation(self.project_id, task_id, input_data_id, query_params={"v": "2"})
167
+ details = editor_annotation["details"]
72
168
 
73
- dict_annotation_id_label_id = _create_dict_annotation_id()
169
+ for detail in details:
170
+ annotation_id = detail["annotation_id"]
171
+ label_id = detail["label_id"]
172
+ dict_annotation_id_label_id[annotation_id] = label_id
173
+
174
+ # annotation_typeを取得
175
+ if label_id in self.dict_label_id_annotation_type:
176
+ # アノテーション仕様に存在しないラベルを使っているアノテーション(アノテーションを作成してから仕様を変更した場合)もあるので、判定する
177
+ annotation_type = self.dict_label_id_annotation_type[label_id]
178
+ dict_annotation_id_data[annotation_id] = convert_annotation_body_to_inspection_data(detail["body"], annotation_type, input_data_type=self.input_data_type)
179
+ else:
180
+ # annotation_idからlabel_idを取得するためだけにAPIを呼ぶ
181
+ editor_annotation, _ = self.service.api.get_editor_annotation(self.project_id, task_id, input_data_id, query_params={"v": "2"})
182
+ details = editor_annotation["details"]
183
+ dict_annotation_id_label_id = {e["annotation_id"]: e["label_id"] for e in details}
74
184
 
75
- def _convert(comment: AddedComment) -> dict[str, Any]:
185
+ def _convert(comment: AddedComment) -> dict[str, Any] | None:
186
+ data = comment.data
187
+ annotation_id = comment.annotation_id
188
+
189
+ # dataがNoneでannotation_idが指定されている場合、dataを補完
190
+ if data is None:
191
+ assert annotation_id is not None
192
+ data = dict_annotation_id_data[annotation_id]
193
+
194
+ assert data is not None
76
195
  return {
77
- "comment_id": str(uuid.uuid4()),
196
+ "comment_id": comment.comment_id if comment.comment_id is not None else str(uuid.uuid4()),
78
197
  "phase": task["phase"],
79
198
  "phase_stage": task["phase_stage"],
80
199
  "account_id": self.service.api.account_id,
81
200
  "comment_type": self.comment_type.value,
82
201
  "comment": comment.comment,
83
202
  "comment_node": {
84
- "data": comment.data,
85
- "annotation_id": comment.annotation_id,
86
- "label_id": dict_annotation_id_label_id.get(comment.annotation_id) if comment.annotation_id is not None else None,
203
+ "data": data,
204
+ "annotation_id": annotation_id,
205
+ "label_id": dict_annotation_id_label_id.get(annotation_id) if annotation_id is not None else None,
87
206
  "status": "open",
88
207
  "_type": "Root",
89
208
  },
@@ -91,7 +210,8 @@ class PutCommentMain(CommandLineWithConfirm):
91
210
  "_type": "Put",
92
211
  }
93
212
 
94
- return [_convert(e) for e in comments]
213
+ converted_comments = [_convert(e) for e in comments]
214
+ return [c for c in converted_comments if c is not None]
95
215
 
96
216
  def change_to_working_status(self, project_id: str, task: dict[str, Any]) -> dict[str, Any]:
97
217
  """
@@ -129,8 +249,8 @@ class PutCommentMain(CommandLineWithConfirm):
129
249
  logger.warning(f"task_id='{task_id}' :: フェーズが検査/受入でないため検査コメントを付与できません。 :: task_phase='{task['phase']}'")
130
250
  return False
131
251
 
132
- if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.WORKING.value, TaskStatus.BREAK.value]:
133
- logger.warning(f"task_id='{task_id}' :: タスクの状態が未着手,作業中,休憩中 以外の状態なので、コメントを付与できません。 :: task_status='{task['status']}'")
252
+ if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.BREAK.value]:
253
+ logger.warning(f"task_id='{task_id}' :: タスクの状態が未着手,休憩中 以外の状態なので、コメントを付与できません。 :: task_status='{task['status']}'")
134
254
  return False
135
255
  return True
136
256
 
@@ -248,7 +368,7 @@ def convert_cli_comments(dict_comments: dict[str, Any], *, comment_type: Comment
248
368
  comment: str
249
369
  """コメントの中身"""
250
370
 
251
- data: dict[str, Any]
371
+ data: dict[str, Any] | None = None
252
372
  """コメントを付与する位置や区間"""
253
373
 
254
374
  annotation_id: str | None = None
@@ -257,6 +377,9 @@ def convert_cli_comments(dict_comments: dict[str, Any], *, comment_type: Comment
257
377
  phrases: list[str] | None = None
258
378
  """参照している定型指摘ID"""
259
379
 
380
+ comment_id: str | None = None
381
+ """コメントID。省略時はUUIDv4が自動生成される。"""
382
+
260
383
  @dataclass
261
384
  class AddedOnholdComment(DataClassJsonMixin):
262
385
  """
@@ -269,13 +392,16 @@ def convert_cli_comments(dict_comments: dict[str, Any], *, comment_type: Comment
269
392
  annotation_id: str | None = None
270
393
  """コメントに紐付けるアノテーションID"""
271
394
 
395
+ comment_id: str | None = None
396
+ """コメントID。省略時はUUIDv4が自動生成される。"""
397
+
272
398
  def convert_inspection_comment(comment: dict[str, Any]) -> AddedComment:
273
399
  tmp = AddedInspectionComment.from_dict(comment)
274
- return AddedComment(comment=tmp.comment, data=tmp.data, annotation_id=tmp.annotation_id, phrases=tmp.phrases)
400
+ return AddedComment(comment=tmp.comment, data=tmp.data, annotation_id=tmp.annotation_id, phrases=tmp.phrases, comment_id=tmp.comment_id)
275
401
 
276
402
  def convert_onhold_comment(comment: dict[str, Any]) -> AddedComment:
277
403
  tmp = AddedOnholdComment.from_dict(comment)
278
- return AddedComment(comment=tmp.comment, annotation_id=tmp.annotation_id, data=None, phrases=None)
404
+ return AddedComment(comment=tmp.comment, annotation_id=tmp.annotation_id, data=None, phrases=None, comment_id=tmp.comment_id)
279
405
 
280
406
  if comment_type == CommentType.INSPECTION:
281
407
  func_convert = convert_inspection_comment
@@ -31,6 +31,9 @@ class AddedSimpleComment:
31
31
  phrases: list[str] | None = None
32
32
  """参照している定型指摘ID"""
33
33
 
34
+ comment_id: str | None = None
35
+ """コメントID。省略時はUUIDv4が自動生成される。"""
36
+
34
37
 
35
38
  class PutCommentSimplyMain(CommandLineWithConfirm):
36
39
  def __init__(self, service: annofabapi.Resource, project_id: str, comment_type: CommentType, all_yes: bool = False) -> None: # noqa: FBT001, FBT002
@@ -49,7 +52,7 @@ class PutCommentSimplyMain(CommandLineWithConfirm):
49
52
  def _convert(comment: AddedSimpleComment) -> dict[str, Any]:
50
53
  return {
51
54
  "comment": comment.comment,
52
- "comment_id": str(uuid.uuid4()),
55
+ "comment_id": comment.comment_id if comment.comment_id is not None else str(uuid.uuid4()),
53
56
  "phase": task["phase"],
54
57
  "phase_stage": task["phase_stage"],
55
58
  "comment_type": self.comment_type.value,
@@ -90,11 +93,11 @@ class PutCommentSimplyMain(CommandLineWithConfirm):
90
93
 
91
94
  if self.comment_type == CommentType.INSPECTION: # noqa: SIM102
92
95
  if task["phase"] == TaskPhase.ANNOTATION.value:
93
- logger.warning(f"task_id='{task_id}': 教師付フェーズなので、検査コメントを付与できません。")
96
+ logger.warning(f"task_id='{task_id}' :: フェーズが検査/受入でないため検査コメントを付与できません。 :: task_phase='{task['phase']}'")
94
97
  return False
95
98
 
96
- if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.WORKING.value, TaskStatus.BREAK.value]:
97
- logger.warning(f"task_id='{task_id}' : タスクの状態が未着手,作業中,休憩中 以外の状態なので、コメントを付与できません。(task_status='{task['status']}'")
99
+ if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.BREAK.value]:
100
+ logger.warning(f"task_id='{task_id}' :: タスクの状態が未着手,休憩中 以外の状態なので、コメントを付与できません。 :: task_status='{task['status']}'")
98
101
  return False
99
102
  return True
100
103
 
@@ -68,7 +68,11 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
68
68
  parser.add_argument(
69
69
  "--json",
70
70
  type=str,
71
- help=(f"付与する検査コメントの内容をJSON形式で指定してください。``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n\n(ex) ``{json.dumps(SAMPLE_JSON, ensure_ascii=False)}``"),
71
+ help=(
72
+ f"付与する検査コメントの内容をJSON形式で指定してください。``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n\n"
73
+ f"各コメントには ``comment_id`` を指定することができます。省略した場合は自動的にUUIDv4が生成されます。\n\n"
74
+ f"(ex) ``{json.dumps(SAMPLE_JSON, ensure_ascii=False)}``"
75
+ ),
72
76
  )
73
77
 
74
78
  parser.add_argument(
@@ -69,7 +69,11 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
69
69
  parser.add_argument(
70
70
  "--json",
71
71
  type=str,
72
- help=(f"付与する保留コメントの情報をJSON形式で指定してください。``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n\n(ex) ``{json.dumps(SAMPLE_JSON, ensure_ascii=False)}``"),
72
+ help=(
73
+ f"付与する保留コメントの情報をJSON形式で指定してください。``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n\n"
74
+ f"各コメントには ``comment_id`` を指定することができます。省略した場合は自動的にUUIDv4が生成されます。\n\n"
75
+ f"(ex) ``{json.dumps(SAMPLE_JSON, ensure_ascii=False)}``"
76
+ ),
73
77
  )
74
78
 
75
79
  parser.add_argument(
@@ -38,9 +38,9 @@ def _create_dict_tasks_by_input_data_id(task_list: list[dict[str, Any]]) -> dict
38
38
  for input_data_index, input_data_id in enumerate(task["input_data_id_list"]):
39
39
  new_task = {
40
40
  "task_id": task["task_id"],
41
- "task_status": task["status"],
42
41
  "task_phase": task["phase"],
43
42
  "task_phase_stage": task["phase_stage"],
43
+ "task_status": task["status"],
44
44
  "frame_no": input_data_index + 1,
45
45
  }
46
46
  result[input_data_id].append(new_task)
@@ -70,7 +70,7 @@ def create_df_input_data_with_merged_task(input_data_list: list[dict[str, Any]])
70
70
  """
71
71
 
72
72
  def get_columns(columns: pandas.Index) -> list[str]:
73
- task_columns = ["task_id", "task_status", "task_phase", "task_phase_stage", "frame_no"]
73
+ task_columns = ["task_id", "task_phase", "task_phase_stage", "task_status", "frame_no"]
74
74
  new_columns = columns.drop(task_columns)
75
75
  return list(new_columns) + task_columns
76
76
 
@@ -93,7 +93,7 @@ def create_df_input_data_with_merged_task(input_data_list: list[dict[str, Any]])
93
93
  # ネストしたオブジェクトを`system_metadata.input_duration`のような列名でアクセスできるようにするため
94
94
  df_input_data = pandas.json_normalize(new_input_data_list)
95
95
 
96
- for column in ["task_id", "task_status", "task_phase", "task_phase_stage", "frame_no"]:
96
+ for column in ["task_id", "task_phase", "task_phase_stage", "task_status", "frame_no"]:
97
97
  if column not in df_input_data.columns:
98
98
  df_input_data[column] = pandas.NA
99
99