github-actions[bot] commited on
Commit
fb8fccc
·
0 Parent(s):

chore: sync uc-deliveries Space

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +6 -0
  2. .gitattributes +36 -0
  3. .gitignore +5 -0
  4. .pre-commit-config.yaml +21 -0
  5. AGENTS.md +97 -0
  6. CHANGELOG.md +62 -0
  7. Cargo.lock +2684 -0
  8. Cargo.toml +37 -0
  9. Dockerfile +34 -0
  10. Makefile +293 -0
  11. README.md +207 -0
  12. WIREFRAME.md +245 -0
  13. docs/screenshot.png +3 -0
  14. solver.toml +76 -0
  15. solverforge.app.toml +62 -0
  16. src/api/dto.rs +242 -0
  17. src/api/dto/runtime.rs +83 -0
  18. src/api/dto/tests.rs +58 -0
  19. src/api/errors.rs +35 -0
  20. src/api/mod.rs +12 -0
  21. src/api/routes.rs +288 -0
  22. src/api/sse.rs +50 -0
  23. src/constraints/all_deliveries_assigned.rs +28 -0
  24. src/constraints/delivery_time_windows.rs +16 -0
  25. src/constraints/mod.rs +33 -0
  26. src/constraints/total_travel_time.rs +11 -0
  27. src/constraints/vehicle_capacity.rs +16 -0
  28. src/data/data_seed.rs +16 -0
  29. src/data/data_seed/entrypoints.rs +132 -0
  30. src/data/data_seed/firenze.rs +8 -0
  31. src/data/data_seed/firenze/depots.rs +64 -0
  32. src/data/data_seed/firenze/visits.rs +292 -0
  33. src/data/data_seed/firenze/visits_extra.rs +196 -0
  34. src/data/data_seed/hartford.rs +8 -0
  35. src/data/data_seed/hartford/depots.rs +64 -0
  36. src/data/data_seed/hartford/visits.rs +184 -0
  37. src/data/data_seed/hartford/visits_extra.rs +124 -0
  38. src/data/data_seed/philadelphia.rs +8 -0
  39. src/data/data_seed/philadelphia/depots.rs +64 -0
  40. src/data/data_seed/philadelphia/visits.rs +298 -0
  41. src/data/data_seed/philadelphia/visits_extra.rs +202 -0
  42. src/data/data_seed/tests.rs +123 -0
  43. src/data/data_seed/types.rs +48 -0
  44. src/data/mod.rs +10 -0
  45. src/domain/clarke_wright_tests.rs +270 -0
  46. src/domain/coord_value.rs +39 -0
  47. src/domain/delivery.rs +95 -0
  48. src/domain/mod.rs +42 -0
  49. src/domain/plan.rs +177 -0
  50. src/domain/plan_tests.rs +216 -0
.dockerignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ target/
2
+ .git/
3
+ .osm_cache/
4
+ test-results/
5
+ playwright-report/
6
+ *.rs.bk
.gitattributes ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ docs/screenshot.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ /target
2
+ .osm_cache/
3
+ **/*.rs.bk
4
+ test-results/
5
+ playwright-report/
.pre-commit-config.yaml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.5.0
4
+ hooks:
5
+ - id: check-yaml
6
+ - id: end-of-file-fixer
7
+ - id: trailing-whitespace
8
+ - id: check-merge-conflict
9
+ - id: check-added-large-files
10
+
11
+ - repo: https://github.com/gitleaks/gitleaks
12
+ rev: v8.18.0
13
+ hooks:
14
+ - id: gitleaks
15
+ - repo: https://github.com/doublify/pre-commit-rust
16
+ rev: v1.0
17
+ hooks:
18
+ - id: fmt
19
+ args: ["--", "--check"]
20
+ - id: clippy
21
+ args: ["--", "-D", "warnings"]
AGENTS.md ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Repository Guidelines
2
+
3
+ ## Project Structure And Naming
4
+
5
+ This repo follows the current `solverforge-cli` app shape. The app package
6
+ version is declared in `Cargo.toml`, and the release binary is
7
+ `solverforge_deliveries`.
8
+
9
+ - `src/domain/mod.rs` owns the `solverforge::planning_model!` manifest.
10
+ - `src/domain/plan.rs` owns the `Plan` planning solution.
11
+ - `src/domain/delivery.rs` owns the `Delivery` problem fact.
12
+ - `src/domain/vehicle.rs` owns the `Vehicle` planning entity and its
13
+ `delivery_order` list variable.
14
+ - `src/domain/preview.rs` owns transport/view preview structs.
15
+ - `src/domain/route_metrics/` owns route preparation, scoring preview, route
16
+ geometry, and insertion ranking. Solver construction and k-opt behavior
17
+ should come from the stock `domain = "cvrp"` list-variable profile.
18
+ - `src/constraints/` owns one score rule per file plus `mod.rs` assembly.
19
+ - `src/data/data_seed/` owns deterministic city demo-data modules with grouped
20
+ visit files for scaled delivery counts.
21
+ - `src/api/` owns REST, DTO, and SSE surfaces.
22
+ - `src/solver/` owns retained-job runtime orchestration.
23
+ - `static/app/models/` owns frontend plan modeling.
24
+ - `static/app/ui/` owns browser layout and rendering helpers.
25
+
26
+ Keep the canonical solution name `Plan`. Do not reintroduce `DeliveryPlan` or
27
+ `delivery_plan.rs`.
28
+
29
+ Do not add local CVRP hook modules to this app. If stock CVRP construction or
30
+ route-local behavior is wrong, fix the SolverForge CVRP profile upstream.
31
+
32
+ ## File Size Rule
33
+
34
+ No source, test, frontend, config, or repo documentation file should reach 300
35
+ lines. Split by responsibility before that point. Large generated/cache output
36
+ under `target/` and `.osm_cache/` is outside this rule.
37
+
38
+ ## Build And Validation Commands
39
+
40
+ - `make help` shows the supported command surface.
41
+ - `make run-release` runs the app locally on `:7860`.
42
+ - `make test` runs Rust, frontend, and Playwright browser tests.
43
+ - `make test-e2e` runs the real browser Playwright smoke.
44
+ - `make space-build` builds the Docker image used by Hugging Face Spaces.
45
+ - `make space-run` builds and runs that image locally.
46
+ - `make ci-local` runs formatting, clippy, release build, standard tests, and
47
+ the Space Docker image build.
48
+ - `make test-live-road` runs the env-enabled road-network smoke test.
49
+ - `make pre-release` runs `ci-local` and the live road-network smoke.
50
+ - `cargo test` runs Rust unit and integration tests.
51
+ - `node --test tests/frontend_models.test.mjs` runs frontend model tests.
52
+
53
+ Use the Makefile as the authoritative local workflow, matching the
54
+ `solverforge-hospital` Space/Docker validation standard.
55
+
56
+ ## No Suppression Policy
57
+
58
+ Do not add warning suppressions, fallback compatibility branches, or unused
59
+ helper modules. If a split creates unused code, restructure the modules so each
60
+ compiled item is used by its crate.
61
+
62
+ ## Documentation Policy
63
+
64
+ Keep `Cargo.toml`, `Cargo.lock`, `Makefile`, `Dockerfile`, `README.md`,
65
+ `WIREFRAME.md`, `AGENTS.md`, `docs/screenshot.png`,
66
+ `solverforge.app.toml`, `solver.toml`, `static/sf-config.json`, and the visible
67
+ API guide in
68
+ `static/app/ui/api-guide.mjs` aligned.
69
+
70
+ When changing routes, solver policy, demo IDs, dependency sources, file layout,
71
+ or browser behavior, update the docs and screenshot in the same patch. Prefer
72
+ current-state documentation over planning language.
73
+
74
+ ## Testing Guidance
75
+
76
+ Add Rust unit tests next to the behavior they protect. Add API integration
77
+ coverage under `tests/api_contract/` and shared integration helpers under
78
+ `tests/support/` only when every helper is used by the single
79
+ `tests/api_contract.rs` crate. Add frontend model tests in
80
+ `tests/frontend_models.test.mjs`, and browser-flow tests in `tests/e2e/`.
81
+
82
+ Road-network tests should stay env-gated unless the test uses an empty plan or
83
+ an explicit prepared matrix fixture. Use `SOLVERFORGE_RUN_LIVE_TESTS=1` through
84
+ `make test-live-road` when validating live map/routing paths.
85
+
86
+ ## Runtime Notes
87
+
88
+ `solver.toml` is embedded by `Plan` through the planning-solution macro. Treat
89
+ it as the solver policy source of truth.
90
+
91
+ `Cargo.toml` currently uses Rust `1.95` and crates.io dependency declarations.
92
+ Keep every direct dependency declaration, `solverforge.app.toml`, `Cargo.lock`,
93
+ and docs truthful if dependency sources or versions change.
94
+
95
+ The app serves stock `solverforge-ui` assets, local static app modules, and
96
+ Axum API routes from one process. Retained solver jobs are controlled through
97
+ REST and observed through SSE.
CHANGELOG.md ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Changelog
2
+
3
+ All notable changes to this use case are documented in this file.
4
+
5
+ ## 2.0.6 (2026-07-29)
6
+
7
+ ### Maintenance
8
+
9
+ * **release:** target SolverForge 0.19.3.
10
+
11
+ ## 2.0.5 (2026-07-17)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * **deliveries:** target SolverForge 0.19.0 ace9700
17
+
18
+ ## 2.0.4 (2026-07-13)
19
+
20
+ ### Maintenance
21
+
22
+ * **release:** target SolverForge 0.18.0.
23
+ * **tests:** run custom construction configs through the compiled runtime entrypoint.
24
+ * **docs:** name the shipped local-search forager exactly as configured.
25
+
26
+ ## 2.0.3 (2026-06-16)
27
+
28
+ ### Features
29
+
30
+ * **routing:** use the road-network-only Deliveries contract with stock SolverForge CVRP construction hooks.
31
+
32
+ ### Maintenance
33
+
34
+ * **release:** target SolverForge 0.17.1 and solverforge-cli 2.2.2.
35
+
36
+ ## 2.0.2 (2026-05-28)
37
+
38
+ ### Maintenance
39
+
40
+ * **release:** target SolverForge 0.15.0.
41
+
42
+ ## 2.0.1 (2026-05-16)
43
+
44
+ ### Maintenance
45
+
46
+ * **release:** target SolverForge 0.14.1 and migrate delivery route hooks to the owner-aware 0.14 list-variable API.
47
+
48
+ ## 2.0.0 (2026-05-14)
49
+
50
+ ### Maintenance
51
+
52
+ * **release:** set the public app release line to 2.0.0 across Cargo metadata and release validation.
53
+
54
+ ## 1.0.1 (2026-05-14)
55
+
56
+ ### Features
57
+
58
+ * **deliveries:** publish the SolverForge deliveries use case in the bundle.
59
+
60
+ ### Maintenance
61
+
62
+ * **release:** align the bundled app with SolverForge 0.13.1, solverforge-ui 0.6.5, and solverforge-maps 2.1.4.
Cargo.lock ADDED
@@ -0,0 +1,2684 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "aho-corasick"
7
+ version = "1.1.4"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
10
+ dependencies = [
11
+ "memchr",
12
+ ]
13
+
14
+ [[package]]
15
+ name = "anyhow"
16
+ version = "1.0.102"
17
+ source = "registry+https://github.com/rust-lang/crates.io-index"
18
+ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
19
+
20
+ [[package]]
21
+ name = "arrayvec"
22
+ version = "0.7.6"
23
+ source = "registry+https://github.com/rust-lang/crates.io-index"
24
+ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
25
+
26
+ [[package]]
27
+ name = "atomic-waker"
28
+ version = "1.1.2"
29
+ source = "registry+https://github.com/rust-lang/crates.io-index"
30
+ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
31
+
32
+ [[package]]
33
+ name = "aws-lc-rs"
34
+ version = "1.17.0"
35
+ source = "registry+https://github.com/rust-lang/crates.io-index"
36
+ checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
37
+ dependencies = [
38
+ "aws-lc-sys",
39
+ "zeroize",
40
+ ]
41
+
42
+ [[package]]
43
+ name = "aws-lc-sys"
44
+ version = "0.41.0"
45
+ source = "registry+https://github.com/rust-lang/crates.io-index"
46
+ checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
47
+ dependencies = [
48
+ "cc",
49
+ "cmake",
50
+ "dunce",
51
+ "fs_extra",
52
+ ]
53
+
54
+ [[package]]
55
+ name = "axum"
56
+ version = "0.8.9"
57
+ source = "registry+https://github.com/rust-lang/crates.io-index"
58
+ checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
59
+ dependencies = [
60
+ "axum-core",
61
+ "bytes",
62
+ "form_urlencoded",
63
+ "futures-util",
64
+ "http",
65
+ "http-body",
66
+ "http-body-util",
67
+ "hyper",
68
+ "hyper-util",
69
+ "itoa",
70
+ "matchit",
71
+ "memchr",
72
+ "mime",
73
+ "percent-encoding",
74
+ "pin-project-lite",
75
+ "serde_core",
76
+ "serde_json",
77
+ "serde_path_to_error",
78
+ "serde_urlencoded",
79
+ "sync_wrapper",
80
+ "tokio",
81
+ "tower",
82
+ "tower-layer",
83
+ "tower-service",
84
+ "tracing",
85
+ ]
86
+
87
+ [[package]]
88
+ name = "axum-core"
89
+ version = "0.5.6"
90
+ source = "registry+https://github.com/rust-lang/crates.io-index"
91
+ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
92
+ dependencies = [
93
+ "bytes",
94
+ "futures-core",
95
+ "http",
96
+ "http-body",
97
+ "http-body-util",
98
+ "mime",
99
+ "pin-project-lite",
100
+ "sync_wrapper",
101
+ "tower-layer",
102
+ "tower-service",
103
+ "tracing",
104
+ ]
105
+
106
+ [[package]]
107
+ name = "base64"
108
+ version = "0.22.1"
109
+ source = "registry+https://github.com/rust-lang/crates.io-index"
110
+ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
111
+
112
+ [[package]]
113
+ name = "bitflags"
114
+ version = "2.13.0"
115
+ source = "registry+https://github.com/rust-lang/crates.io-index"
116
+ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
117
+
118
+ [[package]]
119
+ name = "bumpalo"
120
+ version = "3.20.3"
121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
122
+ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
123
+
124
+ [[package]]
125
+ name = "bytes"
126
+ version = "1.11.1"
127
+ source = "registry+https://github.com/rust-lang/crates.io-index"
128
+ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
129
+
130
+ [[package]]
131
+ name = "cc"
132
+ version = "1.2.64"
133
+ source = "registry+https://github.com/rust-lang/crates.io-index"
134
+ checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f"
135
+ dependencies = [
136
+ "find-msvc-tools",
137
+ "jobserver",
138
+ "libc",
139
+ "shlex",
140
+ ]
141
+
142
+ [[package]]
143
+ name = "cfg-if"
144
+ version = "1.0.4"
145
+ source = "registry+https://github.com/rust-lang/crates.io-index"
146
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
147
+
148
+ [[package]]
149
+ name = "cfg_aliases"
150
+ version = "0.2.1"
151
+ source = "registry+https://github.com/rust-lang/crates.io-index"
152
+ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
153
+
154
+ [[package]]
155
+ name = "chacha20"
156
+ version = "0.10.0"
157
+ source = "registry+https://github.com/rust-lang/crates.io-index"
158
+ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
159
+ dependencies = [
160
+ "cfg-if",
161
+ "cpufeatures",
162
+ "rand_core 0.10.1",
163
+ ]
164
+
165
+ [[package]]
166
+ name = "cmake"
167
+ version = "0.1.58"
168
+ source = "registry+https://github.com/rust-lang/crates.io-index"
169
+ checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
170
+ dependencies = [
171
+ "cc",
172
+ ]
173
+
174
+ [[package]]
175
+ name = "combine"
176
+ version = "4.6.7"
177
+ source = "registry+https://github.com/rust-lang/crates.io-index"
178
+ checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
179
+ dependencies = [
180
+ "bytes",
181
+ "memchr",
182
+ ]
183
+
184
+ [[package]]
185
+ name = "core-foundation"
186
+ version = "0.9.4"
187
+ source = "registry+https://github.com/rust-lang/crates.io-index"
188
+ checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
189
+ dependencies = [
190
+ "core-foundation-sys",
191
+ "libc",
192
+ ]
193
+
194
+ [[package]]
195
+ name = "core-foundation"
196
+ version = "0.10.1"
197
+ source = "registry+https://github.com/rust-lang/crates.io-index"
198
+ checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
199
+ dependencies = [
200
+ "core-foundation-sys",
201
+ "libc",
202
+ ]
203
+
204
+ [[package]]
205
+ name = "core-foundation-sys"
206
+ version = "0.8.7"
207
+ source = "registry+https://github.com/rust-lang/crates.io-index"
208
+ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
209
+
210
+ [[package]]
211
+ name = "cpufeatures"
212
+ version = "0.3.0"
213
+ source = "registry+https://github.com/rust-lang/crates.io-index"
214
+ checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
215
+ dependencies = [
216
+ "libc",
217
+ ]
218
+
219
+ [[package]]
220
+ name = "crossbeam-deque"
221
+ version = "0.8.6"
222
+ source = "registry+https://github.com/rust-lang/crates.io-index"
223
+ checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
224
+ dependencies = [
225
+ "crossbeam-epoch",
226
+ "crossbeam-utils",
227
+ ]
228
+
229
+ [[package]]
230
+ name = "crossbeam-epoch"
231
+ version = "0.9.18"
232
+ source = "registry+https://github.com/rust-lang/crates.io-index"
233
+ checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
234
+ dependencies = [
235
+ "crossbeam-utils",
236
+ ]
237
+
238
+ [[package]]
239
+ name = "crossbeam-utils"
240
+ version = "0.8.21"
241
+ source = "registry+https://github.com/rust-lang/crates.io-index"
242
+ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
243
+
244
+ [[package]]
245
+ name = "displaydoc"
246
+ version = "0.2.6"
247
+ source = "registry+https://github.com/rust-lang/crates.io-index"
248
+ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
249
+ dependencies = [
250
+ "proc-macro2",
251
+ "quote",
252
+ "syn",
253
+ ]
254
+
255
+ [[package]]
256
+ name = "dunce"
257
+ version = "1.0.5"
258
+ source = "registry+https://github.com/rust-lang/crates.io-index"
259
+ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
260
+
261
+ [[package]]
262
+ name = "either"
263
+ version = "1.16.0"
264
+ source = "registry+https://github.com/rust-lang/crates.io-index"
265
+ checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
266
+
267
+ [[package]]
268
+ name = "encoding_rs"
269
+ version = "0.8.35"
270
+ source = "registry+https://github.com/rust-lang/crates.io-index"
271
+ checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
272
+ dependencies = [
273
+ "cfg-if",
274
+ ]
275
+
276
+ [[package]]
277
+ name = "equivalent"
278
+ version = "1.0.2"
279
+ source = "registry+https://github.com/rust-lang/crates.io-index"
280
+ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
281
+
282
+ [[package]]
283
+ name = "errno"
284
+ version = "0.3.14"
285
+ source = "registry+https://github.com/rust-lang/crates.io-index"
286
+ checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
287
+ dependencies = [
288
+ "libc",
289
+ "windows-sys 0.61.2",
290
+ ]
291
+
292
+ [[package]]
293
+ name = "find-msvc-tools"
294
+ version = "0.1.9"
295
+ source = "registry+https://github.com/rust-lang/crates.io-index"
296
+ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
297
+
298
+ [[package]]
299
+ name = "fnv"
300
+ version = "1.0.7"
301
+ source = "registry+https://github.com/rust-lang/crates.io-index"
302
+ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
303
+
304
+ [[package]]
305
+ name = "foldhash"
306
+ version = "0.1.5"
307
+ source = "registry+https://github.com/rust-lang/crates.io-index"
308
+ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
309
+
310
+ [[package]]
311
+ name = "form_urlencoded"
312
+ version = "1.2.2"
313
+ source = "registry+https://github.com/rust-lang/crates.io-index"
314
+ checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
315
+ dependencies = [
316
+ "percent-encoding",
317
+ ]
318
+
319
+ [[package]]
320
+ name = "fs_extra"
321
+ version = "1.3.0"
322
+ source = "registry+https://github.com/rust-lang/crates.io-index"
323
+ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
324
+
325
+ [[package]]
326
+ name = "futures-channel"
327
+ version = "0.3.32"
328
+ source = "registry+https://github.com/rust-lang/crates.io-index"
329
+ checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
330
+ dependencies = [
331
+ "futures-core",
332
+ ]
333
+
334
+ [[package]]
335
+ name = "futures-core"
336
+ version = "0.3.32"
337
+ source = "registry+https://github.com/rust-lang/crates.io-index"
338
+ checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
339
+
340
+ [[package]]
341
+ name = "futures-sink"
342
+ version = "0.3.32"
343
+ source = "registry+https://github.com/rust-lang/crates.io-index"
344
+ checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
345
+
346
+ [[package]]
347
+ name = "futures-task"
348
+ version = "0.3.32"
349
+ source = "registry+https://github.com/rust-lang/crates.io-index"
350
+ checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
351
+
352
+ [[package]]
353
+ name = "futures-util"
354
+ version = "0.3.32"
355
+ source = "registry+https://github.com/rust-lang/crates.io-index"
356
+ checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
357
+ dependencies = [
358
+ "futures-core",
359
+ "futures-task",
360
+ "pin-project-lite",
361
+ "slab",
362
+ ]
363
+
364
+ [[package]]
365
+ name = "getrandom"
366
+ version = "0.2.17"
367
+ source = "registry+https://github.com/rust-lang/crates.io-index"
368
+ checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
369
+ dependencies = [
370
+ "cfg-if",
371
+ "js-sys",
372
+ "libc",
373
+ "wasi",
374
+ "wasm-bindgen",
375
+ ]
376
+
377
+ [[package]]
378
+ name = "getrandom"
379
+ version = "0.3.4"
380
+ source = "registry+https://github.com/rust-lang/crates.io-index"
381
+ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
382
+ dependencies = [
383
+ "cfg-if",
384
+ "js-sys",
385
+ "libc",
386
+ "r-efi 5.3.0",
387
+ "wasip2",
388
+ "wasm-bindgen",
389
+ ]
390
+
391
+ [[package]]
392
+ name = "getrandom"
393
+ version = "0.4.2"
394
+ source = "registry+https://github.com/rust-lang/crates.io-index"
395
+ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
396
+ dependencies = [
397
+ "cfg-if",
398
+ "libc",
399
+ "r-efi 6.0.0",
400
+ "rand_core 0.10.1",
401
+ "wasip2",
402
+ "wasip3",
403
+ ]
404
+
405
+ [[package]]
406
+ name = "h2"
407
+ version = "0.4.14"
408
+ source = "registry+https://github.com/rust-lang/crates.io-index"
409
+ checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
410
+ dependencies = [
411
+ "atomic-waker",
412
+ "bytes",
413
+ "fnv",
414
+ "futures-core",
415
+ "futures-sink",
416
+ "http",
417
+ "indexmap",
418
+ "slab",
419
+ "tokio",
420
+ "tokio-util",
421
+ "tracing",
422
+ ]
423
+
424
+ [[package]]
425
+ name = "hashbrown"
426
+ version = "0.15.5"
427
+ source = "registry+https://github.com/rust-lang/crates.io-index"
428
+ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
429
+ dependencies = [
430
+ "foldhash",
431
+ ]
432
+
433
+ [[package]]
434
+ name = "hashbrown"
435
+ version = "0.17.1"
436
+ source = "registry+https://github.com/rust-lang/crates.io-index"
437
+ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
438
+
439
+ [[package]]
440
+ name = "heck"
441
+ version = "0.5.0"
442
+ source = "registry+https://github.com/rust-lang/crates.io-index"
443
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
444
+
445
+ [[package]]
446
+ name = "http"
447
+ version = "1.4.2"
448
+ source = "registry+https://github.com/rust-lang/crates.io-index"
449
+ checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
450
+ dependencies = [
451
+ "bytes",
452
+ "itoa",
453
+ ]
454
+
455
+ [[package]]
456
+ name = "http-body"
457
+ version = "1.0.1"
458
+ source = "registry+https://github.com/rust-lang/crates.io-index"
459
+ checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
460
+ dependencies = [
461
+ "bytes",
462
+ "http",
463
+ ]
464
+
465
+ [[package]]
466
+ name = "http-body-util"
467
+ version = "0.1.3"
468
+ source = "registry+https://github.com/rust-lang/crates.io-index"
469
+ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
470
+ dependencies = [
471
+ "bytes",
472
+ "futures-core",
473
+ "http",
474
+ "http-body",
475
+ "pin-project-lite",
476
+ ]
477
+
478
+ [[package]]
479
+ name = "http-range-header"
480
+ version = "0.4.2"
481
+ source = "registry+https://github.com/rust-lang/crates.io-index"
482
+ checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
483
+
484
+ [[package]]
485
+ name = "httparse"
486
+ version = "1.10.1"
487
+ source = "registry+https://github.com/rust-lang/crates.io-index"
488
+ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
489
+
490
+ [[package]]
491
+ name = "httpdate"
492
+ version = "1.0.3"
493
+ source = "registry+https://github.com/rust-lang/crates.io-index"
494
+ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
495
+
496
+ [[package]]
497
+ name = "hyper"
498
+ version = "1.10.1"
499
+ source = "registry+https://github.com/rust-lang/crates.io-index"
500
+ checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
501
+ dependencies = [
502
+ "atomic-waker",
503
+ "bytes",
504
+ "futures-channel",
505
+ "futures-core",
506
+ "h2",
507
+ "http",
508
+ "http-body",
509
+ "httparse",
510
+ "httpdate",
511
+ "itoa",
512
+ "pin-project-lite",
513
+ "smallvec",
514
+ "tokio",
515
+ "want",
516
+ ]
517
+
518
+ [[package]]
519
+ name = "hyper-rustls"
520
+ version = "0.27.9"
521
+ source = "registry+https://github.com/rust-lang/crates.io-index"
522
+ checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
523
+ dependencies = [
524
+ "http",
525
+ "hyper",
526
+ "hyper-util",
527
+ "rustls",
528
+ "tokio",
529
+ "tokio-rustls",
530
+ "tower-service",
531
+ ]
532
+
533
+ [[package]]
534
+ name = "hyper-util"
535
+ version = "0.1.20"
536
+ source = "registry+https://github.com/rust-lang/crates.io-index"
537
+ checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
538
+ dependencies = [
539
+ "base64",
540
+ "bytes",
541
+ "futures-channel",
542
+ "futures-util",
543
+ "http",
544
+ "http-body",
545
+ "hyper",
546
+ "ipnet",
547
+ "libc",
548
+ "percent-encoding",
549
+ "pin-project-lite",
550
+ "socket2",
551
+ "system-configuration",
552
+ "tokio",
553
+ "tower-service",
554
+ "tracing",
555
+ "windows-registry",
556
+ ]
557
+
558
+ [[package]]
559
+ name = "icu_collections"
560
+ version = "2.2.0"
561
+ source = "registry+https://github.com/rust-lang/crates.io-index"
562
+ checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
563
+ dependencies = [
564
+ "displaydoc",
565
+ "potential_utf",
566
+ "utf8_iter",
567
+ "yoke",
568
+ "zerofrom",
569
+ "zerovec",
570
+ ]
571
+
572
+ [[package]]
573
+ name = "icu_locale_core"
574
+ version = "2.2.0"
575
+ source = "registry+https://github.com/rust-lang/crates.io-index"
576
+ checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
577
+ dependencies = [
578
+ "displaydoc",
579
+ "litemap",
580
+ "tinystr",
581
+ "writeable",
582
+ "zerovec",
583
+ ]
584
+
585
+ [[package]]
586
+ name = "icu_normalizer"
587
+ version = "2.2.0"
588
+ source = "registry+https://github.com/rust-lang/crates.io-index"
589
+ checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
590
+ dependencies = [
591
+ "icu_collections",
592
+ "icu_normalizer_data",
593
+ "icu_properties",
594
+ "icu_provider",
595
+ "smallvec",
596
+ "zerovec",
597
+ ]
598
+
599
+ [[package]]
600
+ name = "icu_normalizer_data"
601
+ version = "2.2.0"
602
+ source = "registry+https://github.com/rust-lang/crates.io-index"
603
+ checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
604
+
605
+ [[package]]
606
+ name = "icu_properties"
607
+ version = "2.2.0"
608
+ source = "registry+https://github.com/rust-lang/crates.io-index"
609
+ checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
610
+ dependencies = [
611
+ "icu_collections",
612
+ "icu_locale_core",
613
+ "icu_properties_data",
614
+ "icu_provider",
615
+ "zerotrie",
616
+ "zerovec",
617
+ ]
618
+
619
+ [[package]]
620
+ name = "icu_properties_data"
621
+ version = "2.2.0"
622
+ source = "registry+https://github.com/rust-lang/crates.io-index"
623
+ checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
624
+
625
+ [[package]]
626
+ name = "icu_provider"
627
+ version = "2.2.0"
628
+ source = "registry+https://github.com/rust-lang/crates.io-index"
629
+ checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
630
+ dependencies = [
631
+ "displaydoc",
632
+ "icu_locale_core",
633
+ "writeable",
634
+ "yoke",
635
+ "zerofrom",
636
+ "zerotrie",
637
+ "zerovec",
638
+ ]
639
+
640
+ [[package]]
641
+ name = "id-arena"
642
+ version = "2.3.0"
643
+ source = "registry+https://github.com/rust-lang/crates.io-index"
644
+ checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
645
+
646
+ [[package]]
647
+ name = "idna"
648
+ version = "1.1.0"
649
+ source = "registry+https://github.com/rust-lang/crates.io-index"
650
+ checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
651
+ dependencies = [
652
+ "idna_adapter",
653
+ "smallvec",
654
+ "utf8_iter",
655
+ ]
656
+
657
+ [[package]]
658
+ name = "idna_adapter"
659
+ version = "1.2.2"
660
+ source = "registry+https://github.com/rust-lang/crates.io-index"
661
+ checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
662
+ dependencies = [
663
+ "icu_normalizer",
664
+ "icu_properties",
665
+ ]
666
+
667
+ [[package]]
668
+ name = "include_dir"
669
+ version = "0.7.4"
670
+ source = "registry+https://github.com/rust-lang/crates.io-index"
671
+ checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
672
+ dependencies = [
673
+ "include_dir_macros",
674
+ ]
675
+
676
+ [[package]]
677
+ name = "include_dir_macros"
678
+ version = "0.7.4"
679
+ source = "registry+https://github.com/rust-lang/crates.io-index"
680
+ checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
681
+ dependencies = [
682
+ "proc-macro2",
683
+ "quote",
684
+ ]
685
+
686
+ [[package]]
687
+ name = "indexmap"
688
+ version = "2.14.0"
689
+ source = "registry+https://github.com/rust-lang/crates.io-index"
690
+ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
691
+ dependencies = [
692
+ "equivalent",
693
+ "hashbrown 0.17.1",
694
+ "serde",
695
+ "serde_core",
696
+ ]
697
+
698
+ [[package]]
699
+ name = "ipnet"
700
+ version = "2.12.0"
701
+ source = "registry+https://github.com/rust-lang/crates.io-index"
702
+ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
703
+
704
+ [[package]]
705
+ name = "itoa"
706
+ version = "1.0.18"
707
+ source = "registry+https://github.com/rust-lang/crates.io-index"
708
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
709
+
710
+ [[package]]
711
+ name = "jni"
712
+ version = "0.22.4"
713
+ source = "registry+https://github.com/rust-lang/crates.io-index"
714
+ checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
715
+ dependencies = [
716
+ "cfg-if",
717
+ "combine",
718
+ "jni-macros",
719
+ "jni-sys",
720
+ "log",
721
+ "simd_cesu8",
722
+ "thiserror",
723
+ "walkdir",
724
+ "windows-link",
725
+ ]
726
+
727
+ [[package]]
728
+ name = "jni-macros"
729
+ version = "0.22.4"
730
+ source = "registry+https://github.com/rust-lang/crates.io-index"
731
+ checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
732
+ dependencies = [
733
+ "proc-macro2",
734
+ "quote",
735
+ "rustc_version",
736
+ "simd_cesu8",
737
+ "syn",
738
+ ]
739
+
740
+ [[package]]
741
+ name = "jni-sys"
742
+ version = "0.4.1"
743
+ source = "registry+https://github.com/rust-lang/crates.io-index"
744
+ checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
745
+ dependencies = [
746
+ "jni-sys-macros",
747
+ ]
748
+
749
+ [[package]]
750
+ name = "jni-sys-macros"
751
+ version = "0.4.1"
752
+ source = "registry+https://github.com/rust-lang/crates.io-index"
753
+ checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
754
+ dependencies = [
755
+ "quote",
756
+ "syn",
757
+ ]
758
+
759
+ [[package]]
760
+ name = "jobserver"
761
+ version = "0.1.34"
762
+ source = "registry+https://github.com/rust-lang/crates.io-index"
763
+ checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
764
+ dependencies = [
765
+ "getrandom 0.3.4",
766
+ "libc",
767
+ ]
768
+
769
+ [[package]]
770
+ name = "js-sys"
771
+ version = "0.3.100"
772
+ source = "registry+https://github.com/rust-lang/crates.io-index"
773
+ checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162"
774
+ dependencies = [
775
+ "cfg-if",
776
+ "futures-util",
777
+ "wasm-bindgen",
778
+ ]
779
+
780
+ [[package]]
781
+ name = "lazy_static"
782
+ version = "1.5.0"
783
+ source = "registry+https://github.com/rust-lang/crates.io-index"
784
+ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
785
+
786
+ [[package]]
787
+ name = "leb128fmt"
788
+ version = "0.1.0"
789
+ source = "registry+https://github.com/rust-lang/crates.io-index"
790
+ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
791
+
792
+ [[package]]
793
+ name = "libc"
794
+ version = "0.2.186"
795
+ source = "registry+https://github.com/rust-lang/crates.io-index"
796
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
797
+
798
+ [[package]]
799
+ name = "litemap"
800
+ version = "0.8.2"
801
+ source = "registry+https://github.com/rust-lang/crates.io-index"
802
+ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
803
+
804
+ [[package]]
805
+ name = "lock_api"
806
+ version = "0.4.14"
807
+ source = "registry+https://github.com/rust-lang/crates.io-index"
808
+ checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
809
+ dependencies = [
810
+ "scopeguard",
811
+ ]
812
+
813
+ [[package]]
814
+ name = "log"
815
+ version = "0.4.32"
816
+ source = "registry+https://github.com/rust-lang/crates.io-index"
817
+ checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
818
+
819
+ [[package]]
820
+ name = "lru-slab"
821
+ version = "0.1.2"
822
+ source = "registry+https://github.com/rust-lang/crates.io-index"
823
+ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
824
+
825
+ [[package]]
826
+ name = "matchers"
827
+ version = "0.2.0"
828
+ source = "registry+https://github.com/rust-lang/crates.io-index"
829
+ checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
830
+ dependencies = [
831
+ "regex-automata",
832
+ ]
833
+
834
+ [[package]]
835
+ name = "matchit"
836
+ version = "0.8.4"
837
+ source = "registry+https://github.com/rust-lang/crates.io-index"
838
+ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
839
+
840
+ [[package]]
841
+ name = "memchr"
842
+ version = "2.8.2"
843
+ source = "registry+https://github.com/rust-lang/crates.io-index"
844
+ checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
845
+
846
+ [[package]]
847
+ name = "mime"
848
+ version = "0.3.17"
849
+ source = "registry+https://github.com/rust-lang/crates.io-index"
850
+ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
851
+
852
+ [[package]]
853
+ name = "mime_guess"
854
+ version = "2.0.5"
855
+ source = "registry+https://github.com/rust-lang/crates.io-index"
856
+ checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
857
+ dependencies = [
858
+ "mime",
859
+ "unicase",
860
+ ]
861
+
862
+ [[package]]
863
+ name = "mio"
864
+ version = "1.2.1"
865
+ source = "registry+https://github.com/rust-lang/crates.io-index"
866
+ checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
867
+ dependencies = [
868
+ "libc",
869
+ "wasi",
870
+ "windows-sys 0.61.2",
871
+ ]
872
+
873
+ [[package]]
874
+ name = "nu-ansi-term"
875
+ version = "0.50.3"
876
+ source = "registry+https://github.com/rust-lang/crates.io-index"
877
+ checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
878
+ dependencies = [
879
+ "windows-sys 0.61.2",
880
+ ]
881
+
882
+ [[package]]
883
+ name = "num-format"
884
+ version = "0.4.4"
885
+ source = "registry+https://github.com/rust-lang/crates.io-index"
886
+ checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
887
+ dependencies = [
888
+ "arrayvec",
889
+ "itoa",
890
+ ]
891
+
892
+ [[package]]
893
+ name = "once_cell"
894
+ version = "1.21.4"
895
+ source = "registry+https://github.com/rust-lang/crates.io-index"
896
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
897
+
898
+ [[package]]
899
+ name = "openssl-probe"
900
+ version = "0.2.1"
901
+ source = "registry+https://github.com/rust-lang/crates.io-index"
902
+ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
903
+
904
+ [[package]]
905
+ name = "owo-colors"
906
+ version = "4.3.0"
907
+ source = "registry+https://github.com/rust-lang/crates.io-index"
908
+ checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
909
+
910
+ [[package]]
911
+ name = "parking_lot"
912
+ version = "0.12.5"
913
+ source = "registry+https://github.com/rust-lang/crates.io-index"
914
+ checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
915
+ dependencies = [
916
+ "lock_api",
917
+ "parking_lot_core",
918
+ ]
919
+
920
+ [[package]]
921
+ name = "parking_lot_core"
922
+ version = "0.9.12"
923
+ source = "registry+https://github.com/rust-lang/crates.io-index"
924
+ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
925
+ dependencies = [
926
+ "cfg-if",
927
+ "libc",
928
+ "redox_syscall",
929
+ "smallvec",
930
+ "windows-link",
931
+ ]
932
+
933
+ [[package]]
934
+ name = "percent-encoding"
935
+ version = "2.3.2"
936
+ source = "registry+https://github.com/rust-lang/crates.io-index"
937
+ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
938
+
939
+ [[package]]
940
+ name = "pin-project-lite"
941
+ version = "0.2.17"
942
+ source = "registry+https://github.com/rust-lang/crates.io-index"
943
+ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
944
+
945
+ [[package]]
946
+ name = "potential_utf"
947
+ version = "0.1.5"
948
+ source = "registry+https://github.com/rust-lang/crates.io-index"
949
+ checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
950
+ dependencies = [
951
+ "zerovec",
952
+ ]
953
+
954
+ [[package]]
955
+ name = "ppv-lite86"
956
+ version = "0.2.21"
957
+ source = "registry+https://github.com/rust-lang/crates.io-index"
958
+ checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
959
+ dependencies = [
960
+ "zerocopy",
961
+ ]
962
+
963
+ [[package]]
964
+ name = "prettyplease"
965
+ version = "0.2.37"
966
+ source = "registry+https://github.com/rust-lang/crates.io-index"
967
+ checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
968
+ dependencies = [
969
+ "proc-macro2",
970
+ "syn",
971
+ ]
972
+
973
+ [[package]]
974
+ name = "proc-macro2"
975
+ version = "1.0.106"
976
+ source = "registry+https://github.com/rust-lang/crates.io-index"
977
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
978
+ dependencies = [
979
+ "unicode-ident",
980
+ ]
981
+
982
+ [[package]]
983
+ name = "quinn"
984
+ version = "0.11.9"
985
+ source = "registry+https://github.com/rust-lang/crates.io-index"
986
+ checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
987
+ dependencies = [
988
+ "bytes",
989
+ "cfg_aliases",
990
+ "pin-project-lite",
991
+ "quinn-proto",
992
+ "quinn-udp",
993
+ "rustc-hash",
994
+ "rustls",
995
+ "socket2",
996
+ "thiserror",
997
+ "tokio",
998
+ "tracing",
999
+ "web-time",
1000
+ ]
1001
+
1002
+ [[package]]
1003
+ name = "quinn-proto"
1004
+ version = "0.11.14"
1005
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1006
+ checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
1007
+ dependencies = [
1008
+ "aws-lc-rs",
1009
+ "bytes",
1010
+ "getrandom 0.3.4",
1011
+ "lru-slab",
1012
+ "rand 0.9.4",
1013
+ "ring",
1014
+ "rustc-hash",
1015
+ "rustls",
1016
+ "rustls-pki-types",
1017
+ "slab",
1018
+ "thiserror",
1019
+ "tinyvec",
1020
+ "tracing",
1021
+ "web-time",
1022
+ ]
1023
+
1024
+ [[package]]
1025
+ name = "quinn-udp"
1026
+ version = "0.5.14"
1027
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1028
+ checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
1029
+ dependencies = [
1030
+ "cfg_aliases",
1031
+ "libc",
1032
+ "once_cell",
1033
+ "socket2",
1034
+ "tracing",
1035
+ "windows-sys 0.60.2",
1036
+ ]
1037
+
1038
+ [[package]]
1039
+ name = "quote"
1040
+ version = "1.0.45"
1041
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1042
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
1043
+ dependencies = [
1044
+ "proc-macro2",
1045
+ ]
1046
+
1047
+ [[package]]
1048
+ name = "r-efi"
1049
+ version = "5.3.0"
1050
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1051
+ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
1052
+
1053
+ [[package]]
1054
+ name = "r-efi"
1055
+ version = "6.0.0"
1056
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1057
+ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
1058
+
1059
+ [[package]]
1060
+ name = "rand"
1061
+ version = "0.9.4"
1062
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1063
+ checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
1064
+ dependencies = [
1065
+ "rand_chacha 0.9.0",
1066
+ "rand_core 0.9.5",
1067
+ ]
1068
+
1069
+ [[package]]
1070
+ name = "rand"
1071
+ version = "0.10.1"
1072
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1073
+ checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
1074
+ dependencies = [
1075
+ "chacha20",
1076
+ "getrandom 0.4.2",
1077
+ "rand_core 0.10.1",
1078
+ ]
1079
+
1080
+ [[package]]
1081
+ name = "rand_chacha"
1082
+ version = "0.9.0"
1083
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1084
+ checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
1085
+ dependencies = [
1086
+ "ppv-lite86",
1087
+ "rand_core 0.9.5",
1088
+ ]
1089
+
1090
+ [[package]]
1091
+ name = "rand_chacha"
1092
+ version = "0.10.0"
1093
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1094
+ checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb"
1095
+ dependencies = [
1096
+ "ppv-lite86",
1097
+ "rand_core 0.10.1",
1098
+ ]
1099
+
1100
+ [[package]]
1101
+ name = "rand_core"
1102
+ version = "0.9.5"
1103
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1104
+ checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
1105
+ dependencies = [
1106
+ "getrandom 0.3.4",
1107
+ ]
1108
+
1109
+ [[package]]
1110
+ name = "rand_core"
1111
+ version = "0.10.1"
1112
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1113
+ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
1114
+
1115
+ [[package]]
1116
+ name = "rayon"
1117
+ version = "1.12.0"
1118
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1119
+ checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
1120
+ dependencies = [
1121
+ "either",
1122
+ "rayon-core",
1123
+ ]
1124
+
1125
+ [[package]]
1126
+ name = "rayon-core"
1127
+ version = "1.13.0"
1128
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1129
+ checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
1130
+ dependencies = [
1131
+ "crossbeam-deque",
1132
+ "crossbeam-utils",
1133
+ ]
1134
+
1135
+ [[package]]
1136
+ name = "redox_syscall"
1137
+ version = "0.5.18"
1138
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1139
+ checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
1140
+ dependencies = [
1141
+ "bitflags",
1142
+ ]
1143
+
1144
+ [[package]]
1145
+ name = "regex-automata"
1146
+ version = "0.4.14"
1147
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1148
+ checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
1149
+ dependencies = [
1150
+ "aho-corasick",
1151
+ "memchr",
1152
+ "regex-syntax",
1153
+ ]
1154
+
1155
+ [[package]]
1156
+ name = "regex-syntax"
1157
+ version = "0.8.11"
1158
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1159
+ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
1160
+
1161
+ [[package]]
1162
+ name = "reqwest"
1163
+ version = "0.13.4"
1164
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1165
+ checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
1166
+ dependencies = [
1167
+ "base64",
1168
+ "bytes",
1169
+ "encoding_rs",
1170
+ "futures-core",
1171
+ "h2",
1172
+ "http",
1173
+ "http-body",
1174
+ "http-body-util",
1175
+ "hyper",
1176
+ "hyper-rustls",
1177
+ "hyper-util",
1178
+ "js-sys",
1179
+ "log",
1180
+ "mime",
1181
+ "percent-encoding",
1182
+ "pin-project-lite",
1183
+ "quinn",
1184
+ "rustls",
1185
+ "rustls-pki-types",
1186
+ "rustls-platform-verifier",
1187
+ "serde",
1188
+ "serde_json",
1189
+ "sync_wrapper",
1190
+ "tokio",
1191
+ "tokio-rustls",
1192
+ "tower",
1193
+ "tower-http",
1194
+ "tower-service",
1195
+ "url",
1196
+ "wasm-bindgen",
1197
+ "wasm-bindgen-futures",
1198
+ "web-sys",
1199
+ ]
1200
+
1201
+ [[package]]
1202
+ name = "ring"
1203
+ version = "0.17.14"
1204
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1205
+ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
1206
+ dependencies = [
1207
+ "cc",
1208
+ "cfg-if",
1209
+ "getrandom 0.2.17",
1210
+ "libc",
1211
+ "untrusted",
1212
+ "windows-sys 0.52.0",
1213
+ ]
1214
+
1215
+ [[package]]
1216
+ name = "rustc-hash"
1217
+ version = "2.1.2"
1218
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1219
+ checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
1220
+
1221
+ [[package]]
1222
+ name = "rustc_version"
1223
+ version = "0.4.1"
1224
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1225
+ checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
1226
+ dependencies = [
1227
+ "semver",
1228
+ ]
1229
+
1230
+ [[package]]
1231
+ name = "rustls"
1232
+ version = "0.23.40"
1233
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1234
+ checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
1235
+ dependencies = [
1236
+ "aws-lc-rs",
1237
+ "once_cell",
1238
+ "rustls-pki-types",
1239
+ "rustls-webpki",
1240
+ "subtle",
1241
+ "zeroize",
1242
+ ]
1243
+
1244
+ [[package]]
1245
+ name = "rustls-native-certs"
1246
+ version = "0.8.4"
1247
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1248
+ checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
1249
+ dependencies = [
1250
+ "openssl-probe",
1251
+ "rustls-pki-types",
1252
+ "schannel",
1253
+ "security-framework",
1254
+ ]
1255
+
1256
+ [[package]]
1257
+ name = "rustls-pki-types"
1258
+ version = "1.14.1"
1259
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1260
+ checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
1261
+ dependencies = [
1262
+ "web-time",
1263
+ "zeroize",
1264
+ ]
1265
+
1266
+ [[package]]
1267
+ name = "rustls-platform-verifier"
1268
+ version = "0.7.0"
1269
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1270
+ checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
1271
+ dependencies = [
1272
+ "core-foundation 0.10.1",
1273
+ "core-foundation-sys",
1274
+ "jni",
1275
+ "log",
1276
+ "once_cell",
1277
+ "rustls",
1278
+ "rustls-native-certs",
1279
+ "rustls-platform-verifier-android",
1280
+ "rustls-webpki",
1281
+ "security-framework",
1282
+ "security-framework-sys",
1283
+ "webpki-root-certs",
1284
+ "windows-sys 0.61.2",
1285
+ ]
1286
+
1287
+ [[package]]
1288
+ name = "rustls-platform-verifier-android"
1289
+ version = "0.1.1"
1290
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1291
+ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
1292
+
1293
+ [[package]]
1294
+ name = "rustls-webpki"
1295
+ version = "0.103.13"
1296
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1297
+ checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
1298
+ dependencies = [
1299
+ "aws-lc-rs",
1300
+ "ring",
1301
+ "rustls-pki-types",
1302
+ "untrusted",
1303
+ ]
1304
+
1305
+ [[package]]
1306
+ name = "rustversion"
1307
+ version = "1.0.22"
1308
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1309
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
1310
+
1311
+ [[package]]
1312
+ name = "ryu"
1313
+ version = "1.0.23"
1314
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1315
+ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
1316
+
1317
+ [[package]]
1318
+ name = "same-file"
1319
+ version = "1.0.6"
1320
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1321
+ checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
1322
+ dependencies = [
1323
+ "winapi-util",
1324
+ ]
1325
+
1326
+ [[package]]
1327
+ name = "schannel"
1328
+ version = "0.1.29"
1329
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1330
+ checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
1331
+ dependencies = [
1332
+ "windows-sys 0.61.2",
1333
+ ]
1334
+
1335
+ [[package]]
1336
+ name = "scopeguard"
1337
+ version = "1.2.0"
1338
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1339
+ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
1340
+
1341
+ [[package]]
1342
+ name = "security-framework"
1343
+ version = "3.7.0"
1344
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1345
+ checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
1346
+ dependencies = [
1347
+ "bitflags",
1348
+ "core-foundation 0.10.1",
1349
+ "core-foundation-sys",
1350
+ "libc",
1351
+ "security-framework-sys",
1352
+ ]
1353
+
1354
+ [[package]]
1355
+ name = "security-framework-sys"
1356
+ version = "2.17.0"
1357
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1358
+ checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
1359
+ dependencies = [
1360
+ "core-foundation-sys",
1361
+ "libc",
1362
+ ]
1363
+
1364
+ [[package]]
1365
+ name = "semver"
1366
+ version = "1.0.28"
1367
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1368
+ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
1369
+
1370
+ [[package]]
1371
+ name = "serde"
1372
+ version = "1.0.228"
1373
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1374
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
1375
+ dependencies = [
1376
+ "serde_core",
1377
+ "serde_derive",
1378
+ ]
1379
+
1380
+ [[package]]
1381
+ name = "serde_core"
1382
+ version = "1.0.228"
1383
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1384
+ checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
1385
+ dependencies = [
1386
+ "serde_derive",
1387
+ ]
1388
+
1389
+ [[package]]
1390
+ name = "serde_derive"
1391
+ version = "1.0.228"
1392
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1393
+ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
1394
+ dependencies = [
1395
+ "proc-macro2",
1396
+ "quote",
1397
+ "syn",
1398
+ ]
1399
+
1400
+ [[package]]
1401
+ name = "serde_json"
1402
+ version = "1.0.150"
1403
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1404
+ checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
1405
+ dependencies = [
1406
+ "itoa",
1407
+ "memchr",
1408
+ "serde",
1409
+ "serde_core",
1410
+ "zmij",
1411
+ ]
1412
+
1413
+ [[package]]
1414
+ name = "serde_path_to_error"
1415
+ version = "0.1.20"
1416
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1417
+ checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
1418
+ dependencies = [
1419
+ "itoa",
1420
+ "serde",
1421
+ "serde_core",
1422
+ ]
1423
+
1424
+ [[package]]
1425
+ name = "serde_spanned"
1426
+ version = "1.1.1"
1427
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1428
+ checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
1429
+ dependencies = [
1430
+ "serde_core",
1431
+ ]
1432
+
1433
+ [[package]]
1434
+ name = "serde_urlencoded"
1435
+ version = "0.7.1"
1436
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1437
+ checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
1438
+ dependencies = [
1439
+ "form_urlencoded",
1440
+ "itoa",
1441
+ "ryu",
1442
+ "serde",
1443
+ ]
1444
+
1445
+ [[package]]
1446
+ name = "serde_yaml"
1447
+ version = "0.9.34+deprecated"
1448
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1449
+ checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
1450
+ dependencies = [
1451
+ "indexmap",
1452
+ "itoa",
1453
+ "ryu",
1454
+ "serde",
1455
+ "unsafe-libyaml",
1456
+ ]
1457
+
1458
+ [[package]]
1459
+ name = "sharded-slab"
1460
+ version = "0.1.7"
1461
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1462
+ checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
1463
+ dependencies = [
1464
+ "lazy_static",
1465
+ ]
1466
+
1467
+ [[package]]
1468
+ name = "shlex"
1469
+ version = "2.0.1"
1470
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1471
+ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
1472
+
1473
+ [[package]]
1474
+ name = "signal-hook-registry"
1475
+ version = "1.4.8"
1476
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1477
+ checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
1478
+ dependencies = [
1479
+ "errno",
1480
+ "libc",
1481
+ ]
1482
+
1483
+ [[package]]
1484
+ name = "simd_cesu8"
1485
+ version = "1.1.1"
1486
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1487
+ checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
1488
+ dependencies = [
1489
+ "rustc_version",
1490
+ "simdutf8",
1491
+ ]
1492
+
1493
+ [[package]]
1494
+ name = "simdutf8"
1495
+ version = "0.1.5"
1496
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1497
+ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
1498
+
1499
+ [[package]]
1500
+ name = "slab"
1501
+ version = "0.4.12"
1502
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1503
+ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
1504
+
1505
+ [[package]]
1506
+ name = "smallvec"
1507
+ version = "1.15.2"
1508
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1509
+ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
1510
+
1511
+ [[package]]
1512
+ name = "socket2"
1513
+ version = "0.6.4"
1514
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1515
+ checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
1516
+ dependencies = [
1517
+ "libc",
1518
+ "windows-sys 0.61.2",
1519
+ ]
1520
+
1521
+ [[package]]
1522
+ name = "solverforge"
1523
+ version = "0.19.3"
1524
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1525
+ checksum = "15d0a5dfb480a56ec3ee9aa048fd54bba3e65b04866bde610c1a956b89b4da80"
1526
+ dependencies = [
1527
+ "solverforge-bridge",
1528
+ "solverforge-config",
1529
+ "solverforge-console",
1530
+ "solverforge-core",
1531
+ "solverforge-cvrp",
1532
+ "solverforge-macros",
1533
+ "solverforge-scoring",
1534
+ "solverforge-solver",
1535
+ ]
1536
+
1537
+ [[package]]
1538
+ name = "solverforge-bridge"
1539
+ version = "0.19.3"
1540
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1541
+ checksum = "c78f964f79318c3c0ee3161ed3004c708f04c4a751f784fe7d0ccc908194aaa3"
1542
+ dependencies = [
1543
+ "solverforge-config",
1544
+ "solverforge-core",
1545
+ "solverforge-scoring",
1546
+ "solverforge-solver",
1547
+ ]
1548
+
1549
+ [[package]]
1550
+ name = "solverforge-config"
1551
+ version = "0.19.3"
1552
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1553
+ checksum = "27d4cb13eb5097514c5d18eeddb0f88521e3f3d8d402a0d22ffbbdcd08c90c21"
1554
+ dependencies = [
1555
+ "serde",
1556
+ "serde_yaml",
1557
+ "solverforge-core",
1558
+ "thiserror",
1559
+ "toml",
1560
+ ]
1561
+
1562
+ [[package]]
1563
+ name = "solverforge-console"
1564
+ version = "0.19.3"
1565
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1566
+ checksum = "3f64e369266512f53d2bd335592975193fdc4d8c2d6da99929f152edfd721562"
1567
+ dependencies = [
1568
+ "num-format",
1569
+ "owo-colors",
1570
+ "tracing",
1571
+ "tracing-subscriber",
1572
+ ]
1573
+
1574
+ [[package]]
1575
+ name = "solverforge-core"
1576
+ version = "0.19.3"
1577
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1578
+ checksum = "e6118e7d98c3c8ef58646b2779603dd1ab6f9dc9f25481cb104402cbcd06be46"
1579
+ dependencies = [
1580
+ "serde",
1581
+ "thiserror",
1582
+ ]
1583
+
1584
+ [[package]]
1585
+ name = "solverforge-cvrp"
1586
+ version = "0.19.3"
1587
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1588
+ checksum = "554c12afb5908f396833688be18e649eee5967df8c41c0e4f20f0a10445ff651"
1589
+ dependencies = [
1590
+ "solverforge-solver",
1591
+ ]
1592
+
1593
+ [[package]]
1594
+ name = "solverforge-deliveries"
1595
+ version = "2.0.6"
1596
+ dependencies = [
1597
+ "axum",
1598
+ "http-body-util",
1599
+ "parking_lot",
1600
+ "rand 0.10.1",
1601
+ "serde",
1602
+ "serde_json",
1603
+ "solverforge",
1604
+ "solverforge-maps",
1605
+ "solverforge-ui",
1606
+ "tokio",
1607
+ "tokio-stream",
1608
+ "tower",
1609
+ "tower-http",
1610
+ "uuid",
1611
+ ]
1612
+
1613
+ [[package]]
1614
+ name = "solverforge-macros"
1615
+ version = "0.19.3"
1616
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1617
+ checksum = "22c15f305806b185fc4815f8da0ec73acb9acbba5de3583433ab7b6bb2eeffa4"
1618
+ dependencies = [
1619
+ "proc-macro2",
1620
+ "quote",
1621
+ "syn",
1622
+ ]
1623
+
1624
+ [[package]]
1625
+ name = "solverforge-maps"
1626
+ version = "2.1.4"
1627
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1628
+ checksum = "e31f816d221238ba3ade93315e6a605486b53d9ec26527477d165b507ece6a88"
1629
+ dependencies = [
1630
+ "rayon",
1631
+ "reqwest",
1632
+ "serde",
1633
+ "serde_json",
1634
+ "tokio",
1635
+ "tracing",
1636
+ "utoipa",
1637
+ ]
1638
+
1639
+ [[package]]
1640
+ name = "solverforge-scoring"
1641
+ version = "0.19.3"
1642
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1643
+ checksum = "3ddf9f9af52b0d2525f581f921e41fac8352dc2f6f1deb7f9100aff9cfedba25"
1644
+ dependencies = [
1645
+ "solverforge-core",
1646
+ "thiserror",
1647
+ ]
1648
+
1649
+ [[package]]
1650
+ name = "solverforge-solver"
1651
+ version = "0.19.3"
1652
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1653
+ checksum = "19d30aece798a48d08edc630734a181dd2d9bcc1e806de7ec27701f9885989a6"
1654
+ dependencies = [
1655
+ "rand 0.10.1",
1656
+ "rand_chacha 0.10.0",
1657
+ "rayon",
1658
+ "serde",
1659
+ "smallvec",
1660
+ "solverforge-config",
1661
+ "solverforge-core",
1662
+ "solverforge-scoring",
1663
+ "thiserror",
1664
+ "tokio",
1665
+ "tracing",
1666
+ ]
1667
+
1668
+ [[package]]
1669
+ name = "solverforge-ui"
1670
+ version = "0.6.5"
1671
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1672
+ checksum = "1c7fa2d78c84af9a1e264adcffc1bdf8cb4edab8d73a3543fb448d166c95596f"
1673
+ dependencies = [
1674
+ "axum",
1675
+ "include_dir",
1676
+ ]
1677
+
1678
+ [[package]]
1679
+ name = "stable_deref_trait"
1680
+ version = "1.2.1"
1681
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1682
+ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
1683
+
1684
+ [[package]]
1685
+ name = "subtle"
1686
+ version = "2.6.1"
1687
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1688
+ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
1689
+
1690
+ [[package]]
1691
+ name = "syn"
1692
+ version = "2.0.117"
1693
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1694
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
1695
+ dependencies = [
1696
+ "proc-macro2",
1697
+ "quote",
1698
+ "unicode-ident",
1699
+ ]
1700
+
1701
+ [[package]]
1702
+ name = "sync_wrapper"
1703
+ version = "1.0.2"
1704
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1705
+ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
1706
+ dependencies = [
1707
+ "futures-core",
1708
+ ]
1709
+
1710
+ [[package]]
1711
+ name = "synstructure"
1712
+ version = "0.13.2"
1713
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1714
+ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
1715
+ dependencies = [
1716
+ "proc-macro2",
1717
+ "quote",
1718
+ "syn",
1719
+ ]
1720
+
1721
+ [[package]]
1722
+ name = "system-configuration"
1723
+ version = "0.7.0"
1724
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1725
+ checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
1726
+ dependencies = [
1727
+ "bitflags",
1728
+ "core-foundation 0.9.4",
1729
+ "system-configuration-sys",
1730
+ ]
1731
+
1732
+ [[package]]
1733
+ name = "system-configuration-sys"
1734
+ version = "0.6.0"
1735
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1736
+ checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
1737
+ dependencies = [
1738
+ "core-foundation-sys",
1739
+ "libc",
1740
+ ]
1741
+
1742
+ [[package]]
1743
+ name = "thiserror"
1744
+ version = "2.0.18"
1745
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1746
+ checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
1747
+ dependencies = [
1748
+ "thiserror-impl",
1749
+ ]
1750
+
1751
+ [[package]]
1752
+ name = "thiserror-impl"
1753
+ version = "2.0.18"
1754
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1755
+ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
1756
+ dependencies = [
1757
+ "proc-macro2",
1758
+ "quote",
1759
+ "syn",
1760
+ ]
1761
+
1762
+ [[package]]
1763
+ name = "thread_local"
1764
+ version = "1.1.9"
1765
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1766
+ checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
1767
+ dependencies = [
1768
+ "cfg-if",
1769
+ ]
1770
+
1771
+ [[package]]
1772
+ name = "tinystr"
1773
+ version = "0.8.3"
1774
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1775
+ checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
1776
+ dependencies = [
1777
+ "displaydoc",
1778
+ "zerovec",
1779
+ ]
1780
+
1781
+ [[package]]
1782
+ name = "tinyvec"
1783
+ version = "1.11.0"
1784
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1785
+ checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
1786
+ dependencies = [
1787
+ "tinyvec_macros",
1788
+ ]
1789
+
1790
+ [[package]]
1791
+ name = "tinyvec_macros"
1792
+ version = "0.1.1"
1793
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1794
+ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
1795
+
1796
+ [[package]]
1797
+ name = "tokio"
1798
+ version = "1.52.3"
1799
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1800
+ checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
1801
+ dependencies = [
1802
+ "bytes",
1803
+ "libc",
1804
+ "mio",
1805
+ "parking_lot",
1806
+ "pin-project-lite",
1807
+ "signal-hook-registry",
1808
+ "socket2",
1809
+ "tokio-macros",
1810
+ "windows-sys 0.61.2",
1811
+ ]
1812
+
1813
+ [[package]]
1814
+ name = "tokio-macros"
1815
+ version = "2.7.0"
1816
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1817
+ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
1818
+ dependencies = [
1819
+ "proc-macro2",
1820
+ "quote",
1821
+ "syn",
1822
+ ]
1823
+
1824
+ [[package]]
1825
+ name = "tokio-rustls"
1826
+ version = "0.26.4"
1827
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1828
+ checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
1829
+ dependencies = [
1830
+ "rustls",
1831
+ "tokio",
1832
+ ]
1833
+
1834
+ [[package]]
1835
+ name = "tokio-stream"
1836
+ version = "0.1.18"
1837
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1838
+ checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
1839
+ dependencies = [
1840
+ "futures-core",
1841
+ "pin-project-lite",
1842
+ "tokio",
1843
+ "tokio-util",
1844
+ ]
1845
+
1846
+ [[package]]
1847
+ name = "tokio-util"
1848
+ version = "0.7.18"
1849
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1850
+ checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
1851
+ dependencies = [
1852
+ "bytes",
1853
+ "futures-core",
1854
+ "futures-sink",
1855
+ "pin-project-lite",
1856
+ "tokio",
1857
+ ]
1858
+
1859
+ [[package]]
1860
+ name = "toml"
1861
+ version = "1.1.2+spec-1.1.0"
1862
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1863
+ checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
1864
+ dependencies = [
1865
+ "indexmap",
1866
+ "serde_core",
1867
+ "serde_spanned",
1868
+ "toml_datetime",
1869
+ "toml_parser",
1870
+ "toml_writer",
1871
+ "winnow",
1872
+ ]
1873
+
1874
+ [[package]]
1875
+ name = "toml_datetime"
1876
+ version = "1.1.1+spec-1.1.0"
1877
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1878
+ checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
1879
+ dependencies = [
1880
+ "serde_core",
1881
+ ]
1882
+
1883
+ [[package]]
1884
+ name = "toml_parser"
1885
+ version = "1.1.2+spec-1.1.0"
1886
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1887
+ checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
1888
+ dependencies = [
1889
+ "winnow",
1890
+ ]
1891
+
1892
+ [[package]]
1893
+ name = "toml_writer"
1894
+ version = "1.1.1+spec-1.1.0"
1895
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1896
+ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
1897
+
1898
+ [[package]]
1899
+ name = "tower"
1900
+ version = "0.5.3"
1901
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1902
+ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
1903
+ dependencies = [
1904
+ "futures-core",
1905
+ "futures-util",
1906
+ "pin-project-lite",
1907
+ "sync_wrapper",
1908
+ "tokio",
1909
+ "tower-layer",
1910
+ "tower-service",
1911
+ "tracing",
1912
+ ]
1913
+
1914
+ [[package]]
1915
+ name = "tower-http"
1916
+ version = "0.6.11"
1917
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1918
+ checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
1919
+ dependencies = [
1920
+ "bitflags",
1921
+ "bytes",
1922
+ "futures-core",
1923
+ "futures-util",
1924
+ "http",
1925
+ "http-body",
1926
+ "http-body-util",
1927
+ "http-range-header",
1928
+ "httpdate",
1929
+ "mime",
1930
+ "mime_guess",
1931
+ "percent-encoding",
1932
+ "pin-project-lite",
1933
+ "tokio",
1934
+ "tokio-util",
1935
+ "tower",
1936
+ "tower-layer",
1937
+ "tower-service",
1938
+ "url",
1939
+ ]
1940
+
1941
+ [[package]]
1942
+ name = "tower-layer"
1943
+ version = "0.3.3"
1944
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1945
+ checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
1946
+
1947
+ [[package]]
1948
+ name = "tower-service"
1949
+ version = "0.3.3"
1950
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1951
+ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
1952
+
1953
+ [[package]]
1954
+ name = "tracing"
1955
+ version = "0.1.44"
1956
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1957
+ checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
1958
+ dependencies = [
1959
+ "log",
1960
+ "pin-project-lite",
1961
+ "tracing-attributes",
1962
+ "tracing-core",
1963
+ ]
1964
+
1965
+ [[package]]
1966
+ name = "tracing-attributes"
1967
+ version = "0.1.31"
1968
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1969
+ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
1970
+ dependencies = [
1971
+ "proc-macro2",
1972
+ "quote",
1973
+ "syn",
1974
+ ]
1975
+
1976
+ [[package]]
1977
+ name = "tracing-core"
1978
+ version = "0.1.36"
1979
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1980
+ checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
1981
+ dependencies = [
1982
+ "once_cell",
1983
+ "valuable",
1984
+ ]
1985
+
1986
+ [[package]]
1987
+ name = "tracing-log"
1988
+ version = "0.2.0"
1989
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1990
+ checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
1991
+ dependencies = [
1992
+ "log",
1993
+ "once_cell",
1994
+ "tracing-core",
1995
+ ]
1996
+
1997
+ [[package]]
1998
+ name = "tracing-subscriber"
1999
+ version = "0.3.23"
2000
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2001
+ checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
2002
+ dependencies = [
2003
+ "matchers",
2004
+ "nu-ansi-term",
2005
+ "once_cell",
2006
+ "regex-automata",
2007
+ "sharded-slab",
2008
+ "smallvec",
2009
+ "thread_local",
2010
+ "tracing",
2011
+ "tracing-core",
2012
+ "tracing-log",
2013
+ ]
2014
+
2015
+ [[package]]
2016
+ name = "try-lock"
2017
+ version = "0.2.5"
2018
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2019
+ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
2020
+
2021
+ [[package]]
2022
+ name = "unicase"
2023
+ version = "2.9.0"
2024
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2025
+ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
2026
+
2027
+ [[package]]
2028
+ name = "unicode-ident"
2029
+ version = "1.0.24"
2030
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2031
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
2032
+
2033
+ [[package]]
2034
+ name = "unicode-xid"
2035
+ version = "0.2.6"
2036
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2037
+ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
2038
+
2039
+ [[package]]
2040
+ name = "unsafe-libyaml"
2041
+ version = "0.2.11"
2042
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2043
+ checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
2044
+
2045
+ [[package]]
2046
+ name = "untrusted"
2047
+ version = "0.9.0"
2048
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2049
+ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
2050
+
2051
+ [[package]]
2052
+ name = "url"
2053
+ version = "2.5.8"
2054
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2055
+ checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
2056
+ dependencies = [
2057
+ "form_urlencoded",
2058
+ "idna",
2059
+ "percent-encoding",
2060
+ "serde",
2061
+ ]
2062
+
2063
+ [[package]]
2064
+ name = "utf8_iter"
2065
+ version = "1.0.4"
2066
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2067
+ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
2068
+
2069
+ [[package]]
2070
+ name = "utoipa"
2071
+ version = "5.5.0"
2072
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2073
+ checksum = "8bde15df68e80b16c7d16b9616e80770ad158988daa56a27dccd1e55558b0160"
2074
+ dependencies = [
2075
+ "indexmap",
2076
+ "serde",
2077
+ "serde_json",
2078
+ "utoipa-gen",
2079
+ ]
2080
+
2081
+ [[package]]
2082
+ name = "utoipa-gen"
2083
+ version = "5.5.0"
2084
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2085
+ checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8"
2086
+ dependencies = [
2087
+ "proc-macro2",
2088
+ "quote",
2089
+ "syn",
2090
+ ]
2091
+
2092
+ [[package]]
2093
+ name = "uuid"
2094
+ version = "1.23.3"
2095
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2096
+ checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7"
2097
+ dependencies = [
2098
+ "getrandom 0.4.2",
2099
+ "js-sys",
2100
+ "serde_core",
2101
+ "wasm-bindgen",
2102
+ ]
2103
+
2104
+ [[package]]
2105
+ name = "valuable"
2106
+ version = "0.1.1"
2107
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2108
+ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
2109
+
2110
+ [[package]]
2111
+ name = "walkdir"
2112
+ version = "2.5.0"
2113
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2114
+ checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
2115
+ dependencies = [
2116
+ "same-file",
2117
+ "winapi-util",
2118
+ ]
2119
+
2120
+ [[package]]
2121
+ name = "want"
2122
+ version = "0.3.1"
2123
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2124
+ checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
2125
+ dependencies = [
2126
+ "try-lock",
2127
+ ]
2128
+
2129
+ [[package]]
2130
+ name = "wasi"
2131
+ version = "0.11.1+wasi-snapshot-preview1"
2132
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2133
+ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
2134
+
2135
+ [[package]]
2136
+ name = "wasip2"
2137
+ version = "1.0.3+wasi-0.2.9"
2138
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2139
+ checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
2140
+ dependencies = [
2141
+ "wit-bindgen 0.57.1",
2142
+ ]
2143
+
2144
+ [[package]]
2145
+ name = "wasip3"
2146
+ version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
2147
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2148
+ checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
2149
+ dependencies = [
2150
+ "wit-bindgen 0.51.0",
2151
+ ]
2152
+
2153
+ [[package]]
2154
+ name = "wasm-bindgen"
2155
+ version = "0.2.123"
2156
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2157
+ checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563"
2158
+ dependencies = [
2159
+ "cfg-if",
2160
+ "once_cell",
2161
+ "rustversion",
2162
+ "wasm-bindgen-macro",
2163
+ "wasm-bindgen-shared",
2164
+ ]
2165
+
2166
+ [[package]]
2167
+ name = "wasm-bindgen-futures"
2168
+ version = "0.4.73"
2169
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2170
+ checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf"
2171
+ dependencies = [
2172
+ "js-sys",
2173
+ "wasm-bindgen",
2174
+ ]
2175
+
2176
+ [[package]]
2177
+ name = "wasm-bindgen-macro"
2178
+ version = "0.2.123"
2179
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2180
+ checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc"
2181
+ dependencies = [
2182
+ "quote",
2183
+ "wasm-bindgen-macro-support",
2184
+ ]
2185
+
2186
+ [[package]]
2187
+ name = "wasm-bindgen-macro-support"
2188
+ version = "0.2.123"
2189
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2190
+ checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b"
2191
+ dependencies = [
2192
+ "bumpalo",
2193
+ "proc-macro2",
2194
+ "quote",
2195
+ "syn",
2196
+ "wasm-bindgen-shared",
2197
+ ]
2198
+
2199
+ [[package]]
2200
+ name = "wasm-bindgen-shared"
2201
+ version = "0.2.123"
2202
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2203
+ checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92"
2204
+ dependencies = [
2205
+ "unicode-ident",
2206
+ ]
2207
+
2208
+ [[package]]
2209
+ name = "wasm-encoder"
2210
+ version = "0.244.0"
2211
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2212
+ checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
2213
+ dependencies = [
2214
+ "leb128fmt",
2215
+ "wasmparser",
2216
+ ]
2217
+
2218
+ [[package]]
2219
+ name = "wasm-metadata"
2220
+ version = "0.244.0"
2221
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2222
+ checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
2223
+ dependencies = [
2224
+ "anyhow",
2225
+ "indexmap",
2226
+ "wasm-encoder",
2227
+ "wasmparser",
2228
+ ]
2229
+
2230
+ [[package]]
2231
+ name = "wasmparser"
2232
+ version = "0.244.0"
2233
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2234
+ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
2235
+ dependencies = [
2236
+ "bitflags",
2237
+ "hashbrown 0.15.5",
2238
+ "indexmap",
2239
+ "semver",
2240
+ ]
2241
+
2242
+ [[package]]
2243
+ name = "web-sys"
2244
+ version = "0.3.100"
2245
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2246
+ checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69"
2247
+ dependencies = [
2248
+ "js-sys",
2249
+ "wasm-bindgen",
2250
+ ]
2251
+
2252
+ [[package]]
2253
+ name = "web-time"
2254
+ version = "1.1.0"
2255
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2256
+ checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
2257
+ dependencies = [
2258
+ "js-sys",
2259
+ "wasm-bindgen",
2260
+ ]
2261
+
2262
+ [[package]]
2263
+ name = "webpki-root-certs"
2264
+ version = "1.0.7"
2265
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2266
+ checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
2267
+ dependencies = [
2268
+ "rustls-pki-types",
2269
+ ]
2270
+
2271
+ [[package]]
2272
+ name = "winapi-util"
2273
+ version = "0.1.11"
2274
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2275
+ checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
2276
+ dependencies = [
2277
+ "windows-sys 0.61.2",
2278
+ ]
2279
+
2280
+ [[package]]
2281
+ name = "windows-link"
2282
+ version = "0.2.1"
2283
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2284
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
2285
+
2286
+ [[package]]
2287
+ name = "windows-registry"
2288
+ version = "0.6.1"
2289
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2290
+ checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
2291
+ dependencies = [
2292
+ "windows-link",
2293
+ "windows-result",
2294
+ "windows-strings",
2295
+ ]
2296
+
2297
+ [[package]]
2298
+ name = "windows-result"
2299
+ version = "0.4.1"
2300
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2301
+ checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
2302
+ dependencies = [
2303
+ "windows-link",
2304
+ ]
2305
+
2306
+ [[package]]
2307
+ name = "windows-strings"
2308
+ version = "0.5.1"
2309
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2310
+ checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
2311
+ dependencies = [
2312
+ "windows-link",
2313
+ ]
2314
+
2315
+ [[package]]
2316
+ name = "windows-sys"
2317
+ version = "0.52.0"
2318
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2319
+ checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
2320
+ dependencies = [
2321
+ "windows-targets 0.52.6",
2322
+ ]
2323
+
2324
+ [[package]]
2325
+ name = "windows-sys"
2326
+ version = "0.60.2"
2327
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2328
+ checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
2329
+ dependencies = [
2330
+ "windows-targets 0.53.5",
2331
+ ]
2332
+
2333
+ [[package]]
2334
+ name = "windows-sys"
2335
+ version = "0.61.2"
2336
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2337
+ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
2338
+ dependencies = [
2339
+ "windows-link",
2340
+ ]
2341
+
2342
+ [[package]]
2343
+ name = "windows-targets"
2344
+ version = "0.52.6"
2345
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2346
+ checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
2347
+ dependencies = [
2348
+ "windows_aarch64_gnullvm 0.52.6",
2349
+ "windows_aarch64_msvc 0.52.6",
2350
+ "windows_i686_gnu 0.52.6",
2351
+ "windows_i686_gnullvm 0.52.6",
2352
+ "windows_i686_msvc 0.52.6",
2353
+ "windows_x86_64_gnu 0.52.6",
2354
+ "windows_x86_64_gnullvm 0.52.6",
2355
+ "windows_x86_64_msvc 0.52.6",
2356
+ ]
2357
+
2358
+ [[package]]
2359
+ name = "windows-targets"
2360
+ version = "0.53.5"
2361
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2362
+ checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
2363
+ dependencies = [
2364
+ "windows-link",
2365
+ "windows_aarch64_gnullvm 0.53.1",
2366
+ "windows_aarch64_msvc 0.53.1",
2367
+ "windows_i686_gnu 0.53.1",
2368
+ "windows_i686_gnullvm 0.53.1",
2369
+ "windows_i686_msvc 0.53.1",
2370
+ "windows_x86_64_gnu 0.53.1",
2371
+ "windows_x86_64_gnullvm 0.53.1",
2372
+ "windows_x86_64_msvc 0.53.1",
2373
+ ]
2374
+
2375
+ [[package]]
2376
+ name = "windows_aarch64_gnullvm"
2377
+ version = "0.52.6"
2378
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2379
+ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
2380
+
2381
+ [[package]]
2382
+ name = "windows_aarch64_gnullvm"
2383
+ version = "0.53.1"
2384
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2385
+ checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
2386
+
2387
+ [[package]]
2388
+ name = "windows_aarch64_msvc"
2389
+ version = "0.52.6"
2390
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2391
+ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
2392
+
2393
+ [[package]]
2394
+ name = "windows_aarch64_msvc"
2395
+ version = "0.53.1"
2396
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2397
+ checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
2398
+
2399
+ [[package]]
2400
+ name = "windows_i686_gnu"
2401
+ version = "0.52.6"
2402
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2403
+ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
2404
+
2405
+ [[package]]
2406
+ name = "windows_i686_gnu"
2407
+ version = "0.53.1"
2408
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2409
+ checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
2410
+
2411
+ [[package]]
2412
+ name = "windows_i686_gnullvm"
2413
+ version = "0.52.6"
2414
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2415
+ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
2416
+
2417
+ [[package]]
2418
+ name = "windows_i686_gnullvm"
2419
+ version = "0.53.1"
2420
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2421
+ checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
2422
+
2423
+ [[package]]
2424
+ name = "windows_i686_msvc"
2425
+ version = "0.52.6"
2426
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2427
+ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
2428
+
2429
+ [[package]]
2430
+ name = "windows_i686_msvc"
2431
+ version = "0.53.1"
2432
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2433
+ checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
2434
+
2435
+ [[package]]
2436
+ name = "windows_x86_64_gnu"
2437
+ version = "0.52.6"
2438
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2439
+ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
2440
+
2441
+ [[package]]
2442
+ name = "windows_x86_64_gnu"
2443
+ version = "0.53.1"
2444
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2445
+ checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
2446
+
2447
+ [[package]]
2448
+ name = "windows_x86_64_gnullvm"
2449
+ version = "0.52.6"
2450
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2451
+ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
2452
+
2453
+ [[package]]
2454
+ name = "windows_x86_64_gnullvm"
2455
+ version = "0.53.1"
2456
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2457
+ checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
2458
+
2459
+ [[package]]
2460
+ name = "windows_x86_64_msvc"
2461
+ version = "0.52.6"
2462
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2463
+ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
2464
+
2465
+ [[package]]
2466
+ name = "windows_x86_64_msvc"
2467
+ version = "0.53.1"
2468
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2469
+ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
2470
+
2471
+ [[package]]
2472
+ name = "winnow"
2473
+ version = "1.0.3"
2474
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2475
+ checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
2476
+
2477
+ [[package]]
2478
+ name = "wit-bindgen"
2479
+ version = "0.51.0"
2480
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2481
+ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
2482
+ dependencies = [
2483
+ "wit-bindgen-rust-macro",
2484
+ ]
2485
+
2486
+ [[package]]
2487
+ name = "wit-bindgen"
2488
+ version = "0.57.1"
2489
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2490
+ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
2491
+
2492
+ [[package]]
2493
+ name = "wit-bindgen-core"
2494
+ version = "0.51.0"
2495
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2496
+ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
2497
+ dependencies = [
2498
+ "anyhow",
2499
+ "heck",
2500
+ "wit-parser",
2501
+ ]
2502
+
2503
+ [[package]]
2504
+ name = "wit-bindgen-rust"
2505
+ version = "0.51.0"
2506
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2507
+ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
2508
+ dependencies = [
2509
+ "anyhow",
2510
+ "heck",
2511
+ "indexmap",
2512
+ "prettyplease",
2513
+ "syn",
2514
+ "wasm-metadata",
2515
+ "wit-bindgen-core",
2516
+ "wit-component",
2517
+ ]
2518
+
2519
+ [[package]]
2520
+ name = "wit-bindgen-rust-macro"
2521
+ version = "0.51.0"
2522
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2523
+ checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
2524
+ dependencies = [
2525
+ "anyhow",
2526
+ "prettyplease",
2527
+ "proc-macro2",
2528
+ "quote",
2529
+ "syn",
2530
+ "wit-bindgen-core",
2531
+ "wit-bindgen-rust",
2532
+ ]
2533
+
2534
+ [[package]]
2535
+ name = "wit-component"
2536
+ version = "0.244.0"
2537
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2538
+ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
2539
+ dependencies = [
2540
+ "anyhow",
2541
+ "bitflags",
2542
+ "indexmap",
2543
+ "log",
2544
+ "serde",
2545
+ "serde_derive",
2546
+ "serde_json",
2547
+ "wasm-encoder",
2548
+ "wasm-metadata",
2549
+ "wasmparser",
2550
+ "wit-parser",
2551
+ ]
2552
+
2553
+ [[package]]
2554
+ name = "wit-parser"
2555
+ version = "0.244.0"
2556
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2557
+ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
2558
+ dependencies = [
2559
+ "anyhow",
2560
+ "id-arena",
2561
+ "indexmap",
2562
+ "log",
2563
+ "semver",
2564
+ "serde",
2565
+ "serde_derive",
2566
+ "serde_json",
2567
+ "unicode-xid",
2568
+ "wasmparser",
2569
+ ]
2570
+
2571
+ [[package]]
2572
+ name = "writeable"
2573
+ version = "0.6.3"
2574
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2575
+ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
2576
+
2577
+ [[package]]
2578
+ name = "yoke"
2579
+ version = "0.8.3"
2580
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2581
+ checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
2582
+ dependencies = [
2583
+ "stable_deref_trait",
2584
+ "yoke-derive",
2585
+ "zerofrom",
2586
+ ]
2587
+
2588
+ [[package]]
2589
+ name = "yoke-derive"
2590
+ version = "0.8.2"
2591
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2592
+ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
2593
+ dependencies = [
2594
+ "proc-macro2",
2595
+ "quote",
2596
+ "syn",
2597
+ "synstructure",
2598
+ ]
2599
+
2600
+ [[package]]
2601
+ name = "zerocopy"
2602
+ version = "0.8.52"
2603
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2604
+ checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
2605
+ dependencies = [
2606
+ "zerocopy-derive",
2607
+ ]
2608
+
2609
+ [[package]]
2610
+ name = "zerocopy-derive"
2611
+ version = "0.8.52"
2612
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2613
+ checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
2614
+ dependencies = [
2615
+ "proc-macro2",
2616
+ "quote",
2617
+ "syn",
2618
+ ]
2619
+
2620
+ [[package]]
2621
+ name = "zerofrom"
2622
+ version = "0.1.8"
2623
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2624
+ checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
2625
+ dependencies = [
2626
+ "zerofrom-derive",
2627
+ ]
2628
+
2629
+ [[package]]
2630
+ name = "zerofrom-derive"
2631
+ version = "0.1.7"
2632
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2633
+ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
2634
+ dependencies = [
2635
+ "proc-macro2",
2636
+ "quote",
2637
+ "syn",
2638
+ "synstructure",
2639
+ ]
2640
+
2641
+ [[package]]
2642
+ name = "zeroize"
2643
+ version = "1.8.2"
2644
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2645
+ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
2646
+
2647
+ [[package]]
2648
+ name = "zerotrie"
2649
+ version = "0.2.4"
2650
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2651
+ checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
2652
+ dependencies = [
2653
+ "displaydoc",
2654
+ "yoke",
2655
+ "zerofrom",
2656
+ ]
2657
+
2658
+ [[package]]
2659
+ name = "zerovec"
2660
+ version = "0.11.6"
2661
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2662
+ checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
2663
+ dependencies = [
2664
+ "yoke",
2665
+ "zerofrom",
2666
+ "zerovec-derive",
2667
+ ]
2668
+
2669
+ [[package]]
2670
+ name = "zerovec-derive"
2671
+ version = "0.11.3"
2672
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2673
+ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
2674
+ dependencies = [
2675
+ "proc-macro2",
2676
+ "quote",
2677
+ "syn",
2678
+ ]
2679
+
2680
+ [[package]]
2681
+ name = "zmij"
2682
+ version = "1.0.21"
2683
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2684
+ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
Cargo.toml ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [package]
2
+ name = "solverforge-deliveries"
3
+ version = "2.0.6"
4
+ edition = "2021"
5
+ rust-version = "1.95"
6
+ description = "Constraint optimizer built with SolverForge"
7
+
8
+ [[bin]]
9
+ name = "solverforge_deliveries"
10
+ path = "src/main.rs"
11
+
12
+ [dependencies]
13
+ solverforge = { version = "0.19.3", features = [
14
+ "serde",
15
+ "console",
16
+ "verbose-logging",
17
+ ] }
18
+ solverforge-ui = "0.6.5"
19
+ solverforge-maps = "2.1.4"
20
+ # Web server
21
+ axum = "0.8.9"
22
+ tokio = { version = "1.52.3", features = ["full"] }
23
+ tokio-stream = { version = "0.1.18", features = ["sync"] }
24
+ tower-http = { version = "0.6.10", features = ["fs", "cors"] }
25
+ tower = "0.5.3"
26
+
27
+ # Serialization
28
+ serde = { version = "1.0.228", features = ["derive"] }
29
+ serde_json = "1.0.149"
30
+ rand = "0.10.1"
31
+
32
+ # Utilities
33
+ uuid = { version = "1.23.1", features = ["v4", "serde"] }
34
+ parking_lot = "0.12.5"
35
+
36
+ [dev-dependencies]
37
+ http-body-util = "0.1.3"
Dockerfile ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Multi-stage build for solverforge-deliveries.
2
+ #
3
+ # The app is intended to build from registry dependency declarations, so the
4
+ # repository root is the complete Docker build context:
5
+ # docker build -f Dockerfile -t solverforge-deliveries .
6
+
7
+ FROM rust:1.95-alpine AS builder
8
+
9
+ RUN apk add --no-cache musl-dev
10
+
11
+ WORKDIR /build
12
+
13
+ COPY Cargo.toml Cargo.lock ./
14
+ COPY src/ ./src/
15
+ COPY static/ ./static/
16
+ COPY solver.toml ./solver.toml
17
+
18
+ RUN cargo build --release --target x86_64-unknown-linux-musl
19
+
20
+ FROM alpine:latest
21
+
22
+ RUN apk add --no-cache ca-certificates
23
+
24
+ WORKDIR /app
25
+
26
+ COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/solverforge_deliveries ./solverforge_deliveries
27
+ COPY --from=builder /build/static/ ./static/
28
+ COPY --from=builder /build/solver.toml ./solver.toml
29
+
30
+ ENV PORT=7860
31
+
32
+ EXPOSE 7860
33
+
34
+ CMD ["./solverforge_deliveries"]
Makefile ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SolverForge Deliveries Makefile
2
+ # Rust + frontend + Space-oriented local build system.
3
+ #
4
+ # This app is validated for local development and Docker-based Hugging Face
5
+ # Space deployment. `ci-local` therefore includes a Docker image build.
6
+
7
+ SHELL := /bin/sh
8
+ .SHELLFLAGS := -eu -c
9
+ unexport BASH_FUNC_mc%%
10
+
11
+ # ============== Colors & Symbols ==============
12
+ GREEN := \033[92m
13
+ EMERALD := \033[38;2;16;185;129m
14
+ CYAN := \033[96m
15
+ YELLOW := \033[93m
16
+ RED := \033[91m
17
+ GRAY := \033[90m
18
+ BOLD := \033[1m
19
+ RESET := \033[0m
20
+
21
+ CHECK := OK
22
+ CROSS := FAIL
23
+ ARROW := =>
24
+ PROGRESS := ..
25
+
26
+ # ============== Project Metadata ==============
27
+ APP_NAME := solverforge_deliveries
28
+ PACKAGE_NAME := solverforge-deliveries
29
+ VERSION := $(shell sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)
30
+ RELEASE_TAG := $(PACKAGE_NAME)@$(VERSION)
31
+ RUST_VERSION := 1.95+
32
+ PORT ?= 7860
33
+ DOCKER_IMAGE ?= $(PACKAGE_NAME)
34
+ DOCKER_CONTEXT ?= .
35
+ DOCKERFILE_PATH := Dockerfile
36
+ PLAYWRIGHT ?= ../node_modules/.bin/playwright
37
+
38
+ # ============== Phony Targets ==============
39
+ .PHONY: banner help doctor build build-release run run-release test test-rust \
40
+ test-frontend-syntax test-frontend test-e2e test-live-road test-live-e2e test-one lint fmt \
41
+ fmt-check clippy check ci-local space-ci space-build space-run docker-build \
42
+ docker-run pre-release release-ci release-info version clean watch require-node require-docker
43
+
44
+ .DEFAULT_GOAL := help
45
+
46
+ # ============== Banner ==============
47
+ banner:
48
+ @printf "$(EMERALD)$(BOLD) ____ _ _____\n"
49
+ @printf " / ___| ___ | |_ _____ _ __| ___|__ _ __ __ _ ___\n"
50
+ @printf " \\___ \\\\ / _ \\\\| \\\\ \\\\ / / _ \\\\ '__| |_ / _ \\\\| '__/ _\` |/ _ \\\\\n"
51
+ @printf " ___) | (_) | |\\\\ V / __/ | | _| (_) | | | (_| | __/\n"
52
+ @printf " |____/ \\\\___/|_| \\_/ \\___|_| |_| \\___/|_| \\__, |\\___|\n"
53
+ @printf " |___/$(RESET)\n"
54
+ @printf " $(GRAY)v$(VERSION)$(RESET) $(EMERALD)Deliveries demo build system$(RESET)\n\n"
55
+
56
+ # ============== Environment Checks ==============
57
+ require-node:
58
+ @command -v node >/dev/null 2>&1 || (printf "$(RED)$(CROSS) node is required for frontend validation$(RESET)\n" && exit 1)
59
+
60
+ require-docker:
61
+ @command -v docker >/dev/null 2>&1 || (printf "$(RED)$(CROSS) docker is required for Space/Docker targets$(RESET)\n" && exit 1)
62
+
63
+ doctor: banner
64
+ @printf "$(CYAN)$(BOLD)Environment Check$(RESET)\n\n"
65
+ @missing=0; \
66
+ if command -v cargo >/dev/null 2>&1; then \
67
+ printf "$(GREEN)$(CHECK) cargo: $$(cargo --version)$(RESET)\n"; \
68
+ else \
69
+ printf "$(RED)$(CROSS) cargo not found$(RESET)\n"; missing=1; \
70
+ fi; \
71
+ if command -v rustc >/dev/null 2>&1; then \
72
+ printf "$(GREEN)$(CHECK) rustc: $$(rustc --version)$(RESET)\n"; \
73
+ else \
74
+ printf "$(RED)$(CROSS) rustc not found$(RESET)\n"; missing=1; \
75
+ fi; \
76
+ if command -v node >/dev/null 2>&1; then \
77
+ printf "$(GREEN)$(CHECK) node: $$(node --version)$(RESET)\n"; \
78
+ else \
79
+ printf "$(RED)$(CROSS) node not found$(RESET)\n"; missing=1; \
80
+ fi; \
81
+ if command -v docker >/dev/null 2>&1; then \
82
+ printf "$(GREEN)$(CHECK) docker: $$(docker --version)$(RESET)\n"; \
83
+ else \
84
+ printf "$(YELLOW)! docker not found; Space/Docker targets will be unavailable$(RESET)\n"; \
85
+ fi; \
86
+ printf "$(GRAY)Docker build context: $(DOCKER_CONTEXT)$(RESET)\n"; \
87
+ printf "$(GRAY)Default app port: $(PORT)$(RESET)\n"; \
88
+ if [ $$missing -ne 0 ]; then exit 1; fi
89
+ @printf "\n"
90
+
91
+ # ============== Build & Run ==============
92
+ build: banner
93
+ @printf "$(ARROW) $(BOLD)Building $(PACKAGE_NAME)...$(RESET)\n"
94
+ @cargo build --bin $(APP_NAME) && \
95
+ printf "$(GREEN)$(CHECK) Debug build successful$(RESET)\n\n" || \
96
+ (printf "$(RED)$(CROSS) Debug build failed$(RESET)\n\n" && exit 1)
97
+
98
+ build-release: banner
99
+ @printf "$(ARROW) $(BOLD)Building release binary...$(RESET)\n"
100
+ @cargo build --release --bin $(APP_NAME) && \
101
+ printf "$(GREEN)$(CHECK) Release build successful$(RESET)\n\n" || \
102
+ (printf "$(RED)$(CROSS) Release build failed$(RESET)\n\n" && exit 1)
103
+
104
+ run:
105
+ @printf "$(ARROW) Running $(PACKAGE_NAME) on port $(PORT)...\n"
106
+ @PORT=$(PORT) cargo run --bin $(APP_NAME)
107
+
108
+ run-release:
109
+ @printf "$(ARROW) Running release build on port $(PORT)...\n"
110
+ @PORT=$(PORT) cargo run --release --bin $(APP_NAME)
111
+
112
+ # ============== Test Targets ==============
113
+ test: test-rust test-frontend test-e2e
114
+ @printf "\n$(GREEN)$(BOLD)$(CHECK) Standard validation passed$(RESET)\n\n"
115
+
116
+ test-rust: banner
117
+ @printf "$(ARROW) $(BOLD)Running cargo test --quiet...$(RESET)\n"
118
+ @cargo test --quiet && \
119
+ printf "\n$(GREEN)$(CHECK) Rust tests passed$(RESET)\n\n" || \
120
+ (printf "\n$(RED)$(CROSS) Rust tests failed$(RESET)\n\n" && exit 1)
121
+
122
+ test-frontend-syntax: require-node
123
+ @printf "$(PROGRESS) Checking frontend module syntax...\n"
124
+ @find static/app -name '*.mjs' -print0 | xargs -0 -n1 node --check && \
125
+ printf "$(GREEN)$(CHECK) Frontend syntax checks passed$(RESET)\n" || \
126
+ (printf "$(RED)$(CROSS) Frontend syntax checks failed$(RESET)\n" && exit 1)
127
+
128
+ test-frontend: test-frontend-syntax
129
+ @printf "$(PROGRESS) Running frontend tests...\n"
130
+ @node --test tests/frontend_models.test.mjs && \
131
+ printf "$(GREEN)$(CHECK) Frontend tests passed$(RESET)\n" || \
132
+ (printf "$(RED)$(CROSS) Frontend tests failed$(RESET)\n" && exit 1)
133
+
134
+ test-e2e: build-release require-node
135
+ @printf "$(PROGRESS) Running Playwright browser tests...\n"
136
+ @$(PLAYWRIGHT) test --config tests/e2e/playwright.config.js && \
137
+ printf "$(GREEN)$(CHECK) Playwright browser tests passed$(RESET)\n" || \
138
+ (printf "$(RED)$(CROSS) Playwright browser tests failed$(RESET)\n" && exit 1)
139
+
140
+ test-live-road: banner
141
+ @printf "$(ARROW) $(BOLD)Running live road-network route tests...$(RESET)\n"
142
+ @SOLVERFORGE_RUN_LIVE_TESTS=1 cargo test live_demo_locations_are_mutually_reachable_when_enabled -- --nocapture && \
143
+ SOLVERFORGE_RUN_LIVE_TESTS=1 cargo test live_clarke_wright_construction_assigns_full_philadelphia_fixture_when_enabled -- --nocapture && \
144
+ SOLVERFORGE_RUN_LIVE_TESTS=1 cargo test road_network_job_emits_a_non_empty_snapshot_when_live_tests_are_enabled -- --nocapture && \
145
+ SOLVERFORGE_RUN_LIVE_TESTS=1 cargo test road_network_jobs_lifecycle_snapshot_analysis_routes_and_delete_work_when_live_tests_are_enabled -- --nocapture && \
146
+ SOLVERFORGE_RUN_LIVE_TESTS=1 cargo test road_network_job_routes_work_when_live_tests_are_enabled -- --nocapture && \
147
+ SOLVERFORGE_RUN_LIVE_TESTS=1 cargo test recommendations_endpoint_returns_ranked_preview_plans -- --nocapture && \
148
+ printf "\n$(GREEN)$(CHECK) Live road-network Rust tests passed$(RESET)\n\n" || \
149
+ (printf "\n$(RED)$(CROSS) Live road-network Rust tests failed$(RESET)\n\n" && exit 1)
150
+ @$(MAKE) test-live-e2e --no-print-directory
151
+
152
+ test-live-e2e: build-release require-node
153
+ @printf "$(PROGRESS) Running live Playwright road-network browser test...\n"
154
+ @SOLVERFORGE_RUN_LIVE_TESTS=1 $(PLAYWRIGHT) test --config tests/e2e/playwright.config.js tests/e2e/app.live.spec.js && \
155
+ printf "$(GREEN)$(CHECK) Live Playwright road-network test passed$(RESET)\n" || \
156
+ (printf "$(RED)$(CROSS) Live Playwright road-network test failed$(RESET)\n" && exit 1)
157
+
158
+ test-one:
159
+ @printf "$(PROGRESS) Running test: $(YELLOW)$(TEST)$(RESET)\n"
160
+ @RUST_LOG=info cargo test $(TEST) -- --nocapture
161
+
162
+ # ============== Lint & Format ==============
163
+ fmt:
164
+ @printf "$(PROGRESS) Formatting Rust code...\n"
165
+ @cargo fmt
166
+ @printf "$(GREEN)$(CHECK) Code formatted$(RESET)\n"
167
+
168
+ fmt-check:
169
+ @printf "$(PROGRESS) Checking Rust formatting...\n"
170
+ @cargo fmt --check && \
171
+ printf "$(GREEN)$(CHECK) Formatting valid$(RESET)\n" || \
172
+ (printf "$(RED)$(CROSS) Formatting issues found$(RESET)\n" && exit 1)
173
+
174
+ clippy:
175
+ @printf "$(PROGRESS) Running clippy...\n"
176
+ @cargo clippy --all-targets -- -D warnings && \
177
+ printf "$(GREEN)$(CHECK) Clippy passed$(RESET)\n" || \
178
+ (printf "$(RED)$(CROSS) Clippy warnings found$(RESET)\n" && exit 1)
179
+
180
+ lint: fmt-check clippy test-frontend-syntax
181
+ @printf "\n$(GREEN)$(BOLD)$(CHECK) Lint checks passed$(RESET)\n\n"
182
+
183
+ check: lint test
184
+
185
+ # ============== Space & Docker ==============
186
+ docker-build: require-docker
187
+ @printf "$(PROGRESS) Building Docker image $(DOCKER_IMAGE)...\n"
188
+ @docker build -f "$(DOCKERFILE_PATH)" -t "$(DOCKER_IMAGE)" "$(DOCKER_CONTEXT)" && \
189
+ printf "$(GREEN)$(CHECK) Docker image built$(RESET)\n" || \
190
+ (printf "$(RED)$(CROSS) Docker build failed$(RESET)\n" && exit 1)
191
+
192
+ docker-run: require-docker
193
+ @printf "$(ARROW) Running $(DOCKER_IMAGE) on port $(PORT)...\n"
194
+ @docker run --rm -it -e PORT=$(PORT) -p $(PORT):$(PORT) "$(DOCKER_IMAGE)"
195
+
196
+ space-build: docker-build
197
+
198
+ space-run: space-build
199
+ @printf "$(GREEN)$(CHECK) Starting local container that mirrors the Space image$(RESET)\n"
200
+ @$(MAKE) docker-run --no-print-directory PORT=$(PORT) DOCKER_IMAGE=$(DOCKER_IMAGE)
201
+
202
+ space-ci: ci-local
203
+
204
+ # ============== CI & Release Validation ==============
205
+ ci-local: banner
206
+ @printf "$(CYAN)$(BOLD)Local Validation Pipeline$(RESET)\n\n"
207
+ @printf "$(PROGRESS) Step 1/5: Format check...\n"
208
+ @$(MAKE) fmt-check --no-print-directory
209
+ @printf "$(PROGRESS) Step 2/5: Clippy...\n"
210
+ @$(MAKE) clippy --no-print-directory
211
+ @printf "$(PROGRESS) Step 3/5: Release build...\n"
212
+ @$(MAKE) build-release --no-print-directory
213
+ @printf "$(PROGRESS) Step 4/5: Standard test surface...\n"
214
+ @$(MAKE) test --no-print-directory
215
+ @printf "$(PROGRESS) Step 5/5: Docker/Space image build...\n"
216
+ @$(MAKE) space-build --no-print-directory
217
+ @printf "\n$(GREEN)$(BOLD)$(CHECK) LOCAL SPACE VALIDATION PASSED$(RESET)\n\n"
218
+
219
+ pre-release: banner
220
+ @printf "$(CYAN)$(BOLD)Pre-Release Validation v$(VERSION)$(RESET)\n\n"
221
+ @$(MAKE) ci-local --no-print-directory
222
+ @printf "$(PROGRESS) Final step: live road-network smoke...\n"
223
+ @$(MAKE) test-live-road --no-print-directory
224
+ @printf "$(GREEN)$(BOLD)$(CHECK) Ready for publication or Space update$(RESET)\n\n"
225
+
226
+ release-ci: ci-local
227
+ @printf "$(GREEN)$(BOLD)$(CHECK) Release CI passed for $(RELEASE_TAG)$(RESET)\n\n"
228
+
229
+ release-info:
230
+ @printf "$(CYAN)Package:$(RESET) $(YELLOW)$(BOLD)$(PACKAGE_NAME)$(RESET)\n"
231
+ @printf "$(CYAN)Version:$(RESET) $(YELLOW)$(BOLD)$(VERSION)$(RESET)\n"
232
+ @printf "$(CYAN)Release tag:$(RESET) $(YELLOW)$(BOLD)$(RELEASE_TAG)$(RESET)\n"
233
+
234
+ # ============== Metadata & Cleanup ==============
235
+ version:
236
+ @printf "$(CYAN)Current version:$(RESET) $(YELLOW)$(BOLD)$(VERSION)$(RESET)\n"
237
+ @printf "$(CYAN)Release tag:$(RESET) $(YELLOW)$(BOLD)$(RELEASE_TAG)$(RESET)\n"
238
+ @printf "$(CYAN)Default port:$(RESET) $(YELLOW)$(BOLD)$(PORT)$(RESET)\n"
239
+
240
+ clean:
241
+ @printf "$(ARROW) Cleaning build artifacts...\n"
242
+ @cargo clean
243
+ @printf "$(GREEN)$(CHECK) Clean complete$(RESET)\n"
244
+
245
+ watch:
246
+ @printf "$(ARROW) Watching and rerunning the app on port $(PORT)...\n"
247
+ @cargo watch --version >/dev/null 2>&1 || \
248
+ (printf "$(RED)$(CROSS) cargo-watch is required for make watch$(RESET)\n" && exit 1)
249
+ @PORT=$(PORT) cargo watch -x "run --bin $(APP_NAME)"
250
+
251
+ # ============== Help ==============
252
+ help: banner
253
+ @/bin/echo -e "$(CYAN)$(BOLD)Environment:$(RESET)"
254
+ @/bin/echo -e " $(GREEN)make doctor$(RESET) - Check local cargo/rustc/node readiness"
255
+ @/bin/echo -e ""
256
+ @/bin/echo -e "$(CYAN)$(BOLD)Build & Run:$(RESET)"
257
+ @/bin/echo -e " $(GREEN)make build$(RESET) - Build the app in debug mode"
258
+ @/bin/echo -e " $(GREEN)make build-release$(RESET) - Build the app in release mode"
259
+ @/bin/echo -e " $(GREEN)make run$(RESET) - Run locally on port $(PORT)"
260
+ @/bin/echo -e " $(GREEN)make run-release$(RESET) - Run the release build on port $(PORT)"
261
+ @/bin/echo -e ""
262
+ @/bin/echo -e "$(CYAN)$(BOLD)Tests & Validation:$(RESET)"
263
+ @/bin/echo -e " $(GREEN)make test$(RESET) - Run Rust, frontend, and Playwright tests"
264
+ @/bin/echo -e " $(GREEN)make test-rust$(RESET) - Run Rust tests only"
265
+ @/bin/echo -e " $(GREEN)make test-frontend$(RESET) - Run frontend syntax checks and tests"
266
+ @/bin/echo -e " $(GREEN)make test-e2e$(RESET) - Run Playwright browser tests"
267
+ @/bin/echo -e " $(GREEN)make test-live-road$(RESET) - Run the live road-network smoke test"
268
+ @/bin/echo -e " $(GREEN)make test-one TEST=name$(RESET) - Run a specific Rust test with output"
269
+ @/bin/echo -e " $(GREEN)make lint$(RESET) - Run fmt-check, clippy, and frontend syntax checks"
270
+ @/bin/echo -e " $(GREEN)make check$(RESET) - Run lint plus standard tests"
271
+ @/bin/echo -e " $(GREEN)make ci-local$(RESET) - Run local Space validation pipeline"
272
+ @/bin/echo -e " $(GREEN)make release-ci$(RESET) - Run the tag-publish CI gate for this app"
273
+ @/bin/echo -e " $(GREEN)make pre-release$(RESET) - Run ci-local plus live road-network smoke"
274
+ @/bin/echo -e ""
275
+ @/bin/echo -e "$(CYAN)$(BOLD)Space & Docker:$(RESET)"
276
+ @/bin/echo -e " $(GREEN)make space-build$(RESET) - Build the Docker image used for Space deployment"
277
+ @/bin/echo -e " $(GREEN)make space-run$(RESET) - Build and run that image locally on port $(PORT)"
278
+ @/bin/echo -e " $(GREEN)make docker-build$(RESET) - Build the Docker image directly"
279
+ @/bin/echo -e " $(GREEN)make docker-run$(RESET) - Run the Docker image directly"
280
+ @/bin/echo -e ""
281
+ @/bin/echo -e "$(CYAN)$(BOLD)Other:$(RESET)"
282
+ @/bin/echo -e " $(GREEN)make fmt$(RESET) - Format Rust code"
283
+ @/bin/echo -e " $(GREEN)make release-info$(RESET) - Show package version and app-scoped release tag"
284
+ @/bin/echo -e " $(GREEN)make version$(RESET) - Show version and default port"
285
+ @/bin/echo -e " $(GREEN)make clean$(RESET) - Clean build artifacts"
286
+ @/bin/echo -e " $(GREEN)make watch$(RESET) - Watch source files and rerun the app"
287
+ @/bin/echo -e " $(GREEN)make help$(RESET) - Show this help message"
288
+ @/bin/echo -e ""
289
+ @/bin/echo -e "$(GRAY)Rust version required: $(RUST_VERSION)$(RESET)"
290
+ @/bin/echo -e "$(GRAY)Current version: v$(VERSION)$(RESET)"
291
+ @/bin/echo -e "$(GRAY)Release tag: $(RELEASE_TAG)$(RESET)"
292
+ @/bin/echo -e "$(GRAY)Default port: $(PORT)$(RESET)"
293
+ @/bin/echo -e ""
README.md ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: SolverForge Deliveries
3
+ emoji: 🚚
4
+ colorFrom: green
5
+ colorTo: blue
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ license: apache-2.0
10
+ short_description: SolverForge delivery-route optimization example
11
+ ---
12
+
13
+ # SolverForge Deliveries
14
+
15
+ ![SolverForge Deliveries screenshot](docs/screenshot.png)
16
+
17
+ `solverforge-deliveries` is a SolverForge vehicle-routing app with retained
18
+ jobs, route geometry, insertion recommendations, and a browser plan viewer.
19
+
20
+ It answers one concrete question:
21
+
22
+ "Given depots, vehicles, delivery stops, capacities, and time windows, which
23
+ vehicle should visit each delivery and in what order?"
24
+
25
+ ## Quick Start
26
+
27
+ ```sh
28
+ make run-release
29
+ ```
30
+
31
+ Then open `http://localhost:7860`.
32
+
33
+ To inspect the supported command surface:
34
+
35
+ ```sh
36
+ make help
37
+ ```
38
+
39
+ ## Documentation Map
40
+
41
+ - `README.md`
42
+ Quick start, model concepts, validation, REST API, and solver policy.
43
+ - `WIREFRAME.md`
44
+ As-built architecture and runtime/data flow across backend, maps, and UI.
45
+ - `AGENTS.md`
46
+ Codex-facing maintenance, validation, and documentation rules.
47
+ - `Makefile`
48
+ Supported local commands for development, validation, Docker, and Space work.
49
+ - `Dockerfile`
50
+ Docker Space image build using Rust 1.95 and the declared crates.io line.
51
+
52
+ ## Current Dependency Shape
53
+
54
+ - Package: `solverforge-deliveries`; version is declared in `Cargo.toml`
55
+ - Release binary: `solverforge_deliveries`
56
+ - Rust: `1.95`
57
+ - SolverForge runtime: `solverforge` `0.19.3`
58
+ - Browser UI assets: `solverforge-ui` `0.6.5`
59
+ - Routing engine: `solverforge-maps` `2.1.4`
60
+ - Scaffold metadata: `solverforge-cli` `2.2.2` in `solverforge.app.toml`
61
+
62
+ The app serves registry-backed Rust dependencies, local static browser modules,
63
+ and Axum API routes from one process.
64
+
65
+ ## Model Concepts
66
+
67
+ - `Delivery` is a problem fact: input data the solver reads but does not move.
68
+ - `Vehicle` is a planning entity: each vehicle owns one mutable route.
69
+ - `Vehicle.delivery_order` is the list planning variable: the sequence
70
+ SolverForge changes during construction and local search.
71
+ - `Plan` is the planning solution: it owns deliveries, vehicles, road-network
72
+ routing state, view state, and the current `HardSoftScore`.
73
+
74
+ The app ships three deterministic datasets: `PHILADELPHIA` with 82 deliveries,
75
+ `HARTFORD` with 50 deliveries, and `FIRENZE` with 80 deliveries. Each dataset
76
+ has ten vehicles and coherent capacity for the published stops.
77
+
78
+ ## Constraints
79
+
80
+ Hard constraints:
81
+
82
+ - Every delivery is assigned.
83
+ - Vehicle capacity is not exceeded.
84
+ - Vehicle routes respect delivery time windows.
85
+
86
+ Soft constraints:
87
+
88
+ - Total travel time is minimized.
89
+
90
+ ## REST API
91
+
92
+ - `GET /health`
93
+ - `GET /info`
94
+ - `GET /demo-data`
95
+ - `GET /demo-data/{id}`
96
+ - `POST /jobs`
97
+ - `GET /jobs/{id}`
98
+ - `DELETE /jobs/{id}`
99
+ - `GET /jobs/{id}/status`
100
+ - `GET /jobs/{id}/snapshot`
101
+ - `GET /jobs/{id}/analysis`
102
+ - `GET /jobs/{id}/routes`
103
+ - `POST /jobs/{id}/pause`
104
+ - `POST /jobs/{id}/resume`
105
+ - `POST /jobs/{id}/cancel`
106
+ - `GET /jobs/{id}/events`
107
+ - `POST /recommendations/delivery-insertions`
108
+
109
+ `snapshot_revision={n}` is optional for snapshots, analysis, and routes. SSE
110
+ clients receive a bootstrap event and then live retained-job events.
111
+
112
+ ## Solver Policy
113
+
114
+ `solver.toml` is embedded by `Plan` and is the runtime source of truth.
115
+
116
+ - `list_clarke_wright` builds initial delivery routes.
117
+ - `list_k_opt` improves those routes before local search.
118
+ - `Vehicle.delivery_order` declares `domain = "cvrp"`, so SolverForge wires
119
+ stock CVRP construction and route-local behavior over per-vehicle prepared
120
+ matrices.
121
+ - Local search combines nearby list change/swap, reverse, k-opt, ruin, and
122
+ limited sublist moves over `Vehicle.delivery_order`.
123
+ - `late_acceptance` with `first_last_step_score_improving` keeps scanning past
124
+ equal accepted moves until the current step score improves.
125
+ - Solving stops after 30 seconds total or after 5 seconds without improvement.
126
+
127
+ The app uses `solverforge-maps` to load a road graph and return route geometry
128
+ through `/jobs/{id}/routes`.
129
+
130
+ ## Validation
131
+
132
+ Standard validation:
133
+
134
+ ```sh
135
+ make test
136
+ ```
137
+
138
+ Full local validation:
139
+
140
+ ```sh
141
+ make ci-local
142
+ ```
143
+
144
+ Live road-network smoke:
145
+
146
+ ```sh
147
+ make test-live-road
148
+ ```
149
+
150
+ `make test` runs Rust tests, browserless frontend tests, and Playwright browser
151
+ tests. `make ci-local` adds formatting, clippy, release build, and Docker image
152
+ build. `make pre-release` runs `ci-local` plus the live road-network smoke.
153
+
154
+ ## Hugging Face Space Deployment
155
+
156
+ This repo is Docker-Space ready. The Space reads the README front matter,
157
+ builds `Dockerfile`, and expects the app to bind `PORT=7860`.
158
+
159
+ Local Space-equivalent commands:
160
+
161
+ ```sh
162
+ make space-build
163
+ make space-run
164
+ ```
165
+
166
+ ## Read The Code In This Order
167
+
168
+ 1. `src/domain/mod.rs`
169
+ The `planning_model!` manifest and public domain exports.
170
+ 2. `src/domain/plan.rs`
171
+ The `Plan` solution, CVRP list-variable profile, and road-network marker.
172
+ 3. `src/domain/delivery.rs` and `src/domain/vehicle.rs`
173
+ The problem fact and planning entity.
174
+ 4. `src/domain/route_metrics/`
175
+ Route preparation, CVRP matrix data, preview scoring, route geometry, and
176
+ insertion ranking.
177
+ 5. `src/constraints/mod.rs` and `src/constraints/*.rs`
178
+ The score model, one rule per file.
179
+ 6. `src/data/data_seed/entrypoints.rs`
180
+ Public demo-data IDs and generator dispatch.
181
+ 7. `src/data/data_seed/{philadelphia,hartford,firenze}/`
182
+ City depots and delivery coordinates.
183
+ 8. `src/solver/service.rs`
184
+ Retained-job orchestration over `SolverManager<Plan>`.
185
+ 9. `src/api/routes.rs`, `src/api/dto.rs`, and `src/api/sse.rs`
186
+ HTTP routes, transport DTOs, and live-event streaming.
187
+ 10. `static/app/main.mjs`, `static/app/models/`, and `static/app/ui/`
188
+ Browser controller, model normalization, maps, tables, and modals.
189
+
190
+ ## Project Shape
191
+
192
+ - `src/domain/`
193
+ Planning model, domain types, route metrics, and model tests.
194
+ - `src/constraints/`
195
+ Incremental SolverForge scoring rules.
196
+ - `src/data/`
197
+ Deterministic city demo-data generators.
198
+ - `src/solver/`
199
+ Retained-job facade and runtime event payload formatting.
200
+ - `src/api/`
201
+ Axum routes, DTOs, errors, and SSE endpoint.
202
+ - `static/app/`
203
+ Browser modules built on stock `solverforge-ui` assets.
204
+ - `tests/api_contract/`
205
+ API integration coverage for catalog, jobs, lifecycle, SSE, and routes.
206
+ - `tests/e2e/`
207
+ Playwright browser tests for the served app.
WIREFRAME.md ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # solverforge-deliveries WIREFRAME
2
+
3
+ This file is the architectural map for the deliveries example.
4
+
5
+ `README.md` explains how to run and use the app. This document explains how the
6
+ pieces fit together and where each responsibility lives.
7
+
8
+ ## Documentation Roles
9
+
10
+ - `README.md`
11
+ Quick start, dependency shape, route list, and user-facing orientation.
12
+ - `WIREFRAME.md`
13
+ Architecture, execution flow, and file-map walkthrough.
14
+ - `AGENTS.md`
15
+ Repo-specific contribution, validation, and documentation rules.
16
+ - `Makefile`
17
+ Local development, validation, and Space/Docker command surface.
18
+ - `Dockerfile`
19
+ Hugging Face Docker Space image definition.
20
+ - `docs/screenshot.png`
21
+ Current browser screenshot embedded by the README.
22
+
23
+ ## What This Repo Is Teaching
24
+
25
+ This repo is a complete `solverforge-deliveries` list-variable SolverForge app
26
+ for delivery routing.
27
+
28
+ It shows how to combine:
29
+
30
+ - a `Plan` solution with a list planning variable
31
+ - route-specific score rules
32
+ - SolverForge CVRP domain profile for construction and local search
33
+ - `solverforge-maps` road-network preparation and route geometry
34
+ - retained jobs with snapshots, analysis, cancel, pause, resume, and SSE
35
+ - a browser plan viewer built on stock `solverforge-ui` assets
36
+
37
+ ## SolverForge Concepts In Plain Language
38
+
39
+ - `Delivery`
40
+ Input stop data. The solver assigns delivery IDs into vehicle routes.
41
+ - `Vehicle`
42
+ Planning entity. Each vehicle owns one ordered `delivery_order` list.
43
+ - `Plan`
44
+ Planning solution. It holds deliveries, vehicles, score, the road-network
45
+ marker, view state, and prepared routing data.
46
+ - hard score
47
+ Missing assignments, capacity overage, late delivery seconds, and unreachable
48
+ route legs.
49
+ - soft score
50
+ Total travel seconds.
51
+ - retained job
52
+ A solve that lives in memory so the UI can stream events, fetch snapshots,
53
+ pause/resume, cancel, analyze, and delete terminal jobs.
54
+
55
+ ## Runtime Flow
56
+
57
+ 1. The browser loads `static/index.html`.
58
+ 2. `static/app/main.mjs` loads `static/sf-config.json`.
59
+ 3. The app fetches `/demo-data/PHILADELPHIA`.
60
+ 4. `PlanDto::from_plan()` serializes a refreshed transport plan with preview
61
+ state.
62
+ 5. The browser normalizes the plan, renders summary cards, tables, timelines,
63
+ and a map.
64
+ 6. When the user clicks Solve, the browser sends the current plan to
65
+ `POST /jobs`.
66
+ 7. `src/api/routes.rs` deserializes the `PlanDto`, rebuilds a `Plan`, and calls
67
+ `prepare_plan()`.
68
+ 8. `prepare_plan()` builds road-network routing matrices and attaches
69
+ `PreparedVehicleRouting` to each vehicle.
70
+ 9. `SolverService` starts a retained solve through `SolverManager<Plan>`.
71
+ 10. Solver events are converted by `src/solver/service/runtime_payload.rs` into
72
+ UI-facing JSON.
73
+ 11. The browser consumes `/jobs/{id}/events` and fetches snapshots, analysis,
74
+ and route geometry for exact snapshot revisions.
75
+ 12. Road-network route geometry follows the `solverforge-maps` graph route,
76
+ projects each leg endpoint onto the nearest road segment, and stitches the
77
+ exact depot or delivery coordinate back in before encoding so each displayed
78
+ leg reaches the visible markers.
79
+
80
+ ## File Map
81
+
82
+ ```text
83
+ .
84
+ ├── Cargo.toml
85
+ │ Rust 1.95 crate metadata for the app package and registry dependency
86
+ │ requests.
87
+ ├── solver.toml
88
+ │ Embedded search policy for construction heuristics and local search.
89
+ ├── solverforge.app.toml
90
+ │ App metadata, demo IDs, model facts/entities, registry dependency sources,
91
+ │ and the `solverforge 0.19.3` runtime target.
92
+ ├── Makefile
93
+ │ Local build, validation, live-road, and Space/Docker commands.
94
+ ├── Dockerfile
95
+ │ Multi-stage Rust 1.95 Docker image for Hugging Face Spaces.
96
+ ├── .dockerignore
97
+ │ Keeps build artifacts, git metadata, route cache, and Playwright output out
98
+ │ of the Docker context.
99
+ ├── README.md
100
+ │ Run guide, dependency shape, API list, and learning path.
101
+ ├── AGENTS.md
102
+ │ Repo-specific rules for future edits.
103
+ ├── WIREFRAME.md
104
+ │ This architectural walkthrough.
105
+ ├── docs/screenshot.png
106
+ │ Current browser screenshot used by the README.
107
+ ├── src/
108
+ │ ├── domain/
109
+ │ │ `planning_model!` manifest, `Plan`, entities, facts, preview structs,
110
+ │ │ and route metrics.
111
+ │ ├── constraints/
112
+ │ │ Assignment, capacity, time-window, and travel-time score rules.
113
+ │ ├── data/
114
+ │ │ Deterministic city seed data and generator entrypoints.
115
+ │ ├── solver/
116
+ │ │ Retained-job service and runtime event payload formatting.
117
+ │ └── api/
118
+ │ Axum routes, DTOs, and SSE endpoint.
119
+ ├── static/
120
+ │ ├── index.html
121
+ │ ├── sf-config.json
122
+ │ ├── generated/ui-model.json
123
+ │ └── app/
124
+ │ Browser controller, plan models, and UI renderers.
125
+ └── tests/
126
+ ├── api_contract.rs
127
+ │ Single integration-test crate composed from `tests/api_contract/*.rs`.
128
+ ├── api_contract/
129
+ │ Catalog, job, lifecycle, SSE, and live-road modules.
130
+ ├── support/
131
+ │ Shared integration-test helpers used by that single crate.
132
+ ├── e2e/
133
+ │ Playwright browser tests for the served app.
134
+ └── frontend_models.test.mjs
135
+ Frontend model tests.
136
+ ```
137
+
138
+ ## Domain And Route Metrics
139
+
140
+ `src/domain/plan.rs` stays small and macro-facing. It owns the `Plan` struct,
141
+ normalization, list shadow refresh, transport refresh, and the
142
+ `VrpSolution` implementation.
143
+
144
+ Route-specific behavior lives under `src/domain/route_metrics/`:
145
+
146
+ - `preparation.rs`
147
+ Builds per-vehicle prepared routing data and the depot-aware matrices consumed
148
+ by the stock `domain = "cvrp"` list-variable profile.
149
+ - `metrics.rs`
150
+ Computes per-vehicle route metrics.
151
+ - `scoring.rs`
152
+ Builds preview DTOs and aggregate hard/soft score components.
153
+ - `routes.rs`
154
+ Builds road-network route geometry snapshots, including edge-projected visual
155
+ endpoints for road legs.
156
+ - `insertions.rs`
157
+ Ranks candidate insertion positions for one delivery.
158
+ - `types.rs`
159
+ Shared route metrics, snapshots, candidates, and prepared-routing types.
160
+
161
+ This split keeps the public domain API stable while avoiding oversized files.
162
+
163
+ ## Demo Data
164
+
165
+ `src/data/data_seed/entrypoints.rs` exposes three demo IDs:
166
+
167
+ - `PHILADELPHIA`
168
+ - `HARTFORD`
169
+ - `FIRENZE`
170
+
171
+ Each city has a small module with separate depot and grouped visit files. The
172
+ generator is deterministic. Every demo has ten vehicle depots, enough scaled
173
+ deliveries for those vehicles, reachable road-network coordinates, and enough
174
+ aggregate capacity before route ordering.
175
+
176
+ ## API And Retained Runtime
177
+
178
+ The REST API handles discovery, job control, snapshots, and route geometry:
179
+
180
+ - `/health` and `/info` expose liveness and app metadata.
181
+ - `/demo-data` and `/demo-data/{id}` expose the deterministic demo catalog.
182
+ - `/jobs` creates a retained solver job.
183
+ - `/jobs/{id}` and `/jobs/{id}/status` expose summary state.
184
+ - `/jobs/{id}/snapshot` returns an exact or latest snapshot.
185
+ - `/jobs/{id}/analysis` runs constraint analysis for a snapshot.
186
+ - `/jobs/{id}/routes` returns route geometry for a snapshot.
187
+ - `/jobs/{id}/pause`, `/jobs/{id}/resume`, and `/jobs/{id}/cancel` control a
188
+ live job.
189
+ - `DELETE /jobs/{id}` removes a terminal retained job.
190
+ - `/jobs/{id}/events` streams typed lifecycle events.
191
+
192
+ The insertion endpoint, `/recommendations/delivery-insertions`, is app-specific.
193
+ It prepares the submitted plan, removes the requested delivery from any
194
+ existing route, evaluates candidate insert positions, and returns preview plans.
195
+
196
+ ## Frontend Layout
197
+
198
+ `static/app/main.mjs` is the controller. It owns current plan state, retained
199
+ job state, route identity tracking, and event handlers.
200
+
201
+ Supporting modules are split by responsibility:
202
+
203
+ - `static/app/models/core.mjs`
204
+ Clone and normalize incoming plans.
205
+ - `static/app/models/preview.mjs`
206
+ Draft assignment and capacity preview scoring.
207
+ - `static/app/models/timeline.mjs`
208
+ Vehicle and delivery rail models.
209
+ - `static/app/models/formatters.mjs`
210
+ Labels, icons, tones, clocks, and durations.
211
+ - `static/app/ui/layout.mjs`
212
+ Page shell and stock SolverForge UI component composition.
213
+ - `static/app/ui/overview.mjs`
214
+ Summary, route list, vehicle-id keyed route highlighting, map, and timeline
215
+ rendering. The tutorial uses ten distinct map colors for its ten fixed
216
+ vehicles.
217
+ - `static/app/ui/data-tables.mjs`
218
+ Read-only vehicle and delivery tables with delivery insertion recommendations.
219
+ - `static/app/ui/modals.mjs`
220
+ Analysis and insertion recommendation bodies.
221
+ - `static/app/ui/api-guide.mjs`
222
+ Visible API guide content.
223
+ - `static/app/ui/lifecycle.mjs`
224
+ Dataset markers and route-identity helpers.
225
+
226
+ ## Validation Surfaces
227
+
228
+ Use the Makefile as the repo-local workflow:
229
+
230
+ - `make fmt-check`
231
+ - `make clippy`
232
+ - `make build-release`
233
+ - `make test`
234
+ - `make test-e2e`
235
+ - `make space-build`
236
+ - `make test-live-road`
237
+ - `make ci-local`
238
+ - `make pre-release`
239
+
240
+ `make ci-local` includes the Docker image build used by the Hugging Face Space.
241
+ The Playwright command uses the publication bundle's root Node dev dependency;
242
+ runtime UI assets are served from the declared `solverforge-ui` Cargo crate.
243
+
244
+ The file-size rule is part of the architecture: keep files below 300 lines and
245
+ split by responsibility before they become broad catch-all modules.
docs/screenshot.png ADDED

Git LFS Details

  • SHA256: 1e86251fafc867891675533d2aa7d0c2aa852f8d733564bf9251230c6a6698fe
  • Pointer size: 131 Bytes
  • Size of remote file: 127 kB
solver.toml ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ environment_mode = "reproducible"
2
+ random_seed = 42
3
+
4
+ [termination]
5
+ seconds_spent_limit = 30
6
+ unimproved_seconds_spent_limit = 5
7
+
8
+ [[phases]]
9
+ type = "construction_heuristic"
10
+ construction_heuristic_type = "list_clarke_wright"
11
+ entity_class = "Vehicle"
12
+ variable_name = "delivery_order"
13
+
14
+ [[phases]]
15
+ type = "construction_heuristic"
16
+ construction_heuristic_type = "list_k_opt"
17
+ k = 2
18
+ entity_class = "Vehicle"
19
+ variable_name = "delivery_order"
20
+
21
+ [[phases]]
22
+ type = "local_search"
23
+
24
+ [phases.acceptor]
25
+ type = "late_acceptance"
26
+ late_acceptance_size = 200
27
+
28
+ [phases.forager]
29
+ type = "first_last_step_score_improving"
30
+
31
+ [phases.move_selector]
32
+ type = "union_move_selector"
33
+
34
+ [[phases.move_selector.selectors]]
35
+ type = "nearby_list_change_move_selector"
36
+ max_nearby = 20
37
+ entity_class = "Vehicle"
38
+ variable_name = "delivery_order"
39
+
40
+ [[phases.move_selector.selectors]]
41
+ type = "nearby_list_swap_move_selector"
42
+ max_nearby = 20
43
+ entity_class = "Vehicle"
44
+ variable_name = "delivery_order"
45
+
46
+ [[phases.move_selector.selectors]]
47
+ type = "list_reverse_move_selector"
48
+ entity_class = "Vehicle"
49
+ variable_name = "delivery_order"
50
+
51
+ [[phases.move_selector.selectors]]
52
+ type = "k_opt_move_selector"
53
+ k = 3
54
+ min_segment_len = 1
55
+ max_nearby = 10
56
+ entity_class = "Vehicle"
57
+ variable_name = "delivery_order"
58
+
59
+ [[phases.move_selector.selectors]]
60
+ type = "list_ruin_move_selector"
61
+ min_ruin_count = 2
62
+ max_ruin_count = 5
63
+ moves_per_step = 10
64
+ entity_class = "Vehicle"
65
+ variable_name = "delivery_order"
66
+
67
+ [[phases.move_selector.selectors]]
68
+ type = "limited_neighborhood"
69
+ selected_count_limit = 500
70
+
71
+ [phases.move_selector.selectors.selector]
72
+ type = "sublist_change_move_selector"
73
+ min_sublist_size = 1
74
+ max_sublist_size = 3
75
+ entity_class = "Vehicle"
76
+ variable_name = "delivery_order"
solverforge.app.toml ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [app]
2
+ name = "solverforge-deliveries"
3
+ starter = "neutral-shell"
4
+ cli_version = "2.2.2"
5
+ shell = "web"
6
+
7
+ [runtime]
8
+ target = "solverforge 0.19.3"
9
+ runtime_source = "crates.io: solverforge 0.19.3"
10
+ ui_source = "crates.io: solverforge-ui 0.6.5"
11
+
12
+ [demo]
13
+ default_size = "PHILADELPHIA"
14
+ available_sizes = [
15
+ "PHILADELPHIA",
16
+ "HARTFORD",
17
+ "FIRENZE",
18
+ ]
19
+
20
+ [solution]
21
+ name = "Plan"
22
+ score = "HardSoftScore"
23
+
24
+ [[facts]]
25
+ name = "delivery"
26
+ plural = "deliveries"
27
+ kind = "problem_fact"
28
+
29
+ [[entities]]
30
+ name = "vehicle"
31
+ plural = "vehicles"
32
+ kind = "planning_entity"
33
+
34
+ [[variables]]
35
+ entity = "vehicle"
36
+ entity_plural = "vehicles"
37
+ field = "delivery_order"
38
+ kind = "list"
39
+ range = ""
40
+ elements = "deliveries"
41
+ allows_unassigned = false
42
+ enabled = true
43
+
44
+ [[constraints]]
45
+ name = "all_deliveries_assigned"
46
+ module = "all_deliveries_assigned"
47
+ enabled = true
48
+
49
+ [[constraints]]
50
+ name = "vehicle_capacity"
51
+ module = "vehicle_capacity"
52
+ enabled = true
53
+
54
+ [[constraints]]
55
+ name = "delivery_time_windows"
56
+ module = "delivery_time_windows"
57
+ enabled = true
58
+
59
+ [[constraints]]
60
+ name = "total_travel_time"
61
+ module = "total_travel_time"
62
+ enabled = true
src/api/dto.rs ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Browser-facing JSON types for the deliveries API.
2
+ //!
3
+ //! The domain model contains SolverForge annotations and route-preparation
4
+ //! caches. DTOs keep the transport contract plain: strings for scores,
5
+ //! camelCase field names, and only data the browser can render or request
6
+ //! again.
7
+
8
+ use serde::{Deserialize, Serialize};
9
+ use serde_json::{Map, Value};
10
+ use solverforge::{HardSoftScore, SolverSnapshot, SolverSnapshotAnalysis, SolverStatus};
11
+
12
+ use crate::domain::{DeliveryInsertionCandidate, Plan, RoutesSnapshot};
13
+
14
+ mod runtime;
15
+
16
+ pub use runtime::{lifecycle_state_label, terminal_reason_label, TelemetryDto};
17
+
18
+ #[derive(Debug, Clone, Serialize, Deserialize)]
19
+ #[serde(rename_all = "camelCase")]
20
+ pub struct PlanDto {
21
+ /// Flattened domain fields let the browser reuse SolverForge's generic
22
+ /// model metadata while this app adds delivery-specific route previews.
23
+ #[serde(flatten)]
24
+ pub fields: Map<String, Value>,
25
+ #[serde(default)]
26
+ pub score: Option<String>,
27
+ }
28
+
29
+ #[derive(Debug, Clone, Serialize)]
30
+ #[serde(rename_all = "camelCase")]
31
+ pub struct ConstraintAnalysisDto {
32
+ pub name: String,
33
+ pub weight: String,
34
+ pub score: String,
35
+ pub match_count: usize,
36
+ }
37
+
38
+ #[derive(Debug, Clone, Serialize)]
39
+ #[serde(rename_all = "camelCase")]
40
+ pub struct AnalyzeResponse {
41
+ pub score: String,
42
+ pub constraints: Vec<ConstraintAnalysisDto>,
43
+ }
44
+
45
+ #[derive(Debug, Clone, Serialize)]
46
+ #[serde(rename_all = "camelCase")]
47
+ pub struct JobSummaryDto {
48
+ pub id: String,
49
+ pub job_id: String,
50
+ pub lifecycle_state: &'static str,
51
+ pub terminal_reason: Option<&'static str>,
52
+ pub checkpoint_available: bool,
53
+ pub event_sequence: u64,
54
+ pub snapshot_revision: Option<u64>,
55
+ pub current_score: Option<String>,
56
+ pub best_score: Option<String>,
57
+ pub telemetry: TelemetryDto,
58
+ }
59
+
60
+ #[derive(Debug, Clone, Serialize)]
61
+ #[serde(rename_all = "camelCase")]
62
+ pub struct JobSnapshotDto {
63
+ pub id: String,
64
+ pub job_id: String,
65
+ pub snapshot_revision: u64,
66
+ pub lifecycle_state: &'static str,
67
+ pub terminal_reason: Option<&'static str>,
68
+ pub current_score: Option<String>,
69
+ pub best_score: Option<String>,
70
+ pub telemetry: TelemetryDto,
71
+ pub solution: PlanDto,
72
+ }
73
+
74
+ #[derive(Debug, Clone, Serialize)]
75
+ #[serde(rename_all = "camelCase")]
76
+ pub struct JobAnalysisDto {
77
+ pub id: String,
78
+ pub job_id: String,
79
+ pub snapshot_revision: u64,
80
+ pub lifecycle_state: &'static str,
81
+ pub terminal_reason: Option<&'static str>,
82
+ pub analysis: AnalyzeResponse,
83
+ }
84
+
85
+ #[derive(Debug, Clone, Serialize)]
86
+ #[serde(rename_all = "camelCase")]
87
+ pub struct JobRoutesDto {
88
+ pub id: String,
89
+ pub job_id: String,
90
+ pub snapshot_revision: u64,
91
+ #[serde(flatten)]
92
+ pub routes: RoutesSnapshot,
93
+ }
94
+
95
+ #[derive(Debug, Clone, Deserialize)]
96
+ #[serde(rename_all = "camelCase")]
97
+ pub struct DeliveryInsertionRequestDto {
98
+ pub plan: PlanDto,
99
+ pub delivery_id: usize,
100
+ pub limit: Option<usize>,
101
+ }
102
+
103
+ #[derive(Debug, Clone, Serialize)]
104
+ #[serde(rename_all = "camelCase")]
105
+ pub struct DeliveryInsertionCandidateDto {
106
+ pub vehicle_id: usize,
107
+ pub vehicle_name: String,
108
+ pub insert_index: usize,
109
+ pub hard_score: i64,
110
+ pub soft_score: i64,
111
+ pub score: String,
112
+ pub delta_hard: i64,
113
+ pub delta_soft: i64,
114
+ pub preview_plan: PlanDto,
115
+ }
116
+
117
+ #[derive(Debug, Clone, Serialize)]
118
+ #[serde(rename_all = "camelCase")]
119
+ pub struct DeliveryInsertionResponseDto {
120
+ pub delivery_id: usize,
121
+ pub candidates: Vec<DeliveryInsertionCandidateDto>,
122
+ }
123
+
124
+ impl PlanDto {
125
+ /// Converts a domain plan into the browser JSON shape.
126
+ pub fn from_plan(plan: &Plan) -> Self {
127
+ let plan = plan.refreshed_for_transport();
128
+ let score = plan.score.as_ref().map(ToString::to_string);
129
+ let mut fields = match serde_json::to_value(plan).expect("failed to serialize plan") {
130
+ Value::Object(map) => map,
131
+ _ => Map::new(),
132
+ };
133
+ fields.remove("score");
134
+
135
+ Self { fields, score }
136
+ }
137
+
138
+ /// Rebuilds the SolverForge domain value from a browser request payload.
139
+ pub fn to_domain(&self) -> Result<Plan, serde_json::Error> {
140
+ let mut fields = self.fields.clone();
141
+ fields.insert("score".to_string(), Value::Null);
142
+ let mut plan: Plan = serde_json::from_value(Value::Object(fields))?;
143
+ plan.normalize();
144
+ Ok(plan)
145
+ }
146
+ }
147
+
148
+ impl JobSummaryDto {
149
+ pub fn from_status(job_id: usize, status: &SolverStatus<HardSoftScore>) -> Self {
150
+ Self {
151
+ id: job_id.to_string(),
152
+ job_id: job_id.to_string(),
153
+ lifecycle_state: lifecycle_state_label(status.lifecycle_state),
154
+ terminal_reason: status.terminal_reason.map(terminal_reason_label),
155
+ checkpoint_available: status.checkpoint_available,
156
+ event_sequence: status.event_sequence,
157
+ snapshot_revision: status.latest_snapshot_revision,
158
+ current_score: status.current_score.map(|score| score.to_string()),
159
+ best_score: status.best_score.map(|score| score.to_string()),
160
+ telemetry: TelemetryDto::from_runtime(&status.telemetry),
161
+ }
162
+ }
163
+ }
164
+
165
+ impl JobSnapshotDto {
166
+ pub fn from_snapshot(snapshot: &SolverSnapshot<Plan>) -> Self {
167
+ Self {
168
+ id: snapshot.job_id.to_string(),
169
+ job_id: snapshot.job_id.to_string(),
170
+ snapshot_revision: snapshot.snapshot_revision,
171
+ lifecycle_state: lifecycle_state_label(snapshot.lifecycle_state),
172
+ terminal_reason: snapshot.terminal_reason.map(terminal_reason_label),
173
+ current_score: snapshot.current_score.map(|score| score.to_string()),
174
+ best_score: snapshot.best_score.map(|score| score.to_string()),
175
+ telemetry: TelemetryDto::from_runtime(&snapshot.telemetry),
176
+ solution: PlanDto::from_plan(&snapshot.solution),
177
+ }
178
+ }
179
+ }
180
+
181
+ impl JobAnalysisDto {
182
+ pub fn from_snapshot_analysis(
183
+ snapshot: &SolverSnapshotAnalysis<HardSoftScore>,
184
+ analysis: AnalyzeResponse,
185
+ ) -> Self {
186
+ Self {
187
+ id: snapshot.job_id.to_string(),
188
+ job_id: snapshot.job_id.to_string(),
189
+ snapshot_revision: snapshot.snapshot_revision,
190
+ lifecycle_state: lifecycle_state_label(snapshot.lifecycle_state),
191
+ terminal_reason: snapshot.terminal_reason.map(terminal_reason_label),
192
+ analysis,
193
+ }
194
+ }
195
+ }
196
+
197
+ impl JobRoutesDto {
198
+ pub fn new(job_id: usize, snapshot_revision: u64, routes: RoutesSnapshot) -> Self {
199
+ Self {
200
+ id: job_id.to_string(),
201
+ job_id: job_id.to_string(),
202
+ snapshot_revision,
203
+ routes,
204
+ }
205
+ }
206
+ }
207
+
208
+ impl DeliveryInsertionCandidateDto {
209
+ /// Adds score strings to an insertion candidate returned by route metrics.
210
+ pub fn from_candidate(candidate: DeliveryInsertionCandidate) -> Self {
211
+ Self {
212
+ vehicle_id: candidate.vehicle_id,
213
+ vehicle_name: candidate.vehicle_name,
214
+ insert_index: candidate.insert_index,
215
+ hard_score: candidate.hard_score,
216
+ soft_score: candidate.soft_score,
217
+ score: HardSoftScore::of(candidate.hard_score, candidate.soft_score).to_string(),
218
+ delta_hard: candidate.delta_hard,
219
+ delta_soft: candidate.delta_soft,
220
+ preview_plan: PlanDto::from_plan(&candidate.preview_plan),
221
+ }
222
+ }
223
+ }
224
+
225
+ pub fn analysis_response(analysis: &solverforge::ScoreAnalysis<HardSoftScore>) -> AnalyzeResponse {
226
+ AnalyzeResponse {
227
+ score: analysis.score.to_string(),
228
+ constraints: analysis
229
+ .constraints
230
+ .iter()
231
+ .map(|constraint| ConstraintAnalysisDto {
232
+ name: constraint.name.clone(),
233
+ weight: constraint.weight.to_string(),
234
+ score: constraint.score.to_string(),
235
+ match_count: constraint.match_count,
236
+ })
237
+ .collect(),
238
+ }
239
+ }
240
+
241
+ #[cfg(test)]
242
+ mod tests;
src/api/dto/runtime.rs ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use serde::Serialize;
2
+ use solverforge::{SolverLifecycleState, SolverTelemetry, SolverTerminalReason};
3
+ use std::time::Duration;
4
+
5
+ #[derive(Debug, Clone, Copy, Serialize)]
6
+ #[serde(rename_all = "camelCase")]
7
+ pub struct TelemetryDto {
8
+ pub elapsed_ms: u64,
9
+ pub step_count: u64,
10
+ pub moves_generated: u64,
11
+ pub moves_evaluated: u64,
12
+ pub moves_accepted: u64,
13
+ pub score_calculations: u64,
14
+ pub generation_ms: u64,
15
+ pub evaluation_ms: u64,
16
+ pub moves_per_second: u64,
17
+ pub acceptance_rate: f64,
18
+ }
19
+
20
+ impl TelemetryDto {
21
+ pub fn from_runtime(telemetry: &SolverTelemetry) -> Self {
22
+ Self {
23
+ elapsed_ms: duration_to_millis(telemetry.elapsed),
24
+ step_count: telemetry.step_count,
25
+ moves_generated: telemetry.moves_generated,
26
+ moves_evaluated: telemetry.moves_evaluated,
27
+ moves_accepted: telemetry.moves_accepted,
28
+ score_calculations: telemetry.score_calculations,
29
+ generation_ms: duration_to_millis(telemetry.generation_time),
30
+ evaluation_ms: duration_to_millis(telemetry.evaluation_time),
31
+ moves_per_second: whole_units_per_second(telemetry.moves_evaluated, telemetry.elapsed),
32
+ acceptance_rate: derive_acceptance_rate(
33
+ telemetry.moves_accepted,
34
+ telemetry.moves_evaluated,
35
+ ),
36
+ }
37
+ }
38
+ }
39
+
40
+ pub fn lifecycle_state_label(state: SolverLifecycleState) -> &'static str {
41
+ match state {
42
+ SolverLifecycleState::Solving => "SOLVING",
43
+ SolverLifecycleState::PauseRequested => "PAUSE_REQUESTED",
44
+ SolverLifecycleState::Paused => "PAUSED",
45
+ SolverLifecycleState::Completed => "COMPLETED",
46
+ SolverLifecycleState::Cancelled => "CANCELLED",
47
+ SolverLifecycleState::Failed => "FAILED",
48
+ }
49
+ }
50
+
51
+ pub fn terminal_reason_label(reason: SolverTerminalReason) -> &'static str {
52
+ match reason {
53
+ SolverTerminalReason::Completed => "completed",
54
+ SolverTerminalReason::TerminatedByConfig => "terminated_by_config",
55
+ SolverTerminalReason::Cancelled => "cancelled",
56
+ SolverTerminalReason::Failed => "failed",
57
+ }
58
+ }
59
+
60
+ fn duration_to_millis(duration: Duration) -> u64 {
61
+ duration.as_millis().min(u128::from(u64::MAX)) as u64
62
+ }
63
+
64
+ fn whole_units_per_second(count: u64, elapsed: Duration) -> u64 {
65
+ let nanos = elapsed.as_nanos();
66
+ if nanos == 0 {
67
+ 0
68
+ } else {
69
+ let per_second = u128::from(count)
70
+ .saturating_mul(1_000_000_000)
71
+ .checked_div(nanos)
72
+ .unwrap_or(0);
73
+ per_second.min(u128::from(u64::MAX)) as u64
74
+ }
75
+ }
76
+
77
+ fn derive_acceptance_rate(moves_accepted: u64, moves_evaluated: u64) -> f64 {
78
+ if moves_evaluated == 0 {
79
+ 0.0
80
+ } else {
81
+ moves_accepted as f64 / moves_evaluated as f64
82
+ }
83
+ }
src/api/dto/tests.rs ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::*;
2
+
3
+ #[test]
4
+ fn plan_dto_serializes_hard_soft_score_as_display_string() {
5
+ let mut plan = Plan::new("score check", Vec::new(), Vec::new());
6
+ plan.score = Some(HardSoftScore::of(0, -335));
7
+
8
+ let dto = PlanDto::from_plan(&plan);
9
+ assert_eq!(dto.score.as_deref(), Some("0hard/-335soft"));
10
+
11
+ let value = serde_json::to_value(&dto).expect("dto should serialize");
12
+ assert_eq!(value["score"], Value::String("0hard/-335soft".to_string()));
13
+ assert!(
14
+ !value["score"].is_object(),
15
+ "score must not serialize as a JSON object"
16
+ );
17
+ }
18
+
19
+ #[test]
20
+ fn plan_dto_ignores_inbound_score() {
21
+ let mut fields = Map::new();
22
+ fields.insert("name".to_string(), Value::String("spoofed".to_string()));
23
+ fields.insert(
24
+ "routingMode".to_string(),
25
+ Value::String("road_network".to_string()),
26
+ );
27
+ fields.insert("viewState".to_string(), Value::Object(Map::new()));
28
+ fields.insert("deliveries".to_string(), Value::Array(Vec::new()));
29
+ fields.insert("vehicles".to_string(), Value::Array(Vec::new()));
30
+
31
+ let dto = PlanDto {
32
+ fields,
33
+ score: Some("0hard/-335soft".to_string()),
34
+ };
35
+ let plan = dto.to_domain().expect("dto should deserialize");
36
+
37
+ assert_eq!(plan.score, None);
38
+ }
39
+
40
+ #[test]
41
+ fn plan_dto_rejects_removed_straight_line_routing_mode() {
42
+ let mut fields = Map::new();
43
+ fields.insert("name".to_string(), Value::String("legacy".to_string()));
44
+ fields.insert(
45
+ "routingMode".to_string(),
46
+ Value::String("straight_line".to_string()),
47
+ );
48
+ fields.insert("viewState".to_string(), Value::Object(Map::new()));
49
+ fields.insert("deliveries".to_string(), Value::Array(Vec::new()));
50
+ fields.insert("vehicles".to_string(), Value::Array(Vec::new()));
51
+
52
+ let dto = PlanDto {
53
+ fields,
54
+ score: None,
55
+ };
56
+
57
+ assert!(dto.to_domain().is_err());
58
+ }
src/api/errors.rs ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Shared HTTP error mapping for runtime and routing failures.
2
+ //!
3
+ //! Keeping this out of `routes.rs` lets the handler file read as a tutorial
4
+ //! walkthrough of the public API instead of as a collection of mapping helpers.
5
+
6
+ use axum::http::StatusCode;
7
+
8
+ /// Parses the path segment used by stock retained-job routes.
9
+ pub(super) fn parse_job_id(id: &str) -> Result<usize, StatusCode> {
10
+ id.parse::<usize>().map_err(|_| StatusCode::NOT_FOUND)
11
+ }
12
+
13
+ /// Maps retained-runtime errors onto HTTP statuses the stock UI understands.
14
+ pub(super) fn status_from_solver_error(error: solverforge::SolverManagerError) -> StatusCode {
15
+ match error {
16
+ solverforge::SolverManagerError::NoFreeJobSlots => StatusCode::SERVICE_UNAVAILABLE,
17
+ solverforge::SolverManagerError::JobNotFound { .. } => StatusCode::NOT_FOUND,
18
+ solverforge::SolverManagerError::InvalidStateTransition { .. } => StatusCode::CONFLICT,
19
+ solverforge::SolverManagerError::NoSnapshotAvailable { .. } => StatusCode::CONFLICT,
20
+ solverforge::SolverManagerError::SnapshotNotFound { .. } => StatusCode::NOT_FOUND,
21
+ }
22
+ }
23
+
24
+ /// Maps map/routing preparation errors onto client-facing route statuses.
25
+ pub(super) fn status_from_routing_error(error: solverforge_maps::RoutingError) -> StatusCode {
26
+ match error {
27
+ solverforge_maps::RoutingError::InvalidCoordinate { .. } => StatusCode::BAD_REQUEST,
28
+ solverforge_maps::RoutingError::Cancelled => StatusCode::REQUEST_TIMEOUT,
29
+ solverforge_maps::RoutingError::Network(_)
30
+ | solverforge_maps::RoutingError::Parse(_)
31
+ | solverforge_maps::RoutingError::Io(_)
32
+ | solverforge_maps::RoutingError::SnapFailed { .. }
33
+ | solverforge_maps::RoutingError::NoPath { .. } => StatusCode::BAD_GATEWAY,
34
+ }
35
+ }
src/api/mod.rs ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! HTTP transport surface for the deliveries tutorial.
2
+ //!
3
+ //! The API layer stays intentionally thin: routes decode requests, DTOs define
4
+ //! the browser-visible JSON contract, and `SolverService` owns retained jobs.
5
+
6
+ mod dto;
7
+ mod errors;
8
+ mod routes;
9
+ mod sse;
10
+
11
+ pub use dto::PlanDto;
12
+ pub use routes::{router, AppState};
src/api/routes.rs ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! HTTP routes for the deliveries tutorial app.
2
+ //!
3
+ //! Each handler follows the same beginner-friendly shape:
4
+ //! decode request -> prepare the domain model if needed -> call the retained
5
+ //! solver facade -> encode a DTO for the browser.
6
+
7
+ use axum::{
8
+ extract::{Path, Query, State},
9
+ http::StatusCode,
10
+ routing::{get, post},
11
+ Json, Router,
12
+ };
13
+ use serde::{Deserialize, Serialize};
14
+ use std::sync::Arc;
15
+
16
+ use super::dto::{
17
+ analysis_response, DeliveryInsertionCandidateDto, DeliveryInsertionRequestDto,
18
+ DeliveryInsertionResponseDto, JobAnalysisDto, JobRoutesDto, JobSnapshotDto, JobSummaryDto,
19
+ PlanDto,
20
+ };
21
+ use super::errors::{parse_job_id, status_from_routing_error, status_from_solver_error};
22
+ use super::sse;
23
+ use crate::data::{generate, DemoData};
24
+ use crate::domain::{build_routes_snapshot, prepare_plan, rank_delivery_insertions};
25
+ use crate::solver::SolverService;
26
+
27
+ /// Shared application state stored once inside Axum.
28
+ pub struct AppState {
29
+ pub solver: SolverService,
30
+ }
31
+
32
+ impl AppState {
33
+ pub fn new() -> Self {
34
+ Self {
35
+ solver: SolverService::new(),
36
+ }
37
+ }
38
+ }
39
+
40
+ impl Default for AppState {
41
+ fn default() -> Self {
42
+ Self::new()
43
+ }
44
+ }
45
+
46
+ /// Registers the public HTTP surface used by the browser and tests.
47
+ pub fn router(state: Arc<AppState>) -> Router {
48
+ Router::new()
49
+ .route("/health", get(health))
50
+ .route("/info", get(info))
51
+ .route("/demo-data", get(list_demo_data))
52
+ .route("/demo-data/{id}", get(get_demo_data))
53
+ .route("/jobs", post(create_job))
54
+ .route("/jobs/{id}", get(get_job).delete(delete_job))
55
+ .route("/jobs/{id}/status", get(get_job_status))
56
+ .route("/jobs/{id}/snapshot", get(get_snapshot))
57
+ .route("/jobs/{id}/analysis", get(analyze_by_id))
58
+ .route("/jobs/{id}/routes", get(get_routes))
59
+ .route("/jobs/{id}/pause", post(pause_job))
60
+ .route("/jobs/{id}/resume", post(resume_job))
61
+ .route("/jobs/{id}/cancel", post(cancel_job))
62
+ .route("/jobs/{id}/events", get(sse::events))
63
+ .route(
64
+ "/recommendations/delivery-insertions",
65
+ post(recommend_delivery_insertions),
66
+ )
67
+ .with_state(state)
68
+ }
69
+
70
+ #[derive(Serialize)]
71
+ struct HealthResponse {
72
+ status: &'static str,
73
+ }
74
+
75
+ async fn health() -> Json<HealthResponse> {
76
+ Json(HealthResponse { status: "UP" })
77
+ }
78
+
79
+ #[derive(Serialize)]
80
+ #[serde(rename_all = "camelCase")]
81
+ struct InfoResponse {
82
+ name: &'static str,
83
+ version: &'static str,
84
+ solver_engine: &'static str,
85
+ }
86
+
87
+ async fn info() -> Json<InfoResponse> {
88
+ Json(InfoResponse {
89
+ name: "SolverForge Deliveries",
90
+ version: env!("CARGO_PKG_VERSION"),
91
+ solver_engine: "SolverForge",
92
+ })
93
+ }
94
+
95
+ /// Lists the deterministic demo datasets accepted by `/demo-data/{id}`.
96
+ async fn list_demo_data() -> Json<Vec<&'static str>> {
97
+ Json(vec![
98
+ DemoData::Philadelphia.id(),
99
+ DemoData::Hartford.id(),
100
+ DemoData::Firenze.id(),
101
+ ])
102
+ }
103
+
104
+ /// Materializes one demo plan and sends it through the same DTO as snapshots.
105
+ async fn get_demo_data(Path(id): Path<String>) -> Result<Json<PlanDto>, StatusCode> {
106
+ let demo = id.parse::<DemoData>().map_err(|_| StatusCode::NOT_FOUND)?;
107
+ let plan = generate(demo);
108
+ Ok(Json(PlanDto::from_plan(&plan)))
109
+ }
110
+
111
+ #[derive(Serialize)]
112
+ #[serde(rename_all = "camelCase")]
113
+ struct CreateJobResponse {
114
+ id: String,
115
+ }
116
+
117
+ async fn create_job(
118
+ State(state): State<Arc<AppState>>,
119
+ Json(dto): Json<PlanDto>,
120
+ ) -> Result<Json<CreateJobResponse>, StatusCode> {
121
+ let mut plan = dto.to_domain().map_err(|_| StatusCode::BAD_REQUEST)?;
122
+ // Route matrices and shadow variables must be ready before SolverForge
123
+ // starts construction, because the list-variable hooks read them directly.
124
+ prepare_plan(&mut plan)
125
+ .await
126
+ .map_err(status_from_routing_error)?;
127
+ let id = state
128
+ .solver
129
+ .start_job(plan)
130
+ .map_err(status_from_solver_error)?;
131
+ Ok(Json(CreateJobResponse { id }))
132
+ }
133
+
134
+ /// Returns the retained-job summary without requiring a snapshot payload.
135
+ async fn get_job(
136
+ State(state): State<Arc<AppState>>,
137
+ Path(id): Path<String>,
138
+ ) -> Result<Json<JobSummaryDto>, StatusCode> {
139
+ let job_id = parse_job_id(&id)?;
140
+ let status = state
141
+ .solver
142
+ .get_status(&id)
143
+ .map_err(status_from_solver_error)?;
144
+ Ok(Json(JobSummaryDto::from_status(job_id, &status)))
145
+ }
146
+
147
+ /// Stock alias used by the shared SolverForge UI job-status helpers.
148
+ async fn get_job_status(
149
+ State(state): State<Arc<AppState>>,
150
+ Path(id): Path<String>,
151
+ ) -> Result<Json<JobSummaryDto>, StatusCode> {
152
+ get_job(State(state), Path(id)).await
153
+ }
154
+
155
+ #[derive(Debug, Default, Deserialize)]
156
+ struct SnapshotQuery {
157
+ snapshot_revision: Option<u64>,
158
+ }
159
+
160
+ async fn get_snapshot(
161
+ State(state): State<Arc<AppState>>,
162
+ Path(id): Path<String>,
163
+ Query(query): Query<SnapshotQuery>,
164
+ ) -> Result<Json<JobSnapshotDto>, StatusCode> {
165
+ let snapshot = state
166
+ .solver
167
+ .get_snapshot(&id, query.snapshot_revision)
168
+ .map_err(status_from_solver_error)?;
169
+ Ok(Json(JobSnapshotDto::from_snapshot(&snapshot)))
170
+ }
171
+
172
+ /// Runs exact score analysis against a retained snapshot revision.
173
+ async fn analyze_by_id(
174
+ State(state): State<Arc<AppState>>,
175
+ Path(id): Path<String>,
176
+ Query(query): Query<SnapshotQuery>,
177
+ ) -> Result<Json<JobAnalysisDto>, StatusCode> {
178
+ let snapshot_analysis = state
179
+ .solver
180
+ .analyze_snapshot(&id, query.snapshot_revision)
181
+ .map_err(status_from_solver_error)?;
182
+ let analysis = analysis_response(&snapshot_analysis.analysis);
183
+ Ok(Json(JobAnalysisDto::from_snapshot_analysis(
184
+ &snapshot_analysis,
185
+ analysis,
186
+ )))
187
+ }
188
+
189
+ /// Builds route geometry for the exact retained snapshot the browser is viewing.
190
+ async fn get_routes(
191
+ State(state): State<Arc<AppState>>,
192
+ Path(id): Path<String>,
193
+ Query(query): Query<SnapshotQuery>,
194
+ ) -> Result<Json<JobRoutesDto>, StatusCode> {
195
+ let job_id = parse_job_id(&id)?;
196
+ let mut snapshot = state
197
+ .solver
198
+ .get_snapshot(&id, query.snapshot_revision)
199
+ .map_err(status_from_solver_error)?;
200
+ if snapshot
201
+ .solution
202
+ .vehicles
203
+ .iter()
204
+ .any(|vehicle| vehicle.prepared_routing.is_none())
205
+ {
206
+ // Older snapshots can be reconstructed from transport data. If the
207
+ // transient routing cache is absent, rebuild it before drawing routes.
208
+ prepare_plan(&mut snapshot.solution)
209
+ .await
210
+ .map_err(status_from_routing_error)?;
211
+ }
212
+ let routes = build_routes_snapshot(&snapshot.solution)
213
+ .await
214
+ .map_err(status_from_routing_error)?;
215
+ Ok(Json(JobRoutesDto::new(
216
+ job_id,
217
+ snapshot.snapshot_revision,
218
+ routes,
219
+ )))
220
+ }
221
+
222
+ /// Requests a runtime-managed pause at the next safe solver point.
223
+ async fn pause_job(
224
+ State(state): State<Arc<AppState>>,
225
+ Path(id): Path<String>,
226
+ ) -> Result<StatusCode, StatusCode> {
227
+ state.solver.pause(&id).map_err(status_from_solver_error)?;
228
+ Ok(StatusCode::ACCEPTED)
229
+ }
230
+
231
+ /// Resumes a paused retained job.
232
+ async fn resume_job(
233
+ State(state): State<Arc<AppState>>,
234
+ Path(id): Path<String>,
235
+ ) -> Result<StatusCode, StatusCode> {
236
+ state.solver.resume(&id).map_err(status_from_solver_error)?;
237
+ Ok(StatusCode::ACCEPTED)
238
+ }
239
+
240
+ /// Cancels a live or paused retained job without deleting its final snapshot.
241
+ async fn cancel_job(
242
+ State(state): State<Arc<AppState>>,
243
+ Path(id): Path<String>,
244
+ ) -> Result<StatusCode, StatusCode> {
245
+ state.solver.cancel(&id).map_err(status_from_solver_error)?;
246
+ Ok(StatusCode::ACCEPTED)
247
+ }
248
+
249
+ /// Deletes a terminal retained job and its cached SSE bootstrap state.
250
+ async fn delete_job(
251
+ State(state): State<Arc<AppState>>,
252
+ Path(id): Path<String>,
253
+ ) -> Result<StatusCode, StatusCode> {
254
+ state.solver.delete(&id).map_err(status_from_solver_error)?;
255
+ Ok(StatusCode::NO_CONTENT)
256
+ }
257
+
258
+ /// Ranks candidate vehicle/position insertions for one delivery.
259
+ async fn recommend_delivery_insertions(
260
+ Json(request): Json<DeliveryInsertionRequestDto>,
261
+ ) -> Result<Json<DeliveryInsertionResponseDto>, StatusCode> {
262
+ let mut plan = request
263
+ .plan
264
+ .to_domain()
265
+ .map_err(|_| StatusCode::BAD_REQUEST)?;
266
+ if request.delivery_id >= plan.deliveries.len() {
267
+ return Err(StatusCode::BAD_REQUEST);
268
+ }
269
+ // Candidate scoring uses the same prepared data as real solving so the
270
+ // modal preview matches the constraints and route metrics.
271
+ prepare_plan(&mut plan)
272
+ .await
273
+ .map_err(status_from_routing_error)?;
274
+ let candidates = rank_delivery_insertions(
275
+ &plan,
276
+ request.delivery_id,
277
+ request.limit.unwrap_or(8).min(24),
278
+ )
279
+ .await
280
+ .map_err(status_from_routing_error)?
281
+ .into_iter()
282
+ .map(DeliveryInsertionCandidateDto::from_candidate)
283
+ .collect();
284
+ Ok(Json(DeliveryInsertionResponseDto {
285
+ delivery_id: request.delivery_id,
286
+ candidates,
287
+ }))
288
+ }
src/api/sse.rs ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Server-sent events for retained delivery solve jobs.
2
+ //!
3
+ //! A browser may connect after a job has already started. The stream therefore
4
+ //! sends one bootstrap status first, then forwards live events from the
5
+ //! retained job broadcaster.
6
+
7
+ use axum::{
8
+ body::Body,
9
+ extract::{Path, State},
10
+ http::{header, StatusCode},
11
+ response::Response,
12
+ };
13
+ use std::sync::Arc;
14
+ use tokio_stream::wrappers::BroadcastStream;
15
+ use tokio_stream::StreamExt;
16
+
17
+ use super::routes::AppState;
18
+
19
+ pub async fn events(
20
+ State(state): State<Arc<AppState>>,
21
+ Path(id): Path<String>,
22
+ ) -> Result<Response<Body>, StatusCode> {
23
+ let rx = state.solver.subscribe(&id).ok_or(StatusCode::NOT_FOUND)?;
24
+ let bootstrap_json = state
25
+ .solver
26
+ .bootstrap_event(&id)
27
+ .map_err(|_| StatusCode::NOT_FOUND)?;
28
+ let bootstrap = tokio_stream::iter(std::iter::once(Ok::<_, std::convert::Infallible>(
29
+ format!("data: {}\n\n", bootstrap_json).into_bytes(),
30
+ )));
31
+
32
+ let live = BroadcastStream::new(rx).filter_map(|msg| match msg {
33
+ Ok(json) => Some(Ok::<_, std::convert::Infallible>(
34
+ format!("data: {}\n\n", json).into_bytes(),
35
+ )),
36
+ // Broadcast channels can report that a slow browser missed events. The
37
+ // next retained snapshot/status request is still authoritative, so the
38
+ // stream drops that gap instead of failing the connection.
39
+ Err(_) => None,
40
+ });
41
+
42
+ let stream = bootstrap.chain(live);
43
+
44
+ Ok(Response::builder()
45
+ .header(header::CONTENT_TYPE, "text/event-stream")
46
+ .header(header::CACHE_CONTROL, "no-cache")
47
+ .header("X-Accel-Buffering", "no")
48
+ .body(Body::from_stream(stream))
49
+ .unwrap())
50
+ }
src/constraints/all_deliveries_assigned.rs ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::domain::{
2
+ Delivery, Plan, PlanConstraintStreams, Vehicle, UNASSIGNED_DELIVERY_HARD_PENALTY,
3
+ };
4
+ use solverforge::prelude::*;
5
+ use solverforge::stream::joiner::equal_bi;
6
+ use solverforge::IncrementalConstraint;
7
+
8
+ /// HARD: every delivery must appear in some vehicle route.
9
+ pub fn constraint() -> impl IncrementalConstraint<Plan, HardSoftScore> {
10
+ ConstraintFactory::<Plan, HardSoftScore>::new()
11
+ .deliveries()
12
+ // The right side flattens every vehicle route into assigned delivery
13
+ // ids. A delivery that does not exist in that flattened stream is
14
+ // unassigned and receives the dominant hard penalty.
15
+ .if_not_exists((
16
+ ConstraintFactory::<Plan, HardSoftScore>::new()
17
+ .vehicles()
18
+ .flattened(|vehicle: &Vehicle| &vehicle.delivery_order),
19
+ equal_bi(
20
+ |delivery: &Delivery| delivery.id,
21
+ |assigned: &usize| *assigned,
22
+ ),
23
+ ))
24
+ .penalize(hard_weight(|_: &Delivery| {
25
+ HardSoftScore::of(UNASSIGNED_DELIVERY_HARD_PENALTY, 0)
26
+ }))
27
+ .named("All Deliveries Assigned")
28
+ }
src/constraints/delivery_time_windows.rs ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::domain::{Plan, PlanConstraintStreams, Vehicle};
2
+ use solverforge::prelude::*;
3
+ use solverforge::IncrementalConstraint;
4
+
5
+ /// HARD: each vehicle route must respect delivery time windows.
6
+ pub fn constraint() -> impl IncrementalConstraint<Plan, HardSoftScore> {
7
+ ConstraintFactory::<Plan, HardSoftScore>::new()
8
+ .vehicles()
9
+ // Time-window work is precomputed as a vehicle shadow value, so this
10
+ // rule can stay incremental and read one scalar per changed route.
11
+ .filter(|vehicle: &Vehicle| vehicle.time_window_violation_seconds() > 0)
12
+ .penalize(hard_weight(|vehicle: &Vehicle| {
13
+ HardSoftScore::of(vehicle.time_window_violation_seconds(), 0)
14
+ }))
15
+ .named("Delivery Time Windows")
16
+ }
src/constraints/mod.rs ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #![cfg_attr(rustfmt, rustfmt_skip)]
2
+ //! Constraint assembly for delivery routing.
3
+ //!
4
+ //! Each sibling file contributes one named rule. `create_constraints()` lists
5
+ //! them in the order we want beginners to see in score analysis output.
6
+
7
+ use crate::domain::Plan;
8
+ use solverforge::prelude::*;
9
+
10
+ pub use self::assemble::create_constraints;
11
+
12
+ // @solverforge:begin constraint-modules
13
+ mod all_deliveries_assigned;
14
+ mod vehicle_capacity;
15
+ mod delivery_time_windows;
16
+ mod total_travel_time;
17
+ // @solverforge:end constraint-modules
18
+
19
+ mod assemble {
20
+ use super::*;
21
+
22
+ /// Collects the full scoring model used by `Plan`.
23
+ pub fn create_constraints() -> impl ConstraintSet<Plan, HardSoftScore> {
24
+ // @solverforge:begin constraint-calls
25
+ (
26
+ all_deliveries_assigned::constraint(),
27
+ vehicle_capacity::constraint(),
28
+ delivery_time_windows::constraint(),
29
+ total_travel_time::constraint(),
30
+ )
31
+ // @solverforge:end constraint-calls
32
+ }
33
+ }
src/constraints/total_travel_time.rs ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::domain::{Plan, PlanConstraintStreams, Vehicle};
2
+ use solverforge::prelude::*;
3
+ use solverforge::IncrementalConstraint;
4
+
5
+ /// SOFT: prefer less total travel time across all routes.
6
+ pub fn constraint() -> impl IncrementalConstraint<Plan, HardSoftScore> {
7
+ ConstraintFactory::<Plan, HardSoftScore>::new()
8
+ .vehicles()
9
+ .penalize(|vehicle: &Vehicle| HardSoftScore::of(0, vehicle.total_travel_seconds()))
10
+ .named("Total Travel Time")
11
+ }
src/constraints/vehicle_capacity.rs ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::domain::{Plan, PlanConstraintStreams, Vehicle};
2
+ use solverforge::prelude::*;
3
+ use solverforge::IncrementalConstraint;
4
+
5
+ /// HARD: a vehicle's assigned demand cannot exceed its capacity.
6
+ pub fn constraint() -> impl IncrementalConstraint<Plan, HardSoftScore> {
7
+ ConstraintFactory::<Plan, HardSoftScore>::new()
8
+ .vehicles()
9
+ // Capacity overage is also a route shadow value. SolverForge updates it
10
+ // after list moves, and this constraint only scores positive excess.
11
+ .filter(|vehicle: &Vehicle| vehicle.capacity_overage() > 0)
12
+ .penalize(hard_weight(|vehicle: &Vehicle| {
13
+ HardSoftScore::of(vehicle.capacity_overage(), 0)
14
+ }))
15
+ .named("Vehicle Capacity")
16
+ }
src/data/data_seed.rs ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Deterministic delivery demo-data modules.
2
+ //!
3
+ //! `entrypoints` owns the public dataset ids, while each city module owns its
4
+ //! depots and stops. The solver receives ordinary `Plan` values; there is no
5
+ //! hidden runtime data source behind these seeds.
6
+
7
+ mod entrypoints;
8
+ mod firenze;
9
+ mod hartford;
10
+ mod philadelphia;
11
+ mod types;
12
+
13
+ pub use entrypoints::{generate, DemoData};
14
+
15
+ #[cfg(test)]
16
+ mod tests;
src/data/data_seed/entrypoints.rs ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Public dataset ids and generator dispatch for delivery demos.
2
+ //!
3
+ //! City-specific modules only contain depots and visit groups. This file turns
4
+ //! those static fixtures into normalized `Plan` values with vehicles, delivery
5
+ //! ids, and deterministic service windows.
6
+
7
+ use std::str::FromStr;
8
+
9
+ use rand::rngs::StdRng;
10
+ use rand::{RngExt, SeedableRng};
11
+
12
+ use super::types::{LocationData, VEHICLE_NAMES};
13
+ use super::{firenze, hartford, philadelphia};
14
+ use crate::domain::{Delivery, Plan, Vehicle};
15
+
16
+ #[derive(Debug, Clone, Copy)]
17
+ pub enum DemoData {
18
+ Philadelphia,
19
+ Hartford,
20
+ Firenze,
21
+ }
22
+
23
+ impl DemoData {
24
+ pub fn id(self) -> &'static str {
25
+ match self {
26
+ DemoData::Philadelphia => "PHILADELPHIA",
27
+ DemoData::Hartford => "HARTFORD",
28
+ DemoData::Firenze => "FIRENZE",
29
+ }
30
+ }
31
+
32
+ pub fn label(self) -> &'static str {
33
+ match self {
34
+ DemoData::Philadelphia => "Philadelphia",
35
+ DemoData::Hartford => "Hartford",
36
+ DemoData::Firenze => "Firenze",
37
+ }
38
+ }
39
+ }
40
+
41
+ impl FromStr for DemoData {
42
+ type Err = ();
43
+
44
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
45
+ match s.trim().to_uppercase().as_str() {
46
+ "PHILADELPHIA" => Ok(DemoData::Philadelphia),
47
+ "HARTFORD" => Ok(DemoData::Hartford),
48
+ "FIRENZE" => Ok(DemoData::Firenze),
49
+ _ => Err(()),
50
+ }
51
+ }
52
+ }
53
+
54
+ pub fn generate(demo: DemoData) -> Plan {
55
+ match demo {
56
+ DemoData::Philadelphia => generate_demo_data(
57
+ demo,
58
+ 0,
59
+ philadelphia::DEPOTS,
60
+ philadelphia::VISIT_GROUPS,
61
+ 6 * 3600,
62
+ 36,
63
+ 48,
64
+ ),
65
+ DemoData::Hartford => generate_demo_data(
66
+ demo,
67
+ 1,
68
+ hartford::DEPOTS,
69
+ hartford::VISIT_GROUPS,
70
+ 6 * 3600,
71
+ 24,
72
+ 34,
73
+ ),
74
+ DemoData::Firenze => generate_demo_data(
75
+ demo,
76
+ 2,
77
+ firenze::DEPOTS,
78
+ firenze::VISIT_GROUPS,
79
+ 6 * 3600,
80
+ 38,
81
+ 52,
82
+ ),
83
+ }
84
+ }
85
+
86
+ /// Builds one city plan from static stops and deterministic vehicle settings.
87
+ fn generate_demo_data(
88
+ demo: DemoData,
89
+ seed: u64,
90
+ depots: &[LocationData],
91
+ stop_groups: &[&[LocationData]],
92
+ departure_time: i64,
93
+ min_capacity: i32,
94
+ max_capacity: i32,
95
+ ) -> Plan {
96
+ let mut rng = StdRng::seed_from_u64(seed);
97
+ let vehicles = depots
98
+ .iter()
99
+ .enumerate()
100
+ .map(|(idx, depot)| {
101
+ Vehicle::new(
102
+ idx,
103
+ VEHICLE_NAMES[idx % VEHICLE_NAMES.len()],
104
+ rng.random_range(min_capacity..=max_capacity),
105
+ depot.lat,
106
+ depot.lng,
107
+ departure_time,
108
+ )
109
+ })
110
+ .collect::<Vec<_>>();
111
+
112
+ let deliveries = stop_groups
113
+ .iter()
114
+ .flat_map(|stops| stops.iter())
115
+ .enumerate()
116
+ .map(|(idx, location)| {
117
+ let (kind, min_start_time, max_end_time, demand_range, service_range) =
118
+ location.customer_type.profile();
119
+ Delivery::new(
120
+ idx,
121
+ location.name,
122
+ kind,
123
+ (location.lat, location.lng),
124
+ rng.random_range(demand_range.0..=demand_range.1),
125
+ (min_start_time, max_end_time),
126
+ rng.random_range(service_range.0..=service_range.1),
127
+ )
128
+ })
129
+ .collect::<Vec<_>>();
130
+
131
+ Plan::new(demo.label(), deliveries, vehicles)
132
+ }
src/data/data_seed/firenze.rs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ use super::types::LocationData;
2
+
3
+ mod depots;
4
+ mod visits;
5
+ mod visits_extra;
6
+
7
+ pub(super) use depots::DEPOTS;
8
+ pub(super) const VISIT_GROUPS: &[&[LocationData]] = &[visits::VISITS, visits_extra::VISITS];
src/data/data_seed/firenze/depots.rs ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::super::types::{CustomerType, LocationData};
2
+
3
+ pub(in crate::data::data_seed) const DEPOTS: &[LocationData] = &[
4
+ LocationData {
5
+ name: "Centro Storico Depot",
6
+ lat: 43.7696,
7
+ lng: 11.2558,
8
+ customer_type: CustomerType::Business,
9
+ },
10
+ LocationData {
11
+ name: "Santa Maria Novella Depot",
12
+ lat: 43.7745,
13
+ lng: 11.2487,
14
+ customer_type: CustomerType::Business,
15
+ },
16
+ LocationData {
17
+ name: "Campo di Marte Depot",
18
+ lat: 43.7820,
19
+ lng: 11.2820,
20
+ customer_type: CustomerType::Business,
21
+ },
22
+ LocationData {
23
+ name: "Rifredi Depot",
24
+ lat: 43.7950,
25
+ lng: 11.2410,
26
+ customer_type: CustomerType::Business,
27
+ },
28
+ LocationData {
29
+ name: "Novoli Depot",
30
+ lat: 43.7880,
31
+ lng: 11.2220,
32
+ customer_type: CustomerType::Business,
33
+ },
34
+ LocationData {
35
+ name: "Gavinana Depot",
36
+ lat: 43.7520,
37
+ lng: 11.2680,
38
+ customer_type: CustomerType::Business,
39
+ },
40
+ LocationData {
41
+ name: "Mercato Centrale Depot",
42
+ lat: 43.7762,
43
+ lng: 11.2540,
44
+ customer_type: CustomerType::Business,
45
+ },
46
+ LocationData {
47
+ name: "Santa Croce Depot",
48
+ lat: 43.7688,
49
+ lng: 11.2620,
50
+ customer_type: CustomerType::Business,
51
+ },
52
+ LocationData {
53
+ name: "Santo Spirito Depot",
54
+ lat: 43.7665,
55
+ lng: 11.2470,
56
+ customer_type: CustomerType::Business,
57
+ },
58
+ LocationData {
59
+ name: "Careggi Depot",
60
+ lat: 43.8020,
61
+ lng: 11.2530,
62
+ customer_type: CustomerType::Business,
63
+ },
64
+ ];
src/data/data_seed/firenze/visits.rs ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::super::types::{CustomerType, LocationData};
2
+
3
+ pub(in crate::data::data_seed) const VISITS: &[LocationData] = &[
4
+ LocationData {
5
+ name: "Trattoria Mario",
6
+ lat: 43.7762,
7
+ lng: 11.2540,
8
+ customer_type: CustomerType::Restaurant,
9
+ },
10
+ LocationData {
11
+ name: "Buca Mario",
12
+ lat: 43.7698,
13
+ lng: 11.2505,
14
+ customer_type: CustomerType::Restaurant,
15
+ },
16
+ LocationData {
17
+ name: "Il Latini",
18
+ lat: 43.7705,
19
+ lng: 11.2495,
20
+ customer_type: CustomerType::Restaurant,
21
+ },
22
+ LocationData {
23
+ name: "Osteria dell'Enoteca",
24
+ lat: 43.7680,
25
+ lng: 11.2545,
26
+ customer_type: CustomerType::Restaurant,
27
+ },
28
+ LocationData {
29
+ name: "Trattoria Sostanza",
30
+ lat: 43.7735,
31
+ lng: 11.2470,
32
+ customer_type: CustomerType::Restaurant,
33
+ },
34
+ LocationData {
35
+ name: "All'Antico Vinaio",
36
+ lat: 43.7690,
37
+ lng: 11.2570,
38
+ customer_type: CustomerType::Restaurant,
39
+ },
40
+ LocationData {
41
+ name: "Mercato Centrale",
42
+ lat: 43.7762,
43
+ lng: 11.2540,
44
+ customer_type: CustomerType::Restaurant,
45
+ },
46
+ LocationData {
47
+ name: "Cibreo",
48
+ lat: 43.7702,
49
+ lng: 11.2670,
50
+ customer_type: CustomerType::Restaurant,
51
+ },
52
+ LocationData {
53
+ name: "Ora d'Aria",
54
+ lat: 43.7710,
55
+ lng: 11.2610,
56
+ customer_type: CustomerType::Restaurant,
57
+ },
58
+ LocationData {
59
+ name: "Buca Lapi",
60
+ lat: 43.7720,
61
+ lng: 11.2535,
62
+ customer_type: CustomerType::Restaurant,
63
+ },
64
+ LocationData {
65
+ name: "Il Palagio",
66
+ lat: 43.7680,
67
+ lng: 11.2550,
68
+ customer_type: CustomerType::Restaurant,
69
+ },
70
+ LocationData {
71
+ name: "Enoteca Pinchiorri",
72
+ lat: 43.7695,
73
+ lng: 11.2620,
74
+ customer_type: CustomerType::Restaurant,
75
+ },
76
+ LocationData {
77
+ name: "La Giostra",
78
+ lat: 43.7745,
79
+ lng: 11.2650,
80
+ customer_type: CustomerType::Restaurant,
81
+ },
82
+ LocationData {
83
+ name: "Fishing Lab",
84
+ lat: 43.7693,
85
+ lng: 11.2563,
86
+ customer_type: CustomerType::Restaurant,
87
+ },
88
+ LocationData {
89
+ name: "Trattoria Cammillo",
90
+ lat: 43.7665,
91
+ lng: 11.2520,
92
+ customer_type: CustomerType::Restaurant,
93
+ },
94
+ LocationData {
95
+ name: "Palazzo Vecchio",
96
+ lat: 43.7693,
97
+ lng: 11.2563,
98
+ customer_type: CustomerType::Business,
99
+ },
100
+ LocationData {
101
+ name: "Uffizi Gallery",
102
+ lat: 43.7677,
103
+ lng: 11.2553,
104
+ customer_type: CustomerType::Business,
105
+ },
106
+ LocationData {
107
+ name: "Gucci Garden",
108
+ lat: 43.7692,
109
+ lng: 11.2556,
110
+ customer_type: CustomerType::Business,
111
+ },
112
+ LocationData {
113
+ name: "Ferragamo Museum",
114
+ lat: 43.7700,
115
+ lng: 11.2530,
116
+ customer_type: CustomerType::Business,
117
+ },
118
+ LocationData {
119
+ name: "Ospedale Santa Maria",
120
+ lat: 43.7830,
121
+ lng: 11.2690,
122
+ customer_type: CustomerType::Business,
123
+ },
124
+ LocationData {
125
+ name: "Universita degli Studi",
126
+ lat: 43.7765,
127
+ lng: 11.2555,
128
+ customer_type: CustomerType::Business,
129
+ },
130
+ LocationData {
131
+ name: "Palazzo Strozzi",
132
+ lat: 43.7706,
133
+ lng: 11.2515,
134
+ customer_type: CustomerType::Business,
135
+ },
136
+ LocationData {
137
+ name: "Biblioteca Nazionale",
138
+ lat: 43.7660,
139
+ lng: 11.2650,
140
+ customer_type: CustomerType::Business,
141
+ },
142
+ LocationData {
143
+ name: "Teatro del Maggio",
144
+ lat: 43.7780,
145
+ lng: 11.2470,
146
+ customer_type: CustomerType::Business,
147
+ },
148
+ LocationData {
149
+ name: "Palazzo Pitti",
150
+ lat: 43.7665,
151
+ lng: 11.2470,
152
+ customer_type: CustomerType::Business,
153
+ },
154
+ LocationData {
155
+ name: "Accademia Gallery",
156
+ lat: 43.7768,
157
+ lng: 11.2590,
158
+ customer_type: CustomerType::Business,
159
+ },
160
+ LocationData {
161
+ name: "Ospedale Meyer",
162
+ lat: 43.7910,
163
+ lng: 11.2520,
164
+ customer_type: CustomerType::Business,
165
+ },
166
+ LocationData {
167
+ name: "Polo Universitario",
168
+ lat: 43.7920,
169
+ lng: 11.2180,
170
+ customer_type: CustomerType::Business,
171
+ },
172
+ LocationData {
173
+ name: "Santo Spirito",
174
+ lat: 43.7665,
175
+ lng: 11.2470,
176
+ customer_type: CustomerType::Residential,
177
+ },
178
+ LocationData {
179
+ name: "San Frediano",
180
+ lat: 43.7680,
181
+ lng: 11.2420,
182
+ customer_type: CustomerType::Residential,
183
+ },
184
+ LocationData {
185
+ name: "Santa Croce",
186
+ lat: 43.7688,
187
+ lng: 11.2620,
188
+ customer_type: CustomerType::Residential,
189
+ },
190
+ LocationData {
191
+ name: "San Lorenzo",
192
+ lat: 43.7755,
193
+ lng: 11.2540,
194
+ customer_type: CustomerType::Residential,
195
+ },
196
+ LocationData {
197
+ name: "San Marco",
198
+ lat: 43.7768,
199
+ lng: 11.2590,
200
+ customer_type: CustomerType::Residential,
201
+ },
202
+ LocationData {
203
+ name: "Sant'Ambrogio",
204
+ lat: 43.7688,
205
+ lng: 11.2620,
206
+ customer_type: CustomerType::Residential,
207
+ },
208
+ LocationData {
209
+ name: "Campo di Marte",
210
+ lat: 43.7820,
211
+ lng: 11.2820,
212
+ customer_type: CustomerType::Residential,
213
+ },
214
+ LocationData {
215
+ name: "Novoli",
216
+ lat: 43.7880,
217
+ lng: 11.2220,
218
+ customer_type: CustomerType::Residential,
219
+ },
220
+ LocationData {
221
+ name: "Rifredi",
222
+ lat: 43.7950,
223
+ lng: 11.2410,
224
+ customer_type: CustomerType::Residential,
225
+ },
226
+ LocationData {
227
+ name: "Le Cure",
228
+ lat: 43.7890,
229
+ lng: 11.2580,
230
+ customer_type: CustomerType::Residential,
231
+ },
232
+ LocationData {
233
+ name: "Careggi",
234
+ lat: 43.8020,
235
+ lng: 11.2530,
236
+ customer_type: CustomerType::Residential,
237
+ },
238
+ LocationData {
239
+ name: "Peretola",
240
+ lat: 43.7960,
241
+ lng: 11.2050,
242
+ customer_type: CustomerType::Residential,
243
+ },
244
+ LocationData {
245
+ name: "Isolotto",
246
+ lat: 43.7620,
247
+ lng: 11.2200,
248
+ customer_type: CustomerType::Residential,
249
+ },
250
+ LocationData {
251
+ name: "Gavinana",
252
+ lat: 43.7520,
253
+ lng: 11.2680,
254
+ customer_type: CustomerType::Residential,
255
+ },
256
+ LocationData {
257
+ name: "Galluzzo",
258
+ lat: 43.7400,
259
+ lng: 11.2480,
260
+ customer_type: CustomerType::Residential,
261
+ },
262
+ LocationData {
263
+ name: "Porta Romana",
264
+ lat: 43.7610,
265
+ lng: 11.2560,
266
+ customer_type: CustomerType::Residential,
267
+ },
268
+ LocationData {
269
+ name: "Bellosguardo",
270
+ lat: 43.7650,
271
+ lng: 11.2350,
272
+ customer_type: CustomerType::Residential,
273
+ },
274
+ LocationData {
275
+ name: "Arcetri",
276
+ lat: 43.7500,
277
+ lng: 11.2530,
278
+ customer_type: CustomerType::Residential,
279
+ },
280
+ LocationData {
281
+ name: "Fiesole",
282
+ lat: 43.8055,
283
+ lng: 11.2935,
284
+ customer_type: CustomerType::Residential,
285
+ },
286
+ LocationData {
287
+ name: "Settignano",
288
+ lat: 43.7850,
289
+ lng: 11.3100,
290
+ customer_type: CustomerType::Residential,
291
+ },
292
+ ];
src/data/data_seed/firenze/visits_extra.rs ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::super::types::{CustomerType, LocationData};
2
+
3
+ pub(in crate::data::data_seed) const VISITS: &[LocationData] = &[
4
+ LocationData {
5
+ name: "Mercato di Sant'Ambrogio",
6
+ lat: 43.7688,
7
+ lng: 11.2620,
8
+ customer_type: CustomerType::Restaurant,
9
+ },
10
+ LocationData {
11
+ name: "Procacci",
12
+ lat: 43.7706,
13
+ lng: 11.2515,
14
+ customer_type: CustomerType::Restaurant,
15
+ },
16
+ LocationData {
17
+ name: "La Menagere",
18
+ lat: 43.7755,
19
+ lng: 11.2540,
20
+ customer_type: CustomerType::Restaurant,
21
+ },
22
+ LocationData {
23
+ name: "Rivoire",
24
+ lat: 43.7693,
25
+ lng: 11.2563,
26
+ customer_type: CustomerType::Restaurant,
27
+ },
28
+ LocationData {
29
+ name: "Gelateria dei Neri",
30
+ lat: 43.7688,
31
+ lng: 11.2620,
32
+ customer_type: CustomerType::Restaurant,
33
+ },
34
+ LocationData {
35
+ name: "Trattoria Za Za",
36
+ lat: 43.7762,
37
+ lng: 11.2540,
38
+ customer_type: CustomerType::Restaurant,
39
+ },
40
+ LocationData {
41
+ name: "Il Santo Bevitore",
42
+ lat: 43.7680,
43
+ lng: 11.2420,
44
+ customer_type: CustomerType::Restaurant,
45
+ },
46
+ LocationData {
47
+ name: "Da Ruggero",
48
+ lat: 43.7610,
49
+ lng: 11.2560,
50
+ customer_type: CustomerType::Restaurant,
51
+ },
52
+ LocationData {
53
+ name: "Perseus",
54
+ lat: 43.7890,
55
+ lng: 11.2580,
56
+ customer_type: CustomerType::Restaurant,
57
+ },
58
+ LocationData {
59
+ name: "Coquinarius",
60
+ lat: 43.7720,
61
+ lng: 11.2535,
62
+ customer_type: CustomerType::Restaurant,
63
+ },
64
+ LocationData {
65
+ name: "Santa Maria del Fiore",
66
+ lat: 43.7755,
67
+ lng: 11.2540,
68
+ customer_type: CustomerType::Business,
69
+ },
70
+ LocationData {
71
+ name: "Stazione Leopolda",
72
+ lat: 43.7780,
73
+ lng: 11.2470,
74
+ customer_type: CustomerType::Business,
75
+ },
76
+ LocationData {
77
+ name: "Fortezza da Basso",
78
+ lat: 43.7762,
79
+ lng: 11.2540,
80
+ customer_type: CustomerType::Business,
81
+ },
82
+ LocationData {
83
+ name: "Palazzo Medici Riccardi",
84
+ lat: 43.7765,
85
+ lng: 11.2555,
86
+ customer_type: CustomerType::Business,
87
+ },
88
+ LocationData {
89
+ name: "San Lorenzo Market",
90
+ lat: 43.7762,
91
+ lng: 11.2540,
92
+ customer_type: CustomerType::Business,
93
+ },
94
+ LocationData {
95
+ name: "Santa Maria Novella",
96
+ lat: 43.7745,
97
+ lng: 11.2487,
98
+ customer_type: CustomerType::Business,
99
+ },
100
+ LocationData {
101
+ name: "Boboli Gardens Office",
102
+ lat: 43.7665,
103
+ lng: 11.2470,
104
+ customer_type: CustomerType::Business,
105
+ },
106
+ LocationData {
107
+ name: "Villa Bardini",
108
+ lat: 43.7660,
109
+ lng: 11.2650,
110
+ customer_type: CustomerType::Business,
111
+ },
112
+ LocationData {
113
+ name: "Careggi Hospital",
114
+ lat: 43.8020,
115
+ lng: 11.2530,
116
+ customer_type: CustomerType::Business,
117
+ },
118
+ LocationData {
119
+ name: "Coverciano Offices",
120
+ lat: 43.7850,
121
+ lng: 11.3100,
122
+ customer_type: CustomerType::Business,
123
+ },
124
+ LocationData {
125
+ name: "Firenze Airport Cargo",
126
+ lat: 43.7960,
127
+ lng: 11.2050,
128
+ customer_type: CustomerType::Business,
129
+ },
130
+ LocationData {
131
+ name: "Statuto",
132
+ lat: 43.7890,
133
+ lng: 11.2580,
134
+ customer_type: CustomerType::Residential,
135
+ },
136
+ LocationData {
137
+ name: "Piazza Beccaria",
138
+ lat: 43.7688,
139
+ lng: 11.2620,
140
+ customer_type: CustomerType::Residential,
141
+ },
142
+ LocationData {
143
+ name: "Coverciano",
144
+ lat: 43.7850,
145
+ lng: 11.3100,
146
+ customer_type: CustomerType::Residential,
147
+ },
148
+ LocationData {
149
+ name: "Campo di Marte East",
150
+ lat: 43.7820,
151
+ lng: 11.2820,
152
+ customer_type: CustomerType::Residential,
153
+ },
154
+ LocationData {
155
+ name: "Novoli South",
156
+ lat: 43.7880,
157
+ lng: 11.2220,
158
+ customer_type: CustomerType::Residential,
159
+ },
160
+ LocationData {
161
+ name: "Rifredi South",
162
+ lat: 43.7950,
163
+ lng: 11.2410,
164
+ customer_type: CustomerType::Residential,
165
+ },
166
+ LocationData {
167
+ name: "Careggi North",
168
+ lat: 43.8020,
169
+ lng: 11.2530,
170
+ customer_type: CustomerType::Residential,
171
+ },
172
+ LocationData {
173
+ name: "Isolotto South",
174
+ lat: 43.7620,
175
+ lng: 11.2200,
176
+ customer_type: CustomerType::Residential,
177
+ },
178
+ LocationData {
179
+ name: "Gavinana South",
180
+ lat: 43.7520,
181
+ lng: 11.2680,
182
+ customer_type: CustomerType::Residential,
183
+ },
184
+ LocationData {
185
+ name: "Galluzzo Center",
186
+ lat: 43.7400,
187
+ lng: 11.2480,
188
+ customer_type: CustomerType::Residential,
189
+ },
190
+ LocationData {
191
+ name: "Arcetri Hill",
192
+ lat: 43.7500,
193
+ lng: 11.2530,
194
+ customer_type: CustomerType::Residential,
195
+ },
196
+ ];
src/data/data_seed/hartford.rs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ use super::types::LocationData;
2
+
3
+ mod depots;
4
+ mod visits;
5
+ mod visits_extra;
6
+
7
+ pub(super) use depots::DEPOTS;
8
+ pub(super) const VISIT_GROUPS: &[&[LocationData]] = &[visits::VISITS, visits_extra::VISITS];
src/data/data_seed/hartford/depots.rs ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::super::types::{CustomerType, LocationData};
2
+
3
+ pub(in crate::data::data_seed) const DEPOTS: &[LocationData] = &[
4
+ LocationData {
5
+ name: "Downtown Hartford Depot",
6
+ lat: 41.7658,
7
+ lng: -72.6734,
8
+ customer_type: CustomerType::Business,
9
+ },
10
+ LocationData {
11
+ name: "Asylum Hill Depot",
12
+ lat: 41.7700,
13
+ lng: -72.6900,
14
+ customer_type: CustomerType::Business,
15
+ },
16
+ LocationData {
17
+ name: "South End Depot",
18
+ lat: 41.7400,
19
+ lng: -72.6750,
20
+ customer_type: CustomerType::Business,
21
+ },
22
+ LocationData {
23
+ name: "West End Depot",
24
+ lat: 41.7680,
25
+ lng: -72.7100,
26
+ customer_type: CustomerType::Business,
27
+ },
28
+ LocationData {
29
+ name: "Barry Square Depot",
30
+ lat: 41.7450,
31
+ lng: -72.6800,
32
+ customer_type: CustomerType::Business,
33
+ },
34
+ LocationData {
35
+ name: "Clay Arsenal Depot",
36
+ lat: 41.7750,
37
+ lng: -72.6850,
38
+ customer_type: CustomerType::Business,
39
+ },
40
+ LocationData {
41
+ name: "Science Center Depot",
42
+ lat: 41.7650,
43
+ lng: -72.6695,
44
+ customer_type: CustomerType::Business,
45
+ },
46
+ LocationData {
47
+ name: "Frog Hollow Depot",
48
+ lat: 41.7580,
49
+ lng: -72.6900,
50
+ customer_type: CustomerType::Business,
51
+ },
52
+ LocationData {
53
+ name: "Blue Hills Depot",
54
+ lat: 41.7850,
55
+ lng: -72.7050,
56
+ customer_type: CustomerType::Business,
57
+ },
58
+ LocationData {
59
+ name: "Charter Oak Depot",
60
+ lat: 41.7495,
61
+ lng: -72.6650,
62
+ customer_type: CustomerType::Business,
63
+ },
64
+ ];
src/data/data_seed/hartford/visits.rs ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::super::types::{CustomerType, LocationData};
2
+
3
+ pub(in crate::data::data_seed) const VISITS: &[LocationData] = &[
4
+ LocationData {
5
+ name: "Max Downtown",
6
+ lat: 41.7670,
7
+ lng: -72.6730,
8
+ customer_type: CustomerType::Restaurant,
9
+ },
10
+ LocationData {
11
+ name: "Trumbull Kitchen",
12
+ lat: 41.7650,
13
+ lng: -72.6750,
14
+ customer_type: CustomerType::Restaurant,
15
+ },
16
+ LocationData {
17
+ name: "Salute",
18
+ lat: 41.7630,
19
+ lng: -72.6740,
20
+ customer_type: CustomerType::Restaurant,
21
+ },
22
+ LocationData {
23
+ name: "Peppercorns Grill",
24
+ lat: 41.7680,
25
+ lng: -72.6700,
26
+ customer_type: CustomerType::Restaurant,
27
+ },
28
+ LocationData {
29
+ name: "Feng Asian Bistro",
30
+ lat: 41.7640,
31
+ lng: -72.6725,
32
+ customer_type: CustomerType::Restaurant,
33
+ },
34
+ LocationData {
35
+ name: "On20",
36
+ lat: 41.7655,
37
+ lng: -72.6728,
38
+ customer_type: CustomerType::Restaurant,
39
+ },
40
+ LocationData {
41
+ name: "First and Last Tavern",
42
+ lat: 41.7620,
43
+ lng: -72.7050,
44
+ customer_type: CustomerType::Restaurant,
45
+ },
46
+ LocationData {
47
+ name: "Agave Grill",
48
+ lat: 41.7580,
49
+ lng: -72.6820,
50
+ customer_type: CustomerType::Restaurant,
51
+ },
52
+ LocationData {
53
+ name: "Bear's Smokehouse",
54
+ lat: 41.7550,
55
+ lng: -72.6780,
56
+ customer_type: CustomerType::Restaurant,
57
+ },
58
+ LocationData {
59
+ name: "City Steam Brewery",
60
+ lat: 41.7630,
61
+ lng: -72.6750,
62
+ customer_type: CustomerType::Restaurant,
63
+ },
64
+ LocationData {
65
+ name: "Travelers Tower",
66
+ lat: 41.7658,
67
+ lng: -72.6734,
68
+ customer_type: CustomerType::Business,
69
+ },
70
+ LocationData {
71
+ name: "Hartford Steam Boiler",
72
+ lat: 41.7680,
73
+ lng: -72.6700,
74
+ customer_type: CustomerType::Business,
75
+ },
76
+ LocationData {
77
+ name: "Aetna Building",
78
+ lat: 41.7700,
79
+ lng: -72.6900,
80
+ customer_type: CustomerType::Business,
81
+ },
82
+ LocationData {
83
+ name: "Connecticut Convention Center",
84
+ lat: 41.7615,
85
+ lng: -72.6820,
86
+ customer_type: CustomerType::Business,
87
+ },
88
+ LocationData {
89
+ name: "Hartford Hospital",
90
+ lat: 41.7547,
91
+ lng: -72.6858,
92
+ customer_type: CustomerType::Business,
93
+ },
94
+ LocationData {
95
+ name: "Connecticut Children's",
96
+ lat: 41.7560,
97
+ lng: -72.6850,
98
+ customer_type: CustomerType::Business,
99
+ },
100
+ LocationData {
101
+ name: "Trinity College",
102
+ lat: 41.7580,
103
+ lng: -72.6900,
104
+ customer_type: CustomerType::Business,
105
+ },
106
+ LocationData {
107
+ name: "Connecticut Science Center",
108
+ lat: 41.7650,
109
+ lng: -72.6695,
110
+ customer_type: CustomerType::Business,
111
+ },
112
+ LocationData {
113
+ name: "West End Hartford",
114
+ lat: 41.7680,
115
+ lng: -72.7000,
116
+ customer_type: CustomerType::Residential,
117
+ },
118
+ LocationData {
119
+ name: "Asylum Hill",
120
+ lat: 41.7720,
121
+ lng: -72.6850,
122
+ customer_type: CustomerType::Residential,
123
+ },
124
+ LocationData {
125
+ name: "Frog Hollow",
126
+ lat: 41.7580,
127
+ lng: -72.6900,
128
+ customer_type: CustomerType::Residential,
129
+ },
130
+ LocationData {
131
+ name: "Barry Square",
132
+ lat: 41.7450,
133
+ lng: -72.6800,
134
+ customer_type: CustomerType::Residential,
135
+ },
136
+ LocationData {
137
+ name: "South End",
138
+ lat: 41.7400,
139
+ lng: -72.6750,
140
+ customer_type: CustomerType::Residential,
141
+ },
142
+ LocationData {
143
+ name: "Blue Hills",
144
+ lat: 41.7850,
145
+ lng: -72.7050,
146
+ customer_type: CustomerType::Residential,
147
+ },
148
+ LocationData {
149
+ name: "Parkville",
150
+ lat: 41.7650,
151
+ lng: -72.7100,
152
+ customer_type: CustomerType::Residential,
153
+ },
154
+ LocationData {
155
+ name: "Behind the Rocks",
156
+ lat: 41.7550,
157
+ lng: -72.7050,
158
+ customer_type: CustomerType::Residential,
159
+ },
160
+ LocationData {
161
+ name: "Charter Oak",
162
+ lat: 41.7495,
163
+ lng: -72.6650,
164
+ customer_type: CustomerType::Residential,
165
+ },
166
+ LocationData {
167
+ name: "Sheldon Charter Oak",
168
+ lat: 41.7510,
169
+ lng: -72.6700,
170
+ customer_type: CustomerType::Residential,
171
+ },
172
+ LocationData {
173
+ name: "Clay Arsenal",
174
+ lat: 41.7750,
175
+ lng: -72.6850,
176
+ customer_type: CustomerType::Residential,
177
+ },
178
+ LocationData {
179
+ name: "Upper Albany",
180
+ lat: 41.7780,
181
+ lng: -72.6950,
182
+ customer_type: CustomerType::Residential,
183
+ },
184
+ ];
src/data/data_seed/hartford/visits_extra.rs ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::super::types::{CustomerType, LocationData};
2
+
3
+ pub(in crate::data::data_seed) const VISITS: &[LocationData] = &[
4
+ LocationData {
5
+ name: "The Place 2 Be",
6
+ lat: 41.7670,
7
+ lng: -72.6730,
8
+ customer_type: CustomerType::Restaurant,
9
+ },
10
+ LocationData {
11
+ name: "Republic at the Linden",
12
+ lat: 41.7650,
13
+ lng: -72.6750,
14
+ customer_type: CustomerType::Restaurant,
15
+ },
16
+ LocationData {
17
+ name: "Sorella",
18
+ lat: 41.7630,
19
+ lng: -72.6750,
20
+ customer_type: CustomerType::Restaurant,
21
+ },
22
+ LocationData {
23
+ name: "Black-Eyed Sally's",
24
+ lat: 41.7580,
25
+ lng: -72.6820,
26
+ customer_type: CustomerType::Restaurant,
27
+ },
28
+ LocationData {
29
+ name: "The Russell Hartford",
30
+ lat: 41.7655,
31
+ lng: -72.6728,
32
+ customer_type: CustomerType::Restaurant,
33
+ },
34
+ LocationData {
35
+ name: "Fiddleheads Cafe",
36
+ lat: 41.7580,
37
+ lng: -72.6900,
38
+ customer_type: CustomerType::Restaurant,
39
+ },
40
+ LocationData {
41
+ name: "Bushnell Center",
42
+ lat: 41.7615,
43
+ lng: -72.6820,
44
+ customer_type: CustomerType::Business,
45
+ },
46
+ LocationData {
47
+ name: "State Capitol",
48
+ lat: 41.7630,
49
+ lng: -72.6740,
50
+ customer_type: CustomerType::Business,
51
+ },
52
+ LocationData {
53
+ name: "Hartford Public Library",
54
+ lat: 41.7650,
55
+ lng: -72.6695,
56
+ customer_type: CustomerType::Business,
57
+ },
58
+ LocationData {
59
+ name: "XL Center",
60
+ lat: 41.7658,
61
+ lng: -72.6734,
62
+ customer_type: CustomerType::Business,
63
+ },
64
+ LocationData {
65
+ name: "Union Station Hartford",
66
+ lat: 41.7680,
67
+ lng: -72.6700,
68
+ customer_type: CustomerType::Business,
69
+ },
70
+ LocationData {
71
+ name: "Real Art Ways",
72
+ lat: 41.7650,
73
+ lng: -72.7100,
74
+ customer_type: CustomerType::Business,
75
+ },
76
+ LocationData {
77
+ name: "Colt Gateway",
78
+ lat: 41.7495,
79
+ lng: -72.6650,
80
+ customer_type: CustomerType::Business,
81
+ },
82
+ LocationData {
83
+ name: "South Green",
84
+ lat: 41.7550,
85
+ lng: -72.6780,
86
+ customer_type: CustomerType::Residential,
87
+ },
88
+ LocationData {
89
+ name: "North Meadows",
90
+ lat: 41.7750,
91
+ lng: -72.6850,
92
+ customer_type: CustomerType::Residential,
93
+ },
94
+ LocationData {
95
+ name: "South Meadows",
96
+ lat: 41.7400,
97
+ lng: -72.6750,
98
+ customer_type: CustomerType::Residential,
99
+ },
100
+ LocationData {
101
+ name: "West Hartford Line",
102
+ lat: 41.7680,
103
+ lng: -72.7000,
104
+ customer_type: CustomerType::Residential,
105
+ },
106
+ LocationData {
107
+ name: "Southwest Hartford",
108
+ lat: 41.7550,
109
+ lng: -72.7050,
110
+ customer_type: CustomerType::Residential,
111
+ },
112
+ LocationData {
113
+ name: "North End",
114
+ lat: 41.7780,
115
+ lng: -72.6950,
116
+ customer_type: CustomerType::Residential,
117
+ },
118
+ LocationData {
119
+ name: "Park Terrace",
120
+ lat: 41.7450,
121
+ lng: -72.6800,
122
+ customer_type: CustomerType::Residential,
123
+ },
124
+ ];
src/data/data_seed/philadelphia.rs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ use super::types::LocationData;
2
+
3
+ mod depots;
4
+ mod visits;
5
+ mod visits_extra;
6
+
7
+ pub(super) use depots::DEPOTS;
8
+ pub(super) const VISIT_GROUPS: &[&[LocationData]] = &[visits::VISITS, visits_extra::VISITS];
src/data/data_seed/philadelphia/depots.rs ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::super::types::{CustomerType, LocationData};
2
+
3
+ pub(in crate::data::data_seed) const DEPOTS: &[LocationData] = &[
4
+ LocationData {
5
+ name: "Central Depot - City Hall",
6
+ lat: 39.9526,
7
+ lng: -75.1652,
8
+ customer_type: CustomerType::Business,
9
+ },
10
+ LocationData {
11
+ name: "South Philly Depot",
12
+ lat: 39.9256,
13
+ lng: -75.1697,
14
+ customer_type: CustomerType::Business,
15
+ },
16
+ LocationData {
17
+ name: "University City Depot",
18
+ lat: 39.9522,
19
+ lng: -75.1932,
20
+ customer_type: CustomerType::Business,
21
+ },
22
+ LocationData {
23
+ name: "North Philly Depot",
24
+ lat: 39.9907,
25
+ lng: -75.1556,
26
+ customer_type: CustomerType::Business,
27
+ },
28
+ LocationData {
29
+ name: "Fishtown Depot",
30
+ lat: 39.9712,
31
+ lng: -75.1340,
32
+ customer_type: CustomerType::Business,
33
+ },
34
+ LocationData {
35
+ name: "West Philly Depot",
36
+ lat: 39.9601,
37
+ lng: -75.2175,
38
+ customer_type: CustomerType::Business,
39
+ },
40
+ LocationData {
41
+ name: "Logan Square Depot",
42
+ lat: 39.9567,
43
+ lng: -75.1720,
44
+ customer_type: CustomerType::Business,
45
+ },
46
+ LocationData {
47
+ name: "Pennsport Depot",
48
+ lat: 39.9320,
49
+ lng: -75.1450,
50
+ customer_type: CustomerType::Business,
51
+ },
52
+ LocationData {
53
+ name: "Kensington Depot",
54
+ lat: 39.9850,
55
+ lng: -75.1280,
56
+ customer_type: CustomerType::Business,
57
+ },
58
+ LocationData {
59
+ name: "Spruce Hill Depot",
60
+ lat: 39.9530,
61
+ lng: -75.2100,
62
+ customer_type: CustomerType::Business,
63
+ },
64
+ ];
src/data/data_seed/philadelphia/visits.rs ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::super::types::{CustomerType, LocationData};
2
+
3
+ pub(in crate::data::data_seed) const VISITS: &[LocationData] = &[
4
+ LocationData {
5
+ name: "Reading Terminal Market",
6
+ lat: 39.9535,
7
+ lng: -75.1589,
8
+ customer_type: CustomerType::Restaurant,
9
+ },
10
+ LocationData {
11
+ name: "Parc Restaurant",
12
+ lat: 39.9493,
13
+ lng: -75.1727,
14
+ customer_type: CustomerType::Restaurant,
15
+ },
16
+ LocationData {
17
+ name: "Zahav",
18
+ lat: 39.9430,
19
+ lng: -75.1474,
20
+ customer_type: CustomerType::Restaurant,
21
+ },
22
+ LocationData {
23
+ name: "Vetri Cucina",
24
+ lat: 39.9499,
25
+ lng: -75.1659,
26
+ customer_type: CustomerType::Restaurant,
27
+ },
28
+ LocationData {
29
+ name: "Talula's Garden",
30
+ lat: 39.9470,
31
+ lng: -75.1709,
32
+ customer_type: CustomerType::Restaurant,
33
+ },
34
+ LocationData {
35
+ name: "Fork",
36
+ lat: 39.9493,
37
+ lng: -75.1539,
38
+ customer_type: CustomerType::Restaurant,
39
+ },
40
+ LocationData {
41
+ name: "Morimoto",
42
+ lat: 39.9488,
43
+ lng: -75.1559,
44
+ customer_type: CustomerType::Restaurant,
45
+ },
46
+ LocationData {
47
+ name: "Vernick Food & Drink",
48
+ lat: 39.9508,
49
+ lng: -75.1718,
50
+ customer_type: CustomerType::Restaurant,
51
+ },
52
+ LocationData {
53
+ name: "Friday Saturday Sunday",
54
+ lat: 39.9492,
55
+ lng: -75.1715,
56
+ customer_type: CustomerType::Restaurant,
57
+ },
58
+ LocationData {
59
+ name: "Royal Izakaya",
60
+ lat: 39.9410,
61
+ lng: -75.1509,
62
+ customer_type: CustomerType::Restaurant,
63
+ },
64
+ LocationData {
65
+ name: "Laurel",
66
+ lat: 39.9392,
67
+ lng: -75.1538,
68
+ customer_type: CustomerType::Restaurant,
69
+ },
70
+ LocationData {
71
+ name: "Marigold Kitchen",
72
+ lat: 39.9533,
73
+ lng: -75.1920,
74
+ customer_type: CustomerType::Restaurant,
75
+ },
76
+ LocationData {
77
+ name: "Comcast Center",
78
+ lat: 39.9543,
79
+ lng: -75.1690,
80
+ customer_type: CustomerType::Business,
81
+ },
82
+ LocationData {
83
+ name: "Liberty Place",
84
+ lat: 39.9520,
85
+ lng: -75.1685,
86
+ customer_type: CustomerType::Business,
87
+ },
88
+ LocationData {
89
+ name: "BNY Mellon Center",
90
+ lat: 39.9505,
91
+ lng: -75.1660,
92
+ customer_type: CustomerType::Business,
93
+ },
94
+ LocationData {
95
+ name: "One Liberty Place",
96
+ lat: 39.9520,
97
+ lng: -75.1685,
98
+ customer_type: CustomerType::Business,
99
+ },
100
+ LocationData {
101
+ name: "Aramark Tower",
102
+ lat: 39.9550,
103
+ lng: -75.1705,
104
+ customer_type: CustomerType::Business,
105
+ },
106
+ LocationData {
107
+ name: "PSFS Building",
108
+ lat: 39.9521,
109
+ lng: -75.1602,
110
+ customer_type: CustomerType::Business,
111
+ },
112
+ LocationData {
113
+ name: "Three Logan Square",
114
+ lat: 39.9567,
115
+ lng: -75.1720,
116
+ customer_type: CustomerType::Business,
117
+ },
118
+ LocationData {
119
+ name: "Two Commerce Square",
120
+ lat: 39.9551,
121
+ lng: -75.1675,
122
+ customer_type: CustomerType::Business,
123
+ },
124
+ LocationData {
125
+ name: "Penn Medicine",
126
+ lat: 39.9500,
127
+ lng: -75.1930,
128
+ customer_type: CustomerType::Business,
129
+ },
130
+ LocationData {
131
+ name: "Children's Hospital",
132
+ lat: 39.9482,
133
+ lng: -75.1950,
134
+ customer_type: CustomerType::Business,
135
+ },
136
+ LocationData {
137
+ name: "Drexel University",
138
+ lat: 39.9566,
139
+ lng: -75.1899,
140
+ customer_type: CustomerType::Business,
141
+ },
142
+ LocationData {
143
+ name: "Temple University",
144
+ lat: 39.9812,
145
+ lng: -75.1554,
146
+ customer_type: CustomerType::Business,
147
+ },
148
+ LocationData {
149
+ name: "Jefferson Hospital",
150
+ lat: 39.9487,
151
+ lng: -75.1577,
152
+ customer_type: CustomerType::Business,
153
+ },
154
+ LocationData {
155
+ name: "Pennsylvania Hospital",
156
+ lat: 39.9445,
157
+ lng: -75.1545,
158
+ customer_type: CustomerType::Business,
159
+ },
160
+ LocationData {
161
+ name: "FMC Tower",
162
+ lat: 39.9499,
163
+ lng: -75.1780,
164
+ customer_type: CustomerType::Business,
165
+ },
166
+ LocationData {
167
+ name: "Cira Centre",
168
+ lat: 39.9560,
169
+ lng: -75.1822,
170
+ customer_type: CustomerType::Business,
171
+ },
172
+ LocationData {
173
+ name: "Rittenhouse Square",
174
+ lat: 39.9496,
175
+ lng: -75.1718,
176
+ customer_type: CustomerType::Residential,
177
+ },
178
+ LocationData {
179
+ name: "Washington Square West",
180
+ lat: 39.9468,
181
+ lng: -75.1545,
182
+ customer_type: CustomerType::Residential,
183
+ },
184
+ LocationData {
185
+ name: "Society Hill",
186
+ lat: 39.9425,
187
+ lng: -75.1478,
188
+ customer_type: CustomerType::Residential,
189
+ },
190
+ LocationData {
191
+ name: "Old City",
192
+ lat: 39.9510,
193
+ lng: -75.1450,
194
+ customer_type: CustomerType::Residential,
195
+ },
196
+ LocationData {
197
+ name: "Northern Liberties",
198
+ lat: 39.9650,
199
+ lng: -75.1420,
200
+ customer_type: CustomerType::Residential,
201
+ },
202
+ LocationData {
203
+ name: "Fishtown",
204
+ lat: 39.9712,
205
+ lng: -75.1340,
206
+ customer_type: CustomerType::Residential,
207
+ },
208
+ LocationData {
209
+ name: "Queen Village",
210
+ lat: 39.9380,
211
+ lng: -75.1520,
212
+ customer_type: CustomerType::Residential,
213
+ },
214
+ LocationData {
215
+ name: "Bella Vista",
216
+ lat: 39.9395,
217
+ lng: -75.1598,
218
+ customer_type: CustomerType::Residential,
219
+ },
220
+ LocationData {
221
+ name: "Graduate Hospital",
222
+ lat: 39.9425,
223
+ lng: -75.1768,
224
+ customer_type: CustomerType::Residential,
225
+ },
226
+ LocationData {
227
+ name: "Fairmount",
228
+ lat: 39.9680,
229
+ lng: -75.1750,
230
+ customer_type: CustomerType::Residential,
231
+ },
232
+ LocationData {
233
+ name: "Spring Garden",
234
+ lat: 39.9620,
235
+ lng: -75.1620,
236
+ customer_type: CustomerType::Residential,
237
+ },
238
+ LocationData {
239
+ name: "Art Museum Area",
240
+ lat: 39.9656,
241
+ lng: -75.1810,
242
+ customer_type: CustomerType::Residential,
243
+ },
244
+ LocationData {
245
+ name: "Brewerytown",
246
+ lat: 39.9750,
247
+ lng: -75.1850,
248
+ customer_type: CustomerType::Residential,
249
+ },
250
+ LocationData {
251
+ name: "East Passyunk",
252
+ lat: 39.9310,
253
+ lng: -75.1605,
254
+ customer_type: CustomerType::Residential,
255
+ },
256
+ LocationData {
257
+ name: "Point Breeze",
258
+ lat: 39.9285,
259
+ lng: -75.1780,
260
+ customer_type: CustomerType::Residential,
261
+ },
262
+ LocationData {
263
+ name: "Pennsport",
264
+ lat: 39.9320,
265
+ lng: -75.1450,
266
+ customer_type: CustomerType::Residential,
267
+ },
268
+ LocationData {
269
+ name: "Powelton Village",
270
+ lat: 39.9610,
271
+ lng: -75.1950,
272
+ customer_type: CustomerType::Residential,
273
+ },
274
+ LocationData {
275
+ name: "Spruce Hill",
276
+ lat: 39.9530,
277
+ lng: -75.2100,
278
+ customer_type: CustomerType::Residential,
279
+ },
280
+ LocationData {
281
+ name: "Cedar Park",
282
+ lat: 39.9490,
283
+ lng: -75.2200,
284
+ customer_type: CustomerType::Residential,
285
+ },
286
+ LocationData {
287
+ name: "Kensington",
288
+ lat: 39.9850,
289
+ lng: -75.1280,
290
+ customer_type: CustomerType::Residential,
291
+ },
292
+ LocationData {
293
+ name: "Port Richmond",
294
+ lat: 39.9870,
295
+ lng: -75.1120,
296
+ customer_type: CustomerType::Residential,
297
+ },
298
+ ];
src/data/data_seed/philadelphia/visits_extra.rs ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::super::types::{CustomerType, LocationData};
2
+
3
+ pub(in crate::data::data_seed) const VISITS: &[LocationData] = &[
4
+ LocationData {
5
+ name: "Di Bruno Bros",
6
+ lat: 39.9496,
7
+ lng: -75.1718,
8
+ customer_type: CustomerType::Restaurant,
9
+ },
10
+ LocationData {
11
+ name: "Federal Donuts Center City",
12
+ lat: 39.9492,
13
+ lng: -75.1715,
14
+ customer_type: CustomerType::Restaurant,
15
+ },
16
+ LocationData {
17
+ name: "High Street Philly",
18
+ lat: 39.9493,
19
+ lng: -75.1539,
20
+ customer_type: CustomerType::Restaurant,
21
+ },
22
+ LocationData {
23
+ name: "Suraya",
24
+ lat: 39.9712,
25
+ lng: -75.1340,
26
+ customer_type: CustomerType::Restaurant,
27
+ },
28
+ LocationData {
29
+ name: "Wm Mulherin's Sons",
30
+ lat: 39.9712,
31
+ lng: -75.1340,
32
+ customer_type: CustomerType::Restaurant,
33
+ },
34
+ LocationData {
35
+ name: "El Vez",
36
+ lat: 39.9499,
37
+ lng: -75.1659,
38
+ customer_type: CustomerType::Restaurant,
39
+ },
40
+ LocationData {
41
+ name: "Barbuzzo",
42
+ lat: 39.9493,
43
+ lng: -75.1727,
44
+ customer_type: CustomerType::Restaurant,
45
+ },
46
+ LocationData {
47
+ name: "The Love",
48
+ lat: 39.9508,
49
+ lng: -75.1718,
50
+ customer_type: CustomerType::Restaurant,
51
+ },
52
+ LocationData {
53
+ name: "30th Street Station",
54
+ lat: 39.9560,
55
+ lng: -75.1822,
56
+ customer_type: CustomerType::Business,
57
+ },
58
+ LocationData {
59
+ name: "University of Pennsylvania",
60
+ lat: 39.9500,
61
+ lng: -75.1930,
62
+ customer_type: CustomerType::Business,
63
+ },
64
+ LocationData {
65
+ name: "Franklin Institute",
66
+ lat: 39.9567,
67
+ lng: -75.1720,
68
+ customer_type: CustomerType::Business,
69
+ },
70
+ LocationData {
71
+ name: "Academy of Natural Sciences",
72
+ lat: 39.9567,
73
+ lng: -75.1720,
74
+ customer_type: CustomerType::Business,
75
+ },
76
+ LocationData {
77
+ name: "Kimmel Center",
78
+ lat: 39.9499,
79
+ lng: -75.1659,
80
+ customer_type: CustomerType::Business,
81
+ },
82
+ LocationData {
83
+ name: "City Hall Annex",
84
+ lat: 39.9526,
85
+ lng: -75.1652,
86
+ customer_type: CustomerType::Business,
87
+ },
88
+ LocationData {
89
+ name: "Independence Hall",
90
+ lat: 39.9510,
91
+ lng: -75.1450,
92
+ customer_type: CustomerType::Business,
93
+ },
94
+ LocationData {
95
+ name: "Navy Yard Offices",
96
+ lat: 39.9256,
97
+ lng: -75.1697,
98
+ customer_type: CustomerType::Business,
99
+ },
100
+ LocationData {
101
+ name: "Penn's Landing",
102
+ lat: 39.9410,
103
+ lng: -75.1509,
104
+ customer_type: CustomerType::Business,
105
+ },
106
+ LocationData {
107
+ name: "Rodin Museum",
108
+ lat: 39.9656,
109
+ lng: -75.1810,
110
+ customer_type: CustomerType::Business,
111
+ },
112
+ LocationData {
113
+ name: "Barnes Foundation",
114
+ lat: 39.9656,
115
+ lng: -75.1810,
116
+ customer_type: CustomerType::Business,
117
+ },
118
+ LocationData {
119
+ name: "Fitler Square",
120
+ lat: 39.9499,
121
+ lng: -75.1780,
122
+ customer_type: CustomerType::Residential,
123
+ },
124
+ LocationData {
125
+ name: "Logan Square",
126
+ lat: 39.9567,
127
+ lng: -75.1720,
128
+ customer_type: CustomerType::Residential,
129
+ },
130
+ LocationData {
131
+ name: "Callowhill",
132
+ lat: 39.9620,
133
+ lng: -75.1620,
134
+ customer_type: CustomerType::Residential,
135
+ },
136
+ LocationData {
137
+ name: "Francisville",
138
+ lat: 39.9680,
139
+ lng: -75.1750,
140
+ customer_type: CustomerType::Residential,
141
+ },
142
+ LocationData {
143
+ name: "Whitman",
144
+ lat: 39.9256,
145
+ lng: -75.1697,
146
+ customer_type: CustomerType::Residential,
147
+ },
148
+ LocationData {
149
+ name: "Passyunk Square",
150
+ lat: 39.9310,
151
+ lng: -75.1605,
152
+ customer_type: CustomerType::Residential,
153
+ },
154
+ LocationData {
155
+ name: "Girard Estates",
156
+ lat: 39.9285,
157
+ lng: -75.1780,
158
+ customer_type: CustomerType::Residential,
159
+ },
160
+ LocationData {
161
+ name: "West Powelton",
162
+ lat: 39.9610,
163
+ lng: -75.1950,
164
+ customer_type: CustomerType::Residential,
165
+ },
166
+ LocationData {
167
+ name: "Mantua",
168
+ lat: 39.9610,
169
+ lng: -75.1950,
170
+ customer_type: CustomerType::Residential,
171
+ },
172
+ LocationData {
173
+ name: "Walnut Hill",
174
+ lat: 39.9530,
175
+ lng: -75.2100,
176
+ customer_type: CustomerType::Residential,
177
+ },
178
+ LocationData {
179
+ name: "Cobbs Creek",
180
+ lat: 39.9490,
181
+ lng: -75.2200,
182
+ customer_type: CustomerType::Residential,
183
+ },
184
+ LocationData {
185
+ name: "Harrowgate",
186
+ lat: 39.9850,
187
+ lng: -75.1280,
188
+ customer_type: CustomerType::Residential,
189
+ },
190
+ LocationData {
191
+ name: "Tacony",
192
+ lat: 39.9870,
193
+ lng: -75.1120,
194
+ customer_type: CustomerType::Residential,
195
+ },
196
+ LocationData {
197
+ name: "Manayunk",
198
+ lat: 39.9750,
199
+ lng: -75.1850,
200
+ customer_type: CustomerType::Residential,
201
+ },
202
+ ];
src/data/data_seed/tests.rs ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::*;
2
+ use crate::data::data_seed::types::LocationData;
3
+ use solverforge_maps::{BoundingBox, Coord, NetworkConfig, RoadNetwork};
4
+
5
+ #[test]
6
+ fn parses_demo_data_ids_case_insensitively() {
7
+ assert!(matches!(
8
+ "philadelphia".parse::<DemoData>(),
9
+ Ok(DemoData::Philadelphia)
10
+ ));
11
+ assert!(matches!(
12
+ "HARTFORD".parse::<DemoData>(),
13
+ Ok(DemoData::Hartford)
14
+ ));
15
+ }
16
+
17
+ #[test]
18
+ fn generates_city_demo_plan() {
19
+ for (demo, expected_stops, expected_name) in [
20
+ (
21
+ DemoData::Philadelphia,
22
+ visit_count(philadelphia::VISIT_GROUPS),
23
+ "Philadelphia",
24
+ ),
25
+ (
26
+ DemoData::Hartford,
27
+ visit_count(hartford::VISIT_GROUPS),
28
+ "Hartford",
29
+ ),
30
+ (
31
+ DemoData::Firenze,
32
+ visit_count(firenze::VISIT_GROUPS),
33
+ "Firenze",
34
+ ),
35
+ ] {
36
+ let plan = generate(demo);
37
+ assert_eq!(plan.name, expected_name);
38
+ assert_eq!(plan.vehicles.len(), 10);
39
+ assert_eq!(plan.deliveries.len(), expected_stops);
40
+ assert!(plan
41
+ .vehicles
42
+ .iter()
43
+ .all(|vehicle| vehicle.delivery_order.is_empty()));
44
+ }
45
+ }
46
+
47
+ fn visit_count(groups: &[&[LocationData]]) -> usize {
48
+ groups.iter().map(|group| group.len()).sum()
49
+ }
50
+
51
+ #[test]
52
+ fn demo_delivery_counts_scale_with_ten_vehicles() {
53
+ assert_eq!(generate(DemoData::Philadelphia).deliveries.len(), 82);
54
+ assert_eq!(generate(DemoData::Hartford).deliveries.len(), 50);
55
+ assert_eq!(generate(DemoData::Firenze).deliveries.len(), 80);
56
+ }
57
+
58
+ #[test]
59
+ fn demo_plans_have_enough_vehicle_capacity() {
60
+ for demo in [
61
+ DemoData::Philadelphia,
62
+ DemoData::Hartford,
63
+ DemoData::Firenze,
64
+ ] {
65
+ let plan = generate(demo);
66
+ let total_capacity: i32 = plan.vehicles.iter().map(|vehicle| vehicle.capacity).sum();
67
+ let total_demand: i32 = plan.deliveries.iter().map(|delivery| delivery.demand).sum();
68
+
69
+ assert!(
70
+ total_capacity >= total_demand,
71
+ "{demo:?} demo should be capacity-feasible before route ordering: capacity={total_capacity}, demand={total_demand}"
72
+ );
73
+ }
74
+ }
75
+
76
+ #[tokio::test]
77
+ async fn live_demo_locations_are_mutually_reachable_when_enabled() {
78
+ if std::env::var("SOLVERFORGE_RUN_LIVE_TESTS").ok().as_deref() != Some("1") {
79
+ return;
80
+ }
81
+
82
+ for demo in [
83
+ DemoData::Philadelphia,
84
+ DemoData::Hartford,
85
+ DemoData::Firenze,
86
+ ] {
87
+ let plan = generate(demo);
88
+ let mut named_coords = Vec::new();
89
+ for delivery in &plan.deliveries {
90
+ named_coords.push((
91
+ format!("delivery {}", delivery.label),
92
+ delivery.coord().unwrap(),
93
+ ));
94
+ }
95
+ for vehicle in &plan.vehicles {
96
+ named_coords.push((
97
+ format!("depot {}", vehicle.name),
98
+ vehicle.depot_coord().unwrap(),
99
+ ));
100
+ }
101
+
102
+ let coords: Vec<Coord> = named_coords.iter().map(|(_, coord)| *coord).collect();
103
+ let bbox = BoundingBox::from_coords(&coords).expand_for_routing(&coords);
104
+ let network = RoadNetwork::load_or_fetch(&bbox, &NetworkConfig::default(), None)
105
+ .await
106
+ .unwrap();
107
+ let matrix = network.compute_matrix(&coords, None).await;
108
+ let unreachable = matrix
109
+ .unreachable_pairs()
110
+ .into_iter()
111
+ .map(|(from_idx, to_idx)| {
112
+ let from_name = &named_coords[from_idx].0;
113
+ let to_name = &named_coords[to_idx].0;
114
+ format!("{from_name} -> {to_name}")
115
+ })
116
+ .collect::<Vec<_>>();
117
+
118
+ assert!(
119
+ unreachable.is_empty(),
120
+ "{demo:?} has unreachable directed routes: {unreachable:?}"
121
+ );
122
+ }
123
+ }
src/data/data_seed/types.rs ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::domain::DeliveryKind;
2
+
3
+ pub(super) const VEHICLE_NAMES: [&str; 10] = [
4
+ "Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf", "Hotel", "India", "Juliet",
5
+ ];
6
+
7
+ #[derive(Clone, Copy)]
8
+ pub(super) struct LocationData {
9
+ pub name: &'static str,
10
+ pub lat: f64,
11
+ pub lng: f64,
12
+ pub customer_type: CustomerType,
13
+ }
14
+
15
+ #[derive(Clone, Copy)]
16
+ pub(super) enum CustomerType {
17
+ Residential,
18
+ Business,
19
+ Restaurant,
20
+ }
21
+
22
+ impl CustomerType {
23
+ pub(super) fn profile(self) -> (DeliveryKind, i64, i64, (i32, i32), (i64, i64)) {
24
+ match self {
25
+ CustomerType::Residential => (
26
+ DeliveryKind::Residential,
27
+ 17 * 3600,
28
+ 20 * 3600,
29
+ (1, 2),
30
+ (5 * 60, 10 * 60),
31
+ ),
32
+ CustomerType::Business => (
33
+ DeliveryKind::Business,
34
+ 9 * 3600,
35
+ 17 * 3600,
36
+ (3, 6),
37
+ (15 * 60, 30 * 60),
38
+ ),
39
+ CustomerType::Restaurant => (
40
+ DeliveryKind::Restaurant,
41
+ 6 * 3600,
42
+ 10 * 3600,
43
+ (5, 10),
44
+ (20 * 60, 40 * 60),
45
+ ),
46
+ }
47
+ }
48
+ }
src/data/mod.rs ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Stable demo-data boundary for the deliveries app.
2
+ //!
3
+ //! Other layers should import `generate` and `DemoData` from here instead of
4
+ //! reaching into city-specific seed modules. That keeps the public data surface
5
+ //! small while the Philadelphia, Hartford, and Firenze fixtures can stay split
6
+ //! for readability.
7
+
8
+ mod data_seed;
9
+
10
+ pub use data_seed::{generate, DemoData};
src/domain/clarke_wright_tests.rs ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::*;
2
+ use crate::data::{generate, DemoData};
3
+ use solverforge::cvrp::ProblemData;
4
+ use solverforge::SolverConfig;
5
+ use std::collections::BTreeSet;
6
+ use std::sync::Arc;
7
+
8
+ fn attach_synthetic_routing(plan: &mut Plan) {
9
+ let delivery_count = plan.deliveries.len();
10
+ let demands = plan
11
+ .deliveries
12
+ .iter()
13
+ .map(|delivery| delivery.demand)
14
+ .collect::<Vec<_>>();
15
+ let time_windows = plan
16
+ .deliveries
17
+ .iter()
18
+ .map(|delivery| (delivery.min_start_time, delivery.max_end_time))
19
+ .collect::<Vec<_>>();
20
+ let service_durations = plan
21
+ .deliveries
22
+ .iter()
23
+ .map(|delivery| delivery.service_duration)
24
+ .collect::<Vec<_>>();
25
+ let travel_times = matrix(delivery_count, 300, 45);
26
+ let distances = matrix(delivery_count, 1_000, 150);
27
+
28
+ plan.prepared_problem_data.clear();
29
+ for (vehicle_idx, vehicle) in plan.vehicles.iter_mut().enumerate() {
30
+ let depot_to_delivery_seconds = depot_legs(delivery_count, vehicle_idx, 600, 10);
31
+ let delivery_to_depot_seconds = depot_legs(delivery_count, vehicle_idx, 630, 10);
32
+ let depot_to_delivery_meters = depot_legs(delivery_count, vehicle_idx, 2_000, 20);
33
+ let delivery_to_depot_meters = depot_legs(delivery_count, vehicle_idx, 2_100, 20);
34
+ let problem_matrix = problem_matrix(
35
+ delivery_count,
36
+ &travel_times,
37
+ &depot_to_delivery_seconds,
38
+ &delivery_to_depot_seconds,
39
+ );
40
+
41
+ plan.prepared_problem_data.push(Arc::new(ProblemData {
42
+ capacity: vehicle.capacity as i64,
43
+ depot: delivery_count,
44
+ demands: demands.clone(),
45
+ distance_matrix: problem_matrix.clone(),
46
+ time_windows: time_windows.clone(),
47
+ service_durations: service_durations.clone(),
48
+ travel_times: problem_matrix,
49
+ vehicle_departure_time: vehicle.departure_time,
50
+ }));
51
+ vehicle.prepared_routing = Some(PreparedVehicleRouting {
52
+ problem_data_index: vehicle_idx,
53
+ capacity: vehicle.capacity as i64,
54
+ demands: demands.clone(),
55
+ distance_matrix: distances.clone(),
56
+ time_windows: time_windows.clone(),
57
+ service_durations: service_durations.clone(),
58
+ travel_times: travel_times.clone(),
59
+ vehicle_departure_time: vehicle.departure_time,
60
+ depot_to_delivery_seconds,
61
+ delivery_to_depot_seconds,
62
+ depot_to_delivery_meters,
63
+ delivery_to_depot_meters,
64
+ });
65
+ }
66
+ }
67
+
68
+ fn matrix(size: usize, base: i64, step: i64) -> Vec<Vec<i64>> {
69
+ (0..size)
70
+ .map(|from| {
71
+ (0..size)
72
+ .map(|to| {
73
+ if from == to {
74
+ 0
75
+ } else {
76
+ base + from.abs_diff(to) as i64 * step
77
+ }
78
+ })
79
+ .collect()
80
+ })
81
+ .collect()
82
+ }
83
+
84
+ fn depot_legs(size: usize, vehicle_idx: usize, base: i64, step: i64) -> Vec<i64> {
85
+ (0..size)
86
+ .map(|delivery_idx| base + delivery_idx as i64 * step + vehicle_idx as i64)
87
+ .collect()
88
+ }
89
+
90
+ fn problem_matrix(
91
+ delivery_count: usize,
92
+ travel_times: &[Vec<i64>],
93
+ depot_to_delivery_seconds: &[i64],
94
+ delivery_to_depot_seconds: &[i64],
95
+ ) -> Vec<Vec<i64>> {
96
+ let mut matrix = vec![vec![0_i64; delivery_count + 1]; delivery_count + 1];
97
+ for (from, row) in travel_times.iter().enumerate() {
98
+ for (to, seconds) in row.iter().copied().enumerate() {
99
+ matrix[from][to] = seconds;
100
+ }
101
+ }
102
+ for (delivery_idx, seconds) in depot_to_delivery_seconds.iter().copied().enumerate() {
103
+ matrix[delivery_count][delivery_idx] = seconds;
104
+ }
105
+ for (delivery_idx, seconds) in delivery_to_depot_seconds.iter().copied().enumerate() {
106
+ matrix[delivery_idx][delivery_count] = seconds;
107
+ }
108
+ matrix
109
+ }
110
+
111
+ #[test]
112
+ fn clarke_wright_construction_assigns_full_philadelphia_fixture() {
113
+ let mut plan = generate(DemoData::Philadelphia);
114
+ attach_synthetic_routing(&mut plan);
115
+ assert_eq!(plan.deliveries.len(), 82);
116
+
117
+ let config = clarke_wright_only_config();
118
+ let solved = Plan::test_solve_with_config(plan, &config);
119
+ assert_all_deliveries_assigned(&solved, 82);
120
+ }
121
+
122
+ #[test]
123
+ fn construction_policy_assigns_full_philadelphia_fixture() {
124
+ let mut plan = generate(DemoData::Philadelphia);
125
+ attach_synthetic_routing(&mut plan);
126
+ assert_eq!(plan.deliveries.len(), 82);
127
+
128
+ let config = clarke_wright_then_k_opt_config();
129
+ let solved = Plan::test_solve_with_config(plan, &config);
130
+ assert_all_deliveries_assigned(&solved, 82);
131
+ }
132
+
133
+ #[test]
134
+ fn clarke_wright_assigns_over_capacity_delivery_for_scoring() {
135
+ let mut plan = single_delivery_plan(20, 5, (0, 86_400), 60);
136
+ attach_synthetic_routing(&mut plan);
137
+ let unassigned_hard_score = evaluate_plan(&plan).hard_score();
138
+
139
+ let config = clarke_wright_only_config();
140
+ let solved = Plan::test_solve_with_config(plan, &config);
141
+ let components = evaluate_plan(&solved);
142
+
143
+ assert_all_deliveries_assigned(&solved, 1);
144
+ assert!(components.capacity_overage > 0);
145
+ assert!(
146
+ components.hard_score() > unassigned_hard_score,
147
+ "capacity overage must be scored as a better assignment than leaving the delivery unassigned"
148
+ );
149
+ }
150
+
151
+ #[test]
152
+ fn clarke_wright_assigns_late_delivery_for_scoring() {
153
+ let mut plan = single_delivery_plan(1, 10, (0, 100), 1_000);
154
+ attach_synthetic_routing(&mut plan);
155
+ let unassigned_hard_score = evaluate_plan(&plan).hard_score();
156
+
157
+ let config = clarke_wright_only_config();
158
+ let solved = Plan::test_solve_with_config(plan, &config);
159
+ let components = evaluate_plan(&solved);
160
+
161
+ assert_all_deliveries_assigned(&solved, 1);
162
+ assert!(components.late_seconds > 0);
163
+ assert!(
164
+ components.hard_score() > unassigned_hard_score,
165
+ "lateness must be scored as a better assignment than leaving the delivery unassigned"
166
+ );
167
+ }
168
+
169
+ #[tokio::test]
170
+ async fn live_clarke_wright_construction_assigns_full_philadelphia_fixture_when_enabled() {
171
+ if std::env::var("SOLVERFORGE_RUN_LIVE_TESTS").ok().as_deref() != Some("1") {
172
+ return;
173
+ }
174
+
175
+ let mut plan = generate(DemoData::Philadelphia);
176
+ prepare_plan(&mut plan)
177
+ .await
178
+ .expect("live road-network preparation should succeed");
179
+ assert_eq!(plan.deliveries.len(), 82);
180
+
181
+ let config = clarke_wright_only_config();
182
+ let solved = Plan::test_solve_with_config(plan, &config);
183
+ assert_all_deliveries_assigned(&solved, 82);
184
+ }
185
+
186
+ fn single_delivery_plan(
187
+ demand: i32,
188
+ capacity: i32,
189
+ time_window: (i64, i64),
190
+ service_duration: i64,
191
+ ) -> Plan {
192
+ Plan::new(
193
+ "Single delivery",
194
+ vec![Delivery::new(
195
+ 0,
196
+ "Only stop",
197
+ DeliveryKind::Business,
198
+ (39.9526, -75.1652),
199
+ demand,
200
+ time_window,
201
+ service_duration,
202
+ )],
203
+ vec![Vehicle::new(0, "Truck", capacity, 39.9520, -75.1640, 0)],
204
+ )
205
+ }
206
+
207
+ fn clarke_wright_only_config() -> SolverConfig {
208
+ SolverConfig::from_toml_str(
209
+ r#"
210
+ environment_mode = "reproducible"
211
+ random_seed = 42
212
+
213
+ [[phases]]
214
+ type = "construction_heuristic"
215
+ construction_heuristic_type = "list_clarke_wright"
216
+ entity_class = "Vehicle"
217
+ variable_name = "delivery_order"
218
+ "#,
219
+ )
220
+ .expect("valid Clarke-Wright-only test config")
221
+ }
222
+
223
+ fn clarke_wright_then_k_opt_config() -> SolverConfig {
224
+ SolverConfig::from_toml_str(
225
+ r#"
226
+ environment_mode = "reproducible"
227
+ random_seed = 42
228
+
229
+ [[phases]]
230
+ type = "construction_heuristic"
231
+ construction_heuristic_type = "list_clarke_wright"
232
+ entity_class = "Vehicle"
233
+ variable_name = "delivery_order"
234
+
235
+ [[phases]]
236
+ type = "construction_heuristic"
237
+ construction_heuristic_type = "list_k_opt"
238
+ k = 2
239
+ entity_class = "Vehicle"
240
+ variable_name = "delivery_order"
241
+ "#,
242
+ )
243
+ .expect("valid Clarke-Wright plus k-opt test config")
244
+ }
245
+
246
+ fn assert_all_deliveries_assigned(plan: &Plan, expected_count: usize) {
247
+ let assigned = plan
248
+ .vehicles
249
+ .iter()
250
+ .flat_map(|vehicle| vehicle.delivery_order.iter().copied())
251
+ .collect::<Vec<_>>();
252
+ let unique = assigned.iter().copied().collect::<BTreeSet<_>>();
253
+
254
+ assert_eq!(assigned.len(), expected_count);
255
+ assert_eq!(unique.len(), expected_count);
256
+ }
257
+
258
+ #[test]
259
+ fn production_local_search_scans_until_score_improves() {
260
+ let solver_toml = include_str!("../../solver.toml");
261
+
262
+ assert!(
263
+ solver_toml.contains("type = \"first_last_step_score_improving\""),
264
+ "local search must keep scanning past equal accepted moves"
265
+ );
266
+ assert!(
267
+ !solver_toml.contains("type = \"accepted_count\""),
268
+ "accepted_count can stop after equal-score accepted moves before reaching an improvement"
269
+ );
270
+ }
src/domain/coord_value.rs ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use serde::{Deserialize, Serialize};
2
+ use std::fmt;
3
+ use std::hash::{Hash, Hasher};
4
+
5
+ #[derive(Copy, Clone, Default, Serialize, Deserialize)]
6
+ #[serde(transparent)]
7
+ pub struct CoordValue(pub f64);
8
+
9
+ impl CoordValue {
10
+ pub fn get(self) -> f64 {
11
+ self.0
12
+ }
13
+ }
14
+
15
+ impl From<f64> for CoordValue {
16
+ fn from(value: f64) -> Self {
17
+ Self(value)
18
+ }
19
+ }
20
+
21
+ impl fmt::Debug for CoordValue {
22
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23
+ self.0.fmt(f)
24
+ }
25
+ }
26
+
27
+ impl PartialEq for CoordValue {
28
+ fn eq(&self, other: &Self) -> bool {
29
+ self.0.to_bits() == other.0.to_bits()
30
+ }
31
+ }
32
+
33
+ impl Eq for CoordValue {}
34
+
35
+ impl Hash for CoordValue {
36
+ fn hash<H: Hasher>(&self, state: &mut H) {
37
+ self.0.to_bits().hash(state);
38
+ }
39
+ }
src/domain/delivery.rs ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Delivery problem facts.
2
+ //!
3
+ //! A delivery is input data, not something SolverForge mutates directly. The
4
+ //! solver places delivery ids into each vehicle's list variable.
5
+
6
+ use serde::{Deserialize, Serialize};
7
+ use solverforge::prelude::*;
8
+ use solverforge_maps::{Coord, RoutingError};
9
+
10
+ use super::CoordValue;
11
+
12
+ /// A delivery stop that can be assigned into a vehicle route.
13
+ #[problem_fact]
14
+ #[derive(Serialize, Deserialize)]
15
+ #[serde(rename_all = "camelCase")]
16
+ pub struct Delivery {
17
+ #[planning_id]
18
+ pub id: usize,
19
+ pub label: String,
20
+ pub kind: DeliveryKind,
21
+ /// Latitude in decimal degrees, wrapped so derived equality stays stable.
22
+ pub lat: CoordValue,
23
+ /// Longitude in decimal degrees, wrapped so derived equality stays stable.
24
+ pub lng: CoordValue,
25
+ /// Load consumed from the assigned vehicle capacity.
26
+ pub demand: i32,
27
+ /// Earliest allowed service start, expressed as seconds after midnight.
28
+ pub min_start_time: i64,
29
+ /// Latest allowed service end, expressed as seconds after midnight.
30
+ pub max_end_time: i64,
31
+ /// Time spent at the stop after arrival.
32
+ pub service_duration: i64,
33
+ }
34
+
35
+ /// Coarse stop type used to shape demo-data demand and UI icons.
36
+ #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
37
+ #[serde(rename_all = "snake_case")]
38
+ pub enum DeliveryKind {
39
+ Residential,
40
+ Business,
41
+ Restaurant,
42
+ #[default]
43
+ Other,
44
+ }
45
+
46
+ impl Delivery {
47
+ /// Creates one problem fact from transport-friendly primitive values.
48
+ pub fn new(
49
+ id: usize,
50
+ label: impl Into<String>,
51
+ kind: DeliveryKind,
52
+ coord: (f64, f64),
53
+ demand: i32,
54
+ time_window: (i64, i64),
55
+ service_duration: i64,
56
+ ) -> Self {
57
+ Self {
58
+ id,
59
+ label: label.into(),
60
+ kind,
61
+ lat: coord.0.into(),
62
+ lng: coord.1.into(),
63
+ demand,
64
+ min_start_time: time_window.0,
65
+ max_end_time: time_window.1,
66
+ service_duration,
67
+ }
68
+ }
69
+
70
+ /// Converts the serialized coordinates into the map library's checked type.
71
+ pub fn coord(&self) -> Result<Coord, RoutingError> {
72
+ Ok(Coord::try_new(self.lat.get(), self.lng.get())?)
73
+ }
74
+ }
75
+
76
+ #[cfg(test)]
77
+ mod tests {
78
+ use super::*;
79
+
80
+ #[test]
81
+ fn test_delivery_construction() {
82
+ let fact = Delivery::new(
83
+ 3,
84
+ "Test stop",
85
+ DeliveryKind::Business,
86
+ (43.77, 11.25),
87
+ 4,
88
+ (9 * 3600, 17 * 3600),
89
+ 20 * 60,
90
+ );
91
+ assert_eq!(fact.id, 3);
92
+ assert_eq!(fact.label, "Test stop");
93
+ assert_eq!(fact.kind, DeliveryKind::Business);
94
+ }
95
+ }
src/domain/mod.rs ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Planning-model manifest and domain-layer exports.
2
+ //!
3
+ //! `planning_model!` is the single SolverForge model boundary. It lists the
4
+ //! file-backed domain modules, exports the public names used by the rest of the
5
+ //! app, and keeps route preparation close to the domain it describes.
6
+
7
+ solverforge::planning_model! {
8
+ root = "src/domain";
9
+
10
+ // @solverforge:begin domain-exports
11
+ mod coord_value;
12
+ mod delivery;
13
+ mod plan;
14
+ mod vehicle;
15
+
16
+ pub use coord_value::CoordValue;
17
+ pub use delivery::Delivery;
18
+ pub use delivery::DeliveryKind;
19
+ pub use plan::Plan;
20
+ pub use plan::PlanConstraintStreams;
21
+ pub use vehicle::Vehicle;
22
+ // @solverforge:end domain-exports
23
+
24
+ mod preview;
25
+ mod route_metrics;
26
+
27
+ pub use preview::{
28
+ DeliveryPreview, PlanPreview, PlanViewState, RoutingMode, TimelineView, VehiclePreview,
29
+ VehiclePreviewStop,
30
+ };
31
+ pub use route_metrics::{
32
+ build_routes_snapshot, evaluate_plan, prepare_plan, preview_for_plan,
33
+ rank_delivery_insertions, DeliveryInsertionCandidate, PlanScoreComponents,
34
+ PreparedVehicleRouting, RouteLegGeometry, RouteLegSummary, RoutesSnapshot,
35
+ VehicleRouteMetrics, UNASSIGNED_DELIVERY_HARD_PENALTY,
36
+ };
37
+ }
38
+
39
+ #[cfg(test)]
40
+ mod clarke_wright_tests;
41
+ #[cfg(test)]
42
+ mod plan_tests;
src/domain/plan.rs ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Planning solution for the delivery-routing problem.
2
+ //!
3
+ //! The `Plan` owns facts, planning entities, score, the road-network routing
4
+ //! marker, and transient prepared route matrices. It is both the solver input
5
+ //! and the shape serialized through the API after `PlanDto` flattens it.
6
+
7
+ use std::collections::HashMap;
8
+ use std::sync::Arc;
9
+
10
+ use serde::{Deserialize, Serialize};
11
+ use solverforge::cvrp::ProblemData;
12
+ use solverforge::prelude::*;
13
+
14
+ // @solverforge:begin solution-imports
15
+ use super::route_metrics::preview_for_plan;
16
+ use super::{Delivery, PlanViewState, RoutingMode, Vehicle};
17
+ // @solverforge:end solution-imports
18
+
19
+ #[planning_solution(
20
+ constraints = "crate::constraints::create_constraints",
21
+ solver_toml = "../../solver.toml"
22
+ )]
23
+ #[shadow_variable_updates(
24
+ list_owner = "vehicles",
25
+ post_update_listener = "refresh_vehicle_route_shadows"
26
+ )]
27
+ #[derive(Serialize, Deserialize)]
28
+ #[serde(rename_all = "camelCase")]
29
+ pub struct Plan {
30
+ pub name: String,
31
+ #[serde(default)]
32
+ pub routing_mode: RoutingMode,
33
+ #[serde(default)]
34
+ pub view_state: PlanViewState,
35
+ // @solverforge:begin solution-collections
36
+ #[problem_fact_collection]
37
+ pub deliveries: Vec<Delivery>,
38
+ #[planning_entity_collection]
39
+ pub vehicles: Vec<Vehicle>,
40
+ // @solverforge:end solution-collections
41
+ #[planning_score]
42
+ pub score: Option<HardSoftScore>,
43
+ /// Transient CVRP matrices shared with SolverForge list-variable hooks.
44
+ ///
45
+ /// This is skipped in JSON because it is rebuilt from coordinates before a
46
+ /// solve or route-geometry request.
47
+ #[serde(skip, default)]
48
+ pub prepared_problem_data: Vec<Arc<ProblemData>>,
49
+ }
50
+
51
+ impl Plan {
52
+ /// Builds a normalized plan from facts and route-owning vehicles.
53
+ pub fn new(name: impl Into<String>, deliveries: Vec<Delivery>, vehicles: Vec<Vehicle>) -> Self {
54
+ let mut plan = Self {
55
+ name: name.into(),
56
+ routing_mode: RoutingMode::default(),
57
+ view_state: PlanViewState::default(),
58
+ deliveries,
59
+ vehicles,
60
+ score: None,
61
+ prepared_problem_data: Vec::new(),
62
+ };
63
+ plan.normalize();
64
+ plan
65
+ }
66
+
67
+ /// Reassigns dense ids and clears transient routing caches after decoding.
68
+ ///
69
+ /// SolverForge list variables store delivery indexes. If transport data
70
+ /// arrived with older public ids, this maps route entries back onto the
71
+ /// current dense delivery positions before scoring.
72
+ pub fn normalize(&mut self) {
73
+ let delivery_id_map: HashMap<usize, usize> = self
74
+ .deliveries
75
+ .iter()
76
+ .enumerate()
77
+ .map(|(idx, delivery)| (delivery.id, idx))
78
+ .collect();
79
+
80
+ for (idx, delivery) in self.deliveries.iter_mut().enumerate() {
81
+ delivery.id = idx;
82
+ }
83
+
84
+ for (idx, vehicle) in self.vehicles.iter_mut().enumerate() {
85
+ vehicle.id = idx;
86
+ vehicle.prepared_routing = None;
87
+ vehicle.delivery_order = vehicle
88
+ .delivery_order
89
+ .iter()
90
+ .filter_map(|old_id| delivery_id_map.get(old_id).copied())
91
+ .collect();
92
+ vehicle.refresh_route_shadows();
93
+ }
94
+ self.prepared_problem_data.clear();
95
+ }
96
+
97
+ /// Removes one delivery id from every route before insertion previewing.
98
+ pub fn remove_delivery_assignments(&mut self, delivery_id: usize) {
99
+ for vehicle in &mut self.vehicles {
100
+ vehicle
101
+ .delivery_order
102
+ .retain(|assigned| *assigned != delivery_id);
103
+ }
104
+ }
105
+
106
+ /// List-variable post-update hook used by SolverForge shadow variables.
107
+ pub fn refresh_vehicle_route_shadows(&mut self, vehicle_idx: usize) {
108
+ if let Some(vehicle) = self.vehicles.get_mut(vehicle_idx) {
109
+ vehicle.refresh_route_shadows();
110
+ }
111
+ }
112
+
113
+ /// Clones the plan and attaches the UI-facing route preview.
114
+ pub fn refreshed_for_transport(&self) -> Self {
115
+ let mut plan = self.clone();
116
+ plan.view_state.preview = Some(preview_for_plan(&plan));
117
+ plan
118
+ }
119
+ }
120
+
121
+ impl solverforge::cvrp::VrpSolution for Plan {
122
+ /// Gives SolverForge's CVRP move selectors access to prepared matrices.
123
+ fn vehicle_data_ptr(&self, entity_idx: usize) -> *const ProblemData {
124
+ self.vehicles[entity_idx]
125
+ .prepared_routing
126
+ .as_ref()
127
+ .and_then(|prepared| self.prepared_problem_data.get(prepared.problem_data_index))
128
+ .map(Arc::as_ptr)
129
+ .unwrap_or(std::ptr::null())
130
+ }
131
+
132
+ /// Reads the mutable list variable for one vehicle as visit ids.
133
+ fn vehicle_visits(&self, entity_idx: usize) -> &[usize] {
134
+ &self.vehicles[entity_idx].delivery_order
135
+ }
136
+
137
+ /// Lets CVRP construction and local-search hooks replace one vehicle route.
138
+ fn vehicle_visits_mut(&mut self, entity_idx: usize) -> &mut Vec<usize> {
139
+ &mut self.vehicles[entity_idx].delivery_order
140
+ }
141
+
142
+ /// Reports the number of route-owning planning entities.
143
+ fn vehicle_count(&self) -> usize {
144
+ self.vehicles.len()
145
+ }
146
+ }
147
+
148
+ #[cfg(test)]
149
+ impl Plan {
150
+ pub(crate) fn test_has_list_variable() -> bool {
151
+ Self::__solverforge_has_list_variable()
152
+ }
153
+
154
+ pub(crate) fn test_total_list_entities(plan: &Self) -> usize {
155
+ Self::__solverforge_total_list_entities(plan)
156
+ }
157
+
158
+ pub(crate) fn test_total_list_elements(plan: &Self) -> usize {
159
+ Self::__solverforge_total_list_elements(plan)
160
+ }
161
+
162
+ pub(crate) fn test_solve_with_config(plan: Self, config: &solverforge::SolverConfig) -> Self {
163
+ solverforge::__internal::try_run_solver_with_config_and_search(
164
+ plan,
165
+ crate::constraints::create_constraints(),
166
+ Self::descriptor(),
167
+ Self::entity_count,
168
+ solverforge::SolverRuntime::detached(),
169
+ config.clone(),
170
+ Self::__solverforge_default_time_limit_secs(),
171
+ Self::__solverforge_log_scale,
172
+ None,
173
+ Self::__solverforge_search_declaration,
174
+ )
175
+ .unwrap_or_else(|error| panic!("test solver config should compile and run: {error}"))
176
+ }
177
+ }
src/domain/plan_tests.rs ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::*;
2
+ use crate::domain::{DeliveryKind, UNASSIGNED_DELIVERY_HARD_PENALTY};
3
+ use solverforge::cvrp::ProblemData;
4
+ use solverforge::{ScoreDirector, SolverConfig, SolverEvent, SolverManager};
5
+ use std::sync::Arc;
6
+
7
+ fn tiny_plan() -> Plan {
8
+ Plan::new(
9
+ "tiny",
10
+ vec![
11
+ Delivery::new(
12
+ 0,
13
+ "A",
14
+ DeliveryKind::Residential,
15
+ (39.9526, -75.1652),
16
+ 1,
17
+ (8 * 3600, 18 * 3600),
18
+ 10 * 60,
19
+ ),
20
+ Delivery::new(
21
+ 1,
22
+ "B",
23
+ DeliveryKind::Business,
24
+ (39.9626, -75.1752),
25
+ 1,
26
+ (8 * 3600, 18 * 3600),
27
+ 10 * 60,
28
+ ),
29
+ ],
30
+ vec![Vehicle::new(0, "Van 1", 4, 39.9526, -75.1652, 8 * 3600)],
31
+ )
32
+ }
33
+
34
+ fn prepared_tiny_plan_with_route() -> Plan {
35
+ let mut plan = tiny_plan();
36
+ plan.vehicles[0].delivery_order = vec![0, 1];
37
+ attach_test_routing(&mut plan);
38
+ plan
39
+ }
40
+
41
+ fn prepared_tiny_plan() -> Plan {
42
+ let mut plan = tiny_plan();
43
+ attach_test_routing(&mut plan);
44
+ plan
45
+ }
46
+
47
+ fn attach_test_routing(plan: &mut Plan) {
48
+ let delivery_count = plan.deliveries.len();
49
+ let demands = plan
50
+ .deliveries
51
+ .iter()
52
+ .map(|delivery| delivery.demand)
53
+ .collect::<Vec<_>>();
54
+ let time_windows = plan
55
+ .deliveries
56
+ .iter()
57
+ .map(|delivery| (delivery.min_start_time, delivery.max_end_time))
58
+ .collect::<Vec<_>>();
59
+ let service_durations = plan
60
+ .deliveries
61
+ .iter()
62
+ .map(|delivery| delivery.service_duration)
63
+ .collect::<Vec<_>>();
64
+ let travel_times = vec![vec![0, 600], vec![660, 0]];
65
+ let distances = vec![vec![0, 3_000], vec![3_300, 0]];
66
+ let depot_to_delivery_seconds = vec![300, 900];
67
+ let delivery_to_depot_seconds = vec![360, 840];
68
+ let depot_to_delivery_meters = vec![1_500, 4_500];
69
+ let delivery_to_depot_meters = vec![1_800, 4_200];
70
+
71
+ plan.prepared_problem_data.clear();
72
+ for (vehicle_idx, vehicle) in plan.vehicles.iter_mut().enumerate() {
73
+ let mut problem_matrix = vec![vec![0_i64; delivery_count + 1]; delivery_count + 1];
74
+ for (from, row) in travel_times.iter().enumerate() {
75
+ for (to, seconds) in row.iter().copied().enumerate() {
76
+ problem_matrix[from][to] = seconds;
77
+ }
78
+ }
79
+ for (delivery_idx, seconds) in depot_to_delivery_seconds.iter().copied().enumerate() {
80
+ problem_matrix[delivery_count][delivery_idx] = seconds;
81
+ }
82
+ for (delivery_idx, seconds) in delivery_to_depot_seconds.iter().copied().enumerate() {
83
+ problem_matrix[delivery_idx][delivery_count] = seconds;
84
+ }
85
+
86
+ plan.prepared_problem_data.push(Arc::new(ProblemData {
87
+ capacity: vehicle.capacity as i64,
88
+ depot: delivery_count,
89
+ demands: demands.clone(),
90
+ distance_matrix: problem_matrix.clone(),
91
+ time_windows: time_windows.clone(),
92
+ service_durations: service_durations.clone(),
93
+ travel_times: problem_matrix,
94
+ vehicle_departure_time: vehicle.departure_time,
95
+ }));
96
+ vehicle.prepared_routing = Some(PreparedVehicleRouting {
97
+ problem_data_index: vehicle_idx,
98
+ capacity: vehicle.capacity as i64,
99
+ demands: demands.clone(),
100
+ distance_matrix: distances.clone(),
101
+ time_windows: time_windows.clone(),
102
+ service_durations: service_durations.clone(),
103
+ travel_times: travel_times.clone(),
104
+ vehicle_departure_time: vehicle.departure_time,
105
+ depot_to_delivery_seconds: depot_to_delivery_seconds.clone(),
106
+ delivery_to_depot_seconds: delivery_to_depot_seconds.clone(),
107
+ depot_to_delivery_meters: depot_to_delivery_meters.clone(),
108
+ delivery_to_depot_meters: delivery_to_depot_meters.clone(),
109
+ });
110
+ }
111
+ }
112
+
113
+ #[test]
114
+ fn route_shadow_listener_populates_vehicle_route_shadows() {
115
+ let mut plan = prepared_tiny_plan_with_route();
116
+ assert_eq!(
117
+ plan.vehicles[0].route_total_demand, 0,
118
+ "prepared transport data should not eagerly populate solver shadows"
119
+ );
120
+
121
+ plan.refresh_vehicle_route_shadows(0);
122
+ let vehicle = &plan.vehicles[0];
123
+
124
+ assert_eq!(vehicle.total_assigned_demand(), 2);
125
+ assert_eq!(vehicle.capacity_overage(), 0);
126
+ assert!(
127
+ vehicle.total_travel_seconds() > 0,
128
+ "route travel should be maintained as a shadow value"
129
+ );
130
+ }
131
+
132
+ #[test]
133
+ fn vehicle_route_shadows_refresh_after_list_variable_changes() {
134
+ let plan = prepared_tiny_plan_with_route();
135
+ let mut director = ScoreDirector::with_descriptor(
136
+ plan,
137
+ crate::constraints::create_constraints(),
138
+ Plan::descriptor(),
139
+ Plan::entity_count,
140
+ );
141
+ director.calculate_score();
142
+ assert_eq!(
143
+ director.working_solution().vehicles[0].total_assigned_demand(),
144
+ 2
145
+ );
146
+
147
+ director.before_variable_changed(0, 0);
148
+ director.working_solution_mut().vehicles[0]
149
+ .delivery_order
150
+ .clear();
151
+ director.after_variable_changed(0, 0);
152
+ let score = director.calculate_score();
153
+
154
+ let vehicle = &director.working_solution().vehicles[0];
155
+ assert_eq!(vehicle.total_assigned_demand(), 0);
156
+ assert_eq!(vehicle.total_travel_seconds(), 0);
157
+ assert_eq!(vehicle.time_window_violation_seconds(), 0);
158
+ assert_eq!(score.hard(), -(2 * UNASSIGNED_DELIVERY_HARD_PENALTY));
159
+ }
160
+
161
+ #[test]
162
+ fn generated_list_runtime_builds_routes() {
163
+ static MANAGER: SolverManager<Plan> = SolverManager::new();
164
+
165
+ let plan = prepared_tiny_plan();
166
+
167
+ assert!(
168
+ Plan::test_has_list_variable(),
169
+ "delivery plan should expose a list variable"
170
+ );
171
+ assert_eq!(Plan::test_total_list_entities(&plan), 1);
172
+ assert_eq!(Plan::test_total_list_elements(&plan), 2);
173
+ let config =
174
+ SolverConfig::from_toml_str(include_str!("../../solver.toml")).expect("valid config");
175
+ assert_eq!(
176
+ config.phases.len(),
177
+ 3,
178
+ "expected Clarke-Wright construction + list k-opt + local search"
179
+ );
180
+
181
+ let (job_id, mut receiver) = MANAGER.solve(plan).expect("solve should start");
182
+ let mut saw_non_empty_best = false;
183
+ loop {
184
+ match receiver
185
+ .blocking_recv()
186
+ .expect("event stream should reach a terminal event")
187
+ {
188
+ SolverEvent::BestSolution { solution, .. } => {
189
+ if solution
190
+ .vehicles
191
+ .iter()
192
+ .any(|vehicle| !vehicle.delivery_order.is_empty())
193
+ {
194
+ saw_non_empty_best = true;
195
+ MANAGER.cancel(job_id).expect("job cancel should succeed");
196
+ }
197
+ }
198
+ SolverEvent::Completed { .. } | SolverEvent::Cancelled { .. } => break,
199
+ SolverEvent::Failed { error, .. } => {
200
+ panic!("solve unexpectedly failed: {error}");
201
+ }
202
+ SolverEvent::Progress { .. }
203
+ | SolverEvent::PauseRequested { .. }
204
+ | SolverEvent::Paused { .. }
205
+ | SolverEvent::Resumed { .. } => {}
206
+ }
207
+ }
208
+ MANAGER
209
+ .delete(job_id)
210
+ .expect("completed test job should delete");
211
+
212
+ assert!(
213
+ saw_non_empty_best,
214
+ "expected a non-empty best solution before cancellation"
215
+ );
216
+ }