dabke 0.78.0 → 0.78.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  This changelog was generated from the git history of the project when it was
6
6
  named `scheduling-core`, prior to the rename to `dabke` in v0.78.0.
7
7
 
8
+ ## 0.78.2 (2026-02-08)
9
+
10
+ - Fix solver Docker image to support both amd64 and arm64 (Apple Silicon)
11
+ - Fix npm publish by upgrading npm before publish in CI
12
+
13
+ ## 0.78.1 (2026-02-08)
14
+
15
+ - Add AGENTS.md and CONTRIBUTING.md
16
+ - Improve README with hero example, "Why dabke?" section, solver quickstart, scoping and custom rules docs
17
+ - Improve solver README with Docker Hub image reference and API docs
18
+ - Add GitHub Actions workflow for publishing solver Docker image to Docker Hub
19
+
8
20
  ## 0.78.0 (2026-02-07)
9
21
 
10
22
  - Rename package from `scheduling-core` to `dabke`
package/README.md CHANGED
@@ -1,18 +1,56 @@
1
1
  # dabke
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/dabke.svg)](https://www.npmjs.com/package/dabke)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+ [![CI](https://github.com/christianklotz/dabke/actions/workflows/ci.yml/badge.svg)](https://github.com/christianklotz/dabke/actions/workflows/ci.yml)
6
+
3
7
  Scheduling library powered by constraint programming (CP-SAT).
4
8
 
5
9
  Define your team, shifts, coverage, and rules — dabke turns them into an optimized schedule.
6
10
 
7
- ## Features
11
+ ```typescript
12
+ import { ModelBuilder, HttpSolverClient } from "dabke";
13
+
14
+ const builder = new ModelBuilder({
15
+ employees: [
16
+ { id: "alice", roleIds: ["server"] },
17
+ { id: "bob", roleIds: ["server"] },
18
+ ],
19
+ shiftPatterns: [
20
+ { id: "morning", startTime: { hours: 8 }, endTime: { hours: 16 } },
21
+ { id: "evening", startTime: { hours: 16 }, endTime: { hours: 23 } },
22
+ ],
23
+ coverage: [
24
+ { day: "2026-02-09", startTime: { hours: 8 }, endTime: { hours: 16 },
25
+ roleIds: ["server"], targetCount: 1, priority: "MANDATORY" },
26
+ { day: "2026-02-09", startTime: { hours: 16 }, endTime: { hours: 23 },
27
+ roleIds: ["server"], targetCount: 1, priority: "MANDATORY" },
28
+ ],
29
+ schedulingPeriod: { specificDates: ["2026-02-09"] },
30
+ ruleConfigs: [
31
+ { name: "max-hours-day", config: { hours: 8, priority: "MANDATORY" } },
32
+ { name: "min-rest-between-shifts", config: { hours: 10, priority: "MANDATORY" } },
33
+ ],
34
+ });
35
+
36
+ const { request } = builder.compile();
37
+ const solver = new HttpSolverClient(fetch, "http://localhost:8080");
38
+ const response = await solver.solve(request);
39
+ ```
40
+
41
+ ## Why dabke?
8
42
 
9
- - **Declarative scheduling** describe what you need, not how to solve it
10
- - **Built-in rules** — max hours, time off, rest periods, consecutive days, and more
11
- - **Semantic time** — define named time periods ("lunch", "closing") that resolve per day
12
- - **Soft & hard constraints** — priorities from `LOW` (preference) to `MANDATORY` (hard constraint)
13
- - **Scoped rules** apply rules globally, per person, per role, or per skill
14
- - **Validation** — detailed error reporting with coverage gaps and rule violations
15
- - **LLM-friendly** — ships `llms.txt` with full API docs for AI code generation
43
+ Staff scheduling with constraint programming is dominated by Python ([OR-Tools](https://developers.google.com/optimization)) and Java ([Timefold](https://timefold.ai/)). If you're building in TypeScript, your options have been: call a Python service and figure out the model yourself, or write scheduling heuristics by hand.
44
+
45
+ dabke gives you a **TypeScript-native API** for expressing scheduling problems declaratively employees, shifts, coverage requirements, and rules — and compiles them into a CP-SAT model solved by OR-Tools. You describe _what_ you need, not _how_ to solve it.
46
+
47
+ **Key differences from rolling your own:**
48
+
49
+ - **Declarative rules** — express constraints like "max 8 hours/day" or "11 hours rest between shifts" as config, not code
50
+ - **Semantic time** — define named periods ("lunch_rush", "closing") that vary by day of week
51
+ - **Soft and hard constraints** — some rules are mandatory, others are preferences the solver optimizes for
52
+ - **Scoped rules** — apply constraints globally, per person, per role, per skill, or during specific time periods
53
+ - **Validation** — detailed reporting on coverage gaps and rule violations before and after solving
16
54
 
17
55
  ## Install
18
56
 
@@ -22,6 +60,28 @@ npm install dabke
22
60
 
23
61
  ## Quick Start
24
62
 
63
+ ### 1. Start the solver
64
+
65
+ dabke compiles scheduling problems into a constraint model. You need a CP-SAT solver to solve it. A ready-to-use solver is included:
66
+
67
+ ```bash
68
+ # Pull the solver image
69
+ docker pull christianklotz/dabke-solver
70
+
71
+ # Start it
72
+ docker run -p 8080:8080 christianklotz/dabke-solver
73
+ ```
74
+
75
+ Or build from source:
76
+
77
+ ```bash
78
+ cd node_modules/dabke/solver
79
+ docker build -t dabke-solver .
80
+ docker run -p 8080:8080 dabke-solver
81
+ ```
82
+
83
+ ### 2. Build and solve a schedule
84
+
25
85
  ```typescript
26
86
  import {
27
87
  ModelBuilder,
@@ -34,7 +94,6 @@ import {
34
94
  const employees = [
35
95
  { id: "alice", roleIds: ["server"] },
36
96
  { id: "bob", roleIds: ["server"] },
37
- { id: "charlie", roleIds: ["chef"] },
38
97
  ];
39
98
 
40
99
  // Define shift patterns
@@ -49,7 +108,7 @@ const coverage = [
49
108
  day: "2026-02-09",
50
109
  startTime: { hours: 8 },
51
110
  endTime: { hours: 16 },
52
- roleIds: ["server"],
111
+ roleIds: ["server"] as [string],
53
112
  targetCount: 1,
54
113
  priority: "MANDATORY" as const,
55
114
  },
@@ -57,7 +116,7 @@ const coverage = [
57
116
  day: "2026-02-09",
58
117
  startTime: { hours: 16 },
59
118
  endTime: { hours: 23 },
60
- roleIds: ["server"],
119
+ roleIds: ["server"] as [string],
61
120
  targetCount: 1,
62
121
  priority: "MANDATORY" as const,
63
122
  },
@@ -77,10 +136,15 @@ const builder = new ModelBuilder({
77
136
  ],
78
137
  });
79
138
 
80
- const { request } = builder.compile();
139
+ const { request, canSolve, validation } = builder.compile();
81
140
 
82
- // Solve (requires a CP-SAT solver endpoint)
83
- const solver = new HttpSolverClient(fetch, "https://your-solver-endpoint");
141
+ if (!canSolve) {
142
+ console.error("Cannot solve:", validation.errors);
143
+ process.exit(1);
144
+ }
145
+
146
+ // Solve
147
+ const solver = new HttpSolverClient(fetch, "http://localhost:8080");
84
148
  const response = await solver.solve(request);
85
149
  const result = parseSolverResponse(response);
86
150
 
@@ -103,8 +167,16 @@ import { defineSemanticTimes } from "dabke";
103
167
 
104
168
  const times = defineSemanticTimes({
105
169
  lunch: [
106
- { startTime: { hours: 11, minutes: 30 }, endTime: { hours: 14 }, days: ["monday", "tuesday", "wednesday", "thursday", "friday"] },
107
- { startTime: { hours: 12 }, endTime: { hours: 15 }, days: ["saturday", "sunday"] },
170
+ {
171
+ startTime: { hours: 11, minutes: 30 },
172
+ endTime: { hours: 14 },
173
+ days: ["monday", "tuesday", "wednesday", "thursday", "friday"],
174
+ },
175
+ {
176
+ startTime: { hours: 12 },
177
+ endTime: { hours: 15 },
178
+ days: ["saturday", "sunday"],
179
+ },
108
180
  ],
109
181
  closing: { startTime: { hours: 21 }, endTime: { hours: 23 } },
110
182
  });
@@ -113,6 +185,10 @@ const coverage = times.coverage([
113
185
  { semanticTime: "lunch", roleIds: ["server"], targetCount: 3, priority: "MANDATORY" },
114
186
  { semanticTime: "closing", roleIds: ["server"], targetCount: 1, priority: "HIGH" },
115
187
  ]);
188
+
189
+ // Resolve against actual scheduling days
190
+ const days = ["2026-02-09", "2026-02-10", "2026-02-11"];
191
+ const resolved = times.resolve(coverage, days);
116
192
  ```
117
193
 
118
194
  ## Built-in Rules
@@ -134,6 +210,43 @@ const coverage = times.coverage([
134
210
 
135
211
  All rules support scoping by person, role, skill, and time period (date ranges, days of week, recurring periods).
136
212
 
213
+ ### Scoping Example
214
+
215
+ ```typescript
216
+ // Students can work max 4 hours on weekdays
217
+ {
218
+ name: "max-hours-day",
219
+ config: {
220
+ roleIds: ["student"],
221
+ hours: 4,
222
+ dayOfWeek: ["monday", "tuesday", "wednesday", "thursday", "friday"],
223
+ priority: "MANDATORY",
224
+ },
225
+ }
226
+
227
+ // Alice has time off next week
228
+ {
229
+ name: "time-off",
230
+ config: {
231
+ employeeIds: ["alice"],
232
+ dateRange: { start: "2026-02-09", end: "2026-02-13" },
233
+ priority: "MANDATORY",
234
+ },
235
+ }
236
+ ```
237
+
238
+ ### Soft vs. Hard Constraints
239
+
240
+ Rules with `priority: "MANDATORY"` are hard constraints — the solver will not violate them. Rules with `LOW`, `MEDIUM`, or `HIGH` priority are soft constraints — the solver tries to satisfy them but will trade them off against each other to find the best overall schedule.
241
+
242
+ ```typescript
243
+ // Hard: legally required rest period
244
+ { name: "min-rest-between-shifts", config: { hours: 11, priority: "MANDATORY" } }
245
+
246
+ // Soft: prefer to keep Bob under 30 hours/week
247
+ { name: "max-hours-week", config: { employeeIds: ["bob"], hours: 30, weekStartsOn: "monday", priority: "MEDIUM" } }
248
+ ```
249
+
137
250
  ## Validation
138
251
 
139
252
  dabke validates schedules and reports coverage gaps and rule violations:
@@ -162,6 +275,37 @@ for (const s of summaries) {
162
275
  }
163
276
  ```
164
277
 
278
+ ## Custom Rules
279
+
280
+ dabke's rule system is extensible. You can create custom rules that integrate with the model builder:
281
+
282
+ ```typescript
283
+ import { createCpsatRuleFactory, type CompilationRule } from "dabke";
284
+
285
+ function createNoSundayWorkRule(config: { priority: "MANDATORY" | "HIGH" }): CompilationRule {
286
+ return {
287
+ compile(b) {
288
+ for (const emp of b.employees) {
289
+ for (const day of b.days) {
290
+ const date = new Date(`${day}T00:00:00Z`);
291
+ if (date.getUTCDay() !== 0) continue; // Sunday = 0
292
+ for (const pattern of b.shiftPatterns) {
293
+ if (!b.canAssign(emp, pattern)) continue;
294
+ b.addLinear([{ var: b.assignment(emp.id, pattern.id, day), coeff: 1 }], "<=", 0);
295
+ }
296
+ }
297
+ }
298
+ },
299
+ };
300
+ }
301
+
302
+ const customRules = createCpsatRuleFactory({
303
+ "no-sunday-work": createNoSundayWorkRule,
304
+ });
305
+ ```
306
+
307
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide on writing custom rules.
308
+
165
309
  ## LLM Integration
166
310
 
167
311
  dabke ships an `llms.txt` file with complete API documentation, designed for AI code generation:
@@ -178,9 +322,23 @@ Or read the file directly:
178
322
  cat node_modules/dabke/llms.txt
179
323
  ```
180
324
 
181
- ## Solver
325
+ ## Testing
326
+
327
+ dabke provides test utilities for running integration tests against the solver:
328
+
329
+ ```typescript
330
+ import { startSolverContainer } from "dabke/testing";
331
+
332
+ const solver = await startSolverContainer();
333
+ const response = await solver.client.solve(request);
334
+ solver.stop();
335
+ ```
336
+
337
+ This starts a Docker container with the solver, waits for it to be healthy, and provides a pre-configured `HttpSolverClient`.
338
+
339
+ ## Contributing
182
340
 
183
- dabke compiles scheduling problems into a JSON-based constraint model. You need a CP-SAT solver service to solve it. The model format is compatible with Google OR-Tools CP-SAT solver.
341
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, how to add rules, and contribution guidelines.
184
342
 
185
343
  ## License
186
344
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dabke",
3
- "version": "0.78.0",
3
+ "version": "0.78.2",
4
4
  "license": "MIT",
5
5
  "description": "Scheduling library powered by constraint programming (CP-SAT)",
6
6
  "author": "Christian Klotz <hello@christianklotz.co.uk>",
package/solver/README.md CHANGED
@@ -1,23 +1,48 @@
1
- # Scheduler Solver Service
1
+ # dabke solver
2
2
 
3
- Minimal FastAPI service that accepts scheduling primitives (variables, constraints, optional objective) and solves them with OR-Tools CP-SAT.
3
+ Minimal FastAPI service that solves scheduling models with [OR-Tools CP-SAT](https://developers.google.com/optimization/cp/cp_solver).
4
4
 
5
- ## Local run
5
+ Receives a JSON constraint model from dabke's `ModelBuilder`, returns optimized assignments.
6
6
 
7
- ```sh
8
- uv sync
9
- uvicorn solver.app:app --reload --port 8080
7
+ ## Quick start (Docker)
8
+
9
+ ```bash
10
+ # Use the published image
11
+ docker pull christianklotz/dabke-solver
12
+ docker run -p 8080:8080 christianklotz/dabke-solver
13
+
14
+ # Or build from source
15
+ docker build -t dabke-solver .
16
+ docker run -p 8080:8080 dabke-solver
10
17
  ```
11
18
 
12
- ## Docker
19
+ Then point `HttpSolverClient` at it:
13
20
 
14
- ```sh
15
- docker build -t scheduler-solver .
16
- docker run -p 8080:8080 scheduler-solver
21
+ ```typescript
22
+ import { HttpSolverClient } from "dabke";
23
+
24
+ const solver = new HttpSolverClient(fetch, "http://localhost:8080");
25
+ ```
26
+
27
+ ## Local development
28
+
29
+ ```bash
30
+ uv sync
31
+ uvicorn solver.app:app --reload --port 8080
17
32
  ```
18
33
 
19
34
  ## Tests
20
35
 
21
- ```sh
36
+ ```bash
22
37
  uv run pytest
23
38
  ```
39
+
40
+ ## API
41
+
42
+ ### `GET /health`
43
+
44
+ Returns `{ "status": "ok" }`.
45
+
46
+ ### `POST /solve`
47
+
48
+ Accepts a solver request JSON body, returns assignments. See dabke's `SolverRequest` type for the schema.