-
Notifications
You must be signed in to change notification settings - Fork 11
New Usecase: real-time promotion engine #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
tabVersion
wants to merge
3
commits into
main
Choose a base branch
from
tab/promotion-rule-engine
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 1 commit
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
I'll translate the README into English while keeping neutral terminology, update the file, then run a quick lint check. | ||
|
||
[1 tool called] | ||
|
||
I'll open the current README to translate it fully into English while keeping terminology neutral, then update the file. | ||
|
||
[1 tool called] | ||
|
||
I opened the current README and prepared the full English translation below. | ||
|
||
## Real-time Promotion Rule Engine (RisingWave-based) | ||
|
||
A real-time promotion rule engine for membership and loyalty use cases, built on RisingWave streaming with Kafka events. When members perform actions such as signup, activity, or points updates, the engine matches against configurable promotion rules using member profiles, computes capped rewards, and exposes queryable results and analytics views. | ||
|
||
- **Real-time events**: Kafka event stream (aligned with the script configuration) | ||
- **Member profile**: `member_profile` dimension table | ||
- **Rule configuration**: `promotion_rules` table (supports areas/event types/thresholds) | ||
- **Rule engine**: a pipeline of materialized views for matching and reward calculation | ||
- **Outputs**: `mv_promotion_results`, `mv_promotion_stats`, `mv_member_promotion_summary` | ||
|
||
### Use cases | ||
- **Welcome/acquisition**: rewards triggered on member signup | ||
- **Activity incentives**: rewards driven by area and activity intensity/spend | ||
- **Tier benefits**: promotions gated by card tier, ADT/ODT, lodging spend | ||
|
||
## Architecture and data flow | ||
1. Kafka produces member events; RisingWave consumes them via source table `user_events` | ||
2. `member_profile` and `promotion_rules` provide profile and rule configuration | ||
3. Materialized views perform: recent event filter → active rules + area expansion → event–rule matching → profile validation → reward calculation and capping → results and stats aggregation | ||
|
||
## Data model (core tables) | ||
- **`user_events`**: event stream (Kafka source) | ||
- `event_type`: `signup` | `gaming` | `rating_update` | ||
- `event_dt`: processing time (`proctime()`) | ||
- `area`: `Pit01/02/03`, `Slot01/02` | ||
- `gaming_value`, `rating_points`, `metadata` | ||
|
||
- **`member_profile`**: member profile dimension table | ||
- `card_tier`: `Bronze/Silver/Gold/Platinum` | ||
- `adt/odt/lodger`: theoretical/observed daily metrics and lodging spend | ||
- `preferred_area`, `status`, points, etc. | ||
|
||
- **`promotion_rules`**: promotion rule configuration | ||
- `trigger_type`: event trigger type (aligned with `events.event_type`) | ||
- `promotion_from/to`: campaign start/end dates | ||
- `earning_type/earning_days`: accounting semantics and valid days (model placeholders) | ||
- `criteria_*`: profile thresholds (ADT/ODT/lodging) | ||
- `areas`: applicable areas array | ||
- `reward_value/reward_max`: base reward and cap | ||
- `status`: `ACTIVE/INACTIVE` | ||
|
||
## Rule engine pipeline (materialized views) | ||
- **`mv_recent_events`**: keep only last 24 hours of events to reduce noise | ||
- **`mv_active_rules`**: filter `ACTIVE` rules | ||
- **`mv_rule_areas_expanded`**: unnest `areas` for efficient matching | ||
- **`mv_event_rule_matches`**: match by trigger type and area | ||
- **`mv_complete_rule_matches`**: join member profile and validate thresholds | ||
- **`mv_reward_calculations`**: compute dynamic multipliers and capped rewards | ||
- **`mv_promotion_results`**: final promotions and `promotion_code` | ||
- **`mv_promotion_stats`**: aggregated stats by rule and event type | ||
- **`mv_member_promotion_summary`**: member-level promotion summary | ||
|
||
### Reward calculation highlights | ||
- Dynamic multiplier by event type: | ||
- `signup`: 1.0 | ||
- `gaming`: `max(gaming_value/100, 1.0)` | ||
- `rating_update`: `max(rating_points/50, 1.0)` | ||
- Final reward: `final_reward = min(reward_value * multiplier, reward_max)` | ||
|
||
## Quick start | ||
### Prerequisites | ||
- Kafka is deployed and the topic name matches the script configuration | ||
- RisingWave (or a compatible streaming database) is available with SQL access | ||
|
||
### Deploy the SQL | ||
Import the script into the database (example with `psql`): | ||
```bash | ||
psql -h <host> -p <port> -U <user> -d <database> -f casino_promotion_system_fixed.sql | ||
tabVersion marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
``` | ||
|
||
### Ingest events (sample JSON) | ||
Write JSON events to the Kafka topic (aligned with the `events` source): | ||
```json | ||
{"event_id":1,"member_id":100000001,"event_type":"signup","area":"Pit01","gaming_value":0,"rating_points":0,"metadata":{}} | ||
``` | ||
```json | ||
{"event_id":2,"member_id":100000005,"event_type":"gaming","area":"Pit01","gaming_value":420,"rating_points":0,"metadata":{"activity":"typeA"}} | ||
``` | ||
```json | ||
{"event_id":3,"member_id":100000003,"event_type":"rating_update","area":"Slot01","gaming_value":0,"rating_points":120,"metadata":{}} | ||
``` | ||
|
||
> Note: `user_events.event_dt` uses `proctime()`, so no event timestamp is required. | ||
|
||
### Validate queries | ||
After importing sample data, validate directly via SQL: | ||
```sql | ||
-- Matches | ||
SELECT * FROM mv_promotion_results ORDER BY event_dt DESC LIMIT 20; | ||
|
||
-- Rule stats | ||
SELECT * FROM mv_promotion_stats ORDER BY total_matches DESC; | ||
|
||
-- Member summary | ||
SELECT * FROM mv_member_promotion_summary ORDER BY total_promotions DESC LIMIT 20; | ||
``` | ||
|
||
## How to extend | ||
- **Add a new promotion rule**: insert into `promotion_rules` with `trigger_type`, `areas`, thresholds/rewards | ||
- **Add a new area**: include the area string in `areas`; `mv_rule_areas_expanded` will unnest it | ||
- **Add a new event type**: | ||
- Use the new type in `user_events.event_type` and `promotion_rules.trigger_type` | ||
- Extend the CASE logic in `mv_reward_calculations` for multiplier and reward | ||
- **Change the time window**: adjust `NOW() - INTERVAL '24 HOURS'` in `mv_recent_events` | ||
|
||
## Design constraints and trade-offs | ||
- Only last 24 hours of events participate in matching to keep it real-time | ||
- Rule activation via `status='ACTIVE'` allows hot toggling | ||
- `areas` as array + unnest balances flexibility and performance | ||
- Numeric fields use `decimal`; consider precision vs. performance per engine | ||
|
||
## Directory structure | ||
- `pipeline.sql`: complete DDL/DML and materialized views implementing the rule engine |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.