Skip to content

Latest commit

 

History

History
84 lines (54 loc) · 3.42 KB

File metadata and controls

84 lines (54 loc) · 3.42 KB

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.