Compare commits
13
Commits
b3207a64f5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ab2c8928e
|
||
|
|
02505d4d12
|
||
|
|
3d0efdc1d7
|
||
|
|
5eb7ed9dce
|
||
|
|
d859e505a3
|
||
|
|
3cb2e9aaef
|
||
|
|
f056d845e9
|
||
|
|
51088435aa
|
||
|
|
a22cc3a672
|
||
|
|
1e35223bd7
|
||
|
|
55aac2d882
|
||
|
|
3195d39547
|
||
|
|
0b3b5c6896
|
No files matched your search
-11
@@ -1,11 +0,0 @@
|
||||
image: alpine/edge
|
||||
secrets:
|
||||
- 88cce185-d2ec-4573-9546-e33f36f79bbf
|
||||
sources:
|
||||
- git+ssh://git@git.sr.ht/~swaits/mcps
|
||||
tasks:
|
||||
- mirror-to-github: |
|
||||
cd ~/mcps
|
||||
ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
|
||||
git remote add github git@github.com:swaits/mcps.git
|
||||
git push --mirror github
|
||||
Generated
+372
-24
@@ -2,6 +2,21 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.15"
|
||||
@@ -51,6 +66,15 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
@@ -63,12 +87,33 @@ version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@@ -76,10 +121,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.15"
|
||||
name = "chrono"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc"
|
||||
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
@@ -108,6 +168,12 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.5"
|
||||
@@ -133,6 +199,17 @@ version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "distimate"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c93ea4f00253fe5c1fece7e0414fa6b56e59c433f52c6648752ac7eac94d9d4a"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"statrs",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
@@ -157,9 +234,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.1.0"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
|
||||
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
@@ -178,6 +255,29 @@ version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.4.0"
|
||||
@@ -201,10 +301,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
name = "js-sys"
|
||||
version = "0.3.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.158"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@@ -219,17 +328,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "mcps"
|
||||
version = "0.1.1"
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "matrixmultiply"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"rawpointer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mcps"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"distimate",
|
||||
"rand",
|
||||
"rand_distr",
|
||||
"rayon",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"tempfile",
|
||||
"workdays",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -238,6 +365,63 @@ version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "nalgebra"
|
||||
version = "0.32.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"matrixmultiply",
|
||||
"nalgebra-macros",
|
||||
"num-complex",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"rand_distr",
|
||||
"simba",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nalgebra-macros"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -254,6 +438,12 @@ version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.20"
|
||||
@@ -274,9 +464,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -321,6 +511,12 @@ dependencies = [
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rawpointer"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
@@ -343,9 +539,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
version = "0.38.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
@@ -361,19 +557,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.207"
|
||||
name = "safe_arch"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2"
|
||||
checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.209"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.207"
|
||||
version = "1.0.209"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e"
|
||||
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -382,9 +587,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.124"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d"
|
||||
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -405,6 +610,37 @@ dependencies = [
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "simba"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"wide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "statrs"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f697a07e4606a0a25c044de247e583a330dbb1731d11bc7350b81f48ad567255"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"nalgebra",
|
||||
"num-traits",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -413,9 +649,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.74"
|
||||
version = "2.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7"
|
||||
checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -435,6 +671,32 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
@@ -459,6 +721,80 @@ version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
|
||||
|
||||
[[package]]
|
||||
name = "wide"
|
||||
version = "0.7.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"safe_arch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
@@ -541,6 +877,18 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "workdays"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8ef4c004ec2e8532e8c3828620ddce6253474368edfce8fd5cad2996e706baf"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
|
||||
+7
-4
@@ -1,15 +1,16 @@
|
||||
[package]
|
||||
name = "mcps"
|
||||
version = "0.1.1"
|
||||
version = "0.3.1"
|
||||
authors = ["Stephen Waits <steve@waits.net>"]
|
||||
edition = "2021"
|
||||
description = "A command-line tool for running Monte Carlo simulations on project schedules."
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/swaits/mcps"
|
||||
homepage = "https://github.com/swaits/mcps"
|
||||
repository = "https://git.swaits.com/swaits/mcps"
|
||||
homepage = "https://git.swaits.com/swaits/mcps"
|
||||
keywords = ["monte", "carlo", "project", "schedules", "estimation"]
|
||||
categories = ["command-line-utilities", "development-tools", "simulation"]
|
||||
rust-version = "1.74.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -22,13 +23,15 @@ name = "mcps"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
clap = "4.5.15"
|
||||
distimate = "0.2.0"
|
||||
rand = "0.8.5"
|
||||
rand_distr = "0.4.3"
|
||||
rayon = "1.10.0"
|
||||
serde = { version = "1.0.207", features = ["derive"] }
|
||||
serde_json = "1.0.124"
|
||||
serde_yaml = "0.9.34"
|
||||
workdays = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.12.0"
|
||||
@@ -4,13 +4,13 @@ Monte Carlo Project Scheduler (mcps) is a command-line tool that runs Monte
|
||||
Carlo simulations on project schedules to estimate completion times and effort
|
||||
required. It helps project managers and engineers gain a better understanding of
|
||||
potential project risks and timelines by providing a probabilistic analysis of
|
||||
schedule durations and total work effort.
|
||||
project durations and total work effort.
|
||||
|
||||
## Features
|
||||
|
||||
- **Simulate Project Schedules**: Run multiple simulations to estimate the
|
||||
probability distribution of project completion times and total work effort.
|
||||
- **Customizable Inputs**: Accepts project schedules in YAML or JSON formats,
|
||||
- **Customizable Inputs**: Accepts project definitions in YAML or JSON formats,
|
||||
with options to override certain parameters like the number of workers or the
|
||||
number of iterations.
|
||||
- **Visual Output**: Generates an ASCII-based cumulative distribution function
|
||||
@@ -34,10 +34,13 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
cargo install mcps
|
||||
```
|
||||
|
||||
Note, if you're upgrading, use `cargo install --force mcps` to force
|
||||
installation of the latest version..
|
||||
|
||||
### Option 2: Clone and build the repository locally
|
||||
|
||||
```bash
|
||||
git clone https://github.com/swaits/mcps.git
|
||||
git clone https://git.swaits.com/swaits/mcps.git
|
||||
cd mcps
|
||||
cargo build --release
|
||||
```
|
||||
@@ -46,45 +49,79 @@ The binary will be available at `target/release/mcps`.
|
||||
|
||||
## Usage
|
||||
|
||||
Once you have built the tool, you can use it by providing a project schedule
|
||||
Once you have built the tool, you can use it by providing a project definition
|
||||
file in YAML or JSON format.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
mcps example.yaml -i 100000 -w 10
|
||||
mcps project.yaml -i 100000 -w 10
|
||||
```
|
||||
|
||||
This command runs the Monte Carlo simulation on the [`example.yaml`](./example.yaml) schedule file
|
||||
with 100,000 iterations and overrides the number of workers to 10.
|
||||
This command runs the Monte Carlo simulation on the
|
||||
[`project.yaml`](./assets/project.yaml) project file with 100,000 iterations
|
||||
and overrides the number of workers to 10.
|
||||
|
||||
### Command-Line Options
|
||||
|
||||
- `-i, --iterations <iterations>`: Specify the number of iterations to run. Must be at least 100. Default is 50,000.
|
||||
- `-w, --workers <num_workers>`: Override the number of workers specified in the schedule file.
|
||||
- `-v, --version`: Print version information.
|
||||
- `-h, --help`: Print help information.
|
||||
- `-i, --iterations <iterations>`: Specify the number of iterations to run. Must
|
||||
be at least 100. Default is 50,000.
|
||||
- `-n, --workers <num_workers>`: Override `num_workers` specified in project file
|
||||
- `-b, --begin <YYYY-MM-DD>`: Override `start_date` specified in project file
|
||||
- `-s, --schedule <filename>`: Work schedule config file (.yaml or .json)
|
||||
- `-h, --help`: Print help
|
||||
- `-V, --version`: Print version
|
||||
|
||||
### Schedule File Format
|
||||
### Project Definition File Format
|
||||
|
||||
The tool accepts schedule files in YAML or JSON format. Below is an example of the YAML format:
|
||||
`mcps` accepts projects in YAML or JSON format. Below is an example of the
|
||||
YAML format:
|
||||
|
||||
```yaml
|
||||
num_workers: 5 # for the purpose of scheduling simulation
|
||||
estimate_confidence: 0.80 # confidence actuals will fall inside estimate bounds
|
||||
start_date: 2024-08-01 # [optional] project start date
|
||||
tasks:
|
||||
- id: DesignPhase
|
||||
min_time: 1.5 # 1.5 days
|
||||
max_time: 3.5 # 3.5 days
|
||||
estimate:
|
||||
min: 1.5 # 1.5 days
|
||||
likely: 2.2 # 2.2 days (best guess at actual time)
|
||||
max: 3.5 # 3.5 days
|
||||
dependencies: []
|
||||
- id: ImplementationPhase
|
||||
min_time: 2.25 # 2.25 days
|
||||
max_time: 4.75 # 4.75 days
|
||||
estimate:
|
||||
min: 2.25 # 2.25 days
|
||||
likely: 3.9 # 3.9 days (best guess at actual time)
|
||||
max: 4.75 # 4.75 days
|
||||
dependencies: [DesignPhase]
|
||||
```
|
||||
|
||||
This repo includes examples in [JSON](./example.json) and
|
||||
[YAML](./example.yaml).
|
||||
This repo includes examples in [JSON](./assets/project.json) and
|
||||
[YAML](./assets/project.yaml).
|
||||
|
||||
### Work Schedule Configuration File Format
|
||||
|
||||
This file is optional. It allows you to configure the days of the week you work
|
||||
along with any specific work holidays.
|
||||
|
||||
If you don't provide a schedule file, the tool will default to a 5-day work week
|
||||
with no scheduled holidays.
|
||||
|
||||
`mcps` accepts schedules in YAML or JSON format. Below is an example of the
|
||||
YAML format:
|
||||
|
||||
work_days:
|
||||
|
||||
```yaml
|
||||
work_days:
|
||||
- Monday
|
||||
- Tuesday
|
||||
- Wednesday
|
||||
holidays:
|
||||
- 2023-12-25
|
||||
```
|
||||
|
||||
This repo includes examples in [JSON](./assets/schedule.json) and
|
||||
[YAML](./assets/schedule.yaml).
|
||||
|
||||
### Example Output
|
||||
|
||||
@@ -111,4 +148,5 @@ always welcome!
|
||||
|
||||
### License
|
||||
|
||||
This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details.
|
||||
This project is licensed under the MIT License. See the [LICENSE](./LICENSE)
|
||||
file for details.
|
||||
@@ -0,0 +1,5 @@
|
||||
# TODO
|
||||
|
||||
- add CSV output of distributions
|
||||
- break project out into its own crate
|
||||
- add sourcehut autobuild
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 165 KiB |
@@ -1,49 +1,67 @@
|
||||
{
|
||||
"num_workers": 8,
|
||||
"estimate_confidence": 0.8,
|
||||
"start_date": "2024-08-01",
|
||||
"tasks": [
|
||||
{
|
||||
"id": "RequirementsGathering",
|
||||
"min_time": 10.0,
|
||||
"max_time": 30.0,
|
||||
"estimate": {
|
||||
"min": 10.0,
|
||||
"likely": 25.0,
|
||||
"max": 30.0
|
||||
},
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"id": "SystemDesign",
|
||||
"min_time": 15.0,
|
||||
"max_time": 45.0,
|
||||
"estimate": {
|
||||
"min": 10.0,
|
||||
"likely": 25.0,
|
||||
"max": 30.0
|
||||
},
|
||||
"dependencies": [
|
||||
"RequirementsGathering"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DatabaseDesign",
|
||||
"min_time": 10.0,
|
||||
"max_time": 35.0,
|
||||
"estimate": {
|
||||
"min": 10.0,
|
||||
"likely": 25.0,
|
||||
"max": 30.0
|
||||
},
|
||||
"dependencies": [
|
||||
"SystemDesign"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "UIUXDesign",
|
||||
"min_time": 20.0,
|
||||
"max_time": 50.0,
|
||||
"estimate": {
|
||||
"min": 10.0,
|
||||
"likely": 25.0,
|
||||
"max": 30.0
|
||||
},
|
||||
"dependencies": [
|
||||
"RequirementsGathering"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "FrontendDevelopment",
|
||||
"min_time": 25.0,
|
||||
"max_time": 60.0,
|
||||
"estimate": {
|
||||
"min": 10.0,
|
||||
"likely": 25.0,
|
||||
"max": 30.0
|
||||
},
|
||||
"dependencies": [
|
||||
"UIUXDesign"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "BackendDevelopment",
|
||||
"min_time": 30.0,
|
||||
"max_time": 80.0,
|
||||
"estimate": {
|
||||
"min": 10.0,
|
||||
"likely": 25.0,
|
||||
"max": 30.0
|
||||
},
|
||||
"dependencies": [
|
||||
"SystemDesign",
|
||||
"DatabaseDesign"
|
||||
@@ -51,16 +69,22 @@
|
||||
},
|
||||
{
|
||||
"id": "APIDevelopment",
|
||||
"min_time": 20.0,
|
||||
"max_time": 60.0,
|
||||
"estimate": {
|
||||
"min": 10.0,
|
||||
"likely": 25.0,
|
||||
"max": 30.0
|
||||
},
|
||||
"dependencies": [
|
||||
"BackendDevelopment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "IntegrationTesting",
|
||||
"min_time": 15.0,
|
||||
"max_time": 45.0,
|
||||
"estimate": {
|
||||
"min": 10.0,
|
||||
"likely": 25.0,
|
||||
"max": 30.0
|
||||
},
|
||||
"dependencies": [
|
||||
"FrontendDevelopment",
|
||||
"BackendDevelopment",
|
||||
@@ -69,32 +93,44 @@
|
||||
},
|
||||
{
|
||||
"id": "UserAcceptanceTesting",
|
||||
"min_time": 10.0,
|
||||
"max_time": 30.0,
|
||||
"estimate": {
|
||||
"min": 10.0,
|
||||
"likely": 25.0,
|
||||
"max": 30.0
|
||||
},
|
||||
"dependencies": [
|
||||
"IntegrationTesting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PerformanceOptimization",
|
||||
"min_time": 10.0,
|
||||
"max_time": 40.0,
|
||||
"estimate": {
|
||||
"min": 10.0,
|
||||
"likely": 25.0,
|
||||
"max": 30.0
|
||||
},
|
||||
"dependencies": [
|
||||
"IntegrationTesting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "SecurityAudit",
|
||||
"min_time": 10.0,
|
||||
"max_time": 30.0,
|
||||
"estimate": {
|
||||
"min": 10.0,
|
||||
"likely": 25.0,
|
||||
"max": 30.0
|
||||
},
|
||||
"dependencies": [
|
||||
"IntegrationTesting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "FinalDeployment",
|
||||
"min_time": 5.0,
|
||||
"max_time": 15.0,
|
||||
"estimate": {
|
||||
"min": 10.0,
|
||||
"likely": 25.0,
|
||||
"max": 30.0
|
||||
},
|
||||
"dependencies": [
|
||||
"UserAcceptanceTesting",
|
||||
"PerformanceOptimization",
|
||||
@@ -103,8 +139,11 @@
|
||||
},
|
||||
{
|
||||
"id": "ProjectRetrospective",
|
||||
"min_time": 3.0,
|
||||
"max_time": 10.0,
|
||||
"estimate": {
|
||||
"min": 10.0,
|
||||
"likely": 25.0,
|
||||
"max": 30.0
|
||||
},
|
||||
"dependencies": [
|
||||
"FinalDeployment"
|
||||
]
|
||||
@@ -1,129 +1,177 @@
|
||||
num_workers: 5
|
||||
estimate_confidence: 0.80
|
||||
start_date: 2024-08-01
|
||||
tasks:
|
||||
# Project Initiation and Planning
|
||||
- id: project_kickoff
|
||||
min_time: 2
|
||||
max_time: 3
|
||||
estimate:
|
||||
min: 2
|
||||
likely: 2.8
|
||||
max: 3
|
||||
dependencies: []
|
||||
|
||||
- id: requirements_gathering
|
||||
min_time: 10
|
||||
max_time: 15
|
||||
estimate:
|
||||
min: 10
|
||||
likely: 13
|
||||
max: 15
|
||||
dependencies: [project_kickoff]
|
||||
|
||||
- id: system_architecture_design
|
||||
min_time: 15
|
||||
max_time: 20
|
||||
estimate:
|
||||
min: 15
|
||||
likely: 18
|
||||
max: 20
|
||||
dependencies: [requirements_gathering]
|
||||
|
||||
- id: database_design
|
||||
min_time: 5
|
||||
max_time: 10
|
||||
estimate:
|
||||
min: 5
|
||||
likely: 8
|
||||
max: 10
|
||||
dependencies: [system_architecture_design]
|
||||
|
||||
# Frontend Development
|
||||
- id: ui_ux_design
|
||||
min_time: 20
|
||||
max_time: 30
|
||||
estimate:
|
||||
min: 20
|
||||
likely: 24
|
||||
max: 30
|
||||
dependencies: [requirements_gathering]
|
||||
|
||||
- id: frontend_setup
|
||||
min_time: 3
|
||||
max_time: 5
|
||||
estimate:
|
||||
min: 3
|
||||
likely: 4.2
|
||||
max: 5
|
||||
dependencies: [ui_ux_design]
|
||||
|
||||
- id: user_authentication_frontend
|
||||
min_time: 5
|
||||
max_time: 10
|
||||
estimate:
|
||||
min: 5
|
||||
likely: 6.1
|
||||
max: 10
|
||||
dependencies: [frontend_setup]
|
||||
|
||||
- id: product_catalog_frontend
|
||||
min_time: 15
|
||||
max_time: 25
|
||||
estimate:
|
||||
min: 15
|
||||
likely: 17
|
||||
max: 25
|
||||
dependencies: [frontend_setup]
|
||||
|
||||
- id: shopping_cart_frontend
|
||||
min_time: 10
|
||||
max_time: 15
|
||||
estimate:
|
||||
min: 10
|
||||
likely: 12
|
||||
max: 15
|
||||
dependencies: [frontend_setup]
|
||||
|
||||
- id: checkout_process_frontend
|
||||
min_time: 15
|
||||
max_time: 20
|
||||
estimate:
|
||||
min: 15
|
||||
likely: 17
|
||||
max: 20
|
||||
dependencies: [shopping_cart_frontend]
|
||||
|
||||
- id: user_profile_frontend
|
||||
min_time: 5
|
||||
max_time: 10
|
||||
estimate:
|
||||
min: 5
|
||||
likely: 6
|
||||
max: 10
|
||||
dependencies: [user_authentication_frontend]
|
||||
|
||||
- id: search_functionality_frontend
|
||||
min_time: 10
|
||||
max_time: 15
|
||||
estimate:
|
||||
min: 10
|
||||
likely: 12
|
||||
max: 15
|
||||
dependencies: [product_catalog_frontend]
|
||||
|
||||
# Backend Development
|
||||
- id: backend_setup
|
||||
min_time: 5
|
||||
max_time: 10
|
||||
estimate:
|
||||
min: 5
|
||||
likely: 7
|
||||
max: 10
|
||||
dependencies: [system_architecture_design, database_design]
|
||||
|
||||
- id: user_authentication_backend
|
||||
min_time: 10
|
||||
max_time: 15
|
||||
estimate:
|
||||
min: 10
|
||||
likely: 12
|
||||
max: 15
|
||||
dependencies: [backend_setup]
|
||||
|
||||
- id: product_catalog_backend
|
||||
min_time: 20
|
||||
max_time: 30
|
||||
estimate:
|
||||
min: 20
|
||||
likely: 25
|
||||
max: 30
|
||||
dependencies: [backend_setup]
|
||||
|
||||
- id: shopping_cart_backend
|
||||
min_time: 15
|
||||
max_time: 20
|
||||
estimate:
|
||||
min: 15
|
||||
likely: 18
|
||||
max: 20
|
||||
dependencies: [backend_setup]
|
||||
|
||||
- id: checkout_process_backend
|
||||
min_time: 20
|
||||
max_time: 25
|
||||
estimate:
|
||||
min: 20
|
||||
likely: 23
|
||||
max: 25
|
||||
dependencies: [shopping_cart_backend]
|
||||
|
||||
- id: user_profile_backend
|
||||
min_time: 10
|
||||
max_time: 15
|
||||
estimate:
|
||||
min: 10
|
||||
likely: 12
|
||||
max: 15
|
||||
dependencies: [user_authentication_backend]
|
||||
|
||||
- id: search_functionality_backend
|
||||
min_time: 15
|
||||
max_time: 20
|
||||
estimate:
|
||||
min: 15
|
||||
likely: 18
|
||||
max: 20
|
||||
dependencies: [product_catalog_backend]
|
||||
|
||||
- id: payment_gateway_integration
|
||||
min_time: 10
|
||||
max_time: 15
|
||||
estimate:
|
||||
min: 10
|
||||
likely: 13
|
||||
max: 15
|
||||
dependencies: [checkout_process_backend]
|
||||
|
||||
# API Development and Integration
|
||||
- id: api_design
|
||||
min_time: 5
|
||||
max_time: 10
|
||||
estimate:
|
||||
min: 5
|
||||
likely: 7.5
|
||||
max: 10
|
||||
dependencies: [system_architecture_design]
|
||||
|
||||
- id: api_development
|
||||
min_time: 25
|
||||
max_time: 35
|
||||
estimate:
|
||||
min: 25
|
||||
likely: 32
|
||||
max: 35
|
||||
dependencies: [api_design, backend_setup]
|
||||
|
||||
- id: api_documentation
|
||||
min_time: 5
|
||||
max_time: 10
|
||||
estimate:
|
||||
min: 5
|
||||
likely: 7
|
||||
max: 10
|
||||
dependencies: [api_development]
|
||||
|
||||
# Testing
|
||||
- id: unit_testing
|
||||
min_time: 20
|
||||
max_time: 30
|
||||
estimate:
|
||||
min: 20
|
||||
likely: 25
|
||||
max: 30
|
||||
dependencies:
|
||||
[
|
||||
user_authentication_backend,
|
||||
@@ -135,55 +183,75 @@ tasks:
|
||||
]
|
||||
|
||||
- id: integration_testing
|
||||
min_time: 15
|
||||
max_time: 20
|
||||
estimate:
|
||||
min: 15
|
||||
likely: 16
|
||||
max: 20
|
||||
dependencies: [unit_testing, api_development]
|
||||
|
||||
- id: user_acceptance_testing
|
||||
min_time: 10
|
||||
max_time: 15
|
||||
estimate:
|
||||
min: 10
|
||||
likely: 12
|
||||
max: 15
|
||||
dependencies: [integration_testing]
|
||||
|
||||
# Security and Performance
|
||||
- id: security_audit
|
||||
min_time: 10
|
||||
max_time: 15
|
||||
estimate:
|
||||
min: 10
|
||||
likely: 12
|
||||
max: 15
|
||||
dependencies: [integration_testing]
|
||||
|
||||
- id: performance_optimization
|
||||
min_time: 15
|
||||
max_time: 20
|
||||
estimate:
|
||||
min: 15
|
||||
likely: 17
|
||||
max: 20
|
||||
dependencies: [integration_testing]
|
||||
|
||||
# Deployment and Launch
|
||||
- id: deployment_planning
|
||||
min_time: 5
|
||||
max_time: 10
|
||||
estimate:
|
||||
min: 5
|
||||
likely: 7
|
||||
max: 10
|
||||
dependencies: [system_architecture_design]
|
||||
|
||||
- id: staging_environment_setup
|
||||
min_time: 5
|
||||
max_time: 10
|
||||
estimate:
|
||||
min: 5
|
||||
likely: 7
|
||||
max: 10
|
||||
dependencies: [deployment_planning]
|
||||
|
||||
- id: production_environment_setup
|
||||
min_time: 10
|
||||
max_time: 15
|
||||
estimate:
|
||||
min: 10
|
||||
likely: 12
|
||||
max: 15
|
||||
dependencies: [staging_environment_setup]
|
||||
|
||||
- id: data_migration
|
||||
min_time: 10
|
||||
max_time: 20
|
||||
estimate:
|
||||
min: 10
|
||||
likely: 12
|
||||
max: 20
|
||||
dependencies: [production_environment_setup]
|
||||
|
||||
- id: user_training
|
||||
min_time: 5
|
||||
max_time: 10
|
||||
estimate:
|
||||
min: 5
|
||||
likely: 7
|
||||
max: 10
|
||||
dependencies: [user_acceptance_testing]
|
||||
|
||||
- id: final_deployment
|
||||
min_time: 5
|
||||
max_time: 10
|
||||
estimate:
|
||||
min: 5
|
||||
likely: 7
|
||||
max: 10
|
||||
dependencies:
|
||||
[
|
||||
user_acceptance_testing,
|
||||
@@ -195,11 +263,15 @@ tasks:
|
||||
|
||||
# Post-launch
|
||||
- id: post_launch_support
|
||||
min_time: 10
|
||||
max_time: 15
|
||||
estimate:
|
||||
min: 10
|
||||
likely: 12
|
||||
max: 15
|
||||
dependencies: [final_deployment]
|
||||
|
||||
- id: project_retrospective
|
||||
min_time: 2
|
||||
max_time: 3
|
||||
estimate:
|
||||
min: 2
|
||||
likely: 2.8
|
||||
max: 3
|
||||
dependencies: [post_launch_support]
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"work_days": [
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday"
|
||||
],
|
||||
"holidays": [
|
||||
"2023-12-25"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
work_days:
|
||||
- Monday
|
||||
- Tuesday
|
||||
- Wednesday
|
||||
holidays:
|
||||
- 2023-12-25
|
||||
@@ -0,0 +1,231 @@
|
||||
# This template contains all of the possible sections and their default values
|
||||
|
||||
# Note that all fields that take a lint level have these possible values:
|
||||
# * deny - An error will be produced and the check will fail
|
||||
# * warn - A warning will be produced, but the check will not fail
|
||||
# * allow - No warning or error will be produced, though in some cases a note
|
||||
# will be
|
||||
|
||||
# The values provided in this template are the default values that will be used
|
||||
# when any section or field is not specified in your own configuration
|
||||
|
||||
# Root options
|
||||
|
||||
# The graph table configures how the dependency graph is constructed and thus
|
||||
# which crates the checks are performed against
|
||||
[graph]
|
||||
# If 1 or more target triples (and optionally, target_features) are specified,
|
||||
# only the specified targets will be checked when running `cargo deny check`.
|
||||
# This means, if a particular package is only ever used as a target specific
|
||||
# dependency, such as, for example, the `nix` crate only being used via the
|
||||
# `target_family = "unix"` configuration, that only having windows targets in
|
||||
# this list would mean the nix crate, as well as any of its exclusive
|
||||
# dependencies not shared by any other crates, would be ignored, as the target
|
||||
# list here is effectively saying which targets you are building for.
|
||||
targets = [
|
||||
# The triple can be any string, but only the target triples built in to
|
||||
# rustc (as of 1.40) can be checked against actual config expressions
|
||||
#"x86_64-unknown-linux-musl",
|
||||
# You can also specify which target_features you promise are enabled for a
|
||||
# particular target. target_features are currently not validated against
|
||||
# the actual valid features supported by the target architecture.
|
||||
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
|
||||
]
|
||||
# When creating the dependency graph used as the source of truth when checks are
|
||||
# executed, this field can be used to prune crates from the graph, removing them
|
||||
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
|
||||
# is pruned from the graph, all of its dependencies will also be pruned unless
|
||||
# they are connected to another crate in the graph that hasn't been pruned,
|
||||
# so it should be used with care. The identifiers are [Package ID Specifications]
|
||||
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
|
||||
#exclude = []
|
||||
# If true, metadata will be collected with `--all-features`. Note that this can't
|
||||
# be toggled off if true, if you want to conditionally enable `--all-features` it
|
||||
# is recommended to pass `--all-features` on the cmd line instead
|
||||
all-features = false
|
||||
# If true, metadata will be collected with `--no-default-features`. The same
|
||||
# caveat with `all-features` applies
|
||||
no-default-features = false
|
||||
# If set, these feature will be enabled when collecting metadata. If `--features`
|
||||
# is specified on the cmd line they will take precedence over this option.
|
||||
#features = []
|
||||
|
||||
# The output table provides options for how/if diagnostics are outputted
|
||||
[output]
|
||||
# When outputting inclusion graphs in diagnostics that include features, this
|
||||
# option can be used to specify the depth at which feature edges will be added.
|
||||
# This option is included since the graphs can be quite large and the addition
|
||||
# of features from the crate(s) to all of the graph roots can be far too verbose.
|
||||
# This option can be overridden via `--feature-depth` on the cmd line
|
||||
feature-depth = 1
|
||||
|
||||
# This section is considered when running `cargo deny check advisories`
|
||||
# More documentation for the advisories section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
|
||||
[advisories]
|
||||
# The path where the advisory databases are cloned/fetched into
|
||||
#db-path = "$CARGO_HOME/advisory-dbs"
|
||||
# The url(s) of the advisory databases to use
|
||||
#db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||
# output a note when they are encountered.
|
||||
ignore = [
|
||||
#"RUSTSEC-0000-0000",
|
||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||
]
|
||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||
# If this is false, then it uses a built-in git library.
|
||||
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
|
||||
# See Git Authentication for more information about setting up git authentication.
|
||||
#git-fetch-with-cli = true
|
||||
|
||||
# This section is considered when running `cargo deny check licenses`
|
||||
# More documentation for the licenses section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
|
||||
[licenses]
|
||||
# List of explicitly allowed licenses
|
||||
# See https://spdx.org/licenses/ for list of possible licenses
|
||||
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||
allow = ["MIT", "Apache-2.0", "BSD-3-Clause", "Unicode-DFS-2016"]
|
||||
# The confidence threshold for detecting a license from license text.
|
||||
# The higher the value, the more closely the license text must be to the
|
||||
# canonical license text of a valid SPDX license file.
|
||||
# [possible values: any between 0.0 and 1.0].
|
||||
confidence-threshold = 0.8
|
||||
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
|
||||
# aren't accepted for every possible crate as with the normal allow list
|
||||
exceptions = [
|
||||
# Each entry is the crate and version constraint, and its specific allow
|
||||
# list
|
||||
#{ allow = ["Zlib"], crate = "adler32" },
|
||||
]
|
||||
|
||||
# Some crates don't have (easily) machine readable licensing information,
|
||||
# adding a clarification entry for it allows you to manually specify the
|
||||
# licensing information
|
||||
#[[licenses.clarify]]
|
||||
# The package spec the clarification applies to
|
||||
#crate = "ring"
|
||||
# The SPDX expression for the license requirements of the crate
|
||||
#expression = "MIT AND ISC AND OpenSSL"
|
||||
# One or more files in the crate's source used as the "source of truth" for
|
||||
# the license expression. If the contents match, the clarification will be used
|
||||
# when running the license check, otherwise the clarification will be ignored
|
||||
# and the crate will be checked normally, which may produce warnings or errors
|
||||
# depending on the rest of your configuration
|
||||
#license-files = [
|
||||
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
||||
#{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||
#]
|
||||
|
||||
[licenses.private]
|
||||
# If true, ignores workspace crates that aren't published, or are only
|
||||
# published to private registries.
|
||||
# To see how to mark a crate as unpublished (to the official registry),
|
||||
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
|
||||
ignore = false
|
||||
# One or more private registries that you might publish crates to, if a crate
|
||||
# is only published to private registries, and ignore is true, the crate will
|
||||
# not have its license(s) checked
|
||||
registries = [
|
||||
#"https://sekretz.com/registry
|
||||
]
|
||||
|
||||
# This section is considered when running `cargo deny check bans`.
|
||||
# More documentation about the 'bans' section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
|
||||
[bans]
|
||||
# Lint level for when multiple versions of the same crate are detected
|
||||
multiple-versions = "warn"
|
||||
# Lint level for when a crate version requirement is `*`
|
||||
wildcards = "allow"
|
||||
# The graph highlighting used when creating dotgraphs for crates
|
||||
# with multiple versions
|
||||
# * lowest-version - The path to the lowest versioned duplicate is highlighted
|
||||
# * simplest-path - The path to the version with the fewest edges is highlighted
|
||||
# * all - Both lowest-version and simplest-path are used
|
||||
highlight = "all"
|
||||
# The default lint level for `default` features for crates that are members of
|
||||
# the workspace that is being checked. This can be overridden by allowing/denying
|
||||
# `default` on a crate-by-crate basis if desired.
|
||||
workspace-default-features = "allow"
|
||||
# The default lint level for `default` features for external crates that are not
|
||||
# members of the workspace. This can be overridden by allowing/denying `default`
|
||||
# on a crate-by-crate basis if desired.
|
||||
external-default-features = "allow"
|
||||
# List of crates that are allowed. Use with care!
|
||||
allow = [
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
|
||||
]
|
||||
# List of crates to deny
|
||||
deny = [
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
|
||||
# Wrapper crates can optionally be specified to allow the crate when it
|
||||
# is a direct dependency of the otherwise banned crate
|
||||
#{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
|
||||
]
|
||||
|
||||
# List of features to allow/deny
|
||||
# Each entry the name of a crate and a version range. If version is
|
||||
# not specified, all versions will be matched.
|
||||
#[[bans.features]]
|
||||
#crate = "reqwest"
|
||||
# Features to not allow
|
||||
#deny = ["json"]
|
||||
# Features to allow
|
||||
#allow = [
|
||||
# "rustls",
|
||||
# "__rustls",
|
||||
# "__tls",
|
||||
# "hyper-rustls",
|
||||
# "rustls",
|
||||
# "rustls-pemfile",
|
||||
# "rustls-tls-webpki-roots",
|
||||
# "tokio-rustls",
|
||||
# "webpki-roots",
|
||||
#]
|
||||
# If true, the allowed features must exactly match the enabled feature set. If
|
||||
# this is set there is no point setting `deny`
|
||||
#exact = true
|
||||
|
||||
# Certain crates/versions that will be skipped when doing duplicate detection.
|
||||
skip = [
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
|
||||
]
|
||||
# Similarly to `skip` allows you to skip certain crates during duplicate
|
||||
# detection. Unlike skip, it also includes the entire tree of transitive
|
||||
# dependencies starting at the specified crate, up to a certain depth, which is
|
||||
# by default infinite.
|
||||
skip-tree = [
|
||||
#"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
|
||||
#{ crate = "ansi_term@0.11.0", depth = 20 },
|
||||
]
|
||||
|
||||
# This section is considered when running `cargo deny check sources`.
|
||||
# More documentation about the 'sources' section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
|
||||
[sources]
|
||||
# Lint level for what to happen when a crate from a crate registry that is not
|
||||
# in the allow list is encountered
|
||||
unknown-registry = "warn"
|
||||
# Lint level for what to happen when a crate from a git repository that is not
|
||||
# in the allow list is encountered
|
||||
unknown-git = "warn"
|
||||
# List of URLs for allowed crate registries. Defaults to the crates.io index
|
||||
# if not specified. If it is specified but empty, no registries are allowed.
|
||||
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||
# List of URLs for allowed Git repositories
|
||||
allow-git = []
|
||||
|
||||
[sources.allow-org]
|
||||
# github.com organizations to allow git sources for
|
||||
github = []
|
||||
# gitlab.com organizations to allow git sources for
|
||||
gitlab = []
|
||||
# bitbucket.org organizations to allow git sources for
|
||||
bitbucket = []
|
||||
+89
-69
@@ -1,61 +1,26 @@
|
||||
use mcps::{schedule::Schedule, simulation::run_multiple_simulations};
|
||||
use chrono::{NaiveDate, Utc};
|
||||
use mcps::{schedule::Project, simulation::run_multiple_simulations};
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use workdays::WorkCalendar;
|
||||
|
||||
use std::time::Duration;
|
||||
use std::{str::FromStr, time::Duration};
|
||||
|
||||
const AFTER_HELP_TEXT: &str = "\
|
||||
Examples of supported file formats:
|
||||
Find example project definition and schedule config files in the repository:
|
||||
|
||||
YAML Format:
|
||||
https://git.swaits.com/swaits/mcps/src/branch/main/examples/
|
||||
|
||||
num_workers: 5
|
||||
estimate_confidence: 0.80
|
||||
tasks:
|
||||
- id: DesignPhase
|
||||
min_time: 1.5 # 1.5 days
|
||||
max_time: 3.5 # 3.5 days
|
||||
dependencies: []
|
||||
- id: ImplementationPhase
|
||||
min_time: 2.25 # 2.25 days
|
||||
max_time: 4.75 # 4.75 days
|
||||
dependencies: [DesignPhase]
|
||||
|
||||
JSON Format:
|
||||
|
||||
{
|
||||
\"num_workers\": 5,
|
||||
\"estimate_confidence\": 0.80,
|
||||
\"tasks\": [
|
||||
{
|
||||
\"id\": \"DesignPhase\",
|
||||
\"min_time\": 1.5,
|
||||
\"max_time\": 3.5,
|
||||
\"dependencies\": []
|
||||
},
|
||||
{
|
||||
\"id\": \"ImplementationPhase\",
|
||||
\"min_time\": 2.25,
|
||||
\"max_time\": 4.75,
|
||||
\"dependencies\": [\"DesignPhase\"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Note: `estimate_confidence` is the confidence (0.0,1.0) that the
|
||||
actual values fall in the task estimate ranges. For example,
|
||||
0.8 means you believe that the actual time it will take to
|
||||
complete a task falls in your [min_time,max_time] estimate
|
||||
range for that task 80% of the time.";
|
||||
";
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let matches = Command::new("Monte Carlo Project Scheduler")
|
||||
.version("0.1.1")
|
||||
.version("0.3.0")
|
||||
.author("Stephen Waits <steve@waits.net>")
|
||||
.about("Runs Monte Carlo simulations on project schedules")
|
||||
.arg(
|
||||
Arg::new("filename")
|
||||
.help("Path to the schedule file (.yaml or .json).")
|
||||
.help("Path to the project file (.yaml or .json)")
|
||||
.required(true)
|
||||
.index(1),
|
||||
)
|
||||
@@ -68,15 +33,29 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
)
|
||||
.arg(
|
||||
Arg::new("workers")
|
||||
.short('w')
|
||||
.short('n')
|
||||
.long("workers")
|
||||
.help("Override `num_workers` specified in schedule file")
|
||||
.help("Override `num_workers` specified in project file")
|
||||
.value_name("num_workers"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("begin")
|
||||
.short('b')
|
||||
.long("begin")
|
||||
.help("Override `start_date` specified in project file")
|
||||
.value_name("YYYY-MM-DD"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("schedule")
|
||||
.short('s')
|
||||
.long("schedule")
|
||||
.help("Work schedule config file (.yaml or .json)")
|
||||
.value_name("filename"),
|
||||
)
|
||||
.after_help(AFTER_HELP_TEXT)
|
||||
.get_matches();
|
||||
|
||||
let schedule_path = matches.get_one::<String>("filename").unwrap();
|
||||
let project_path = matches.get_one::<String>("filename").unwrap();
|
||||
let num_simulations: usize = matches.get_one::<String>("iterations").unwrap().parse()?;
|
||||
|
||||
// Ensure iterations is >= 100
|
||||
@@ -84,8 +63,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
return Err("Iterations must be at least 100.".into());
|
||||
}
|
||||
|
||||
// Load the schedule
|
||||
let mut schedule = Schedule::from_file(schedule_path)?;
|
||||
// Load the project
|
||||
let mut project = Project::from_file(project_path)?;
|
||||
|
||||
// Load work schedule if it exists
|
||||
let calendar: WorkCalendar = match matches.get_one::<String>("schedule") {
|
||||
Some(filename) => WorkCalendar::from_str(&std::fs::read_to_string(filename)?)?,
|
||||
None => WorkCalendar::new(),
|
||||
};
|
||||
|
||||
// Determine the start date (command line > project file > TODAY)
|
||||
let start_date = matches
|
||||
.get_one::<String>("begin")
|
||||
.and_then(|date| NaiveDate::parse_from_str(date, "%Y-%m-%d").ok())
|
||||
.or(project.start_date)
|
||||
.unwrap_or_else(|| Utc::now().date_naive());
|
||||
|
||||
// Check if workers are overridden by command-line argument
|
||||
if let Some(workers_str) = matches.get_one::<String>("workers") {
|
||||
@@ -93,34 +85,39 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if workers < 1 {
|
||||
return Err("Invalid number of workers, must be 1 or more".into());
|
||||
}
|
||||
schedule.num_workers = workers;
|
||||
project.num_workers = workers;
|
||||
}
|
||||
|
||||
// Monte Carlo simulation
|
||||
let (project_durations, effort_times) = run_multiple_simulations(&schedule, num_simulations);
|
||||
let (project_durations, effort_times) = run_multiple_simulations(&project, num_simulations);
|
||||
|
||||
// Results output
|
||||
print_ascii_cdf(
|
||||
&project_durations,
|
||||
format!(
|
||||
"Project Completion Time with {} Worker{} (by probability)",
|
||||
schedule.num_workers,
|
||||
if schedule.num_workers == 1 { "" } else { "s" }
|
||||
"Completion Time ({} Worker{}, starting {})",
|
||||
project.num_workers,
|
||||
if project.num_workers == 1 { "" } else { "s" },
|
||||
start_date,
|
||||
)
|
||||
.as_str(),
|
||||
"Duration",
|
||||
&start_date,
|
||||
&calendar,
|
||||
);
|
||||
|
||||
println!();
|
||||
|
||||
print_ascii_cdf(
|
||||
&effort_times,
|
||||
"Total Work Effort (by probability)",
|
||||
"Effort",
|
||||
format!("Total Work Effort (1 worker, starting {})", start_date).as_str(),
|
||||
&start_date,
|
||||
&calendar,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_ascii_cdf(data: &[Duration], title: &str, units: &str) {
|
||||
fn print_ascii_cdf(data: &[Duration], title: &str, start: &NaiveDate, calendar: &WorkCalendar) {
|
||||
let mut sorted_data = data.to_vec();
|
||||
sorted_data.sort_unstable();
|
||||
let min = sorted_data[0];
|
||||
@@ -139,9 +136,9 @@ fn print_ascii_cdf(data: &[Duration], title: &str, units: &str) {
|
||||
padding_right = width - padding - title.len()
|
||||
);
|
||||
|
||||
println!("────┬────────────────────────────────────────────────────────────┬──────────");
|
||||
println!("%ile│{}│{:>10}", centered_title, units);
|
||||
println!("────┼────────────────────────────────────────────────────────────┼──────────");
|
||||
println!("────┬────────────────────────────────────────────────────────────┬──────────┬──────────┬──────────");
|
||||
println!("%ile│{}│ Workdays │ Schedule │ Complete ", centered_title);
|
||||
println!("────┼────────────────────────────────────────────────────────────┼──────────┼──────────┼──────────");
|
||||
|
||||
let bar_width = 60;
|
||||
|
||||
@@ -179,6 +176,9 @@ fn print_ascii_cdf(data: &[Duration], title: &str, units: &str) {
|
||||
let offset = (bar_width - (max_bar_position - min_bar_position)) / 2;
|
||||
|
||||
for (i, (bar_position, days)) in bar_positions.iter().enumerate() {
|
||||
let (end_date, calendar_duration) =
|
||||
calendar.compute_end_date(*start, *days as i64).unwrap();
|
||||
|
||||
let shifted_bar_position = bar_position + offset;
|
||||
|
||||
let (fg, bg) = if i % 2 == 0 {
|
||||
@@ -188,11 +188,12 @@ fn print_ascii_cdf(data: &[Duration], title: &str, units: &str) {
|
||||
};
|
||||
|
||||
let color_code = match 100 - i * 5 {
|
||||
0..=50 => "\x1b[31m", // Red
|
||||
55..=75 => "\x1b[33m", // Orange
|
||||
80..=90 => "\x1b[32m", // Green
|
||||
95..=100 => "\x1b[31m", // Red
|
||||
_ => "\x1b[0m", // Default (shouldn't happen)
|
||||
0..=45 => "\x1b[31m", // Red
|
||||
50..=65 => "\x1b[38;5;173m", // Orange
|
||||
70..=80 => "\x1b[33m", // Yellow
|
||||
85..=95 => "\x1b[32m", // Green
|
||||
96..=100 => "\x1b[33m", // Yellow
|
||||
_ => "\x1b[0m", // Default (shouldn't happen)
|
||||
};
|
||||
let reset_code = "\x1b[0m";
|
||||
|
||||
@@ -204,8 +205,20 @@ fn print_ascii_cdf(data: &[Duration], title: &str, units: &str) {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let trailing = match 100 - i * 5 {
|
||||
95 => "◀━┓",
|
||||
60 => " ┣━━━━━━━━━━━━┓",
|
||||
55 => " ┃ 90% ┃",
|
||||
50 => " ┃ Confidence ┃",
|
||||
45 => " ┃ Interval ┃",
|
||||
40 => " ┣━━━━━━━━━━━━┛",
|
||||
5 => "◀━┛",
|
||||
6..=94 => " ┃",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
println!(
|
||||
"{}{:>4}{}│{}{}{}│{}{:5.0} days{}",
|
||||
"{}{:>4}{}│{}{}{}│{}{:5.0} days{}│{}{:5.0} days{}│{}{}{}{}",
|
||||
color_code,
|
||||
format!("p{}", 100 - i * 5),
|
||||
reset_code,
|
||||
@@ -214,8 +227,15 @@ fn print_ascii_cdf(data: &[Duration], title: &str, units: &str) {
|
||||
reset_code,
|
||||
color_code,
|
||||
days,
|
||||
reset_code
|
||||
reset_code,
|
||||
color_code,
|
||||
calendar_duration.num_days(),
|
||||
reset_code,
|
||||
color_code,
|
||||
end_date,
|
||||
reset_code,
|
||||
trailing
|
||||
);
|
||||
}
|
||||
println!("────┴────────────────────────────────────────────────────────────┴──────────");
|
||||
println!("────┴────────────────────────────────────────────────────────────┴──────────┴──────────┴──────────");
|
||||
}
|
||||
+41
-38
@@ -1,25 +1,27 @@
|
||||
use crate::task::Task;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Schedule {
|
||||
pub struct Project {
|
||||
pub tasks: Vec<Task>,
|
||||
pub num_workers: usize,
|
||||
pub estimate_confidence: f64,
|
||||
pub start_date: Option<NaiveDate>,
|
||||
}
|
||||
|
||||
impl Schedule {
|
||||
impl Project {
|
||||
pub fn new(
|
||||
tasks: Vec<Task>,
|
||||
num_workers: usize,
|
||||
estimate_confidence: f64,
|
||||
start_date: Option<NaiveDate>,
|
||||
) -> Result<Self, String> {
|
||||
let schedule = Schedule {
|
||||
let schedule = Project {
|
||||
tasks,
|
||||
num_workers,
|
||||
estimate_confidence,
|
||||
start_date,
|
||||
};
|
||||
schedule.validate()?;
|
||||
Ok(schedule)
|
||||
@@ -94,11 +96,12 @@ impl Schedule {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn create_task(id: &str, min: u64, max: u64, deps: Vec<&str>) -> Task {
|
||||
fn create_task(id: &str, min: u64, likely: u64, max: u64, deps: Vec<&str>) -> Task {
|
||||
Task {
|
||||
id: id.to_string(),
|
||||
dependencies: deps.into_iter().map(String::from).collect(),
|
||||
min_time: Duration::from_secs(min),
|
||||
likely_time: Duration::from_secs(likely),
|
||||
max_time: Duration::from_secs(max),
|
||||
}
|
||||
}
|
||||
@@ -106,25 +109,25 @@ mod tests {
|
||||
#[test]
|
||||
fn test_valid_schedule() {
|
||||
let tasks = vec![
|
||||
create_task("A", 1, 3, vec![]),
|
||||
create_task("B", 2, 4, vec!["A"]),
|
||||
create_task("C", 3, 5, vec!["A"]),
|
||||
create_task("A", 1, 2, 3, vec![]),
|
||||
create_task("B", 2, 3, 4, vec!["A"]),
|
||||
create_task("C", 3, 4, 5, vec!["A"]),
|
||||
];
|
||||
let schedule = Schedule::new(tasks, 2, 0.8);
|
||||
let schedule = Project::new(tasks, 2, None);
|
||||
assert!(schedule.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_task_list() {
|
||||
let schedule = Schedule::new(vec![], 2, 0.8);
|
||||
let schedule = Project::new(vec![], 2, None);
|
||||
assert!(schedule.is_err());
|
||||
assert_eq!(schedule.unwrap_err(), "Empty task list");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_task_duration() {
|
||||
let tasks = vec![create_task("A", 3, 1, vec![])];
|
||||
let schedule = Schedule::new(tasks, 2, 0.8);
|
||||
let tasks = vec![create_task("A", 3, 2, 1, vec![])];
|
||||
let schedule = Project::new(tasks, 2, None);
|
||||
assert!(schedule.is_err());
|
||||
assert_eq!(
|
||||
schedule.unwrap_err(),
|
||||
@@ -134,8 +137,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_zero_duration() {
|
||||
let tasks = vec![create_task("A", 0, 1, vec![])];
|
||||
let schedule = Schedule::new(tasks, 2, 0.8);
|
||||
let tasks = vec![create_task("A", 0, 1, 2, vec![])];
|
||||
let schedule = Project::new(tasks, 2, None);
|
||||
assert!(schedule.is_err());
|
||||
assert_eq!(schedule.unwrap_err(), "Invalid task duration for task A");
|
||||
}
|
||||
@@ -143,10 +146,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_missing_dependency() {
|
||||
let tasks = vec![
|
||||
create_task("A", 1, 3, vec![]),
|
||||
create_task("B", 2, 4, vec!["C"]),
|
||||
create_task("A", 1, 2, 3, vec![]),
|
||||
create_task("B", 2, 3, 4, vec!["C"]),
|
||||
];
|
||||
let schedule = Schedule::new(tasks, 2, 0.8);
|
||||
let schedule = Project::new(tasks, 2, None);
|
||||
assert!(schedule.is_err());
|
||||
assert_eq!(schedule.unwrap_err(), "Missing dependency C for task B");
|
||||
}
|
||||
@@ -154,10 +157,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_cyclic_dependency() {
|
||||
let tasks = vec![
|
||||
create_task("A", 1, 3, vec!["B"]),
|
||||
create_task("B", 2, 4, vec!["A"]),
|
||||
create_task("A", 1, 2, 3, vec!["B"]),
|
||||
create_task("B", 2, 3, 4, vec!["A"]),
|
||||
];
|
||||
let schedule = Schedule::new(tasks, 2, 0.8);
|
||||
let schedule = Project::new(tasks, 2, None);
|
||||
assert!(schedule.is_err());
|
||||
assert!(schedule
|
||||
.unwrap_err()
|
||||
@@ -167,28 +170,28 @@ mod tests {
|
||||
#[test]
|
||||
fn test_complex_valid_schedule() {
|
||||
let tasks = vec![
|
||||
create_task("A", 1, 3, vec![]),
|
||||
create_task("B", 2, 4, vec!["A"]),
|
||||
create_task("C", 3, 5, vec!["A"]),
|
||||
create_task("D", 2, 6, vec!["B", "C"]),
|
||||
create_task("E", 1, 2, vec!["A"]),
|
||||
create_task("F", 2, 4, vec!["D", "E"]),
|
||||
create_task("A", 1, 2, 3, vec![]),
|
||||
create_task("B", 2, 3, 4, vec!["A"]),
|
||||
create_task("C", 3, 4, 5, vec!["A"]),
|
||||
create_task("D", 2, 4, 6, vec!["B", "C"]),
|
||||
create_task("E", 1, 2, 4, vec!["A"]),
|
||||
create_task("F", 2, 3, 4, vec!["D", "E"]),
|
||||
];
|
||||
let schedule = Schedule::new(tasks, 3, 0.9);
|
||||
let schedule = Project::new(tasks, 3, None);
|
||||
assert!(schedule.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_cyclic_dependency() {
|
||||
let tasks = vec![
|
||||
create_task("A", 1, 3, vec!["F"]),
|
||||
create_task("B", 2, 4, vec!["A"]),
|
||||
create_task("C", 3, 5, vec!["B"]),
|
||||
create_task("D", 2, 6, vec!["C"]),
|
||||
create_task("E", 1, 2, vec!["D"]),
|
||||
create_task("F", 2, 4, vec!["E"]),
|
||||
create_task("A", 1, 2, 3, vec!["F"]),
|
||||
create_task("B", 2, 3, 4, vec!["A"]),
|
||||
create_task("C", 3, 4, 5, vec!["B"]),
|
||||
create_task("D", 2, 3, 6, vec!["C"]),
|
||||
create_task("E", 1, 2, 3, vec!["D"]),
|
||||
create_task("F", 2, 3, 4, vec!["E"]),
|
||||
];
|
||||
let schedule = Schedule::new(tasks, 3, 0.9);
|
||||
let schedule = Project::new(tasks, 3, None);
|
||||
assert!(
|
||||
schedule.is_err(),
|
||||
"Expected cyclic dependency error, but got Ok"
|
||||
@@ -204,8 +207,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_self_dependency() {
|
||||
let tasks = vec![create_task("A", 1, 3, vec!["A"])];
|
||||
let schedule = Schedule::new(tasks, 1, 0.8);
|
||||
let tasks = vec![create_task("A", 1, 2, 3, vec!["A"])];
|
||||
let schedule = Project::new(tasks, 1, None);
|
||||
assert!(
|
||||
schedule.is_err(),
|
||||
"Expected self-dependency error, but got Ok"
|
||||
|
||||
+55
-35
@@ -1,5 +1,6 @@
|
||||
use crate::{schedule::Schedule, task::Task};
|
||||
use crate::{schedule::Project, task::Task};
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::{fs::File, io::Read, path::Path, time::Duration};
|
||||
@@ -7,15 +8,23 @@ use std::{fs::File, io::Read, path::Path, time::Duration};
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct ScheduleInput {
|
||||
num_workers: usize,
|
||||
estimate_confidence: f64,
|
||||
start_date: Option<NaiveDate>,
|
||||
tasks: Vec<TaskInput>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct EstimateInput {
|
||||
min: f64,
|
||||
max: f64,
|
||||
likely: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct TaskInput {
|
||||
id: String,
|
||||
min_time: f64,
|
||||
max_time: f64,
|
||||
name: Option<String>,
|
||||
description: Option<String>,
|
||||
estimate: EstimateInput,
|
||||
dependencies: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -24,7 +33,7 @@ enum FileFormat {
|
||||
Json,
|
||||
}
|
||||
|
||||
impl Schedule {
|
||||
impl Project {
|
||||
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let mut file = File::open(&path)?;
|
||||
let mut contents = String::new();
|
||||
@@ -41,13 +50,14 @@ impl Schedule {
|
||||
.into_iter()
|
||||
.map(|t| Task {
|
||||
id: t.id,
|
||||
min_time: Duration::from_secs_f64(t.min_time * 24.0 * 60.0 * 60.0),
|
||||
max_time: Duration::from_secs_f64(t.max_time * 24.0 * 60.0 * 60.0),
|
||||
min_time: Duration::from_secs_f64(t.estimate.min * 24.0 * 60.0 * 60.0),
|
||||
likely_time: Duration::from_secs_f64(t.estimate.likely * 24.0 * 60.0 * 60.0),
|
||||
max_time: Duration::from_secs_f64(t.estimate.max * 24.0 * 60.0 * 60.0),
|
||||
dependencies: t.dependencies,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let schedule = Schedule::new(tasks, input.num_workers, input.estimate_confidence)?;
|
||||
let schedule = Project::new(tasks, input.num_workers, input.start_date)?;
|
||||
|
||||
schedule.validate()?;
|
||||
|
||||
@@ -89,22 +99,24 @@ mod tests {
|
||||
fn test_load_from_yaml() {
|
||||
let yaml_content = r#"
|
||||
num_workers: 3
|
||||
estimate_confidence: 0.9
|
||||
tasks:
|
||||
- id: A
|
||||
min_time: 1
|
||||
max_time: 3
|
||||
estimate:
|
||||
min: 1
|
||||
likely: 2.4
|
||||
max: 3
|
||||
dependencies: []
|
||||
- id: B
|
||||
min_time: 2
|
||||
max_time: 4
|
||||
estimate:
|
||||
min: 2
|
||||
likely: 2.4
|
||||
max: 4
|
||||
dependencies: [A]
|
||||
"#;
|
||||
let (_temp_file, path) = create_temp_file(yaml_content, "yaml");
|
||||
let schedule = Schedule::from_file(path).unwrap();
|
||||
let schedule = Project::from_file(path).unwrap();
|
||||
assert_eq!(schedule.tasks.len(), 2);
|
||||
assert_eq!(schedule.num_workers, 3);
|
||||
assert_eq!(schedule.estimate_confidence, 0.9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -112,35 +124,39 @@ tasks:
|
||||
let json_content = r#"
|
||||
{
|
||||
"num_workers": 3,
|
||||
"estimate_confidence": 0.9,
|
||||
"tasks": [
|
||||
{
|
||||
"id": "A",
|
||||
"min_time": 1,
|
||||
"max_time": 3,
|
||||
"estimate": {
|
||||
"min": 1,
|
||||
"likely": 1.8,
|
||||
"max": 3
|
||||
},
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"id": "B",
|
||||
"min_time": 2,
|
||||
"max_time": 4,
|
||||
"estimate": {
|
||||
"min": 2,
|
||||
"likely": 1.8,
|
||||
"max": 4
|
||||
},
|
||||
"dependencies": ["A"]
|
||||
}
|
||||
]
|
||||
}
|
||||
"#;
|
||||
let (_temp_file, path) = create_temp_file(json_content, "json");
|
||||
let schedule = Schedule::from_file(path).unwrap();
|
||||
let schedule = Project::from_file(path).unwrap();
|
||||
assert_eq!(schedule.tasks.len(), 2);
|
||||
assert_eq!(schedule.num_workers, 3);
|
||||
assert_eq!(schedule.estimate_confidence, 0.9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unsupported_format() {
|
||||
let content = "irrelevant content";
|
||||
let (_temp_file, path) = create_temp_file(content, "txt");
|
||||
let result = Schedule::from_file(path);
|
||||
let result = Project::from_file(path);
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
@@ -152,15 +168,16 @@ tasks:
|
||||
fn test_invalid_schedule() {
|
||||
let yaml_content = r#"
|
||||
num_workers: 3
|
||||
estimate_confidence: 0.9
|
||||
tasks:
|
||||
- id: A
|
||||
min_time: 3
|
||||
max_time: 1 # Invalid: min_time > max_time
|
||||
estimate:
|
||||
min: 3
|
||||
likely: 2
|
||||
max: 1 # Invalid: min_time > max_time
|
||||
dependencies: []
|
||||
"#;
|
||||
let (_temp_file, path) = create_temp_file(yaml_content, "yaml");
|
||||
let result = Schedule::from_file(path);
|
||||
let result = Project::from_file(path);
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
@@ -172,15 +189,16 @@ tasks:
|
||||
fn test_load_with_floating_point_times_yaml() {
|
||||
let yaml_content = r#"
|
||||
num_workers: 3
|
||||
estimate_confidence: 0.9
|
||||
tasks:
|
||||
- id: A
|
||||
min_time: 1.5 # 1.5 days
|
||||
max_time: 3.5 # 3.5 days
|
||||
estimate:
|
||||
min: 1.5 # 1.5 days
|
||||
likely: 2 # 2.0 days
|
||||
max: 3.5 # 3.5 days
|
||||
dependencies: []
|
||||
"#;
|
||||
let (_temp_file, path) = create_temp_file(yaml_content, "yaml");
|
||||
let schedule = Schedule::from_file(path).unwrap();
|
||||
let schedule = Project::from_file(path).unwrap();
|
||||
|
||||
// Verify that there is one task and it has the correct times
|
||||
assert_eq!(schedule.tasks.len(), 1);
|
||||
@@ -199,19 +217,21 @@ tasks:
|
||||
let json_content = r#"
|
||||
{
|
||||
"num_workers": 3,
|
||||
"estimate_confidence": 0.9,
|
||||
"tasks": [
|
||||
{
|
||||
"id": "A",
|
||||
"min_time": 1.5,
|
||||
"max_time": 3.5,
|
||||
"estimate": {
|
||||
"min": 1.5,
|
||||
"likely": 3,
|
||||
"max": 3.5
|
||||
},
|
||||
"dependencies": []
|
||||
}
|
||||
]
|
||||
}
|
||||
"#;
|
||||
let (_temp_file, path) = create_temp_file(json_content, "json");
|
||||
let schedule = Schedule::from_file(path).unwrap();
|
||||
let schedule = Project::from_file(path).unwrap();
|
||||
|
||||
// Verify that there is one task and it has the correct times
|
||||
assert_eq!(schedule.tasks.len(), 1);
|
||||
|
||||
+62
-34
@@ -1,7 +1,7 @@
|
||||
use crate::schedule::Schedule;
|
||||
use crate::schedule::Project;
|
||||
|
||||
use distimate::Pert;
|
||||
use rand::prelude::*;
|
||||
use rand_distr::{Distribution, Normal};
|
||||
use rayon::prelude::*;
|
||||
|
||||
use std::{
|
||||
@@ -9,13 +9,14 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SimulationResult {
|
||||
pub total_project_duration: Duration,
|
||||
pub total_effort_time: Duration,
|
||||
}
|
||||
|
||||
pub fn run_multiple_simulations(
|
||||
schedule: &Schedule,
|
||||
schedule: &Project,
|
||||
num_simulations: usize,
|
||||
) -> (Vec<Duration>, Vec<Duration>) {
|
||||
(0..num_simulations)
|
||||
@@ -27,7 +28,7 @@ pub fn run_multiple_simulations(
|
||||
.unzip()
|
||||
}
|
||||
|
||||
fn run_simulation(schedule: &Schedule) -> SimulationResult {
|
||||
fn run_simulation(schedule: &Project) -> SimulationResult {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
// Simulate task times
|
||||
@@ -35,12 +36,7 @@ fn run_simulation(schedule: &Schedule) -> SimulationResult {
|
||||
.tasks
|
||||
.iter()
|
||||
.map(|task| {
|
||||
let task_time = simulate_task_time(
|
||||
&mut rng,
|
||||
task.min_time,
|
||||
task.max_time,
|
||||
schedule.estimate_confidence,
|
||||
);
|
||||
let task_time = simulate_task_time(&mut rng, task.min_time, task.max_time);
|
||||
(&task.id, task_time)
|
||||
})
|
||||
.collect();
|
||||
@@ -117,21 +113,13 @@ fn run_simulation(schedule: &Schedule) -> SimulationResult {
|
||||
}
|
||||
}
|
||||
|
||||
fn simulate_task_time(
|
||||
rng: &mut impl Rng,
|
||||
min_time: Duration,
|
||||
max_time: Duration,
|
||||
estimate_confidence: f64,
|
||||
) -> Duration {
|
||||
fn simulate_task_time(rng: &mut impl Rng, min_time: Duration, max_time: Duration) -> Duration {
|
||||
let min_secs = min_time.as_secs_f64();
|
||||
let max_secs = max_time.as_secs_f64();
|
||||
let mean = (min_secs + max_secs) / 2.0;
|
||||
|
||||
let z_score = (1.0 - (1.0 - estimate_confidence) / 2.0).sqrt() * 2.0;
|
||||
let std_dev = (max_secs - min_secs) / (2.0 * z_score);
|
||||
|
||||
let normal = Normal::new(mean, std_dev).unwrap();
|
||||
let sampled_secs = normal.sample(rng).clamp(min_secs, max_secs);
|
||||
let pert = Pert::new(min_secs, mean, max_secs).unwrap();
|
||||
let sampled_secs = pert.sample(rng);
|
||||
|
||||
Duration::from_secs_f64(sampled_secs)
|
||||
}
|
||||
@@ -146,10 +134,9 @@ mod tests {
|
||||
let mut rng = thread_rng();
|
||||
let min_time = Duration::from_secs(5);
|
||||
let max_time = Duration::from_secs(15);
|
||||
let estimate_confidence = 0.8;
|
||||
|
||||
for _ in 0..1000 {
|
||||
let time = simulate_task_time(&mut rng, min_time, max_time, estimate_confidence);
|
||||
let time = simulate_task_time(&mut rng, min_time, max_time);
|
||||
assert!(
|
||||
time >= min_time && time <= max_time,
|
||||
"Simulated time should be within range"
|
||||
@@ -159,8 +146,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_single_task_simulation() {
|
||||
let task = Task::new("A", vec![], Duration::from_secs(5), Duration::from_secs(15));
|
||||
let schedule = Schedule::new(vec![task], 1, 0.8).unwrap();
|
||||
let task = Task::new(
|
||||
"A",
|
||||
vec![],
|
||||
Duration::from_secs(5),
|
||||
Duration::from_secs(10),
|
||||
Duration::from_secs(15),
|
||||
);
|
||||
let schedule = Project::new(vec![task], 1, None).unwrap();
|
||||
|
||||
let result = run_simulation(&schedule);
|
||||
|
||||
@@ -178,11 +171,29 @@ mod tests {
|
||||
#[test]
|
||||
fn test_multiple_independent_tasks() {
|
||||
let tasks = vec![
|
||||
Task::new("A", vec![], Duration::from_secs(5), Duration::from_secs(10)),
|
||||
Task::new("B", vec![], Duration::from_secs(7), Duration::from_secs(12)),
|
||||
Task::new("C", vec![], Duration::from_secs(3), Duration::from_secs(8)),
|
||||
Task::new(
|
||||
"A",
|
||||
vec![],
|
||||
Duration::from_secs(5),
|
||||
Duration::from_secs(6),
|
||||
Duration::from_secs(10),
|
||||
),
|
||||
Task::new(
|
||||
"B",
|
||||
vec![],
|
||||
Duration::from_secs(7),
|
||||
Duration::from_secs(10),
|
||||
Duration::from_secs(12),
|
||||
),
|
||||
Task::new(
|
||||
"C",
|
||||
vec![],
|
||||
Duration::from_secs(3),
|
||||
Duration::from_secs(6),
|
||||
Duration::from_secs(8),
|
||||
),
|
||||
];
|
||||
let schedule = Schedule::new(tasks, 2, 0.8).unwrap();
|
||||
let schedule = Project::new(tasks, 2, None).unwrap();
|
||||
|
||||
let result = run_simulation(&schedule);
|
||||
|
||||
@@ -199,32 +210,42 @@ mod tests {
|
||||
#[test]
|
||||
fn test_tasks_with_dependencies() {
|
||||
let tasks = vec![
|
||||
Task::new("A", vec![], Duration::from_secs(5), Duration::from_secs(10)),
|
||||
Task::new(
|
||||
"A",
|
||||
vec![],
|
||||
Duration::from_secs(5),
|
||||
Duration::from_secs(8),
|
||||
Duration::from_secs(10),
|
||||
),
|
||||
Task::new(
|
||||
"B",
|
||||
vec!["A".to_string()],
|
||||
Duration::from_secs(7),
|
||||
Duration::from_secs(9),
|
||||
Duration::from_secs(12),
|
||||
),
|
||||
Task::new(
|
||||
"C",
|
||||
vec!["A".to_string()],
|
||||
Duration::from_secs(3),
|
||||
Duration::from_secs(6),
|
||||
Duration::from_secs(8),
|
||||
),
|
||||
Task::new(
|
||||
"D",
|
||||
vec!["B".to_string(), "C".to_string()],
|
||||
Duration::from_secs(4),
|
||||
Duration::from_secs(7),
|
||||
Duration::from_secs(9),
|
||||
),
|
||||
];
|
||||
let schedule = Schedule::new(tasks, 2, 0.8).unwrap();
|
||||
let schedule = Project::new(tasks, 2, None).unwrap();
|
||||
|
||||
let result = run_simulation(&schedule);
|
||||
println!("{:?}", result);
|
||||
|
||||
assert!(
|
||||
result.total_project_duration >= Duration::from_secs(14),
|
||||
result.total_project_duration >= Duration::from_secs(13),
|
||||
"Project duration should be at least the minimum critical path duration"
|
||||
);
|
||||
}
|
||||
@@ -232,15 +253,22 @@ mod tests {
|
||||
#[test]
|
||||
fn test_multiple_simulations_consistency() {
|
||||
let tasks = vec![
|
||||
Task::new("A", vec![], Duration::from_secs(5), Duration::from_secs(10)),
|
||||
Task::new(
|
||||
"A",
|
||||
vec![],
|
||||
Duration::from_secs(5),
|
||||
Duration::from_secs(8),
|
||||
Duration::from_secs(10),
|
||||
),
|
||||
Task::new(
|
||||
"B",
|
||||
vec!["A".to_string()],
|
||||
Duration::from_secs(7),
|
||||
Duration::from_secs(10),
|
||||
Duration::from_secs(12),
|
||||
),
|
||||
];
|
||||
let schedule = Schedule::new(tasks, 1, 0.8).unwrap();
|
||||
let schedule = Project::new(tasks, 1, None).unwrap();
|
||||
|
||||
let (durations, efforts) = run_multiple_simulations(&schedule, 1000);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ pub struct Task {
|
||||
pub id: String,
|
||||
pub dependencies: Vec<String>,
|
||||
pub min_time: Duration,
|
||||
pub likely_time: Duration,
|
||||
pub max_time: Duration,
|
||||
}
|
||||
|
||||
@@ -13,12 +14,14 @@ impl Task {
|
||||
id: &str,
|
||||
dependencies: Vec<String>,
|
||||
min_time: Duration,
|
||||
likely_time: Duration,
|
||||
max_time: Duration,
|
||||
) -> Self {
|
||||
Task {
|
||||
id: id.to_string(),
|
||||
dependencies,
|
||||
min_time,
|
||||
likely_time,
|
||||
max_time,
|
||||
}
|
||||
}
|
||||
@@ -51,6 +54,7 @@ mod tests {
|
||||
"Task1",
|
||||
vec!["Dep1".to_string(), "Dep2".to_string()],
|
||||
Duration::from_secs(1),
|
||||
Duration::from_secs(3),
|
||||
Duration::from_secs(5),
|
||||
);
|
||||
|
||||
@@ -66,6 +70,7 @@ mod tests {
|
||||
"Task2",
|
||||
vec![],
|
||||
Duration::from_secs(2),
|
||||
Duration::from_secs(7),
|
||||
Duration::from_secs(10),
|
||||
);
|
||||
|
||||
@@ -82,6 +87,7 @@ mod tests {
|
||||
vec!["Dep3".to_string()],
|
||||
Duration::from_secs(5),
|
||||
Duration::from_secs(5),
|
||||
Duration::from_secs(5),
|
||||
);
|
||||
|
||||
assert_eq!(task.id, "Task3");
|
||||
@@ -95,6 +101,7 @@ mod tests {
|
||||
"Task4",
|
||||
vec!["Dep4".to_string()],
|
||||
Duration::from_secs(3),
|
||||
Duration::from_secs(5),
|
||||
Duration::from_secs(7),
|
||||
);
|
||||
let task2 = task1.clone();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use mcps::{
|
||||
schedule::Schedule,
|
||||
schedule::Project,
|
||||
simulation::run_multiple_simulations,
|
||||
task::{days_to_duration, Task},
|
||||
};
|
||||
@@ -12,7 +12,6 @@ fn test_average_effort_calculation() {
|
||||
let min_days = 5.0;
|
||||
let max_days = 15.0;
|
||||
let expected_avg_days = 10.0;
|
||||
let estimate_confidence = 0.8;
|
||||
let num_simulations = 10000;
|
||||
let num_workers = 1; // Using 1 worker to simplify effort calculation
|
||||
|
||||
@@ -23,13 +22,13 @@ fn test_average_effort_calculation() {
|
||||
&format!("Task{}", i),
|
||||
vec![],
|
||||
days_to_duration(min_days),
|
||||
days_to_duration(0.5 * (min_days + max_days)),
|
||||
days_to_duration(max_days),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let schedule =
|
||||
Schedule::new(tasks, num_workers, estimate_confidence).expect("Failed to create schedule");
|
||||
let schedule = Project::new(tasks, num_workers, None).expect("Failed to create schedule");
|
||||
|
||||
let (_, effort_times) = run_multiple_simulations(&schedule, num_simulations);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use mcps::{
|
||||
schedule::Schedule,
|
||||
schedule::Project,
|
||||
task::{days_to_duration, Task},
|
||||
};
|
||||
|
||||
fn create_schedule_and_expect_error(tasks: Vec<Task>, expected_error: &str) {
|
||||
let result = Schedule::new(tasks, 1, 0.8);
|
||||
let result = Project::new(tasks, 1, None);
|
||||
|
||||
assert!(
|
||||
result.is_err(),
|
||||
@@ -27,12 +27,14 @@ fn test_cyclic_dependency() {
|
||||
"A",
|
||||
vec!["B".to_string()],
|
||||
days_to_duration(1.0),
|
||||
days_to_duration(1.5),
|
||||
days_to_duration(2.0),
|
||||
),
|
||||
Task::new(
|
||||
"B",
|
||||
vec!["A".to_string()],
|
||||
days_to_duration(1.0),
|
||||
days_to_duration(1.5),
|
||||
days_to_duration(2.0),
|
||||
),
|
||||
];
|
||||
@@ -47,12 +49,14 @@ fn test_missing_dependency() {
|
||||
"A",
|
||||
vec!["C".to_string()],
|
||||
days_to_duration(1.0),
|
||||
days_to_duration(1.5),
|
||||
days_to_duration(2.0),
|
||||
),
|
||||
Task::new(
|
||||
"B",
|
||||
vec!["A".to_string()],
|
||||
days_to_duration(1.0),
|
||||
days_to_duration(1.5),
|
||||
days_to_duration(2.0),
|
||||
),
|
||||
];
|
||||
@@ -66,6 +70,7 @@ fn test_negative_duration() {
|
||||
"A",
|
||||
vec![],
|
||||
days_to_duration(0.0),
|
||||
days_to_duration(0.5),
|
||||
days_to_duration(1.0),
|
||||
)];
|
||||
|
||||
@@ -79,6 +84,7 @@ fn test_min_greater_than_max() {
|
||||
vec![],
|
||||
days_to_duration(3.0),
|
||||
days_to_duration(2.0),
|
||||
days_to_duration(2.0),
|
||||
)];
|
||||
|
||||
create_schedule_and_expect_error(tasks, "Minimum duration greater than maximum");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use mcps::{
|
||||
schedule::Schedule,
|
||||
schedule::Project,
|
||||
simulation::run_multiple_simulations,
|
||||
task::{days_to_duration, Task},
|
||||
};
|
||||
@@ -13,6 +13,7 @@ fn create_independent_tasks(num_tasks: usize) -> Vec<Task> {
|
||||
&format!("Task_{}", i),
|
||||
vec![],
|
||||
days_to_duration(1.0),
|
||||
days_to_duration(2.0),
|
||||
days_to_duration(3.0),
|
||||
)
|
||||
})
|
||||
@@ -23,14 +24,13 @@ fn create_independent_tasks(num_tasks: usize) -> Vec<Task> {
|
||||
fn test_no_dependencies_schedule() {
|
||||
let num_tasks = 32;
|
||||
let tasks = create_independent_tasks(num_tasks);
|
||||
let estimate_confidence = 0.8;
|
||||
let num_simulations = 1_000;
|
||||
|
||||
let mut prev_avg_effort = 0.0;
|
||||
|
||||
for num_workers in [1, 2, 4, 8] {
|
||||
let schedule = Schedule::new(tasks.clone(), num_workers, estimate_confidence)
|
||||
.expect("Failed to create schedule");
|
||||
let schedule =
|
||||
Project::new(tasks.clone(), num_workers, None).expect("Failed to create schedule");
|
||||
let (project_durations, effort_times) =
|
||||
run_multiple_simulations(&schedule, num_simulations);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use mcps::{
|
||||
schedule::Schedule,
|
||||
schedule::Project,
|
||||
simulation::run_multiple_simulations,
|
||||
task::{days_to_duration, Task},
|
||||
};
|
||||
@@ -9,47 +9,57 @@ use std::time::Duration;
|
||||
#[test]
|
||||
fn test_simulation_with_different_worker_counts() {
|
||||
let tasks = vec![
|
||||
Task::new("A", vec![], days_to_duration(1.0), days_to_duration(3.0)),
|
||||
Task::new(
|
||||
"A",
|
||||
vec![],
|
||||
days_to_duration(1.0),
|
||||
days_to_duration(2.0),
|
||||
days_to_duration(3.0),
|
||||
),
|
||||
Task::new(
|
||||
"B",
|
||||
vec!["A".to_string()],
|
||||
days_to_duration(2.0),
|
||||
days_to_duration(3.0),
|
||||
days_to_duration(4.0),
|
||||
),
|
||||
Task::new(
|
||||
"C",
|
||||
vec!["A".to_string()],
|
||||
days_to_duration(3.0),
|
||||
days_to_duration(4.0),
|
||||
days_to_duration(5.0),
|
||||
),
|
||||
Task::new(
|
||||
"D",
|
||||
vec!["B".to_string(), "C".to_string()],
|
||||
days_to_duration(2.0),
|
||||
days_to_duration(4.0),
|
||||
days_to_duration(6.0),
|
||||
),
|
||||
Task::new(
|
||||
"E",
|
||||
vec!["B".to_string()],
|
||||
days_to_duration(1.0),
|
||||
days_to_duration(1.5),
|
||||
days_to_duration(2.0),
|
||||
),
|
||||
Task::new(
|
||||
"F",
|
||||
vec!["D".to_string(), "E".to_string()],
|
||||
days_to_duration(2.0),
|
||||
days_to_duration(3.0),
|
||||
days_to_duration(4.0),
|
||||
),
|
||||
];
|
||||
|
||||
let estimate_confidence = 0.8;
|
||||
let num_simulations = 1000;
|
||||
|
||||
let mut all_efforts = Vec::new();
|
||||
|
||||
for num_workers in [1, 2, 4, 8, 16, 32] {
|
||||
let schedule = Schedule::new(tasks.clone(), num_workers, estimate_confidence)
|
||||
.expect("Failed to create schedule");
|
||||
let schedule =
|
||||
Project::new(tasks.clone(), num_workers, None).expect("Failed to create schedule");
|
||||
let (project_durations, effort_times) =
|
||||
run_multiple_simulations(&schedule, num_simulations);
|
||||
|
||||
|
||||
Reference in new issue
Block a user