Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use scores or raw scores for report server processing (M2-7933) #1616

Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7c56efb
Modified SubscaleSettings to accept one more argument score_type, tha…
AlejandroCoronadoN Sep 24, 2024
7108b36
Adding new entries to score_and_reports and removing from subscale_se…
AlejandroCoronadoN Sep 30, 2024
15a7d59
scoring_type and subscale_table_data is now passed to ReportServer as…
AlejandroCoronadoN Sep 30, 2024
6edf0c6
Applied ruff formatting and using raw_scores as default value for sco…
AlejandroCoronadoN Sep 30, 2024
ab7334f
Created validation for unliked lookup tables and unexistent lookup ta…
AlejandroCoronadoN Sep 30, 2024
fe953d1
Created unit test first version
AlejandroCoronadoN Oct 1, 2024
bd18694
Created unit test for creating applets with scoring_type and subscale…
AlejandroCoronadoN Oct 1, 2024
24b53c4
Replaced payload data with CamelCase and refcator code according to c…
AlejandroCoronadoN Oct 1, 2024
076ed45
Fixed test_create_applet__activity_with_subscale_settings_and_lookup_…
AlejandroCoronadoN Oct 3, 2024
286c057
Updated scoring_type and scoring_name validations when creating an ap…
AlejandroCoronadoN Oct 7, 2024
e84210e
Moved validations to the validate_score_and_sections function and upd…
AlejandroCoronadoN Oct 7, 2024
bc449be
formated code with ruff
AlejandroCoronadoN Oct 7, 2024
98082ba
Updated create_function section obtaining subscale_scoring and names …
AlejandroCoronadoN Oct 7, 2024
0a179cf
subscale_table_dataruff formatting required type
AlejandroCoronadoN Oct 7, 2024
4a84ddf
Updated unit tests callin new SusbcaleSetting with lookup table refer…
AlejandroCoronadoN Oct 10, 2024
8b47db2
Changed subscale definitions inside test script (imports didn't worked)
AlejandroCoronadoN Oct 10, 2024
5c57ca8
mypy required additional changes and validations for possible empty a…
AlejandroCoronadoN Oct 10, 2024
f44a2a9
Deleted subscale_scoring and subscale_name from create_report, now us…
AlejandroCoronadoN Oct 11, 2024
70d476e
Fixed validatio to execure only validate_subscale_setting_match_repor…
AlejandroCoronadoN Oct 11, 2024
42123ba
Subscale misspelling
AlejandroCoronadoN Oct 11, 2024
66819bb
Applied refactorization to validate_subscale_setting_match_reports
AlejandroCoronadoN Oct 11, 2024
f49830d
Merge branch 'develop' into feature/M2-7933-Use-Scores-Or-Raw-Scores-…
AlejandroCoronadoN Oct 11, 2024
d3c7325
Fixed tests on test_custom_validation that required typing for payloa…
AlejandroCoronadoN Oct 15, 2024
3dbf416
Fixed test_custom_validation tests
AlejandroCoronadoN Oct 15, 2024
47c1e17
Refactor test_create_activity_item_conditional_logic and updated Sing…
AlejandroCoronadoN Oct 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion src/apps/activities/domain/custom_validation.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from apps.activities.domain.conditions import MultiSelectConditionType, SingleSelectConditionType
from apps.activities.domain.response_type_config import PerformanceTaskType, ResponseType
from apps.activities.domain.response_values import PhrasalTemplateFieldType
from apps.activities.domain.scores_reports import ReportType, SubscaleItemType
from apps.activities.domain.scores_reports import ReportType, Score, SubscaleItemType, SubscaleSetting
from apps.activities.errors import (
IncorrectConditionItemError,
IncorrectConditionItemIndexError,
Expand All @@ -20,9 +20,14 @@
IncorrectSectionPrintItemTypeError,
IncorrectSubscaleInsideSubscaleError,
IncorrectSubscaleItemError,
SubscaleDoesNotExist,
SubscaleInsideSubscaleError,
SubscaleItemDoesNotExist,
SubscaleItemScoreError,
SubscaleItemTypeError,
SubscaleItemTypeItemDoesNotExist,
SubscaleNameDoesNotExist,
SubscaleSettingDoesNotExist,
)


Expand Down Expand Up @@ -63,6 +68,25 @@ def validate_item_flow(values: dict):
return values


def validate_subscale_setting_match_reports(report: Score, subscale_setting: SubscaleSetting):
if not subscale_setting: # report of type score exist then we need a subscale setting
raise SubscaleSettingDoesNotExist()
report_subscale_linked = report.subscale_name
subscales = subscale_setting.subscales
if not subscales:
raise SubscaleDoesNotExist()

linked_subscale = next((subscale for subscale in subscales if subscale.name == report_subscale_linked), None)
if not linked_subscale:
raise SubscaleNameDoesNotExist()
elif not linked_subscale.items:
raise SubscaleItemDoesNotExist()
else:
has_non_subscale_items = any(item.type == SubscaleItemType.ITEM for item in linked_subscale.items)
if not has_non_subscale_items:
raise SubscaleItemTypeItemDoesNotExist()


def validate_score_and_sections(values: dict): # noqa: C901
items = values.get("items", [])
item_names = [item.name for item in items]
Expand All @@ -78,6 +102,10 @@ def validate_score_and_sections(values: dict): # noqa: C901

for report in list(scores):
score_item_ids.append(report.id)
if report.scoring_type == "score":
subscale_setting = values.get("subscale_setting")
validate_subscale_setting_match_reports(report, subscale_setting)

# check if all item names are same as values.name
for item in report.items_score:
if item not in item_names:
Expand Down
7 changes: 7 additions & 0 deletions src/apps/activities/domain/scores_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ class CalculationType(str, Enum):
PERCENTAGE = "percentage"


class ScoringType(str, Enum):
SCORE = "score"
RAW_SCORE = "raw_score"


class ScoreConditionalLogic(PublicModel):
name: str
id: str
Expand Down Expand Up @@ -60,6 +65,8 @@ class Score(PublicModel):
message: str | None = None
items_print: list[str] | None = Field(default_factory=list)
conditional_logic: list[ScoreConditionalLogic] | None = None
scoring_type: ScoringType | None = None
subscale_name: str | None = None

@validator("conditional_logic")
def validate_conditional_logic(cls, value, values):
Expand Down
35 changes: 35 additions & 0 deletions src/apps/activities/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,38 @@ class IncorrectPhrasalTemplateItemTypeError(ValidationError):

class IncorrectPhrasalTemplateItemIndexError(ValidationError):
message = _("Invalid item index for activity item inside phrasal template")


class SubscaleIsNotLinked(ValidationError):
message = _("The scoring_type is score but no suscale_name string pased")
code = _("no_subscale_items_exist")


class SubscaleDoesNotExist(ValidationError):
message = _("The scoring_type is score but there are no subscales")
code = _("no_subscale_linked")


class SubscaleNameDoesNotExist(ValidationError):
message = _("The lookup table with the passed name does not exist in subscale settings")
code = _("no_subscale_name_exist")


class SubscaleDataDoesNotExist(ValidationError):
message = _("The scoring_type is score but the subscale data does not exist")
code = _("no_subscaledata_exist")


class SubscaleSettingDoesNotExist(ValidationError):
message = _("The scoring_type is score but there are no subscale settings associated with activity")
code = _("no_subscale_setting_exist")


class SubscaleItemDoesNotExist(ValidationError):
message = _("The linked subscale should contain at least one item")
code = _("no_items_exist")


class SubscaleItemTypeItemDoesNotExist(ValidationError):
message = _("The linked subscale should contain at least one non-subscale type item")
code = _("no_subscale_type_items_exist")
65 changes: 64 additions & 1 deletion src/apps/activities/tests/fixtures/scores_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,30 @@ def score() -> Score:
)


