aboutsummaryrefslogtreecommitdiff
path: root/generalresearch/models/thl/profiling/upk_question_answer.py
blob: 28b9f277bf729d8d71b9ef20bbb444bd7eb38d69 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
from datetime import datetime, timezone
from typing import Optional, Union, Dict
from uuid import uuid4

from pydantic import (
    BaseModel,
    ConfigDict,
    Field,
    PositiveInt,
    model_validator,
    computed_field,
)
from typing_extensions import Self

from generalresearch.models import MAX_INT32
from generalresearch.models.custom_types import (
    UUIDStr,
    AwareDatetimeISO,
    CountryISOLike,
)
from generalresearch.models.thl.profiling.upk_property import (
    PropertyType,
    Cardinality,
)


class UpkQuestionAnswer(BaseModel):
    """ """

    model_config = ConfigDict(populate_by_name=True)

    user_id: PositiveInt = Field(lt=MAX_INT32)

    question_id: Optional[UUIDStr] = Field(
        examples=[uuid4().hex],
        description="The ID of the question that was asked in order to determine this",
        default=None,
    )
    session_id: Optional[UUIDStr] = Field(
        examples=[uuid4().hex],
        description="The thl_session in which the question was asked",
        default=None,
    )

    property_id: UUIDStr = Field(examples=[uuid4().hex])

    property_label: str = Field(max_length=255, examples=["educational_attainment"])

    prop_type: PropertyType = Field(
        default=PropertyType.UPK_ITEM,
        description=PropertyType.as_openapi_with_value_descriptions(),
    )

    cardinality: Cardinality = Field(
        default=Cardinality.ZERO_OR_ONE,
        description=Cardinality.as_openapi_with_value_descriptions(),
    )

    # ISO 3166-1 alpha-2 (two-letter codes, lowercase)
    country_iso: CountryISOLike = Field()

    created: AwareDatetimeISO = Field(
        default_factory=lambda: datetime.now(tz=timezone.utc)
    )

    # If the property is PropertyType.UPK_ITEM, it should have an item (and no value).
    # If the property is UPK_NUMERICAL or UPK_TEXT, it'll have a value (and no item).
    item_id: Optional[UUIDStr] = Field(
        default=None, examples=["497b1fedec464151b063cd5367643ffa"]
    )
    item_label: Optional[str] = Field(
        default=None, max_length=255, examples=["high_school_completion"]
    )
    value_text: Optional[str] = Field(
        default=None,
        max_length=1024,
    )
    value_num: Optional[float] = Field(
        default=None,
    )

    @computed_field
    @property
    def value(self) -> Optional[Union[str, float]]:
        if self.prop_type == PropertyType.UPK_ITEM:
            return self.item_label
        elif self.prop_type == PropertyType.UPK_TEXT:
            return self.value_text
        elif self.prop_type == PropertyType.UPK_NUMERICAL:
            return self.value_num

    @model_validator(mode="after")
    def check_value_vs_item(self) -> Self:
        if self.prop_type == PropertyType.UPK_ITEM:
            if not self.item_id or not self.item_label:
                raise ValueError("item_id and item_label must be provided for UPK_ITEM")
            if self.value_num is not None or self.value_text is not None:
                raise ValueError("value and value_text must be None for UPK_ITEM")

        elif self.prop_type in {
            PropertyType.UPK_NUMERICAL,
            PropertyType.UPK_TEXT,
        }:
            if self.item_id or self.item_label:
                raise ValueError("item_id and item_label must be None for non-UPK_ITEM")
            if self.prop_type == PropertyType.UPK_NUMERICAL and self.value_num is None:
                raise ValueError("value must be provided for UPK_NUMERICAL")
            if self.prop_type == PropertyType.UPK_TEXT and self.value_text is None:
                raise ValueError("value_text must be provided for UPK_TEXT")

        return self

    def model_dump_mysql(self) -> Dict:
        d = self.model_dump(mode="json")
        d["created"] = self.created
        return d