blackopsrepl commited on
Commit
f093ab7
·
1 Parent(s): 7b2a37a

feat: add Bergamo field service routing model

Browse files

Replace the neutral SolverForge scaffold with a FieldServicePlan model for a Bergamo field service routing demo.

The model now includes locations, service visits, travel legs, and technician routes with a list variable over visits. The retained solver service and API DTOs now use that solution type instead of the generated neutral Plan shell.

Add route-aware hard and soft constraints for reachable road legs, technician skills, vehicle parts, time windows, shift capacity, travel minimization, workload balance, territory affinity, and priority slack. These constraints share route metric helpers so scoring and seeded insertion use the same route semantics.

Generate Bergamo demo data from solverforge-maps OSM road data, including cached network loading, travel matrices, encoded route geometry, and deterministic initial routes for small, standard, and large demo sizes. The demo-data endpoint is async and returns service unavailable when map routing data cannot be loaded.

Update solver metadata, generated UI model metadata, solver phase settings, the Cargo lockfile, and ignore the local OSM cache so this backend feature can be reverted independently from the visual workspace.

.gitignore CHANGED
@@ -1,2 +1,3 @@
1
  /target
 
2
  **/*.rs.bk
 
1
  /target
2
+ .osm_cache/
3
  **/*.rs.bk