@pytest.fixture
def score_with_subscale() -> Score:
return Score(
type=ReportType.score,
name="testscore type score",
id=SCORE_ID,
calculation_type=CalculationType.SUM,
scoring_type="score",
subscale_name="subscale type score",
)


@pytest.fixture
def score_with_subscale_raw() -> Score:
return Score(
type=ReportType.score,
name="testscore type score",
id=SCORE_ID,
calculation_type=CalculationType.SUM,
scoring_type="raw_score",
subscale_name=None,
)


@pytest.fixture
def section_conditional_logic() -> SectionConditionalLogic:
return SectionConditionalLogic(
Expand Down Expand Up @@ -76,6 +100,15 @@ def scores_and_reports(score: Score, section: Section) -> ScoresAndReports:
)


@pytest.fixture
def scores_and_reports_raw_score(score_with_subscale_raw: Score, section: Section) -> ScoresAndReports:
return ScoresAndReports(
generate_report=True,
show_score_summary=True,
reports=[score_with_subscale_raw, section],
)


@pytest.fixture
def subscale_item() -> SubscaleItem:
return SubscaleItem(name="activity_item_1", type=SubscaleItemType.ITEM)
Expand All @@ -90,6 +123,26 @@ def subscale(subscale_item: SubscaleItem) -> Subscale:
)


