Skip to content

Repository files navigation

Belle Operation

Small ops dashboard for managing today's visits. Built for the Belle take-home.

Ruby 3.4.9 · Rails 8.1.3 · PostgreSQL · UUID primary keys

Setup

You need Ruby, PostgreSQL, and Bundler installed locally.

bin/setup --skip-server   # gems, db, seed from JSON
bin/dev                   # http://localhost:3000

bin/setup loads db/seed_data/visits.json and reschedule_requests.json. Safe to run again.

Re-seed only:

bin/rails db:seed

"Today" on the dashboard is hardcoded to 2026-06-29 (Visit::OPERATING_DATE) so the demo matches the dataset.

Tests and lint

bundle exec rspec
bin/rubocop
bin/brakeman
bin/bundler-audit
bin/ci                    # runs the lot locally

CI on GitHub runs the same checks in parallel (see .github/workflows/ci.yml).

What I focused on

I spent most of my time in domain logic, models, and RSpec. That's where I think this project is strongest.

The two pieces that matter most to me:

1. Cancellation fee recommendation (Visits::CancellationFeePolicy)
This was my top priority. When a visit falls through, Belle eats the CHW drive time and the slot goes empty — that's direct revenue leakage. The policy answers "should ops recommend charging this member?" based on how the visit failed: on-site/no-show gets a fee, last-minute remote cancel gets a fee, enough notice or hospitalized gets waived, unclear data goes to review. It never charges anyone; it just gives the coordinator a clear recommendation and reason.

2. Risk scoring (Visits::RiskScorer)
Second priority. I wanted a simple way to spot members who keep cancelling or no-showing so ops can act before sending a CHW out again. Prior no-shows, prior cancellations (derived from the member's other cancelled visits), confirmation signals, and same-day failure all add points. High risk → call and confirm, maybe line up a backfill from the reschedule queue.

The reschedule queue is basic on purpose: pending members who need an open slot, and a separate tab for assignments waiting on confirmation. No second scoring system — reliability lives in the one risk model.

Architecture

Light DDD split: models for persistence, domain objects for business rules, query objects for the dashboard, thin controllers, plain ERB views.

app/domains/visits/cancellation_fee_policy.rb
app/domains/visits/risk_scorer.rb
app/queries/ops/dashboard_query.rb

Fee policy and risk scorer specs run without the database (plain attribute objects).

Gems

Testing & quality

  • rspec-rails — domain logic, models, request specs
  • rubocop-rails-omakase — Rails-flavored RuboCop defaults (bin/rubocop)
  • brakeman — static security scan on the codebase
  • bundler-audit — checks gems against known CVEs
  • bullet — dev-only N+1 query warnings (caught the reschedule eager-load issue)

Other

  • ostruct — lightweight visit stand-ins in pure domain specs (no DB)
  • bootsnap — faster boot; Rails default

I used AI mostly for architecture discussions — where to draw boundaries, what to leave out given the time box, tradeoffs on the fee rules and risk signals.

I leaned on it more for views and templating (Tailwind markup, Stimulus pagination, the reschedule tabs). The domain code and tests I wrote and iterated on myself.

Left out

No auth, no background jobs, no real billing integration, no scheduling engine. Fee recommendations are internal only.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages