Skip to content

Commit

Permalink
Merge branch 'develop' into loris-integration-phase-2-pull-develop-da…
Browse files Browse the repository at this point in the history
…6e8cba73b4da8465224c85125b119ec15cbd62
  • Loading branch information
david-montano-metalab committed Oct 18, 2024
2 parents 2f95813 + f896599 commit b4b655b
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 67 deletions.
31 changes: 29 additions & 2 deletions src/apps/activities/domain/conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from pydantic import Field, root_validator, validator

from apps.activities.errors import IncorrectMaxTimeRange, IncorrectMinTimeRange, IncorrectTimeRange
from apps.shared.domain import PublicModel, PublicModelNoExtra


Expand Down Expand Up @@ -152,7 +153,7 @@ def dict(self, *args, **kwargs):


class TimePayload(PublicModel):
type: str | None = None
type: TimePayloadType | None = None
value: datetime.time

def dict(self, *args, **kwargs):
Expand All @@ -163,14 +164,40 @@ def dict(self, *args, **kwargs):

class SingleTimePayload(PublicModel):
time: Optional[datetime.time] = None
max_value: Optional[datetime.time] = None
min_value: Optional[datetime.time] = None

@root_validator(pre=True)
def validate_time(cls, values: Dict[str, Any]) -> Dict[str, Any]:
time_value = values.get("time")
max_time_value = values.get("max_value")
min_time_value = values.get("min_value")

if isinstance(time_value, dict):
values["time"] = cls._dict_to_time(time_value)
elif isinstance(time_value, str):
values["time"] = cls._string_to_time(time_value)
if max_time_value and min_time_value:
if isinstance(max_time_value, dict):
max_time_value = cls._dict_to_time(max_time_value)
elif isinstance(max_time_value, str):
max_time_value = cls._string_to_time(max_time_value)

if isinstance(min_time_value, dict):
min_time_value = cls._dict_to_time(min_time_value)
elif isinstance(min_time_value, str):
min_time_value = cls._string_to_time(min_time_value)

if max_time_value < min_time_value:
raise IncorrectTimeRange()

if min_time_value is not None:
if max_time_value is None:
raise IncorrectMaxTimeRange()
if max_time_value is not None:
if min_time_value is None:
raise IncorrectMinTimeRange()

return values

def dict(self, *args, **kwargs) -> Dict[str, Any]:
Expand Down Expand Up @@ -554,7 +581,7 @@ class EqualToDateCondition(_EqualToDateCondition):


class EqualCondition(_EqualCondition):
payload: ValuePayload | ValueIndexPayload | TimePayload
payload: ValuePayload | ValueIndexPayload | TimePayload | SingleTimePayload


class NotEqualToDateCondition(_NotEqualToDateCondition):
Expand Down
31 changes: 30 additions & 1 deletion src/apps/activities/domain/custom_validation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
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 @@ -19,9 +19,14 @@
IncorrectSectionPrintItemTypeError,
IncorrectSubscaleInsideSubscaleError,
IncorrectSubscaleItemError,
SubscaleDoesNotExist,
SubscaleInsideSubscaleError,
SubscaleItemDoesNotExist,
SubscaleItemScoreError,
SubscaleItemTypeError,
SubscaleItemTypeItemDoesNotExist,
SubscaleNameDoesNotExist,
SubscaleSettingDoesNotExist,
)


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


def validate_subscale_setting_match_reports(report: Score, subscale_setting: SubscaleSetting):
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 @@ -83,6 +105,13 @@ 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")
if not subscale_setting: # report of type score exist then we need a subscale setting
raise SubscaleSettingDoesNotExist()
else:
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 @@ -29,6 +29,11 @@ class CalculationType(enum.StrEnum):
PERCENTAGE = "percentage"


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


class ScoreConditionalLogic(PublicModel):
name: str
id: str
Expand Down Expand Up @@ -59,6 +64,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
43 changes: 43 additions & 0 deletions src/apps/activities/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,14 @@ class IncorrectTimeRange(ValidationError):
message = _("Incorrect timerange")


class IncorrectMinTimeRange(ValidationError):
message = _("Mix timerange was not passed")


class IncorrectMaxTimeRange(ValidationError):
message = _("Max timerange was not passed")


class FlowDoesNotExist(NotFoundError):
message = _("Flow does not exist.")

Expand All @@ -274,3 +282,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
Loading

0 comments on commit b4b655b

Please sign in to comment.