Skip to content

Commit 76b9301

Browse files
Narek MkhitaryanNarek Mkhitaryan
authored andcommitted
updated set_scores logic
1 parent 6fc224c commit 76b9301

File tree

7 files changed

+229
-46
lines changed

7 files changed

+229
-46
lines changed

src/superannotate/lib/app/interface/sdk_interface.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,11 @@ def get_user_scores(
758758
]
759759
"""
760760
project, folder = self.controller.get_project_folder(project)
761+
if project.type != ProjectType.MULTIMODAL:
762+
raise AppException(
763+
"This function is only supported for Multimodal projects."
764+
)
765+
761766
item = self.controller.get_item(project=project, folder=folder, item=item)
762767
response = BaseSerializer.serialize_iterable(
763768
self.controller.work_management.get_user_scores(
@@ -789,7 +794,7 @@ def set_user_scores(
789794
:type scored_user: str
790795
791796
:param scores: A list of dictionaries containing the following key-value pairs:
792-
* **name** (*str*): The name of the score (required).
797+
* **component_id** (*str*): The component_id of the score (required).
793798
* **value** (*Any*): The score value (required).
794799
* **weight** (*Union[float, int]*, optional): The weight of the score. Defaults to `1` if not provided.
795800
@@ -798,7 +803,7 @@ def set_user_scores(
798803
799804
scores = [
800805
{
801-
"name": "Speed", # str (required)
806+
"component_id": "<component_id_for_score>", # str (required)
802807
"value": 90, # Any (required)
803808
"weight": 1 # Union[float, int] (optional, defaults to 1.0 if not provided)
804809
}
@@ -813,20 +818,27 @@ def set_user_scores(
813818
item_=12345,
814819
scored_user="example@superannotate.com",
815820
scores=[
816-
{"name": "Speed", "value": 90},
817-
{"name": "Accuracy", "value": 9, "weight": 4.0},
818-
{"name": "Attention to Detail", "value": None, "weight": None},
821+
{"component_id": "r_kfrp3n", "value": 90},
822+
{"component_id": "h_jbrp4v", "value": 9, "weight": 4.0},
823+
{"component_id": "m_kf8pss", "value": None, "weight": None},
819824
]
820825
)
821826
822827
"""
823828
project, folder = self.controller.get_project_folder(project)
829+
if project.type != ProjectType.MULTIMODAL:
830+
raise AppException(
831+
"This function is only supported for Multimodal projects."
832+
)
824833
item = self.controller.get_item(project=project, folder=folder, item=item)
834+
editor_template = self.controller.projects.get_editor_template(project.id)
835+
components = editor_template.get("components", [])
825836
self.controller.work_management.set_user_scores(
826837
project=project,
827838
item=item,
828839
scored_user=scored_user,
829840
scores=scores,
841+
components=components,
830842
)
831843
logger.info("Scores successfully set.")
832844

@@ -879,7 +891,7 @@ def retrieve_context(
879891
"This function is only supported for Multimodal projects."
880892
)
881893

882-
editor_template = self.controller.projects.get_editor_template(project)
894+
editor_template = self.controller.projects.get_editor_template(project.id)
883895
components = editor_template.get("components", [])
884896

885897
_found, _context = retrieve_context(components, component_id)

src/superannotate/lib/core/entities/work_managament.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ class ScoreEntity(TimedBaseModel):
184184

185185

186186
class ScorePayloadEntity(BaseModel):
187-
name: str
187+
component_id: str
188188
value: Any
189189
weight: Optional[Union[float, int]] = 1.0
190190

