New
Apps 1,382Makers 1,380This week 1,382
Promote your app−30%
Altocumulus screenshot 1
0views in
the last 30 days

Altocumulus

Clinical ledger for patients, diagnoses, surgeries and hospitalizations, with elective surgery...

Built with Claude Code

by studiome · Unclaimed · from JPJapan · added Oct 29, 2026
no ratings0 comments

English | 日本語

A clinical ledger application for a single hospital department, tracking patients, diagnoses, surgeries, and hospitalizations. It brings outpatient and ward records together in one place, with a focus on making elective slot utilization, the lifecycle of a hospitalization from request to discharge, and an audit log of every change visible under per-user permissions. The landing page is an operations calendar spanning 50 days by default, showing daily surgery and admission counts, congestion warnings, holidays, and announcements at a glance.

It is built entirely on the stock Rails 8.1 stack (Propshaft + importmap + Turbo/Stimulus) — no Node build pipeline and no external database server.


Table of contents


Features

Feature Screen Description
Login /login Login id (email or username, per ACCOUNT_IDENTIFIER) + password authentication. Sessions expire automatically after a period of inactivity. Published announcements are shown on this page, and it is displayed in Japanese for anonymous visitors (a language switcher sits on the page)
Operations calendar (home) /operations_calendar Day-grouped ledger table spanning 50 days by default (start date and day count adjustable). Per day: surgeries by slot with the referral source, admission counts, congestion warnings, special slot usage, and holidays / per-day comments. The date heading and a small "+入約" link open the reservation form pre-filled with that day; each surgery row of a hospitalization has an edit link to the same form; two tabs, 手術 (surgeries) and 検査入院 (non-surgery admissions by scheduled procedure/admission date, columns from ledger_columns.examination_calendar), selected with ?view=; emergency rows carry a red stripe and rows outside the regular slot time are hatched (legend above the table, kept in print); administrators edit a day's note/closure/slot override inline (/day_notes/:date) and that day's special slot lanes through the day cell's "Edit special slots" link (/day_special_slots/:date); filters 自分が関与する症例のみ (mine=1) and, with features.teams, a team select (team_id) are kept across tabs and ranges
Patient ledger /patients Patient master data, with keyword search by name or hospital ID and pagination
Patient diagnoses /patients/:id/patient_diagnoses Per-patient diagnosis history referencing the diagnosis master, holding the diagnosis date and laterality
Surgery records /surgeries Surgery date (may be "undecided"), procedures (up to 5), anesthesia method, duration, elective/emergency type, slot category (regular / joint / backup / off-slot), operator/assistant/operation order, location / device / partner and performing department. Linked to patient diagnoses and, optionally, to the hospitalization it belongs to. Searchable by operator
Hospitalization records /hospitalizations Lifecycle from reservation (scheduled admission and surgery dates) through actual admission and discharge, admission purpose (surgery / examination / chemotherapy / chemoradiotherapy / other, the last requiring a reason), emergency admission, transfer status, reservation doctor, referral source, administrator confirmation, soft delete/restore, and rebooking (copy)
Reservation form (入約) /reservations/new, /reservations/:hospitalization_id/edit One screen that creates or edits a hospitalization and, for the purpose surgery, its surgery together (see Reservation form). The calendar's booking flow goes through it; the separate hospitalization and surgery forms remain for administration and detail edits
Surgery slot schedule /surgery_schedule Weekly calendar showing the surgeries and used time per slot, warning about slot overruns, unassigned surgeries, and holidays. The same week can be printed as a landscape A4 PDF (/surgery_schedule/pdf?start_date=YYYY-MM-DD, any start date) in black and white, with operator/assistant, referral source, and a heading per day (date, closed-day name / note, slot count when overridden, slot usage in minutes, case count, special slot usage); emergency rows are shaded and rows outside the regular slot time lightly shaded (legend under the title)
Data download (admin / data manager only) /exports CSV exports defined in the site config, with a date range (and team) per export (see CSV export)
Dashboard /dashboard Statistics filterable by year (patient counts, inpatients, monthly counts, average length of stay, procedure ranking)
Cross-entity search /search Search patients, hospitalizations, and surgeries with a single keyword; results are split into 入院 / 手術 / 患者 tabs (?tab=, each with its match count; without one, the first tab with matches opens) and listed as tables of up to 20 rows with a link to the full list
Audit log /audit_events Records and displays create/update/delete of patients, surgeries, hospitalizations, and case databases with the operator, IP address, and before/after values
Master data /diagnoses /surgery_procedures /elective_slot_rules /holidays /departments /surgery_locations /surgical_devices /teams Diagnosis names, procedures, per-weekday slot rules (fractional slot counts supported), holidays / per-day comments and slot overrides, departments, surgery locations and surgical devices (the latter two with weekly special slot lane counts), and teams with their terms and members (/teams only with features.teams)
Case databases /case_databases Named patient registries with custom fields (text / number / select) defined by a data manager. Any signed-in user can add patients, enter values, and export the registry as CSV; only a data manager can create, edit, or delete a database, its fields, or a patient entry
Account settings /account Change your own name, login id, and password, and switch the display language
User management (admin only) /admin/users Create, edit, and deactivate users and reset passwords. The last active admin can be neither deactivated nor demoted
Bulk user registration (admin only) /admin/user_import/new Register users from a CSV of login ids and passwords. Existing login ids are skipped, and every line is reported back as registered / skipped / failed
Announcement management (admin only) /admin/announcements Create announcements shown on the sign-in page and toggle their visibility
Admin notes (admin only) /admin/admin_notes Free-text handover notes shared between administrators
Application settings (admin only) /admin/settings Change the application title shown in the navigation bar, the browser tab, and the installable app

