aboutsummaryrefslogtreecommitdiff
path: root/tests/models/spectrum
diff options
context:
space:
mode:
Diffstat (limited to 'tests/models/spectrum')
-rw-r--r--tests/models/spectrum/__init__.py0
-rw-r--r--tests/models/spectrum/test_question.py216
-rw-r--r--tests/models/spectrum/test_survey.py413
-rw-r--r--tests/models/spectrum/test_survey_manager.py130
4 files changed, 759 insertions, 0 deletions
diff --git a/tests/models/spectrum/__init__.py b/tests/models/spectrum/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/models/spectrum/__init__.py
diff --git a/tests/models/spectrum/test_question.py b/tests/models/spectrum/test_question.py
new file mode 100644
index 0000000..ba118d7
--- /dev/null
+++ b/tests/models/spectrum/test_question.py
@@ -0,0 +1,216 @@
+from datetime import datetime, timezone
+
+from generalresearch.models import Source
+from generalresearch.models.spectrum.question import (
+ SpectrumQuestionOption,
+ SpectrumQuestion,
+ SpectrumQuestionType,
+ SpectrumQuestionClass,
+)
+from generalresearch.models.thl.profiling.upk_question import (
+ UpkQuestion,
+ UpkQuestionSelectorMC,
+ UpkQuestionType,
+ UpkQuestionChoice,
+)
+
+
+class TestSpectrumQuestion:
+
+ def test_parse_from_api_1(self):
+
+ example_1 = {
+ "qualification_code": 213,
+ "text": "My household earns approximately $%%213%% per year",
+ "cat": None,
+ "desc": "Income",
+ "type": 5,
+ "class": 1,
+ "condition_codes": [],
+ "format": {"min": 0, "max": 999999, "regex": "/^([0-9]{1,6})$/i"},
+ "crtd_on": 1502869927688,
+ "mod_on": 1706557247467,
+ }
+ q = SpectrumQuestion.from_api(example_1, "us", "eng")
+
+ expected_q = SpectrumQuestion(
+ question_id="213",
+ country_iso="us",
+ language_iso="eng",
+ question_name="Income",
+ question_text="My household earns approximately $___ per year",
+ question_type=SpectrumQuestionType.TEXT_ENTRY,
+ tags=None,
+ options=None,
+ class_num=SpectrumQuestionClass.CORE,
+ created=datetime(2017, 8, 16, 7, 52, 7, 688000, tzinfo=timezone.utc),
+ is_live=True,
+ source=Source.SPECTRUM,
+ category_id=None,
+ )
+ assert "My household earns approximately $___ per year" == q.question_text
+ assert "213" == q.question_id
+ assert expected_q == q
+ q.to_upk_question()
+ assert "s:213" == q.external_id
+
+ def test_parse_from_api_2(self):
+
+ example_2 = {
+ "qualification_code": 211,
+ "text": "I'm a %%211%%",
+ "cat": None,
+ "desc": "Gender",
+ "type": 1,
+ "class": 1,
+ "condition_codes": [
+ {"id": "111", "text": "Male"},
+ {"id": "112", "text": "Female"},
+ ],
+ "format": {"min": None, "max": None, "regex": ""},
+ "crtd_on": 1502869927688,
+ "mod_on": 1706557249817,
+ }
+ q = SpectrumQuestion.from_api(example_2, "us", "eng")
+ expected_q = SpectrumQuestion(
+ question_id="211",
+ country_iso="us",
+ language_iso="eng",
+ question_name="Gender",
+ question_text="I'm a",
+ question_type=SpectrumQuestionType.SINGLE_SELECT,
+ tags=None,
+ options=[
+ SpectrumQuestionOption(id="111", text="Male", order=0),
+ SpectrumQuestionOption(id="112", text="Female", order=1),
+ ],
+ class_num=SpectrumQuestionClass.CORE,
+ created=datetime(2017, 8, 16, 7, 52, 7, 688000, tzinfo=timezone.utc),
+ is_live=True,
+ source=Source.SPECTRUM,
+ category_id=None,
+ )
+ assert expected_q == q
+ q.to_upk_question()
+
+ def test_parse_from_api_3(self):
+
+ example_3 = {
+ "qualification_code": 220,
+ "text": "My child is a %%230%% %%221%% old %%220%%",
+ "cat": None,
+ "desc": "Child Dependent",
+ "type": 6,
+ "class": 4,
+ "condition_codes": [
+ {"id": "111", "text": "Boy"},
+ {"id": "112", "text": "Girl"},
+ ],
+ "format": {"min": None, "max": None, "regex": ""},
+ "crtd_on": 1502869927688,
+ "mod_on": 1706556781278,
+ }
+ q = SpectrumQuestion.from_api(example_3, "us", "eng")
+ # This fails because the text has variables from other questions in it
+ assert q is None
+
+ def test_parse_from_api_4(self):
+
+ example_4 = {
+ "qualification_code": 1039,
+ "text": "Do you suffer from any of the following ailments or medical conditions? (Select all that apply) "
+ " %%1039%%",
+ "cat": "Ailments, Illness",
+ "desc": "Standard Ailments",
+ "type": 3,
+ "class": 2,
+ "condition_codes": [
+ {"id": "111", "text": "Allergies (Food, Nut, Skin)"},
+ {"id": "999", "text": "None of the above"},
+ {"id": "130", "text": "Other"},
+ {
+ "id": "129",
+ "text": "Women's Health Conditions (Reproductive Issues)",
+ },
+ ],
+ "format": {"min": None, "max": None, "regex": ""},
+ "crtd_on": 1502869927688,
+ "mod_on": 1706557241693,
+ }
+ q = SpectrumQuestion.from_api(example_4, "us", "eng")
+ expected_q = SpectrumQuestion(
+ question_id="1039",
+ country_iso="us",
+ language_iso="eng",
+ question_name="Standard Ailments",
+ question_text="Do you suffer from any of the following ailments or medical conditions? (Select all that "
+ "apply)",
+ question_type=SpectrumQuestionType.MULTI_SELECT,
+ tags="Ailments, Illness",
+ options=[
+ SpectrumQuestionOption(
+ id="111", text="Allergies (Food, Nut, Skin)", order=0
+ ),
+ SpectrumQuestionOption(
+ id="129",
+ text="Women's Health Conditions (Reproductive Issues)",
+ order=1,
+ ),
+ SpectrumQuestionOption(id="130", text="Other", order=2),
+ SpectrumQuestionOption(id="999", text="None of the above", order=3),
+ ],
+ class_num=SpectrumQuestionClass.EXTENDED,
+ created=datetime(2017, 8, 16, 7, 52, 7, 688000, tzinfo=timezone.utc),
+ is_live=True,
+ source=Source.SPECTRUM,
+ category_id=None,
+ )
+ assert expected_q == q
+
+ # todo: we should have something that infers that if the choice text is "None of the above",
+ # then the choice is exclusive
+ u = UpkQuestion(
+ id=None,
+ ext_question_id="s:1039",
+ type=UpkQuestionType.MULTIPLE_CHOICE,
+ selector=UpkQuestionSelectorMC.MULTIPLE_ANSWER,
+ country_iso="us",
+ language_iso="eng",
+ text="Do you suffer from any of the following ailments or medical conditions? (Select all "
+ "that apply)",
+ choices=[
+ UpkQuestionChoice(
+ id="111",
+ text="Allergies (Food, Nut, Skin)",
+ order=0,
+ group=None,
+ exclusive=False,
+ importance=None,
+ ),
+ UpkQuestionChoice(
+ id="129",
+ text="Women's Health Conditions (Reproductive Issues)",
+ order=1,
+ group=None,
+ exclusive=False,
+ importance=None,
+ ),
+ UpkQuestionChoice(
+ id="130",
+ text="Other",
+ order=2,
+ group=None,
+ exclusive=False,
+ importance=None,
+ ),
+ UpkQuestionChoice(
+ id="999",
+ text="None of the above",
+ order=3,
+ group=None,
+ exclusive=False,
+ importance=None,
+ ),
+ ],
+ )
+ assert u == q.to_upk_question()
diff --git a/tests/models/spectrum/test_survey.py b/tests/models/spectrum/test_survey.py
new file mode 100644
index 0000000..65dec60
--- /dev/null
+++ b/tests/models/spectrum/test_survey.py
@@ -0,0 +1,413 @@
+from datetime import timezone, datetime
+from decimal import Decimal
+
+
+class TestSpectrumCondition:
+
+ def test_condition_create(self):
+ from generalresearch.models import LogicalOperator
+ from generalresearch.models.spectrum.survey import (
+ SpectrumCondition,
+ )
+ from generalresearch.models.thl.survey.condition import ConditionValueType
+
+ c = SpectrumCondition.from_api(
+ {
+ "qualification_code": 212,
+ "range_sets": [
+ {"units": 311, "to": 28, "from": 25},
+ {"units": 311, "to": 42, "from": 40},
+ ],
+ }
+ )
+ assert (
+ SpectrumCondition(
+ question_id="212",
+ values=["25-28", "40-42"],
+ value_type=ConditionValueType.RANGE,
+ negate=False,
+ logical_operator=LogicalOperator.OR,
+ )
+ == c
+ )
+
+ # These equal each other b/c age ranges get automatically converted
+ assert (
+ SpectrumCondition(
+ question_id="212",
+ values=["25", "26", "27", "28", "40", "41", "42"],
+ value_type=ConditionValueType.LIST,
+ negate=False,
+ logical_operator=LogicalOperator.OR,
+ )
+ == c
+ )
+
+ c = SpectrumCondition.from_api(
+ {
+ "condition_codes": ["111", "117", "112", "113", "118"],
+ "qualification_code": 1202,
+ }
+ )
+ assert (
+ SpectrumCondition(
+ question_id="1202",
+ values=["111", "112", "113", "117", "118"],
+ value_type=ConditionValueType.LIST,
+ negate=False,
+ logical_operator=LogicalOperator.OR,
+ )
+ == c
+ )
+
+
+class TestSpectrumQuota:
+
+ def test_quota_create(self):
+ from generalresearch.models.spectrum.survey import (
+ SpectrumCondition,
+ SpectrumQuota,
+ )
+
+ d = {
+ "quota_id": "a846b545-4449-4d76-93a2-f8ebdf6e711e",
+ "quantities": {"currently_open": 57, "remaining": 57, "achieved": 0},
+ "criteria": [{"qualification_code": 211, "condition_codes": ["111"]}],
+ "crtd_on": 1716227282077,
+ "mod_on": 1716227284146,
+ "last_complete_date": None,
+ }
+ criteria = [SpectrumCondition.from_api(q) for q in d["criteria"]]
+ d["condition_hashes"] = [x.criterion_hash for x in criteria]
+ q = SpectrumQuota.from_api(d)
+ assert SpectrumQuota(remaining_count=57, condition_hashes=["c23c0b9"]) == q
+ assert q.is_open
+
+ def test_quota_passes(self):
+ from generalresearch.models.spectrum.survey import (
+ SpectrumQuota,
+ )
+
+ q = SpectrumQuota(remaining_count=57, condition_hashes=["a"])
+ assert q.passes({"a": True})
+ assert not q.passes({"a": False})
+ assert not q.passes({})
+
+ # We have to match all
+ q = SpectrumQuota(remaining_count=57, condition_hashes=["a", "b", "c"])
+ assert not q.passes({"a": True, "b": False})
+ assert q.passes({"a": True, "b": True, "c": True})
+
+ # Quota must be open, even if we match
+ q = SpectrumQuota(remaining_count=0, condition_hashes=["a"])
+ assert not q.passes({"a": True})
+
+ def test_quota_passes_soft(self):
+ from generalresearch.models.spectrum.survey import (
+ SpectrumQuota,
+ )
+
+ q = SpectrumQuota(remaining_count=57, condition_hashes=["a", "b", "c"])
+ # Pass if we match all
+ assert (True, set()) == q.matches_soft({"a": True, "b": True, "c": True})
+ # Fail if we don't match any
+ assert (False, set()) == q.matches_soft({"a": True, "b": False, "c": None})
+ # Unknown if any are unknown AND we don't fail any
+ assert (None, {"c", "b"}) == q.matches_soft({"a": True, "b": None, "c": None})
+ assert (None, {"a", "c", "b"}) == q.matches_soft(
+ {"a": None, "b": None, "c": None}
+ )
+ assert (False, set()) == q.matches_soft({"a": None, "b": False, "c": None})
+
+
+class TestSpectrumSurvey:
+ def test_survey_create(self):
+ from generalresearch.models import (
+ LogicalOperator,
+ Source,
+ TaskCalculationType,
+ )
+ from generalresearch.models.spectrum import SpectrumStatus
+ from generalresearch.models.spectrum.survey import (
+ SpectrumCondition,
+ SpectrumQuota,
+ SpectrumSurvey,
+ )
+ from generalresearch.models.thl.survey.condition import ConditionValueType
+
+ # Note: d is the raw response after calling SpectrumAPI.preprocess_survey() on it!
+ d = {
+ "survey_id": 29333264,
+ "survey_name": "Exciting New Survey #29333264",
+ "survey_status": 22,
+ "field_end_date": datetime(2024, 5, 23, 18, 18, 31, tzinfo=timezone.utc),
+ "category": "Exciting New",
+ "category_code": 232,
+ "crtd_on": datetime(2024, 5, 20, 17, 48, 13, tzinfo=timezone.utc),
+ "mod_on": datetime(2024, 5, 20, 18, 18, 31, tzinfo=timezone.utc),
+ "soft_launch": False,
+ "click_balancing": 0,
+ "price_type": 1,
+ "pii": False,
+ "buyer_message": "",
+ "buyer_id": 4726,
+ "incl_excl": 0,
+ "cpi": Decimal("1.20000"),
+ "last_complete_date": None,
+ "project_last_complete_date": None,
+ "survey_performance": {
+ "overall": {"ir": 40, "loi": 10},
+ "last_block": {"ir": None, "loi": None},
+ },
+ "supplier_completes": {
+ "needed": 495,
+ "achieved": 0,
+ "remaining": 495,
+ "guaranteed_allocation": 0,
+ "guaranteed_allocation_remaining": 0,
+ },
+ "pds": {"enabled": False, "buyer_name": None},
+ "quotas": [
+ {
+ "quota_id": "c2bc961e-4f26-4223-b409-ebe9165cfdf5",
+ "quantities": {
+ "currently_open": 491,
+ "remaining": 495,
+ "achieved": 0,
+ },
+ "criteria": [
+ {
+ "qualification_code": 212,
+ "range_sets": [{"units": 311, "to": 64, "from": 18}],
+ }
+ ],
+ "crtd_on": 1716227293496,
+ "mod_on": 1716229289847,
+ "last_complete_date": None,
+ }
+ ],
+ "qualifications": [
+ {
+ "range_sets": [{"units": 311, "to": 64, "from": 18}],
+ "qualification_code": 212,
+ }
+ ],
+ "country_iso": "fr",
+ "language_iso": "fre",
+ "bid_ir": 0.4,
+ "bid_loi": 600,
+ "last_block_ir": None,
+ "last_block_loi": None,
+ "survey_exclusions": set(),
+ "exclusion_period": 0,
+ }
+ s = SpectrumSurvey.from_api(d)
+ expected_survey = SpectrumSurvey(
+ cpi=Decimal("1.20000"),
+ country_isos=["fr"],
+ language_isos=["fre"],
+ buyer_id="4726",
+ source=Source.SPECTRUM,
+ used_question_ids={"212"},
+ survey_id="29333264",
+ survey_name="Exciting New Survey #29333264",
+ status=SpectrumStatus.LIVE,
+ field_end_date=datetime(2024, 5, 23, 18, 18, 31, tzinfo=timezone.utc),
+ category_code="232",
+ calculation_type=TaskCalculationType.COMPLETES,
+ requires_pii=False,
+ survey_exclusions=set(),
+ exclusion_period=0,
+ bid_ir=0.40,
+ bid_loi=600,
+ last_block_loi=None,
+ last_block_ir=None,
+ overall_loi=None,
+ overall_ir=None,
+ project_last_complete_date=None,
+ country_iso="fr",
+ language_iso="fre",
+ include_psids=None,
+ exclude_psids=None,
+ qualifications=["77f493d"],
+ quotas=[SpectrumQuota(remaining_count=491, condition_hashes=["77f493d"])],
+ conditions={
+ "77f493d": SpectrumCondition(
+ logical_operator=LogicalOperator.OR,
+ value_type=ConditionValueType.RANGE,
+ negate=False,
+ question_id="212",
+ values=["18-64"],
+ )
+ },
+ created_api=datetime(2024, 5, 20, 17, 48, 13, tzinfo=timezone.utc),
+ modified_api=datetime(2024, 5, 20, 18, 18, 31, tzinfo=timezone.utc),
+ updated=None,
+ )
+ assert expected_survey.model_dump_json() == s.model_dump_json()
+
+ def test_survey_properties(self):
+ from generalresearch.models.spectrum.survey import (
+ SpectrumSurvey,
+ )
+
+ d = {
+ "survey_id": 29333264,
+ "survey_name": "#29333264",
+ "survey_status": 22,
+ "field_end_date": datetime(2024, 5, 23, 18, 18, 31, tzinfo=timezone.utc),
+ "category": "Exciting New",
+ "category_code": 232,
+ "crtd_on": datetime(2024, 5, 20, 17, 48, 13, tzinfo=timezone.utc),
+ "mod_on": datetime(2024, 5, 20, 18, 18, 31, tzinfo=timezone.utc),
+ "soft_launch": False,
+ "click_balancing": 0,
+ "price_type": 1,
+ "pii": False,
+ "buyer_message": "",
+ "buyer_id": 4726,
+ "incl_excl": 0,
+ "cpi": Decimal("1.20000"),
+ "last_complete_date": None,
+ "project_last_complete_date": None,
+ "quotas": [
+ {
+ "quota_id": "c2bc961e-4f26-4223-b409-ebe9165cfdf5",
+ "quantities": {
+ "currently_open": 491,
+ "remaining": 495,
+ "achieved": 0,
+ },
+ "criteria": [
+ {
+ "qualification_code": 214,
+ "range_sets": [{"units": 311, "to": 64, "from": 18}],
+ }
+ ],
+ }
+ ],
+ "qualifications": [
+ {
+ "range_sets": [{"units": 311, "to": 64, "from": 18}],
+ "qualification_code": 212,
+ },
+ {"condition_codes": ["111", "117", "112"], "qualification_code": 1202},
+ ],
+ "country_iso": "fr",
+ "language_iso": "fre",
+ "overall_ir": 0.4,
+ "overall_loi": 600,
+ "last_block_ir": None,
+ "last_block_loi": None,
+ "survey_exclusions": set(),
+ "exclusion_period": 0,
+ }
+ s = SpectrumSurvey.from_api(d)
+ assert {"212", "1202", "214"} == s.used_question_ids
+ assert s.is_live
+ assert s.is_open
+ assert {"38cea5e", "83955ef", "77f493d"} == s.all_hashes
+
+ def test_survey_eligibility(self):
+ from generalresearch.models.spectrum.survey import (
+ SpectrumQuota,
+ SpectrumSurvey,
+ )
+
+ d = {
+ "survey_id": 29333264,
+ "survey_name": "#29333264",
+ "survey_status": 22,
+ "field_end_date": datetime(2024, 5, 23, 18, 18, 31, tzinfo=timezone.utc),
+ "category": "Exciting New",
+ "category_code": 232,
+ "crtd_on": datetime(2024, 5, 20, 17, 48, 13, tzinfo=timezone.utc),
+ "mod_on": datetime(2024, 5, 20, 18, 18, 31, tzinfo=timezone.utc),
+ "soft_launch": False,
+ "click_balancing": 0,
+ "price_type": 1,
+ "pii": False,
+ "buyer_message": "",
+ "buyer_id": 4726,
+ "incl_excl": 0,
+ "cpi": Decimal("1.20000"),
+ "last_complete_date": None,
+ "project_last_complete_date": None,
+ "quotas": [],
+ "qualifications": [],
+ "country_iso": "fr",
+ "language_iso": "fre",
+ "overall_ir": 0.4,
+ "overall_loi": 600,
+ "last_block_ir": None,
+ "last_block_loi": None,
+ "survey_exclusions": set(),
+ "exclusion_period": 0,
+ }
+ s = SpectrumSurvey.from_api(d)
+ s.qualifications = ["a", "b", "c"]
+ s.quotas = [
+ SpectrumQuota(remaining_count=10, condition_hashes=["a", "b"]),
+ SpectrumQuota(remaining_count=0, condition_hashes=["d"]),
+ SpectrumQuota(remaining_count=10, condition_hashes=["e"]),
+ ]
+
+ assert s.passes_qualifications({"a": True, "b": True, "c": True})
+ assert not s.passes_qualifications({"a": True, "b": True, "c": False})
+
+ # we do NOT match a full quota, so we pass
+ assert s.passes_quotas({"a": True, "b": True, "d": False})
+ # We dont pass any
+ assert not s.passes_quotas({})
+ # we only pass a full quota
+ assert not s.passes_quotas({"d": True})
+ # we only dont pass a full quota, but we haven't passed any open
+ assert not s.passes_quotas({"d": False})
+ # we pass a quota, but also pass a full quota, so fail
+ assert not s.passes_quotas({"e": True, "d": True})
+ # we pass a quota, but are unknown in a full quota, so fail
+ assert not s.passes_quotas({"e": True})
+
+ # # Soft Pair
+ assert (True, set()) == s.passes_qualifications_soft(
+ {"a": True, "b": True, "c": True}
+ )
+ assert (False, set()) == s.passes_qualifications_soft(
+ {"a": True, "b": True, "c": False}
+ )
+ assert (None, set("c")) == s.passes_qualifications_soft(
+ {"a": True, "b": True, "c": None}
+ )
+
+ # we do NOT match a full quota, so we pass
+ assert (True, set()) == s.passes_quotas_soft({"a": True, "b": True, "d": False})
+ # We dont pass any
+ assert (None, {"a", "b", "d", "e"}) == s.passes_quotas_soft({})
+ # we only pass a full quota
+ assert (False, set()) == s.passes_quotas_soft({"d": True})
+ # we only dont pass a full quota, but we haven't passed any open
+ assert (None, {"a", "b", "e"}) == s.passes_quotas_soft({"d": False})
+ # we pass a quota, but also pass a full quota, so fail
+ assert (False, set()) == s.passes_quotas_soft({"e": True, "d": True})
+ # we pass a quota, but are unknown in a full quota, so fail
+ assert (None, {"d"}) == s.passes_quotas_soft({"e": True})
+
+ assert s.determine_eligibility({"a": True, "b": True, "c": True, "d": False})
+ assert not s.determine_eligibility(
+ {"a": True, "b": True, "c": False, "d": False}
+ )
+ assert not s.determine_eligibility(
+ {"a": True, "b": True, "c": None, "d": False}
+ )
+ assert (True, set()) == s.determine_eligibility_soft(
+ {"a": True, "b": True, "c": True, "d": False}
+ )
+ assert (False, set()) == s.determine_eligibility_soft(
+ {"a": True, "b": True, "c": False, "d": False}
+ )
+ assert (None, set("c")) == s.determine_eligibility_soft(
+ {"a": True, "b": True, "c": None, "d": False}
+ )
+ assert (None, {"c", "d"}) == s.determine_eligibility_soft(
+ {"a": True, "b": True, "c": None, "d": None}
+ )
diff --git a/tests/models/spectrum/test_survey_manager.py b/tests/models/spectrum/test_survey_manager.py
new file mode 100644
index 0000000..582093c
--- /dev/null
+++ b/tests/models/spectrum/test_survey_manager.py
@@ -0,0 +1,130 @@
+import copy
+import logging
+from datetime import timezone, datetime
+from decimal import Decimal
+
+from pymysql import IntegrityError
+
+
+logger = logging.getLogger()
+
+example_survey_api_response = {
+ "survey_id": 29333264,
+ "survey_name": "#29333264",
+ "survey_status": 22,
+ "field_end_date": datetime(2024, 5, 23, 18, 18, 31, tzinfo=timezone.utc),
+ "category": "Exciting New",
+ "category_code": 232,
+ "crtd_on": datetime(2024, 5, 20, 17, 48, 13, tzinfo=timezone.utc),
+ "mod_on": datetime(2024, 5, 20, 18, 18, 31, tzinfo=timezone.utc),
+ "soft_launch": False,
+ "click_balancing": 0,
+ "price_type": 1,
+ "pii": False,
+ "buyer_message": "",
+ "buyer_id": 4726,
+ "incl_excl": 0,
+ "cpi": Decimal("1.20"),
+ "last_complete_date": None,
+ "project_last_complete_date": None,
+ "quotas": [
+ {
+ "quota_id": "c2bc961e-4f26-4223-b409-ebe9165cfdf5",
+ "quantities": {"currently_open": 491, "remaining": 495, "achieved": 0},
+ "criteria": [
+ {
+ "qualification_code": 214,
+ "range_sets": [{"units": 311, "to": 64, "from": 18}],
+ }
+ ],
+ }
+ ],
+ "qualifications": [
+ {
+ "range_sets": [{"units": 311, "to": 64, "from": 18}],
+ "qualification_code": 212,
+ },
+ {"condition_codes": ["111", "117", "112"], "qualification_code": 1202},
+ ],
+ "country_iso": "fr",
+ "language_iso": "fre",
+ "bid_ir": 0.4,
+ "bid_loi": 600,
+ "overall_ir": None,
+ "overall_loi": None,
+ "last_block_ir": None,
+ "last_block_loi": None,
+ "survey_exclusions": set(),
+ "exclusion_period": 0,
+}
+
+
+class TestSpectrumSurvey:
+
+ def test_survey_create(self, settings, spectrum_manager, spectrum_rw):
+ from generalresearch.models.spectrum.survey import SpectrumSurvey
+
+ assert settings.debug, "CRITICAL: Do not run this on production."
+
+ now = datetime.now(tz=timezone.utc)
+ spectrum_rw.execute_sql_query(
+ query=f"""
+ DELETE FROM `{spectrum_rw.db}`.spectrum_survey
+ WHERE survey_id = '29333264'""",
+ commit=True,
+ )
+
+ d = example_survey_api_response.copy()
+ s = SpectrumSurvey.from_api(d)
+ spectrum_manager.create(s)
+
+ surveys = spectrum_manager.get_survey_library(updated_since=now)
+ assert len(surveys) == 1
+ assert "29333264" == surveys[0].survey_id
+ assert s.is_unchanged(surveys[0])
+
+ try:
+ spectrum_manager.create(s)
+ except IntegrityError as e:
+ print(e.args)
+
+ def test_survey_update(self, settings, spectrum_manager, spectrum_rw):
+ from generalresearch.models.spectrum.survey import SpectrumSurvey
+
+ assert settings.debug, "CRITICAL: Do not run this on production."
+
+ now = datetime.now(tz=timezone.utc)
+ spectrum_rw.execute_sql_query(
+ query=f"""
+ DELETE FROM `{spectrum_rw.db}`.spectrum_survey
+ WHERE survey_id = '29333264'
+ """,
+ commit=True,
+ )
+ d = copy.deepcopy(example_survey_api_response)
+ s = SpectrumSurvey.from_api(d)
+ print(s)
+
+ spectrum_manager.create(s)
+ s.cpi = Decimal("0.50")
+ spectrum_manager.update([s])
+ surveys = spectrum_manager.get_survey_library(updated_since=now)
+ assert len(surveys) == 1
+ assert "29333264" == surveys[0].survey_id
+ assert Decimal("0.50") == surveys[0].cpi
+ assert s.is_unchanged(surveys[0])
+
+ # --- Updating bid/overall/last block
+ assert 600 == s.bid_loi
+ assert s.overall_loi is None
+ assert s.last_block_loi is None
+
+ # now the last block is set
+ s.bid_loi = None
+ s.overall_loi = 1000
+ s.last_block_loi = 1000
+ spectrum_manager.update([s])
+ surveys = spectrum_manager.get_survey_library(updated_since=now)
+ assert 600 == surveys[0].bid_loi
+ assert 1000 == surveys[0].overall_loi
+ assert 1000 == surveys[0].last_block_loi