Conversation
New incremental balance model for Bitcoin using the established
forward-fill pattern from stablecoins_tron_balances_from_transfers.
- Tracks UTXO-based balances: outputs as deposits, inputs as withdrawals
- Proper satoshi-to-BTC conversion (/ 1e8)
- Forward-fills balances across inactive days
- Incremental merge with prior_balances from {{ this }}
- Partitioned by day with incremental_predicates
- Includes unique_combination_of_columns + not_null tests
Co-authored-by: Cursor <cursoragent@cursor.com>
PR SummaryMedium Risk Overview The model is incremental/merge partitioned by Written by Cursor Bugbot for commit 150cc7f. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
| left join {{ source('prices', 'usd') }} as p | ||
| on f.day = p.minute | ||
| and p.symbol = 'BTC' | ||
| and p.blockchain is null |
There was a problem hiding this comment.
Price join may produce duplicate rows
High Severity
The join f.day = p.minute compares a date column with a timestamp column. In Spark SQL this typically casts the timestamp to date, so all price rows whose date matches f.day are returned. If prices.usd has multiple rows per day for BTC (e.g. minute or hourly granularity), the join produces multiple rows per (day, wallet_address), violating the model unique_key and causing merge failures or incorrect data.
| and p.symbol = 'BTC' | ||
| and p.blockchain is null | ||
| where f.balance_satoshi > 0 | ||
| and f.wallet_address is not null |
There was a problem hiding this comment.
Zero-balance rows excluded, diverges from reference pattern
Medium Severity
The model filters f.balance_satoshi > 0, which drops the day a wallet empties. The reference pattern in stablecoins_tron_balances_from_transfers and stablecoins_balances_from_transfers keeps zero-balance rows when cast(f.last_updated as date) = f.day so the day of the actual balance change is recorded.
| {% if is_incremental() %} | ||
| and {{ incremental_predicate('i.block_time') }} | ||
| {% endif %} | ||
| ) |
There was a problem hiding this comment.
Missing null checks on block_date and block_time
Medium Severity
The deposits and withdrawals CTEs do not filter out rows where block_date or block_time is null. Null block_date becomes null day, which flows into daily_aggregated and changed_balances. The forward-fill join d.day >= b.day fails to match when b.day is null, so those transactions are dropped. Transactions with null block_time can produce null last_updated and affect ordering in window functions.


Summary
balances_bitcoin_dailyincremental model that tracks daily BTC balances per wallet addressstablecoins_tron_balances_from_transfers— the same architecture used across all stablecoin balance modelsbalances_bitcoin_satoshi_daywhich had a missing satoshi→BTC conversion, bogus "profit"/"total_asset" columns, and unmaterialized views that couldn't execute at scaleHow it works
bitcoin.outputsas deposits (credit),bitcoin.inputsas withdrawals (debit){{ this }}to avoid recomputing from genesislead(day)+ date spinesatoshi / 1e8 * BTC priceat the final layerOutput columns
blockchain'bitcoin'daywallet_addressbalance_satoshibalance_btcbalance_satoshi / 1e8)btc_price_usdbalance_usdlast_updatedConfig
materialized = 'incremental'withmergestrategypartition_by = ['day']unique_key = ['day', 'wallet_address']incremental_predicatesfor partition pruningunique_combination_of_columns+not_nullonbalance_satoshiTest plan
dbt compile --select balances_bitcoin_daily✅ verified locally)Made with Cursor