context
A Telegram platform with incentivized tasks: users submit step completions for partner projects, moderators validate them via admin handlers, and once a month the system distributes the budget through a pool-based scheme on top of review-points. The bot is responsible for computing the distribution, storing wallet addresses and broadcasting results; on-chain settlement is done separately using the PLpgSQL output. Over 7 months — growth from 25k to 63k MAU, more than 1M manual task reviews.
tradeoffs
- Scoring inside PLpgSQL stored procedures vs application level: moved it into the DB — atomicity with the leaderboard write, no network round-trips, no way to accidentally compute half the picture; the cost is harder unit testing, the win is simple auditable SQL logic that can be git-tracked.
- Pool-based distribution (winner_flag + price_if_last) vs flat per-task: a pool scheme prevents a single user from dominating and keeps distribution fair across the long tail, at the cost of needing SQL window functions.
- Mozilla Fluent through fluentogram vs gettext: Fluent gives inline pluralization and a DSL for business phrases; fluentogram gives async integration with aiogram. The .ftl locales in the repo are the textual source of truth.
- Jupyter notebook for monthly budget calibration instead of a real-time dashboard: cheaper to maintain (1 notebook vs a Grafana stack), faster iteration on formulas against historical data before each distribution round.
- docker-compose over k8s: a compact set of services on one host — k8s overhead is not justified.
architecture
The Telegram webhook hits the bot service on aiogram (with Mozilla Fluent through fluentogram for i18n, ru/en .ftl locales). Submission/review workflow: a user submits a completion via FSM on Redis, a moderator validates it through admin handlers. Payout computation lives entirely in Postgres: a set of PLpgSQL stored procedures (compile_leaderboard, compile_monthly_rewards, compile_weekly_results, compile_referred_rewards) applies a pool-based algorithm (winner_flag + price_if_last on top of review-points) to review/submission data. User wallet addresses are stored in Postgres; on-chain settlement runs as a separate process over the SQL output, the bot then broadcasts a payout notification. The audit module logs balance operations. FastAPI/uvicorn powers the webhook runner and admin area.
results
- $138k+ processed through the payout system over 7 months at 63k+ MAU.
- pool-based reward distribution fully in SQL — atomic computation, auditable formula.
- >1M manual task reviews by moderators through the bot flow.
- i18n: ru/en via Fluent + fluentogram, .ftl locales as the source of truth.
- compact stack in docker-compose; bot + admin + webhook runner on a single host.
takeaways
- PLpgSQL stored procedures for financial scoring pay off when the formula changes monthly — git-tracked migrations give clean history without an application redeploy and atomicity at the transaction level.
- SQL window functions (FIRST_VALUE OVER PARTITION, ROWS UNBOUNDED PRECEDING) make complex distributions declarative — what would otherwise become an imperative loop with manual aggregation in application code.
- Fluent + fluentogram raises the bar for i18n in products with a multilingual audience — native pluralization and contextual variants, killing a whole class of manual-formatting bugs.