Master data is edited by administrators only. Diagnoses, surgery procedures, holidays (closed days / day notes / slot overrides), elective slot rules, departments, surgery locations, surgical devices, teams and team terms can be viewed by every signed-in user, but creating, editing and deleting them (and the "new" buttons) require the admin role; others are redirected with a "not authorized" notice. The one exception is data entry: any signed-in user may register a new diagnosis or surgery procedure from inside a picker modal (a turbo-frame request) while booking a hospitalization or surgery.

Reservation form. The calendar books through one form object (Reservation) that saves a hospitalization and, for the purpose "surgery", its surgery in a single transaction, with live previews of the patient, admission counts, slot usage and team members next to the form. Details are under Reservation form in the design notes.

Authentication, user management, role separation (user / data manager / admin), access logging, and idle timeout are implemented. Every application screen requires a logged-in user; the exceptions are /login, the language switcher (PATCH /locale), and the /up health check.

Tech stack

Area Technology
Language / framework Ruby 4.0.7 / Rails 8.1
Database SQLite (four schemas: the app itself, Solid Queue, Solid Cache, Solid Cable)
Background processing Solid Queue / Solid Cache / Solid Cable
Assets Propshaft + importmap-rails (no Node, no JS bundler)
CSS Tailwind CSS + daisyUI (tailwindcss-rails)
Front end Hotwire (Turbo Drive / Turbo Streams / Stimulus)
Testing Minitest + fixtures; system tests with Capybara + Selenium
PDF output Prawn + prawn-table (weekly surgery schedule; bundled IPAex Gothic font in vendor/fonts)
Static analysis RuboCop (rubocop-rails-omakase), Brakeman, bundler-audit, importmap audit
Deployment Kamal + Thruster (Dockerfile included)

Setup

Prerequisites: Ruby 4.0.7 (see .ruby-version) and Bundler.

git clone https://github.com/studiome/altocumulus.git
cd altocumulus
bin/setup

bin/setup installs gems, creates/migrates/seeds the database, clears logs, and then starts the development server. To prepare everything without starting the server:

bin/setup --skip-server

After that, use bin/dev (via Procfile.dev it runs the Rails server and the Tailwind watcher together).

bin/dev

The app is available at http://localhost:3000. The root path is the operations calendar (OperationsCalendarController#index); unauthenticated visitors are redirected to the login screen.

In development, bin/setup also runs the seeds, so you can log in with the following demo account (see db/seeds.rb), which holds both the admin and data_manager roles. The sign-in screen is shown in Japanese for visitors who have not signed in; switch the language on the sign-in page itself, or after logging in from the Account page (/account).

