From e5a3e308a52496ccdc7faf90eec3d9e6aa9918d0 Mon Sep 17 00:00:00 2001 From: stuppie Date: Mon, 5 Oct 2026 11:21:37 -0600 Subject: ProductBalances and ProductUserWalletBalances to_prometheus. Add a test --- tests/models/thl/test_product.py | 92 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) (limited to 'tests/models/thl/test_product.py') diff --git a/tests/models/thl/test_product.py b/tests/models/thl/test_product.py index 97abf0c..5a3e488 100644 --- a/tests/models/thl/test_product.py +++ b/tests/models/thl/test_product.py @@ -26,6 +26,7 @@ from generalresearch.models.thl.product import ( SourcesConfig, SupplyConfig, SupplyPolicy, + UserWalletConfig, ) if TYPE_CHECKING: @@ -696,6 +697,8 @@ class TestProductFinancials: assert p1.balance.retainer == 35 assert p1.balance.available_balance == 108 + body, content_type = p1.balance.to_prometheus() + p1.prebuild_payouts( thl_lm=thl_ledger_manager, bp_pem=brokerage_product_payout_event_manager, @@ -803,6 +806,95 @@ class TestProductFinancials: assert p1.payouts_total == 55 assert p1.payouts_total_str == "$0.55" + def test_balance_user_wallet( + self, + gr_business: Business, + product_factory: Callable[..., Product], + user_factory: Callable[..., User], + mnt_filepath: GRLDatasets, + thl_ledger_manager: ThlLedgerManager, + start: datetime, + session_with_tx_factory: Callable[..., Session], + delete_ledger_db: Callable[..., None], + create_main_accounts: Callable[..., None], + client_no_amm: DaskClient, + ledger_collection: LedgerDFCollection, + pop_ledger_merge: PopLedgerMerge, + delete_df_collection: Callable[..., None], + payout_config, + ): + delete_ledger_db() + create_main_accounts() + delete_df_collection(coll=ledger_collection) + + p1: Product = product_factory( + business=gr_business, + user_wallet_config=UserWalletConfig(enabled=True), + payout_config=payout_config, + ) + u1: User = user_factory(product=p1) + bp_wallet = thl_ledger_manager.get_account_or_create_bp_wallet(product=p1) + user_wallet = thl_ledger_manager.get_account_or_create_user_wallet(user=u1) + + session_with_tx_factory( + user=u1, + wall_req_cpi=Decimal("1.00"), + started=start + timedelta(days=1), + ) + assert ( + thl_ledger_manager.get_account_balance(account=bp_wallet) == 57 + ) # 95 * 60% + assert ( + thl_ledger_manager.get_account_balance(account=user_wallet) == 38 + ) # 95-57 + + session_with_tx_factory( + user=u1, + wall_req_cpi=Decimal("1.00"), + started=start + timedelta(days=2), + ) + txs = thl_ledger_manager.get_tx_filtered_by_account(bp_wallet.uuid) + assert len(txs) == 2 + + with pytest.raises(expected_exception=AssertionError) as cm: + p1.prebuild_balance( + thl_lm=thl_ledger_manager, + ds=mnt_filepath, + client=client_no_amm, + ) + assert "Cannot build Product Balance" in str(cm.value) + + ledger_collection.initial_load(client=None, sync=True) + pop_ledger_merge.build(client=client_no_amm, ledger_coll=ledger_collection) + + p1.prebuild_balance( + thl_lm=thl_ledger_manager, + ds=mnt_filepath, + client=client_no_amm, + ) + assert isinstance(p1.balance, ProductBalances) + assert p1.balance.payout == 114 + assert p1.balance.adjustment == 0 + assert p1.balance.expense == 0 + assert p1.balance.net == 114 + assert p1.balance.balance == 114 + + body, content_type = p1.balance.to_prometheus() + + p1.prebuild_private_balance( + thl_lm=thl_ledger_manager, + ds=mnt_filepath, + client=client_no_amm, + ) + assert p1.private_balance.commission == 5 * 2 + + p1.prebuild_user_wallet_balances( + ds=mnt_filepath, + client=client_no_amm, + ) + assert p1.user_wallet_balance.outstanding_liability == 38 * 2 + body, content_type = p1.user_wallet_balance.to_prometheus() + class TestProductBalance: @pytest.fixture -- cgit v1.2.3 From 8c6b9cd45fbeb0400f51356e4540943c8d21a886 Mon Sep 17 00:00:00 2001 From: stuppie Date: Mon, 5 Oct 2026 12:38:37 -0600 Subject: cache balance and user_wallet_balance in redis hash separately for easier bulk retrieval --- generalresearch/models/thl/product.py | 26 +++++++++++++++++++++++++- tests/models/thl/test_product.py | 12 ++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) (limited to 'tests/models/thl/test_product.py') diff --git a/generalresearch/models/thl/product.py b/generalresearch/models/thl/product.py index 8f8cc4b..16288f6 100644 --- a/generalresearch/models/thl/product.py +++ b/generalresearch/models/thl/product.py @@ -84,6 +84,12 @@ if TYPE_CHECKING: from generalresearch.models.thl.ledger import LedgerAccount +PRODUCT_BALANCES_METRICS_CACHE_KEY = "metrics:product_balances" +PRODUCT_USER_WALLET_BALANCES_METRICS_CACHE_KEY = ( + "metrics:product_user_wallet_balances" +) + + # fmt: off GRS_SKINS = [ "mmfwcl.com", "profile.generalresearch.com", @@ -1475,8 +1481,26 @@ class Product(BaseModel, validate_assignment=True): # a delay in the incite merge file not being built yet. # bal = thl_lm.get_account_balance_timerange(time_end=) + assert self.balance is not None rc = redis_config.create_redis_client() - rc.set(name=self.cache_key, value=self.model_dump_json(), ex=timedelta(days=3)) + with rc.pipeline() as pipe: + pipe.set( + name=self.cache_key, + value=self.model_dump_json(), + ex=timedelta(days=3), + ) + pipe.hset( + name=PRODUCT_BALANCES_METRICS_CACHE_KEY, + key=self.uuid, + value=self.balance.model_dump_json(), + ) + if self.user_wallet_balance is not None: + pipe.hset( + name=PRODUCT_USER_WALLET_BALANCES_METRICS_CACHE_KEY, + key=self.uuid, + value=self.user_wallet_balance.model_dump_json(), + ) + pipe.execute() def determine_bp_payment(self, thl_net: Decimal) -> Decimal: """ diff --git a/tests/models/thl/test_product.py b/tests/models/thl/test_product.py index 5a3e488..e45bd42 100644 --- a/tests/models/thl/test_product.py +++ b/tests/models/thl/test_product.py @@ -1174,6 +1174,18 @@ class TestProductCache: assert p1.balance.retainer_usd_str == "$0.17" assert p1.balance.available_balance_usd_str == "$0.54" + from generalresearch.models.thl.product import ( + PRODUCT_BALANCES_METRICS_CACHE_KEY, + ) + + metrics_balance_json = rc.hget( + PRODUCT_BALANCES_METRICS_CACHE_KEY, + product.uuid, + ) + assert isinstance(metrics_balance_json, str) + metrics_balance = ProductBalances.model_validate_json(metrics_balance_json) + assert metrics_balance == p1.balance + def test_neg_balance_cache( self, product: Product, -- cgit v1.2.3