src/superannotate/lib/core/serviceproviders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ def attach_editor_template(
234234

235235
@abstractmethod
236236
def get_editor_template(
237-
self, team: entities.TeamEntity, project: entities.ProjectEntity
237+
self, organization_id: str, project_id: int
238238
) -> ServiceResponse:
239239
raise NotImplementedError
240240

src/superannotate/lib/infrastructure/controller.py

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import copy
12
import io
23
import logging
34
import os
@@ -307,9 +308,7 @@ def get_user_scores(
307308
return scores
308309

309310
@staticmethod
310-
def _validate_scores(
311-
scores: List[dict], all_score_names: List[str]
312-
) -> List[ScorePayloadEntity]:
311+
def _validate_scores(scores: List[dict]) -> List[ScorePayloadEntity]:
313312
score_objects: List[ScorePayloadEntity] = []
314313

315314
for s in scores:
@@ -322,53 +321,79 @@ def _validate_scores(
322321
except Exception:
323322
raise AppException("Invalid Scores.")
324323

325-
# validate provided score names
326-
provided_score_names = [s.name for s in score_objects]
327-
if set(provided_score_names) - set(all_score_names):
328-
raise AppException("Please provide valid score names.")
329-
330-
names = [score.name for score in score_objects]
331-
if len(names) != len(set(names)):
332-
raise AppException("Invalid Scores.")
324+
component_ids = [score.component_id for score in score_objects]
325+
if len(component_ids) != len(set(component_ids)):
326+
raise AppException("Component IDs in scores data must be unique.")
333327
return score_objects
334328

329+
@staticmethod
330+
def retrieve_scores(
331+
components: List[dict], score_component_ids: List[str]
332+
) -> Dict[str, Dict]:
333+
score_component_ids = copy.copy(score_component_ids)
334+
found_scores = {}
335+
try:
336+
337+
def _retrieve_score_recursive(
338+
all_components: List[dict], component_ids: List[str]
339+
):
340+
for component in all_components:
341+
if "children" in component:
342+
_retrieve_score_recursive(component["children"], component_ids)
343+
if "scoring" in component and component["id"] in component_ids:
344+
component_ids.remove(component["id"])
345+
found_scores[component["id"]] = {
346+
"score_id": component["scoring"]["id"],
347+
"user_role_name": component["scoring"]["role"]["name"],
348+
"user_role": component["scoring"]["role"]["id"],
349+
}
350+
351+
_retrieve_score_recursive(components, score_component_ids)
352+
except KeyError:
353+
raise AppException("An error occurred while parsing the editor template.")
354+
return found_scores
355+
335356
def set_user_scores(
336357
self,
337358
project: ProjectEntity,
338359
item: BaseItemEntity,
339360
scored_user: str,
340361
scores: List[Dict[str, Any]],
362+
components: List[dict],
341363
):
342-
users = self.list_users(project=project, email=scored_user)
364+
users = self.list_users(email=scored_user)
343365
if not users:
344366
raise AppException("User not found.")
345367
user = users[0]
346-
role_id = user.role
347-
role_name = self.service_provider.get_role_name(project, int(role_id))
348-
349-
score_name_field_options_map = {
350-
s.name: s for s in self.service_provider.work_management.list_scores().data
351-
}
352368

353369
# get validate scores
354-
scores: List[ScorePayloadEntity] = self._validate_scores(
355-
scores, list(score_name_field_options_map.keys())
370+
scores: List[ScorePayloadEntity] = self._validate_scores(scores)
371+
372+
provided_score_component_ids = [s.component_id for s in scores]
373+
component_id_score_data_map = self.retrieve_scores(
374+
components, provided_score_component_ids
356375
)
357376

358-
scores_to_create: List[dict] = []
377+
if len(component_id_score_data_map) != len(scores):
378+
raise AppException("Invalid component_id provided")
379+
380+
scores_to_set: List[dict] = []
359381
for s in scores:
360-
score_to_create = {
382+
score_data = {
361383
"item_id": item.id,
362-
"score_id": score_name_field_options_map[s.name].id,
363-
"user_role_name": role_name,
364-
"user_role": role_id,
384+
"score_id": component_id_score_data_map[s.component_id]["score_id"],
385+
"user_role_name": component_id_score_data_map[s.component_id][
386+
"user_role_name"
387+
],
388+
"user_role": component_id_score_data_map[s.component_id]["user_role"],
365389
"user_id": user.email,
366390
"value": s.value,
367391
"weight": s.weight,
392+
"component_id": s.component_id,
368393
}
369-
scores_to_create.append(score_to_create)
394+
scores_to_set.append(score_data)
370395
res = self.service_provider.telemetry_scoring.set_score_values(
371-
project_id=project.id, data=scores_to_create
396+
project_id=project.id, data=scores_to_set
372397
)
373398
if res.status_code == 400:
374399
res.res_error = "Please provide valid score values."
@@ -533,9 +558,10 @@ def upload_priority_scores(
533558
)
534559
return use_case.execute()
535560

536-
def get_editor_template(self, project: ProjectEntity) -> dict:
561+
@timed_lru_cache(seconds=5)
562+
def get_editor_template(self, project_id: int) -> dict:
537563
response = self.service_provider.projects.get_editor_template(
538-
team=self._team, project=project
564+
organization_id=self._team.owner_id, project_id=project_id
539565
)
540566
response.raise_for_status()
541567
return response.data

src/superannotate/lib/infrastructure/services/project.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,10 @@ def attach_editor_template(
4949
url, "post", data=template, content_type=ServiceResponse, params=params
5050
)
5151

52-
def get_editor_template(
53-
self, team: entities.TeamEntity, project: entities.ProjectEntity
54-
) -> bool:
55-
url = self.URL_EDITOR_TEMPLATE.format(project_id=project.id)
52+
def get_editor_template(self, organization_id: str, project_id: int) -> bool:
53+
url = self.URL_EDITOR_TEMPLATE.format(project_id=project_id)
5654
params = {
57-
"organization_id": team.owner_id,
55+
"organization_id": organization_id,
5856
}
5957
return self.client.request(
6058
url, "get", content_type=ServiceResponse, params=params
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
{
2+
"components": [
3+
{
4+
"id": "component_id_0",
5+
"type": "select",
6+
"permissions": [],
7+
"hasTooltip": false,
8+
"label": "Select",
9+
"isRequired": false,
10+
"value": [],
11+
"exclude": false,
12+
"disablePasting": false,
13+
"options": [
14+
{
15+
"value": "Partially complete, needs review",
16+
"checked": false
17+
},
18+
{
19+
"value": "Incomplete",
20+
"checked": false
21+
},
22+
{
23+
"value": "Complete",
24+
"checked": false
25+
},
26+
{
27+
"value": "4",
28+
"checked": false
29+
}
30+
],
31+
"isMultiselect": true,
32+
"placeholder": "Select"
33+
},
34+
{
35+
"id": "component_id_1",
36+
"type": "input",
37+
"permissions": [],
38+
"hasTooltip": false,
39+
"label": "Text input",
40+
"isRequired": false,
41+
"value": "",
42+
"exclude": false,
43+
"disablePasting": false,
44+
"placeholder": "Placeholder",
45+
"min": 0,
46+
"max": 300
47+
},
48+
{
49+
"id": "component_id_2",
50+
"type": "number",
51+
"permissions": [],
52+
"hasTooltip": false,
53+
"label": "Number",
54+
"isRequired": false,
55+
"value": null,
56+
"exclude": false,
57+
"disablePasting": false,
58+
"min": null,
59+
"max": null,
60+
"step": 1
61+
},
62+
{
63+
"id": "r_34k7k7",
64+
"type": "rating",
65+
"permissions": [],
66+
"hasTooltip": false,
67+
"scoring": {
68+
"id": null,
69+
"role": {
70+
"id": 1,
71+
"name": "Annotator"
72+
},
73+
"name": "SDK-my-score-1"
74+
},
75+
"label": "SDK-my-score-1",
76+
"isRequired": false,
77+
"value": null,
78+
"exclude": false,
79+
"disablePasting": false,
80+
"numberOfStars": 10
81+
},
82+
{
83+
"id": "r_ioc7wd",
84+
"type": "number",
85+
"permissions": [],
86+
"hasTooltip": false,
87+
"scoring": {
88+
"id": null,
89+
"role": {
90+
"id": 1,
91+
"name": "Annotator"
92+
},
93+
"name": "SDK-my-score-2"
94+
},
95+
"label": "SDK-my-score-2",
96+
"isRequired": false,
97+
"value": null,
98+
"exclude": false,
99+
"disablePasting": false,
100+
"min": 1,
101+
"max": 100,
102+
"step": 1
103+
},
104+
{
105+
"id": "r_tcof7o",
106+
"type": "radio",
107+
"permissions": [],
108+
"hasTooltip": false,
109+
"scoring": {
110+
"id": null,
111+
"role": {
112+
"id": 1,
113+
"name": "Annotator"
114+
},
115+
"name": "SDK-my-score-3"
116+
},
117+
"label": "SDK-my-score-3",
118+
"isRequired": false,
119+
"value": "",
120+
"exclude": false,
121+
"disablePasting": false,
122+
"options": [
123+
{
124+
"value": "1",
125+
"checked": null
126+
},
127+
{
128+
"value": "2",
129+
"checked": null
130+
},
131+
{
132+
"value": "3",
133+
"checked": null
134+
}
135+
],
136+
"layout": "column"
137+
}
138+
],
139+
"code": [
140+
[
141+
"__init__",
142+
"from typing import List, Union\n# import requests.asyncs as requests\nimport requests\nimport sa\n\nnumber_component_id_2 = ['component_id_2']\nselect_component_id_0 = ['component_id_0']\ninput_component_id_1 = ['component_id_1']\n\ndef before_save_hook(old_status: str, new_status: str) -> bool:\n # Your code goes here\n return\n\ndef on_saved_hook():\n # Your code goes here\n return\n\ndef before_status_change_hook(old_status: str, new_status: str) -> bool:\n # Your code goes here\n return\n\ndef on_status_changed_hook(old_status: str, new_status: str):\n # Your code goes here\n return\n\ndef post_hook():\n # Your code goes here\n return\n\ndef on_component_id_2_change(path: List[Union[str, int]], value):\n # The path is a list of strings and integers, the length of which is always an odd number and not less than 1.\n # The last value is the identifier of the form element and the pairs preceding it are\n # the group identifiers and the subgroup index, respectively\n # value is current value of the form element\n\n # Your code goes here\n return\n\ndef on_component_id_0_change(path: List[Union[str, int]], value):\n # The path is a list of strings and integers, the length of which is always an odd number and not less than 1.\n # The last value is the identifier of the form element and the pairs preceding it are\n # the group identifiers and the subgroup index, respectively\n # value is current value of the form element\n\n # Your code goes here\n return\n\ndef on_component_id_1_change(path: List[Union[str, int]], value):\n # The path is a list of strings and integers, the length of which is always an odd number and not less than 1.\n # The last value is the identifier of the form element and the pairs preceding it are\n # the group identifiers and the subgroup index, respectively\n # value is current value of the form element\n\n # Your code goes here\n return\n"
143+
]
144+
],
145+
"environments": []
146+
}

0 commit comments

Comments
 (0)