Login ID Password
admin@example.com password

The seeds contain only this account and the masters. To fill a development database with fictional patients, hospitalizations and surgeries for UI review, run bin/rails dev:demo_data (see Development commands and testing and Development demo data).

For production, the first administrator and the other runtime settings are configured through environment variables and a site configuration file; see Configuration.

Configuration

Environment variables

Runtime behavior is tuned with environment variables (config/application.rb, db/seeds.rb).

Environment variable Description Default
ACCOUNT_IDENTIFIER Whether an account logs in with an email address or a username. Decide this when first setting up the server and do not change it afterward -- switching modes later makes existing login ids fail the new format validation. email
SESSION_IDLE_TIMEOUT_MINUTES Session idle timeout, in minutes 10
ADMISSION_WARNING_THRESHOLD Admissions per day above which the operations calendar flags congestion 5
APP_TITLE Application title used before an administrator sets one on /admin/settings. Useful for branding a fresh deploy without signing in first Altocumulus
SITE_CONFIG_PATH Path of the site configuration YAML (see Site configuration). In production, mount the file with a Kamal volume config/site.yml
BOOTSTRAP_ADMIN_LOGIN_ID Login id (email or username, depending on ACCOUNT_IDENTIFIER) of the first administrator (unset)
BOOTSTRAP_ADMIN_PASSWORD Password of the first administrator (unset)

Setting both BOOTSTRAP_ADMIN_LOGIN_ID and BOOTSTRAP_ADMIN_PASSWORD on first boot creates a bootstrap administrator account (named Administrator) when db:seed runs. If a user with the same login id already exists, nothing happens. BOOTSTRAP_ADMIN_EMAIL is kept as an older name for BOOTSTRAP_ADMIN_LOGIN_ID: it is still read as a fallback when that is unset. With Kamal these variables must be listed under env: in config/deploy.yml -- see Before the first deploy.

Site configuration