Cargo.lock ADDED
@@ -0,0 +1,2682 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.61"
133
+ source = "registry+https://github.com/rust-lang/crates.io-index"
134
+ checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
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.13"
408
+ source = "registry+https://github.com/rust-lang/crates.io-index"
409
+ checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
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.0"
436
+ source = "registry+https://github.com/rust-lang/crates.io-index"
437
+ checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
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.0",
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 = "iri-string"
706
+ version = "0.7.12"
707
+ source = "registry+https://github.com/rust-lang/crates.io-index"
708
+ checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
709
+ dependencies = [
710
+ "memchr",
711
+ "serde",
712
+ ]
713
+
714
+ [[package]]
715
+ name = "itoa"
716
+ version = "1.0.18"
717
+ source = "registry+https://github.com/rust-lang/crates.io-index"
718
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
719
+
720
+ [[package]]
721
+ name = "jni"
722
+ version = "0.22.4"
723
+ source = "registry+https://github.com/rust-lang/crates.io-index"
724
+ checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
725
+ dependencies = [
726
+ "cfg-if",
727
+ "combine",
728
+ "jni-macros",
729
+ "jni-sys",
730
+ "log",
731
+ "simd_cesu8",
732
+ "thiserror",
733
+ "walkdir",
734
+ "windows-link",
735
+ ]
736
+
737
+ [[package]]
738
+ name = "jni-macros"
739
+ version = "0.22.4"
740
+ source = "registry+https://github.com/rust-lang/crates.io-index"
741
+ checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
742
+ dependencies = [
743
+ "proc-macro2",
744
+ "quote",
745
+ "rustc_version",
746
+ "simd_cesu8",
747
+ "syn",
748
+ ]
749
+
750
+ [[package]]
751
+ name = "jni-sys"
752
+ version = "0.4.1"
753
+ source = "registry+https://github.com/rust-lang/crates.io-index"
754
+ checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
755
+ dependencies = [
756
+ "jni-sys-macros",
757
+ ]
758
+
759
+ [[package]]
760
+ name = "jni-sys-macros"
761
+ version = "0.4.1"
762
+ source = "registry+https://github.com/rust-lang/crates.io-index"
763
+ checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
764
+ dependencies = [
765
+ "quote",
766
+ "syn",
767
+ ]
768
+
769
+ [[package]]
770
+ name = "jobserver"
771
+ version = "0.1.34"
772
+ source = "registry+https://github.com/rust-lang/crates.io-index"
773
+ checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
774
+ dependencies = [
775
+ "getrandom 0.3.4",
776
+ "libc",
777
+ ]
778
+
779
+ [[package]]
780
+ name = "js-sys"
781
+ version = "0.3.97"
782
+ source = "registry+https://github.com/rust-lang/crates.io-index"
783
+ checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
784
+ dependencies = [
785
+ "cfg-if",
786
+ "futures-util",
787
+ "once_cell",
788
+ "wasm-bindgen",
789
+ ]
790
+
791
+ [[package]]
792
+ name = "lazy_static"
793
+ version = "1.5.0"
794
+ source = "registry+https://github.com/rust-lang/crates.io-index"
795
+ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
796
+
797
+ [[package]]
798
+ name = "leb128fmt"
799
+ version = "0.1.0"
800
+ source = "registry+https://github.com/rust-lang/crates.io-index"
801
+ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
802
+
803
+ [[package]]
804
+ name = "libc"
805
+ version = "0.2.186"
806
+ source = "registry+https://github.com/rust-lang/crates.io-index"
807
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
808
+
809
+ [[package]]
810
+ name = "litemap"
811
+ version = "0.8.2"
812
+ source = "registry+https://github.com/rust-lang/crates.io-index"
813
+ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
814
+
815
+ [[package]]
816
+ name = "lock_api"
817
+ version = "0.4.14"
818
+ source = "registry+https://github.com/rust-lang/crates.io-index"
819
+ checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
820
+ dependencies = [
821
+ "scopeguard",
822
+ ]
823
+
824
+ [[package]]
825
+ name = "log"
826
+ version = "0.4.29"
827
+ source = "registry+https://github.com/rust-lang/crates.io-index"
828
+ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
829
+
830
+ [[package]]
831
+ name = "lru-slab"
832
+ version = "0.1.2"
833
+ source = "registry+https://github.com/rust-lang/crates.io-index"
834
+ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
835
+
836
+ [[package]]
837
+ name = "matchers"
838
+ version = "0.2.0"
839
+ source = "registry+https://github.com/rust-lang/crates.io-index"
840
+ checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
841
+ dependencies = [
842
+ "regex-automata",
843
+ ]
844
+
845
+ [[package]]
846
+ name = "matchit"
847
+ version = "0.8.4"
848
+ source = "registry+https://github.com/rust-lang/crates.io-index"
849
+ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
850
+
851
+ [[package]]
852
+ name = "memchr"
853
+ version = "2.8.0"
854
+ source = "registry+https://github.com/rust-lang/crates.io-index"
855
+ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
856
+
857
+ [[package]]
858
+ name = "mime"
859
+ version = "0.3.17"
860
+ source = "registry+https://github.com/rust-lang/crates.io-index"
861
+ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
862
+
863
+ [[package]]
864
+ name = "mime_guess"
865
+ version = "2.0.5"
866
+ source = "registry+https://github.com/rust-lang/crates.io-index"
867
+ checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
868
+ dependencies = [
869
+ "mime",
870
+ "unicase",
871
+ ]
872
+
873
+ [[package]]
874
+ name = "mio"
875
+ version = "1.2.0"
876
+ source = "registry+https://github.com/rust-lang/crates.io-index"
877
+ checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
878
+ dependencies = [
879
+ "libc",
880
+ "wasi",
881
+ "windows-sys 0.61.2",
882
+ ]
883
+
884
+ [[package]]
885
+ name = "nu-ansi-term"
886
+ version = "0.50.3"
887
+ source = "registry+https://github.com/rust-lang/crates.io-index"
888
+ checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
889
+ dependencies = [
890
+ "windows-sys 0.61.2",
891
+ ]
892
+
893
+ [[package]]
894
+ name = "num-format"
895
+ version = "0.4.4"
896
+ source = "registry+https://github.com/rust-lang/crates.io-index"
897
+ checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
898
+ dependencies = [
899
+ "arrayvec",
900
+ "itoa",
901
+ ]
902
+
903
+ [[package]]
904
+ name = "once_cell"
905
+ version = "1.21.4"
906
+ source = "registry+https://github.com/rust-lang/crates.io-index"
907
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
908
+
909
+ [[package]]
910
+ name = "openssl-probe"
911
+ version = "0.2.1"
912
+ source = "registry+https://github.com/rust-lang/crates.io-index"
913
+ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
914
+
915
+ [[package]]
916
+ name = "owo-colors"
917
+ version = "4.3.0"
918
+ source = "registry+https://github.com/rust-lang/crates.io-index"
919
+ checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
920
+
921
+ [[package]]
922
+ name = "parking_lot"
923
+ version = "0.12.5"
924
+ source = "registry+https://github.com/rust-lang/crates.io-index"
925
+ checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
926
+ dependencies = [
927
+ "lock_api",
928
+ "parking_lot_core",
929
+ ]
930
+
931
+ [[package]]
932
+ name = "parking_lot_core"
933
+ version = "0.9.12"
934
+ source = "registry+https://github.com/rust-lang/crates.io-index"
935
+ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
936
+ dependencies = [
937
+ "cfg-if",
938
+ "libc",
939
+ "redox_syscall",
940
+ "smallvec",
941
+ "windows-link",
942
+ ]
943
+
944
+ [[package]]
945
+ name = "percent-encoding"
946
+ version = "2.3.2"
947
+ source = "registry+https://github.com/rust-lang/crates.io-index"
948
+ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
949
+
950
+ [[package]]
951
+ name = "pin-project-lite"
952
+ version = "0.2.17"
953
+ source = "registry+https://github.com/rust-lang/crates.io-index"
954
+ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
955
+
956
+ [[package]]
957
+ name = "potential_utf"
958
+ version = "0.1.5"
959
+ source = "registry+https://github.com/rust-lang/crates.io-index"
960
+ checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
961
+ dependencies = [
962
+ "zerovec",
963
+ ]
964
+
965
+ [[package]]
966
+ name = "ppv-lite86"
967
+ version = "0.2.21"
968
+ source = "registry+https://github.com/rust-lang/crates.io-index"
969
+ checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
970
+ dependencies = [
971
+ "zerocopy",
972
+ ]
973
+
974
+ [[package]]
975
+ name = "prettyplease"
976
+ version = "0.2.37"
977
+ source = "registry+https://github.com/rust-lang/crates.io-index"
978
+ checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
979
+ dependencies = [
980
+ "proc-macro2",
981
+ "syn",
982
+ ]
983
+
984
+ [[package]]
985
+ name = "proc-macro2"
986
+ version = "1.0.106"
987
+ source = "registry+https://github.com/rust-lang/crates.io-index"
988
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
989
+ dependencies = [
990
+ "unicode-ident",
991
+ ]
992
+
993
+ [[package]]
994
+ name = "quinn"
995
+ version = "0.11.9"
996
+ source = "registry+https://github.com/rust-lang/crates.io-index"
997
+ checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
998
+ dependencies = [
999
+ "bytes",
1000
+ "cfg_aliases",
1001
+ "pin-project-lite",
1002
+ "quinn-proto",
1003
+ "quinn-udp",
1004
+ "rustc-hash",
1005
+ "rustls",
1006
+ "socket2",
1007
+ "thiserror",
1008
+ "tokio",
1009
+ "tracing",
1010
+ "web-time",
1011
+ ]
1012
+
1013
+ [[package]]
1014
+ name = "quinn-proto"
1015
+ version = "0.11.14"
1016
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1017
+ checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
1018
+ dependencies = [
1019
+ "aws-lc-rs",
1020
+ "bytes",
1021
+ "getrandom 0.3.4",
1022
+ "lru-slab",
1023
+ "rand 0.9.4",
1024
+ "ring",
1025
+ "rustc-hash",
1026
+ "rustls",
1027
+ "rustls-pki-types",
1028
+ "slab",
1029
+ "thiserror",
1030
+ "tinyvec",
1031
+ "tracing",
1032
+ "web-time",
1033
+ ]
1034
+
1035
+ [[package]]
1036
+ name = "quinn-udp"
1037
+ version = "0.5.14"
1038
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1039
+ checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
1040
+ dependencies = [
1041
+ "cfg_aliases",
1042
+ "libc",
1043
+ "once_cell",
1044
+ "socket2",
1045
+ "tracing",
1046
+ "windows-sys 0.60.2",
1047
+ ]
1048
+
1049
+ [[package]]
1050
+ name = "quote"
1051
+ version = "1.0.45"
1052
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1053
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
1054
+ dependencies = [
1055
+ "proc-macro2",
1056
+ ]
1057
+
1058
+ [[package]]
1059
+ name = "r-efi"
1060
+ version = "5.3.0"
1061
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1062
+ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
1063
+
1064
+ [[package]]
1065
+ name = "r-efi"
1066
+ version = "6.0.0"
1067
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1068
+ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
1069
+
1070
+ [[package]]
1071
+ name = "rand"
1072
+ version = "0.9.4"
1073
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1074
+ checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
1075
+ dependencies = [
1076
+ "rand_chacha 0.9.0",
1077
+ "rand_core 0.9.5",
1078
+ ]
1079
+
1080
+ [[package]]
1081
+ name = "rand"
1082
+ version = "0.10.1"
1083
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1084
+ checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
1085
+ dependencies = [
1086
+ "chacha20",
1087
+ "getrandom 0.4.2",
1088
+ "rand_core 0.10.1",
1089
+ ]
1090
+
1091
+ [[package]]
1092
+ name = "rand_chacha"
1093
+ version = "0.9.0"
1094
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1095
+ checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
1096
+ dependencies = [
1097
+ "ppv-lite86",
1098
+ "rand_core 0.9.5",
1099
+ ]
1100
+
1101
+ [[package]]
1102
+ name = "rand_chacha"
1103
+ version = "0.10.0"
1104
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1105
+ checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb"
1106
+ dependencies = [
1107
+ "ppv-lite86",
1108
+ "rand_core 0.10.1",
1109
+ ]
1110
+
1111
+ [[package]]
1112
+ name = "rand_core"
1113
+ version = "0.9.5"
1114
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1115
+ checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
1116
+ dependencies = [
1117
+ "getrandom 0.3.4",
1118
+ ]
1119
+
1120
+ [[package]]
1121
+ name = "rand_core"
1122
+ version = "0.10.1"
1123
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1124
+ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
1125
+
1126
+ [[package]]
1127
+ name = "rayon"
1128
+ version = "1.12.0"
1129
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1130
+ checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
1131
+ dependencies = [
1132
+ "either",
1133
+ "rayon-core",
1134
+ ]
1135
+
1136
+ [[package]]
1137
+ name = "rayon-core"
1138
+ version = "1.13.0"
1139
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1140
+ checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
1141
+ dependencies = [
1142
+ "crossbeam-deque",
1143
+ "crossbeam-utils",
1144
+ ]
1145
+
1146
+ [[package]]
1147
+ name = "redox_syscall"
1148
+ version = "0.5.18"
1149
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1150
+ checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
1151
+ dependencies = [
1152
+ "bitflags",
1153
+ ]
1154
+
1155
+ [[package]]
1156
+ name = "regex-automata"
1157
+ version = "0.4.14"
1158
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1159
+ checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
1160
+ dependencies = [
1161
+ "aho-corasick",
1162
+ "memchr",
1163
+ "regex-syntax",
1164
+ ]
1165
+
1166
+ [[package]]
1167
+ name = "regex-syntax"
1168
+ version = "0.8.10"
1169
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1170
+ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
1171
+
1172
+ [[package]]
1173
+ name = "reqwest"
1174
+ version = "0.13.3"
1175
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1176
+ checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0"
1177
+ dependencies = [
1178
+ "base64",
1179
+ "bytes",
1180
+ "encoding_rs",
1181
+ "futures-core",
1182
+ "h2",
1183
+ "http",
1184
+ "http-body",
1185
+ "http-body-util",
1186
+ "hyper",
1187
+ "hyper-rustls",
1188
+ "hyper-util",
1189
+ "js-sys",
1190
+ "log",
1191
+ "mime",
1192
+ "percent-encoding",
1193
+ "pin-project-lite",
1194
+ "quinn",
1195
+ "rustls",
1196
+ "rustls-pki-types",
1197
+ "rustls-platform-verifier",
1198
+ "serde",
1199
+ "serde_json",
1200
+ "sync_wrapper",
1201
+ "tokio",
1202
+ "tokio-rustls",
1203
+ "tower",
1204
+ "tower-http",
1205
+ "tower-service",
1206
+ "url",
1207
+ "wasm-bindgen",
1208
+ "wasm-bindgen-futures",
1209
+ "web-sys",
1210
+ ]
1211
+
1212
+ [[package]]
1213
+ name = "ring"
1214
+ version = "0.17.14"
1215
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1216
+ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
1217
+ dependencies = [
1218
+ "cc",
1219
+ "cfg-if",
1220
+ "getrandom 0.2.17",
1221
+ "libc",
1222
+ "untrusted",
1223
+ "windows-sys 0.52.0",
1224
+ ]
1225
+
1226
+ [[package]]
1227
+ name = "rustc-hash"
1228
+ version = "2.1.2"
1229
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1230
+ checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
1231
+
1232
+ [[package]]
1233
+ name = "rustc_version"
1234
+ version = "0.4.1"
1235
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1236
+ checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
1237
+ dependencies = [
1238
+ "semver",
1239
+ ]
1240
+
1241
+ [[package]]
1242
+ name = "rustls"
1243
+ version = "0.23.40"
1244
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1245
+ checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
1246
+ dependencies = [
1247
+ "aws-lc-rs",
1248
+ "once_cell",
1249
+ "rustls-pki-types",
1250
+ "rustls-webpki",
1251
+ "subtle",
1252
+ "zeroize",
1253
+ ]
1254
+
1255
+ [[package]]
1256
+ name = "rustls-native-certs"
1257
+ version = "0.8.3"
1258
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1259
+ checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
1260
+ dependencies = [
1261
+ "openssl-probe",
1262
+ "rustls-pki-types",
1263
+ "schannel",
1264
+ "security-framework",
1265
+ ]
1266
+
1267
+ [[package]]
1268
+ name = "rustls-pki-types"
1269
+ version = "1.14.1"
1270
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1271
+ checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
1272
+ dependencies = [
1273
+ "web-time",
1274
+ "zeroize",
1275
+ ]
1276
+
1277
+ [[package]]
1278
+ name = "rustls-platform-verifier"
1279
+ version = "0.7.0"
1280
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1281
+ checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
1282
+ dependencies = [
1283
+ "core-foundation 0.10.1",
1284
+ "core-foundation-sys",
1285
+ "jni",
1286
+ "log",
1287
+ "once_cell",
1288
+ "rustls",
1289
+ "rustls-native-certs",
1290
+ "rustls-platform-verifier-android",
1291
+ "rustls-webpki",
1292
+ "security-framework",
1293
+ "security-framework-sys",
1294
+ "webpki-root-certs",
1295
+ "windows-sys 0.61.2",
1296
+ ]
1297
+
1298
+ [[package]]
1299
+ name = "rustls-platform-verifier-android"
1300
+ version = "0.1.1"
1301
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1302
+ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
1303
+
1304
+ [[package]]
1305
+ name = "rustls-webpki"
1306
+ version = "0.103.13"
1307
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1308
+ checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
1309
+ dependencies = [
1310
+ "aws-lc-rs",
1311
+ "ring",
1312
+ "rustls-pki-types",
1313
+ "untrusted",
1314
+ ]
1315
+
1316
+ [[package]]
1317
+ name = "rustversion"
1318
+ version = "1.0.22"
1319
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1320
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
1321
+
1322
+ [[package]]
1323
+ name = "ryu"
1324
+ version = "1.0.23"
1325
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1326
+ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
1327
+
1328
+ [[package]]
1329
+ name = "same-file"
1330
+ version = "1.0.6"
1331
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1332
+ checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
1333
+ dependencies = [
1334
+ "winapi-util",
1335
+ ]
1336
+
1337
+ [[package]]
1338
+ name = "schannel"
1339
+ version = "0.1.29"
1340
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1341
+ checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
1342
+ dependencies = [
1343
+ "windows-sys 0.61.2",
1344
+ ]
1345
+
1346
+ [[package]]
1347
+ name = "scopeguard"
1348
+ version = "1.2.0"
1349
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1350
+ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
1351
+
1352
+ [[package]]
1353
+ name = "security-framework"
1354
+ version = "3.7.0"
1355
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1356
+ checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
1357
+ dependencies = [
1358
+ "bitflags",
1359
+ "core-foundation 0.10.1",
1360
+ "core-foundation-sys",
1361
+ "libc",
1362
+ "security-framework-sys",
1363
+ ]
1364
+
1365
+ [[package]]
1366
+ name = "security-framework-sys"
1367
+ version = "2.17.0"
1368
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1369
+ checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
1370
+ dependencies = [
1371
+ "core-foundation-sys",
1372
+ "libc",
1373
+ ]
1374
+
1375
+ [[package]]
1376
+ name = "semver"
1377
+ version = "1.0.28"
1378
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1379
+ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
1380
+
1381
+ [[package]]
1382
+ name = "serde"
1383
+ version = "1.0.228"
1384
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1385
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
1386
+ dependencies = [
1387
+ "serde_core",
1388
+ "serde_derive",
1389
+ ]
1390
+
1391
+ [[package]]
1392
+ name = "serde_core"
1393
+ version = "1.0.228"
1394
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1395
+ checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
1396
+ dependencies = [
1397
+ "serde_derive",
1398
+ ]
1399
+
1400
+ [[package]]
1401
+ name = "serde_derive"
1402
+ version = "1.0.228"
1403
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1404
+ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
1405
+ dependencies = [
1406
+ "proc-macro2",
1407
+ "quote",
1408
+ "syn",
1409
+ ]
1410
+
1411
+ [[package]]
1412
+ name = "serde_json"
1413
+ version = "1.0.149"
1414
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1415
+ checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
1416
+ dependencies = [
1417
+ "itoa",
1418
+ "memchr",
1419
+ "serde",
1420
+ "serde_core",
1421
+ "zmij",
1422
+ ]
1423
+
1424
+ [[package]]
1425
+ name = "serde_path_to_error"
1426
+ version = "0.1.20"
1427
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1428
+ checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
1429
+ dependencies = [
1430
+ "itoa",
1431
+ "serde",
1432
+ "serde_core",
1433
+ ]
1434
+
1435
+ [[package]]
1436
+ name = "serde_spanned"
1437
+ version = "1.1.1"
1438
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1439
+ checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
1440
+ dependencies = [
1441
+ "serde_core",
1442
+ ]
1443
+
1444
+ [[package]]
1445
+ name = "serde_urlencoded"
1446
+ version = "0.7.1"
1447
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1448
+ checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
1449
+ dependencies = [
1450
+ "form_urlencoded",
1451
+ "itoa",
1452
+ "ryu",
1453
+ "serde",
1454
+ ]
1455
+
1456
+ [[package]]
1457
+ name = "serde_yaml"
1458
+ version = "0.9.34+deprecated"
1459
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1460
+ checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
1461
+ dependencies = [
1462
+ "indexmap",
1463
+ "itoa",
1464
+ "ryu",
1465
+ "serde",
1466
+ "unsafe-libyaml",
1467
+ ]
1468
+
1469
+ [[package]]
1470
+ name = "sharded-slab"
1471
+ version = "0.1.7"
1472
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1473
+ checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
1474
+ dependencies = [
1475
+ "lazy_static",
1476
+ ]
1477
+
1478
+ [[package]]
1479
+ name = "shlex"
1480
+ version = "1.3.0"
1481
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1482
+ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
1483
+
1484
+ [[package]]
1485
+ name = "signal-hook-registry"
1486
+ version = "1.4.8"
1487
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1488
+ checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
1489
+ dependencies = [
1490
+ "errno",
1491
+ "libc",
1492
+ ]
1493
+
1494
+ [[package]]
1495
+ name = "simd_cesu8"
1496
+ version = "1.1.1"
1497
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1498
+ checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
1499
+ dependencies = [
1500
+ "rustc_version",
1501
+ "simdutf8",
1502
+ ]
1503
+
1504
+ [[package]]
1505
+ name = "simdutf8"
1506
+ version = "0.1.5"
1507
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1508
+ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
1509
+
1510
+ [[package]]
1511
+ name = "slab"
1512
+ version = "0.4.12"
1513
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1514
+ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
1515
+
1516
+ [[package]]
1517
+ name = "smallvec"
1518
+ version = "1.15.1"
1519
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1520
+ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
1521
+
1522
+ [[package]]
1523
+ name = "socket2"
1524
+ version = "0.6.3"
1525
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1526
+ checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
1527
+ dependencies = [
1528
+ "libc",
1529
+ "windows-sys 0.61.2",
1530
+ ]
1531
+
1532
+ [[package]]
1533
+ name = "solverforge"
1534
+ version = "0.10.0"
1535
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1536
+ checksum = "ac8ac3cbea60faa17ab8ddceeaa6a611c688cb890dca116cabfebfea3790648d"
1537
+ dependencies = [
1538
+ "solverforge-config",
1539
+ "solverforge-console",
1540
+ "solverforge-core",
1541
+ "solverforge-cvrp",
1542
+ "solverforge-macros",
1543
+ "solverforge-scoring",
1544
+ "solverforge-solver",
1545
+ "tokio",
1546
+ ]
1547
+
1548
+ [[package]]
1549
+ name = "solverforge-config"
1550
+ version = "0.10.0"
1551
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1552
+ checksum = "d46b32d7da5da623923fdfa9a2e36b1ba3eb363662993b410d89db5ff9c2e541"
1553
+ dependencies = [
1554
+ "serde",
1555
+ "serde_yaml",
1556
+ "solverforge-core",
1557
+ "thiserror",
1558
+ "toml",
1559
+ ]
1560
+
1561
+ [[package]]
1562
+ name = "solverforge-console"
1563
+ version = "0.10.0"
1564
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1565
+ checksum = "7d2ae5147ba1fdf39755176cca0c3747979f9d7055a5dfbb3ce6d8c069f0baa7"
1566
+ dependencies = [
1567
+ "num-format",
1568
+ "owo-colors",
1569
+ "tracing",
1570
+ "tracing-subscriber",
1571
+ ]
1572
+
1573
+ [[package]]
1574
+ name = "solverforge-core"
1575
+ version = "0.10.0"
1576
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1577
+ checksum = "ad26653a0d2675342b0958fb58973de16ce46570d50c0a2424d7e9b5b8da2114"
1578
+ dependencies = [
1579
+ "serde",
1580
+ "thiserror",
1581
+ ]
1582
+
1583
+ [[package]]
1584
+ name = "solverforge-cvrp"
1585
+ version = "0.10.0"
1586
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1587
+ checksum = "7360e8ad653c9b762d72a335067b3fb1f391ccc563ffcdee371ab93606bfb79e"
1588
+ dependencies = [
1589
+ "solverforge-solver",
1590
+ ]
1591
+
1592
+ [[package]]
1593
+ name = "solverforge-fsr"
1594
+ version = "0.1.0"
1595
+ dependencies = [
1596
+ "axum",
1597
+ "parking_lot",
1598
+ "serde",
1599
+ "serde_json",
1600
+ "solverforge",
1601
+ "solverforge-maps",
1602
+ "solverforge-ui",
1603
+ "tokio",
1604
+ "tokio-stream",
1605
+ "tower",
1606
+ "tower-http",
1607
+ "uuid",
1608
+ ]
1609
+
1610
+ [[package]]
1611
+ name = "solverforge-macros"
1612
+ version = "0.10.0"
1613
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1614
+ checksum = "64aedbc82265f2d7dab186f27d56d3ab7eb5ddaa024701831471e1cd6b850e91"
1615
+ dependencies = [
1616
+ "proc-macro2",
1617
+ "quote",
1618
+ "syn",
1619
+ ]
1620
+
1621
+ [[package]]
1622
+ name = "solverforge-maps"
1623
+ version = "2.1.3"
1624
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1625
+ checksum = "939b91fd4706c75795ef82db3a569fa47868d72763c48e24e992c00df242dbe0"
1626
+ dependencies = [
1627
+ "rayon",
1628
+ "reqwest",
1629
+ "serde",
1630
+ "serde_json",
1631
+ "tokio",
1632
+ "tracing",
1633
+ "utoipa",
1634
+ ]
1635
+
1636
+ [[package]]
1637
+ name = "solverforge-scoring"
1638
+ version = "0.10.0"
1639
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1640
+ checksum = "6645ffc7e554c69d61d98d5cd922eb0ea4a3a6edde2cd5efc11190451f9ffce9"
1641
+ dependencies = [
1642
+ "solverforge-core",
1643
+ "thiserror",
1644
+ ]
1645
+
1646
+ [[package]]
1647
+ name = "solverforge-solver"
1648
+ version = "0.10.0"
1649
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1650
+ checksum = "eae27c4be49530466ffa6b961bcc085f5ff4c83750356e65e7a65d109a1bb867"
1651
+ dependencies = [
1652
+ "rand 0.10.1",
1653
+ "rand_chacha 0.10.0",
1654
+ "rayon",
1655
+ "serde",
1656
+ "smallvec",
1657
+ "solverforge-config",
1658
+ "solverforge-core",
1659
+ "solverforge-scoring",
1660
+ "thiserror",
1661
+ "tokio",
1662
+ "tracing",
1663
+ ]
1664
+
1665
+ [[package]]
1666
+ name = "solverforge-ui"
1667
+ version = "0.6.4"
1668
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1669
+ checksum = "7fa4894a0295ef1b538d0c25cb5a8ca0a93a97936421df4ac169154cd3d2a533"
1670
+ dependencies = [
1671
+ "axum",
1672
+ "include_dir",
1673
+ ]
1674
+
1675
+ [[package]]
1676
+ name = "stable_deref_trait"
1677
+ version = "1.2.1"
1678
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1679
+ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
1680
+
1681
+ [[package]]
1682
+ name = "subtle"
1683
+ version = "2.6.1"
1684
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1685
+ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
1686
+
1687
+ [[package]]
1688
+ name = "syn"
1689
+ version = "2.0.117"
1690
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1691
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
1692
+ dependencies = [
1693
+ "proc-macro2",
1694
+ "quote",
1695
+ "unicode-ident",
1696
+ ]
1697
+
1698
+ [[package]]
1699
+ name = "sync_wrapper"
1700
+ version = "1.0.2"
1701
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1702
+ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
1703
+ dependencies = [
1704
+ "futures-core",
1705
+ ]
1706
+
1707
+ [[package]]
1708
+ name = "synstructure"
1709
+ version = "0.13.2"
1710
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1711
+ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
1712
+ dependencies = [
1713
+ "proc-macro2",
1714
+ "quote",
1715
+ "syn",
1716
+ ]
1717
+
1718
+ [[package]]
1719
+ name = "system-configuration"
1720
+ version = "0.7.0"
1721
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1722
+ checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
1723
+ dependencies = [
1724
+ "bitflags",
1725
+ "core-foundation 0.9.4",
1726
+ "system-configuration-sys",
1727
+ ]
1728
+
1729
+ [[package]]
1730
+ name = "system-configuration-sys"
1731
+ version = "0.6.0"
1732
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1733
+ checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
1734
+ dependencies = [
1735
+ "core-foundation-sys",
1736
+ "libc",
1737
+ ]
1738
+
1739
+ [[package]]
1740
+ name = "thiserror"
1741
+ version = "2.0.18"
1742
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1743
+ checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
1744
+ dependencies = [
1745
+ "thiserror-impl",
1746
+ ]
1747
+
1748
+ [[package]]
1749
+ name = "thiserror-impl"
1750
+ version = "2.0.18"
1751
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1752
+ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
1753
+ dependencies = [
1754
+ "proc-macro2",
1755
+ "quote",
1756
+ "syn",
1757
+ ]
1758
+
1759
+ [[package]]
1760
+ name = "thread_local"
1761
+ version = "1.1.9"
1762
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1763
+ checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
1764
+ dependencies = [
1765
+ "cfg-if",
1766
+ ]
1767
+
1768
+ [[package]]
1769
+ name = "tinystr"
1770
+ version = "0.8.3"
1771
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1772
+ checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
1773
+ dependencies = [
1774
+ "displaydoc",
1775
+ "zerovec",
1776
+ ]
1777
+
1778
+ [[package]]
1779
+ name = "tinyvec"
1780
+ version = "1.11.0"
1781
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1782
+ checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
1783
+ dependencies = [
1784
+ "tinyvec_macros",
1785
+ ]
1786
+
1787
+ [[package]]
1788
+ name = "tinyvec_macros"
1789
+ version = "0.1.1"
1790
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1791
+ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
1792
+
1793
+ [[package]]
1794
+ name = "tokio"
1795
+ version = "1.52.1"
1796
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1797
+ checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
1798
+ dependencies = [
1799
+ "bytes",
1800
+ "libc",
1801
+ "mio",
1802
+ "parking_lot",
1803
+ "pin-project-lite",
1804
+ "signal-hook-registry",
1805
+ "socket2",
1806
+ "tokio-macros",
1807
+ "windows-sys 0.61.2",
1808
+ ]
1809
+
1810
+ [[package]]
1811
+ name = "tokio-macros"
1812
+ version = "2.7.0"
1813
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1814
+ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
1815
+ dependencies = [
1816
+ "proc-macro2",
1817
+ "quote",
1818
+ "syn",
1819
+ ]
1820
+
1821
+ [[package]]
1822
+ name = "tokio-rustls"
1823
+ version = "0.26.4"
1824
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1825
+ checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
1826
+ dependencies = [
1827
+ "rustls",
1828
+ "tokio",
1829
+ ]
1830
+
1831
+ [[package]]
1832
+ name = "tokio-stream"
1833
+ version = "0.1.18"
1834
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1835
+ checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
1836
+ dependencies = [
1837
+ "futures-core",
1838
+ "pin-project-lite",
1839
+ "tokio",
1840
+ "tokio-util",
1841
+ ]
1842
+
1843
+ [[package]]
1844
+ name = "tokio-util"
1845
+ version = "0.7.18"
1846
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1847
+ checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
1848
+ dependencies = [
1849
+ "bytes",
1850
+ "futures-core",
1851
+ "futures-sink",
1852
+ "pin-project-lite",
1853
+ "tokio",
1854
+ ]
1855
+
1856
+ [[package]]
1857
+ name = "toml"
1858
+ version = "1.1.2+spec-1.1.0"
1859
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1860
+ checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
1861
+ dependencies = [
1862
+ "indexmap",
1863
+ "serde_core",
1864
+ "serde_spanned",
1865
+ "toml_datetime",
1866
+ "toml_parser",
1867
+ "toml_writer",
1868
+ "winnow",
1869
+ ]
1870
+
1871
+ [[package]]
1872
+ name = "toml_datetime"
1873
+ version = "1.1.1+spec-1.1.0"
1874
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1875
+ checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
1876
+ dependencies = [
1877
+ "serde_core",
1878
+ ]
1879
+
1880
+ [[package]]
1881
+ name = "toml_parser"
1882
+ version = "1.1.2+spec-1.1.0"
1883
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1884
+ checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
1885
+ dependencies = [
1886
+ "winnow",
1887
+ ]
1888
+
1889
+ [[package]]
1890
+ name = "toml_writer"
1891
+ version = "1.1.1+spec-1.1.0"
1892
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1893
+ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
1894
+
1895
+ [[package]]
1896
+ name = "tower"
1897
+ version = "0.5.3"
1898
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1899
+ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
1900
+ dependencies = [
1901
+ "futures-core",
1902
+ "futures-util",
1903
+ "pin-project-lite",
1904
+ "sync_wrapper",
1905
+ "tokio",
1906
+ "tower-layer",
1907
+ "tower-service",
1908
+ "tracing",
1909
+ ]
1910
+
1911
+ [[package]]
1912
+ name = "tower-http"
1913
+ version = "0.6.8"
1914
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1915
+ checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
1916
+ dependencies = [
1917
+ "bitflags",
1918
+ "bytes",
1919
+ "futures-core",
1920
+ "futures-util",
1921
+ "http",
1922
+ "http-body",
1923
+ "http-body-util",
1924
+ "http-range-header",
1925
+ "httpdate",
1926
+ "iri-string",
1927
+ "mime",
1928
+ "mime_guess",
1929
+ "percent-encoding",
1930
+ "pin-project-lite",
1931
+ "tokio",
1932
+ "tokio-util",
1933
+ "tower",
1934
+ "tower-layer",
1935
+ "tower-service",
1936
+ "tracing",
1937
+ ]
1938
+
1939
+ [[package]]
1940
+ name = "tower-layer"
1941
+ version = "0.3.3"
1942
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1943
+ checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
1944
+
1945
+ [[package]]
1946
+ name = "tower-service"
1947
+ version = "0.3.3"
1948
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1949
+ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
1950
+
1951
+ [[package]]
1952
+ name = "tracing"
1953
+ version = "0.1.44"
1954
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1955
+ checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
1956
+ dependencies = [
1957
+ "log",
1958
+ "pin-project-lite",
1959
+ "tracing-attributes",
1960
+ "tracing-core",
1961
+ ]
1962
+
1963
+ [[package]]
1964
+ name = "tracing-attributes"
1965
+ version = "0.1.31"
1966
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1967
+ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
1968
+ dependencies = [
1969
+ "proc-macro2",
1970
+ "quote",
1971
+ "syn",
1972
+ ]
1973
+
1974
+ [[package]]
1975
+ name = "tracing-core"
1976
+ version = "0.1.36"
1977
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1978
+ checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
1979
+ dependencies = [
1980
+ "once_cell",
1981
+ "valuable",
1982
+ ]
1983
+
1984
+ [[package]]
1985
+ name = "tracing-log"
1986
+ version = "0.2.0"
1987
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1988
+ checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
1989
+ dependencies = [
1990
+ "log",
1991
+ "once_cell",
1992
+ "tracing-core",
1993
+ ]
1994
+
1995
+ [[package]]
1996
+ name = "tracing-subscriber"
1997
+ version = "0.3.23"
1998
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1999
+ checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
2000
+ dependencies = [
2001
+ "matchers",
2002
+ "nu-ansi-term",
2003
+ "once_cell",
2004
+ "regex-automata",
2005
+ "sharded-slab",
2006
+ "smallvec",
2007
+ "thread_local",
2008
+ "tracing",
2009
+ "tracing-core",
2010
+ "tracing-log",
2011
+ ]
2012
+
2013
+ [[package]]
2014
+ name = "try-lock"
2015
+ version = "0.2.5"
2016
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2017
+ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
2018
+
2019
+ [[package]]
2020
+ name = "unicase"
2021
+ version = "2.9.0"
2022
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2023
+ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
2024
+
2025
+ [[package]]
2026
+ name = "unicode-ident"
2027
+ version = "1.0.24"
2028
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2029
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
2030
+
2031
+ [[package]]
2032
+ name = "unicode-xid"
2033
+ version = "0.2.6"
2034
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2035
+ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
2036
+
2037
+ [[package]]
2038
+ name = "unsafe-libyaml"
2039
+ version = "0.2.11"
2040
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2041
+ checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
2042
+
2043
+ [[package]]
2044
+ name = "untrusted"
2045
+ version = "0.9.0"
2046
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2047
+ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
2048
+
2049
+ [[package]]
2050
+ name = "url"
2051
+ version = "2.5.8"
2052
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2053
+ checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
2054
+ dependencies = [
2055
+ "form_urlencoded",
2056
+ "idna",
2057
+ "percent-encoding",
2058
+ "serde",
2059
+ ]
2060
+
2061
+ [[package]]
2062
+ name = "utf8_iter"
2063
+ version = "1.0.4"
2064
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2065
+ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
2066
+
2067
+ [[package]]
2068
+ name = "utoipa"
2069
+ version = "5.4.0"
2070
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2071
+ checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993"
2072
+ dependencies = [
2073
+ "indexmap",
2074
+ "serde",
2075
+ "serde_json",
2076
+ "utoipa-gen",
2077
+ ]
2078
+
2079
+ [[package]]
2080
+ name = "utoipa-gen"
2081
+ version = "5.4.0"
2082
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2083
+ checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b"
2084
+ dependencies = [
2085
+ "proc-macro2",
2086
+ "quote",
2087
+ "syn",
2088
+ ]
2089
+
2090
+ [[package]]
2091
+ name = "uuid"
2092
+ version = "1.23.1"
2093
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2094
+ checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
2095
+ dependencies = [
2096
+ "getrandom 0.4.2",
2097
+ "js-sys",
2098
+ "serde_core",
2099
+ "wasm-bindgen",
2100
+ ]
2101
+
2102
+ [[package]]
2103
+ name = "valuable"
2104
+ version = "0.1.1"
2105
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2106
+ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
2107
+
2108
+ [[package]]
2109
+ name = "walkdir"
2110
+ version = "2.5.0"
2111
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2112
+ checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
2113
+ dependencies = [
2114
+ "same-file",
2115
+ "winapi-util",
2116
+ ]
2117
+
2118
+ [[package]]
2119
+ name = "want"
2120
+ version = "0.3.1"
2121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2122
+ checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
2123
+ dependencies = [
2124
+ "try-lock",
2125
+ ]
2126
+
2127
+ [[package]]
2128
+ name = "wasi"
2129
+ version = "0.11.1+wasi-snapshot-preview1"
2130
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2131
+ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
2132
+
2133
+ [[package]]
2134
+ name = "wasip2"
2135
+ version = "1.0.3+wasi-0.2.9"
2136
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2137
+ checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
2138
+ dependencies = [
2139
+ "wit-bindgen 0.57.1",
2140
+ ]
2141
+
2142
+ [[package]]
2143
+ name = "wasip3"
2144
+ version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
2145
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2146
+ checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
2147
+ dependencies = [
2148
+ "wit-bindgen 0.51.0",
2149
+ ]
2150
+
2151
+ [[package]]
2152
+ name = "wasm-bindgen"
2153
+ version = "0.2.120"
2154
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2155
+ checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
2156
+ dependencies = [
2157
+ "cfg-if",
2158
+ "once_cell",
2159
+ "rustversion",
2160
+ "wasm-bindgen-macro",
2161
+ "wasm-bindgen-shared",
2162
+ ]
2163
+
2164
+ [[package]]
2165
+ name = "wasm-bindgen-futures"
2166
+ version = "0.4.70"
2167
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2168
+ checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084"
2169
+ dependencies = [
2170
+ "js-sys",
2171
+ "wasm-bindgen",
2172
+ ]
2173
+
2174
+ [[package]]
2175
+ name = "wasm-bindgen-macro"
2176
+ version = "0.2.120"
2177
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2178
+ checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
2179
+ dependencies = [
2180
+ "quote",
2181
+ "wasm-bindgen-macro-support",
2182
+ ]
2183
+
2184
+ [[package]]
2185
+ name = "wasm-bindgen-macro-support"
2186
+ version = "0.2.120"
2187
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2188
+ checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
2189
+ dependencies = [
2190
+ "bumpalo",
2191
+ "proc-macro2",
2192
+ "quote",
2193
+ "syn",
2194
+ "wasm-bindgen-shared",
2195
+ ]
2196
+
2197
+ [[package]]
2198
+ name = "wasm-bindgen-shared"
2199
+ version = "0.2.120"
2200
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2201
+ checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
2202
+ dependencies = [
2203
+ "unicode-ident",
2204
+ ]
2205
+
2206
+ [[package]]
2207
+ name = "wasm-encoder"
2208
+ version = "0.244.0"
2209
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2210
+ checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
2211
+ dependencies = [
2212
+ "leb128fmt",
2213
+ "wasmparser",
2214
+ ]
2215
+
2216
+ [[package]]
2217
+ name = "wasm-metadata"
2218
+ version = "0.244.0"
2219
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2220
+ checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
2221
+ dependencies = [
2222
+ "anyhow",
2223
+ "indexmap",
2224
+ "wasm-encoder",
2225
+ "wasmparser",
2226
+ ]
2227
+
2228
+ [[package]]
2229
+ name = "wasmparser"
2230
+ version = "0.244.0"
2231
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2232
+ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
2233
+ dependencies = [
2234
+ "bitflags",
2235
+ "hashbrown 0.15.5",
2236
+ "indexmap",
2237
+ "semver",
2238
+ ]
2239
+
2240
+ [[package]]
2241
+ name = "web-sys"
2242
+ version = "0.3.97"
2243
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2244
+ checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602"
2245
+ dependencies = [
2246
+ "js-sys",
2247
+ "wasm-bindgen",
2248
+ ]
2249
+
2250
+ [[package]]
2251
+ name = "web-time"
2252
+ version = "1.1.0"
2253
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2254
+ checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
2255
+ dependencies = [
2256
+ "js-sys",
2257
+ "wasm-bindgen",
2258
+ ]
2259
+
2260
+ [[package]]
2261
+ name = "webpki-root-certs"
2262
+ version = "1.0.7"
2263
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2264
+ checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
2265
+ dependencies = [
2266
+ "rustls-pki-types",
2267
+ ]
2268
+
2269
+ [[package]]
2270
+ name = "winapi-util"
2271
+ version = "0.1.11"
2272
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2273
+ checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
2274
+ dependencies = [
2275
+ "windows-sys 0.61.2",
2276
+ ]
2277
+
2278
+ [[package]]
2279
+ name = "windows-link"
2280
+ version = "0.2.1"
2281
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2282
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
2283
+
2284
+ [[package]]
2285
+ name = "windows-registry"
2286
+ version = "0.6.1"
2287
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2288
+ checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
2289
+ dependencies = [
2290
+ "windows-link",
2291
+ "windows-result",
2292
+ "windows-strings",
2293
+ ]
2294
+
2295
+ [[package]]
2296
+ name = "windows-result"
2297
+ version = "0.4.1"
2298
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2299
+ checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
2300
+ dependencies = [
2301
+ "windows-link",
2302
+ ]
2303
+
2304
+ [[package]]
2305
+ name = "windows-strings"
2306
+ version = "0.5.1"
2307
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2308
+ checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
2309
+ dependencies = [
2310
+ "windows-link",
2311
+ ]
2312
+
2313
+ [[package]]
2314
+ name = "windows-sys"
2315
+ version = "0.52.0"
2316
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2317
+ checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
2318
+ dependencies = [
2319
+ "windows-targets 0.52.6",
2320
+ ]
2321
+
2322
+ [[package]]
2323
+ name = "windows-sys"
2324
+ version = "0.60.2"
2325
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2326
+ checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
2327
+ dependencies = [
2328
+ "windows-targets 0.53.5",
2329
+ ]
2330
+
2331
+ [[package]]
2332
+ name = "windows-sys"
2333
+ version = "0.61.2"
2334
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2335
+ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
2336
+ dependencies = [
2337
+ "windows-link",
2338
+ ]
2339
+
2340
+ [[package]]
2341
+ name = "windows-targets"
2342
+ version = "0.52.6"
2343
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2344
+ checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
2345
+ dependencies = [
2346
+ "windows_aarch64_gnullvm 0.52.6",
2347
+ "windows_aarch64_msvc 0.52.6",
2348
+ "windows_i686_gnu 0.52.6",
2349
+ "windows_i686_gnullvm 0.52.6",
2350
+ "windows_i686_msvc 0.52.6",
2351
+ "windows_x86_64_gnu 0.52.6",
2352
+ "windows_x86_64_gnullvm 0.52.6",
2353
+ "windows_x86_64_msvc 0.52.6",
2354
+ ]
2355
+
2356
+ [[package]]
2357
+ name = "windows-targets"
2358
+ version = "0.53.5"
2359
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2360
+ checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
2361
+ dependencies = [
2362
+ "windows-link",
2363
+ "windows_aarch64_gnullvm 0.53.1",
2364
+ "windows_aarch64_msvc 0.53.1",
2365
+ "windows_i686_gnu 0.53.1",
2366
+ "windows_i686_gnullvm 0.53.1",
2367
+ "windows_i686_msvc 0.53.1",
2368
+ "windows_x86_64_gnu 0.53.1",
2369
+ "windows_x86_64_gnullvm 0.53.1",
2370
+ "windows_x86_64_msvc 0.53.1",
2371
+ ]
2372
+
2373
+ [[package]]
2374
+ name = "windows_aarch64_gnullvm"
2375
+ version = "0.52.6"
2376
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2377
+ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
2378
+
2379
+ [[package]]
2380
+ name = "windows_aarch64_gnullvm"
2381
+ version = "0.53.1"
2382
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2383
+ checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
2384
+
2385
+ [[package]]
2386
+ name = "windows_aarch64_msvc"
2387
+ version = "0.52.6"
2388
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2389
+ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
2390
+
2391
+ [[package]]
2392
+ name = "windows_aarch64_msvc"
2393
+ version = "0.53.1"
2394
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2395
+ checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
2396
+
2397
+ [[package]]
2398
+ name = "windows_i686_gnu"
2399
+ version = "0.52.6"
2400
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2401
+ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
2402
+
2403
+ [[package]]
2404
+ name = "windows_i686_gnu"
2405
+ version = "0.53.1"
2406
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2407
+ checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
2408
+
2409
+ [[package]]
2410
+ name = "windows_i686_gnullvm"
2411
+ version = "0.52.6"
2412
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2413
+ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
2414
+
2415
+ [[package]]
2416
+ name = "windows_i686_gnullvm"
2417
+ version = "0.53.1"
2418
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2419
+ checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
2420
+
2421
+ [[package]]
2422
+ name = "windows_i686_msvc"
2423
+ version = "0.52.6"
2424
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2425
+ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
2426
+
2427
+ [[package]]
2428
+ name = "windows_i686_msvc"
2429
+ version = "0.53.1"
2430
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2431
+ checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
2432
+
2433
+ [[package]]
2434
+ name = "windows_x86_64_gnu"
2435
+ version = "0.52.6"
2436
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2437
+ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
2438
+
2439
+ [[package]]
2440
+ name = "windows_x86_64_gnu"
2441
+ version = "0.53.1"
2442
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2443
+ checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
2444
+
2445
+ [[package]]
2446
+ name = "windows_x86_64_gnullvm"
2447
+ version = "0.52.6"
2448
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2449
+ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
2450
+
2451
+ [[package]]
2452
+ name = "windows_x86_64_gnullvm"
2453
+ version = "0.53.1"
2454
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2455
+ checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
2456
+
2457
+ [[package]]
2458
+ name = "windows_x86_64_msvc"
2459
+ version = "0.52.6"
2460
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2461
+ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
2462
+
2463
+ [[package]]
2464
+ name = "windows_x86_64_msvc"
2465
+ version = "0.53.1"
2466
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2467
+ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
2468
+
2469
+ [[package]]
2470
+ name = "winnow"
2471
+ version = "1.0.2"
2472
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2473
+ checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
2474
+
2475
+ [[package]]
2476
+ name = "wit-bindgen"
2477
+ version = "0.51.0"
2478
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2479
+ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
2480
+ dependencies = [
2481
+ "wit-bindgen-rust-macro",
2482
+ ]
2483
+
2484
+ [[package]]
2485
+ name = "wit-bindgen"
2486
+ version = "0.57.1"
2487
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2488
+ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
2489
+
2490
+ [[package]]
2491
+ name = "wit-bindgen-core"
2492
+ version = "0.51.0"
2493
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2494
+ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
2495
+ dependencies = [
2496
+ "anyhow",
2497
+ "heck",
2498
+ "wit-parser",
2499
+ ]
2500
+
2501
+ [[package]]
2502
+ name = "wit-bindgen-rust"
2503
+ version = "0.51.0"
2504
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2505
+ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
2506
+ dependencies = [
2507
+ "anyhow",
2508
+ "heck",
2509
+ "indexmap",
2510
+ "prettyplease",
2511
+ "syn",
2512
+ "wasm-metadata",
2513
+ "wit-bindgen-core",
2514
+ "wit-component",
2515
+ ]
2516
+
2517
+ [[package]]
2518
+ name = "wit-bindgen-rust-macro"
2519
+ version = "0.51.0"
2520
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2521
+ checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
2522
+ dependencies = [
2523
+ "anyhow",
2524
+ "prettyplease",
2525
+ "proc-macro2",
2526
+ "quote",
2527
+ "syn",
2528
+ "wit-bindgen-core",
2529
+ "wit-bindgen-rust",
2530
+ ]
2531
+
2532
+ [[package]]
2533
+ name = "wit-component"
2534
+ version = "0.244.0"
2535
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2536
+ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
2537
+ dependencies = [
2538
+ "anyhow",
2539
+ "bitflags",
2540
+ "indexmap",
2541
+ "log",
2542
+ "serde",
2543
+ "serde_derive",
2544
+ "serde_json",
2545
+ "wasm-encoder",
2546
+ "wasm-metadata",
2547
+ "wasmparser",
2548
+ "wit-parser",
2549
+ ]
2550
+
2551
+ [[package]]
2552
+ name = "wit-parser"
2553
+ version = "0.244.0"
2554
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2555
+ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
2556
+ dependencies = [
2557
+ "anyhow",
2558
+ "id-arena",
2559
+ "indexmap",
2560
+ "log",
2561
+ "semver",
2562
+ "serde",
2563
+ "serde_derive",
2564
+ "serde_json",
2565
+ "unicode-xid",
2566
+ "wasmparser",
2567
+ ]
2568
+
2569
+ [[package]]
2570
+ name = "writeable"
2571
+ version = "0.6.3"
2572
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2573
+ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
2574
+
2575
+ [[package]]
2576
+ name = "yoke"
2577
+ version = "0.8.2"
2578
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2579
+ checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
2580
+ dependencies = [
2581
+ "stable_deref_trait",
2582
+ "yoke-derive",
2583
+ "zerofrom",
2584
+ ]
2585
+
2586
+ [[package]]
2587
+ name = "yoke-derive"
2588
+ version = "0.8.2"
2589
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2590
+ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
2591
+ dependencies = [
2592
+ "proc-macro2",
2593
+ "quote",
2594
+ "syn",
2595
+ "synstructure",
2596
+ ]
2597
+
2598
+ [[package]]
2599
+ name = "zerocopy"
2600
+ version = "0.8.48"
2601
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2602
+ checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
2603
+ dependencies = [
2604
+ "zerocopy-derive",
2605
+ ]
2606
+
2607
+ [[package]]
2608
+ name = "zerocopy-derive"
2609
+ version = "0.8.48"
2610
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2611
+ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
2612
+ dependencies = [
2613
+ "proc-macro2",
2614
+ "quote",
2615
+ "syn",
2616
+ ]
2617
+
2618
+ [[package]]
2619
+ name = "zerofrom"
2620
+ version = "0.1.7"
2621
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2622
+ checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
2623
+ dependencies = [
2624
+ "zerofrom-derive",
2625
+ ]
2626
+
2627
+ [[package]]
2628
+ name = "zerofrom-derive"
2629
+ version = "0.1.7"
2630
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2631
+ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
2632
+ dependencies = [
2633
+ "proc-macro2",
2634
+ "quote",
2635
+ "syn",
2636
+ "synstructure",
2637
+ ]
2638
+
2639
+ [[package]]
2640
+ name = "zeroize"
2641
+ version = "1.8.2"
2642
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2643
+ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
2644
+
2645
+ [[package]]
2646
+ name = "zerotrie"
2647
+ version = "0.2.4"
2648
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2649
+ checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
2650
+ dependencies = [
2651
+ "displaydoc",
2652
+ "yoke",
2653
+ "zerofrom",
2654
+ ]
2655
+
2656
+ [[package]]
2657
+ name = "zerovec"
2658
+ version = "0.11.6"
2659
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2660
+ checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
2661
+ dependencies = [
2662
+ "yoke",
2663
+ "zerofrom",
2664
+ "zerovec-derive",
2665
+ ]
2666
+
2667
+ [[package]]
2668
+ name = "zerovec-derive"
2669
+ version = "0.11.3"
2670
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2671
+ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
2672
+ dependencies = [
2673
+ "proc-macro2",
2674
+ "quote",
2675
+ "syn",
2676
+ ]
2677
+
2678
+ [[package]]
2679
+ name = "zmij"
2680
+ version = "1.0.21"
2681
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2682
+ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
solver.toml CHANGED
@@ -1,15 +1,17 @@
1
  [[phases]]
