diff options
Diffstat (limited to 'tests/models/dynata')
| -rw-r--r-- | tests/models/dynata/__init__.py | 0 | ||||
| -rw-r--r-- | tests/models/dynata/test_eligbility.py | 324 | ||||
| -rw-r--r-- | tests/models/dynata/test_survey.py | 164 |
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})) |
