aboutsummaryrefslogtreecommitdiff
path: root/tests/models/dynata
diff options
context:
space:
mode:
Diffstat (limited to 'tests/models/dynata')
-rw-r--r--tests/models/dynata/__init__.py0
-rw-r--r--tests/models/dynata/test_eligbility.py324
-rw-r--r--tests/models/dynata/test_survey.py164
3 files changed, 488 insertions, 0 deletions
diff --git a/tests/models/dynata/__init__.py b/tests/models/dynata/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/models/dynata/__init__.py
diff --git a/tests/models/dynata/test_eligbility.py b/tests/models/dynata/test_eligbility.py
new file mode 100644
index 0000000..23437f5
--- /dev/null
+++ b/tests/models/dynata/test_eligbility.py
@@ -0,0 +1,324 @@
+from datetime import datetime, timezone
+
+
+class TestEligibility:
+
+ def test_evaluate_task_criteria(self):
+ from generalresearch.models.dynata.survey import (
+ DynataQuotaGroup,
+ DynataFilterGroup,
+ DynataSurvey,
+ DynataRequirements,
+ )
+
+ filters = [[["a", "b"], ["c", "d"]], [["e"], ["f"]]]
+ filters = [DynataFilterGroup.model_validate(f) for f in filters]
+ criteria_evaluation = {
+ "a": True,
+ "b": True,
+ "c": True,
+ "d": False,
+ "e": True,
+ "f": True,
+ }
+ quotas = [
+ DynataQuotaGroup.model_validate(
+ [{"count": 100, "condition_hashes": [], "status": "OPEN"}]
+ )
+ ]
+ task = DynataSurvey.model_validate(
+ {
+ "survey_id": "1",
+ "filters": filters,
+ "quotas": quotas,
+ "allowed_devices": set("1"),
+ "calculation_type": "COMPLETES",
+ "client_id": "",
+ "country_iso": "us",
+ "language_iso": "eng",
+ "group_id": "g1",
+ "project_id": "p1",
+ "status": "OPEN",
+ "project_exclusions": set(),
+ "created": datetime.now(tz=timezone.utc),
+ "category_exclusions": set(),
+ "category_ids": set(),
+ "cpi": 1,
+ "days_in_field": 0,
+ "expected_count": 0,
+ "order_number": "",
+ "live_link": "",
+ "bid_ir": 0.5,
+ "bid_loi": 500,
+ "requirements": DynataRequirements(),
+ }
+ )
+ assert task.determine_eligibility(criteria_evaluation)
+
+ # task status
+ task.status = "CLOSED"
+ assert not task.determine_eligibility(criteria_evaluation)
+ task.status = "OPEN"
+
+ # one quota with no space left (count = 0)
+ quotas = [
+ DynataQuotaGroup.model_validate(
+ [{"count": 0, "condition_hashes": [], "status": "OPEN"}]
+ )
+ ]
+ task.quotas = quotas
+ assert not task.determine_eligibility(criteria_evaluation)
+
+ # we pass 'a' and 'b'
+ quotas = [
+ DynataQuotaGroup.model_validate(
+ [{"count": 100, "condition_hashes": ["a", "b"], "status": "OPEN"}]
+ )
+ ]
+ task.quotas = quotas
+ assert task.determine_eligibility(criteria_evaluation)
+
+ # make 'f' false, we still pass the 2nd filtergroup b/c 'e' is True
+ criteria_evaluation = {
+ "a": True,
+ "b": True,
+ "c": True,
+ "d": False,
+ "e": True,
+ "f": False,
+ }
+ assert task.determine_eligibility(criteria_evaluation)
+
+ # make 'e' false, we don't pass the 2nd filtergroup
+ criteria_evaluation = {
+ "a": True,
+ "b": True,
+ "c": True,
+ "d": False,
+ "e": False,
+ "f": False,
+ }
+ assert not task.determine_eligibility(criteria_evaluation)
+
+ # We fail quota 'c','d', but we pass 'a','b', so we pass the first quota group
+ criteria_evaluation = {
+ "a": True,
+ "b": True,
+ "c": True,
+ "d": False,
+ "e": True,
+ "f": True,
+ }
+ quotas = [
+ DynataQuotaGroup.model_validate(
+ [
+ {"count": 100, "condition_hashes": ["a", "b"], "status": "OPEN"},
+ {"count": 100, "condition_hashes": ["c", "d"], "status": "CLOSED"},
+ ]
+ )
+ ]
+ task.quotas = quotas
+ assert task.determine_eligibility(criteria_evaluation)
+
+ # we pass the first qg, but then fall into a full 2nd qg
+ quotas = [
+ DynataQuotaGroup.model_validate(
+ [
+ {"count": 100, "condition_hashes": ["a", "b"], "status": "OPEN"},
+ {"count": 100, "condition_hashes": ["c", "d"], "status": "CLOSED"},
+ ]
+ ),
+ DynataQuotaGroup.model_validate(
+ [{"count": 100, "condition_hashes": ["f"], "status": "CLOSED"}]
+ ),
+ ]
+ task.quotas = quotas
+ assert not task.determine_eligibility(criteria_evaluation)
+
+ def test_soft_pair(self):
+ from generalresearch.models.dynata.survey import (
+ DynataQuotaGroup,
+ DynataFilterGroup,
+ DynataSurvey,
+ DynataRequirements,
+ )
+
+ filters = [[["a", "b"], ["c", "d"]], [["e"], ["f"]]]
+ filters = [DynataFilterGroup.model_validate(f) for f in filters]
+ criteria_evaluation = {
+ "a": True,
+ "b": True,
+ "c": True,
+ "d": False,
+ "e": True,
+ "f": True,
+ }
+ quotas = [
+ DynataQuotaGroup.model_validate(
+ [{"count": 100, "condition_hashes": [], "status": "OPEN"}]
+ )
+ ]
+ task = DynataSurvey.model_validate(
+ {
+ "survey_id": "1",
+ "filters": filters,
+ "quotas": quotas,
+ "allowed_devices": set("1"),
+ "calculation_type": "COMPLETES",
+ "client_id": "",
+ "country_iso": "us",
+ "language_iso": "eng",
+ "group_id": "g1",
+ "project_id": "p1",
+ "status": "OPEN",
+ "project_exclusions": set(),
+ "created": datetime.now(tz=timezone.utc),
+ "category_exclusions": set(),
+ "category_ids": set(),
+ "cpi": 1,
+ "days_in_field": 0,
+ "expected_count": 0,
+ "order_number": "",
+ "live_link": "",
+ "bid_ir": 0.5,
+ "bid_loi": 500,
+ "requirements": DynataRequirements(),
+ }
+ )
+ assert task.passes_filters(criteria_evaluation)
+ passes, condition_hashes = task.passes_filters_soft(criteria_evaluation)
+ assert passes
+
+ # make 'e' & 'f' None, we don't pass the 2nd filtergroup
+ criteria_evaluation = {
+ "a": True,
+ "b": True,
+ "c": True,
+ "d": False,
+ "e": None,
+ "f": None,
+ }
+ assert not task.passes_filters(criteria_evaluation)
+ passes, conditional_hashes = task.passes_filters_soft(criteria_evaluation)
+ assert passes is None
+ assert {"e", "f"} == conditional_hashes
+
+ # 1st filtergroup unknown
+ criteria_evaluation = {
+ "a": True,
+ "b": None,
+ "c": None,
+ "d": None,
+ "e": None,
+ "f": None,
+ }
+ assert not task.passes_filters(criteria_evaluation)
+ passes, conditional_hashes = task.passes_filters_soft(criteria_evaluation)
+ assert passes is None
+ assert {"b", "c", "d", "e", "f"} == conditional_hashes
+
+ # 1st filtergroup unknown, 2nd cell False
+ criteria_evaluation = {
+ "a": True,
+ "b": None,
+ "c": None,
+ "d": False,
+ "e": None,
+ "f": None,
+ }
+ assert not task.passes_filters(criteria_evaluation)
+ passes, conditional_hashes = task.passes_filters_soft(criteria_evaluation)
+ assert passes is None
+ assert {"b", "e", "f"} == conditional_hashes
+
+ # we pass the first qg, unknown 2nd
+ criteria_evaluation = {
+ "a": True,
+ "b": True,
+ "c": None,
+ "d": False,
+ "e": None,
+ "f": None,
+ }
+ quotas = [
+ DynataQuotaGroup.model_validate(
+ [
+ {"count": 100, "condition_hashes": ["a", "b"], "status": "OPEN"},
+ {"count": 100, "condition_hashes": ["c", "d"], "status": "CLOSED"},
+ ]
+ ),
+ DynataQuotaGroup.model_validate(
+ [{"count": 100, "condition_hashes": ["f"], "status": "OPEN"}]
+ ),
+ ]
+ task.quotas = quotas
+ passes, conditional_hashes = task.passes_quotas_soft(criteria_evaluation)
+ assert passes is None
+ assert {"f"} == conditional_hashes
+
+ # both quota groups unknown
+ criteria_evaluation = {
+ "a": True,
+ "b": None,
+ "c": None,
+ "d": False,
+ "e": None,
+ "g": None,
+ }
+ quotas = [
+ DynataQuotaGroup.model_validate(
+ [
+ {"count": 100, "condition_hashes": ["a", "b"], "status": "OPEN"},
+ {"count": 100, "condition_hashes": ["c", "d"], "status": "CLOSED"},
+ ]
+ ),
+ DynataQuotaGroup.model_validate(
+ [{"count": 100, "condition_hashes": ["g"], "status": "OPEN"}]
+ ),
+ ]
+ task.quotas = quotas
+ passes, conditional_hashes = task.passes_quotas_soft(criteria_evaluation)
+ assert passes is None
+ assert {"b", "g"} == conditional_hashes
+
+ passes, conditional_hashes = task.determine_eligibility_soft(
+ criteria_evaluation
+ )
+ assert passes is None
+ assert {"b", "e", "f", "g"} == conditional_hashes
+
+ # def x(self):
+ # # ----
+ # c1 = DynataCondition(question_id='gender', values=['male'], value_type=ConditionValueType.LIST) # 718f759
+ # c2 = DynataCondition(question_id='age', values=['18-24'], value_type=ConditionValueType.RANGE) # 7a7b290
+ # obj1 = DynataFilterObject(cells=[c1.criterion_hash, c2.criterion_hash])
+ #
+ # c3 = DynataCondition(question_id='gender', values=['female'], value_type=ConditionValueType.LIST) # 38fa4e1
+ # c4 = DynataCondition(question_id='age', values=['35-45'], value_type=ConditionValueType.RANGE) # e4f06fa
+ # obj2 = DynataFilterObject(cells=[c3.criterion_hash, c4.criterion_hash])
+ #
+ # grp1 = DynataFilterGroup(objects=[obj1, obj2])
+ #
+ # # -----
+ # c5 = DynataCondition(question_id='ethnicity', values=['white'], value_type=ConditionValueType.LIST) # eb9b9a4
+ # obj3 = DynataFilterObject(cells=[c5.criterion_hash])
+ #
+ # c6 = DynataCondition(question_id='ethnicity', values=['black'], value_type=ConditionValueType.LIST) # 039fe2d
+ # obj4 = DynataFilterObject(cells=[c6.criterion_hash])
+ #
+ # grp2 = DynataFilterGroup(objects=[obj3, obj4])
+ # # -----
+ # q1 = DynataQuota(count=5, status=DynataStatus.OPEN,
+ # condition_hashes=[c1.criterion_hash, c2.criterion_hash])
+ # q2 = DynataQuota(count=10, status=DynataStatus.CLOSED,
+ # condition_hashes=[c3.criterion_hash, c4.criterion_hash])
+ # qg1 = DynataQuotaGroup(cells=[q1, q2])
+ # # ----
+ #
+ # s = DynataSurvey(survey_id='123', status=DynataStatus.OPEN, country_iso='us',
+ # language_iso='eng', group_id='123', client_id='123', project_id='12',
+ # filters=[grp1, grp2],
+ # quotas=[qg1])
+ # ce = {'718f759': True, '7a7b290': True, 'eb9b9a4': True}
+ # s.passes_filters(ce)
+ # s.passes_quotas(ce)
diff --git a/tests/models/dynata/test_survey.py b/tests/models/dynata/test_survey.py
new file mode 100644
index 0000000..ad953a3
--- /dev/null
+++ b/tests/models/dynata/test_survey.py
@@ -0,0 +1,164 @@
+class TestDynataCondition:
+
+ def test_condition_create(self):
+ from generalresearch.models.dynata.survey import DynataCondition
+
+ cell = {
+ "tag": "90606986-5508-461b-a821-216e9a72f1a0",
+ "attribute_id": 120,
+ "negate": False,
+ "kind": "VALUE",
+ "value": "45398",
+ }
+ c = DynataCondition.from_api(cell)
+ assert c.evaluate_criterion({"120": {"45398"}})
+ assert not c.evaluate_criterion({"120": {"11111"}})
+
+ cell = {
+ "tag": "aa7169c0-cb34-499a-aadd-31e0013df8fd",
+ "attribute_id": 231302,
+ "negate": False,
+ "operator": "OR",
+ "kind": "LIST",
+ "list": ["514802", "514804", "514808", "514810"],
+ }
+ c = DynataCondition.from_api(cell)
+ assert c.evaluate_criterion({"231302": {"514804", "123445"}})
+ assert not c.evaluate_criterion({"231302": {"123445"}})
+
+ cell = {
+ "tag": "aa7169c0-cb34-499a-aadd-31e0013df8fd",
+ "attribute_id": 231302,
+ "negate": False,
+ "operator": "AND",
+ "kind": "LIST",
+ "list": ["514802", "514804"],
+ }
+ c = DynataCondition.from_api(cell)
+ assert c.evaluate_criterion({"231302": {"514802", "514804"}})
+ assert not c.evaluate_criterion({"231302": {"514802"}})
+
+ cell = {
+ "tag": "75a36c67-0328-4c1b-a4dd-67d34688ff68",
+ "attribute_id": 80,
+ "negate": False,
+ "kind": "RANGE",
+ "range": {"from": 18, "to": 99},
+ }
+ c = DynataCondition.from_api(cell)
+ assert c.evaluate_criterion({"80": {"20"}})
+ assert not c.evaluate_criterion({"80": {"120"}})
+
+ cell = {
+ "tag": "dd64b622-ed10-4a3b-e1h8-a4e63b59vha2",
+ "attribute_id": 83,
+ "negate": False,
+ "kind": "INEFFABLE",
+ }
+ c = DynataCondition.from_api(cell)
+ assert c.evaluate_criterion({"83": {"20"}})
+
+ cell = {
+ "tag": "kei35kkjj-d00k-52kj-b3j4-a4jinx9832",
+ "attribute_id": 8,
+ "negate": False,
+ "kind": "ANSWERED",
+ }
+ c = DynataCondition.from_api(cell)
+ assert c.evaluate_criterion({"8": {"20"}})
+ assert not c.evaluate_criterion({"81": {"20"}})
+
+ def test_condition_range(self):
+ from generalresearch.models.dynata.survey import DynataCondition
+
+ cell = {
+ "tag": "75a36c67-0328-4c1b-a4dd-67d34688ff68",
+ "attribute_id": 80,
+ "negate": False,
+ "kind": "RANGE",
+ "range": {"from": 18, "to": None},
+ }
+ c = DynataCondition.from_api(cell)
+ assert c.evaluate_criterion({"80": {"20"}})
+
+ def test_recontact(self):
+ from generalresearch.models.dynata.survey import DynataCondition
+
+ cell = {
+ "tag": "d559212d-7984-4239-89c2-06c29588d79e",
+ "attribute_id": 238384,
+ "negate": False,
+ "operator": "OR",
+ "kind": "INVITE_COLLECTIONS",
+ "invite_collections": ["621041", "621042"],
+ }
+ c = DynataCondition.from_api(cell)
+ assert c.evaluate_criterion({"80": {"20"}}, user_groups={"621041", "a"})
+
+
+class TestDynataSurvey:
+ pass
+
+ # def test_survey_eligibility(self):
+ # 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 = DynataSurvey.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'])
+ # ]
+ #
+ # self.assertTrue(s.passes_qualifications({'a': True, 'b': True, 'c': True}))
+ # self.assertFalse(s.passes_qualifications({'a': True, 'b': True, 'c': False}))
+ #
+ # # we do NOT match a full quota, so we pass
+ # self.assertTrue(s.passes_quotas({'a': True, 'b': True, 'd': False}))
+ # # We dont pass any
+ # self.assertFalse(s.passes_quotas({}))
+ # # we only pass a full quota
+ # self.assertFalse(s.passes_quotas({'d': True}))
+ # # we only dont pass a full quota, but we haven't passed any open
+ # self.assertFalse(s.passes_quotas({'d': False}))
+ # # we pass a quota, but also pass a full quota, so fail
+ # self.assertFalse(s.passes_quotas({'e': True, 'd': True}))
+ # # we pass a quota, but are unknown in a full quota, so fail
+ # self.assertFalse(s.passes_quotas({'e': True}))
+ #
+ # # # Soft Pair
+ # self.assertEqual((True, set()), s.passes_qualifications_soft({'a': True, 'b': True, 'c': True}))
+ # self.assertEqual((False, set()), s.passes_qualifications_soft({'a': True, 'b': True, 'c': False}))
+ # self.assertEqual((None, set('c')), s.passes_qualifications_soft({'a': True, 'b': True, 'c': None}))
+ #
+ # # we do NOT match a full quota, so we pass
+ # self.assertEqual((True, set()), s.passes_quotas_soft({'a': True, 'b': True, 'd': False}))
+ # # We dont pass any
+ # self.assertEqual((None, {'a', 'b', 'd', 'e'}), s.passes_quotas_soft({}))
+ # # we only pass a full quota
+ # self.assertEqual((False, set()), s.passes_quotas_soft({'d': True}))
+ # # we only dont pass a full quota, but we haven't passed any open
+ # self.assertEqual((None, {'a', 'b', 'e'}), s.passes_quotas_soft({'d': False}))
+ # # we pass a quota, but also pass a full quota, so fail
+ # self.assertEqual((False, set()), s.passes_quotas_soft({'e': True, 'd': True}))
+ # # we pass a quota, but are unknown in a full quota, so fail
+ # self.assertEqual((None, {'d'}), s.passes_quotas_soft({'e': True}))
+ #
+ # self.assertEqual(True, s.determine_eligibility({'a': True, 'b': True, 'c': True, 'd': False}))
+ # self.assertEqual(False, s.determine_eligibility({'a': True, 'b': True, 'c': False, 'd': False}))
+ # self.assertEqual(False, s.determine_eligibility({'a': True, 'b': True, 'c': None, 'd': False}))
+ # self.assertEqual((True, set()), s.determine_eligibility_soft({'a': True, 'b': True, 'c': True, 'd': False}))
+ # self.assertEqual((False, set()), s.determine_eligibility_soft({'a': True, 'b': True, 'c': False, 'd': False}))
+ # self.assertEqual((None, set('c')), s.determine_eligibility_soft({'a': True, 'b': True, 'c': None,
+ # 'd': False}))
+ # self.assertEqual((None, {'c', 'd'}), s.determine_eligibility_soft({'a': True, 'b': True, 'c': None,
+ # 'd': None}))