Per-hospital customizations are not hardcoded; they live in a YAML file loaded once at boot by SiteConfig (app/models/site_config.rb). The file is the one at SITE_CONFIG_PATH if that environment variable is set (in production, mount it with a Kamal volume -- see the commented example in config/deploy.yml), otherwise the committed config/site.yml, a neutral default that keeps the stock behavior. config/site.example.yml shows every key filled in. A malformed file, an unknown key or an unknown field name stops the boot with a SiteConfig::Error. (Field names are not checked while migrations are pending, so db:prepare can boot and migrate first; the server's boot afterwards checks them.)

Keys:

  • features.teams (default false): turns the care-team feature on (see Teams). While false, the Teams screens, the team selects and the team displays are hidden and team_id may not be listed under required_fields (the boot fails if it is).
  • required_fields.hospitalization / required_fields.surgery: attribute names that must be filled in before saving. The red asterisk on the form follows the same list (Model.required_attribute?).
  • required_fields.hospitalization_by_purpose: the same, but only for hospitalizations of one purpose (surgery, examination, chemotherapy, chemoradiotherapy, other), e.g. examination: [scheduled_procedure_date, procedure_name]. The asterisk follows the purpose chosen in the form (Hospitalization.required_attribute?(attr, record) or (attr, purpose: ...)). The reservation doctor is the user reference reservation_doctor_id (not the old reservation_doctor string).
  • patient.hospital_id_format / patient.hospital_id_format_hint: a regular expression the patient's hospital ID must match when it is set or changed, and a human-readable description for the error message. Omit for no restriction.
  • masters.departments / masters.surgery_locations / masters.surgical_devices: lists of names (for example a hospital's departments, its rooms such as a hybrid room or cath lab, and its support devices) to make sure exist in the master tables. Nothing hospital-specific is hardcoded in the app; the vocabulary lives in those admin-editable masters (Masters menu), and this key only seeds them. Run bin/rails site:seed_masters after a deploy (it is also part of db:seed): it only creates missing names, in the listed order after the existing rows, and never renames, reorders or deletes anything. A deactivated master is no longer offered for new selections but still shows on the surgeries that use it; a master in use cannot be deleted. A surgery_locations entry may also be a mapping, {name: ..., asks_performing_department: true}, which flags a place whose cases may be performed by another department (a hint for the surgery form; set only on newly created rows, and editable on the Surgery Locations master screen). Special slot lane counts are not part of this file; they are edited on the master screens.
  • masters.teams: team names (or {name: ..., color: blue}, color one of blue/red/green/orange/purple/ teal/gray), seeded the same additive way as the other masters. Terms and members are entered in the app, not in this file.
  • ledger_columns.operations_calendar / weekly_pdf / examination_calendar: the columns each ledger table shows, in order (the calendar's and PDF's day cell is always first and not listed). Columns come from the ledger column registry (LedgerColumn, app/models/ledger_column.rb and app/models/ledger_columns/): surgery ledgers use time_slot, admission_dates, patient, age_sex, diagnosis_procedure, operator_assistant, duration_anesthesia, team (needs features.teams), category, reservation, referral_adl, status; the examination ledger uses admission_dates, patient, age_sex, diagnosis_procedure_name, purpose, team, category, reservation, referral_adl, status. Omit a ledger for its built-in default. Unknown or duplicate columns, an empty list, or team without features.teams stop the boot. See config/site.yml for the column meanings.
  • weekly_pdf: font_size (6-10, default 8) and title (replaces the translated PDF title; keep site-specific wording here, not in code). Columns have a preferred and a minimum width in the registry: spare page width is shared out by each column's flex weight (category 3, team 2, diagnosis/procedure 2, referral/ADL 1.5, patient, reservation and status 1; others 0), not by width, so the columns that wrap most grow most; a too wide set shrinks towards the minimums, and only then does the font step down (8 -> 7.5 -> 7 -> 6.5) before a final proportional squeeze (LedgerColumnFitting).
  • exports: named CSV exports for the clerk's admission list / registry (NCD) lists. Each entry is <name>: {subject: surgery|hospitalization, fields: [...], label: optional}; fields come from the export field registry (ExportField, app/models/export_field.rb and app/models/export_fields/), which yields one plain value per column (unlike the stacked ledger cells). Omit the key for the built-in surgery_list and admission_list (every field of the subject); exports: {} switches exports off. Unknown subjects/fields, duplicates, an empty list, a bad name or team/team_members without features.teams stop the boot. See config/site.yml for every field and config/site.example.yml for an ncd_list.

In tests, with_site_config(hash) { ... } swaps the config temporarily.

Development commands and testing

bin/dev                                        # dev server + Tailwind watcher
bin/rails test                                 # all tests except system tests
bin/rails test test/models/patient_test.rb     # a single file
bin/rails test test/models/patient_test.rb:12  # a single test at a line
bin/rails test:system                          # system tests (requires Selenium)
bin/rails db:prepare                           # create / migrate / seed the database
bin/rubocop                                    # lint
bin/brakeman                                   # static security analysis
bin/bundler-audit                              # audit gems for known vulnerabilities
bin/ci                                         # the same checks CI runs (config/ci.rb)

bin/ci runs setup → RuboCop → the gem/importmap/Brakeman audits → tests → a re-run of the seeds. GitHub Actions (.github/workflows/ci.yml) runs an equivalent job on pushes to main and on pull requests.

Rake tasks (lib/tasks/):

Task Purpose
bin/rails site:seed_masters Creates the departments, surgery locations, surgical devices and teams listed under masters in the site config. Additive and idempotent: it never renames, reorders or deletes existing rows. Also run from db/seeds.rb
bin/rails dev:demo_data Development only. Fills the database with fictional, idempotently created demo data for UI review. REFRESH=1 also rewrites the clinical content of the demo cases in place (see Development demo data)
bin/rails surgeries:move_special_off_slot_to_regular Moves elective off-slot surgeries that use a special slot resource (hybrid room, ca

Description taken from the repository README.

Ratings

–
0 ratings
5 0
4 0
3 0
2 0
1 0

Rate this app

Sign in to rate

Comments 0

Sign in to comment

No comments yet.

Related apps

#clinical · #healthcare · #japanese