2
  type = "construction_heuristic"
3
- construction_heuristic_type = "first_fit"
4
 
5
  [[phases]]
6
  type = "local_search"
 
7
  [phases.acceptor]
8
- type = "late_acceptance"
9
  late_acceptance_size = 400
 
 
10
  [phases.forager]
11
- type = "accepted_count"
12
  limit = 4
 
13
 
14
  [termination]
15
- seconds_spent_limit = 30
 
1
  [[phases]]
2
  type = "construction_heuristic"
3
+ construction_heuristic_type = "list_cheapest_insertion"
4
 
5
  [[phases]]
6
  type = "local_search"
7
+
8
  [phases.acceptor]
 
9
  late_acceptance_size = 400
10
+ type = "late_acceptance"
11
+
12
  [phases.forager]
 
13
  limit = 4
14
+ type = "accepted_count"
15
 
16
  [termination]
17
+ seconds_spent_limit = 45
solverforge.app.toml CHANGED
@@ -10,8 +10,87 @@ ui_source = "crates.io: solverforge-ui 0.6.4"
10
 
11
  [demo]
12
  default_size = "standard"
13
- available_sizes = ["small", "standard", "large"]
 
 
 
 
14
 
15
  [solution]
16
- name = "Plan"
17
  score = "HardSoftScore"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  [demo]
12
  default_size = "standard"
13
+ available_sizes = [
14
+ "small",
15
+ "standard",
16
+ "large",
17
+ ]
18
 
19
  [solution]
20
+ name = "FieldServicePlan"
21
  score = "HardSoftScore"
22
+
23
+ [[facts]]
24
+ name = "location"
25
+ plural = "locations"
26
+ kind = "problem_fact"
27
+
28
+ [[facts]]
29
+ name = "service_visit"
30
+ plural = "service_visits"
31
+ kind = "problem_fact"
32
+
33
+ [[facts]]
34
+ name = "travel_leg"
35
+ plural = "travel_legs"
36
+ kind = "problem_fact"
37
+
38
+ [[entities]]
39
+ name = "technician_route"
40
+ plural = "technician_routes"
41
+ kind = "planning_entity"
42
+
43
+ [[variables]]
44
+ entity = "technician_route"
45
+ entity_plural = "technician_routes"
46
+ field = "visits"
47
+ kind = "list"
48
+ range = ""
49
+ elements = "service_visits"
50
+ allows_unassigned = false
51
+ enabled = true
52
+
53
+ [[constraints]]
54
+ name = "balance_workload"
55
+ module = "balance_workload"
56
+ enabled = true
57
+
58
+ [[constraints]]
59
+ name = "minimize_travel"
60
+ module = "minimize_travel"
61
+ enabled = true
62
+
63
+ [[constraints]]
64
+ name = "priority_slack"
65
+ module = "priority_slack"
66
+ enabled = true
67
+
68
+ [[constraints]]
69
+ name = "reachable_legs"
70
+ module = "reachable_legs"
71
+ enabled = true
72
+
73
+ [[constraints]]
74
+ name = "required_parts"
75
+ module = "required_parts"
76
+ enabled = true
77
+
78
+ [[constraints]]
79
+ name = "required_skills"
80
+ module = "required_skills"
81
+ enabled = true
82
+
83
+ [[constraints]]
84
+ name = "shift_capacity"
85
+ module = "shift_capacity"
86
+ enabled = true
87
+
88
+ [[constraints]]
89
+ name = "territory_affinity"
90
+ module = "territory_affinity"
91
+ enabled = true
92
+
93
+ [[constraints]]
94
+ name = "time_windows"
95
+ module = "time_windows"
96
+ enabled = true
src/api/dto.rs CHANGED
@@ -6,7 +6,7 @@ use solverforge::{
6
  };
7
  use std::time::Duration;
8
 
9
- use crate::domain::Plan;
10
 
11
  #[derive(Debug, Clone, Serialize, Deserialize)]
12
  #[serde(rename_all = "camelCase")]
@@ -90,7 +90,7 @@ pub struct JobAnalysisDto {
90
  }
91
 
