Skip to content

Technical Architecture

Technical Architecture

HCP is built on modern web technologies optimised for mobile field use.

Tech Stack

Backend

ComponentTechnologyVersion
FrameworkRuby on Rails8.1.1
LanguageRuby3.3+
DatabaseSQLite (dev), PostgreSQL (prod)3.x / 14+
Authenticationhas_secure_passwordBuilt-in
AuthorizationPundit2.3+
JobsSolid QueueBuilt-in
CachingSolid CacheBuilt-in

Frontend

ComponentTechnology
FrameworkHotwire (Turbo + Stimulus)
StylingTailwind CSS v4
ThemeShad-CN with OKLCH colors
Scanning@zxing/browser
IconsLucide

Infrastructure

ComponentTechnology
ContainerDocker
DeploymentKamal
ProxyThruster
ServerPuma

Data Model

Core Entities

# Organisation — Top-level container
class Organisation
has_many :clients
has_many :users
has_many :sites, through: :clients
end
# Client — Retail chain/brand
class Client
belongs_to :organisation
has_many :sites
has_many :products
end
# Site — Individual retail location
class Site
belongs_to :client
has_many :site_products
has_many :products, through: :site_products
has_many :site_memberships
has_many :users, through: :site_memberships
end
# Product — Item in catalog
class Product
belongs_to :client
has_many :site_products
has_many :sites, through: :site_products
end
# SiteProduct — Join with thresholds
class SiteProduct
belongs_to :site
belongs_to :product
has_many :count_entries
has_many :sap_entries
has_many :anomalies
end

Entry Models

# Physical count entry
class CountEntry
belongs_to :site_product
belongs_to :user
# Fields:
# - date
# - locker_opening, locker_stock_in, locker_shelf_out, locker_damages
# - sales_floor_opening, sales_floor_shelf_in, sales_floor_damages
after_save :evaluate_anomalies
end
# SAP data entry
class SapEntry
belongs_to :site_product
belongs_to :user
# Fields:
# - date
# - inventory_qty
# - sales_qty
# - notes
after_save :evaluate_anomalies
end
# Auto-detected issue
class Anomaly
belongs_to :site_product
belongs_to :source, polymorphic: true # CountEntry or SapEntry
# Fields:
# - detected_on (date)
# - rule (enum: locker_short, sales_floor_short, etc.)
# - severity (ok, watch, alert)
# - value, threshold
end

Anomaly Detection System

AnomalyEvaluator Service

class AnomalyEvaluator
RULES = %i[
locker_short
sales_floor_short
out_of_stock
damages_high
short_delivery
osa_breach
on_shelf_available
]
def evaluate(site_product, date)
# Destroy existing anomalies for date
# Evaluate each rule
# Create new anomalies for triggered rules
# Upsert to maintain current state
end
end

Rule Logic

RuleConditionSeverity
locker_shortlocker_closing < 0ALERT
sales_floor_shortsales_floor_closing < 0ALERT
out_of_stocksales_floor_closing == 0ALERT
damages_highdamages > thresholdALERT
short_deliverydelivery < threshold %WATCH
osa_breach(sales_floor / total) < target %WATCH
on_shelf_availablesales_floor > 0OK

Authentication Flow

User enters email + PIN
SessionsController#create
User.authenticate(pin) [bcrypt]
Session created (cookie)
Redirect to role-appropriate landing

PIN Security

  • 4-digit PINs stored with bcrypt
  • Rate limiting: 5 attempts per 20 seconds
  • Session timeout: 30 minutes

Authorization (Pundit)

Policy Structure

class ApplicationPolicy
def initialize(user, record)
@user = user
@record = record
end
end
class SitePolicy < ApplicationPolicy
def show?
user.admin? || user.supervisor? || assigned_to_user?
end
def update?
user.admin? || user.manager?
end
end

Role Hierarchy

class User
def super_admin?; role == 'super_admin'; end
def admin?; role == 'admin'; end
def manager?; role == 'manager'; end
def supervisor?; role == 'supervisor'; end
def controller?; role == 'controller'; end
end

Frontend Architecture

Stimulus Controllers

count_form_controller.js
// Real-time balance calculations
// scan_checklist_controller.js
// Barcode scanning and modal management

Mobile Optimisations

  • Viewportviewport-fit=cover for notched devices
  • Touch targets — Min 44x44px for buttons
  • Input modesinputmode="numeric" triggers number pad
  • Camera — Full-width preview with overlay

Deployment

Docker Production

# Multi-stage build
FROM ruby:3.3-slim AS builder
# ... gems, assets
FROM ruby:3.3-slim AS runtime
# ... runtime only
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]

Kamal Configuration

.kamal/deploy.yml
service: hcp
image: benmacdonald/hcp
servers:
web:
- 192.168.1.1
proxy:
ssl: true
host: hcp.example.com

API Endpoints

Barcode Lookup

GET /barcode/lookup?barcode=1234567890
Response:
{
"item": {
"name": "Product Name",
"sku": "SKU123",
"barcode": "1234567890",
"category": "Electronics"
}
}

Next: Deployment Guide