@pytest.fixture
def subscale_score_type() -> Subscale:
return Subscale(
name="subscale type score",
scoring=SubscaleCalculationType.AVERAGE,
items=[SubscaleItem(name="subscale_item", type=SubscaleItemType.ITEM)],
)


@pytest.fixture
def scores_and_reports_lookup_scores(
score_with_subscale: Score, section: Section, subscale: Subscale
) -> ScoresAndReports:
return ScoresAndReports(
generate_report=True,
show_score_summary=True,
reports=[score_with_subscale],
)


@pytest.fixture
def subscale_item_type_subscale(subscale: Subscale) -> SubscaleItem:
# Depends on subscalke because name should contain subscale item
Expand All @@ -99,7 +152,9 @@ def subscale_item_type_subscale(subscale: Subscale) -> SubscaleItem:
@pytest.fixture
def subscale_with_item_type_subscale(subscale_item_type_subscale: SubscaleItem) -> Subscale:
return Subscale(
name="subscale type subscale", items=[subscale_item_type_subscale], scoring=SubscaleCalculationType.AVERAGE
name="subscale type subscale",
scoring=SubscaleCalculationType.AVERAGE,
items=[subscale_item_type_subscale],
)


Expand All @@ -111,6 +166,14 @@ def subscale_setting(subscale: Subscale) -> SubscaleSetting:
)


@pytest.fixture
def subscale_setting_score_type(subscale_score_type: Subscale) -> SubscaleSetting:
return SubscaleSetting(
calculate_total_score=SubscaleCalculationType.AVERAGE,
subscales=[subscale_score_type],
)


@pytest.fixture
def subscale_total_score_table() -> list[TotalScoreTable]:
return [
Expand Down
4 changes: 3 additions & 1 deletion src/apps/answers/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -1922,7 +1922,9 @@ async def is_flow_finished(self, submit_id: uuid.UUID, answer_id: uuid.UUID) ->
return self._is_activity_last_in_flow(applet_full, activity_id, flow_id)

async def create_report(
sultanofcardio marked this conversation as resolved.
Show resolved Hide resolved
self, submit_id: uuid.UUID, answer_id: uuid.UUID | None = None
self,
submit_id: uuid.UUID,
answer_id: uuid.UUID | None = None,
) -> ReportServerResponse | None:
answers = await AnswersCRUD(self.answers_session).get_by_submit_id(submit_id, answer_id)
if not answers:
Expand Down
33 changes: 33 additions & 0 deletions src/apps/applets/tests/test_applet_activity_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from apps.activities.domain.scores_reports import (
ScoreConditionalLogic,
ScoresAndReports,
ScoringType,
SectionConditionalLogic,
Subscale,
SubScaleLookupTable,
Expand Down Expand Up @@ -522,6 +523,38 @@ async def test_create_applet__activity_with_subscale_settings_with_subscale_look
result = resp.json()["result"]
assert result["activities"][0]["subscaleSetting"] == sub_setting.dict(by_alias=True)

async def test_create_applet__activity_with_subscale_settings_and_lookup_table_and_score_report_lookup_scoring(
self,
client: TestClient,
applet_minimal_data: AppletCreate,
single_select_item_create_with_score: ActivityItemCreate,
tom: User,
subscale_setting_score_type: SubscaleSetting,
subscale_lookup_table: list[SubScaleLookupTable],
scores_and_reports_lookup_scores: ScoresAndReports,
):
client.login(tom)
data = applet_minimal_data.copy(deep=True)
sub_setting = subscale_setting_score_type.copy(deep=True)

# Update subscale setting with item name and lookup table
sub_setting.subscales[0].items[0].name = single_select_item_create_with_score.name # type: ignore[index]
sub_setting.subscales[0].subscale_table_data = subscale_lookup_table # type: ignore[index]

data.activities[0].items = [single_select_item_create_with_score]
data.activities[0].subscale_setting = sub_setting
data.activities[0].scores_and_reports = scores_and_reports_lookup_scores

# Make the POST request
resp = await client.post(self.applet_create_url.format(owner_id=tom.id), data=data)

# Assertions
assert resp.status_code == http.HTTPStatus.CREATED
result = resp.json()["result"]
assert result["activities"][0]["subscaleSetting"] == sub_setting.dict(by_alias=True)
assert result["activities"][0]["scoresAndReports"]["reports"][0]["scoringType"] == ScoringType.SCORE.value
assert result["activities"][0]["scoresAndReports"]["reports"][0]["subscaleName"] == "subscale type score"

async def test_create_applet__activity_with_subscale_settings_with_invalid_subscale_lookup_table_age(
self,
client: TestClient,
Expand Down
Loading