github-actions[bot] commited on
Commit
f6213fc
·
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 +92 -0
  6. CHANGELOG.md +19 -0
  7. Cargo.lock +2673 -0
  8. Cargo.toml +37 -0
  9. Dockerfile +34 -0
  10. Makefile +282 -0
  11. README.md +206 -0
  12. WIREFRAME.md +241 -0
  13. docs/screenshot.png +3 -0
  14. solver.toml +77 -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 +38 -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 +32 -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 +134 -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/coord_value.rs +39 -0
  46. src/domain/delivery.rs +95 -0
  47. src/domain/mod.rs +42 -0
  48. src/domain/plan.rs +169 -0
  49. src/domain/plan_tests.rs +243 -0
  50. src/domain/preview.rs +105 -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,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 `1.0.1`, and the release binary is `solverforge_deliveries`.
7
+
8
+ - `src/domain/mod.rs` owns the `solverforge::planning_model!` manifest.
9
+ - `src/domain/plan.rs` owns the `Plan` planning solution.
10
+ - `src/domain/delivery.rs` owns the `Delivery` problem fact.
11
+ - `src/domain/vehicle.rs` owns the `Vehicle` planning entity and its
12
+ `delivery_order` list variable.
13
+ - `src/domain/preview.rs` owns transport/view preview structs.
14
+ - `src/domain/route_metrics/` owns route preparation, CVRP hooks, scoring
15
+ preview, route geometry, and insertion ranking.
16
+ - `src/constraints/` owns one score rule per file plus `mod.rs` assembly.
17
+ - `src/data/data_seed/` owns deterministic city demo-data modules with grouped
18
+ visit files for scaled delivery counts.
19
+ - `src/api/` owns REST, DTO, and SSE surfaces.
20
+ - `src/solver/` owns retained-job runtime orchestration.
21
+ - `static/app/models/` owns frontend plan modeling.
22
+ - `static/app/ui/` owns browser layout and rendering helpers.
23
+
24
+ Keep the canonical solution name `Plan`. Do not reintroduce `DeliveryPlan` or
25
+ `delivery_plan.rs`.
26
+
27
+ ## File Size Rule
28
+
29
+ No source, test, frontend, config, or repo documentation file should reach 300
30
+ lines. Split by responsibility before that point. Large generated/cache output
31
+ under `target/` and `.osm_cache/` is outside this rule.
32
+
33
+ ## Build And Validation Commands
34
+
35
+ - `make help` shows the supported command surface.
36
+ - `make run-release` runs the app locally on `:7860`.
37
+ - `make test` runs Rust, frontend, and Playwright browser tests.
38
+ - `make test-e2e` runs the real browser Playwright smoke.
39
+ - `make space-build` builds the Docker image used by Hugging Face Spaces.
40
+ - `make space-run` builds and runs that image locally.
41
+ - `make ci-local` runs formatting, clippy, release build, standard tests, and
42
+ the Space Docker image build.
43
+ - `make test-live-road` runs the env-enabled road-network smoke test.
44
+ - `make pre-release` runs `ci-local` and the live road-network smoke.
45
+ - `cargo test` runs Rust unit and integration tests.
46
+ - `node --test tests/frontend_models.test.mjs` runs frontend model tests.
47
+
48
+ Use the Makefile as the authoritative local workflow, matching the
49
+ `solverforge-hospital` Space/Docker validation standard.
50
+
51
+ ## No Suppression Policy
52
+
53
+ Do not add warning suppressions, fallback compatibility branches, or unused
54
+ helper modules. If a split creates unused code, restructure the modules so each
55
+ compiled item is used by its crate.
56
+
57
+ ## Documentation Policy
58
+
59
+ Keep `Cargo.toml`, `Cargo.lock`, `Makefile`, `Dockerfile`, `README.md`,
60
+ `WIREFRAME.md`, `AGENTS.md`, `docs/screenshot.png`,
61
+ `solverforge.app.toml`, `solver.toml`, `static/sf-config.json`, and the visible
62
+ API guide in
63
+ `static/app/ui/api-guide.mjs` aligned.
64
+
65
+ When changing routes, solver policy, demo IDs, dependency sources, file layout,
66
+ or browser behavior, update the docs and screenshot in the same patch. Prefer
67
+ current-state documentation over planning language.
68
+
69
+ ## Testing Guidance
70
+
71
+ Add Rust unit tests next to the behavior they protect. Add API integration
72
+ coverage under `tests/api_contract/` and shared integration helpers under
73
+ `tests/support/` only when every helper is used by the single
74
+ `tests/api_contract.rs` crate. Add frontend model tests in
75
+ `tests/frontend_models.test.mjs`, and browser-flow tests in `tests/e2e/`.
76
+
77
+ Road-network tests should stay env-gated unless the test uses only cached or
78
+ straight-line behavior. Use `SOLVERFORGE_RUN_LIVE_TESTS=1` through
79
+ `make test-live-road` when validating live map/routing paths.
80
+
81
+ ## Runtime Notes
82
+
83
+ `solver.toml` is embedded by `Plan` through the planning-solution macro. Treat
84
+ it as the solver policy source of truth.
85
+
86
+ `Cargo.toml` currently uses Rust `1.95` and crates.io dependency declarations.
87
+ Keep every direct dependency declaration, `solverforge.app.toml`, `Cargo.lock`,
88
+ and docs truthful if dependency sources or versions change.
89
+
90
+ The app serves stock `solverforge-ui` assets, local static app modules, and
91
+ Axum API routes from one process. Retained solver jobs are controlled through
92
+ REST and observed through SSE.
CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Changelog
2
+
3
+ All notable changes to this use case are documented in this file.
4
+
5
+ ## 2.0.0 (2026-05-14)
6
+
7
+ ### Maintenance
8
+
9
+ * **release:** set the public app release line to 2.0.0 across Cargo metadata and release validation.
10
+
11
+ ## 1.0.1 (2026-05-14)
12
+
13
+ ### Features
14
+
15
+ * **deliveries:** publish the SolverForge deliveries use case in the bundle.
16
+
17
+ ### Maintenance
18
+
19
+ * **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,2673 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.16.3"
35
+ source = "registry+https://github.com/rust-lang/crates.io-index"
36
+ checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f"
37
+ dependencies = [
38
+ "aws-lc-sys",
39
+ "zeroize",
40
+ ]
41
+
42
+ [[package]]
43
+ name = "aws-lc-sys"
44
+ version = "0.40.0"
45
+ source = "registry+https://github.com/rust-lang/crates.io-index"
46
+ checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7"
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.11.1"
115
+ source = "registry+https://github.com/rust-lang/crates.io-index"
116
+ checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
117
+
118
+ [[package]]
119
+ name = "bumpalo"
120
+ version = "3.20.2"
121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
122
+ checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
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.62"
133
+ source = "registry+https://github.com/rust-lang/crates.io-index"
134
+ checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
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.5"
247
+ source = "registry+https://github.com/rust-lang/crates.io-index"
248
+ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
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.15.0"
264
+ source = "registry+https://github.com/rust-lang/crates.io-index"
265
+ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
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.0"
448
+ source = "registry+https://github.com/rust-lang/crates.io-index"
449
+ checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
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.9.0"
499
+ source = "registry+https://github.com/rust-lang/crates.io-index"
500
+ checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
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.98"
772
+ source = "registry+https://github.com/rust-lang/crates.io-index"
773
+ checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
774
+ dependencies = [
775
+ "cfg-if",
776
+ "futures-util",
777
+ "once_cell",
778
+ "wasm-bindgen",
779
+ ]
780
+
781
+ [[package]]
782
+ name = "lazy_static"
783
+ version = "1.5.0"
784
+ source = "registry+https://github.com/rust-lang/crates.io-index"
785
+ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
786
+
787
+ [[package]]
788
+ name = "leb128fmt"
789
+ version = "0.1.0"
790
+ source = "registry+https://github.com/rust-lang/crates.io-index"
791
+ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
792
+
793
+ [[package]]
794
+ name = "libc"
795
+ version = "0.2.186"
796
+ source = "registry+https://github.com/rust-lang/crates.io-index"
797
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
798
+
799
+ [[package]]
800
+ name = "litemap"
801
+ version = "0.8.2"
802
+ source = "registry+https://github.com/rust-lang/crates.io-index"
803
+ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
804
+
805
+ [[package]]
806
+ name = "lock_api"
807
+ version = "0.4.14"
808
+ source = "registry+https://github.com/rust-lang/crates.io-index"
809
+ checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
810
+ dependencies = [
811
+ "scopeguard",
812
+ ]
813
+
814
+ [[package]]
815
+ name = "log"
816
+ version = "0.4.29"
817
+ source = "registry+https://github.com/rust-lang/crates.io-index"
818
+ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
819
+
820
+ [[package]]
821
+ name = "lru-slab"
822
+ version = "0.1.2"
823
+ source = "registry+https://github.com/rust-lang/crates.io-index"
824
+ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
825
+
826
+ [[package]]
827
+ name = "matchers"
828
+ version = "0.2.0"
829
+ source = "registry+https://github.com/rust-lang/crates.io-index"
830
+ checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
831
+ dependencies = [
832
+ "regex-automata",
833
+ ]
834
+
835
+ [[package]]
836
+ name = "matchit"
837
+ version = "0.8.4"
838
+ source = "registry+https://github.com/rust-lang/crates.io-index"
839
+ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
840
+
841
+ [[package]]
842
+ name = "memchr"
843
+ version = "2.8.0"
844
+ source = "registry+https://github.com/rust-lang/crates.io-index"
845
+ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
846
+
847
+ [[package]]
848
+ name = "mime"
849
+ version = "0.3.17"
850
+ source = "registry+https://github.com/rust-lang/crates.io-index"
851
+ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
852
+
853
+ [[package]]
854
+ name = "mime_guess"
855
+ version = "2.0.5"
856
+ source = "registry+https://github.com/rust-lang/crates.io-index"
857
+ checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
858
+ dependencies = [
859
+ "mime",
860
+ "unicase",
861
+ ]
862
+
863
+ [[package]]
864
+ name = "mio"
865
+ version = "1.2.0"
866
+ source = "registry+https://github.com/rust-lang/crates.io-index"
867
+ checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
868
+ dependencies = [
869
+ "libc",
870
+ "wasi",
871
+ "windows-sys 0.61.2",
872
+ ]
873
+
874
+ [[package]]
875
+ name = "nu-ansi-term"
876
+ version = "0.50.3"
877
+ source = "registry+https://github.com/rust-lang/crates.io-index"
878
+ checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
879
+ dependencies = [
880
+ "windows-sys 0.61.2",
881
+ ]
882
+
883
+ [[package]]
884
+ name = "num-format"
885
+ version = "0.4.4"
886
+ source = "registry+https://github.com/rust-lang/crates.io-index"
887
+ checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
888
+ dependencies = [
889
+ "arrayvec",
890
+ "itoa",
891
+ ]
892
+
893
+ [[package]]
894
+ name = "once_cell"
895
+ version = "1.21.4"
896
+ source = "registry+https://github.com/rust-lang/crates.io-index"
897
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
898
+
899
+ [[package]]
900
+ name = "openssl-probe"
901
+ version = "0.2.1"
902
+ source = "registry+https://github.com/rust-lang/crates.io-index"
903
+ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
904
+
905
+ [[package]]
906
+ name = "owo-colors"
907
+ version = "4.3.0"
908
+ source = "registry+https://github.com/rust-lang/crates.io-index"
909
+ checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
910
+
911
+ [[package]]
912
+ name = "parking_lot"
913
+ version = "0.12.5"
914
+ source = "registry+https://github.com/rust-lang/crates.io-index"
915
+ checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
916
+ dependencies = [
917
+ "lock_api",
918
+ "parking_lot_core",
919
+ ]
920
+
921
+ [[package]]
922
+ name = "parking_lot_core"
923
+ version = "0.9.12"
924
+ source = "registry+https://github.com/rust-lang/crates.io-index"
925
+ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
926
+ dependencies = [
927
+ "cfg-if",
928
+ "libc",
929
+ "redox_syscall",
930
+ "smallvec",
931
+ "windows-link",
932
+ ]
933
+
934
+ [[package]]
935
+ name = "percent-encoding"
936
+ version = "2.3.2"
937
+ source = "registry+https://github.com/rust-lang/crates.io-index"
938
+ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
939
+
940
+ [[package]]
941
+ name = "pin-project-lite"
942
+ version = "0.2.17"
943
+ source = "registry+https://github.com/rust-lang/crates.io-index"
944
+ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
945
+
946
+ [[package]]
947
+ name = "potential_utf"
948
+ version = "0.1.5"
949
+ source = "registry+https://github.com/rust-lang/crates.io-index"
950
+ checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
951
+ dependencies = [
952
+ "zerovec",
953
+ ]
954
+
955
+ [[package]]
956
+ name = "ppv-lite86"
957
+ version = "0.2.21"
958
+ source = "registry+https://github.com/rust-lang/crates.io-index"
959
+ checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
960
+ dependencies = [
961
+ "zerocopy",
962
+ ]
963
+
964
+ [[package]]
965
+ name = "prettyplease"
966
+ version = "0.2.37"
967
+ source = "registry+https://github.com/rust-lang/crates.io-index"
968
+ checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
969
+ dependencies = [
970
+ "proc-macro2",
971
+ "syn",
972
+ ]
973
+
974
+ [[package]]
975
+ name = "proc-macro2"
976
+ version = "1.0.106"
977
+ source = "registry+https://github.com/rust-lang/crates.io-index"
978
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
979
+ dependencies = [
980
+ "unicode-ident",
981
+ ]
982
+
983
+ [[package]]
984
+ name = "quinn"
985
+ version = "0.11.9"
986
+ source = "registry+https://github.com/rust-lang/crates.io-index"
987
+ checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
988
+ dependencies = [
989
+ "bytes",
990
+ "cfg_aliases",
991
+ "pin-project-lite",
992
+ "quinn-proto",
993
+ "quinn-udp",
994
+ "rustc-hash",
995
+ "rustls",
996
+ "socket2",
997
+ "thiserror",
998
+ "tokio",
999
+ "tracing",
1000
+ "web-time",
1001
+ ]
1002
+
1003
+ [[package]]
1004
+ name = "quinn-proto"
1005
+ version = "0.11.14"
1006
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1007
+ checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
1008
+ dependencies = [
1009
+ "aws-lc-rs",
1010
+ "bytes",
1011
+ "getrandom 0.3.4",
1012
+ "lru-slab",
1013
+ "rand 0.9.4",
1014
+ "ring",
1015
+ "rustc-hash",
1016
+ "rustls",
1017
+ "rustls-pki-types",
1018
+ "slab",
1019
+ "thiserror",
1020
+ "tinyvec",
1021
+ "tracing",
1022
+ "web-time",
1023
+ ]
1024
+
1025
+ [[package]]
1026
+ name = "quinn-udp"
1027
+ version = "0.5.14"
1028
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1029
+ checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
1030
+ dependencies = [
1031
+ "cfg_aliases",
1032
+ "libc",
1033
+ "once_cell",
1034
+ "socket2",
1035
+ "tracing",
1036
+ "windows-sys 0.60.2",
1037
+ ]
1038
+
1039
+ [[package]]
1040
+ name = "quote"
1041
+ version = "1.0.45"
1042
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1043
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
1044
+ dependencies = [
1045
+ "proc-macro2",
1046
+ ]
1047
+
1048
+ [[package]]
1049
+ name = "r-efi"
1050
+ version = "5.3.0"
1051
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1052
+ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
1053
+
1054
+ [[package]]
1055
+ name = "r-efi"
1056
+ version = "6.0.0"
1057
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1058
+ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
1059
+
1060
+ [[package]]
1061
+ name = "rand"
1062
+ version = "0.9.4"
1063
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1064
+ checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
1065
+ dependencies = [
1066
+ "rand_chacha 0.9.0",
1067
+ "rand_core 0.9.5",
1068
+ ]
1069
+
1070
+ [[package]]
1071
+ name = "rand"
1072
+ version = "0.10.1"
1073
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1074
+ checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
1075
+ dependencies = [
1076
+ "chacha20",
1077
+ "getrandom 0.4.2",
1078
+ "rand_core 0.10.1",
1079
+ ]
1080
+
1081
+ [[package]]
1082
+ name = "rand_chacha"
1083
+ version = "0.9.0"
1084
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1085
+ checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
1086
+ dependencies = [
1087
+ "ppv-lite86",
1088
+ "rand_core 0.9.5",
1089
+ ]
1090
+
1091
+ [[package]]
1092
+ name = "rand_chacha"
1093
+ version = "0.10.0"
1094
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1095
+ checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb"
1096
+ dependencies = [
1097
+ "ppv-lite86",
1098
+ "rand_core 0.10.1",
1099
+ ]
1100
+
1101
+ [[package]]
1102
+ name = "rand_core"
1103
+ version = "0.9.5"
1104
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1105
+ checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
1106
+ dependencies = [
1107
+ "getrandom 0.3.4",
1108
+ ]
1109
+
1110
+ [[package]]
1111
+ name = "rand_core"
1112
+ version = "0.10.1"
1113
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1114
+ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
1115
+
1116
+ [[package]]
1117
+ name = "rayon"
1118
+ version = "1.12.0"
1119
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1120
+ checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
1121
+ dependencies = [
1122
+ "either",
1123
+ "rayon-core",
1124
+ ]
1125
+
1126
+ [[package]]
1127
+ name = "rayon-core"
1128
+ version = "1.13.0"
1129
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1130
+ checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
1131
+ dependencies = [
1132
+ "crossbeam-deque",
1133
+ "crossbeam-utils",
1134
+ ]
1135
+
1136
+ [[package]]
1137
+ name = "redox_syscall"
1138
+ version = "0.5.18"
1139
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1140
+ checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
1141
+ dependencies = [
1142
+ "bitflags",
1143
+ ]
1144
+
1145
+ [[package]]
1146
+ name = "regex-automata"
1147
+ version = "0.4.14"
1148
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1149
+ checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
1150
+ dependencies = [
1151
+ "aho-corasick",
1152
+ "memchr",
1153
+ "regex-syntax",
1154
+ ]
1155
+
1156
+ [[package]]
1157
+ name = "regex-syntax"
1158
+ version = "0.8.10"
1159
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1160
+ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
1161
+
1162
+ [[package]]
1163
+ name = "reqwest"
1164
+ version = "0.13.3"
1165
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1166
+ checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0"
1167
+ dependencies = [
1168
+ "base64",
1169
+ "bytes",
1170
+ "encoding_rs",
1171
+ "futures-core",
1172
+ "h2",
1173
+ "http",
1174
+ "http-body",
1175
+ "http-body-util",
1176
+ "hyper",
1177
+ "hyper-rustls",
1178
+ "hyper-util",
1179
+ "js-sys",
1180
+ "log",
1181
+ "mime",
1182
+ "percent-encoding",
1183
+ "pin-project-lite",
1184
+ "quinn",
1185
+ "rustls",
1186
+ "rustls-pki-types",
1187
+ "rustls-platform-verifier",
1188
+ "serde",
1189
+ "serde_json",
1190
+ "sync_wrapper",
1191
+ "tokio",
1192
+ "tokio-rustls",
1193
+ "tower",
1194
+ "tower-http",
1195
+ "tower-service",
1196
+ "url",
1197
+ "wasm-bindgen",
1198
+ "wasm-bindgen-futures",
1199
+ "web-sys",
1200
+ ]
1201
+
1202
+ [[package]]
1203
+ name = "ring"
1204
+ version = "0.17.14"
1205
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1206
+ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
1207
+ dependencies = [
1208
+ "cc",
1209
+ "cfg-if",
1210
+ "getrandom 0.2.17",
1211
+ "libc",
1212
+ "untrusted",
1213
+ "windows-sys 0.52.0",
1214
+ ]
1215
+
1216
+ [[package]]
1217
+ name = "rustc-hash"
1218
+ version = "2.1.2"
1219
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1220
+ checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
1221
+
1222
+ [[package]]
1223
+ name = "rustc_version"
1224
+ version = "0.4.1"
1225
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1226
+ checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
1227
+ dependencies = [
1228
+ "semver",
1229
+ ]
1230
+
1231
+ [[package]]
1232
+ name = "rustls"
1233
+ version = "0.23.40"
1234
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1235
+ checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
1236
+ dependencies = [
1237
+ "aws-lc-rs",
1238
+ "once_cell",
1239
+ "rustls-pki-types",
1240
+ "rustls-webpki",
1241
+ "subtle",
1242
+ "zeroize",
1243
+ ]
1244
+
1245
+ [[package]]
1246
+ name = "rustls-native-certs"
1247
+ version = "0.8.3"
1248
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1249
+ checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
1250
+ dependencies = [
1251
+ "openssl-probe",
1252
+ "rustls-pki-types",
1253
+ "schannel",
1254
+ "security-framework",
1255
+ ]
1256
+
1257
+ [[package]]
1258
+ name = "rustls-pki-types"
1259
+ version = "1.14.1"
1260
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1261
+ checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
1262
+ dependencies = [
1263
+ "web-time",
1264
+ "zeroize",
1265
+ ]
1266
+
1267
+ [[package]]
1268
+ name = "rustls-platform-verifier"
1269
+ version = "0.7.0"
1270
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1271
+ checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
1272
+ dependencies = [
1273
+ "core-foundation 0.10.1",
1274
+ "core-foundation-sys",
1275
+ "jni",
1276
+ "log",
1277
+ "once_cell",
1278
+ "rustls",
1279
+ "rustls-native-certs",
1280
+ "rustls-platform-verifier-android",
1281
+ "rustls-webpki",
1282
+ "security-framework",
1283
+ "security-framework-sys",
1284
+ "webpki-root-certs",
1285
+ "windows-sys 0.61.2",
1286
+ ]
1287
+
1288
+ [[package]]
1289
+ name = "rustls-platform-verifier-android"
1290
+ version = "0.1.1"
1291
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1292
+ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
1293
+
1294
+ [[package]]
1295
+ name = "rustls-webpki"
1296
+ version = "0.103.13"
1297
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1298
+ checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
1299
+ dependencies = [
1300
+ "aws-lc-rs",
1301
+ "ring",
1302
+ "rustls-pki-types",
1303
+ "untrusted",
1304
+ ]
1305
+
1306
+ [[package]]
1307
+ name = "rustversion"
1308
+ version = "1.0.22"
1309
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1310
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
1311
+
1312
+ [[package]]
1313
+ name = "ryu"
1314
+ version = "1.0.23"
1315
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1316
+ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
1317
+
1318
+ [[package]]
1319
+ name = "same-file"
1320
+ version = "1.0.6"
1321
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1322
+ checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
1323
+ dependencies = [
1324
+ "winapi-util",
1325
+ ]
1326
+
1327
+ [[package]]
1328
+ name = "schannel"
1329
+ version = "0.1.29"
1330
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1331
+ checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
1332
+ dependencies = [
1333
+ "windows-sys 0.61.2",
1334
+ ]
1335
+
1336
+ [[package]]
1337
+ name = "scopeguard"
1338
+ version = "1.2.0"
1339
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1340
+ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
1341
+
1342
+ [[package]]
1343
+ name = "security-framework"
1344
+ version = "3.7.0"
1345
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1346
+ checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
1347
+ dependencies = [
1348
+ "bitflags",
1349
+ "core-foundation 0.10.1",
1350
+ "core-foundation-sys",
1351
+ "libc",
1352
+ "security-framework-sys",
1353
+ ]
1354
+
1355
+ [[package]]
1356
+ name = "security-framework-sys"
1357
+ version = "2.17.0"
1358
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1359
+ checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
1360
+ dependencies = [
1361
+ "core-foundation-sys",
1362
+ "libc",
1363
+ ]
1364
+
1365
+ [[package]]
1366
+ name = "semver"
1367
+ version = "1.0.28"
1368
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1369
+ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
1370
+
1371
+ [[package]]
1372
+ name = "serde"
1373
+ version = "1.0.228"
1374
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1375
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
1376
+ dependencies = [
1377
+ "serde_core",
1378
+ "serde_derive",
1379
+ ]
1380
+
1381
+ [[package]]
1382
+ name = "serde_core"
1383
+ version = "1.0.228"
1384
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1385
+ checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
1386
+ dependencies = [
1387
+ "serde_derive",
1388
+ ]
1389
+
1390
+ [[package]]
1391
+ name = "serde_derive"
1392
+ version = "1.0.228"
1393
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1394
+ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
1395
+ dependencies = [
1396
+ "proc-macro2",
1397
+ "quote",
1398
+ "syn",
1399
+ ]
1400
+
1401
+ [[package]]
1402
+ name = "serde_json"
1403
+ version = "1.0.149"
1404
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1405
+ checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
1406
+ dependencies = [
1407
+ "itoa",
1408
+ "memchr",
1409
+ "serde",
1410
+ "serde_core",
1411
+ "zmij",
1412
+ ]
1413
+
1414
+ [[package]]
1415
+ name = "serde_path_to_error"
1416
+ version = "0.1.20"
1417
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1418
+ checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
1419
+ dependencies = [
1420
+ "itoa",
1421
+ "serde",
1422
+ "serde_core",
1423
+ ]
1424
+
1425
+ [[package]]
1426
+ name = "serde_spanned"
1427
+ version = "1.1.1"
1428
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1429
+ checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
1430
+ dependencies = [
1431
+ "serde_core",
1432
+ ]
1433
+
1434
+ [[package]]
1435
+ name = "serde_urlencoded"
1436
+ version = "0.7.1"
1437
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1438
+ checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
1439
+ dependencies = [
1440
+ "form_urlencoded",
1441
+ "itoa",
1442
+ "ryu",
1443
+ "serde",
1444
+ ]
1445
+
1446
+ [[package]]
1447
+ name = "serde_yaml"
1448
+ version = "0.9.34+deprecated"
1449
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1450
+ checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
1451
+ dependencies = [
1452
+ "indexmap",
1453
+ "itoa",
1454
+ "ryu",
1455
+ "serde",
1456
+ "unsafe-libyaml",
1457
+ ]
1458
+
1459
+ [[package]]
1460
+ name = "sharded-slab"
1461
+ version = "0.1.7"
1462
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1463
+ checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
1464
+ dependencies = [
1465
+ "lazy_static",
1466
+ ]
1467
+
1468
+ [[package]]
1469
+ name = "shlex"
1470
+ version = "1.3.0"
1471
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1472
+ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
1473
+
1474
+ [[package]]
1475
+ name = "signal-hook-registry"
1476
+ version = "1.4.8"
1477
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1478
+ checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
1479
+ dependencies = [
1480
+ "errno",
1481
+ "libc",
1482
+ ]
1483
+
1484
+ [[package]]
1485
+ name = "simd_cesu8"
1486
+ version = "1.1.1"
1487
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1488
+ checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
1489
+ dependencies = [
1490
+ "rustc_version",
1491
+ "simdutf8",
1492
+ ]
1493
+
1494
+ [[package]]
1495
+ name = "simdutf8"
1496
+ version = "0.1.5"
1497
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1498
+ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
1499
+
1500
+ [[package]]
1501
+ name = "slab"
1502
+ version = "0.4.12"
1503
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1504
+ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
1505
+
1506
+ [[package]]
1507
+ name = "smallvec"
1508
+ version = "1.15.1"
1509
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1510
+ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
1511
+
1512
+ [[package]]
1513
+ name = "socket2"
1514
+ version = "0.6.3"
1515
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1516
+ checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
1517
+ dependencies = [
1518
+ "libc",
1519
+ "windows-sys 0.61.2",
1520
+ ]
1521
+
1522
+ [[package]]
1523
+ name = "solverforge"
1524
+ version = "0.13.1"
1525
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1526
+ checksum = "d86fef307d129ed9d674f29e2f37be912a9b901a4fe9f43348f1044879ef7094"
1527
+ dependencies = [
1528
+ "solverforge-config",
1529
+ "solverforge-console",
1530
+ "solverforge-core",
1531
+ "solverforge-cvrp",
1532
+ "solverforge-macros",
1533
+ "solverforge-scoring",
1534
+ "solverforge-solver",
1535
+ "tokio",
1536
+ ]
1537
+
1538
+ [[package]]
1539
+ name = "solverforge-config"
1540
+ version = "0.13.1"
1541
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1542
+ checksum = "434a6c8f9f308d20c7d364324de9d0dbb27d5fe03a7e8f1cead0c2574ebe947e"
1543
+ dependencies = [
1544
+ "serde",
1545
+ "serde_yaml",
1546
+ "solverforge-core",
1547
+ "thiserror",
1548
+ "toml",
1549
+ ]
1550
+
1551
+ [[package]]
1552
+ name = "solverforge-console"
1553
+ version = "0.13.1"
1554
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1555
+ checksum = "7456459eb2efbb3d57a6b5234c7d29a2aab84c8d14d5a09807ab0ac94bf9a786"
1556
+ dependencies = [
1557
+ "num-format",
1558
+ "owo-colors",
1559
+ "tracing",
1560
+ "tracing-subscriber",
1561
+ ]
1562
+
1563
+ [[package]]
1564
+ name = "solverforge-core"
1565
+ version = "0.13.1"
1566
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1567
+ checksum = "ccc29944f60ac425f53b4605ea2f0fa5fa499549139df7a889aa5fdc47bcc066"
1568
+ dependencies = [
1569
+ "serde",
1570
+ "thiserror",
1571
+ ]
1572
+
1573
+ [[package]]
1574
+ name = "solverforge-cvrp"
1575
+ version = "0.13.1"
1576
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1577
+ checksum = "91cbe85229dd46e845f95f1f4d2ac993145680b02e044acdce220ee4ddecbd36"
1578
+ dependencies = [
1579
+ "solverforge-solver",
1580
+ ]
1581
+
1582
+ [[package]]
1583
+ name = "solverforge-deliveries"
1584
+ version = "2.0.0"
1585
+ dependencies = [
1586
+ "axum",
1587
+ "http-body-util",
1588
+ "parking_lot",
1589
+ "rand 0.10.1",
1590
+ "serde",
1591
+ "serde_json",
1592
+ "solverforge",
1593
+ "solverforge-maps",
1594
+ "solverforge-ui",
1595
+ "tokio",
1596
+ "tokio-stream",
1597
+ "tower",
1598
+ "tower-http",
1599
+ "uuid",
1600
+ ]
1601
+
1602
+ [[package]]
1603
+ name = "solverforge-macros"
1604
+ version = "0.13.1"
1605
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1606
+ checksum = "2f830c19a7d9c7fe10911cf597b0b98c6d63ef2ec70330ac6e0b2746e8db8d98"
1607
+ dependencies = [
1608
+ "proc-macro2",
1609
+ "quote",
1610
+ "syn",
1611
+ ]
1612
+
1613
+ [[package]]
1614
+ name = "solverforge-maps"
1615
+ version = "2.1.4"
1616
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1617
+ checksum = "e31f816d221238ba3ade93315e6a605486b53d9ec26527477d165b507ece6a88"
1618
+ dependencies = [
1619
+ "rayon",
1620
+ "reqwest",
1621
+ "serde",
1622
+ "serde_json",
1623
+ "tokio",
1624
+ "tracing",
1625
+ "utoipa",
1626
+ ]
1627
+
1628
+ [[package]]
1629
+ name = "solverforge-scoring"
1630
+ version = "0.13.1"
1631
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1632
+ checksum = "1efaede461a62baa20a0f65887bda5760158a125a831ecc73c50d713ac9d6ce7"
1633
+ dependencies = [
1634
+ "solverforge-core",
1635
+ "thiserror",
1636
+ ]
1637
+
1638
+ [[package]]
1639
+ name = "solverforge-solver"
1640
+ version = "0.13.1"
1641
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1642
+ checksum = "0156969a9e5d965d7fda36a8581646115e5594a2d50d1036e101dcb001167530"
1643
+ dependencies = [
1644
+ "rand 0.10.1",
1645
+ "rand_chacha 0.10.0",
1646
+ "rayon",
1647
+ "serde",
1648
+ "smallvec",
1649
+ "solverforge-config",
1650
+ "solverforge-core",
1651
+ "solverforge-scoring",
1652
+ "thiserror",
1653
+ "tokio",
1654
+ "tracing",
1655
+ ]
1656
+
1657
+ [[package]]
1658
+ name = "solverforge-ui"
1659
+ version = "0.6.5"
1660
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1661
+ checksum = "1c7fa2d78c84af9a1e264adcffc1bdf8cb4edab8d73a3543fb448d166c95596f"
1662
+ dependencies = [
1663
+ "axum",
1664
+ "include_dir",
1665
+ ]
1666
+
1667
+ [[package]]
1668
+ name = "stable_deref_trait"
1669
+ version = "1.2.1"
1670
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1671
+ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
1672
+
1673
+ [[package]]
1674
+ name = "subtle"
1675
+ version = "2.6.1"
1676
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1677
+ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
1678
+
1679
+ [[package]]
1680
+ name = "syn"
1681
+ version = "2.0.117"
1682
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1683
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
1684
+ dependencies = [
1685
+ "proc-macro2",
1686
+ "quote",
1687
+ "unicode-ident",
1688
+ ]
1689
+
1690
+ [[package]]
1691
+ name = "sync_wrapper"
1692
+ version = "1.0.2"
1693
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1694
+ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
1695
+ dependencies = [
1696
+ "futures-core",
1697
+ ]
1698
+
1699
+ [[package]]
1700
+ name = "synstructure"
1701
+ version = "0.13.2"
1702
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1703
+ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
1704
+ dependencies = [
1705
+ "proc-macro2",
1706
+ "quote",
1707
+ "syn",
1708
+ ]
1709
+
1710
+ [[package]]
1711
+ name = "system-configuration"
1712
+ version = "0.7.0"
1713
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1714
+ checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
1715
+ dependencies = [
1716
+ "bitflags",
1717
+ "core-foundation 0.9.4",
1718
+ "system-configuration-sys",
1719
+ ]
1720
+
1721
+ [[package]]
1722
+ name = "system-configuration-sys"
1723
+ version = "0.6.0"
1724
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1725
+ checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
1726
+ dependencies = [
1727
+ "core-foundation-sys",
1728
+ "libc",
1729
+ ]
1730
+
1731
+ [[package]]
1732
+ name = "thiserror"
1733
+ version = "2.0.18"
1734
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1735
+ checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
1736
+ dependencies = [
1737
+ "thiserror-impl",
1738
+ ]
1739
+
1740
+ [[package]]
1741
+ name = "thiserror-impl"
1742
+ version = "2.0.18"
1743
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1744
+ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
1745
+ dependencies = [
1746
+ "proc-macro2",
1747
+ "quote",
1748
+ "syn",
1749
+ ]
1750
+
1751
+ [[package]]
1752
+ name = "thread_local"
1753
+ version = "1.1.9"
1754
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1755
+ checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
1756
+ dependencies = [
1757
+ "cfg-if",
1758
+ ]
1759
+
1760
+ [[package]]
1761
+ name = "tinystr"
1762
+ version = "0.8.3"
1763
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1764
+ checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
1765
+ dependencies = [
1766
+ "displaydoc",
1767
+ "zerovec",
1768
+ ]
1769
+
1770
+ [[package]]
1771
+ name = "tinyvec"
1772
+ version = "1.11.0"
1773
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1774
+ checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
1775
+ dependencies = [
1776
+ "tinyvec_macros",
1777
+ ]
1778
+
1779
+ [[package]]
1780
+ name = "tinyvec_macros"
1781
+ version = "0.1.1"
1782
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1783
+ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
1784
+
1785
+ [[package]]
1786
+ name = "tokio"
1787
+ version = "1.52.3"
1788
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1789
+ checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
1790
+ dependencies = [
1791
+ "bytes",
1792
+ "libc",
1793
+ "mio",
1794
+ "parking_lot",
1795
+ "pin-project-lite",
1796
+ "signal-hook-registry",
1797
+ "socket2",
1798
+ "tokio-macros",
1799
+ "windows-sys 0.61.2",
1800
+ ]
1801
+
1802
+ [[package]]
1803
+ name = "tokio-macros"
1804
+ version = "2.7.0"
1805
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1806
+ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
1807
+ dependencies = [
1808
+ "proc-macro2",
1809
+ "quote",
1810
+ "syn",
1811
+ ]
1812
+
1813
+ [[package]]
1814
+ name = "tokio-rustls"
1815
+ version = "0.26.4"
1816
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1817
+ checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
1818
+ dependencies = [
1819
+ "rustls",
1820
+ "tokio",
1821
+ ]
1822
+
1823
+ [[package]]
1824
+ name = "tokio-stream"
1825
+ version = "0.1.18"
1826
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1827
+ checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
1828
+ dependencies = [
1829
+ "futures-core",
1830
+ "pin-project-lite",
1831
+ "tokio",
1832
+ "tokio-util",
1833
+ ]
1834
+
1835
+ [[package]]
1836
+ name = "tokio-util"
1837
+ version = "0.7.18"
1838
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1839
+ checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
1840
+ dependencies = [
1841
+ "bytes",
1842
+ "futures-core",
1843
+ "futures-sink",
1844
+ "pin-project-lite",
1845
+ "tokio",
1846
+ ]
1847
+
1848
+ [[package]]
1849
+ name = "toml"
1850
+ version = "1.1.2+spec-1.1.0"
1851
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1852
+ checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
1853
+ dependencies = [
1854
+ "indexmap",
1855
+ "serde_core",
1856
+ "serde_spanned",
1857
+ "toml_datetime",
1858
+ "toml_parser",
1859
+ "toml_writer",
1860
+ "winnow",
1861
+ ]
1862
+
1863
+ [[package]]
1864
+ name = "toml_datetime"
1865
+ version = "1.1.1+spec-1.1.0"
1866
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1867
+ checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
1868
+ dependencies = [
1869
+ "serde_core",
1870
+ ]
1871
+
1872
+ [[package]]
1873
+ name = "toml_parser"
1874
+ version = "1.1.2+spec-1.1.0"
1875
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1876
+ checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
1877
+ dependencies = [
1878
+ "winnow",
1879
+ ]
1880
+
1881
+ [[package]]
1882
+ name = "toml_writer"
1883
+ version = "1.1.1+spec-1.1.0"
1884
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1885
+ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
1886
+
1887
+ [[package]]
1888
+ name = "tower"
1889
+ version = "0.5.3"
1890
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1891
+ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
1892
+ dependencies = [
1893
+ "futures-core",
1894
+ "futures-util",
1895
+ "pin-project-lite",
1896
+ "sync_wrapper",
1897
+ "tokio",
1898
+ "tower-layer",
1899
+ "tower-service",
1900
+ "tracing",
1901
+ ]
1902
+
1903
+ [[package]]
1904
+ name = "tower-http"
1905
+ version = "0.6.10"
1906
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1907
+ checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51"
1908
+ dependencies = [
1909
+ "bitflags",
1910
+ "bytes",
1911
+ "futures-core",
1912
+ "futures-util",
1913
+ "http",
1914
+ "http-body",
1915
+ "http-body-util",
1916
+ "http-range-header",
1917
+ "httpdate",
1918
+ "mime",
1919
+ "mime_guess",
1920
+ "percent-encoding",
1921
+ "pin-project-lite",
1922
+ "tokio",
1923
+ "tokio-util",
1924
+ "tower",
1925
+ "tower-layer",
1926
+ "tower-service",
1927
+ "url",
1928
+ ]
1929
+
1930
+ [[package]]
1931
+ name = "tower-layer"
1932
+ version = "0.3.3"
1933
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1934
+ checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
1935
+
1936
+ [[package]]
1937
+ name = "tower-service"
1938
+ version = "0.3.3"
1939
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1940
+ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
1941
+
1942
+ [[package]]
1943
+ name = "tracing"
1944
+ version = "0.1.44"
1945
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1946
+ checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
1947
+ dependencies = [
1948
+ "log",
1949
+ "pin-project-lite",
1950
+ "tracing-attributes",
1951
+ "tracing-core",
1952
+ ]
1953
+
1954
+ [[package]]
1955
+ name = "tracing-attributes"
1956
+ version = "0.1.31"
1957
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1958
+ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
1959
+ dependencies = [
1960
+ "proc-macro2",
1961
+ "quote",
1962
+ "syn",
1963
+ ]
1964
+
1965
+ [[package]]
1966
+ name = "tracing-core"
1967
+ version = "0.1.36"
1968
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1969
+ checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
1970
+ dependencies = [
1971
+ "once_cell",
1972
+ "valuable",
1973
+ ]
1974
+
1975
+ [[package]]
1976
+ name = "tracing-log"
1977
+ version = "0.2.0"
1978
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1979
+ checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
1980
+ dependencies = [
1981
+ "log",
1982
+ "once_cell",
1983
+ "tracing-core",
1984
+ ]
1985
+
1986
+ [[package]]
1987
+ name = "tracing-subscriber"
1988
+ version = "0.3.23"
1989
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1990
+ checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
1991
+ dependencies = [
1992
+ "matchers",
1993
+ "nu-ansi-term",
1994
+ "once_cell",
1995
+ "regex-automata",
1996
+ "sharded-slab",
1997
+ "smallvec",
1998
+ "thread_local",
1999
+ "tracing",
2000
+ "tracing-core",
2001
+ "tracing-log",
2002
+ ]
2003
+
2004
+ [[package]]
2005
+ name = "try-lock"
2006
+ version = "0.2.5"
2007
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2008
+ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
2009
+
2010
+ [[package]]
2011
+ name = "unicase"
2012
+ version = "2.9.0"
2013
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2014
+ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
2015
+
2016
+ [[package]]
2017
+ name = "unicode-ident"
2018
+ version = "1.0.24"
2019
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2020
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
2021
+
2022
+ [[package]]
2023
+ name = "unicode-xid"
2024
+ version = "0.2.6"
2025
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2026
+ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
2027
+
2028
+ [[package]]
2029
+ name = "unsafe-libyaml"
2030
+ version = "0.2.11"
2031
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2032
+ checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
2033
+
2034
+ [[package]]
2035
+ name = "untrusted"
2036
+ version = "0.9.0"
2037
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2038
+ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
2039
+
2040
+ [[package]]
2041
+ name = "url"
2042
+ version = "2.5.8"
2043
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2044
+ checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
2045
+ dependencies = [
2046
+ "form_urlencoded",
2047
+ "idna",
2048
+ "percent-encoding",
2049
+ "serde",
2050
+ ]
2051
+
2052
+ [[package]]
2053
+ name = "utf8_iter"
2054
+ version = "1.0.4"
2055
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2056
+ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
2057
+
2058
+ [[package]]
2059
+ name = "utoipa"
2060
+ version = "5.5.0"
2061
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2062
+ checksum = "8bde15df68e80b16c7d16b9616e80770ad158988daa56a27dccd1e55558b0160"
2063
+ dependencies = [
2064
+ "indexmap",
2065
+ "serde",
2066
+ "serde_json",
2067
+ "utoipa-gen",
2068
+ ]
2069
+
2070
+ [[package]]
2071
+ name = "utoipa-gen"
2072
+ version = "5.5.0"
2073
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2074
+ checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8"
2075
+ dependencies = [
2076
+ "proc-macro2",
2077
+ "quote",
2078
+ "syn",
2079
+ ]
2080
+
2081
+ [[package]]
2082
+ name = "uuid"
2083
+ version = "1.23.1"
2084
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2085
+ checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
2086
+ dependencies = [
2087
+ "getrandom 0.4.2",
2088
+ "js-sys",
2089
+ "serde_core",
2090
+ "wasm-bindgen",
2091
+ ]
2092
+
2093
+ [[package]]
2094
+ name = "valuable"
2095
+ version = "0.1.1"
2096
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2097
+ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
2098
+
2099
+ [[package]]
2100
+ name = "walkdir"
2101
+ version = "2.5.0"
2102
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2103
+ checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
2104
+ dependencies = [
2105
+ "same-file",
2106
+ "winapi-util",
2107
+ ]
2108
+
2109
+ [[package]]
2110
+ name = "want"
2111
+ version = "0.3.1"
2112
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2113
+ checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
2114
+ dependencies = [
2115
+ "try-lock",
2116
+ ]
2117
+
2118
+ [[package]]
2119
+ name = "wasi"
2120
+ version = "0.11.1+wasi-snapshot-preview1"
2121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2122
+ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
2123
+
2124
+ [[package]]
2125
+ name = "wasip2"
2126
+ version = "1.0.3+wasi-0.2.9"
2127
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2128
+ checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
2129
+ dependencies = [
2130
+ "wit-bindgen 0.57.1",
2131
+ ]
2132
+
2133
+ [[package]]
2134
+ name = "wasip3"
2135
+ version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
2136
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2137
+ checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
2138
+ dependencies = [
2139
+ "wit-bindgen 0.51.0",
2140
+ ]
2141
+
2142
+ [[package]]
2143
+ name = "wasm-bindgen"
2144
+ version = "0.2.121"
2145
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2146
+ checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
2147
+ dependencies = [
2148
+ "cfg-if",
2149
+ "once_cell",
2150
+ "rustversion",
2151
+ "wasm-bindgen-macro",
2152
+ "wasm-bindgen-shared",
2153
+ ]
2154
+
2155
+ [[package]]
2156
+ name = "wasm-bindgen-futures"
2157
+ version = "0.4.71"
2158
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2159
+ checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8"
2160
+ dependencies = [
2161
+ "js-sys",
2162
+ "wasm-bindgen",
2163
+ ]
2164
+
2165
+ [[package]]
2166
+ name = "wasm-bindgen-macro"
2167
+ version = "0.2.121"
2168
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2169
+ checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
2170
+ dependencies = [
2171
+ "quote",
2172
+ "wasm-bindgen-macro-support",
2173
+ ]
2174
+
2175
+ [[package]]
2176
+ name = "wasm-bindgen-macro-support"
2177
+ version = "0.2.121"
2178
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2179
+ checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
2180
+ dependencies = [
2181
+ "bumpalo",
2182
+ "proc-macro2",
2183
+ "quote",
2184
+ "syn",
2185
+ "wasm-bindgen-shared",
2186
+ ]
2187
+
2188
+ [[package]]
2189
+ name = "wasm-bindgen-shared"
2190
+ version = "0.2.121"
2191
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2192
+ checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
2193
+ dependencies = [
2194
+ "unicode-ident",
2195
+ ]
2196
+
2197
+ [[package]]
2198
+ name = "wasm-encoder"
2199
+ version = "0.244.0"
2200
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2201
+ checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
2202
+ dependencies = [
2203
+ "leb128fmt",
2204
+ "wasmparser",
2205
+ ]
2206
+
2207
+ [[package]]
2208
+ name = "wasm-metadata"
2209
+ version = "0.244.0"
2210
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2211
+ checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
2212
+ dependencies = [
2213
+ "anyhow",
2214
+ "indexmap",
2215
+ "wasm-encoder",
2216
+ "wasmparser",
2217
+ ]
2218
+
2219
+ [[package]]
2220
+ name = "wasmparser"
2221
+ version = "0.244.0"
2222
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2223
+ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
2224
+ dependencies = [
2225
+ "bitflags",
2226
+ "hashbrown 0.15.5",
2227
+ "indexmap",
2228
+ "semver",
2229
+ ]
2230
+
2231
+ [[package]]
2232
+ name = "web-sys"
2233
+ version = "0.3.98"
2234
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2235
+ checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa"
2236
+ dependencies = [
2237
+ "js-sys",
2238
+ "wasm-bindgen",
2239
+ ]
2240
+
2241
+ [[package]]
2242
+ name = "web-time"
2243
+ version = "1.1.0"
2244
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2245
+ checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
2246
+ dependencies = [
2247
+ "js-sys",
2248
+ "wasm-bindgen",
2249
+ ]
2250
+
2251
+ [[package]]
2252
+ name = "webpki-root-certs"
2253
+ version = "1.0.7"
2254
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2255
+ checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
2256
+ dependencies = [
2257
+ "rustls-pki-types",
2258
+ ]
2259
+
2260
+ [[package]]
2261
+ name = "winapi-util"
2262
+ version = "0.1.11"
2263
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2264
+ checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
2265
+ dependencies = [
2266
+ "windows-sys 0.61.2",
2267
+ ]
2268
+
2269
+ [[package]]
2270
+ name = "windows-link"
2271
+ version = "0.2.1"
2272
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2273
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
2274
+
2275
+ [[package]]
2276
+ name = "windows-registry"
2277
+ version = "0.6.1"
2278
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2279
+ checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
2280
+ dependencies = [
2281
+ "windows-link",
2282
+ "windows-result",
2283
+ "windows-strings",
2284
+ ]
2285
+
2286
+ [[package]]
2287
+ name = "windows-result"
2288
+ version = "0.4.1"
2289
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2290
+ checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
2291
+ dependencies = [
2292
+ "windows-link",
2293
+ ]
2294
+
2295
+ [[package]]
2296
+ name = "windows-strings"
2297
+ version = "0.5.1"
2298
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2299
+ checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
2300
+ dependencies = [
2301
+ "windows-link",
2302
+ ]
2303
+
2304
+ [[package]]
2305
+ name = "windows-sys"
2306
+ version = "0.52.0"
2307
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2308
+ checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
2309
+ dependencies = [
2310
+ "windows-targets 0.52.6",
2311
+ ]
2312
+
2313
+ [[package]]
2314
+ name = "windows-sys"
2315
+ version = "0.60.2"
2316
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2317
+ checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
2318
+ dependencies = [
2319
+ "windows-targets 0.53.5",
2320
+ ]
2321
+
2322
+ [[package]]
2323
+ name = "windows-sys"
2324
+ version = "0.61.2"
2325
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2326
+ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
2327
+ dependencies = [
2328
+ "windows-link",
2329
+ ]
2330
+
2331
+ [[package]]
2332
+ name = "windows-targets"
2333
+ version = "0.52.6"
2334
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2335
+ checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
2336
+ dependencies = [
2337
+ "windows_aarch64_gnullvm 0.52.6",
2338
+ "windows_aarch64_msvc 0.52.6",
2339
+ "windows_i686_gnu 0.52.6",
2340
+ "windows_i686_gnullvm 0.52.6",
2341
+ "windows_i686_msvc 0.52.6",
2342
+ "windows_x86_64_gnu 0.52.6",
2343
+ "windows_x86_64_gnullvm 0.52.6",
2344
+ "windows_x86_64_msvc 0.52.6",
2345
+ ]
2346
+
2347
+ [[package]]
2348
+ name = "windows-targets"
2349
+ version = "0.53.5"
2350
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2351
+ checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
2352
+ dependencies = [
2353
+ "windows-link",
2354
+ "windows_aarch64_gnullvm 0.53.1",
2355
+ "windows_aarch64_msvc 0.53.1",
2356
+ "windows_i686_gnu 0.53.1",
2357
+ "windows_i686_gnullvm 0.53.1",
2358
+ "windows_i686_msvc 0.53.1",
2359
+ "windows_x86_64_gnu 0.53.1",
2360
+ "windows_x86_64_gnullvm 0.53.1",
2361
+ "windows_x86_64_msvc 0.53.1",
2362
+ ]
2363
+
2364
+ [[package]]
2365
+ name = "windows_aarch64_gnullvm"
2366
+ version = "0.52.6"
2367
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2368
+ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
2369
+
2370
+ [[package]]
2371
+ name = "windows_aarch64_gnullvm"
2372
+ version = "0.53.1"
2373
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2374
+ checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
2375
+
2376
+ [[package]]
2377
+ name = "windows_aarch64_msvc"
2378
+ version = "0.52.6"
2379
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2380
+ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
2381
+
2382
+ [[package]]
2383
+ name = "windows_aarch64_msvc"
2384
+ version = "0.53.1"
2385
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2386
+ checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
2387
+
2388
+ [[package]]
2389
+ name = "windows_i686_gnu"
2390
+ version = "0.52.6"
2391
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2392
+ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
2393
+
2394
+ [[package]]
2395
+ name = "windows_i686_gnu"
2396
+ version = "0.53.1"
2397
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2398
+ checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
2399
+
2400
+ [[package]]
2401
+ name = "windows_i686_gnullvm"
2402
+ version = "0.52.6"
2403
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2404
+ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
2405
+
2406
+ [[package]]
2407
+ name = "windows_i686_gnullvm"
2408
+ version = "0.53.1"
2409
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2410
+ checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
2411
+
2412
+ [[package]]
2413
+ name = "windows_i686_msvc"
2414
+ version = "0.52.6"
2415
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2416
+ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
2417
+
2418
+ [[package]]
2419
+ name = "windows_i686_msvc"
2420
+ version = "0.53.1"
2421
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2422
+ checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
2423
+
2424
+ [[package]]
2425
+ name = "windows_x86_64_gnu"
2426
+ version = "0.52.6"
2427
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2428
+ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
2429
+
2430
+ [[package]]
2431
+ name = "windows_x86_64_gnu"
2432
+ version = "0.53.1"
2433
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2434
+ checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
2435
+
2436
+ [[package]]
2437
+ name = "windows_x86_64_gnullvm"
2438
+ version = "0.52.6"
2439
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2440
+ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
2441
+
2442
+ [[package]]
2443
+ name = "windows_x86_64_gnullvm"
2444
+ version = "0.53.1"
2445
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2446
+ checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
2447
+
2448
+ [[package]]
2449
+ name = "windows_x86_64_msvc"
2450
+ version = "0.52.6"
2451
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2452
+ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
2453
+
2454
+ [[package]]
2455
+ name = "windows_x86_64_msvc"
2456
+ version = "0.53.1"
2457
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2458
+ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
2459
+
2460
+ [[package]]
2461
+ name = "winnow"
2462
+ version = "1.0.2"
2463
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2464
+ checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
2465
+
2466
+ [[package]]
2467
+ name = "wit-bindgen"
2468
+ version = "0.51.0"
2469
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2470
+ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
2471
+ dependencies = [
2472
+ "wit-bindgen-rust-macro",
2473
+ ]
2474
+
2475
+ [[package]]
2476
+ name = "wit-bindgen"
2477
+ version = "0.57.1"
2478
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2479
+ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
2480
+
2481
+ [[package]]
2482
+ name = "wit-bindgen-core"
2483
+ version = "0.51.0"
2484
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2485
+ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
2486
+ dependencies = [
2487
+ "anyhow",
2488
+ "heck",
2489
+ "wit-parser",
2490
+ ]
2491
+
2492
+ [[package]]
2493
+ name = "wit-bindgen-rust"
2494
+ version = "0.51.0"
2495
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2496
+ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
2497
+ dependencies = [
2498
+ "anyhow",
2499
+ "heck",
2500
+ "indexmap",
2501
+ "prettyplease",
2502
+ "syn",
2503
+ "wasm-metadata",
2504
+ "wit-bindgen-core",
2505
+ "wit-component",
2506
+ ]
2507
+
2508
+ [[package]]
2509
+ name = "wit-bindgen-rust-macro"
2510
+ version = "0.51.0"
2511
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2512
+ checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
2513
+ dependencies = [
2514
+ "anyhow",
2515
+ "prettyplease",
2516
+ "proc-macro2",
2517
+ "quote",
2518
+ "syn",
2519
+ "wit-bindgen-core",
2520
+ "wit-bindgen-rust",
2521
+ ]
2522
+
2523
+ [[package]]
2524
+ name = "wit-component"
2525
+ version = "0.244.0"
2526
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2527
+ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
2528
+ dependencies = [
2529
+ "anyhow",
2530
+ "bitflags",
2531
+ "indexmap",
2532
+ "log",
2533
+ "serde",
2534
+ "serde_derive",
2535
+ "serde_json",
2536
+ "wasm-encoder",
2537
+ "wasm-metadata",
2538
+ "wasmparser",
2539
+ "wit-parser",
2540
+ ]
2541
+
2542
+ [[package]]
2543
+ name = "wit-parser"
2544
+ version = "0.244.0"
2545
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2546
+ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
2547
+ dependencies = [
2548
+ "anyhow",
2549
+ "id-arena",
2550
+ "indexmap",
2551
+ "log",
2552
+ "semver",
2553
+ "serde",
2554
+ "serde_derive",
2555
+ "serde_json",
2556
+ "unicode-xid",
2557
+ "wasmparser",
2558
+ ]
2559
+
2560
+ [[package]]
2561
+ name = "writeable"
2562
+ version = "0.6.3"
2563
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2564
+ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
2565
+
2566
+ [[package]]
2567
+ name = "yoke"
2568
+ version = "0.8.2"
2569
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2570
+ checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
2571
+ dependencies = [
2572
+ "stable_deref_trait",
2573
+ "yoke-derive",
2574
+ "zerofrom",
2575
+ ]
2576
+
2577
+ [[package]]
2578
+ name = "yoke-derive"
2579
+ version = "0.8.2"
2580
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2581
+ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
2582
+ dependencies = [
2583
+ "proc-macro2",
2584
+ "quote",
2585
+ "syn",
2586
+ "synstructure",
2587
+ ]
2588
+
2589
+ [[package]]
2590
+ name = "zerocopy"
2591
+ version = "0.8.48"
2592
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2593
+ checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
2594
+ dependencies = [
2595
+ "zerocopy-derive",
2596
+ ]
2597
+
2598
+ [[package]]
2599
+ name = "zerocopy-derive"
2600
+ version = "0.8.48"
2601
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2602
+ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
2603
+ dependencies = [
2604
+ "proc-macro2",
2605
+ "quote",
2606
+ "syn",
2607
+ ]
2608
+
2609
+ [[package]]
2610
+ name = "zerofrom"
2611
+ version = "0.1.8"
2612
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2613
+ checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
2614
+ dependencies = [
2615
+ "zerofrom-derive",
2616
+ ]
2617
+
2618
+ [[package]]
2619
+ name = "zerofrom-derive"
2620
+ version = "0.1.7"
2621
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2622
+ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
2623
+ dependencies = [
2624
+ "proc-macro2",
2625
+ "quote",
2626
+ "syn",
2627
+ "synstructure",
2628
+ ]
2629
+
2630
+ [[package]]
2631
+ name = "zeroize"
2632
+ version = "1.8.2"
2633
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2634
+ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
2635
+
2636
+ [[package]]
2637
+ name = "zerotrie"
2638
+ version = "0.2.4"
2639
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2640
+ checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
2641
+ dependencies = [
2642
+ "displaydoc",
2643
+ "yoke",
2644
+ "zerofrom",
2645
+ ]
2646
+
2647
+ [[package]]
2648
+ name = "zerovec"
2649
+ version = "0.11.6"
2650
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2651
+ checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
2652
+ dependencies = [
2653
+ "yoke",
2654
+ "zerofrom",
2655
+ "zerovec-derive",
2656
+ ]
2657
+
2658
+ [[package]]
2659
+ name = "zerovec-derive"
2660
+ version = "0.11.3"
2661
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2662
+ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
2663
+ dependencies = [
2664
+ "proc-macro2",
2665
+ "quote",
2666
+ "syn",
2667
+ ]
2668
+
2669
+ [[package]]
2670
+ name = "zmij"
2671
+ version = "1.0.21"
2672
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2673
+ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
Cargo.toml ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [package]
2
+ name = "solverforge-deliveries"
3
+ version = "2.0.0"
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.13.1", 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,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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-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 road_network_job_routes_work_when_live_tests_are_enabled -- --nocapture && \
144
+ printf "\n$(GREEN)$(CHECK) Live road-network test passed$(RESET)\n\n" || \
145
+ (printf "\n$(RED)$(CROSS) Live road-network test failed$(RESET)\n\n" && exit 1)
146
+
147
+ test-one:
148
+ @printf "$(PROGRESS) Running test: $(YELLOW)$(TEST)$(RESET)\n"
149
+ @RUST_LOG=info cargo test $(TEST) -- --nocapture
150
+
151
+ # ============== Lint & Format ==============
152
+ fmt:
153
+ @printf "$(PROGRESS) Formatting Rust code...\n"
154
+ @cargo fmt
155
+ @printf "$(GREEN)$(CHECK) Code formatted$(RESET)\n"
156
+
157
+ fmt-check:
158
+ @printf "$(PROGRESS) Checking Rust formatting...\n"
159
+ @cargo fmt --check && \
160
+ printf "$(GREEN)$(CHECK) Formatting valid$(RESET)\n" || \
161
+ (printf "$(RED)$(CROSS) Formatting issues found$(RESET)\n" && exit 1)
162
+
163
+ clippy:
164
+ @printf "$(PROGRESS) Running clippy...\n"
165
+ @cargo clippy --all-targets -- -D warnings && \
166
+ printf "$(GREEN)$(CHECK) Clippy passed$(RESET)\n" || \
167
+ (printf "$(RED)$(CROSS) Clippy warnings found$(RESET)\n" && exit 1)
168
+
169
+ lint: fmt-check clippy test-frontend-syntax
170
+ @printf "\n$(GREEN)$(BOLD)$(CHECK) Lint checks passed$(RESET)\n\n"
171
+
172
+ check: lint test
173
+
174
+ # ============== Space & Docker ==============
175
+ docker-build: require-docker
176
+ @printf "$(PROGRESS) Building Docker image $(DOCKER_IMAGE)...\n"
177
+ @docker build -f "$(DOCKERFILE_PATH)" -t "$(DOCKER_IMAGE)" "$(DOCKER_CONTEXT)" && \
178
+ printf "$(GREEN)$(CHECK) Docker image built$(RESET)\n" || \
179
+ (printf "$(RED)$(CROSS) Docker build failed$(RESET)\n" && exit 1)
180
+
181
+ docker-run: require-docker
182
+ @printf "$(ARROW) Running $(DOCKER_IMAGE) on port $(PORT)...\n"
183
+ @docker run --rm -it -e PORT=$(PORT) -p $(PORT):$(PORT) "$(DOCKER_IMAGE)"
184
+
185
+ space-build: docker-build
186
+
187
+ space-run: space-build
188
+ @printf "$(GREEN)$(CHECK) Starting local container that mirrors the Space image$(RESET)\n"
189
+ @$(MAKE) docker-run --no-print-directory PORT=$(PORT) DOCKER_IMAGE=$(DOCKER_IMAGE)
190
+
191
+ space-ci: ci-local
192
+
193
+ # ============== CI & Release Validation ==============
194
+ ci-local: banner
195
+ @printf "$(CYAN)$(BOLD)Local Validation Pipeline$(RESET)\n\n"
196
+ @printf "$(PROGRESS) Step 1/5: Format check...\n"
197
+ @$(MAKE) fmt-check --no-print-directory
198
+ @printf "$(PROGRESS) Step 2/5: Clippy...\n"
199
+ @$(MAKE) clippy --no-print-directory
200
+ @printf "$(PROGRESS) Step 3/5: Release build...\n"
201
+ @$(MAKE) build-release --no-print-directory
202
+ @printf "$(PROGRESS) Step 4/5: Standard test surface...\n"
203
+ @$(MAKE) test --no-print-directory
204
+ @printf "$(PROGRESS) Step 5/5: Docker/Space image build...\n"
205
+ @$(MAKE) space-build --no-print-directory
206
+ @printf "\n$(GREEN)$(BOLD)$(CHECK) LOCAL SPACE VALIDATION PASSED$(RESET)\n\n"
207
+
208
+ pre-release: banner
209
+ @printf "$(CYAN)$(BOLD)Pre-Release Validation v$(VERSION)$(RESET)\n\n"
210
+ @$(MAKE) ci-local --no-print-directory
211
+ @printf "$(PROGRESS) Final step: live road-network smoke...\n"
212
+ @$(MAKE) test-live-road --no-print-directory
213
+ @printf "$(GREEN)$(BOLD)$(CHECK) Ready for publication or Space update$(RESET)\n\n"
214
+
215
+ release-ci: ci-local
216
+ @printf "$(GREEN)$(BOLD)$(CHECK) Release CI passed for $(RELEASE_TAG)$(RESET)\n\n"
217
+
218
+ release-info:
219
+ @printf "$(CYAN)Package:$(RESET) $(YELLOW)$(BOLD)$(PACKAGE_NAME)$(RESET)\n"
220
+ @printf "$(CYAN)Version:$(RESET) $(YELLOW)$(BOLD)$(VERSION)$(RESET)\n"
221
+ @printf "$(CYAN)Release tag:$(RESET) $(YELLOW)$(BOLD)$(RELEASE_TAG)$(RESET)\n"
222
+
223
+ # ============== Metadata & Cleanup ==============
224
+ version:
225
+ @printf "$(CYAN)Current version:$(RESET) $(YELLOW)$(BOLD)$(VERSION)$(RESET)\n"
226
+ @printf "$(CYAN)Release tag:$(RESET) $(YELLOW)$(BOLD)$(RELEASE_TAG)$(RESET)\n"
227
+ @printf "$(CYAN)Default port:$(RESET) $(YELLOW)$(BOLD)$(PORT)$(RESET)\n"
228
+
229
+ clean:
230
+ @printf "$(ARROW) Cleaning build artifacts...\n"
231
+ @cargo clean
232
+ @printf "$(GREEN)$(CHECK) Clean complete$(RESET)\n"
233
+
234
+ watch:
235
+ @printf "$(ARROW) Watching and rerunning the app on port $(PORT)...\n"
236
+ @cargo watch --version >/dev/null 2>&1 || \
237
+ (printf "$(RED)$(CROSS) cargo-watch is required for make watch$(RESET)\n" && exit 1)
238
+ @PORT=$(PORT) cargo watch -x "run --bin $(APP_NAME)"
239
+
240
+ # ============== Help ==============
241
+ help: banner
242
+ @/bin/echo -e "$(CYAN)$(BOLD)Environment:$(RESET)"
243
+ @/bin/echo -e " $(GREEN)make doctor$(RESET) - Check local cargo/rustc/node readiness"
244
+ @/bin/echo -e ""
245
+ @/bin/echo -e "$(CYAN)$(BOLD)Build & Run:$(RESET)"
246
+ @/bin/echo -e " $(GREEN)make build$(RESET) - Build the app in debug mode"
247
+ @/bin/echo -e " $(GREEN)make build-release$(RESET) - Build the app in release mode"
248
+ @/bin/echo -e " $(GREEN)make run$(RESET) - Run locally on port $(PORT)"
249
+ @/bin/echo -e " $(GREEN)make run-release$(RESET) - Run the release build on port $(PORT)"
250
+ @/bin/echo -e ""
251
+ @/bin/echo -e "$(CYAN)$(BOLD)Tests & Validation:$(RESET)"
252
+ @/bin/echo -e " $(GREEN)make test$(RESET) - Run Rust, frontend, and Playwright tests"
253
+ @/bin/echo -e " $(GREEN)make test-rust$(RESET) - Run Rust tests only"
254
+ @/bin/echo -e " $(GREEN)make test-frontend$(RESET) - Run frontend syntax checks and tests"
255
+ @/bin/echo -e " $(GREEN)make test-e2e$(RESET) - Run Playwright browser tests"
256
+ @/bin/echo -e " $(GREEN)make test-live-road$(RESET) - Run the live road-network smoke test"
257
+ @/bin/echo -e " $(GREEN)make test-one TEST=name$(RESET) - Run a specific Rust test with output"
258
+ @/bin/echo -e " $(GREEN)make lint$(RESET) - Run fmt-check, clippy, and frontend syntax checks"
259
+ @/bin/echo -e " $(GREEN)make check$(RESET) - Run lint plus standard tests"
260
+ @/bin/echo -e " $(GREEN)make ci-local$(RESET) - Run local Space validation pipeline"
261
+ @/bin/echo -e " $(GREEN)make release-ci$(RESET) - Run the tag-publish CI gate for this app"
262
+ @/bin/echo -e " $(GREEN)make pre-release$(RESET) - Run ci-local plus live road-network smoke"
263
+ @/bin/echo -e ""
264
+ @/bin/echo -e "$(CYAN)$(BOLD)Space & Docker:$(RESET)"
265
+ @/bin/echo -e " $(GREEN)make space-build$(RESET) - Build the Docker image used for Space deployment"
266
+ @/bin/echo -e " $(GREEN)make space-run$(RESET) - Build and run that image locally on port $(PORT)"
267
+ @/bin/echo -e " $(GREEN)make docker-build$(RESET) - Build the Docker image directly"
268
+ @/bin/echo -e " $(GREEN)make docker-run$(RESET) - Run the Docker image directly"
269
+ @/bin/echo -e ""
270
+ @/bin/echo -e "$(CYAN)$(BOLD)Other:$(RESET)"
271
+ @/bin/echo -e " $(GREEN)make fmt$(RESET) - Format Rust code"
272
+ @/bin/echo -e " $(GREEN)make release-info$(RESET) - Show package version and app-scoped release tag"
273
+ @/bin/echo -e " $(GREEN)make version$(RESET) - Show version and default port"
274
+ @/bin/echo -e " $(GREEN)make clean$(RESET) - Clean build artifacts"
275
+ @/bin/echo -e " $(GREEN)make watch$(RESET) - Watch source files and rerun the app"
276
+ @/bin/echo -e " $(GREEN)make help$(RESET) - Show this help message"
277
+ @/bin/echo -e ""
278
+ @/bin/echo -e "$(GRAY)Rust version required: $(RUST_VERSION)$(RESET)"
279
+ @/bin/echo -e "$(GRAY)Current version: v$(VERSION)$(RESET)"
280
+ @/bin/echo -e "$(GRAY)Release tag: $(RELEASE_TAG)$(RESET)"
281
+ @/bin/echo -e "$(GRAY)Default port: $(PORT)$(RESET)"
282
+ @/bin/echo -e ""
README.md ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.13.1`
58
+ - Browser UI assets: `solverforge-ui` `0.6.5`
59
+ - Routing engine: `solverforge-maps` `2.1.4`
60
+ - Scaffold metadata: `solverforge-cli` `2.0.4` 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, routing mode,
72
+ view state, and the current `HardSoftScore`.
73
+ - `RoutingMode` selects `road_network` or `straight_line` route geometry.
74
+
75
+ The app ships three deterministic datasets: `PHILADELPHIA` with 82 deliveries,
76
+ `HARTFORD` with 50 deliveries, and `FIRENZE` with 80 deliveries. Each dataset
77
+ has ten vehicles and coherent capacity for the published stops.
78
+
79
+ ## Constraints
80
+
81
+ Hard constraints:
82
+
83
+ - Every delivery is assigned.
84
+ - Vehicle capacity is not exceeded.
85
+ - Vehicle routes respect delivery time windows.
86
+
87
+ Soft constraints:
88
+
89
+ - Total travel time is minimized.
90
+
91
+ ## REST API
92
+
93
+ - `GET /health`
94
+ - `GET /info`
95
+ - `GET /demo-data`
96
+ - `GET /demo-data/{id}`
97
+ - `POST /jobs`
98
+ - `GET /jobs/{id}`
99
+ - `DELETE /jobs/{id}`
100
+ - `GET /jobs/{id}/status`
101
+ - `GET /jobs/{id}/snapshot`
102
+ - `GET /jobs/{id}/analysis`
103
+ - `GET /jobs/{id}/routes`
104
+ - `POST /jobs/{id}/pause`
105
+ - `POST /jobs/{id}/resume`
106
+ - `POST /jobs/{id}/cancel`
107
+ - `GET /jobs/{id}/events`
108
+ - `POST /recommendations/delivery-insertions`
109
+
110
+ `snapshot_revision={n}` is optional for snapshots, analysis, and routes. SSE
111
+ clients receive a bootstrap event and then live retained-job events.
112
+
113
+ ## Solver Policy
114
+
115
+ `solver.toml` is embedded by `Plan` and is the runtime source of truth.
116
+
117
+ - `list_clarke_wright` builds initial delivery routes.
118
+ - `list_k_opt` improves those routes before local search.
119
+ - Local search combines nearby list change/swap, reverse, k-opt, ruin, and
120
+ limited sublist moves over `Vehicle.delivery_order`.
121
+ - `late_acceptance` with an accepted-count forager keeps several candidate
122
+ moves alive per step.
123
+ - Solving stops after 30 seconds total or after 5 seconds without improvement.
124
+
125
+ `road_network` mode uses `solverforge-maps` to load a graph and return route
126
+ geometry through `/jobs/{id}/routes`. `straight_line` mode is a fast draft and
127
+ testing path.
128
+
129
+ ## Validation
130
+
131
+ Standard validation:
132
+
133
+ ```sh
134
+ make test
135
+ ```
136
+
137
+ Full local validation:
138
+
139
+ ```sh
140
+ make ci-local
141
+ ```
142
+
143
+ Live road-network smoke:
144
+
145
+ ```sh
146
+ make test-live-road
147
+ ```
148
+
149
+ `make test` runs Rust tests, browserless frontend tests, and Playwright browser
150
+ tests. `make ci-local` adds formatting, clippy, release build, and Docker image
151
+ build. `make pre-release` runs `ci-local` plus the live road-network smoke.
152
+
153
+ ## Hugging Face Space Deployment
154
+
155
+ This repo is Docker-Space ready. The Space reads the README front matter,
156
+ builds `Dockerfile`, and expects the app to bind `PORT=7860`.
157
+
158
+ Local Space-equivalent commands:
159
+
160
+ ```sh
161
+ make space-build
162
+ make space-run
163
+ ```
164
+
165
+ ## Read The Code In This Order
166
+
167
+ 1. `src/domain/mod.rs`
168
+ The `planning_model!` manifest and public domain exports.
169
+ 2. `src/domain/plan.rs`
170
+ The `Plan` solution, list-variable hook wiring, and routing mode.
171
+ 3. `src/domain/delivery.rs` and `src/domain/vehicle.rs`
172
+ The problem fact and planning entity.
173
+ 4. `src/domain/route_metrics/`
174
+ Route preparation, SolverForge CVRP hooks, preview scoring, route geometry,
175
+ and insertion ranking.
176
+ 5. `src/constraints/mod.rs` and `src/constraints/*.rs`
177
+ The score model, one rule per file.
178
+ 6. `src/data/data_seed/entrypoints.rs`
179
+ Public demo-data IDs and generator dispatch.
180
+ 7. `src/data/data_seed/{philadelphia,hartford,firenze}/`
181
+ City depots and delivery coordinates.
182
+ 8. `src/solver/service.rs`
183
+ Retained-job orchestration over `SolverManager<Plan>`.
184
+ 9. `src/api/routes.rs`, `src/api/dto.rs`, and `src/api/sse.rs`
185
+ HTTP routes, transport DTOs, and live-event streaming.
186
+ 10. `static/app/main.mjs`, `static/app/models/`, and `static/app/ui/`
187
+ Browser controller, model normalization, maps, tables, and modals.
188
+
189
+ ## Project Shape
190
+
191
+ - `src/domain/`
192
+ Planning model, domain types, route metrics, and model tests.
193
+ - `src/constraints/`
194
+ Incremental SolverForge scoring rules.
195
+ - `src/data/`
196
+ Deterministic city demo-data generators.
197
+ - `src/solver/`
198
+ Retained-job facade and runtime event payload formatting.
199
+ - `src/api/`
200
+ Axum routes, DTOs, errors, and SSE endpoint.
201
+ - `static/app/`
202
+ Browser modules built on stock `solverforge-ui` assets.
203
+ - `tests/api_contract/`
204
+ API integration coverage for catalog, jobs, lifecycle, SSE, and routes.
205
+ - `tests/e2e/`
206
+ Playwright browser tests for the served app.
WIREFRAME.md ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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` `1.0.1` list-variable
26
+ SolverForge app 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 construction and local-search hooks
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, routing mode, view
45
+ 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 straight-line or road-network 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 app version 1.0.1 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.13.1` runtime target.
92
+ ├── Makefile
93
+ │ Hospital-style local build, validation, 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 matrices and per-vehicle prepared routing data.
148
+ - `cvrp_hooks.rs`
149
+ Supplies Clarke-Wright, k-opt, load, capacity, and route replacement hooks.
150
+ - `metrics.rs`
151
+ Computes per-vehicle route metrics.
152
+ - `scoring.rs`
153
+ Builds preview DTOs and aggregate hard/soft score components.
154
+ - `routes.rs`
155
+ Builds straight-line or road-network route geometry snapshots, including
156
+ edge-projected visual endpoints for road legs.
157
+ - `insertions.rs`
158
+ Ranks candidate insertion positions for one delivery.
159
+ - `types.rs`
160
+ Shared route metrics, snapshots, candidates, and routing trait types.
161
+
162
+ This split keeps the public domain API stable while avoiding oversized files.
163
+
164
+ ## Demo Data
165
+
166
+ `src/data/data_seed/entrypoints.rs` exposes three demo IDs:
167
+
168
+ - `PHILADELPHIA`
169
+ - `HARTFORD`
170
+ - `FIRENZE`
171
+
172
+ Each city has a small module with separate depot and grouped visit files. The
173
+ generator is deterministic. Every demo has ten vehicle depots, enough scaled
174
+ deliveries for those vehicles, reachable road-network coordinates, and enough
175
+ aggregate capacity before route ordering.
176
+
177
+ ## API And Retained Runtime
178
+
179
+ The REST API handles job control and snapshot reads:
180
+
181
+ - `/jobs` creates a retained solver job.
182
+ - `/jobs/{id}` and `/jobs/{id}/status` expose summary state.
183
+ - `/jobs/{id}/snapshot` returns an exact or latest snapshot.
184
+ - `/jobs/{id}/analysis` runs constraint analysis for a snapshot.
185
+ - `/jobs/{id}/routes` returns route geometry for a snapshot.
186
+ - `/jobs/{id}/events` streams typed lifecycle events.
187
+
188
+ The insertion endpoint, `/recommendations/delivery-insertions`, is app-specific.
189
+ It prepares the submitted plan, removes the requested delivery from any
190
+ existing route, evaluates candidate insert positions, and returns preview plans.
191
+
192
+ ## Frontend Layout
193
+
194
+ `static/app/main.mjs` is the controller. It owns current plan state, retained
195
+ job state, route identity tracking, and event handlers.
196
+
197
+ Supporting modules are split by responsibility:
198
+
199
+ - `static/app/models/core.mjs`
200
+ Clone and normalize incoming plans.
201
+ - `static/app/models/preview.mjs`
202
+ Draft straight-line preview scoring.
203
+ - `static/app/models/timeline.mjs`
204
+ Vehicle and delivery rail models.
205
+ - `static/app/models/formatters.mjs`
206
+ Labels, icons, tones, clocks, and durations.
207
+ - `static/app/ui/layout.mjs`
208
+ Page shell and stock SolverForge UI component composition.
209
+ - `static/app/ui/overview.mjs`
210
+ Summary, route list, vehicle-id keyed route highlighting, map, and timeline
211
+ rendering. The tutorial uses ten distinct map colors for its ten fixed
212
+ vehicles.
213
+ - `static/app/ui/data-tables.mjs`
214
+ Read-only vehicle and delivery tables with delivery insertion recommendations.
215
+ - `static/app/ui/modals.mjs`
216
+ Analysis and insertion recommendation bodies.
217
+ - `static/app/ui/api-guide.mjs`
218
+ Visible API guide content.
219
+ - `static/app/ui/lifecycle.mjs`
220
+ Dataset markers and route-identity helpers.
221
+
222
+ ## Validation Surfaces
223
+
224
+ Use the Makefile as the repo-local workflow:
225
+
226
+ - `make fmt-check`
227
+ - `make clippy`
228
+ - `make build-release`
229
+ - `make test`
230
+ - `make test-e2e`
231
+ - `make space-build`
232
+ - `make test-live-road`
233
+ - `make ci-local`
234
+ - `make pre-release`
235
+
236
+ `make ci-local` includes the Docker image build used by the Hugging Face Space.
237
+ The Playwright command uses the publication bundle's root Node dev dependency;
238
+ runtime UI assets are served from the declared `solverforge-ui` Cargo crate.
239
+
240
+ The file-size rule is part of the architecture: keep files below 300 lines and
241
+ split by responsibility before they become broad catch-all modules.
docs/screenshot.png ADDED

Git LFS Details

  • SHA256: ab699c0cc0a22f01ee41866fce787b3b049f24665fbb1f8a4a1e30434488510f
  • Pointer size: 131 Bytes
  • Size of remote file: 136 kB
solver.toml ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = "accepted_count"
30
+ limit = 4
31
+
32
+ [phases.move_selector]
33
+ type = "union_move_selector"
34
+
35
+ [[phases.move_selector.selectors]]
36
+ type = "nearby_list_change_move_selector"
37
+ max_nearby = 20
38
+ entity_class = "Vehicle"
39
+ variable_name = "delivery_order"
40
+
41
+ [[phases.move_selector.selectors]]
42
+ type = "nearby_list_swap_move_selector"
43
+ max_nearby = 20
44
+ entity_class = "Vehicle"
45
+ variable_name = "delivery_order"
46
+
47
+ [[phases.move_selector.selectors]]
48
+ type = "list_reverse_move_selector"
49
+ entity_class = "Vehicle"
50
+ variable_name = "delivery_order"
51
+
52
+ [[phases.move_selector.selectors]]
53
+ type = "k_opt_move_selector"
54
+ k = 3
55
+ min_segment_len = 1
56
+ max_nearby = 10
57
+ entity_class = "Vehicle"
58
+ variable_name = "delivery_order"
59
+
60
+ [[phases.move_selector.selectors]]
61
+ type = "list_ruin_move_selector"
62
+ min_ruin_count = 2
63
+ max_ruin_count = 5
64
+ moves_per_step = 10
65
+ entity_class = "Vehicle"
66
+ variable_name = "delivery_order"
67
+
68
+ [[phases.move_selector.selectors]]
69
+ type = "limited_neighborhood"
70
+ selected_count_limit = 500
71
+
72
+ [phases.move_selector.selectors.selector]
73
+ type = "sublist_change_move_selector"
74
+ min_sublist_size = 1
75
+ max_sublist_size = 3
76
+ entity_class = "Vehicle"
77
+ 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.0.4"
5
+
6
+ [runtime]
7
+ target = "solverforge 0.13.1"
8
+ runtime_source = "crates.io: solverforge 0.13.1"
9
+ ui_source = "crates.io: solverforge-ui 0.6.5"
10
+ maps_source = "crates.io: solverforge-maps 2.1.4"
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 = "delivery_time_windows"
51
+ module = "delivery_time_windows"
52
+ enabled = true
53
+
54
+ [[constraints]]
55
+ name = "total_travel_time"
56
+ module = "total_travel_time"
57
+ enabled = true
58
+
59
+ [[constraints]]
60
+ name = "vehicle_capacity"
61
+ module = "vehicle_capacity"
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,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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("straight_line".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
+ }
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,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Constraint assembly for delivery routing.
2
+ //!
3
+ //! Each sibling file contributes one named rule. `create_constraints()` lists
4
+ //! them in the order we want beginners to see in score analysis output.
5
+
6
+ use crate::domain::Plan;
7
+ use solverforge::prelude::*;
8
+
9
+ pub use self::assemble::create_constraints;
10
+
11
+ // @solverforge:begin constraint-modules
12
+ mod all_deliveries_assigned;
13
+ mod delivery_time_windows;
14
+ mod total_travel_time;
15
+ mod vehicle_capacity;
16
+ // @solverforge:end constraint-modules
17
+
18
+ mod assemble {
19
+ use super::*;
20
+
21
+ /// Collects the full scoring model used by `Plan`.
22
+ pub fn create_constraints() -> impl ConstraintSet<Plan, HardSoftScore> {
23
+ // @solverforge:begin constraint-calls
24
+ (
25
+ all_deliveries_assigned::constraint(),
26
+ vehicle_capacity::constraint(),
27
+ delivery_time_windows::constraint(),
28
+ total_travel_time::constraint(),
29
+ )
30
+ // @solverforge:end constraint-calls
31
+ }
32
+ }
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,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, routing mode, 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, RoutingMode, 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
+ let mut plan = Plan::new(demo.label(), deliveries, vehicles);
132
+ plan.routing_mode = RoutingMode::RoadNetwork;
133
+ plan
134
+ }
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/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 list-variable hooks close to the domain they describe.
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 preview;
15
+ mod vehicle;
16
+
17
+ pub use coord_value::CoordValue;
18
+ pub use delivery::Delivery;
19
+ pub use delivery::DeliveryKind;
20
+ pub use plan::{Plan, PlanConstraintStreams};
21
+ pub use preview::{
22
+ DeliveryPreview, PlanPreview, PlanViewState, RoutingMode, TimelineView, VehiclePreview,
23
+ VehiclePreviewStop,
24
+ };
25
+ pub use vehicle::Vehicle;
26
+ // @solverforge:end domain-exports
27
+
28
+ mod route_metrics;
29
+
30
+ pub use route_metrics::{
31
+ build_routes_snapshot, delivery_clarke_wright_depot, delivery_element_load,
32
+ delivery_k_opt_depot, delivery_k_opt_feasible, delivery_route_capacity,
33
+ delivery_route_distance, evaluate_plan, get_delivery_route, prepare_plan,
34
+ preview_for_plan, rank_delivery_insertions, replace_delivery_route,
35
+ DeliveryInsertionCandidate, DeliveryRoutingSolution, PlanScoreComponents,
36
+ PreparedVehicleRouting, RouteLegGeometry, RouteLegSummary, RoutesSnapshot,
37
+ VehicleRouteMetrics, UNASSIGNED_DELIVERY_HARD_PENALTY,
38
+ };
39
+ }
40
+
41
+ #[cfg(test)]
42
+ mod plan_tests;
src/domain/plan.rs ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Planning solution for the delivery-routing problem.
2
+ //!
3
+ //! The `Plan` owns facts, planning entities, score, routing mode, and transient
4
+ //! prepared route matrices. It is both the solver input and the shape serialized
5
+ //! 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_is_trivial(plan: &Self) -> bool {
163
+ Self::__solverforge_is_trivial(plan)
164
+ }
165
+
166
+ pub(crate) fn test_phase_count(config: &solverforge::SolverConfig) -> usize {
167
+ Self::__solverforge_build_phases(config).phases().len()
168
+ }
169
+ }
src/domain/plan_tests.rs ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::*;
2
+ use crate::data::{generate, DemoData};
3
+ use crate::domain::{prepare_plan, DeliveryKind, UNASSIGNED_DELIVERY_HARD_PENALTY};
4
+ use solverforge::{Director, ScoreDirector, SolverConfig, SolverEvent, SolverManager};
5
+
6
+ fn tiny_plan() -> Plan {
7
+ Plan::new(
8
+ "tiny",
9
+ vec![
10
+ Delivery::new(
11
+ 0,
12
+ "A",
13
+ DeliveryKind::Residential,
14
+ (39.9526, -75.1652),
15
+ 1,
16
+ (8 * 3600, 18 * 3600),
17
+ 10 * 60,
18
+ ),
19
+ Delivery::new(
20
+ 1,
21
+ "B",
22
+ DeliveryKind::Business,
23
+ (39.9626, -75.1752),
24
+ 1,
25
+ (8 * 3600, 18 * 3600),
26
+ 10 * 60,
27
+ ),
28
+ ],
29
+ vec![Vehicle::new(0, "Van 1", 4, 39.9526, -75.1652, 8 * 3600)],
30
+ )
31
+ }
32
+
33
+ fn prepared_tiny_plan_with_route() -> Plan {
34
+ let mut plan = tiny_plan();
35
+ plan.routing_mode = RoutingMode::StraightLine;
36
+ plan.vehicles[0].delivery_order = vec![0, 1];
37
+ tokio::runtime::Builder::new_current_thread()
38
+ .enable_all()
39
+ .build()
40
+ .expect("tokio runtime")
41
+ .block_on(async {
42
+ prepare_plan(&mut plan)
43
+ .await
44
+ .expect("plan preparation should work");
45
+ });
46
+ plan
47
+ }
48
+
49
+ #[test]
50
+ fn score_director_populates_vehicle_route_shadows() {
51
+ let plan = prepared_tiny_plan_with_route();
52
+ assert_eq!(
53
+ plan.vehicles[0].route_total_demand, 0,
54
+ "prepared transport data should not eagerly populate solver shadows"
55
+ );
56
+
57
+ let mut director = ScoreDirector::with_descriptor(
58
+ plan,
59
+ crate::constraints::create_constraints(),
60
+ Plan::descriptor(),
61
+ Plan::entity_count,
62
+ );
63
+ let score = director.calculate_score();
64
+ let vehicle = &director.working_solution().vehicles[0];
65
+
66
+ assert_eq!(vehicle.total_assigned_demand(), 2);
67
+ assert_eq!(vehicle.capacity_overage(), 0);
68
+ assert!(
69
+ vehicle.total_travel_seconds() > 0,
70
+ "route travel should be maintained as a shadow value"
71
+ );
72
+ assert_eq!(score.hard(), 0);
73
+ }
74
+
75
+ #[test]
76
+ fn vehicle_route_shadows_refresh_after_list_variable_changes() {
77
+ let plan = prepared_tiny_plan_with_route();
78
+ let mut director = ScoreDirector::with_descriptor(
79
+ plan,
80
+ crate::constraints::create_constraints(),
81
+ Plan::descriptor(),
82
+ Plan::entity_count,
83
+ );
84
+ director.calculate_score();
85
+ assert_eq!(
86
+ director.working_solution().vehicles[0].total_assigned_demand(),
87
+ 2
88
+ );
89
+
90
+ director.before_variable_changed(0, 0);
91
+ director.working_solution_mut().vehicles[0]
92
+ .delivery_order
93
+ .clear();
94
+ director.after_variable_changed(0, 0);
95
+ let score = director.calculate_score();
96
+
97
+ let vehicle = &director.working_solution().vehicles[0];
98
+ assert_eq!(vehicle.total_assigned_demand(), 0);
99
+ assert_eq!(vehicle.total_travel_seconds(), 0);
100
+ assert_eq!(vehicle.time_window_violation_seconds(), 0);
101
+ assert_eq!(score.hard(), -(2 * UNASSIGNED_DELIVERY_HARD_PENALTY));
102
+ }
103
+
104
+ #[test]
105
+ fn generated_list_runtime_is_non_trivial_and_builds_routes() {
106
+ static MANAGER: SolverManager<Plan> = SolverManager::new();
107
+
108
+ let mut plan = tiny_plan();
109
+ plan.routing_mode = RoutingMode::StraightLine;
110
+ tokio::runtime::Builder::new_current_thread()
111
+ .enable_all()
112
+ .build()
113
+ .expect("tokio runtime")
114
+ .block_on(async {
115
+ prepare_plan(&mut plan)
116
+ .await
117
+ .expect("plan preparation should work");
118
+ });
119
+
120
+ assert!(
121
+ Plan::test_has_list_variable(),
122
+ "delivery plan should expose a list variable"
123
+ );
124
+ assert_eq!(Plan::test_total_list_entities(&plan), 1);
125
+ assert_eq!(Plan::test_total_list_elements(&plan), 2);
126
+ assert!(
127
+ !Plan::test_is_trivial(&plan),
128
+ "prepared plan should not be trivial"
129
+ );
130
+
131
+ let config =
132
+ SolverConfig::from_toml_str(include_str!("../../solver.toml")).expect("valid config");
133
+ assert_eq!(
134
+ Plan::test_phase_count(&config),
135
+ 3,
136
+ "expected Clarke-Wright construction + list k-opt + local search"
137
+ );
138
+
139
+ let (job_id, mut receiver) = MANAGER.solve(plan).expect("solve should start");
140
+ let mut saw_non_empty_best = false;
141
+ loop {
142
+ match receiver
143
+ .blocking_recv()
144
+ .expect("event stream should reach a terminal event")
145
+ {
146
+ SolverEvent::BestSolution { solution, .. } => {
147
+ if solution
148
+ .vehicles
149
+ .iter()
150
+ .any(|vehicle| !vehicle.delivery_order.is_empty())
151
+ {
152
+ saw_non_empty_best = true;
153
+ MANAGER.cancel(job_id).expect("job cancel should succeed");
154
+ }
155
+ }
156
+ SolverEvent::Completed { .. } | SolverEvent::Cancelled { .. } => break,
157
+ SolverEvent::Failed { error, .. } => {
158
+ panic!("solve unexpectedly failed: {error}");
159
+ }
160
+ SolverEvent::Progress { .. }
161
+ | SolverEvent::PauseRequested { .. }
162
+ | SolverEvent::Paused { .. }
163
+ | SolverEvent::Resumed { .. } => {}
164
+ }
165
+ }
166
+ MANAGER
167
+ .delete(job_id)
168
+ .expect("completed test job should delete");
169
+
170
+ assert!(
171
+ saw_non_empty_best,
172
+ "expected a non-empty best solution before cancellation"
173
+ );
174
+ }
175
+
176
+ #[test]
177
+ fn seeded_philadelphia_plan_emits_a_non_empty_best_solution() {
178
+ static MANAGER: SolverManager<Plan> = SolverManager::new();
179
+
180
+ let mut plan = generate(DemoData::Philadelphia);
181
+ plan.routing_mode = RoutingMode::StraightLine;
182
+ tokio::runtime::Builder::new_current_thread()
183
+ .enable_all()
184
+ .build()
185
+ .expect("tokio runtime")
186
+ .block_on(async {
187
+ prepare_plan(&mut plan)
188
+ .await
189
+ .expect("plan preparation should work");
190
+ });
191
+
192
+ let (job_id, mut receiver) = MANAGER.solve(plan).expect("solve should start");
193
+ let mut saw_non_empty_best = false;
194
+ let mut first_non_empty_best: Option<Plan> = None;
195
+ loop {
196
+ match receiver
197
+ .blocking_recv()
198
+ .expect("event stream should reach a terminal event")
199
+ {
200
+ SolverEvent::BestSolution { solution, .. } => {
201
+ if solution
202
+ .vehicles
203
+ .iter()
204
+ .any(|vehicle| !vehicle.delivery_order.is_empty())
205
+ {
206
+ saw_non_empty_best = true;
207
+ first_non_empty_best.get_or_insert(solution.clone());
208
+ MANAGER.cancel(job_id).expect("job cancel should succeed");
209
+ }
210
+ }
211
+ SolverEvent::Completed { .. } | SolverEvent::Cancelled { .. } => break,
212
+ SolverEvent::Failed { error, .. } => {
213
+ panic!("solve unexpectedly failed: {error}");
214
+ }
215
+ SolverEvent::Progress { .. }
216
+ | SolverEvent::PauseRequested { .. }
217
+ | SolverEvent::Paused { .. }
218
+ | SolverEvent::Resumed { .. } => {}
219
+ }
220
+ }
221
+ MANAGER
222
+ .delete(job_id)
223
+ .expect("completed test job should delete");
224
+
225
+ assert!(
226
+ saw_non_empty_best,
227
+ "expected a non-empty best solution for the seeded Philadelphia plan"
228
+ );
229
+ let best = first_non_empty_best.expect("should retain the first non-empty best solution");
230
+ assert!(
231
+ best.vehicles
232
+ .iter()
233
+ .any(|vehicle| vehicle.delivery_order.len() > 1),
234
+ "expected at least one multi-stop route after construction"
235
+ );
236
+ let director = ScoreDirector::with_descriptor(
237
+ best.clone(),
238
+ crate::constraints::create_constraints(),
239
+ Plan::descriptor(),
240
+ Plan::entity_count,
241
+ );
242
+ assert_eq!(director.entity_count(0), Some(best.vehicles.len()));
243
+ }
src/domain/preview.rs ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Browser preview structs embedded in `Plan.view_state`.
2
+ //!
3
+ //! These values are derived from the domain plan before transport. They let the
4
+ //! frontend render route summaries and timelines without duplicating every
5
+ //! scoring rule.
6
+
7
+ use serde::{Deserialize, Serialize};
8
+
9
+ /// Chooses whether routing uses fast straight lines or real road-network data.
10
+ #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
11
+ #[serde(rename_all = "snake_case")]
12
+ pub enum RoutingMode {
13
+ StraightLine,
14
+ #[default]
15
+ RoadNetwork,
16
+ }
17
+
18
+ /// Selects which timeline rail the browser shows.
19
+ #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
20
+ #[serde(rename_all = "snake_case")]
21
+ pub enum TimelineView {
22
+ #[default]
23
+ ByVehicle,
24
+ ByDelivery,
25
+ }
26
+
27
+ /// UI-only state that travels with the plan between browser and backend.
28
+ #[derive(Clone, Debug, Default, Serialize, Deserialize)]
29
+ #[serde(rename_all = "camelCase")]
30
+ pub struct PlanViewState {
31
+ #[serde(default)]
32
+ pub timeline_view: TimelineView,
33
+ pub selected_vehicle_id: Option<usize>,
34
+ pub selected_delivery_id: Option<usize>,
35
+ #[serde(default)]
36
+ pub preview: Option<PlanPreview>,
37
+ }
38
+
39
+ /// Aggregate route and score preview for the full plan.
40
+ #[derive(Clone, Debug, Default, Serialize, Deserialize)]
41
+ #[serde(rename_all = "camelCase")]
42
+ pub struct PlanPreview {
43
+ pub hard_score: i64,
44
+ pub soft_score: i64,
45
+ pub unassigned_delivery_ids: Vec<usize>,
46
+ pub vehicles: Vec<VehiclePreview>,
47
+ pub deliveries: Vec<DeliveryPreview>,
48
+ }
49
+
50
+ /// Per-vehicle route summary used by cards, lists, and timelines.
51
+ #[derive(Clone, Debug, Default, Serialize, Deserialize)]
52
+ #[serde(rename_all = "camelCase")]
53
+ pub struct VehiclePreview {
54
+ pub vehicle_id: usize,
55
+ pub vehicle_name: String,
56
+ pub total_demand: i32,
57
+ pub capacity_overage: i32,
58
+ pub stop_count: usize,
59
+ pub total_travel_seconds: i64,
60
+ pub total_wait_seconds: i64,
61
+ pub total_service_seconds: i64,
62
+ pub total_late_seconds: i64,
63
+ pub start_time: i64,
64
+ pub end_time: i64,
65
+ pub stops: Vec<VehiclePreviewStop>,
66
+ }
67
+
68
+ /// One delivery stop on a vehicle timeline.
69
+ #[derive(Clone, Debug, Default, Serialize, Deserialize)]
70
+ #[serde(rename_all = "camelCase")]
71
+ pub struct VehiclePreviewStop {
72
+ pub delivery_id: usize,
73
+ pub label: String,
74
+ pub kind: String,
75
+ pub sequence: usize,
76
+ pub demand: i32,
77
+ pub min_start_time: i64,
78
+ pub max_end_time: i64,
79
+ pub arrival_time: i64,
80
+ pub service_start_time: i64,
81
+ pub departure_time: i64,
82
+ pub travel_seconds_from_previous: i64,
83
+ pub wait_seconds: i64,
84
+ pub late_seconds: i64,
85
+ }
86
+
87
+ /// Per-delivery assignment summary used by data tables and timelines.
88
+ #[derive(Clone, Debug, Default, Serialize, Deserialize)]
89
+ #[serde(rename_all = "camelCase")]
90
+ pub struct DeliveryPreview {
91
+ pub delivery_id: usize,
92
+ pub label: String,
93
+ pub kind: String,
94
+ pub demand: i32,
95
+ pub min_start_time: i64,
96
+ pub max_end_time: i64,
97
+ pub service_duration: i64,
98
+ pub assigned_vehicle_id: Option<usize>,
99
+ pub assigned_vehicle_name: Option<String>,
100
+ pub sequence: Option<usize>,
101
+ pub arrival_time: Option<i64>,
102
+ pub service_start_time: Option<i64>,
103
+ pub departure_time: Option<i64>,
104
+ pub late_seconds: Option<i64>,
105
+ }