92
  impl PlanDto {
93
- pub fn from_plan(plan: &Plan) -> Self {
94
  let mut fields = match serde_json::to_value(plan).expect("failed to serialize plan") {
95
  Value::Object(map) => map,
96
  _ => Map::new(),
@@ -108,7 +108,7 @@ impl PlanDto {
108
  Self { fields, score }
109
  }
110
 
111
- pub fn to_domain(&self) -> Result<Plan, serde_json::Error> {
112
  let mut fields = self.fields.clone();
113
  let _ = &self.score;
114
  fields.insert("score".to_string(), Value::Null);
@@ -154,7 +154,7 @@ impl JobSummaryDto {
154
  }
155
 
156
  impl JobSnapshotDto {
157
- pub fn from_snapshot(snapshot: &SolverSnapshot<Plan>) -> Self {
158
  Self {
159
  id: snapshot.job_id.to_string(),
160
  job_id: snapshot.job_id.to_string(),
 
6
  };
7
  use std::time::Duration;
8
 
9
+ use crate::domain::FieldServicePlan;
10
 
11
  #[derive(Debug, Clone, Serialize, Deserialize)]
12
  #[serde(rename_all = "camelCase")]
 
90
  }
91
 
92
  impl PlanDto {
93
+ pub fn from_plan(plan: &FieldServicePlan) -> Self {
94
  let mut fields = match serde_json::to_value(plan).expect("failed to serialize plan") {
95
  Value::Object(map) => map,
96
  _ => Map::new(),
 
108
  Self { fields, score }
109
  }
110
 
111
+ pub fn to_domain(&self) -> Result<FieldServicePlan, serde_json::Error> {
112
  let mut fields = self.fields.clone();
113
  let _ = &self.score;
114
  fields.insert("score".to_string(), Value::Null);
 
154
  }
155
 
156
  impl JobSnapshotDto {
157
+ pub fn from_snapshot(snapshot: &SolverSnapshot<FieldServicePlan>) -> Self {
158
  Self {
159
  id: snapshot.job_id.to_string(),
160
  job_id: snapshot.job_id.to_string(),
src/api/routes.rs CHANGED
@@ -9,7 +9,7 @@ use std::sync::Arc;
9
 
10
  use super::dto::{analysis_response, JobAnalysisDto, JobSnapshotDto, JobSummaryDto, PlanDto};
11
  use super::sse;
12
- use crate::data::{generate, DemoData};
13
  use crate::solver::SolverService;
14
 
15
  /// Shared application state.
@@ -94,7 +94,7 @@ async fn list_demo_data() -> Json<DemoDataCatalogResponse> {
94
 
95
  async fn get_demo_data(Path(id): Path<String>) -> Result<Json<PlanDto>, StatusCode> {
96
  let demo = id.parse::<DemoData>().map_err(|_| StatusCode::NOT_FOUND)?;
97
- let plan = generate(demo);
98
  Ok(Json(PlanDto::from_plan(&plan)))
99
  }
100
 
@@ -213,3 +213,8 @@ fn status_from_solver_error(error: solverforge::SolverManagerError) -> StatusCod
213
  solverforge::SolverManagerError::SnapshotNotFound { .. } => StatusCode::NOT_FOUND,
214
  }
215
  }
 
 
 
 
 
 
9
 
10
  use super::dto::{analysis_response, JobAnalysisDto, JobSnapshotDto, JobSummaryDto, PlanDto};
11
  use super::sse;
12
+ use crate::data::{generate, DemoData, DemoDataError};
13
  use crate::solver::SolverService;
14
 
15
  /// Shared application state.
 
94
 
95
  async fn get_demo_data(Path(id): Path<String>) -> Result<Json<PlanDto>, StatusCode> {
96
  let demo = id.parse::<DemoData>().map_err(|_| StatusCode::NOT_FOUND)?;
97
+ let plan = generate(demo).await.map_err(status_from_demo_data_error)?;
98
  Ok(Json(PlanDto::from_plan(&plan)))
99
  }
100
 
 
213
  solverforge::SolverManagerError::SnapshotNotFound { .. } => StatusCode::NOT_FOUND,
214
  }
215
  }
216
+
217
+ fn status_from_demo_data_error(error: DemoDataError) -> StatusCode {
218
+ eprintln!("{error}");
219
+ StatusCode::SERVICE_UNAVAILABLE
220
+ }
src/constraints/balance_workload.rs ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::constraints::route_metrics::{balance_workload_score, RouteConstraint};
2
+ use crate::domain::FieldServicePlan;
3
+ use solverforge::prelude::*;
4
+ use solverforge::IncrementalConstraint;
5
+
6
+ /// SOFT: discourage concentrating all service and travel minutes on one route.
7
+ pub fn constraint() -> impl IncrementalConstraint<FieldServicePlan, HardSoftScore> {
8
+ RouteConstraint::new(
9
+ "Balance Workload",
10
+ false,
11
+ HardSoftScore::of(0, 1),
12
+ balance_workload_score,
13
+ )
14
+ }
src/constraints/minimize_travel.rs ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::constraints::route_metrics::{minimize_travel_score, RouteConstraint};
2
+ use crate::domain::FieldServicePlan;
3
+ use solverforge::prelude::*;
4
+ use solverforge::IncrementalConstraint;
5
+
6
+ /// SOFT: minimize road travel time and distance across technician routes.
7
+ pub fn constraint() -> impl IncrementalConstraint<FieldServicePlan, HardSoftScore> {
8
+ RouteConstraint::new(
9
+ "Minimize Travel",
10
+ false,
11
+ HardSoftScore::of(0, 1),
12
+ minimize_travel_score,
13
+ )
14
+ }
src/constraints/mod.rs CHANGED
@@ -3,20 +3,41 @@
3
  Add constraint modules with `solverforge generate constraint ...`.
4
  The neutral shell starts with an empty constraint set. */
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
  // @solverforge:end constraint-modules
13
 
14
  mod assemble {
15
  use super::*;
16
 
17
- pub fn create_constraints() -> impl ConstraintSet<Plan, HardSoftScore> {
18
  // @solverforge:begin constraint-calls
19
- ()
 
 
 
 
 
 
 
 
 
 
20
  // @solverforge:end constraint-calls
21
  }
22
  }
 
3
  Add constraint modules with `solverforge generate constraint ...`.
4
  The neutral shell starts with an empty constraint set. */
5
 
6
+ use crate::domain::FieldServicePlan;
7
  use solverforge::prelude::*;
8
 
9
  pub use self::assemble::create_constraints;
10
 
11
+ pub mod route_metrics;
12
+
13
  // @solverforge:begin constraint-modules
14
+ mod balance_workload;
15
+ mod minimize_travel;
16
+ mod priority_slack;
17
+ mod reachable_legs;
18
+ mod required_parts;
19
+ mod required_skills;
20
+ mod shift_capacity;
21
+ mod territory_affinity;
22
+ mod time_windows;
23
  // @solverforge:end constraint-modules
24
 
25
  mod assemble {
26
  use super::*;
27
 
28
+ pub fn create_constraints() -> impl ConstraintSet<FieldServicePlan, HardSoftScore> {
29
  // @solverforge:begin constraint-calls
30
+ (
31
+ balance_workload::constraint(),
32
+ minimize_travel::constraint(),
33
+ priority_slack::constraint(),
34
+ reachable_legs::constraint(),
35
+ required_parts::constraint(),
36
+ required_skills::constraint(),
37
+ shift_capacity::constraint(),
38
+ territory_affinity::constraint(),
39
+ time_windows::constraint(),
40
+ )
41
  // @solverforge:end constraint-calls
42
  }
43
  }
src/constraints/priority_slack.rs ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::constraints::route_metrics::{priority_slack_score, RouteConstraint};
2
+ use crate::domain::FieldServicePlan;
3
+ use solverforge::prelude::*;
4
+ use solverforge::IncrementalConstraint;
5
+
6
+ /// SOFT: reward serving high-priority visits with slack before their deadline.
7
+ pub fn constraint() -> impl IncrementalConstraint<FieldServicePlan, HardSoftScore> {
8
+ RouteConstraint::new(
9
+ "Priority Slack",
10
+ false,
11
+ HardSoftScore::of(0, 1),
12
+ priority_slack_score,
13
+ )
14
+ }
src/constraints/reachable_legs.rs ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::constraints::route_metrics::{reachable_score, RouteConstraint};
2
+ use crate::domain::FieldServicePlan;
3
+ use solverforge::prelude::*;
4
+ use solverforge::IncrementalConstraint;
5
+
6
+ /// HARD: every depot-to-visit, visit-to-visit, and visit-to-depot leg must be routable.
7
+ pub fn constraint() -> impl IncrementalConstraint<FieldServicePlan, HardSoftScore> {
8
+ RouteConstraint::new(
9
+ "Reachable Legs",
10
+ true,
11
+ HardSoftScore::of(1, 0),
12
+ reachable_score,
13
+ )
14
+ }
src/constraints/required_parts.rs ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::constraints::route_metrics::{required_parts_score, RouteConstraint};
2
+ use crate::domain::FieldServicePlan;
3
+ use solverforge::prelude::*;
4
+ use solverforge::IncrementalConstraint;
5
+
6
+ /// HARD: route inventory must cover every assigned visit's required parts.
7
+ pub fn constraint() -> impl IncrementalConstraint<FieldServicePlan, HardSoftScore> {
8
+ RouteConstraint::new(
9
+ "Required Parts",
10
+ true,
11
+ HardSoftScore::of(1, 0),
12
+ required_parts_score,
13
+ )
14
+ }
src/constraints/required_skills.rs ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::constraints::route_metrics::{required_skills_score, RouteConstraint};
2
+ use crate::domain::FieldServicePlan;
3
+ use solverforge::prelude::*;
4
+ use solverforge::IncrementalConstraint;
5
+
6
+ /// HARD: a technician route may only contain visits whose skill mask is covered.
7
+ pub fn constraint() -> impl IncrementalConstraint<FieldServicePlan, HardSoftScore> {
8
+ RouteConstraint::new(
9
+ "Required Skills",
10
+ true,
11
+ HardSoftScore::of(1, 0),
12
+ required_skills_score,
13
+ )
14
+ }
src/constraints/route_metrics.rs ADDED
@@ -0,0 +1,435 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::domain::{FieldServicePlan, ServiceVisit, TechnicianRoute, TravelLeg};
2
+ use solverforge::prelude::*;
3
+ use solverforge::IncrementalConstraint;
4
+
5
+ pub struct RouteConstraint {
6
+ name: &'static str,
7
+ hard: bool,
8
+ weight: HardSoftScore,
9
+ scorer: fn(&FieldServicePlan, &TechnicianRoute) -> HardSoftScore,
10
+ }
11
+
12
+ impl RouteConstraint {
13
+ pub const fn new(
14
+ name: &'static str,
15
+ hard: bool,
16
+ weight: HardSoftScore,
17
+ scorer: fn(&FieldServicePlan, &TechnicianRoute) -> HardSoftScore,
18
+ ) -> Self {
19
+ Self {
20
+ name,
21
+ hard,
22
+ weight,
23
+ scorer,
24
+ }
25
+ }
26
+
27
+ fn route_score(&self, solution: &FieldServicePlan, entity_index: usize) -> HardSoftScore {
28
+ solution
29
+ .technician_routes
30
+ .get(entity_index)
31
+ .map(|route| (self.scorer)(solution, route))
32
+ .unwrap_or(HardSoftScore::ZERO)
33
+ }
34
+ }
35
+
36
+ impl IncrementalConstraint<FieldServicePlan, HardSoftScore> for RouteConstraint {
37
+ fn evaluate(&self, solution: &FieldServicePlan) -> HardSoftScore {
38
+ solution
39
+ .technician_routes
40
+ .iter()
41
+ .map(|route| (self.scorer)(solution, route))
42
+ .fold(HardSoftScore::ZERO, |total, score| total + score)
43
+ }
44
+
45
+ fn match_count(&self, solution: &FieldServicePlan) -> usize {
46
+ solution
47
+ .technician_routes
48
+ .iter()
49
+ .filter(|route| (self.scorer)(solution, route) != HardSoftScore::ZERO)
50
+ .count()
51
+ }
52
+
53
+ fn initialize(&mut self, solution: &FieldServicePlan) -> HardSoftScore {
54
+ self.evaluate(solution)
55
+ }
56
+
57
+ fn on_insert(
58
+ &mut self,
59
+ solution: &FieldServicePlan,
60
+ entity_index: usize,
61
+ _descriptor_index: usize,
62
+ ) -> HardSoftScore {
63
+ self.route_score(solution, entity_index)
64
+ }
65
+
66
+ fn on_retract(
67
+ &mut self,
68
+ solution: &FieldServicePlan,
69
+ entity_index: usize,
70
+ _descriptor_index: usize,
71
+ ) -> HardSoftScore {
72
+ -self.route_score(solution, entity_index)
73
+ }
74
+
75
+ fn reset(&mut self) {}
76
+
77
+ fn name(&self) -> &str {
78
+ self.name
79
+ }
80
+
81
+ fn is_hard(&self) -> bool {
82
+ self.hard
83
+ }
84
+
85
+ fn weight(&self) -> HardSoftScore {
86
+ self.weight
87
+ }
88
+ }
89
+
90
+ #[derive(Debug, Clone, Default, PartialEq, Eq)]
91
+ pub struct RouteStats {
92
+ pub invalid_visits: i64,
93
+ pub unreachable_legs: i64,
94
+ pub missing_skill_visits: i64,
95
+ pub missing_part_visits: i64,
96
+ pub late_minutes: i64,
97
+ pub overtime_minutes: i64,
98
+ pub travel_seconds: i64,
99
+ pub distance_meters: i64,
100
+ pub service_minutes: i64,
101
+ pub waiting_minutes: i64,
102
+ pub route_minutes: i64,
103
+ pub finish_minute: i32,
104
+ pub territory_matches: i64,
105
+ pub priority_slack: i64,
106
+ }
107
+
108
+ impl RouteStats {
109
+ pub fn travel_minutes(&self) -> i64 {
110
+ div_ceil(self.travel_seconds, 60)
111
+ }
112
+ }
113
+
114
+ #[derive(Debug, Clone, Copy)]
115
+ struct VisitTiming {
116
+ visit_idx: usize,
117
+ service_start: i32,
118
+ }
119
+
120
+ pub fn route_stats(plan: &FieldServicePlan, route: &TechnicianRoute) -> RouteStats {
121
+ let mut stats = RouteStats {
122
+ finish_minute: route.shift_start_minute,
123
+ ..RouteStats::default()
124
+ };
125
+ let mut clock = route.shift_start_minute;
126
+ let mut previous_location = route.start_location_idx;
127
+ let mut timings = Vec::with_capacity(route.visits.len());
128
+
129
+ for &visit_idx in &route.visits {
130
+ let Some(visit) = plan.service_visits.get(visit_idx) else {
131
+ stats.invalid_visits += 1;
132
+ continue;
133
+ };
134
+
135
+ apply_leg(
136
+ plan,
137
+ previous_location,
138
+ visit.location_idx,
139
+ &mut clock,
140
+ &mut stats,
141
+ );
142
+
143
+ if clock < visit.earliest_minute {
144
+ stats.waiting_minutes += i64::from(visit.earliest_minute - clock);
145
+ clock = visit.earliest_minute;
146
+ }
147
+ if clock > visit.latest_minute {
148
+ stats.late_minutes += i64::from(clock - visit.latest_minute);
149
+ }
150
+
151
+ if !mask_contains(route.skill_mask, visit.required_skill_mask) {
152
+ stats.missing_skill_visits += 1;
153
+ }
154
+ if !mask_contains(route.inventory_mask, visit.required_parts_mask) {
155
+ stats.missing_part_visits += 1;
156
+ }
157
+ if route.territory == visit.territory {
158
+ stats.territory_matches += 1;
159
+ }
160
+
161
+ timings.push(VisitTiming {
162
+ visit_idx,
163
+ service_start: clock,
164
+ });
165
+
166
+ let service_minutes = visit.duration_minutes.max(0);
167
+ stats.service_minutes += i64::from(service_minutes);
168
+ clock = clock.saturating_add(service_minutes);
169
+ previous_location = visit.location_idx;
170
+ }
171
+
172
+ apply_leg(
173
+ plan,
174
+ previous_location,
175
+ route.end_location_idx,
176
+ &mut clock,
177
+ &mut stats,
178
+ );
179
+
180
+ stats.finish_minute = clock;
181
+ stats.route_minutes = i64::from(clock.saturating_sub(route.shift_start_minute));
182
+ stats.overtime_minutes = i64::from((clock - route.shift_end_minute).max(0))
183
+ + (stats.route_minutes - i64::from(route.max_route_minutes)).max(0);
184
+ stats.priority_slack = priority_slack(plan, &timings);
185
+ stats
186
+ }
187
+
188
+ pub fn reachable_score(plan: &FieldServicePlan, route: &TechnicianRoute) -> HardSoftScore {
189
+ let stats = route_stats(plan, route);
190
+ HardSoftScore::of(-(stats.invalid_visits + stats.unreachable_legs), 0)
191
+ }
192
+
193
+ pub fn required_skills_score(plan: &FieldServicePlan, route: &TechnicianRoute) -> HardSoftScore {
194
+ HardSoftScore::of(-route_stats(plan, route).missing_skill_visits, 0)
195
+ }
196
+
197
+ pub fn required_parts_score(plan: &FieldServicePlan, route: &TechnicianRoute) -> HardSoftScore {
198
+ HardSoftScore::of(-route_stats(plan, route).missing_part_visits, 0)
199
+ }
200
+
201
+ pub fn time_windows_score(plan: &FieldServicePlan, route: &TechnicianRoute) -> HardSoftScore {
202
+ HardSoftScore::of(-route_stats(plan, route).late_minutes, 0)
203
+ }
204
+
205
+ pub fn shift_capacity_score(plan: &FieldServicePlan, route: &TechnicianRoute) -> HardSoftScore {
206
+ HardSoftScore::of(-route_stats(plan, route).overtime_minutes, 0)
207
+ }
208
+
209
+ pub fn minimize_travel_score(plan: &FieldServicePlan, route: &TechnicianRoute) -> HardSoftScore {
210
+ let stats = route_stats(plan, route);
211
+ let travel_minutes = stats.travel_minutes();
212
+ let travel_km = div_ceil(stats.distance_meters, 1_000);
213
+ HardSoftScore::of(0, -(travel_minutes + travel_km))
214
+ }
215
+
216
+ pub fn balance_workload_score(plan: &FieldServicePlan, route: &TechnicianRoute) -> HardSoftScore {
217
+ let normalized = (route_stats(plan, route).route_minutes / 15).max(0);
218
+ HardSoftScore::of(0, -(normalized * normalized))
219
+ }
220
+
221
+ pub fn territory_affinity_score(plan: &FieldServicePlan, route: &TechnicianRoute) -> HardSoftScore {
222
+ HardSoftScore::of(0, route_stats(plan, route).territory_matches * 25)
223
+ }
224
+
225
+ pub fn priority_slack_score(plan: &FieldServicePlan, route: &TechnicianRoute) -> HardSoftScore {
226
+ HardSoftScore::of(0, route_stats(plan, route).priority_slack)
227
+ }
228
+
229
+ pub fn leg_for(
230
+ plan: &FieldServicePlan,
231
+ from_location_idx: usize,
232
+ to_location_idx: usize,
233
+ ) -> Option<&TravelLeg> {
234
+ let width = plan.locations.len();
235
+ let direct_idx = from_location_idx
236
+ .checked_mul(width)?
237
+ .checked_add(to_location_idx)?;
238
+
239
+ if let Some(leg) = plan.travel_legs.get(direct_idx) {
240
+ if leg.from_location_idx == from_location_idx && leg.to_location_idx == to_location_idx {
241
+ return Some(leg);
242
+ }
243
+ }
244
+
245
+ plan.travel_legs.iter().find(|leg| {
246
+ leg.from_location_idx == from_location_idx && leg.to_location_idx == to_location_idx
247
+ })
248
+ }
249
+
250
+ fn apply_leg(
251
+ plan: &FieldServicePlan,
252
+ from_location_idx: usize,
253
+ to_location_idx: usize,
254
+ clock: &mut i32,
255
+ stats: &mut RouteStats,
256
+ ) {
257
+ let Some(leg) = leg_for(plan, from_location_idx, to_location_idx) else {
258
+ stats.unreachable_legs += 1;
259
+ return;
260
+ };
261
+
262
+ if !leg.reachable {
263
+ stats.unreachable_legs += 1;
264
+ return;
265
+ }
266
+
267
+ stats.travel_seconds += leg.duration_seconds.max(0);
268
+ stats.distance_meters += leg.distance_meters.max(0);
269
+ *clock = clock.saturating_add(div_ceil(leg.duration_seconds.max(0), 60) as i32);
270
+ }
271
+
272
+ fn priority_slack(plan: &FieldServicePlan, timings: &[VisitTiming]) -> i64 {
273
+ timings
274
+ .iter()
275
+ .filter_map(|timing| {
276
+ plan.service_visits
277
+ .get(timing.visit_idx)
278
+ .map(|visit| visit_priority_slack(visit, timing.service_start))
279
+ })
280
+ .sum()
281
+ }
282
+
283
+ fn visit_priority_slack(visit: &ServiceVisit, service_start: i32) -> i64 {
284
+ let slack_quarters = i64::from((visit.latest_minute - service_start).max(0) / 15);
285
+ i64::from(visit.priority.max(1)) * (slack_quarters + 1)
286
+ }
287
+
288
+ fn mask_contains(available: i64, required: i64) -> bool {
289
+ (available & required) == required
290
+ }
291
+
292
+ fn div_ceil(value: i64, divisor: i64) -> i64 {
293
+ if value <= 0 {
294
+ 0
295
+ } else {
296
+ (value + divisor - 1) / divisor
297
+ }
298
+ }
299
+
300
+ #[cfg(test)]
301
+ mod tests {
302
+ use super::*;
303
+ use crate::domain::{
304
+ FieldServicePlan, Location, ServiceVisit, ServiceVisitInit, TechnicianRoute,
305
+ TechnicianRouteInit, TravelLeg, TravelLegInit,
306
+ };
307
+ use solverforge::ConstraintSet;
308
+
309
+ #[test]
310
+ fn route_stats_accounts_for_travel_service_and_lateness() {
311
+ let plan = sample_plan(vec![0, 1]);
312
+ let stats = route_stats(&plan, &plan.technician_routes[0]);
313
+
314
+ assert_eq!(stats.travel_seconds, 1_800);
315
+ assert_eq!(stats.service_minutes, 75);
316
+ assert_eq!(stats.late_minutes, 0);
317
+ assert_eq!(stats.route_minutes, 125);
318
+ assert_eq!(stats.overtime_minutes, 55);
319
+ assert_eq!(stats.missing_skill_visits, 0);
320
+ assert_eq!(stats.missing_part_visits, 1);
321
+ }
322
+
323
+ #[test]
324
+ fn travel_leg_lookup_prefers_row_major_contract() {
325
+ let plan = sample_plan(vec![0]);
326
+ let leg = leg_for(&plan, 0, 1).expect("leg should exist");
327
+
328
+ assert_eq!(leg.id, "leg-0-1");
329
+ assert!(leg.reachable);
330
+ }
331
+
332
+ #[test]
333
+ fn full_constraint_set_reports_expected_hard_penalties() {
334
+ let constraints = crate::constraints::create_constraints();
335
+ let score = constraints.evaluate_all(&sample_plan(vec![0, 1]));
336
+
337
+ assert_eq!(score.hard(), -56);
338
+ assert!(score.soft() < 0);
339
+ }
340
+
341
+ fn sample_plan(visits: Vec<usize>) -> FieldServicePlan {
342
+ let locations = vec![
343
+ Location::new(
344
+ "loc-0",
345
+ "Hub",
346
+ "Hub".to_string(),
347
+ 45_700_000,
348
+ 9_670_000,
349
+ "depot".to_string(),
350
+ ),
351
+ Location::new(
352
+ "loc-1",
353
+ "Customer 1",
354
+ "Customer 1".to_string(),
355
+ 45_710_000,
356
+ 9_680_000,
357
+ "customer".to_string(),
358
+ ),
359
+ Location::new(
360
+ "loc-2",
361
+ "Customer 2",
362
+ "Customer 2".to_string(),
363
+ 45_720_000,
364
+ 9_690_000,
365
+ "customer".to_string(),
366
+ ),
367
+ ];
368
+ let service_visits = vec![
369
+ ServiceVisit::new(ServiceVisitInit {
370
+ id: "visit-0".to_string(),
371
+ name: "Boiler".to_string(),
372
+ customer: "Customer 1".to_string(),
373
+ location_idx: 1,
374
+ duration_minutes: 30,
375
+ earliest_minute: 510,
376
+ latest_minute: 540,
377
+ required_skill_mask: 0b001,
378
+ required_parts_mask: 0b010,
379
+ priority: 3,
380
+ territory: "center".to_string(),
381
+ }),
382
+ ServiceVisit::new(ServiceVisitInit {
383
+ id: "visit-1".to_string(),
384
+ name: "Lift".to_string(),
385
+ customer: "Customer 2".to_string(),
386
+ location_idx: 2,
387
+ duration_minutes: 45,
388
+ earliest_minute: 540,
389
+ latest_minute: 570,
390
+ required_skill_mask: 0b001,
391
+ required_parts_mask: 0b100,
392
+ priority: 2,
393
+ territory: "center".to_string(),
394
+ }),
395
+ ];
396
+ let travel_legs = row_major_legs(3);
397
+ let mut route = TechnicianRoute::new(TechnicianRouteInit {
398
+ id: "route-0".to_string(),
399
+ technician_id: "tech-0".to_string(),
400
+ technician_name: "Ada".to_string(),
401
+ color: "#2563eb".to_string(),
402
+ start_location_idx: 0,
403
+ end_location_idx: 0,
404
+ shift_start_minute: 480,
405
+ shift_end_minute: 585,
406
+ max_route_minutes: 90,
407
+ skill_mask: 0b001,
408
+ inventory_mask: 0b010,
409
+ territory: "center".to_string(),
410
+ });
411
+ route.visits = visits;
412
+
413
+ FieldServicePlan::new(locations, service_visits, travel_legs, vec![route])
414
+ }
415
+
416
+ fn row_major_legs(width: usize) -> Vec<TravelLeg> {
417
+ (0..width)
418
+ .flat_map(|from| {
419
+ (0..width).map(move |to| {
420
+ let same = from == to;
421
+ TravelLeg::new(TravelLegInit {
422
+ id: format!("leg-{from}-{to}"),
423
+ name: format!("leg-{from}-{to}"),
424
+ from_location_idx: from,
425
+ to_location_idx: to,
426
+ duration_seconds: if same { 0 } else { 600 },
427
+ distance_meters: if same { 0 } else { 2_000 },
428
+ encoded_polyline: String::new(),
429
+ reachable: true,
430
+ })
431
+ })
432
+ })
433
+ .collect()
434
+ }
435
+ }
src/constraints/shift_capacity.rs ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::constraints::route_metrics::{shift_capacity_score, RouteConstraint};
2
+ use crate::domain::FieldServicePlan;
3
+ use solverforge::prelude::*;
4
+ use solverforge::IncrementalConstraint;
5
+
6
+ /// HARD: the complete route must fit inside the technician shift and route cap.
7
+ pub fn constraint() -> impl IncrementalConstraint<FieldServicePlan, HardSoftScore> {
8
+ RouteConstraint::new(
9
+ "Shift Capacity",
10
+ true,
11
+ HardSoftScore::of(1, 0),
12
+ shift_capacity_score,
13
+ )
14
+ }
src/constraints/territory_affinity.rs ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::constraints::route_metrics::{territory_affinity_score, RouteConstraint};
2
+ use crate::domain::FieldServicePlan;
3
+ use solverforge::prelude::*;
4
+ use solverforge::IncrementalConstraint;
5
+
6
+ /// SOFT: prefer visits inside the technician's familiar territory.
7
+ pub fn constraint() -> impl IncrementalConstraint<FieldServicePlan, HardSoftScore> {
8
+ RouteConstraint::new(
9
+ "Territory Affinity",
10
+ false,
11
+ HardSoftScore::of(0, 1),
12
+ territory_affinity_score,
13
+ )
14
+ }
src/constraints/time_windows.rs ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::constraints::route_metrics::{time_windows_score, RouteConstraint};
2
+ use crate::domain::FieldServicePlan;
3
+ use solverforge::prelude::*;
4
+ use solverforge::IncrementalConstraint;
5
+
6
+ /// HARD: each visit must start no later than its latest service minute.
7
+ pub fn constraint() -> impl IncrementalConstraint<FieldServicePlan, HardSoftScore> {
8
+ RouteConstraint::new(
9
+ "Time Windows",
10
+ true,
11
+ HardSoftScore::of(1, 0),
12
+ time_windows_score,
13
+ )
14
+ }
src/data/data_seed.rs CHANGED
@@ -1,8 +1,24 @@
1
- // @generated by solverforge-cli: data v1
2
-
3
  use std::str::FromStr;
 
 
 
 
 
 
 
 
 
 
 
4
 
5
- use crate::domain::Plan;
 
 
 
 
 
6
 
7
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
8
  pub enum DemoData {
@@ -11,6 +27,27 @@ pub enum DemoData {
11
  Large,
12
  }
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  const AVAILABLE_DEMO_DATA: &[DemoData] = &[DemoData::Small, DemoData::Standard, DemoData::Large];
15
  const DEFAULT_DEMO_DATA: DemoData = DemoData::Standard;
16
 
@@ -38,6 +75,22 @@ impl DemoData {
38
  pub fn available_demo_data() -> &'static [Self] {
39
  available_demo_data()
40
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  }
42
 
43
  impl FromStr for DemoData {
@@ -53,14 +106,600 @@ impl FromStr for DemoData {
53
  }
54
  }
55
 
56
- pub fn generate(demo: DemoData) -> Plan {
57
- match demo {
58
- DemoData::Small => generate_plan(),
59
- DemoData::Standard => generate_plan(),
60
- DemoData::Large => generate_plan(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
62
  }
63
 
64
- fn generate_plan() -> Plan {
65
- Plan::new()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::fmt;
2
+ use std::path::PathBuf;
3
  use std::str::FromStr;
4
+ use std::time::Duration;
5
+
6
+ use solverforge_maps::{
7
+ encode_polyline, BoundingBox, Coord, NetworkConfig, RoadNetwork, RoutingError, UNREACHABLE,
8
+ };
9
+
10
+ use crate::constraints::route_metrics::{route_stats, RouteStats};
11
+ use crate::domain::{
12
+ FieldServicePlan, Location, ServiceVisit, ServiceVisitInit, TechnicianRoute,
13
+ TechnicianRouteInit, TravelLeg, TravelLegInit,
14
+ };
15
 
16
+ const BERGAMO_BBOX: BoundingBox = BoundingBox {
17
+ min_lat: 45.64,
18
+ min_lng: 9.58,
19
+ max_lat: 45.75,
20
+ max_lng: 9.78,
21
+ };
22
 
23
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
24
  pub enum DemoData {
 
27
  Large,
28
  }
29
 
30
+ #[derive(Debug)]
31
+ pub enum DemoDataError {
32
+ Routing(RoutingError),
33
+ }
34
+
35
+ impl fmt::Display for DemoDataError {
36
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37
+ match self {
38
+ Self::Routing(error) => write!(f, "Bergamo OSM routing data is unavailable: {error}"),
39
+ }
40
+ }
41
+ }
42
+
43
+ impl std::error::Error for DemoDataError {}
44
+
45
+ impl From<RoutingError> for DemoDataError {
46
+ fn from(error: RoutingError) -> Self {
47
+ Self::Routing(error)
48
+ }
49
+ }
50
+
51
  const AVAILABLE_DEMO_DATA: &[DemoData] = &[DemoData::Small, DemoData::Standard, DemoData::Large];
52
  const DEFAULT_DEMO_DATA: DemoData = DemoData::Standard;
53
 
 
75
  pub fn available_demo_data() -> &'static [Self] {
76
  available_demo_data()
77
  }
78
+
79
+ fn technician_count(self) -> usize {
80
+ match self {
81
+ Self::Small => 2,
82
+ Self::Standard => 4,
83
+ Self::Large => 6,
84
+ }
85
+ }
86
+
87
+ fn visit_count(self) -> usize {
88
+ match self {
89
+ Self::Small => 6,
90
+ Self::Standard => 14,
91
+ Self::Large => SERVICE_LOCATIONS.len(),
92
+ }
93
+ }
94
  }
95
 
96
  impl FromStr for DemoData {
 
106
  }
107
  }
108
 
109
+ pub async fn generate(demo: DemoData) -> Result<FieldServicePlan, DemoDataError> {
110
+ let locations = build_locations(demo);
111
+ let coords = locations
112
+ .iter()
113
+ .map(|location| Coord::new(location.lat(), location.lng()))
114
+ .collect::<Vec<_>>();
115
+ let network = RoadNetwork::load_or_fetch(&BERGAMO_BBOX, &network_config(), None).await?;
116
+ let matrix = network.compute_matrix(&coords, None).await;
117
+ let travel_legs = build_travel_legs(&network, &matrix, &coords);
118
+ let service_visits = build_service_visits(demo);
119
+ let mut technician_routes = build_technician_routes(demo);
120
+ seed_initial_routes(
121
+ &locations,
122
+ &service_visits,
123
+ &travel_legs,
124
+ &mut technician_routes,
125
+ );
126
+
127
+ Ok(FieldServicePlan::new(
128
+ locations,
129
+ service_visits,
130
+ travel_legs,
131
+ technician_routes,
132
+ ))
133
+ }
134
+
135
+ fn network_config() -> NetworkConfig {
136
+ NetworkConfig::default()
137
+ .cache_dir(PathBuf::from(".osm_cache/field-service-routing/bergamo"))
138
+ .connect_timeout(Duration::from_secs(10))
139
+ .read_timeout(Duration::from_secs(30))
140
+ .overpass_max_retries(1)
141
+ .overpass_retry_backoff(Duration::from_secs(2))
142
+ }
143
+
144
+ fn build_locations(demo: DemoData) -> Vec<Location> {
145
+ DEPOTS
146
+ .iter()
147
+ .map(|seed| seed.to_location("depot"))
148
+ .chain(
149
+ SERVICE_LOCATIONS
150
+ .iter()
151
+ .take(demo.visit_count())
152
+ .map(|seed| seed.to_location("customer")),
153
+ )
154
+ .collect()
155
+ }
156
+
157
+ fn build_service_visits(demo: DemoData) -> Vec<ServiceVisit> {
158
+ SERVICE_LOCATIONS
159
+ .iter()
160
+ .take(demo.visit_count())
161
+ .enumerate()
162
+ .map(|(idx, seed)| {
163
+ let profile = VISIT_PROFILES[idx % VISIT_PROFILES.len()];
164
+ ServiceVisit::new(ServiceVisitInit {
165
+ id: format!("visit-{idx:02}"),
166
+ name: profile.name.to_string(),
167
+ customer: seed.label.to_string(),
168
+ location_idx: DEPOTS.len() + idx,
169
+ duration_minutes: profile.duration_minutes,
170
+ earliest_minute: profile.earliest_minute,
171
+ latest_minute: profile.latest_minute,
172
+ required_skill_mask: profile.required_skill_mask,
173
+ required_parts_mask: profile.required_parts_mask,
174
+ priority: profile.priority,
175
+ territory: seed.territory.to_string(),
176
+ })
177
+ })
178
+ .collect()
179
+ }
180
+
181
+ fn build_technician_routes(demo: DemoData) -> Vec<TechnicianRoute> {
182
+ TECHNICIANS
183
+ .iter()
184
+ .take(demo.technician_count())
185
+ .enumerate()
186
+ .map(|(idx, seed)| {
187
+ TechnicianRoute::new(TechnicianRouteInit {
188
+ id: format!("route-{idx:02}"),
189
+ technician_id: seed.id.to_string(),
190
+ technician_name: seed.name.to_string(),
191
+ color: seed.color.to_string(),
192
+ start_location_idx: seed.start_location_idx,
193
+ end_location_idx: seed.end_location_idx,
194
+ shift_start_minute: 8 * 60,
195
+ shift_end_minute: 17 * 60,
196
+ max_route_minutes: 8 * 60,
197
+ skill_mask: seed.skill_mask,
198
+ inventory_mask: seed.inventory_mask,
199
+ territory: seed.territory.to_string(),
200
+ })
201
+ })
202
+ .collect()
203
+ }
204
+
205
+ fn seed_initial_routes(
206
+ locations: &[Location],
207
+ service_visits: &[ServiceVisit],
208
+ travel_legs: &[TravelLeg],
209
+ technician_routes: &mut [TechnicianRoute],
210
+ ) {
211
+ let mut order = (0..service_visits.len()).collect::<Vec<_>>();
212
+ order.sort_by_key(|&idx| {
213
+ let visit = &service_visits[idx];
214
+ (visit.latest_minute, -visit.priority)
215
+ });
216
+
217
+ for visit_idx in order {
218
+ if let Some((route_idx, insert_pos)) = best_insertion(
219
+ locations,
220
+ service_visits,
221
+ travel_legs,
222
+ technician_routes,
223
+ visit_idx,
224
+ ) {
225
+ technician_routes[route_idx]
226
+ .visits
227
+ .insert(insert_pos, visit_idx);
228
+ }
229
+ }
230
+ }
231
+
232
+ fn best_insertion(
233
+ locations: &[Location],
234
+ service_visits: &[ServiceVisit],
235
+ travel_legs: &[TravelLeg],
236
+ technician_routes: &[TechnicianRoute],
237
+ visit_idx: usize,
238
+ ) -> Option<(usize, usize)> {
239
+ let plan = FieldServicePlan::new(
240
+ locations.to_vec(),
241
+ service_visits.to_vec(),
242
+ travel_legs.to_vec(),
243
+ technician_routes.to_vec(),
244
+ );
245
+ let mut best: Option<(InsertionCost, usize, usize)> = None;
246
+
247
+ for (route_idx, route) in technician_routes.iter().enumerate() {
248
+ for insert_pos in 0..=route.visits.len() {
249
+ let mut candidate = route.clone();
250
+ candidate.visits.insert(insert_pos, visit_idx);
251
+ let cost = insertion_cost(&plan, &candidate);
252
+ if best
253
+ .as_ref()
254
+ .map(|(best_cost, _, _)| cost < *best_cost)
255
+ .unwrap_or(true)
256
+ {
257
+ best = Some((cost, route_idx, insert_pos));
258
+ }
259
+ }
260
+ }
261
+
262
+ best.map(|(_, route_idx, insert_pos)| (route_idx, insert_pos))
263
+ }
264
+
265
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
266
+ struct InsertionCost {
267
+ hard: i64,
268
+ soft: i64,
269
+ }
270
+
271
+ fn insertion_cost(plan: &FieldServicePlan, route: &TechnicianRoute) -> InsertionCost {
272
+ let stats = route_stats(plan, route);
273
+ InsertionCost {
274
+ hard: hard_issue_count(&stats),
275
+ soft: stats.travel_minutes() + stats.route_minutes
276
+ - (stats.territory_matches * 10)
277
+ - stats.priority_slack,
278
  }
279
  }
280
 
281
+ fn hard_issue_count(stats: &RouteStats) -> i64 {
282
+ stats.invalid_visits
283
+ + stats.unreachable_legs
284
+ + stats.missing_skill_visits
285
+ + stats.missing_part_visits
286
+ + stats.late_minutes
287
+ + stats.overtime_minutes
288
+ }
289
+
290
+ fn build_travel_legs(
291
+ network: &RoadNetwork,
292
+ matrix: &solverforge_maps::TravelTimeMatrix,
293
+ coords: &[Coord],
294
+ ) -> Vec<TravelLeg> {
295
+ let width = coords.len();
296
+ let mut legs = Vec::with_capacity(width * width);
297
+
298
+ for from in 0..width {
299
+ for to in 0..width {
300
+ let (duration_seconds, distance_meters, encoded_polyline, reachable) = if from == to {
301
+ (0, 0, encode_polyline(&[coords[from]]), true)
302
+ } else {
303
+ let matrix_duration = matrix.get(from, to).unwrap_or(UNREACHABLE);
304
+ if matrix_duration == UNREACHABLE {
305
+ (0, 0, String::new(), false)
306
+ } else {
307
+ match network.route(coords[from], coords[to]) {
308
+ Ok(route) => {
309
+ let duration_seconds = route.duration_seconds;
310
+ let distance_meters = route.distance_meters.round() as i64;
311
+ let simplified = route.simplify(12.0);
312
+ (
313
+ duration_seconds,
314
+ distance_meters,
315
+ encode_polyline(&simplified.geometry),
316
+ true,
317
+ )
318
+ }
319
+ Err(_) => (0, 0, String::new(), false),
320
+ }
321
+ }
322
+ };
323
+
324
+ legs.push(TravelLeg::new(TravelLegInit {
325
+ id: format!("leg-{from:02}-{to:02}"),
326
+ name: format!("leg-{from:02}-{to:02}"),
327
+ from_location_idx: from,
328
+ to_location_idx: to,
329
+ duration_seconds,
330
+ distance_meters,
331
+ encoded_polyline,
332
+ reachable,
333
+ }));
334
+ }
335
+ }
336
+
337
+ legs
338
  }
339
+
340
+ #[derive(Clone, Copy)]
341
+ struct LocationSeed {
342
+ id: &'static str,
343
+ label: &'static str,
344
+ lat: f64,
345
+ lng: f64,
346
+ territory: &'static str,
347
+ }
348
+
349
+ impl LocationSeed {
350
+ fn to_location(self, kind: &'static str) -> Location {
351
+ Location::new(
352
+ self.id,
353
+ self.label,
354
+ self.label.to_string(),
355
+ coord_e6(self.lat),
356
+ coord_e6(self.lng),
357
+ kind.to_string(),
358
+ )
359
+ }
360
+ }
361
+
362
+ fn coord_e6(value: f64) -> i32 {
363
+ (value * 1_000_000.0).round() as i32
364
+ }
365
+
366
+ #[derive(Clone, Copy)]
367
+ struct VisitProfile {
368
+ name: &'static str,
369
+ duration_minutes: i32,
370
+ earliest_minute: i32,
371
+ latest_minute: i32,
372
+ required_skill_mask: i64,
373
+ required_parts_mask: i64,
374
+ priority: i32,
375
+ }
376
+
377
+ #[derive(Clone, Copy)]
378
+ struct TechnicianSeed {
379
+ id: &'static str,
380
+ name: &'static str,
381
+ color: &'static str,
382
+ start_location_idx: usize,
383
+ end_location_idx: usize,
384
+ skill_mask: i64,
385
+ inventory_mask: i64,
386
+ territory: &'static str,
387
+ }
388
+
389
+ const SKILL_HVAC: i64 = 0b0001;
390
+ const SKILL_ELECTRICAL: i64 = 0b0010;
391
+ const SKILL_PLUMBING: i64 = 0b0100;
392
+ const SKILL_ELEVATOR: i64 = 0b1000;
393
+
394
+ const PART_FILTERS: i64 = 0b0001;
395
+ const PART_RELAYS: i64 = 0b0010;
396
+ const PART_VALVES: i64 = 0b0100;
397
+ const PART_SENSORS: i64 = 0b1000;
398
+
399
+ const DEPOTS: &[LocationSeed] = &[
400
+ LocationSeed {
401
+ id: "depot-ops",
402
+ label: "Bergamo Operations Hub",
403
+ lat: 45.6954,
404
+ lng: 9.6703,
405
+ territory: "center",
406
+ },
407
+ LocationSeed {
408
+ id: "depot-east",
409
+ label: "Seriate Parts Locker",
410
+ lat: 45.6835,
411
+ lng: 9.7210,
412
+ territory: "east",
413
+ },
414
+ ];
415
+
416
+ const SERVICE_LOCATIONS: &[LocationSeed] = &[
417
+ LocationSeed {
418
+ id: "loc-citta-alta",
419
+ label: "Citta Alta heating fault",
420
+ lat: 45.7036,
421
+ lng: 9.6627,
422
+ territory: "north",
423
+ },
424
+ LocationSeed {
425
+ id: "loc-borgo-palazzo",
426
+ label: "Borgo Palazzo refrigeration",
427
+ lat: 45.6903,
428
+ lng: 9.6909,
429
+ territory: "east",
430
+ },
431
+ LocationSeed {
432
+ id: "loc-stazione",
433
+ label: "Station kiosk power",
434
+ lat: 45.6900,
435
+ lng: 9.6750,
436
+ territory: "center",
437
+ },
438
+ LocationSeed {
439
+ id: "loc-longuelo",
440
+ label: "Longuelo pump service",
441
+ lat: 45.6982,
442
+ lng: 9.6377,
443
+ territory: "west",
444
+ },
445
+ LocationSeed {
446
+ id: "loc-redona",
447
+ label: "Redona lift inspection",
448
+ lat: 45.7107,
449
+ lng: 9.6999,
450
+ territory: "north",
451
+ },
452
+ LocationSeed {
453
+ id: "loc-celadina",
454
+ label: "Celadina controls alarm",
455
+ lat: 45.6815,
456
+ lng: 9.7056,
457
+ territory: "east",
458
+ },
459
+ LocationSeed {
460
+ id: "loc-valtesse",
461
+ label: "Valtesse boiler reset",
462
+ lat: 45.7202,
463
+ lng: 9.6736,
464
+ territory: "north",
465
+ },
466
+ LocationSeed {
467
+ id: "loc-colognola",
468
+ label: "Colognola valve leak",
469
+ lat: 45.6767,
470
+ lng: 9.6469,
471
+ territory: "south",
472
+ },
473
+ LocationSeed {
474
+ id: "loc-malpensata",
475
+ label: "Malpensata sensor swap",
476
+ lat: 45.6840,
477
+ lng: 9.6687,
478
+ territory: "south",
479
+ },
480
+ LocationSeed {
481
+ id: "loc-seriate",
482
+ label: "Seriate medical cooler",
483
+ lat: 45.6856,
484
+ lng: 9.7242,
485
+ territory: "east",
486
+ },
487
+ LocationSeed {
488
+ id: "loc-gorle",
489
+ label: "Gorle access control",
490
+ lat: 45.7014,
491
+ lng: 9.7138,
492
+ territory: "east",
493
+ },
494
+ LocationSeed {
495
+ id: "loc-treviglio-road",
496
+ label: "Azzano workshop air unit",
497
+ lat: 45.6579,
498
+ lng: 9.6734,
499
+ territory: "south",
500
+ },
501
+ LocationSeed {
502
+ id: "loc-monterosso",
503
+ label: "Monterosso lift callout",
504
+ lat: 45.7161,
505
+ lng: 9.6905,
506
+ territory: "north",
507
+ },
508
+ LocationSeed {
509
+ id: "loc-loreto",
510
+ label: "Loreto electrical board",
511
+ lat: 45.6995,
512
+ lng: 9.6517,
513
+ territory: "west",
514
+ },
515
+ LocationSeed {
516
+ id: "loc-stezzano",
517
+ label: "Stezzano retail HVAC",
518
+ lat: 45.6508,
519
+ lng: 9.6534,
520
+ territory: "south",
521
+ },
522
+ LocationSeed {
523
+ id: "loc-grumello",
524
+ label: "Grumello pressure issue",
525
+ lat: 45.6888,
526
+ lng: 9.6275,
527
+ territory: "west",
528
+ },
529
+ LocationSeed {
530
+ id: "loc-orio",
531
+ label: "Orio terminal chiller",
532
+ lat: 45.6689,
533
+ lng: 9.7044,
534
+ territory: "south",
535
+ },
536
+ LocationSeed {
537
+ id: "loc-ranica",
538
+ label: "Ranica municipal lift",
539
+ lat: 45.7241,
540
+ lng: 9.7133,
541
+ territory: "north",
542
+ },
543
+ LocationSeed {
544
+ id: "loc-torre-boldone",
545
+ label: "Torre Boldone boiler",
546
+ lat: 45.7178,
547
+ lng: 9.7075,
548
+ territory: "north",
549
+ },
550
+ LocationSeed {
551
+ id: "loc-villaggio-sposi",
552
+ label: "Villaggio Sposi pump",
553
+ lat: 45.6901,
554
+ lng: 9.6365,
555
+ territory: "west",
556
+ },
557
+ LocationSeed {
558
+ id: "loc-dalmine",
559
+ label: "Dalmine line sensor",
560
+ lat: 45.6482,
561
+ lng: 9.6061,
562
+ territory: "west",
563
+ },
564
+ LocationSeed {
565
+ id: "loc-alzano",
566
+ label: "Alzano Lombardo relay",
567
+ lat: 45.7362,
568
+ lng: 9.7271,
569
+ territory: "north",
570
+ },
571
+ LocationSeed {
572
+ id: "loc-ponte-san-pietro",
573
+ label: "Ponte San Pietro valve",
574
+ lat: 45.7001,
575
+ lng: 9.5908,
576
+ territory: "west",
577
+ },
578
+ LocationSeed {
579
+ id: "loc-scanzo",
580
+ label: "Scanzorosciate cooler",
581
+ lat: 45.7105,
582
+ lng: 9.7354,
583
+ territory: "east",
584
+ },
585
+ ];
586
+
587
+ const VISIT_PROFILES: &[VisitProfile] = &[
588
+ VisitProfile {
589
+ name: "Boiler restart",
590
+ duration_minutes: 35,
591
+ earliest_minute: 8 * 60,
592
+ latest_minute: 10 * 60,
593
+ required_skill_mask: SKILL_HVAC,
594
+ required_parts_mask: PART_SENSORS,
595
+ priority: 4,
596
+ },
597
+ VisitProfile {
598
+ name: "Refrigeration diagnosis",
599
+ duration_minutes: 45,
600
+ earliest_minute: 9 * 60,
601
+ latest_minute: 12 * 60,
602
+ required_skill_mask: SKILL_HVAC | SKILL_ELECTRICAL,
603
+ required_parts_mask: PART_RELAYS,
604
+ priority: 5,
605
+ },
606
+ VisitProfile {
607
+ name: "Electrical board check",
608
+ duration_minutes: 30,
609
+ earliest_minute: 8 * 60 + 30,
610
+ latest_minute: 13 * 60,
611
+ required_skill_mask: SKILL_ELECTRICAL,
612
+ required_parts_mask: PART_RELAYS,
613
+ priority: 3,
614
+ },
615
+ VisitProfile {
616
+ name: "Pump service",
617
+ duration_minutes: 50,
618
+ earliest_minute: 10 * 60,
619
+ latest_minute: 15 * 60,
620
+ required_skill_mask: SKILL_PLUMBING,
621
+ required_parts_mask: PART_VALVES,
622
+ priority: 3,
623
+ },
624
+ VisitProfile {
625
+ name: "Lift safety inspection",
626
+ duration_minutes: 60,
627
+ earliest_minute: 11 * 60,
628
+ latest_minute: 16 * 60,
629
+ required_skill_mask: SKILL_ELEVATOR | SKILL_ELECTRICAL,
630
+ required_parts_mask: PART_SENSORS,
631
+ priority: 4,
632
+ },
633
+ VisitProfile {
634
+ name: "Controls alarm reset",
635
+ duration_minutes: 25,
636
+ earliest_minute: 13 * 60,
637
+ latest_minute: 17 * 60,
638
+ required_skill_mask: SKILL_ELECTRICAL,
639
+ required_parts_mask: PART_SENSORS,
640
+ priority: 2,
641
+ },
642
+ ];
643
+
644
+ const TECHNICIANS: &[TechnicianSeed] = &[
645
+ TechnicianSeed {
646
+ id: "tech-ada",
647
+ name: "Ada Romano",
648
+ color: "#2563eb",
649
+ start_location_idx: 0,
650
+ end_location_idx: 0,
651
+ skill_mask: SKILL_HVAC | SKILL_ELECTRICAL,
652
+ inventory_mask: PART_FILTERS | PART_RELAYS | PART_SENSORS,
653
+ territory: "center",
654
+ },
655
+ TechnicianSeed {
656
+ id: "tech-marco",
657
+ name: "Marco Bianchi",
658
+ color: "#059669",
659
+ start_location_idx: 1,
660
+ end_location_idx: 1,
661
+ skill_mask: SKILL_PLUMBING | SKILL_HVAC,
662
+ inventory_mask: PART_VALVES | PART_FILTERS | PART_SENSORS,
663
+ territory: "east",
664
+ },
665
+ TechnicianSeed {
666
+ id: "tech-elena",
667
+ name: "Elena Conti",
668
+ color: "#d97706",
669
+ start_location_idx: 0,
670
+ end_location_idx: 0,
671
+ skill_mask: SKILL_ELEVATOR | SKILL_ELECTRICAL,
672
+ inventory_mask: PART_RELAYS | PART_SENSORS,
673
+ territory: "north",
674
+ },
675
+ TechnicianSeed {
676
+ id: "tech-paolo",
677
+ name: "Paolo Gatti",
678
+ color: "#be123c",
679
+ start_location_idx: 0,
680
+ end_location_idx: 0,
681
+ skill_mask: SKILL_PLUMBING | SKILL_ELECTRICAL | SKILL_HVAC,
682
+ inventory_mask: PART_VALVES | PART_RELAYS | PART_FILTERS,
683
+ territory: "west",
684
+ },
685
+ TechnicianSeed {
686
+ id: "tech-sara",
687
+ name: "Sara Ferri",
688
+ color: "#7c3aed",
689
+ start_location_idx: 1,
690
+ end_location_idx: 1,
691
+ skill_mask: SKILL_HVAC | SKILL_ELECTRICAL | SKILL_ELEVATOR,
692
+ inventory_mask: PART_RELAYS | PART_SENSORS | PART_FILTERS,
693
+ territory: "south",
694
+ },
695
+ TechnicianSeed {
696
+ id: "tech-luca",
697
+ name: "Luca Moretti",
698
+ color: "#0f766e",
699
+ start_location_idx: 0,
700
+ end_location_idx: 0,
701
+ skill_mask: SKILL_PLUMBING | SKILL_HVAC | SKILL_ELECTRICAL,
702
+ inventory_mask: PART_VALVES | PART_RELAYS | PART_SENSORS | PART_FILTERS,
703
+ territory: "east",
704
+ },
705
+ ];
src/data/mod.rs CHANGED
@@ -1,3 +1,3 @@
1
  mod data_seed;
2
 
3
- pub use data_seed::{available_demo_data, default_demo_data, generate, DemoData};
 
1
  mod data_seed;
2
 
3
+ pub use data_seed::{available_demo_data, default_demo_data, generate, DemoData, DemoDataError};
src/domain/{plan.rs → field_service_plan.rs} RENAMED
@@ -1,35 +1,49 @@
1
  use serde::{Deserialize, Serialize};
2
  use solverforge::prelude::*;
3
 
4
- // @solverforge:neutral-solution
5
  // @solverforge:begin solution-imports
 
 
 
 
6
  // @solverforge:end solution-imports
7
 
8
- /// The root planning solution.
9
- ///
10
- /// Fresh projects start as a neutral shell. Add fact collections, planning
11
- /// entity collections, and variable fields through the CLI as your domain
12
- /// takes shape.
13
  #[planning_solution(
14
  constraints = "crate::constraints::create_constraints",
15
  solver_toml = "../../solver.toml"
16
  )]
17
  #[derive(Serialize, Deserialize)]
18
- pub struct Plan {
19
  // @solverforge:begin solution-collections
 
 
 
 
 
 
 
 
20
  // @solverforge:end solution-collections
21
  #[planning_score]
22
  pub score: Option<HardSoftScore>,
23
  }
24
 
25
- impl Plan {
26
  #[rustfmt::skip]
27
  pub fn new(
28
  // @solverforge:begin solution-constructor-params
 
 
 
 
29
  // @solverforge:end solution-constructor-params
30
  ) -> Self {
31
  Self {
32
  // @solverforge:begin solution-constructor-init
 
 
 
 
33
  // @solverforge:end solution-constructor-init
34
  score: None,
35
  }
 
1
  use serde::{Deserialize, Serialize};
2
  use solverforge::prelude::*;
3
 
 
4
  // @solverforge:begin solution-imports
5
+ use super::Location;
6
+ use super::ServiceVisit;
7
+ use super::TechnicianRoute;
8
+ use super::TravelLeg;
9
  // @solverforge:end solution-imports
10
 
 
 
 
 
 
11
  #[planning_solution(
12
  constraints = "crate::constraints::create_constraints",
13
  solver_toml = "../../solver.toml"
14
  )]
15
  #[derive(Serialize, Deserialize)]
16
+ pub struct FieldServicePlan {
17
  // @solverforge:begin solution-collections
18
+ #[problem_fact_collection]
19
+ pub locations: Vec<Location>,
20
+ #[problem_fact_collection]
21
+ pub service_visits: Vec<ServiceVisit>,
22
+ #[problem_fact_collection]
23
+ pub travel_legs: Vec<TravelLeg>,
24
+ #[planning_entity_collection]
25
+ pub technician_routes: Vec<TechnicianRoute>,
26
  // @solverforge:end solution-collections
27
  #[planning_score]
28
  pub score: Option<HardSoftScore>,
29
  }
30
 
31
+ impl FieldServicePlan {
32
  #[rustfmt::skip]
33
  pub fn new(
34
  // @solverforge:begin solution-constructor-params
35
+ locations: Vec<Location>,
36
+ service_visits: Vec<ServiceVisit>,
37
+ travel_legs: Vec<TravelLeg>,
38
+ technician_routes: Vec<TechnicianRoute>,
39
  // @solverforge:end solution-constructor-params
40
  ) -> Self {
41
  Self {
42
  // @solverforge:begin solution-constructor-init
43
+ locations,
44
+ service_visits,
45
+ travel_legs,
46
+ technician_routes,
47
  // @solverforge:end solution-constructor-init
48
  score: None,
49
  }
src/domain/location.rs ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use serde::{Deserialize, Serialize};
2
+ use solverforge::prelude::*;
3
+
4
+ /// TODO — describe this fact.
5
+ #[problem_fact]
6
+ #[derive(Serialize, Deserialize)]
7
+ pub struct Location {
8
+ #[planning_id]
9
+ pub id: String,
10
+ pub name: String,
11
+ pub label: String,
12
+ pub lat_e6: i32,
13
+ pub lng_e6: i32,
14
+ pub kind: String,
15
+ }
16
+
17
+ impl Location {
18
+ pub fn new(
19
+ id: impl Into<String>,
20
+ name: impl Into<String>,
21
+ label: String,
22
+ lat_e6: i32,
23
+ lng_e6: i32,
24
+ kind: String,
25
+ ) -> Self {
26
+ Self {
27
+ id: id.into(),
28
+ name: name.into(),
29
+ label,
30
+ lat_e6,
31
+ lng_e6,
32
+ kind,
33
+ }
34
+ }
35
+
36
+ pub fn lat(&self) -> f64 {
37
+ f64::from(self.lat_e6) / 1_000_000.0
38
+ }
39
+
40
+ pub fn lng(&self) -> f64 {
41
+ f64::from(self.lng_e6) / 1_000_000.0
42
+ }
43
+ }
44
+
45
+ #[cfg(test)]
46
+ mod tests {
47
+ use super::*;
48
+
49
+ #[test]
50
+ fn test_location_construction() {
51
+ let fact = Location::new(
52
+ "test-id",
53
+ "test",
54
+ "test".to_string(),
55
+ 0,
56
+ 0,
57
+ "test".to_string(),
58
+ );
59
+ assert_eq!(fact.id, "test-id");
60
+ assert_eq!(fact.name, "test");
61
+ let _ = &fact.label;
62
+ let _ = &fact.lat_e6;
63
+ let _ = &fact.lng_e6;
64
+ let _ = &fact.kind;
65
+ }
66
+ }
src/domain/mod.rs CHANGED
@@ -2,8 +2,19 @@ solverforge::planning_model! {
2
  root = "src/domain";
3
 
4
  // @solverforge:begin domain-exports
5
- mod plan;
 
 
 
 
6
 
7
- pub use plan::Plan;
8
- // @solverforge:end domain-exports
 
 
 
 
 
 
 
9
  }
 
2
  root = "src/domain";
3
 
4
  // @solverforge:begin domain-exports
5
+ mod location;
6
+ mod service_visit;
7
+ mod travel_leg;
8
+ mod technician_route;
9
+ mod field_service_plan;
10
 
11
+ pub use location::Location;
12
+ pub use service_visit::ServiceVisit;
13
+ pub use service_visit::ServiceVisitInit;
14
+ pub use travel_leg::TravelLeg;
15
+ pub use travel_leg::TravelLegInit;
16
+ pub use technician_route::TechnicianRoute;
17
+ pub use technician_route::TechnicianRouteInit;
18
+ pub use field_service_plan::FieldServicePlan;
19
+ // @solverforge:end domain-exports
20
  }
src/domain/service_visit.rs ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use serde::{Deserialize, Serialize};
2
+ use solverforge::prelude::*;
3
+
4
+ /// TODO — describe this fact.
5
+ #[problem_fact]
6
+ #[derive(Serialize, Deserialize)]
7
+ pub struct ServiceVisit {
8
+ #[planning_id]
9
+ pub id: String,
10
+ pub name: String,
11
+ pub customer: String,
12
+ pub location_idx: usize,
13
+ pub duration_minutes: i32,
14
+ pub earliest_minute: i32,
15
+ pub latest_minute: i32,
16
+ pub required_skill_mask: i64,
17
+ pub required_parts_mask: i64,
18
+ pub priority: i32,
19
+ pub territory: String,
20
+ }
21
+
22
+ #[derive(Debug, Clone)]
23
+ pub struct ServiceVisitInit {
24
+ pub id: String,
25
+ pub name: String,
26
+ pub customer: String,
27
+ pub location_idx: usize,
28
+ pub duration_minutes: i32,
29
+ pub earliest_minute: i32,
30
+ pub latest_minute: i32,
31
+ pub required_skill_mask: i64,
32
+ pub required_parts_mask: i64,
33
+ pub priority: i32,
34
+ pub territory: String,
35
+ }
36
+
37
+ impl ServiceVisit {
38
+ pub fn new(init: ServiceVisitInit) -> Self {
39
+ Self {
40
+ id: init.id,
41
+ name: init.name,
42
+ customer: init.customer,
43
+ location_idx: init.location_idx,
44
+ duration_minutes: init.duration_minutes,
45
+ earliest_minute: init.earliest_minute,
46
+ latest_minute: init.latest_minute,
47
+ required_skill_mask: init.required_skill_mask,
48
+ required_parts_mask: init.required_parts_mask,
49
+ priority: init.priority,
50
+ territory: init.territory,
51
+ }
52
+ }
53
+ }
54
+
55
+ #[cfg(test)]
56
+ mod tests {
57
+ use super::*;
58
+
59
+ #[test]
60
+ fn test_service_visit_construction() {
61
+ let fact = ServiceVisit::new(ServiceVisitInit {
62
+ id: "test-id".to_string(),
63
+ name: "test".to_string(),
64
+ customer: "test".to_string(),
65
+ location_idx: Default::default(),
66
+ duration_minutes: Default::default(),
67
+ earliest_minute: Default::default(),
68
+ latest_minute: Default::default(),
69
+ required_skill_mask: Default::default(),
70
+ required_parts_mask: Default::default(),
71
+ priority: Default::default(),
72
+ territory: "test".to_string(),
73
+ });
74
+ assert_eq!(fact.id, "test-id");
75
+ assert_eq!(fact.name, "test");
76
+ let _ = &fact.customer;
77
+ let _ = &fact.location_idx;
78
+ let _ = &fact.duration_minutes;
79
+ let _ = &fact.earliest_minute;
80
+ let _ = &fact.latest_minute;
81
+ let _ = &fact.required_skill_mask;
82
+ let _ = &fact.required_parts_mask;
83
+ let _ = &fact.priority;
84
+ let _ = &fact.territory;
85
+ }
86
+ }
src/domain/technician_route.rs ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use serde::{Deserialize, Serialize};
2
+ use solverforge::prelude::*;
3
+
4
+ /// TODO — describe this entity.
5
+ #[planning_entity]
6
+ #[derive(Serialize, Deserialize)]
7
+ pub struct TechnicianRoute {
8
+ #[planning_id]
9
+ pub id: String,
10
+ pub technician_id: String,
11
+ pub technician_name: String,
12
+ pub color: String,
13
+ pub start_location_idx: usize,
14
+ pub end_location_idx: usize,
15
+ pub shift_start_minute: i32,
16
+ pub shift_end_minute: i32,
17
+ pub max_route_minutes: i32,
18
+ pub skill_mask: i64,
19
+ pub inventory_mask: i64,
20
+ pub territory: String,
21
+ // @solverforge:begin entity-variables
22
+ #[planning_list_variable(element_collection = "service_visits")]
23
+ pub visits: Vec<usize>,
24
+ // @solverforge:end entity-variables
25
+ }
26
+
27
+ #[derive(Debug, Clone)]
28
+ pub struct TechnicianRouteInit {
29
+ pub id: String,
30
+ pub technician_id: String,
31
+ pub technician_name: String,
32
+ pub color: String,
33
+ pub start_location_idx: usize,
34
+ pub end_location_idx: usize,
35
+ pub shift_start_minute: i32,
36
+ pub shift_end_minute: i32,
37
+ pub max_route_minutes: i32,
38
+ pub skill_mask: i64,
39
+ pub inventory_mask: i64,
40
+ pub territory: String,
41
+ }
42
+
43
+ impl TechnicianRoute {
44
+ pub fn new(init: TechnicianRouteInit) -> Self {
45
+ Self {
46
+ id: init.id,
47
+ technician_id: init.technician_id,
48
+ technician_name: init.technician_name,
49
+ color: init.color,
50
+ start_location_idx: init.start_location_idx,
51
+ end_location_idx: init.end_location_idx,
52
+ shift_start_minute: init.shift_start_minute,
53
+ shift_end_minute: init.shift_end_minute,
54
+ max_route_minutes: init.max_route_minutes,
55
+ skill_mask: init.skill_mask,
56
+ inventory_mask: init.inventory_mask,
57
+ territory: init.territory,
58
+ // @solverforge:begin entity-variable-init
59
+ visits: Vec::new(),
60
+ // @solverforge:end entity-variable-init
61
+ }
62
+ }
63
+ }
64
+
65
+ #[cfg(test)]
66
+ mod tests {
67
+ use super::*;
68
+
69
+ #[test]
70
+ fn test_technician_route_construction() {
71
+ let entity = TechnicianRoute::new(TechnicianRouteInit {
72
+ id: "test-id".to_string(),
73
+ technician_id: "test".to_string(),
74
+ technician_name: "test".to_string(),
75
+ color: "test".to_string(),
76
+ start_location_idx: Default::default(),
77
+ end_location_idx: Default::default(),
78
+ shift_start_minute: Default::default(),
79
+ shift_end_minute: Default::default(),
80
+ max_route_minutes: Default::default(),
81
+ skill_mask: Default::default(),
82
+ inventory_mask: Default::default(),
83
+ territory: "test".to_string(),
84
+ });
85
+ assert_eq!(entity.id, "test-id");
86
+ let _ = &entity.technician_id;
87
+ let _ = &entity.technician_name;
88
+ let _ = &entity.color;
89
+ let _ = &entity.start_location_idx;
90
+ let _ = &entity.end_location_idx;
91
+ let _ = &entity.shift_start_minute;
92
+ let _ = &entity.shift_end_minute;
93
+ let _ = &entity.max_route_minutes;
94
+ let _ = &entity.skill_mask;
95
+ let _ = &entity.inventory_mask;
96
+ let _ = &entity.territory;
97
+ }
98
+ }
src/domain/travel_leg.rs ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use serde::{Deserialize, Serialize};
2
+ use solverforge::prelude::*;
3
+
4
+ /// TODO — describe this fact.
5
+ #[problem_fact]
6
+ #[derive(Serialize, Deserialize)]
7
+ pub struct TravelLeg {
8
+ #[planning_id]
9
+ pub id: String,
10
+ pub name: String,
11
+ pub from_location_idx: usize,
12
+ pub to_location_idx: usize,
13
+ pub duration_seconds: i64,
14
+ pub distance_meters: i64,
15
+ pub encoded_polyline: String,
16
+ pub reachable: bool,
17
+ }
18
+
19
+ #[derive(Debug, Clone)]
20
+ pub struct TravelLegInit {
21
+ pub id: String,
22
+ pub name: String,
23
+ pub from_location_idx: usize,
24
+ pub to_location_idx: usize,
25
+ pub duration_seconds: i64,
26
+ pub distance_meters: i64,
27
+ pub encoded_polyline: String,
28
+ pub reachable: bool,
29
+ }
30
+
31
+ impl TravelLeg {
32
+ pub fn new(init: TravelLegInit) -> Self {
33
+ Self {
34
+ id: init.id,
35
+ name: init.name,
36
+ from_location_idx: init.from_location_idx,
37
+ to_location_idx: init.to_location_idx,
38
+ duration_seconds: init.duration_seconds,
39
+ distance_meters: init.distance_meters,
40
+ encoded_polyline: init.encoded_polyline,
41
+ reachable: init.reachable,
42
+ }
43
+ }
44
+ }
45
+
46
+ #[cfg(test)]
47
+ mod tests {
48
+ use super::*;
49
+
50
+ #[test]
51
+ fn test_travel_leg_construction() {
52
+ let fact = TravelLeg::new(TravelLegInit {
53
+ id: "test-id".to_string(),
54
+ name: "test".to_string(),
55
+ from_location_idx: Default::default(),
56
+ to_location_idx: Default::default(),
57
+ duration_seconds: Default::default(),
58
+ distance_meters: Default::default(),
59
+ encoded_polyline: "test".to_string(),
60
+ reachable: false,
61
+ });
62
+ assert_eq!(fact.id, "test-id");
63
+ assert_eq!(fact.name, "test");
64
+ let _ = &fact.from_location_idx;
65
+ let _ = &fact.to_location_idx;
66
+ let _ = &fact.duration_seconds;
67
+ let _ = &fact.distance_meters;
68
+ let _ = &fact.encoded_polyline;
69
+ let _ = &fact.reachable;
70
+ }
71
+ }
src/lib.rs CHANGED
@@ -1,7 +1,7 @@
1
  /* solverforge-fsr — neutral constraint optimizer built with SolverForge
2
 
3
  Structure:
4
- domain/ — Plan (solution) plus CLI-generated entities and facts
5
  constraints/ — Scoring rules
6
  solver/ — Engine, service, termination config
7
  api/ — HTTP API (axum)
 
1
  /* solverforge-fsr — neutral constraint optimizer built with SolverForge
2
 
3
  Structure:
4
+ domain/ — FieldServicePlan (solution) plus CLI-generated entities and facts
5
  constraints/ — Scoring rules
6
  solver/ — Engine, service, termination config
7
  api/ — HTTP API (axum)
src/main.rs CHANGED
@@ -1,6 +1,6 @@
1
  /* solverforge-fsr — unified optimizer with SolverForge
2
- Run with: solverforge server
3
- Then open the printed local URL (default port 7860) */
4
 
5
  use solverforge_fsr::api;
6
 
 
1
  /* solverforge-fsr — unified optimizer with SolverForge
2
+ Run with: solverforge server
3
+ Then open the printed local URL (default port 7860) */
4
 
5
  use solverforge_fsr::api;
6
 
src/solver/service.rs CHANGED
@@ -12,10 +12,10 @@ use solverforge::{
12
  };
13
 
14
  use crate::api::PlanDto;
15
- use crate::domain::Plan;
16
 
17
  // Static manager — must be 'static for retained job execution.
18
- static MANAGER: SolverManager<Plan> = SolverManager::new();
19
 
20
  #[derive(Serialize)]
21
  #[serde(rename_all = "camelCase")]
@@ -65,7 +65,7 @@ impl SolverService {
65
  }
66
  }
67
 
68
- pub fn start_job(&self, plan: Plan) -> Result<String, SolverManagerError> {
69
  let (job_id, receiver) = MANAGER.solve(plan)?;
70
  let (sse_tx, _) = broadcast::channel(64);
71
 
@@ -140,7 +140,7 @@ impl SolverService {
140
  &self,
141
  id: &str,
142
  snapshot_revision: Option<u64>,
143
- ) -> Result<SolverSnapshot<Plan>, SolverManagerError> {
144
  MANAGER.get_snapshot(parse_job_id(id)?, snapshot_revision)
145
  }
146
 
@@ -157,7 +157,7 @@ async fn drain_receiver(
157
  jobs: Arc<RwLock<HashMap<usize, JobState>>>,
158
  job_id: usize,
159
  sse_tx: broadcast::Sender<String>,
160
- mut receiver: mpsc::UnboundedReceiver<SolverEvent<Plan>>,
161
  ) {
162
  while let Some(event) = receiver.recv().await {
163
  let payload = match &event {
@@ -225,7 +225,7 @@ fn snapshot_status_event_payload(
225
  job_id: usize,
226
  event_type: &'static str,
227
  status: &SolverStatus<HardSoftScore>,
228
- snapshot: &SolverSnapshot<Plan>,
229
  ) -> String {
230
  serialize_payload(JobEventPayload {
231
  id: job_id.to_string(),
@@ -271,7 +271,7 @@ fn event_payload(
271
  job_id: usize,
272
  event_type: &'static str,
273
  metadata: &SolverEventMetadata<HardSoftScore>,
274
- solution: Option<&Plan>,
275
  error: Option<&str>,
276
  ) -> String {
277
  serialize_payload(JobEventPayload {
 
12
  };
13
 
14
  use crate::api::PlanDto;
15
+ use crate::domain::FieldServicePlan;
16
 
17
  // Static manager — must be 'static for retained job execution.
18
+ static MANAGER: SolverManager<FieldServicePlan> = SolverManager::new();
19
 
20
  #[derive(Serialize)]
21
  #[serde(rename_all = "camelCase")]
 
65
  }
66
  }
67
 
68
+ pub fn start_job(&self, plan: FieldServicePlan) -> Result<String, SolverManagerError> {
69
  let (job_id, receiver) = MANAGER.solve(plan)?;
70
  let (sse_tx, _) = broadcast::channel(64);
71
 
 
140
  &self,
141
  id: &str,
142
  snapshot_revision: Option<u64>,
143
+ ) -> Result<SolverSnapshot<FieldServicePlan>, SolverManagerError> {
144
  MANAGER.get_snapshot(parse_job_id(id)?, snapshot_revision)
145
  }
146
 
 
157
  jobs: Arc<RwLock<HashMap<usize, JobState>>>,
158
  job_id: usize,
159
  sse_tx: broadcast::Sender<String>,
160
+ mut receiver: mpsc::UnboundedReceiver<SolverEvent<FieldServicePlan>>,
161
  ) {
162
  while let Some(event) = receiver.recv().await {
163
  let payload = match &event {
 
225
  job_id: usize,
226
  event_type: &'static str,
227
  status: &SolverStatus<HardSoftScore>,
228
+ snapshot: &SolverSnapshot<FieldServicePlan>,
229
  ) -> String {
230
  serialize_payload(JobEventPayload {
231
  id: job_id.to_string(),
 
271
  job_id: usize,
272
  event_type: &'static str,
273
  metadata: &SolverEventMetadata<HardSoftScore>,
274
+ solution: Option<&FieldServicePlan>,
275
  error: Option<&str>,
276
  ) -> String {
277
  serialize_payload(JobEventPayload {
static/generated/ui-model.json CHANGED
@@ -1,6 +1,49 @@
1
  {
2
- "entities": [],
3
- "facts": [],
4
- "constraints": [],
5
- "views": []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  }
 
1
  {
2
+ "constraints": [
3
+ "balance_workload",
4
+ "minimize_travel",
5
+ "priority_slack",
6
+ "reachable_legs",
7
+ "required_parts",
8
+ "required_skills",
9
+ "shift_capacity",
10
+ "territory_affinity",
11
+ "time_windows"
12
+ ],
13
+ "entities": [
14
+ {
15
+ "label": "Technician Route",
16
+ "name": "technician_route",
17
+ "plural": "technician_routes"
18
+ }
19
+ ],
20
+ "facts": [
21
+ {
22
+ "label": "Location",
23
+ "name": "location",
24
+ "plural": "locations"
25
+ },
26
+ {
27
+ "label": "Service Visit",
28
+ "name": "service_visit",
29
+ "plural": "service_visits"
30
+ },
31
+ {
32
+ "label": "Travel Leg",
33
+ "name": "travel_leg",
34
+ "plural": "travel_legs"
35
+ }
36
+ ],
37
+ "views": [
38
+ {
39
+ "allowsUnassigned": false,
40
+ "entity": "technician_route",
41
+ "entityPlural": "technician_routes",
42
+ "id": "technician_route-visits",
43
+ "kind": "list",
44
+ "label": "Technician Route · visits",
45
+ "sourcePlural": "service_visits",
46
+ "variableField": "visits"
47
+ }
48
+ ]
49
  }