Update readme, justfile and .gitignore
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -8,3 +8,7 @@ node_modules
|
|||||||
!.env.example
|
!.env.example
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
|
# exported schedules
|
||||||
|
*.docx
|
||||||
|
rota.txt
|
||||||
|
|||||||
62
README.md
62
README.md
@@ -1,18 +1,54 @@
|
|||||||
<h1 align="center">rota</h1>
|
<h1 align="center">rota</h1>
|
||||||
|
|
||||||
Desktop app for generating monthly schedules for medical residents. The scheduling engine uses a parallel constraint-satisfaction DFS with a least-flexibility-first heuristic.
|
<p align="center">
|
||||||
|
Desktop app for generating monthly shift schedules for medical residents.
|
||||||
|
</p>
|
||||||
|
|
||||||
### Features
|
<p align="center">
|
||||||
- Configurable residents, shift types, and forbidden pairings
|
<img alt="Tauri" src="https://img.shields.io/badge/Tauri-2-24C8DB?logo=tauri&logoColor=white">
|
||||||
- Negative shift requests and manual pre-assignments
|
<img alt="Rust" src="https://img.shields.io/badge/Rust-2021-000000?logo=rust&logoColor=white">
|
||||||
- Fairness-aware workload distribution
|
<img alt="SvelteKit" src="https://img.shields.io/badge/SvelteKit-5-FF3E00?logo=svelte&logoColor=white">
|
||||||
- Export to `.docx` and `.txt`
|
<img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-green">
|
||||||
|
</p>
|
||||||
|
|
||||||
### Stack
|
## Features
|
||||||
- **Frontend**: SvelteKit + TypeScript + Tailwind CSS
|
|
||||||
- **Backend**: Rust (Tauri)
|
|
||||||
|
|
||||||
### Usage
|
- **Configurable roster**: residents, their allowed shift types, and per-resident shift caps
|
||||||
```bash
|
- **Constraints**: forbidden pairings, day-off (negative) requests, and manual pre-assignments that the solver works around
|
||||||
just --list
|
- **Fairness**: workload, weekend/holiday, and per-shift-type loads are balanced across the team
|
||||||
```
|
- **Interactive swaps**: after generating, ask the engine for all legal swaps for any shift and apply one without breaking constraints
|
||||||
|
- **Export**: one click writes the schedule and per-resident metrics to `.docx` and `.txt`
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
A month is laid out as a sequence of **slots**. Days alternate between two kinds of duty:
|
||||||
|
|
||||||
|
| Day | Shift | Slots per day |
|
||||||
|
|-----|-------|---------------|
|
||||||
|
| odd | *open* | 2 |
|
||||||
|
| even | *closed* | 1 |
|
||||||
|
|
||||||
|
The solver fills every slot with exactly one resident subject to the hard constraints:
|
||||||
|
|
||||||
|
- no resident works two consecutive days
|
||||||
|
- forbidden ("toxic") pairs never share a day
|
||||||
|
- a resident is never placed on a day they requested off, or into a shift type they aren't allowed to take
|
||||||
|
- per-resident caps on total shifts, weekend/holiday shifts, and shifts of each type are respected
|
||||||
|
|
||||||
|
The search is a **backtracking DFS** over the slots, with a few tricks to make it fast and produce good schedules:
|
||||||
|
|
||||||
|
- **Least-flexibility-first**: at each slot, candidate residents are ordered by how few options they have left in the rest of the month, so the most constrained people get placed first (a fail-first / most-constrained heuristic).
|
||||||
|
- **Parallel roots**:the first slot's candidates are fanned out across threads with [rayon](https://github.com/rayon-rs/rayon); the first thread to find a complete schedule wins and signals the others to stop.
|
||||||
|
- **Timeout**: the search aborts after 5s and reports that no solution was found rather than hanging.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [Rust](https://www.rust-lang.org/tools/install) (stable) and the [Tauri 2 system dependencies](https://tauri.app/start/prerequisites/) for your OS
|
||||||
|
- [Node.js](https://nodejs.org/) and [pnpm](https://pnpm.io/)
|
||||||
|
- [`just`](https://github.com/casey/just) (optional, for the shortcuts below)
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
Run `just --list` to see every recipe.
|
||||||
|
|||||||
64
justfile
64
justfile
@@ -1,36 +1,58 @@
|
|||||||
build:
|
tauri_path := "src-tauri"
|
||||||
pnpm tauri build
|
|
||||||
|
|
||||||
|
# Launch the desktop app with hot reload
|
||||||
|
[group('dev')]
|
||||||
dev:
|
dev:
|
||||||
pnpm tauri dev
|
pnpm tauri dev
|
||||||
|
|
||||||
tauri_path := "src-tauri"
|
# Produce a release bundle
|
||||||
|
[group('dev')]
|
||||||
|
build:
|
||||||
|
pnpm tauri build
|
||||||
|
|
||||||
|
# Run the Rust unit tests
|
||||||
|
[group('test')]
|
||||||
|
test:
|
||||||
|
cd {{tauri_path}} && cargo test --lib -- --test-threads=1
|
||||||
|
|
||||||
|
# Run the end-to-end solver tests
|
||||||
|
[group('test')]
|
||||||
|
test-integration:
|
||||||
|
cd {{tauri_path}} && cargo test --test integration -- --test-threads=1 --nocapture
|
||||||
|
|
||||||
|
# Run the full suite in release mode
|
||||||
|
[group('test')]
|
||||||
|
test-all:
|
||||||
|
cd {{tauri_path}} && cargo test --release
|
||||||
|
|
||||||
|
# Benchmark the parallel search (criterion)
|
||||||
|
[group('test')]
|
||||||
|
bench:
|
||||||
|
cd {{tauri_path}} && cargo bench
|
||||||
|
|
||||||
|
# Measure coverage via cargo-llvm-cov
|
||||||
|
[group('test')]
|
||||||
|
cov:
|
||||||
|
cd {{tauri_path}} && cargo llvm-cov run
|
||||||
|
|
||||||
|
# Run mutation testing via cargo-mutants
|
||||||
|
[group('test')]
|
||||||
|
mutants:
|
||||||
|
cd {{tauri_path}} && cargo mutants
|
||||||
|
|
||||||
|
# Format frontend (prettier) + backend (cargo fmt)
|
||||||
|
[group('quality')]
|
||||||
fmt:
|
fmt:
|
||||||
pnpm prettier --write "**/*.svelte" "**/*.ts"
|
pnpm prettier --write "**/*.svelte" "**/*.ts"
|
||||||
cd {{tauri_path}} && cargo fmt
|
cd {{tauri_path}} && cargo fmt
|
||||||
|
|
||||||
|
# Lint the backend (cargo clippy)
|
||||||
|
[group('quality')]
|
||||||
lint:
|
lint:
|
||||||
cd {{tauri_path}} && cargo clippy
|
cd {{tauri_path}} && cargo clippy
|
||||||
|
|
||||||
test:
|
# Remove node_modules and cargo build artifacts
|
||||||
cd {{tauri_path}} && cargo test --lib -- --test-threads=1
|
[group('misc')]
|
||||||
|
|
||||||
test-integration:
|
|
||||||
cd {{tauri_path}} && cargo test --test integration -- --test-threads=1 --nocapture
|
|
||||||
|
|
||||||
test-all:
|
|
||||||
cd {{tauri_path}} && cargo test --release
|
|
||||||
|
|
||||||
bench:
|
|
||||||
cd {{tauri_path}} && cargo bench
|
|
||||||
|
|
||||||
cov:
|
|
||||||
cd {{tauri_path}} && cargo llvm-cov run
|
|
||||||
|
|
||||||
mutants:
|
|
||||||
cd {{tauri_path}} && cargo mutants
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf node_modules
|
rm -rf node_modules
|
||||||
cd {{tauri_path}} && cargo clean
|
cd {{tauri_path}} && cargo clean
|
||||||
|
|||||||
Reference in New Issue
Block a user