Skip to content

digital-dumbo/engagement-decision-service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Engagement Decision Service

Ruby translation of the analytics-service module with a Rails-compatible structure and a Rails-native rules-engine flow.

This component mirrors the Rust service responsibilities:

  • interaction event ingestion
  • Redis hot-state management
  • PostgreSQL persistence boundaries
  • deterministic rule evaluation
  • notification candidate persistence
  • Mixpanel dispatch persistence and processing

The preferred Rails-style execution path is:

  • thin controller entry point
  • TrackEventService for event creation
  • EvaluateRulesJob for async rule evaluation
  • RulesEngine::Processor for cooldown-aware matching
  • PushNotificationJob for notification dispatch

Documentation

Run Locally

Prerequisites

  • Homebrew Ruby on PATH
  • PostgreSQL running locally
  • Redis running locally

The verified local toolchain on this machine is:

  • Ruby 3.4.4
  • Bundler 2.6.9
  • PostgreSQL 17
  • Redis 8

Debug Endpoint Auth

All /api/v1/debug/* endpoints now require a bearer token.

The runtime config key is:

  • ANALYTICS_SERVICE_DEBUG_AUTH_TOKEN

Local default:

  • engagement-decision-local-debug-token

Install Gems

cd /Users/ab000111/Downloads/app-dazzle/api-service/engagement-decision-service
PATH=/opt/homebrew/opt/ruby/bin:$PATH bundle install

Start PostgreSQL And Redis

brew services start postgresql@17
brew services start redis

Quick checks:

PATH=/opt/homebrew/opt/postgresql@17/bin:$PATH psql -d postgres -c 'select current_user, current_database();'
PATH=/opt/homebrew/opt/redis/bin:$PATH redis-cli ping

Expected:

  • PostgreSQL query returns one row
  • Redis returns PONG

Create And Migrate Databases

cd /Users/ab000111/Downloads/app-dazzle/api-service/engagement-decision-service
PATH=/opt/homebrew/opt/ruby/bin:$PATH bundle exec rails db:create
PATH=/opt/homebrew/opt/ruby/bin:$PATH bundle exec rails db:migrate

Seed A Local User

cd /Users/ab000111/Downloads/app-dazzle/api-service/engagement-decision-service
PATH=/opt/homebrew/opt/ruby/bin:$PATH bundle exec rails runner 'user = User.find_or_create_by!(email: "local-user@example.com") { |u| u.name = "Local User" }; puts user.id'

The examples below assume the printed user id is 1.

Event Idempotency

The standalone app now persists the external event_id from the request payload and treats it as the idempotency key for ingestion.

  • first request with a new event_id returns 201 Created and status: "ok"
  • repeated request with the same event_id returns 200 OK and status: "duplicate"
  • the duplicate response returns the original event record instead of creating a second row

Each response now also includes an ingestion object with:

  • status: accepted, duplicate, or failed
  • attempt_id: the persisted ingestion_attempts.id
  • correlation_id: the request correlation id used in structured logs

The service now audits every HTTP ingest attempt in ingestion_attempts, including accepted requests, duplicate replays, and failure paths such as unknown-user requests.

Start The App

Terminal 1, Puma:

cd /Users/ab000111/Downloads/app-dazzle/api-service/engagement-decision-service
PATH=/opt/homebrew/opt/ruby/bin:$PATH bundle exec rails server -b 127.0.0.1 -p 3001

Terminal 2, Sidekiq:

cd /Users/ab000111/Downloads/app-dazzle/api-service/engagement-decision-service
PATH=/opt/homebrew/opt/ruby/bin:$PATH bundle exec sidekiq -C config/sidekiq.yml

Basic Smoke Check

curl -sS http://127.0.0.1:3001/health

Expected:

{"status":"ok","service":"engagement-decision-service"}

Example Requests

Repeat Salon View Without Service Add

Post two salon_detail_viewed events for the same session and salon:

curl -sS -X POST http://127.0.0.1:3001/api/v1/events \
  -H 'Content-Type: application/json' \
  -d '{"user_id":1,"event_type":"salon_detail_viewed","properties":{"event_id":"evt-local-a1","session_id":"session-repeat-1","salon_id":"salon-repeat-1","platform":"ios","screen_name":"salon_detail"}}'

curl -sS -X POST http://127.0.0.1:3001/api/v1/events \
  -H 'Content-Type: application/json' \
  -d '{"user_id":1,"event_type":"salon_detail_viewed","properties":{"event_id":"evt-local-a2","session_id":"session-repeat-1","salon_id":"salon-repeat-1","platform":"ios","screen_name":"salon_detail"}}'

Inspect the decision:

curl -sS http://127.0.0.1:3001/api/v1/debug/session-decisions/session-repeat-1 \
  -H 'Authorization: Bearer engagement-decision-local-debug-token'

Expected outcome:

  • rule_id = repeat_salon_view_without_service_add
  • one notification_candidate

Service Added No Next

Post one service_added event:

curl -sS -X POST http://127.0.0.1:3001/api/v1/events \
  -H 'Content-Type: application/json' \
  -d '{"user_id":1,"event_type":"service_added","properties":{"event_id":"evt-local-b1","session_id":"session-service-1","salon_id":"salon-service-1","service_id":"service-1","platform":"ios","screen_name":"salon_detail","cart_value":299}}'

Inspect the decision:

curl -sS http://127.0.0.1:3001/api/v1/debug/session-decisions/session-service-1 \
  -H 'Authorization: Bearer engagement-decision-local-debug-token'

Expected outcome:

  • rule_id = service_added_no_next
  • priority = high

Duplicate Event Replay

curl -sS -X POST http://127.0.0.1:3001/api/v1/events \
  -H 'Content-Type: application/json' \
  -d '{"user_id":1,"event_type":"salon_detail_viewed","properties":{"event_id":"evt-local-dup-1","session_id":"session-dup-1","salon_id":"salon-dup-1","platform":"ios","screen_name":"salon_detail"}}'

curl -sS -X POST http://127.0.0.1:3001/api/v1/events \
  -H 'Content-Type: application/json' \
  -d '{"user_id":1,"event_type":"salon_detail_viewed","properties":{"event_id":"evt-local-dup-1","session_id":"session-dup-1","salon_id":"salon-dup-1","platform":"ios","screen_name":"salon_detail"}}'

Expected outcome:

  • first response has status = "ok"
  • second response has status = "duplicate"
  • both responses point to the same event record id
  • the two responses have different ingestion.attempt_id values because each HTTP attempt is persisted separately

Hot State Debug

curl -sS http://127.0.0.1:3001/api/v1/debug/session-hot-state/1/session-repeat-1 \
  -H 'Authorization: Bearer engagement-decision-local-debug-token'

This returns:

  • last_ten_events
  • recent_window_events

Ingestion Attempt Debug

Inspect persisted HTTP ingest attempts without querying Postgres directly:

curl -sS "http://127.0.0.1:3001/api/v1/debug/ingestion-attempts?event_id=evt-local-dup-1" \
  -H 'Authorization: Bearer engagement-decision-local-debug-token'

Supported filters:

  • event_id
  • correlation_id
  • session_id
  • status
  • limit

Example for failed ingests:

curl -sS "http://127.0.0.1:3001/api/v1/debug/ingestion-attempts?status=failed&limit=10" \
  -H 'Authorization: Bearer engagement-decision-local-debug-token'

This returns the persisted ingestion_attempts rows, including:

  • status
  • failure_type
  • correlation_id
  • request metadata
  • metadata.attempted_user_id for unknown-user failures

Requests without the bearer token return 401 unauthorized.

Logs And Troubleshooting

Puma

Puma logs request handling, controller selection, and ActiveRecord queries directly in the server terminal.

Useful checks:

  • request reached the right controller
  • 201 Created on POST /api/v1/events
  • no RoutingError or Unprocessable Entity

Sidekiq

Sidekiq logs:

  • job enqueue and execution
  • EvaluateRulesJob
  • PushNotificationJob
  • failures in rule evaluation or dispatch persistence

For a successful repeat-view flow you should see:

  • EvaluateRulesJob start and finish
  • PushNotificationJob enqueued for repeat_salon_view_without_service_add

Redis

Inspect hot-state keys:

PATH=/opt/homebrew/opt/redis/bin:$PATH redis-cli keys 'analytics/*'

Test Suite

Run the standalone module tests:

cd /Users/ab000111/Downloads/app-dazzle/api-service/engagement-decision-service
PATH=/opt/homebrew/opt/ruby/bin:$PATH ruby -I lib test/run_all.rb

Current verified result:

  • 29 runs, 81 assertions, 0 failures, 0 errors, 0 skips

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages