aboutsummaryrefslogtreecommitdiff
path: root/generalresearch/models/thl
diff options
context:
space:
mode:
Diffstat (limited to 'generalresearch/models/thl')
-rw-r--r--generalresearch/models/thl/contest/contest.py10
-rw-r--r--generalresearch/models/thl/contest/leaderboard.py13
-rw-r--r--generalresearch/models/thl/contest/milestone.py20
-rw-r--r--generalresearch/models/thl/contest/raffle.py46
-rw-r--r--generalresearch/models/thl/finance.py12
-rw-r--r--generalresearch/models/thl/product.py36
-rw-r--r--generalresearch/models/thl/profiling/other_option.py4
-rw-r--r--generalresearch/models/thl/profiling/upk_question.py13
-rw-r--r--generalresearch/models/thl/profiling/user_question_answer.py31
9 files changed, 89 insertions, 96 deletions
diff --git a/generalresearch/models/thl/contest/contest.py b/generalresearch/models/thl/contest/contest.py
index 2a8853d..bd0fc04 100644
--- a/generalresearch/models/thl/contest/contest.py
+++ b/generalresearch/models/thl/contest/contest.py
@@ -136,10 +136,12 @@ class Contest(ContestBase):
# return False
def should_end(self) -> tuple[bool, ContestEndReason | None]:
- if self.status == ContestStatus.ACTIVE:
- if self.end_condition.ends_at:
- if datetime.now(tz=UTC) >= self.end_condition.ends_at:
- return True, ContestEndReason.ENDS_AT
+ if (
+ self.status == ContestStatus.ACTIVE
+ and self.end_condition.ends_at
+ and datetime.now(tz=UTC) >= self.end_condition.ends_at
+ ):
+ return True, ContestEndReason.ENDS_AT
return False, None
diff --git a/generalresearch/models/thl/contest/leaderboard.py b/generalresearch/models/thl/contest/leaderboard.py
index 696cdea..e923383 100644
--- a/generalresearch/models/thl/contest/leaderboard.py
+++ b/generalresearch/models/thl/contest/leaderboard.py
@@ -151,7 +151,8 @@ class LeaderboardContest(LeaderboardContestCreate, Contest):
len(self.country_isos) == 1
), "Can only set 1 country_iso in a leaderboard contest"
assert (
- list(self.country_isos)[0] == self.leaderboard_key_parts["country_iso"]
+ next(iter(self.country_isos))
+ == self.leaderboard_key_parts["country_iso"]
), "leaderboard_key country_iso must match the country_isos"
else:
self.country_isos = {self.leaderboard_key_parts["country_iso"]}
@@ -192,10 +193,12 @@ class LeaderboardContest(LeaderboardContestCreate, Contest):
return lbm
def should_end(self) -> tuple[bool, ContestEndReason | None]:
- if self.status == ContestStatus.ACTIVE:
- if self.end_condition.ends_at:
- if datetime.now(tz=UTC) >= self.end_condition.ends_at:
- return True, ContestEndReason.ENDS_AT
+ if (
+ self.status == ContestStatus.ACTIVE
+ and self.end_condition.ends_at
+ and datetime.now(tz=UTC) >= self.end_condition.ends_at
+ ):
+ return True, ContestEndReason.ENDS_AT
return False, None
diff --git a/generalresearch/models/thl/contest/milestone.py b/generalresearch/models/thl/contest/milestone.py
index 8d96fcb..5fc27fa 100644
--- a/generalresearch/models/thl/contest/milestone.py
+++ b/generalresearch/models/thl/contest/milestone.py
@@ -132,10 +132,12 @@ class MilestoneContest(MilestoneContestCreate, Contest):
if res:
return res, msg
- if self.status == ContestStatus.ACTIVE:
- if self.end_condition.max_winners:
- if self.win_count >= self.end_condition.max_winners:
- return True, ContestEndReason.MAX_WINNERS
+ if (
+ self.status == ContestStatus.ACTIVE
+ and self.end_condition.max_winners
+ and self.win_count >= self.end_condition.max_winners
+ ):
+ return True, ContestEndReason.MAX_WINNERS
return False, None
@@ -189,16 +191,10 @@ class MilestoneUserView(MilestoneContest, ContestUserView):
)
def should_award(self):
- if self.status == ContestStatus.ACTIVE:
- if self.should_have_awarded():
- return True
- return False
+ return bool(self.status == ContestStatus.ACTIVE and self.should_have_awarded())
def should_have_awarded(self):
- if self.target_amount:
- if self.user_amount >= self.target_amount:
- return True
- return False
+ return bool(self.target_amount and self.user_amount >= self.target_amount)
def is_user_eligible(self, country_iso: str) -> tuple[bool, str]:
passes, msg = super().is_user_eligible(country_iso=country_iso)
diff --git a/generalresearch/models/thl/contest/raffle.py b/generalresearch/models/thl/contest/raffle.py
index 08243f4..16a0a47 100644
--- a/generalresearch/models/thl/contest/raffle.py
+++ b/generalresearch/models/thl/contest/raffle.py
@@ -127,7 +127,7 @@ class RaffleContest(RaffleContestCreate, Contest):
# If there is more than 1 prize, the winning entry is subtracted
# from the user's entry count
user_amount = defaultdict(int)
- user_id_user = dict()
+ user_id_user = {}
for entry in self.entries:
user_amount[entry.user.user_id] += entry.amount
user_id_user[entry.user.user_id] = entry.user
@@ -149,10 +149,12 @@ class RaffleContest(RaffleContestCreate, Contest):
res, msg = super().should_end()
if res:
return res, msg
- if self.status == ContestStatus.ACTIVE:
- if self.end_condition.target_entry_amount:
- if self.current_amount >= self.end_condition.target_entry_amount:
- return True, ContestEndReason.TARGET_ENTRY_AMOUNT
+ if (
+ self.status == ContestStatus.ACTIVE
+ and self.end_condition.target_entry_amount
+ and self.current_amount >= self.end_condition.target_entry_amount
+ ):
+ return True, ContestEndReason.TARGET_ENTRY_AMOUNT
return False, None
@staticmethod
@@ -278,17 +280,19 @@ class RaffleUserView(RaffleContest, ContestUserView):
return probs
def is_entry_eligible(self, entry: ContestEntry) -> tuple[bool, str]:
- if self.entry_rule.max_entry_amount_per_user:
- if (
- self.user_amount + entry.amount
- ) > self.entry_rule.max_entry_amount_per_user:
- return False, "Entry would exceed max amount per user."
-
- if self.entry_rule.max_daily_entries_per_user:
- if (
- self.user_amount_today + entry.amount
- ) > self.entry_rule.max_daily_entries_per_user:
- return False, "Entry would exceed max amount per user per day."
+ if (
+ self.entry_rule.max_entry_amount_per_user
+ and (self.user_amount + entry.amount)
+ > self.entry_rule.max_entry_amount_per_user
+ ):
+ return False, "Entry would exceed max amount per user."
+
+ if (
+ self.entry_rule.max_daily_entries_per_user
+ and (self.user_amount_today + entry.amount)
+ > self.entry_rule.max_daily_entries_per_user
+ ):
+ return False, "Entry would exceed max amount per user per day."
return True, ""
def is_user_eligible(self, country_iso: str) -> tuple[bool, str]:
@@ -296,16 +300,18 @@ class RaffleUserView(RaffleContest, ContestUserView):
if not passes:
return False, msg
- if self.entry_rule.max_entry_amount_per_user:
+ if self.entry_rule.max_entry_amount_per_user: # noqa: SIM102
# Greater or equal b/c we're asking if the user is eligible to
# enter MORE, now! If it equals, nothing is wrong, just that they
# are not eligible anymore.
if self.user_amount >= self.entry_rule.max_entry_amount_per_user:
return False, "Reached max amount per user."
- if self.entry_rule.max_daily_entries_per_user:
- if self.user_amount_today >= self.entry_rule.max_daily_entries_per_user:
- return False, "Reached max amount today."
+ if (
+ self.entry_rule.max_daily_entries_per_user
+ and self.user_amount_today >= self.entry_rule.max_daily_entries_per_user
+ ):
+ return False, "Reached max amount today."
# This would indicate something is wrong, as something else should have done this
e, _ = self.should_end()
diff --git a/generalresearch/models/thl/finance.py b/generalresearch/models/thl/finance.py
index 0856825..8c94390 100644
--- a/generalresearch/models/thl/finance.py
+++ b/generalresearch/models/thl/finance.py
@@ -124,10 +124,10 @@ class POPFinancial(BaseModel):
Direction,
)
- assert all([a.account_type == AccountType.BP_WALLET for a in accounts])
- assert all([a.normal_balance == Direction.CREDIT for a in accounts])
+ assert all(a.account_type == AccountType.BP_WALLET for a in accounts)
+ assert all(a.normal_balance == Direction.CREDIT for a in accounts)
if not is_debug():
- assert all([a.currency == "USD" for a in accounts])
+ assert all(a.currency == "USD" for a in accounts)
if input_data.empty:
return []
@@ -850,11 +850,11 @@ class BusinessBalances(BaseModel):
# Validate the input accounts
assert len(accounts) > 0, "Must provide accounts"
- assert all([a.account_type == AccountType.BP_WALLET for a in accounts])
- assert all([a.normal_balance == Direction.CREDIT for a in accounts])
+ assert all(a.account_type == AccountType.BP_WALLET for a in accounts)
+ assert all(a.normal_balance == Direction.CREDIT for a in accounts)
if not is_debug():
- assert all([a.currency == "USD" for a in accounts])
+ assert all(a.currency == "USD" for a in accounts)
# Validate the input dataframe
assert input_data.index.name == "account_id"
diff --git a/generalresearch/models/thl/product.py b/generalresearch/models/thl/product.py
index 76a8e83..a7ecd55 100644
--- a/generalresearch/models/thl/product.py
+++ b/generalresearch/models/thl/product.py
@@ -430,8 +430,8 @@ class UserWalletConfig(BaseModel):
@field_serializer("supported_payout_types", when_used="json")
def serialize_supported_payout_types_in_order(
self, supported_payout_types: set[PayoutType]
- ) -> set[PayoutType]:
- return set(sorted(supported_payout_types))
+ ) -> list[PayoutType]:
+ return sorted(supported_payout_types)
@field_validator("min_cashout", mode="after")
@classmethod
@@ -552,14 +552,14 @@ class PayoutTransformation(BaseModel):
min_payout = Decimal(0)
pct = Decimal(pct)
- payout = Decimal(payout)
+ _payout = Decimal(payout)
min_payout = Decimal(min_payout)
max_payout = Decimal(max_payout) if max_payout else None
- payout: Decimal = payout * pct
- payout: Decimal = max([payout, min_payout])
- payout: Decimal = min([payout, max_payout]) if max_payout else payout
- return payout
+ _payout: Decimal = _payout * pct
+ _payout: Decimal = max([_payout, min_payout])
+ _payout: Decimal = min([_payout, max_payout]) if max_payout else payout
+ return _payout
def payout_transformation_amt(
self, payout: Decimal, user_wallet_balance: Decimal | None = None
@@ -569,22 +569,22 @@ class PayoutTransformation(BaseModel):
# (display, adjustment) so ignore the 7-cent rounding.
if user_wallet_balance is None:
return self.payout_transformation_percent(payout=payout, pct=Decimal(".95"))
- payout = Decimal(payout)
+ _payout = Decimal(payout)
- payout: Decimal = payout * Decimal("0.95")
- new_balance = payout + user_wallet_balance
+ _payout: Decimal = _payout * Decimal("0.95")
+ new_balance = _payout + user_wallet_balance
# If the new_balance is <0, we aren't paying anything, so use the
# full amount
if new_balance < 0:
- return payout
+ return _payout
amt = (5 * math.floor((int(new_balance * 100) - 2) / 5)) + 2
rounded_new_balance = Decimal(amt / 100).quantize(Decimal("0.00"))
- payout = rounded_new_balance - user_wallet_balance
- if payout < Decimal(0):
+ _payout = rounded_new_balance - user_wallet_balance
+ if _payout < Decimal(0):
return Decimal(0)
- return payout
+ return _payout
class SourceConfig(BaseModel):
@@ -731,8 +731,8 @@ class SupplyConfig(BaseModel):
Use global config.
"""
d = self.global_scoped_policies_dict.copy()
- d.update(self.team_scoped_policies_dict.get(team_id, dict()))
- d.update(self.product_scoped_policies_dict.get(product_id, dict()))
+ d.update(self.team_scoped_policies_dict.get(team_id, {}))
+ d.update(self.product_scoped_policies_dict.get(product_id, {}))
return d
def get_config_for_product(self, product: Product) -> MergedSupplyConfig:
@@ -751,7 +751,7 @@ class SupplyConfig(BaseModel):
supply_policy=policy_dict[source],
source_config=sources_dict[source],
)
- for source in policy_dict.keys()
+ for source in policy_dict
]
)
@@ -1000,10 +1000,12 @@ class Product(BaseModel, validate_assignment=True):
@property
def business_uuid(self) -> UUIDStr:
+ assert self.business_id
return self.business_id
@property
def team_uuid(self) -> UUIDStr:
+ assert self.team_id
return self.team_id
@property
diff --git a/generalresearch/models/thl/profiling/other_option.py b/generalresearch/models/thl/profiling/other_option.py
index 6d789e5..2f3cac9 100644
--- a/generalresearch/models/thl/profiling/other_option.py
+++ b/generalresearch/models/thl/profiling/other_option.py
@@ -51,6 +51,4 @@ def option_is_catch_all(c: UpkQuestionChoice) -> bool:
return True
if c.text.lower() in texts_exact:
return True
- if any(t in c.text.lower() for t in texts_in):
- return True
- return False
+ return bool(any(t in c.text.lower() for t in texts_in))
diff --git a/generalresearch/models/thl/profiling/upk_question.py b/generalresearch/models/thl/profiling/upk_question.py
index 77bba6f..3bb0733 100644
--- a/generalresearch/models/thl/profiling/upk_question.py
+++ b/generalresearch/models/thl/profiling/upk_question.py
@@ -475,10 +475,9 @@ class UpkQuestion(BaseModel):
# Almost nothing has >1k options, besides location stuff (cities,
# etc.) which should get harmonized. When presenting them, we'll
# filter down options to at most 50.
- if self.choices and (len(self.choices) <= 1 or len(self.choices) > 1000):
- return False
-
- return True
+ return not (
+ self.choices and (len(self.choices) <= 1 or len(self.choices) > 1000)
+ )
@property
def md5sum(self):
@@ -534,7 +533,7 @@ class UpkQuestion(BaseModel):
), "Multiple of the same answer submitted"
if self.type == UpkQuestionType.MULTIPLE_CHOICE:
assert len(answer) >= 1, "MC question with no selected answers"
- choice_codes = set(x.id for x in self.choices)
+ choice_codes = {x.id for x in self.choices}
if self.selector == UpkQuestionSelectorMC.SINGLE_ANSWER:
assert (
len(answer) == 1
@@ -563,9 +562,7 @@ class UpkQuestion(BaseModel):
assert len(answer) == 1, "Only one answer allowed"
answer = answer[0]
assert len(answer) > 0, "Must provide answer"
- max_length = (
- self.configuration.max_length if self.configuration else 0 or 100000
- )
+ max_length = self.configuration.max_length if self.configuration else 100000
assert len(answer) <= max_length, "Answer longer than allowed"
if self.validation and self.validation.patterns:
for pattern in self.validation.patterns:
diff --git a/generalresearch/models/thl/profiling/user_question_answer.py b/generalresearch/models/thl/profiling/user_question_answer.py
index 2db07b7..378345e 100644
--- a/generalresearch/models/thl/profiling/user_question_answer.py
+++ b/generalresearch/models/thl/profiling/user_question_answer.py
@@ -3,7 +3,7 @@ from __future__ import annotations
import json
from collections.abc import Iterator
from datetime import UTC, datetime, timedelta
-from typing import Any, Literal, Self
+from typing import Any, Literal
from pydantic import (
BaseModel,
@@ -14,7 +14,6 @@ from pydantic import (
model_validator,
)
-from generalresearch.grpc import timestamp_to_datetime
from generalresearch.models import MAX_INT32, Source
from generalresearch.models.custom_types import AwareDatetimeISO, UUIDStr
from generalresearch.models.thl.locales import CountryISO, LanguageISO
@@ -39,19 +38,23 @@ class UserQuestionAnswer(BaseModel):
calc_answers: dict[str, tuple[str, ...]] | None = Field(default=None)
@field_validator("calc_answers")
- def sorted_calc_answers(cls, calc_answers) -> dict[str, tuple[str, ...]] | None:
+ def sorted_calc_answers(
+ cls, calc_answers: dict[str, tuple[str, ...]] | None
+ ) -> dict[str, tuple[str, ...]] | None:
if calc_answers is None:
return None
return {k: tuple(sorted(v)) for k, v in calc_answers.items()}
@field_validator("calc_answers")
- def validate_keys(cls, calc_answers) -> dict[str, tuple[str, ...]] | None:
+ def validate_keys(
+ cls, calc_answers: dict[str, tuple[str, ...]] | None
+ ) -> dict[str, tuple[str, ...]] | None:
if calc_answers is None:
return None
assert all(
- ":" in k for k in calc_answers.keys()
+ ":" in k for k in calc_answers
), "calc_answers expects the keys to be in format source:question_code"
return calc_answers
@@ -66,6 +69,7 @@ class UserQuestionAnswer(BaseModel):
return d
def get_mrpqs(self) -> Iterator[MarketplaceResearchProfileQuestion]:
+ assert self.calc_answers
for k, v in self.calc_answers.items():
source, question_code = k.split(":", 1)
yield MarketplaceResearchProfileQuestion(
@@ -105,21 +109,6 @@ class UserQuestionAnswer(BaseModel):
def is_stale(self) -> bool:
return self.timestamp < datetime.now(tz=UTC) - timedelta(days=30)
- @classmethod
- def from_grpc(cls, msg, default_timestamp: datetime) -> Self:
- """
- Handles correctly issues with grpc timestamps
- :param msg: "thl.protos.generalresearch_pb2.ProfilingQuestionAnswer"
- """
- assert default_timestamp.tzinfo is not None, "must use tz-aware timestamps"
- timestamp = timestamp_to_datetime(msg.timestamp)
- timestamp = default_timestamp if timestamp < datetime(2000, 1, 1) else timestamp
- return cls(
- question_id=msg.question_id,
- answer=tuple(msg.answer),
- timestamp=timestamp,
- )
-
# We can't set a redis list to [] vs None. We'll push this dummy answer into
# the cache to signify the user has no answered questions. It'll get removed
@@ -131,7 +120,7 @@ DUMMY_UQA = UserQuestionAnswer(
country_iso="xx",
language_iso="xxx",
property_code="dummy",
- calc_answers=dict(),
+ calc_answers={},
)