diff --git a/.gitignore b/.gitignore
index 6635cf5..dd688e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,7 @@ node_modules
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
+
+# exported schedules
+*.docx
+rota.txt
diff --git a/README.md b/README.md
index 954f388..2b04fe4 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,54 @@
rota
-Desktop app for generating monthly schedules for medical residents. The scheduling engine uses a parallel constraint-satisfaction DFS with a least-flexibility-first heuristic.
+
+ Desktop app for generating monthly shift schedules for medical residents.
+
-### Features
-- Configurable residents, shift types, and forbidden pairings
-- Negative shift requests and manual pre-assignments
-- Fairness-aware workload distribution
-- Export to `.docx` and `.txt`
+
+
+
+
+
+
-### Stack
-- **Frontend**: SvelteKit + TypeScript + Tailwind CSS
-- **Backend**: Rust (Tauri)
+## Features
-### Usage
-```bash
-just --list
-```
+- **Configurable roster**: residents, their allowed shift types, and per-resident shift caps
+- **Constraints**: forbidden pairings, day-off (negative) requests, and manual pre-assignments that the solver works around
+- **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.
diff --git a/justfile b/justfile
index f9b45f8..152777d 100644
--- a/justfile
+++ b/justfile
@@ -1,36 +1,58 @@
-build:
- pnpm tauri build
+tauri_path := "src-tauri"
+# Launch the desktop app with hot reload
+[group('dev')]
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:
pnpm prettier --write "**/*.svelte" "**/*.ts"
cd {{tauri_path}} && cargo fmt
+# Lint the backend (cargo clippy)
+[group('quality')]
lint:
cd {{tauri_path}} && cargo clippy
-test:
- cd {{tauri_path}} && cargo test --lib -- --test-threads=1
-
-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
-
+# Remove node_modules and cargo build artifacts
+[group('misc')]
clean:
rm -rf node_modules
cd {{tauri_path}} && cargo clean